From 1d19da0c76cca65b1b5b6916345486726b96e188 Mon Sep 17 00:00:00 2001 From: Jarno Date: Wed, 31 Jul 2024 11:50:04 +0200 Subject: [PATCH 01/98] Timespinner: migrate to new options api and correct random (#2485) * Implemented new options system into Timespinner * Fixed typo * Fixed typo * Fixed slotdata maybe * Fixes * more fixes * Fixed failing unit tests * Implemented options backwards comnpatibility * Fixed option fallbacks * Implemented review results * Fixed logic bug * Fixed python 3.8/3.9 compatibility * Replaced one more multiworld option usage * Update worlds/timespinner/Options.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Updated logging of options replacement to include player name and also write it to spoiler Fixed generation bug Implemented review results --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/timespinner/Locations.py | 21 +- worlds/timespinner/LogicExtensions.py | 23 +- worlds/timespinner/Options.py | 343 +++++++++++++++------ worlds/timespinner/PreCalculatedWeights.py | 68 ++-- worlds/timespinner/Regions.py | 24 +- worlds/timespinner/__init__.py | 185 ++++++----- 6 files changed, 424 insertions(+), 240 deletions(-) diff --git a/worlds/timespinner/Locations.py b/worlds/timespinner/Locations.py index 7b378b4637fa..86839f0f2167 100644 --- a/worlds/timespinner/Locations.py +++ b/worlds/timespinner/Locations.py @@ -1,6 +1,6 @@ from typing import List, Optional, Callable, NamedTuple -from BaseClasses import MultiWorld, CollectionState -from .Options import is_option_enabled +from BaseClasses import CollectionState +from .Options import TimespinnerOptions from .PreCalculatedWeights import PreCalculatedWeights from .LogicExtensions import TimespinnerLogic @@ -14,11 +14,10 @@ class LocationData(NamedTuple): rule: Optional[Callable[[CollectionState], bool]] = None -def get_location_datas(world: Optional[MultiWorld], player: Optional[int], - precalculated_weights: PreCalculatedWeights) -> List[LocationData]: - - flooded: PreCalculatedWeights = precalculated_weights - logic = TimespinnerLogic(world, player, precalculated_weights) +def get_location_datas(player: Optional[int], options: Optional[TimespinnerOptions], + precalculated_weights: Optional[PreCalculatedWeights]) -> List[LocationData]: + flooded: Optional[PreCalculatedWeights] = precalculated_weights + logic = TimespinnerLogic(player, options, precalculated_weights) # 1337000 - 1337155 Generic locations # 1337171 - 1337175 New Pickup checks @@ -203,7 +202,7 @@ def get_location_datas(world: Optional[MultiWorld], player: Optional[int], ] # 1337156 - 1337170 Downloads - if not world or is_option_enabled(world, player, "DownloadableItems"): + if not options or options.downloadable_items: location_table += ( LocationData('Library', 'Library: Terminal 2 (Lachiem)', 1337156, lambda state: state.has('Tablet', player)), LocationData('Library', 'Library: Terminal 1 (Windaria)', 1337157, lambda state: state.has('Tablet', player)), @@ -223,13 +222,13 @@ def get_location_datas(world: Optional[MultiWorld], player: Optional[int], ) # 1337176 - 1337176 Cantoran - if not world or is_option_enabled(world, player, "Cantoran"): + if not options or options.cantoran: location_table += ( LocationData('Left Side forest Caves', 'Lake Serene: Cantoran', 1337176), ) # 1337177 - 1337198 Lore Checks - if not world or is_option_enabled(world, player, "LoreChecks"): + if not options or options.lore_checks: location_table += ( LocationData('Lower lake desolation', 'Lake Desolation: Memory - Coyote Jump (Time Messenger)', 1337177), LocationData('Library', 'Library: Memory - Waterway (A Message)', 1337178), @@ -258,7 +257,7 @@ def get_location_datas(world: Optional[MultiWorld], player: Optional[int], # 1337199 - 1337236 Reserved for future use # 1337237 - 1337245 GyreArchives - if not world or is_option_enabled(world, player, "GyreArchives"): + if not options or options.gyre_archives: location_table += ( LocationData('Ravenlord\'s Lair', 'Ravenlord: Post fight (pedestal)', 1337237), LocationData('Ifrit\'s Lair', 'Ifrit: Post fight (pedestal)', 1337238), diff --git a/worlds/timespinner/LogicExtensions.py b/worlds/timespinner/LogicExtensions.py index d316a936b02f..6c9cb3f684a0 100644 --- a/worlds/timespinner/LogicExtensions.py +++ b/worlds/timespinner/LogicExtensions.py @@ -1,6 +1,6 @@ -from typing import Union -from BaseClasses import MultiWorld, CollectionState -from .Options import is_option_enabled +from typing import Union, Optional +from BaseClasses import CollectionState +from .Options import TimespinnerOptions from .PreCalculatedWeights import PreCalculatedWeights @@ -10,17 +10,18 @@ class TimespinnerLogic: flag_unchained_keys: bool flag_eye_spy: bool flag_specific_keycards: bool - pyramid_keys_unlock: Union[str, None] - present_keys_unlock: Union[str, None] - past_keys_unlock: Union[str, None] - time_keys_unlock: Union[str, None] + pyramid_keys_unlock: Optional[str] + present_keys_unlock: Optional[str] + past_keys_unlock: Optional[str] + time_keys_unlock: Optional[str] - def __init__(self, world: MultiWorld, player: int, precalculated_weights: PreCalculatedWeights): + def __init__(self, player: int, options: Optional[TimespinnerOptions], + precalculated_weights: Optional[PreCalculatedWeights]): self.player = player - self.flag_specific_keycards = is_option_enabled(world, player, "SpecificKeycards") - self.flag_eye_spy = is_option_enabled(world, player, "EyeSpy") - self.flag_unchained_keys = is_option_enabled(world, player, "UnchainedKeys") + self.flag_specific_keycards = bool(options and options.specific_keycards) + self.flag_eye_spy = bool(options and options.eye_spy) + self.flag_unchained_keys = bool(options and options.unchained_keys) if precalculated_weights: if self.flag_unchained_keys: diff --git a/worlds/timespinner/Options.py b/worlds/timespinner/Options.py index f7921fcb81e0..20ad8132c45f 100644 --- a/worlds/timespinner/Options.py +++ b/worlds/timespinner/Options.py @@ -1,59 +1,50 @@ -from typing import Dict, Union, List -from BaseClasses import MultiWorld -from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, Option, OptionDict, OptionList +from dataclasses import dataclass +from typing import Type, Any +from typing import Dict +from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, OptionDict, OptionList, Visibility, Option +from Options import PerGameCommonOptions, DeathLinkMixin, AssembleOptions from schema import Schema, And, Optional, Or - class StartWithJewelryBox(Toggle): "Start with Jewelry Box unlocked" display_name = "Start with Jewelry Box" - class DownloadableItems(DefaultOnToggle): "With the tablet you will be able to download items at terminals" display_name = "Downloadable items" - class EyeSpy(Toggle): "Requires Oculus Ring in inventory to be able to break hidden walls." display_name = "Eye Spy" - class StartWithMeyef(Toggle): "Start with Meyef, ideal for when you want to play multiplayer." display_name = "Start with Meyef" - class QuickSeed(Toggle): "Start with Talaria Attachment, Nyoom!" display_name = "Quick seed" - class SpecificKeycards(Toggle): "Keycards can only open corresponding doors" display_name = "Specific Keycards" - class Inverted(Toggle): "Start in the past" display_name = "Inverted" - class GyreArchives(Toggle): "Gyre locations are in logic. New warps are gated by Merchant Crow and Kobo" display_name = "Gyre Archives" - class Cantoran(Toggle): "Cantoran's fight and check are available upon revisiting his room" display_name = "Cantoran" - class LoreChecks(Toggle): "Memories and journal entries contain items." display_name = "Lore Checks" - class BossRando(Choice): "Wheter all boss locations are shuffled, and if their damage/hp should be scaled." display_name = "Boss Randomization" @@ -62,7 +53,6 @@ class BossRando(Choice): option_unscaled = 2 alias_true = 1 - class EnemyRando(Choice): "Wheter enemies will be randomized, and if their damage/hp should be scaled." display_name = "Enemy Randomization" @@ -72,7 +62,6 @@ class EnemyRando(Choice): option_ryshia = 3 alias_true = 1 - class DamageRando(Choice): "Randomly nerfs and buffs some orbs and their associated spells as well as some associated rings." display_name = "Damage Rando" @@ -85,7 +74,6 @@ class DamageRando(Choice): option_manual = 6 alias_true = 2 - class DamageRandoOverrides(OptionDict): """Manual +/-/normal odds for an orb. Put 0 if you don't want a certain nerf or buff to be a possibility. Orbs that you don't specify will roll with 1/1/1 as odds""" @@ -191,7 +179,6 @@ class DamageRandoOverrides(OptionDict): "Radiant": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 }, } - class HpCap(Range): "Sets the number that Lunais's HP maxes out at." display_name = "HP Cap" @@ -199,7 +186,6 @@ class HpCap(Range): range_end = 999 default = 999 - class LevelCap(Range): """Sets the max level Lunais can achieve.""" display_name = "Level Cap" @@ -207,20 +193,17 @@ class LevelCap(Range): range_end = 99 default = 99 - class ExtraEarringsXP(Range): """Adds additional XP granted by Galaxy Earrings.""" display_name = "Extra Earrings XP" range_start = 0 range_end = 24 default = 0 - class BossHealing(DefaultOnToggle): "Enables/disables healing after boss fights. NOTE: Currently only applicable when Boss Rando is enabled." display_name = "Heal After Bosses" - class ShopFill(Choice): """Sets the items for sale in Merchant Crow's shops. Default: No sunglasses or trendy jacket, but sand vials for sale. @@ -233,12 +216,10 @@ class ShopFill(Choice): option_vanilla = 2 option_empty = 3 - class ShopWarpShards(DefaultOnToggle): "Shops always sell warp shards (when keys possessed), ignoring inventory setting." display_name = "Always Sell Warp Shards" - class ShopMultiplier(Range): "Multiplier for the cost of items in the shop. Set to 0 for free shops." display_name = "Shop Price Multiplier" @@ -246,7 +227,6 @@ class ShopMultiplier(Range): range_end = 10 default = 1 - class LootPool(Choice): """Sets the items that drop from enemies (does not apply to boss reward checks) Vanilla: Drops are the same as the base game @@ -257,7 +237,6 @@ class LootPool(Choice): option_randomized = 1 option_empty = 2 - class DropRateCategory(Choice): """Sets the drop rate when 'Loot Pool' is set to 'Random' Tiered: Based on item rarity/value @@ -271,7 +250,6 @@ class DropRateCategory(Choice): option_randomized = 2 option_fixed = 3 - class FixedDropRate(Range): "Base drop rate percentage when 'Drop Rate Category' is set to 'Fixed'" display_name = "Fixed Drop Rate" @@ -279,7 +257,6 @@ class FixedDropRate(Range): range_end = 100 default = 5 - class LootTierDistro(Choice): """Sets how often items of each rarity tier are placed when 'Loot Pool' is set to 'Random' Default Weight: Rarer items will be assigned to enemy drop slots less frequently than common items @@ -291,32 +268,26 @@ class LootTierDistro(Choice): option_full_random = 1 option_inverted_weight = 2 - class ShowBestiary(Toggle): "All entries in the bestiary are visible, without needing to kill one of a given enemy first" display_name = "Show Bestiary Entries" - class ShowDrops(Toggle): "All item drops in the bestiary are visible, without needing an enemy to drop one of a given item first" display_name = "Show Bestiary Item Drops" - class EnterSandman(Toggle): "The Ancient Pyramid is unlocked by the Twin Pyramid Keys, but the final boss door opens if you have all 5 Timespinner pieces" display_name = "Enter Sandman" - class DadPercent(Toggle): """The win condition is beating the boss of Emperor's Tower""" display_name = "Dad Percent" - class RisingTides(Toggle): """Random areas are flooded or drained, can be further specified with RisingTidesOverrides""" display_name = "Rising Tides" - def rising_tide_option(location: str, with_save_point_option: bool = False) -> Dict[Optional, Or]: if with_save_point_option: return { @@ -341,7 +312,6 @@ def rising_tide_option(location: str, with_save_point_option: bool = False) -> D "Flooded") } - class RisingTidesOverrides(OptionDict): """Odds for specific areas to be flooded or drained, only has effect when RisingTides is on. Areas that are not specified will roll with the default 33% chance of getting flooded or drained""" @@ -373,13 +343,11 @@ class RisingTidesOverrides(OptionDict): "Lab": { "Dry": 67, "Flooded": 33 }, } - class UnchainedKeys(Toggle): """Start with Twin Pyramid Key, which does not give free warp; warp items for Past, Present, (and ??? with Enter Sandman) can be found.""" display_name = "Unchained Keys" - class TrapChance(Range): """Chance of traps in the item pool. Traps will only replace filler items such as potions, vials and antidotes""" @@ -388,67 +356,256 @@ class TrapChance(Range): range_end = 100 default = 10 - class Traps(OptionList): """List of traps that may be in the item pool to find""" display_name = "Traps Types" valid_keys = { "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap" } default = [ "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap" ] - class PresentAccessWithWheelAndSpindle(Toggle): """When inverted, allows using the refugee camp warp when both the Timespinner Wheel and Spindle is acquired.""" - display_name = "Past Wheel & Spindle Warp" - - -# Some options that are available in the timespinner randomizer arent currently implemented -timespinner_options: Dict[str, Option] = { - "StartWithJewelryBox": StartWithJewelryBox, - "DownloadableItems": DownloadableItems, - "EyeSpy": EyeSpy, - "StartWithMeyef": StartWithMeyef, - "QuickSeed": QuickSeed, - "SpecificKeycards": SpecificKeycards, - "Inverted": Inverted, - "GyreArchives": GyreArchives, - "Cantoran": Cantoran, - "LoreChecks": LoreChecks, - "BossRando": BossRando, - "EnemyRando": EnemyRando, - "DamageRando": DamageRando, - "DamageRandoOverrides": DamageRandoOverrides, - "HpCap": HpCap, - "LevelCap": LevelCap, - "ExtraEarringsXP": ExtraEarringsXP, - "BossHealing": BossHealing, - "ShopFill": ShopFill, - "ShopWarpShards": ShopWarpShards, - "ShopMultiplier": ShopMultiplier, - "LootPool": LootPool, - "DropRateCategory": DropRateCategory, - "FixedDropRate": FixedDropRate, - "LootTierDistro": LootTierDistro, - "ShowBestiary": ShowBestiary, - "ShowDrops": ShowDrops, - "EnterSandman": EnterSandman, - "DadPercent": DadPercent, - "RisingTides": RisingTides, - "RisingTidesOverrides": RisingTidesOverrides, - "UnchainedKeys": UnchainedKeys, - "TrapChance": TrapChance, - "Traps": Traps, - "PresentAccessWithWheelAndSpindle": PresentAccessWithWheelAndSpindle, - "DeathLink": DeathLink, -} - - -def is_option_enabled(world: MultiWorld, player: int, name: str) -> bool: - return get_option_value(world, player, name) > 0 - - -def get_option_value(world: MultiWorld, player: int, name: str) -> Union[int, Dict, List]: - option = getattr(world, name, None) - if option == None: - return 0 - - return option[player].value + display_name = "Back to the future" + +@dataclass +class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin): + start_with_jewelry_box: StartWithJewelryBox + downloadable_items: DownloadableItems + eye_spy: EyeSpy + start_with_meyef: StartWithMeyef + quick_seed: QuickSeed + specific_keycards: SpecificKeycards + inverted: Inverted + gyre_archives: GyreArchives + cantoran: Cantoran + lore_checks: LoreChecks + boss_rando: BossRando + damage_rando: DamageRando + damage_rando_overrides: DamageRandoOverrides + hp_cap: HpCap + level_cap: LevelCap + extra_earrings_xp: ExtraEarringsXP + boss_healing: BossHealing + shop_fill: ShopFill + shop_warp_shards: ShopWarpShards + shop_multiplier: ShopMultiplier + loot_pool: LootPool + drop_rate_category: DropRateCategory + fixed_drop_rate: FixedDropRate + loot_tier_distro: LootTierDistro + show_bestiary: ShowBestiary + show_drops: ShowDrops + enter_sandman: EnterSandman + dad_percent: DadPercent + rising_tides: RisingTides + rising_tides_overrides: RisingTidesOverrides + unchained_keys: UnchainedKeys + back_to_the_future: PresentAccessWithWheelAndSpindle + trap_chance: TrapChance + traps: Traps + +class HiddenDamageRandoOverrides(DamageRandoOverrides): + """Manual +/-/normal odds for an orb. Put 0 if you don't want a certain nerf or buff to be a possibility. Orbs that + you don't specify will roll with 1/1/1 as odds""" + visibility = Visibility.none + +class HiddenRisingTidesOverrides(RisingTidesOverrides): + """Odds for specific areas to be flooded or drained, only has effect when RisingTides is on. + Areas that are not specified will roll with the default 33% chance of getting flooded or drained""" + visibility = Visibility.none + +class HiddenTraps(Traps): + """List of traps that may be in the item pool to find""" + visibility = Visibility.none + +class OptionsHider: + @classmethod + def hidden(cls, option: Type[Option[Any]]) -> Type[Option]: + new_option = AssembleOptions(f"{option}Hidden", option.__bases__, vars(option).copy()) + new_option.visibility = Visibility.none + new_option.__doc__ = option.__doc__ + return new_option + +class HasReplacedCamelCase(Toggle): + """For internal use will display a warning message if true""" + visibility = Visibility.none + +@dataclass +class BackwardsCompatiableTimespinnerOptions(TimespinnerOptions): + StartWithJewelryBox: OptionsHider.hidden(StartWithJewelryBox) # type: ignore + DownloadableItems: OptionsHider.hidden(DownloadableItems) # type: ignore + EyeSpy: OptionsHider.hidden(EyeSpy) # type: ignore + StartWithMeyef: OptionsHider.hidden(StartWithMeyef) # type: ignore + QuickSeed: OptionsHider.hidden(QuickSeed) # type: ignore + SpecificKeycards: OptionsHider.hidden(SpecificKeycards) # type: ignore + Inverted: OptionsHider.hidden(Inverted) # type: ignore + GyreArchives: OptionsHider.hidden(GyreArchives) # type: ignore + Cantoran: OptionsHider.hidden(Cantoran) # type: ignore + LoreChecks: OptionsHider.hidden(LoreChecks) # type: ignore + BossRando: OptionsHider.hidden(BossRando) # type: ignore + DamageRando: OptionsHider.hidden(DamageRando) # type: ignore + DamageRandoOverrides: HiddenDamageRandoOverrides + HpCap: OptionsHider.hidden(HpCap) # type: ignore + LevelCap: OptionsHider.hidden(LevelCap) # type: ignore + ExtraEarringsXP: OptionsHider.hidden(ExtraEarringsXP) # type: ignore + BossHealing: OptionsHider.hidden(BossHealing) # type: ignore + ShopFill: OptionsHider.hidden(ShopFill) # type: ignore + ShopWarpShards: OptionsHider.hidden(ShopWarpShards) # type: ignore + ShopMultiplier: OptionsHider.hidden(ShopMultiplier) # type: ignore + LootPool: OptionsHider.hidden(LootPool) # type: ignore + DropRateCategory: OptionsHider.hidden(DropRateCategory) # type: ignore + FixedDropRate: OptionsHider.hidden(FixedDropRate) # type: ignore + LootTierDistro: OptionsHider.hidden(LootTierDistro) # type: ignore + ShowBestiary: OptionsHider.hidden(ShowBestiary) # type: ignore + ShowDrops: OptionsHider.hidden(ShowDrops) # type: ignore + EnterSandman: OptionsHider.hidden(EnterSandman) # type: ignore + DadPercent: OptionsHider.hidden(DadPercent) # type: ignore + RisingTides: OptionsHider.hidden(RisingTides) # type: ignore + RisingTidesOverrides: HiddenRisingTidesOverrides + UnchainedKeys: OptionsHider.hidden(UnchainedKeys) # type: ignore + PresentAccessWithWheelAndSpindle: OptionsHider.hidden(PresentAccessWithWheelAndSpindle) # type: ignore + TrapChance: OptionsHider.hidden(TrapChance) # type: ignore + Traps: HiddenTraps # type: ignore + DeathLink: OptionsHider.hidden(DeathLink) # type: ignore + has_replaced_options: HasReplacedCamelCase + + def handle_backward_compatibility(self) -> None: + if self.StartWithJewelryBox != StartWithJewelryBox.default and \ + self.start_with_jewelry_box == StartWithJewelryBox.default: + self.start_with_jewelry_box.value = self.StartWithJewelryBox.value + self.has_replaced_options.value = Toggle.option_true + if self.DownloadableItems != DownloadableItems.default and \ + self.downloadable_items == DownloadableItems.default: + self.downloadable_items.value = self.DownloadableItems.value + self.has_replaced_options.value = Toggle.option_true + if self.EyeSpy != EyeSpy.default and \ + self.eye_spy == EyeSpy.default: + self.eye_spy.value = self.EyeSpy.value + self.has_replaced_options.value = Toggle.option_true + if self.StartWithMeyef != StartWithMeyef.default and \ + self.start_with_meyef == StartWithMeyef.default: + self.start_with_meyef.value = self.StartWithMeyef.value + self.has_replaced_options.value = Toggle.option_true + if self.QuickSeed != QuickSeed.default and \ + self.quick_seed == QuickSeed.default: + self.quick_seed.value = self.QuickSeed.value + self.has_replaced_options.value = Toggle.option_true + if self.SpecificKeycards != SpecificKeycards.default and \ + self.specific_keycards == SpecificKeycards.default: + self.specific_keycards.value = self.SpecificKeycards.value + self.has_replaced_options.value = Toggle.option_true + if self.Inverted != Inverted.default and \ + self.inverted == Inverted.default: + self.inverted.value = self.Inverted.value + self.has_replaced_options.value = Toggle.option_true + if self.GyreArchives != GyreArchives.default and \ + self.gyre_archives == GyreArchives.default: + self.gyre_archives.value = self.GyreArchives.value + self.has_replaced_options.value = Toggle.option_true + if self.Cantoran != Cantoran.default and \ + self.cantoran == Cantoran.default: + self.cantoran.value = self.Cantoran.value + self.has_replaced_options.value = Toggle.option_true + if self.LoreChecks != LoreChecks.default and \ + self.lore_checks == LoreChecks.default: + self.lore_checks.value = self.LoreChecks.value + self.has_replaced_options.value = Toggle.option_true + if self.BossRando != BossRando.default and \ + self.boss_rando == BossRando.default: + self.boss_rando.value = self.BossRando.value + self.has_replaced_options.value = Toggle.option_true + if self.DamageRando != DamageRando.default and \ + self.damage_rando == DamageRando.default: + self.damage_rando.value = self.DamageRando.value + self.has_replaced_options.value = Toggle.option_true + if self.DamageRandoOverrides != DamageRandoOverrides.default and \ + self.damage_rando_overrides == DamageRandoOverrides.default: + self.damage_rando_overrides.value = self.DamageRandoOverrides.value + self.has_replaced_options.value = Toggle.option_true + if self.HpCap != HpCap.default and \ + self.hp_cap == HpCap.default: + self.hp_cap.value = self.HpCap.value + self.has_replaced_options.value = Toggle.option_true + if self.LevelCap != LevelCap.default and \ + self.level_cap == LevelCap.default: + self.level_cap.value = self.LevelCap.value + self.has_replaced_options.value = Toggle.option_true + if self.ExtraEarringsXP != ExtraEarringsXP.default and \ + self.extra_earrings_xp == ExtraEarringsXP.default: + self.extra_earrings_xp.value = self.ExtraEarringsXP.value + self.has_replaced_options.value = Toggle.option_true + if self.BossHealing != BossHealing.default and \ + self.boss_healing == BossHealing.default: + self.boss_healing.value = self.BossHealing.value + self.has_replaced_options.value = Toggle.option_true + if self.ShopFill != ShopFill.default and \ + self.shop_fill == ShopFill.default: + self.shop_fill.value = self.ShopFill.value + self.has_replaced_options.value = Toggle.option_true + if self.ShopWarpShards != ShopWarpShards.default and \ + self.shop_warp_shards == ShopWarpShards.default: + self.shop_warp_shards.value = self.ShopWarpShards.value + self.has_replaced_options.value = Toggle.option_true + if self.ShopMultiplier != ShopMultiplier.default and \ + self.shop_multiplier == ShopMultiplier.default: + self.shop_multiplier.value = self.ShopMultiplier.value + self.has_replaced_options.value = Toggle.option_true + if self.LootPool != LootPool.default and \ + self.loot_pool == LootPool.default: + self.loot_pool.value = self.LootPool.value + self.has_replaced_options.value = Toggle.option_true + if self.DropRateCategory != DropRateCategory.default and \ + self.drop_rate_category == DropRateCategory.default: + self.drop_rate_category.value = self.DropRateCategory.value + self.has_replaced_options.value = Toggle.option_true + if self.FixedDropRate != FixedDropRate.default and \ + self.fixed_drop_rate == FixedDropRate.default: + self.fixed_drop_rate.value = self.FixedDropRate.value + self.has_replaced_options.value = Toggle.option_true + if self.LootTierDistro != LootTierDistro.default and \ + self.loot_tier_distro == LootTierDistro.default: + self.loot_tier_distro.value = self.LootTierDistro.value + self.has_replaced_options.value = Toggle.option_true + if self.ShowBestiary != ShowBestiary.default and \ + self.show_bestiary == ShowBestiary.default: + self.show_bestiary.value = self.ShowBestiary.value + self.has_replaced_options.value = Toggle.option_true + if self.ShowDrops != ShowDrops.default and \ + self.show_drops == ShowDrops.default: + self.show_drops.value = self.ShowDrops.value + self.has_replaced_options.value = Toggle.option_true + if self.EnterSandman != EnterSandman.default and \ + self.enter_sandman == EnterSandman.default: + self.enter_sandman.value = self.EnterSandman.value + self.has_replaced_options.value = Toggle.option_true + if self.DadPercent != DadPercent.default and \ + self.dad_percent == DadPercent.default: + self.dad_percent.value = self.DadPercent.value + self.has_replaced_options.value = Toggle.option_true + if self.RisingTides != RisingTides.default and \ + self.rising_tides == RisingTides.default: + self.rising_tides.value = self.RisingTides.value + self.has_replaced_options.value = Toggle.option_true + if self.RisingTidesOverrides != RisingTidesOverrides.default and \ + self.rising_tides_overrides == RisingTidesOverrides.default: + self.rising_tides_overrides.value = self.RisingTidesOverrides.value + self.has_replaced_options.value = Toggle.option_true + if self.UnchainedKeys != UnchainedKeys.default and \ + self.unchained_keys == UnchainedKeys.default: + self.unchained_keys.value = self.UnchainedKeys.value + self.has_replaced_options.value = Toggle.option_true + if self.PresentAccessWithWheelAndSpindle != PresentAccessWithWheelAndSpindle.default and \ + self.back_to_the_future == PresentAccessWithWheelAndSpindle.default: + self.back_to_the_future.value = self.PresentAccessWithWheelAndSpindle.value + self.has_replaced_options.value = Toggle.option_true + if self.TrapChance != TrapChance.default and \ + self.trap_chance == TrapChance.default: + self.trap_chance.value = self.TrapChance.value + self.has_replaced_options.value = Toggle.option_true + if self.Traps != Traps.default and \ + self.traps == Traps.default: + self.traps.value = self.Traps.value + self.has_replaced_options.value = Toggle.option_true + if self.DeathLink != DeathLink.default and \ + self.death_link == DeathLink.default: + self.death_link.value = self.DeathLink.value + self.has_replaced_options.value = Toggle.option_true diff --git a/worlds/timespinner/PreCalculatedWeights.py b/worlds/timespinner/PreCalculatedWeights.py index ff7f031d3b67..c9d80d7a709d 100644 --- a/worlds/timespinner/PreCalculatedWeights.py +++ b/worlds/timespinner/PreCalculatedWeights.py @@ -1,6 +1,6 @@ from typing import Tuple, Dict, Union, List -from BaseClasses import MultiWorld -from .Options import timespinner_options, is_option_enabled, get_option_value +from random import Random +from .Options import TimespinnerOptions class PreCalculatedWeights: pyramid_keys_unlock: str @@ -21,22 +21,22 @@ class PreCalculatedWeights: flood_lake_serene_bridge: bool flood_lab: bool - def __init__(self, world: MultiWorld, player: int): - if world and is_option_enabled(world, player, "RisingTides"): - weights_overrrides: Dict[str, Union[str, Dict[str, int]]] = self.get_flood_weights_overrides(world, player) + def __init__(self, options: TimespinnerOptions, random: Random): + if options.rising_tides: + weights_overrrides: Dict[str, Union[str, Dict[str, int]]] = self.get_flood_weights_overrides(options) self.flood_basement, self.flood_basement_high = \ - self.roll_flood_setting(world, player, weights_overrrides, "CastleBasement") - self.flood_xarion, _ = self.roll_flood_setting(world, player, weights_overrrides, "Xarion") - self.flood_maw, _ = self.roll_flood_setting(world, player, weights_overrrides, "Maw") - self.flood_pyramid_shaft, _ = self.roll_flood_setting(world, player, weights_overrrides, "AncientPyramidShaft") - self.flood_pyramid_back, _ = self.roll_flood_setting(world, player, weights_overrrides, "Sandman") - self.flood_moat, _ = self.roll_flood_setting(world, player, weights_overrrides, "CastleMoat") - self.flood_courtyard, _ = self.roll_flood_setting(world, player, weights_overrrides, "CastleCourtyard") - self.flood_lake_desolation, _ = self.roll_flood_setting(world, player, weights_overrrides, "LakeDesolation") - self.flood_lake_serene, _ = self.roll_flood_setting(world, player, weights_overrrides, "LakeSerene") - self.flood_lake_serene_bridge, _ = self.roll_flood_setting(world, player, weights_overrrides, "LakeSereneBridge") - self.flood_lab, _ = self.roll_flood_setting(world, player, weights_overrrides, "Lab") + self.roll_flood_setting(random, weights_overrrides, "CastleBasement") + self.flood_xarion, _ = self.roll_flood_setting(random, weights_overrrides, "Xarion") + self.flood_maw, _ = self.roll_flood_setting(random, weights_overrrides, "Maw") + self.flood_pyramid_shaft, _ = self.roll_flood_setting(random, weights_overrrides, "AncientPyramidShaft") + self.flood_pyramid_back, _ = self.roll_flood_setting(random, weights_overrrides, "Sandman") + self.flood_moat, _ = self.roll_flood_setting(random, weights_overrrides, "CastleMoat") + self.flood_courtyard, _ = self.roll_flood_setting(random, weights_overrrides, "CastleCourtyard") + self.flood_lake_desolation, _ = self.roll_flood_setting(random, weights_overrrides, "LakeDesolation") + self.flood_lake_serene, _ = self.roll_flood_setting(random, weights_overrrides, "LakeSerene") + self.flood_lake_serene_bridge, _ = self.roll_flood_setting(random, weights_overrrides, "LakeSereneBridge") + self.flood_lab, _ = self.roll_flood_setting(random, weights_overrrides, "Lab") else: self.flood_basement = False self.flood_basement_high = False @@ -52,10 +52,12 @@ def __init__(self, world: MultiWorld, player: int): self.flood_lab = False self.pyramid_keys_unlock, self.present_key_unlock, self.past_key_unlock, self.time_key_unlock = \ - self.get_pyramid_keys_unlocks(world, player, self.flood_maw, self.flood_xarion) + self.get_pyramid_keys_unlocks(options, random, self.flood_maw, self.flood_xarion) @staticmethod - def get_pyramid_keys_unlocks(world: MultiWorld, player: int, is_maw_flooded: bool, is_xarion_flooded: bool) -> Tuple[str, str, str, str]: + def get_pyramid_keys_unlocks(options: TimespinnerOptions, random: Random, + is_maw_flooded: bool, is_xarion_flooded: bool) -> Tuple[str, str, str, str]: + present_teleportation_gates: List[str] = [ "GateKittyBoss", "GateLeftLibrary", @@ -80,38 +82,30 @@ def get_pyramid_keys_unlocks(world: MultiWorld, player: int, is_maw_flooded: boo "GateRightPyramid" ) - if not world: - return ( - present_teleportation_gates[0], - present_teleportation_gates[0], - past_teleportation_gates[0], - ancient_pyramid_teleportation_gates[0] - ) - if not is_maw_flooded: past_teleportation_gates.append("GateMaw") if not is_xarion_flooded: present_teleportation_gates.append("GateXarion") - if is_option_enabled(world, player, "Inverted"): + if options.inverted: all_gates: Tuple[str, ...] = present_teleportation_gates else: all_gates: Tuple[str, ...] = past_teleportation_gates + present_teleportation_gates return ( - world.random.choice(all_gates), - world.random.choice(present_teleportation_gates), - world.random.choice(past_teleportation_gates), - world.random.choice(ancient_pyramid_teleportation_gates) + random.choice(all_gates), + random.choice(present_teleportation_gates), + random.choice(past_teleportation_gates), + random.choice(ancient_pyramid_teleportation_gates) ) @staticmethod - def get_flood_weights_overrides(world: MultiWorld, player: int) -> Dict[str, Union[str, Dict[str, int]]]: + def get_flood_weights_overrides(options: TimespinnerOptions) -> Dict[str, Union[str, Dict[str, int]]]: weights_overrides_option: Union[int, Dict[str, Union[str, Dict[str, int]]]] = \ - get_option_value(world, player, "RisingTidesOverrides") + options.rising_tides_overrides.value - default_weights: Dict[str, Dict[str, int]] = timespinner_options["RisingTidesOverrides"].default + default_weights: Dict[str, Dict[str, int]] = options.rising_tides_overrides.default if not weights_overrides_option: weights_overrides_option = default_weights @@ -123,13 +117,13 @@ def get_flood_weights_overrides(world: MultiWorld, player: int) -> Dict[str, Uni return weights_overrides_option @staticmethod - def roll_flood_setting(world: MultiWorld, player: int, - all_weights: Dict[str, Union[Dict[str, int], str]], key: str) -> Tuple[bool, bool]: + def roll_flood_setting(random: Random, all_weights: Dict[str, Union[Dict[str, int], str]], + key: str) -> Tuple[bool, bool]: weights: Union[Dict[str, int], str] = all_weights[key] if isinstance(weights, dict): - result: str = world.random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0] + result: str = random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0] else: result: str = weights diff --git a/worlds/timespinner/Regions.py b/worlds/timespinner/Regions.py index 757a41c38821..f737b461d0bc 100644 --- a/worlds/timespinner/Regions.py +++ b/worlds/timespinner/Regions.py @@ -1,14 +1,16 @@ from typing import List, Set, Dict, Optional, Callable from BaseClasses import CollectionState, MultiWorld, Region, Entrance, Location -from .Options import is_option_enabled +from .Options import TimespinnerOptions from .Locations import LocationData, get_location_datas from .PreCalculatedWeights import PreCalculatedWeights from .LogicExtensions import TimespinnerLogic -def create_regions_and_locations(world: MultiWorld, player: int, precalculated_weights: PreCalculatedWeights): +def create_regions_and_locations(world: MultiWorld, player: int, options: TimespinnerOptions, + precalculated_weights: PreCalculatedWeights): + locations_per_region: Dict[str, List[LocationData]] = split_location_datas_per_region( - get_location_datas(world, player, precalculated_weights)) + get_location_datas(player, options, precalculated_weights)) regions = [ create_region(world, player, locations_per_region, 'Menu'), @@ -53,7 +55,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w create_region(world, player, locations_per_region, 'Space time continuum') ] - if is_option_enabled(world, player, "GyreArchives"): + if options.gyre_archives: regions.extend([ create_region(world, player, locations_per_region, 'Ravenlord\'s Lair'), create_region(world, player, locations_per_region, 'Ifrit\'s Lair'), @@ -64,10 +66,10 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w world.regions += regions - connectStartingRegion(world, player) + connectStartingRegion(world, player, options) flooded: PreCalculatedWeights = precalculated_weights - logic = TimespinnerLogic(world, player, precalculated_weights) + logic = TimespinnerLogic(player, options, precalculated_weights) connect(world, player, 'Lake desolation', 'Lower lake desolation', lambda state: flooded.flood_lake_desolation or logic.has_timestop(state) or state.has('Talaria Attachment', player)) connect(world, player, 'Lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player), "Upper Lake Serene") @@ -123,7 +125,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w connect(world, player, 'Sealed Caves (Xarion)', 'Skeleton Shaft') connect(world, player, 'Sealed Caves (Xarion)', 'Space time continuum', logic.has_teleport) connect(world, player, 'Refugee Camp', 'Forest') - connect(world, player, 'Refugee Camp', 'Library', lambda state: is_option_enabled(world, player, "Inverted") and is_option_enabled(world, player, "PresentAccessWithWheelAndSpindle") and state.has_all({'Timespinner Wheel', 'Timespinner Spindle'}, player)) + connect(world, player, 'Refugee Camp', 'Library', lambda state: options.inverted and options.back_to_the_future and state.has_all({'Timespinner Wheel', 'Timespinner Spindle'}, player)) connect(world, player, 'Refugee Camp', 'Space time continuum', logic.has_teleport) connect(world, player, 'Forest', 'Refugee Camp') connect(world, player, 'Forest', 'Left Side forest Caves', lambda state: flooded.flood_lake_serene_bridge or state.has('Talaria Attachment', player) or logic.has_timestop(state)) @@ -178,11 +180,11 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w connect(world, player, 'Space time continuum', 'Royal towers (lower)', lambda state: logic.can_teleport_to(state, "Past", "GateRoyalTowers")) connect(world, player, 'Space time continuum', 'Caves of Banishment (Maw)', lambda state: logic.can_teleport_to(state, "Past", "GateMaw")) connect(world, player, 'Space time continuum', 'Caves of Banishment (upper)', lambda state: logic.can_teleport_to(state, "Past", "GateCavesOfBanishment")) - connect(world, player, 'Space time continuum', 'Ancient Pyramid (entrance)', lambda state: logic.can_teleport_to(state, "Time", "GateGyre") or (not is_option_enabled(world, player, "UnchainedKeys") and is_option_enabled(world, player, "EnterSandman"))) + connect(world, player, 'Space time continuum', 'Ancient Pyramid (entrance)', lambda state: logic.can_teleport_to(state, "Time", "GateGyre") or (not options.unchained_keys and options.enter_sandman)) connect(world, player, 'Space time continuum', 'Ancient Pyramid (left)', lambda state: logic.can_teleport_to(state, "Time", "GateLeftPyramid")) connect(world, player, 'Space time continuum', 'Ancient Pyramid (right)', lambda state: logic.can_teleport_to(state, "Time", "GateRightPyramid")) - if is_option_enabled(world, player, "GyreArchives"): + if options.gyre_archives: connect(world, player, 'The lab (upper)', 'Ravenlord\'s Lair', lambda state: state.has('Merchant Crow', player)) connect(world, player, 'Ravenlord\'s Lair', 'The lab (upper)') connect(world, player, 'Library top', 'Ifrit\'s Lair', lambda state: state.has('Kobo', player) and state.can_reach('Refugee Camp', 'Region', player), "Refugee Camp") @@ -220,12 +222,12 @@ def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str return region -def connectStartingRegion(world: MultiWorld, player: int): +def connectStartingRegion(world: MultiWorld, player: int, options: TimespinnerOptions): menu = world.get_region('Menu', player) tutorial = world.get_region('Tutorial', player) space_time_continuum = world.get_region('Space time continuum', player) - if is_option_enabled(world, player, "Inverted"): + if options.inverted: starting_region = world.get_region('Refugee Camp', player) else: starting_region = world.get_region('Lake desolation', player) diff --git a/worlds/timespinner/__init__.py b/worlds/timespinner/__init__.py index cab6fb648b95..66744cffdf85 100644 --- a/worlds/timespinner/__init__.py +++ b/worlds/timespinner/__init__.py @@ -1,12 +1,13 @@ -from typing import Dict, List, Set, Tuple, TextIO, Union -from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification +from typing import Dict, List, Set, Tuple, TextIO +from BaseClasses import Item, Tutorial, ItemClassification from .Items import get_item_names_per_category from .Items import item_table, starter_melee_weapons, starter_spells, filler_items, starter_progression_items from .Locations import get_location_datas, EventId -from .Options import is_option_enabled, get_option_value, timespinner_options +from .Options import BackwardsCompatiableTimespinnerOptions, Toggle from .PreCalculatedWeights import PreCalculatedWeights from .Regions import create_regions_and_locations from worlds.AutoWorld import World, WebWorld +import logging class TimespinnerWebWorld(WebWorld): theme = "ice" @@ -35,32 +36,34 @@ class TimespinnerWorld(World): Timespinner is a beautiful metroidvania inspired by classic 90s action-platformers. Travel back in time to change fate itself. Join timekeeper Lunais on her quest for revenge against the empire that killed her family. """ - - option_definitions = timespinner_options + options_dataclass = BackwardsCompatiableTimespinnerOptions + options: BackwardsCompatiableTimespinnerOptions game = "Timespinner" topology_present = True web = TimespinnerWebWorld() required_client_version = (0, 4, 2) item_name_to_id = {name: data.code for name, data in item_table.items()} - location_name_to_id = {location.name: location.code for location in get_location_datas(None, None, None)} + location_name_to_id = {location.name: location.code for location in get_location_datas(-1, None, None)} item_name_groups = get_item_names_per_category() precalculated_weights: PreCalculatedWeights def generate_early(self) -> None: - self.precalculated_weights = PreCalculatedWeights(self.multiworld, self.player) + self.options.handle_backward_compatibility() + + self.precalculated_weights = PreCalculatedWeights(self.options, self.random) # in generate_early the start_inventory isnt copied over to precollected_items yet, so we can still modify the options directly - if self.multiworld.start_inventory[self.player].value.pop('Meyef', 0) > 0: - self.multiworld.StartWithMeyef[self.player].value = self.multiworld.StartWithMeyef[self.player].option_true - if self.multiworld.start_inventory[self.player].value.pop('Talaria Attachment', 0) > 0: - self.multiworld.QuickSeed[self.player].value = self.multiworld.QuickSeed[self.player].option_true - if self.multiworld.start_inventory[self.player].value.pop('Jewelry Box', 0) > 0: - self.multiworld.StartWithJewelryBox[self.player].value = self.multiworld.StartWithJewelryBox[self.player].option_true + if self.options.start_inventory.value.pop('Meyef', 0) > 0: + self.options.start_with_meyef.value = Toggle.option_true + if self.options.start_inventory.value.pop('Talaria Attachment', 0) > 0: + self.options.quick_seed.value = Toggle.option_true + if self.options.start_inventory.value.pop('Jewelry Box', 0) > 0: + self.options.start_with_jewelry_box.value = Toggle.option_true def create_regions(self) -> None: - create_regions_and_locations(self.multiworld, self.player, self.precalculated_weights) + create_regions_and_locations(self.multiworld, self.player, self.options, self.precalculated_weights) def create_items(self) -> None: self.create_and_assign_event_items() @@ -74,7 +77,7 @@ def create_items(self) -> None: def set_rules(self) -> None: final_boss: str - if self.is_option_enabled("DadPercent"): + if self.options.dad_percent: final_boss = "Killed Emperor" else: final_boss = "Killed Nightmare" @@ -82,48 +85,74 @@ def set_rules(self) -> None: self.multiworld.completion_condition[self.player] = lambda state: state.has(final_boss, self.player) def fill_slot_data(self) -> Dict[str, object]: - slot_data: Dict[str, object] = {} - - ap_specific_settings: Set[str] = {"RisingTidesOverrides", "TrapChance"} - - for option_name in timespinner_options: - if (option_name not in ap_specific_settings): - slot_data[option_name] = self.get_option_value(option_name) - - slot_data["StinkyMaw"] = True - slot_data["ProgressiveVerticalMovement"] = False - slot_data["ProgressiveKeycards"] = False - slot_data["PersonalItems"] = self.get_personal_items() - slot_data["PyramidKeysGate"] = self.precalculated_weights.pyramid_keys_unlock - slot_data["PresentGate"] = self.precalculated_weights.present_key_unlock - slot_data["PastGate"] = self.precalculated_weights.past_key_unlock - slot_data["TimeGate"] = self.precalculated_weights.time_key_unlock - slot_data["Basement"] = int(self.precalculated_weights.flood_basement) + \ - int(self.precalculated_weights.flood_basement_high) - slot_data["Xarion"] = self.precalculated_weights.flood_xarion - slot_data["Maw"] = self.precalculated_weights.flood_maw - slot_data["PyramidShaft"] = self.precalculated_weights.flood_pyramid_shaft - slot_data["BackPyramid"] = self.precalculated_weights.flood_pyramid_back - slot_data["CastleMoat"] = self.precalculated_weights.flood_moat - slot_data["CastleCourtyard"] = self.precalculated_weights.flood_courtyard - slot_data["LakeDesolation"] = self.precalculated_weights.flood_lake_desolation - slot_data["DryLakeSerene"] = not self.precalculated_weights.flood_lake_serene - slot_data["LakeSereneBridge"] = self.precalculated_weights.flood_lake_serene_bridge - slot_data["Lab"] = self.precalculated_weights.flood_lab - - return slot_data + return { + # options + "StartWithJewelryBox": self.options.start_with_jewelry_box.value, + "DownloadableItems": self.options.downloadable_items.value, + "EyeSpy": self.options.eye_spy.value, + "StartWithMeyef": self.options.start_with_meyef.value, + "QuickSeed": self.options.quick_seed.value, + "SpecificKeycards": self.options.specific_keycards.value, + "Inverted": self.options.inverted.value, + "GyreArchives": self.options.gyre_archives.value, + "Cantoran": self.options.cantoran.value, + "LoreChecks": self.options.lore_checks.value, + "BossRando": self.options.boss_rando.value, + "DamageRando": self.options.damage_rando.value, + "DamageRandoOverrides": self.options.damage_rando_overrides.value, + "HpCap": self.options.hp_cap.value, + "LevelCap": self.options.level_cap.value, + "ExtraEarringsXP": self.options.extra_earrings_xp.value, + "BossHealing": self.options.boss_healing.value, + "ShopFill": self.options.shop_fill.value, + "ShopWarpShards": self.options.shop_warp_shards.value, + "ShopMultiplier": self.options.shop_multiplier.value, + "LootPool": self.options.loot_pool.value, + "DropRateCategory": self.options.drop_rate_category.value, + "FixedDropRate": self.options.fixed_drop_rate.value, + "LootTierDistro": self.options.loot_tier_distro.value, + "ShowBestiary": self.options.show_bestiary.value, + "ShowDrops": self.options.show_drops.value, + "EnterSandman": self.options.enter_sandman.value, + "DadPercent": self.options.dad_percent.value, + "RisingTides": self.options.rising_tides.value, + "UnchainedKeys": self.options.unchained_keys.value, + "PresentAccessWithWheelAndSpindle": self.options.back_to_the_future.value, + "Traps": self.options.traps.value, + "DeathLink": self.options.death_link.value, + "StinkyMaw": True, + # data + "PersonalItems": self.get_personal_items(), + "PyramidKeysGate": self.precalculated_weights.pyramid_keys_unlock, + "PresentGate": self.precalculated_weights.present_key_unlock, + "PastGate": self.precalculated_weights.past_key_unlock, + "TimeGate": self.precalculated_weights.time_key_unlock, + # rising tides + "Basement": int(self.precalculated_weights.flood_basement) + \ + int(self.precalculated_weights.flood_basement_high), + "Xarion": self.precalculated_weights.flood_xarion, + "Maw": self.precalculated_weights.flood_maw, + "PyramidShaft": self.precalculated_weights.flood_pyramid_shaft, + "BackPyramid": self.precalculated_weights.flood_pyramid_back, + "CastleMoat": self.precalculated_weights.flood_moat, + "CastleCourtyard": self.precalculated_weights.flood_courtyard, + "LakeDesolation": self.precalculated_weights.flood_lake_desolation, + "DryLakeSerene": not self.precalculated_weights.flood_lake_serene, + "LakeSereneBridge": self.precalculated_weights.flood_lake_serene_bridge, + "Lab": self.precalculated_weights.flood_lab + } def write_spoiler_header(self, spoiler_handle: TextIO) -> None: - if self.is_option_enabled("UnchainedKeys"): + if self.options.unchained_keys: spoiler_handle.write(f'Modern Warp Beacon unlock: {self.precalculated_weights.present_key_unlock}\n') spoiler_handle.write(f'Timeworn Warp Beacon unlock: {self.precalculated_weights.past_key_unlock}\n') - if self.is_option_enabled("EnterSandman"): + if self.options.enter_sandman: spoiler_handle.write(f'Mysterious Warp Beacon unlock: {self.precalculated_weights.time_key_unlock}\n') else: spoiler_handle.write(f'Twin Pyramid Keys unlock: {self.precalculated_weights.pyramid_keys_unlock}\n') - if self.is_option_enabled("RisingTides"): + if self.options.rising_tides: flooded_areas: List[str] = [] if self.precalculated_weights.flood_basement: @@ -159,6 +188,15 @@ def write_spoiler_header(self, spoiler_handle: TextIO) -> None: spoiler_handle.write(f'Flooded Areas: {flooded_areas_string}\n') + if self.options.has_replaced_options: + warning = \ + f"NOTICE: Timespinner options for player '{self.player_name}' where renamed from PasCalCase to snake_case, " \ + "please update your yaml" + + spoiler_handle.write("\n") + spoiler_handle.write(warning) + logging.warning(warning) + def create_item(self, name: str) -> Item: data = item_table[name] @@ -176,41 +214,41 @@ def create_item(self, name: str) -> Item: if not item.advancement: return item - if (name == 'Tablet' or name == 'Library Keycard V') and not self.is_option_enabled("DownloadableItems"): + if (name == 'Tablet' or name == 'Library Keycard V') and not self.options.downloadable_items: item.classification = ItemClassification.filler - elif name == 'Oculus Ring' and not self.is_option_enabled("EyeSpy"): + elif name == 'Oculus Ring' and not self.options.eye_spy: item.classification = ItemClassification.filler - elif (name == 'Kobo' or name == 'Merchant Crow') and not self.is_option_enabled("GyreArchives"): + elif (name == 'Kobo' or name == 'Merchant Crow') and not self.options.gyre_archives: item.classification = ItemClassification.filler elif name in {"Timeworn Warp Beacon", "Modern Warp Beacon", "Mysterious Warp Beacon"} \ - and not self.is_option_enabled("UnchainedKeys"): + and not self.options.unchained_keys: item.classification = ItemClassification.filler return item def get_filler_item_name(self) -> str: - trap_chance: int = self.get_option_value("TrapChance") - enabled_traps: List[str] = self.get_option_value("Traps") + trap_chance: int = self.options.trap_chance.value + enabled_traps: List[str] = self.options.traps.value - if self.multiworld.random.random() < (trap_chance / 100) and enabled_traps: - return self.multiworld.random.choice(enabled_traps) + if self.random.random() < (trap_chance / 100) and enabled_traps: + return self.random.choice(enabled_traps) else: - return self.multiworld.random.choice(filler_items) + return self.random.choice(filler_items) def get_excluded_items(self) -> Set[str]: excluded_items: Set[str] = set() - if self.is_option_enabled("StartWithJewelryBox"): + if self.options.start_with_jewelry_box: excluded_items.add('Jewelry Box') - if self.is_option_enabled("StartWithMeyef"): + if self.options.start_with_meyef: excluded_items.add('Meyef') - if self.is_option_enabled("QuickSeed"): + if self.options.quick_seed: excluded_items.add('Talaria Attachment') - if self.is_option_enabled("UnchainedKeys"): + if self.options.unchained_keys: excluded_items.add('Twin Pyramid Key') - if not self.is_option_enabled("EnterSandman"): + if not self.options.enter_sandman: excluded_items.add('Mysterious Warp Beacon') else: excluded_items.add('Timeworn Warp Beacon') @@ -224,8 +262,8 @@ def get_excluded_items(self) -> Set[str]: return excluded_items def assign_starter_items(self, excluded_items: Set[str]) -> None: - non_local_items: Set[str] = self.multiworld.non_local_items[self.player].value - local_items: Set[str] = self.multiworld.local_items[self.player].value + non_local_items: Set[str] = self.options.non_local_items.value + local_items: Set[str] = self.options.local_items.value local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if item in local_items or not item in non_local_items) @@ -247,27 +285,26 @@ def assign_starter_items(self, excluded_items: Set[str]) -> None: self.assign_starter_item(excluded_items, 'Tutorial: Yo Momma 2', local_starter_spells) def assign_starter_item(self, excluded_items: Set[str], location: str, item_list: Tuple[str, ...]) -> None: - item_name = self.multiworld.random.choice(item_list) + item_name = self.random.choice(item_list) self.place_locked_item(excluded_items, location, item_name) def place_first_progression_item(self, excluded_items: Set[str]) -> None: - if self.is_option_enabled("QuickSeed") or self.is_option_enabled("Inverted") \ - or self.precalculated_weights.flood_lake_desolation: + if self.options.quick_seed or self.options.inverted or self.precalculated_weights.flood_lake_desolation: return - for item in self.multiworld.precollected_items[self.player]: - if item.name in starter_progression_items and not item.name in excluded_items: + for item_name in self.options.start_inventory.value.keys(): + if item_name in starter_progression_items: return local_starter_progression_items = tuple( item for item in starter_progression_items - if item not in excluded_items and item not in self.multiworld.non_local_items[self.player].value) + if item not in excluded_items and item not in self.options.non_local_items.value) if not local_starter_progression_items: return - progression_item = self.multiworld.random.choice(local_starter_progression_items) + progression_item = self.random.choice(local_starter_progression_items) self.multiworld.local_early_items[self.player][progression_item] = 1 @@ -307,9 +344,3 @@ def get_personal_items(self) -> Dict[int, int]: personal_items[location.address] = location.item.code return personal_items - - def is_option_enabled(self, option: str) -> bool: - return is_option_enabled(self.multiworld, self.player, option) - - def get_option_value(self, option: str) -> Union[int, Dict, List]: - return get_option_value(self.multiworld, self.player, option) From 83521e99d9c78f3240f4bba1447d13f19ca34681 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Wed, 31 Jul 2024 05:04:21 -0500 Subject: [PATCH 02/98] Core: migrate item links out of main (#2914) * Core: move item linking out of main * add a test that item link option correctly validates * remove unused fluff --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- BaseClasses.py | 79 ++++++++++++++++++++++++++++++++++++ Main.py | 77 +---------------------------------- test/general/test_options.py | 14 ++++++- 3 files changed, 93 insertions(+), 77 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 1c7dad7f3b9e..6456210e95dc 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1,5 +1,6 @@ from __future__ import annotations +import collections import copy import itertools import functools @@ -288,6 +289,84 @@ def set_item_links(self): group["non_local_items"] = item_link["non_local_items"] group["link_replacement"] = replacement_prio[item_link["link_replacement"]] + def link_items(self) -> None: + """Called to link together items in the itempool related to the registered item link groups.""" + for group_id, group in self.groups.items(): + def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ + Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]] + ]: + classifications: Dict[str, int] = collections.defaultdict(int) + counters = {player: {name: 0 for name in shared_pool} for player in players} + for item in self.itempool: + if item.player in counters and item.name in shared_pool: + counters[item.player][item.name] += 1 + classifications[item.name] |= item.classification + + for player in players.copy(): + if all([counters[player][item] == 0 for item in shared_pool]): + players.remove(player) + del (counters[player]) + + if not players: + return None, None + + for item in shared_pool: + count = min(counters[player][item] for player in players) + if count: + for player in players: + counters[player][item] = count + else: + for player in players: + del (counters[player][item]) + return counters, classifications + + common_item_count, classifications = find_common_pool(group["players"], group["item_pool"]) + if not common_item_count: + continue + + new_itempool: List[Item] = [] + for item_name, item_count in next(iter(common_item_count.values())).items(): + for _ in range(item_count): + new_item = group["world"].create_item(item_name) + # mangle together all original classification bits + new_item.classification |= classifications[item_name] + new_itempool.append(new_item) + + region = Region("Menu", group_id, self, "ItemLink") + self.regions.append(region) + locations = region.locations + for item in self.itempool: + count = common_item_count.get(item.player, {}).get(item.name, 0) + if count: + loc = Location(group_id, f"Item Link: {item.name} -> {self.player_name[item.player]} {count}", + None, region) + loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \ + state.has(item_name, group_id_, count_) + + locations.append(loc) + loc.place_locked_item(item) + common_item_count[item.player][item.name] -= 1 + else: + new_itempool.append(item) + + itemcount = len(self.itempool) + self.itempool = new_itempool + + while itemcount > len(self.itempool): + items_to_add = [] + for player in group["players"]: + if group["link_replacement"]: + item_player = group_id + else: + item_player = player + if group["replacement_items"][player]: + 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)) + self.random.shuffle(items_to_add) + self.itempool.extend(items_to_add[:itemcount - len(self.itempool)]) + def secure(self): self.random = ThreadBarrierProxy(secrets.SystemRandom()) self.is_race = True diff --git a/Main.py b/Main.py index 56b3a6545db2..ce054dcd393f 100644 --- a/Main.py +++ b/Main.py @@ -184,82 +184,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No assert len(multiworld.itempool) == len(new_items), "Item Pool amounts should not change." multiworld.itempool[:] = new_items - # temporary home for item links, should be moved out of Main - for group_id, group in multiworld.groups.items(): - def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ - Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]] - ]: - classifications: Dict[str, int] = collections.defaultdict(int) - counters = {player: {name: 0 for name in shared_pool} for player in players} - for item in multiworld.itempool: - if item.player in counters and item.name in shared_pool: - counters[item.player][item.name] += 1 - classifications[item.name] |= item.classification - - for player in players.copy(): - if all([counters[player][item] == 0 for item in shared_pool]): - players.remove(player) - del (counters[player]) - - if not players: - return None, None - - for item in shared_pool: - count = min(counters[player][item] for player in players) - if count: - for player in players: - counters[player][item] = count - else: - for player in players: - del (counters[player][item]) - return counters, classifications - - common_item_count, classifications = find_common_pool(group["players"], group["item_pool"]) - if not common_item_count: - continue - - new_itempool: List[Item] = [] - for item_name, item_count in next(iter(common_item_count.values())).items(): - for _ in range(item_count): - new_item = group["world"].create_item(item_name) - # mangle together all original classification bits - new_item.classification |= classifications[item_name] - new_itempool.append(new_item) - - region = Region("Menu", group_id, multiworld, "ItemLink") - multiworld.regions.append(region) - locations = region.locations - for item in multiworld.itempool: - count = common_item_count.get(item.player, {}).get(item.name, 0) - if count: - loc = Location(group_id, f"Item Link: {item.name} -> {multiworld.player_name[item.player]} {count}", - None, region) - loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \ - state.has(item_name, group_id_, count_) - - locations.append(loc) - loc.place_locked_item(item) - common_item_count[item.player][item.name] -= 1 - else: - new_itempool.append(item) - - itemcount = len(multiworld.itempool) - multiworld.itempool = new_itempool - - while itemcount > len(multiworld.itempool): - items_to_add = [] - for player in group["players"]: - if group["link_replacement"]: - item_player = group_id - else: - item_player = player - if group["replacement_items"][player]: - items_to_add.append(AutoWorld.call_single(multiworld, "create_item", item_player, - group["replacement_items"][player])) - else: - items_to_add.append(AutoWorld.call_single(multiworld, "create_filler", item_player)) - multiworld.random.shuffle(items_to_add) - multiworld.itempool.extend(items_to_add[:itemcount - len(multiworld.itempool)]) + multiworld.link_items() if any(multiworld.item_links.values()): multiworld._all_state = None diff --git a/test/general/test_options.py b/test/general/test_options.py index 6cf642029e65..2229b7ea7e66 100644 --- a/test/general/test_options.py +++ b/test/general/test_options.py @@ -1,6 +1,6 @@ import unittest -from BaseClasses import PlandoOptions +from BaseClasses import MultiWorld, PlandoOptions from Options import ItemLinks from worlds.AutoWorld import AutoWorldRegister @@ -47,3 +47,15 @@ def test_item_links_name_groups(self): self.assertIn("Bow", link.value[0]["item_pool"]) # TODO test that the group created using these options has the items + + def test_item_links_resolve(self): + """Test item link option resolves correctly.""" + item_link_group = [{ + "name": "ItemLinkTest", + "item_pool": ["Everything"], + "link_replacement": False, + "replacement_item": None, + }] + item_links = {1: ItemLinks.from_any(item_link_group), 2: ItemLinks.from_any(item_link_group)} + for link in item_links.values(): + self.assertEqual(link.value[0], item_link_group[0]) From a05dbac55ffdd1e2f2192421b3fba7fca5341c5c Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Wed, 31 Jul 2024 05:13:14 -0500 Subject: [PATCH 03/98] Core: Rework accessibility (#1481) * rename locations accessibility to "full" and make old locations accessibility debug only * fix a bug in oot * reorder lttp tests to not override its overrides * changed the wrong word in the dict * :forehead: * update the manual lttp yaml * use __debug__ * update pokemon and messenger * fix conflicts from 993 * fix stardew presets * add that locations may be inaccessible to description * use reST format and make the items description one line so that it renders correctly on webhost * forgot i renamed that * add aliases for back compat * some cleanup * fix imports * fix test failure * only check "items" players when the item is progression * Revert "only check "items" players when the item is progression" This reverts commit ecbf986145e6194aa99a39c481d8ecd0736d5a4c. * remove some unnecessary diffs * CV64: Add ItemsAccessibility * put items description at the bottom of the docstring since that's it's visual order * : * rename accessibility reference in pokemon rb dexsanity * make the rendered tooltips look nicer --- BaseClasses.py | 23 ++++++--------- Options.py | 29 +++++++++++++++---- test/general/test_fill.py | 4 +-- test/multiworld/test_multiworlds.py | 2 +- worlds/alttp/Options.py | 5 ++-- worlds/alttp/Rules.py | 17 ++++++----- worlds/alttp/test/inverted/TestInverted.py | 4 +-- .../TestInvertedMinor.py | 2 +- .../test/inverted_owg/TestInvertedOWG.py | 2 +- worlds/cv64/options.py | 4 ++- worlds/ffmq/Regions.py | 2 +- worlds/messenger/options.py | 8 ++--- worlds/minecraft/docs/minecraft_es.md | 2 +- worlds/minecraft/docs/minecraft_sv.md | 2 +- worlds/pokemon_rb/__init__.py | 2 +- worlds/pokemon_rb/options.py | 5 ++-- worlds/pokemon_rb/rules.py | 2 +- worlds/smz3/Options.py | 3 +- worlds/smz3/__init__.py | 2 +- worlds/stardew_valley/presets.py | 12 ++++---- 20 files changed, 75 insertions(+), 57 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 6456210e95dc..a0c243c0fd9d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -64,7 +64,6 @@ class MultiWorld(): state: CollectionState plando_options: PlandoOptions - accessibility: Dict[int, Options.Accessibility] early_items: Dict[int, Dict[str, int]] local_early_items: Dict[int, Dict[str, int]] local_items: Dict[int, Options.LocalItems] @@ -602,26 +601,22 @@ def fulfills_accessibility(self, state: Optional[CollectionState] = None): players: Dict[str, Set[int]] = { "minimal": set(), "items": set(), - "locations": set() + "full": set() } - for player, access in self.accessibility.items(): - players[access.current_key].add(player) + for player, world in self.worlds.items(): + players[world.options.accessibility.current_key].add(player) beatable_fulfilled = False - def location_condition(location: Location): + def location_condition(location: Location) -> bool: """Determine if this location has to be accessible, location is already filtered by location_relevant""" - if location.player in players["locations"] or (location.item and location.item.player not in - players["minimal"]): - return True - return False + return location.player in players["full"] or \ + (location.item and location.item.player not in players["minimal"]) - def location_relevant(location: Location): + def location_relevant(location: Location) -> bool: """Determine if this location is relevant to sweep.""" - if location.progress_type != LocationProgressType.EXCLUDED \ - and (location.player in players["locations"] or location.advancement): - return True - return False + return location.progress_type != LocationProgressType.EXCLUDED \ + and (location.player in players["full"] or location.advancement) def all_done() -> bool: """Check if all access rules are fulfilled""" diff --git a/Options.py b/Options.py index b5fb25ea34a0..d5e0ce1a550f 100644 --- a/Options.py +++ b/Options.py @@ -1144,18 +1144,35 @@ def __len__(self) -> int: class Accessibility(Choice): - """Set rules for reachability of your items/locations. + """ + Set rules for reachability of your items/locations. + + **Full:** ensure everything can be reached and acquired. - - **Locations:** ensure everything can be reached and acquired. - - **Items:** ensure all logically relevant items can be acquired. - - **Minimal:** ensure what is needed to reach your goal can be acquired. + **Minimal:** ensure what is needed to reach your goal can be acquired. """ display_name = "Accessibility" rich_text_doc = True - option_locations = 0 - option_items = 1 + option_full = 0 option_minimal = 2 alias_none = 2 + alias_locations = 0 + alias_items = 0 + default = 0 + + +class ItemsAccessibility(Accessibility): + """ + Set rules for reachability of your items/locations. + + **Full:** ensure everything can be reached and acquired. + + **Minimal:** ensure what is needed to reach your goal can be acquired. + + **Items:** ensure all logically relevant items can be acquired. Some items, such as keys, may be self-locking, and + some locations may be inaccessible. + """ + option_items = 1 default = 1 diff --git a/test/general/test_fill.py b/test/general/test_fill.py index 485007ff0d56..db24b706918c 100644 --- a/test/general/test_fill.py +++ b/test/general/test_fill.py @@ -174,8 +174,8 @@ def test_minimal_mixed_fill(self): player1 = generate_player_data(multiworld, 1, 3, 3) player2 = generate_player_data(multiworld, 2, 3, 3) - multiworld.accessibility[player1.id].value = multiworld.accessibility[player1.id].option_minimal - multiworld.accessibility[player2.id].value = multiworld.accessibility[player2.id].option_locations + multiworld.worlds[player1.id].options.accessibility.value = Accessibility.option_minimal + multiworld.worlds[player2.id].options.accessibility.value = Accessibility.option_full multiworld.completion_condition[player1.id] = lambda state: True multiworld.completion_condition[player2.id] = lambda state: state.has(player2.prog_items[2].name, player2.id) diff --git a/test/multiworld/test_multiworlds.py b/test/multiworld/test_multiworlds.py index 677f0de82930..5289cac6c357 100644 --- a/test/multiworld/test_multiworlds.py +++ b/test/multiworld/test_multiworlds.py @@ -69,7 +69,7 @@ def test_two_player_single_game_fills(self) -> None: for world in AutoWorldRegister.world_types.values(): self.multiworld = setup_multiworld([world, world], ()) for world in self.multiworld.worlds.values(): - world.options.accessibility.value = Accessibility.option_locations + world.options.accessibility.value = Accessibility.option_full self.assertSteps(gen_steps) with self.subTest("filling multiworld", seed=self.multiworld.seed): distribute_items_restrictive(self.multiworld) diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index ee3ebc587ce7..20dd18038a14 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -1,8 +1,8 @@ import typing from BaseClasses import MultiWorld -from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, \ - StartInventoryPool, PlandoBosses, PlandoConnections, PlandoTexts, FreeText, Removed +from Options import Choice, Range, DeathLink, DefaultOnToggle, FreeText, ItemsAccessibility, Option, \ + PlandoBosses, PlandoConnections, PlandoTexts, Removed, StartInventoryPool, Toggle from .EntranceShuffle import default_connections, default_dungeon_connections, \ inverted_default_connections, inverted_default_dungeon_connections from .Text import TextTable @@ -743,6 +743,7 @@ class ALttPPlandoTexts(PlandoTexts): alttp_options: typing.Dict[str, type(Option)] = { + "accessibility": ItemsAccessibility, "plando_connections": ALttPPlandoConnections, "plando_texts": ALttPPlandoTexts, "start_inventory_from_pool": StartInventoryPool, diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 67684a6f3ced..f596749ae669 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -2,6 +2,7 @@ import logging from typing import Iterator, Set +from Options import ItemsAccessibility from BaseClasses import Entrance, MultiWorld from worlds.generic.Rules import (add_item_rule, add_rule, forbid_item, item_name_in_location_names, location_item_name, set_rule, allow_self_locking_items) @@ -39,7 +40,7 @@ def set_rules(world): else: # Set access rules according to max glitches for multiworld progression. # Set accessibility to none, and shuffle assuming the no logic players can always win - world.accessibility[player] = world.accessibility[player].from_text("minimal") + world.accessibility[player].value = ItemsAccessibility.option_minimal world.progression_balancing[player].value = 0 else: @@ -377,7 +378,7 @@ def global_rules(multiworld: MultiWorld, player: int): or state.has("Cane of Somaria", player))) set_rule(multiworld.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) set_rule(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player)) - if multiworld.accessibility[player] != 'locations': + if multiworld.accessibility[player] != 'full': set_always_allow(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player) set_rule(multiworld.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) @@ -393,7 +394,7 @@ def global_rules(multiworld: MultiWorld, player: int): if state.has('Hookshot', player) else state._lttp_has_key('Small Key (Swamp Palace)', player, 4)) set_rule(multiworld.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player)) - if multiworld.accessibility[player] != 'locations': + if multiworld.accessibility[player] != 'full': allow_self_locking_items(multiworld.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)') set_rule(multiworld.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 5)) if not multiworld.small_key_shuffle[player] and multiworld.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: @@ -423,7 +424,7 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) set_rule(multiworld.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) set_rule(multiworld.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, player)) - if multiworld.accessibility[player] != 'locations': + if multiworld.accessibility[player] != 'full': allow_self_locking_items(multiworld.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)') set_rule(multiworld.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain add_rule(multiworld.get_location('Skull Woods - Prize', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) @@ -522,12 +523,12 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( location_item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 3)))) - if multiworld.accessibility[player] != 'locations': + if multiworld.accessibility[player] != 'full': set_always_allow(multiworld.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) set_rule(multiworld.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( location_item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 4))) - if multiworld.accessibility[player] != 'locations': + if multiworld.accessibility[player] != 'full': set_always_allow(multiworld.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) set_rule(multiworld.get_entrance('Palace of Darkness Maze Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6)) @@ -1200,7 +1201,7 @@ def tr_big_key_chest_keys_needed(state): # Must not go in the Chain Chomps chest - only 2 other chests available and 3+ keys required for all other chests forbid_item(world.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player) forbid_item(world.get_location('Turtle Rock - Pokey 2 Key Drop', player), 'Big Key (Turtle Rock)', player) - if world.accessibility[player] == 'locations': + if world.accessibility[player] == 'full': if world.big_key_shuffle[player] and can_reach_big_chest: # Must not go in the dungeon - all 3 available chests (Chomps, Big Chest, Crystaroller) must be keys to access laser bridge, and the big key is required first for location in ['Turtle Rock - Chain Chomps', 'Turtle Rock - Compass Chest', @@ -1214,7 +1215,7 @@ def tr_big_key_chest_keys_needed(state): location.place_locked_item(item) toss_junk_item(world, player) - if world.accessibility[player] != 'locations': + if world.accessibility[player] != 'full': set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player))) diff --git a/worlds/alttp/test/inverted/TestInverted.py b/worlds/alttp/test/inverted/TestInverted.py index 0a2aa7a18653..a0a654991b43 100644 --- a/worlds/alttp/test/inverted/TestInverted.py +++ b/worlds/alttp/test/inverted/TestInverted.py @@ -1,11 +1,11 @@ -from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool +from worlds.alttp.Dungeons import get_dungeon_item_pool from worlds.alttp.EntranceShuffle import link_inverted_entrances from worlds.alttp.InvertedRegions import create_inverted_regions from worlds.alttp.ItemPool import difficulties from worlds.alttp.Items import item_factory from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops -from test.TestBase import TestBase +from test.bases import TestBase from worlds.alttp.test import LTTPTestBase diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py index a8fa5c808c3b..bf25c5c9a164 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py @@ -6,7 +6,7 @@ from worlds.alttp.Options import GlitchesRequired from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops -from test.TestBase import TestBase +from test.bases import TestBase from worlds.alttp.test import LTTPTestBase diff --git a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py index bbdf0f792444..1de22b95e593 100644 --- a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py +++ b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py @@ -6,7 +6,7 @@ from worlds.alttp.Options import GlitchesRequired from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops -from test.TestBase import TestBase +from test.bases import TestBase from worlds.alttp.test import LTTPTestBase diff --git a/worlds/cv64/options.py b/worlds/cv64/options.py index 93b417ad26fd..07e86347bda6 100644 --- a/worlds/cv64/options.py +++ b/worlds/cv64/options.py @@ -1,5 +1,6 @@ from dataclasses import dataclass -from Options import OptionGroup, Choice, DefaultOnToggle, Range, Toggle, PerGameCommonOptions, StartInventoryPool +from Options import (OptionGroup, Choice, DefaultOnToggle, ItemsAccessibility, PerGameCommonOptions, Range, Toggle, + StartInventoryPool) class CharacterStages(Choice): @@ -521,6 +522,7 @@ class DeathLink(Choice): @dataclass class CV64Options(PerGameCommonOptions): + accessibility: ItemsAccessibility start_inventory_from_pool: StartInventoryPool character_stages: CharacterStages stage_shuffle: StageShuffle diff --git a/worlds/ffmq/Regions.py b/worlds/ffmq/Regions.py index f7b9b9eed4d8..c1d3d619ffaa 100644 --- a/worlds/ffmq/Regions.py +++ b/worlds/ffmq/Regions.py @@ -216,7 +216,7 @@ def stage_set_rules(multiworld): multiworld.worlds[player].options.accessibility == "minimal"]) * 3): for player in no_enemies_players: for location in vendor_locations: - if multiworld.worlds[player].options.accessibility == "locations": + if multiworld.worlds[player].options.accessibility == "full": multiworld.get_location(location, player).progress_type = LocationProgressType.EXCLUDED else: multiworld.get_location(location, player).access_rule = lambda state: False diff --git a/worlds/messenger/options.py b/worlds/messenger/options.py index 1f76dba4894a..59e694cd3963 100644 --- a/worlds/messenger/options.py +++ b/worlds/messenger/options.py @@ -3,15 +3,15 @@ from schema import And, Optional, Or, Schema -from Options import Accessibility, Choice, DeathLinkMixin, DefaultOnToggle, OptionDict, PerGameCommonOptions, \ +from Options import Choice, DeathLinkMixin, DefaultOnToggle, ItemsAccessibility, OptionDict, PerGameCommonOptions, \ PlandoConnections, Range, StartInventoryPool, Toggle, Visibility from .portals import CHECKPOINTS, PORTALS, SHOP_POINTS -class MessengerAccessibility(Accessibility): - default = Accessibility.option_locations +class MessengerAccessibility(ItemsAccessibility): # defaulting to locations accessibility since items makes certain items self-locking - __doc__ = Accessibility.__doc__.replace(f"default {Accessibility.default}", f"default {default}") + default = ItemsAccessibility.option_full + __doc__ = ItemsAccessibility.__doc__ class PortalPlando(PlandoConnections): diff --git a/worlds/minecraft/docs/minecraft_es.md b/worlds/minecraft/docs/minecraft_es.md index 3f2df6e7ba76..4f4899212240 100644 --- a/worlds/minecraft/docs/minecraft_es.md +++ b/worlds/minecraft/docs/minecraft_es.md @@ -29,7 +29,7 @@ name: TuNombre game: Minecraft # Opciones compartidas por todos los juegos: -accessibility: locations +accessibility: full progression_balancing: 50 # Opciones Especficicas para Minecraft diff --git a/worlds/minecraft/docs/minecraft_sv.md b/worlds/minecraft/docs/minecraft_sv.md index fd89d681ee37..ab8c1b5d8ea7 100644 --- a/worlds/minecraft/docs/minecraft_sv.md +++ b/worlds/minecraft/docs/minecraft_sv.md @@ -79,7 +79,7 @@ description: Template Name # Ditt spelnamn. Mellanslag kommer bli omplacerad med understräck och det är en 16-karaktärsgräns. name: YourName game: Minecraft -accessibility: locations +accessibility: full progression_balancing: 0 advancement_goal: few: 0 diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py index 5f527033289a..277d73b2258b 100644 --- a/worlds/pokemon_rb/__init__.py +++ b/worlds/pokemon_rb/__init__.py @@ -443,7 +443,7 @@ def pre_fill(self) -> None: self.multiworld.elite_four_pokedex_condition[self.player].total = \ int((len(reachable_mons) / 100) * self.multiworld.elite_four_pokedex_condition[self.player].value) - if self.multiworld.accessibility[self.player] == "locations": + if self.multiworld.accessibility[self.player] == "full": balls = [self.create_item(ball) for ball in ["Poke Ball", "Great Ball", "Ultra Ball"]] traps = [self.create_item(trap) for trap in item_groups["Traps"]] locations = [location for location in self.multiworld.get_locations(self.player) if "Pokedex - " in diff --git a/worlds/pokemon_rb/options.py b/worlds/pokemon_rb/options.py index bd6515913aca..54d486a6cf9f 100644 --- a/worlds/pokemon_rb/options.py +++ b/worlds/pokemon_rb/options.py @@ -1,4 +1,4 @@ -from Options import Toggle, Choice, Range, NamedRange, TextChoice, DeathLink +from Options import Toggle, Choice, Range, NamedRange, TextChoice, DeathLink, ItemsAccessibility class GameVersion(Choice): @@ -287,7 +287,7 @@ class AllPokemonSeen(Toggle): class DexSanity(NamedRange): """Adds location checks for Pokemon flagged "owned" on your Pokedex. You may specify a percentage of Pokemon to - have checks added. If Accessibility is set to locations, this will be the percentage of all logically reachable + have checks added. If Accessibility is set to full, this will be the percentage of all logically reachable Pokemon that will get a location check added to it. With items or minimal Accessibility, it will be the percentage of all 151 Pokemon. If Pokedex is required, the items for Pokemon acquired before acquiring the Pokedex can be found by talking to @@ -861,6 +861,7 @@ class RandomizePokemonPalettes(Choice): pokemon_rb_options = { + "accessibility": ItemsAccessibility, "game_version": GameVersion, "trainer_name": TrainerName, "rival_name": RivalName, diff --git a/worlds/pokemon_rb/rules.py b/worlds/pokemon_rb/rules.py index 21dceb75e8df..1d68f3148963 100644 --- a/worlds/pokemon_rb/rules.py +++ b/worlds/pokemon_rb/rules.py @@ -22,7 +22,7 @@ def prize_rule(i): item_rules["Celadon Prize Corner - Item Prize 2"] = prize_rule item_rules["Celadon Prize Corner - Item Prize 3"] = prize_rule - if multiworld.accessibility[player] != "locations": + if multiworld.accessibility[player] != "full": multiworld.get_location("Cerulean Bicycle Shop", player).always_allow = (lambda state, item: item.name == "Bike Voucher" and item.player == player) diff --git a/worlds/smz3/Options.py b/worlds/smz3/Options.py index ada463fa3629..8c5efc431f5c 100644 --- a/worlds/smz3/Options.py +++ b/worlds/smz3/Options.py @@ -1,5 +1,5 @@ import typing -from Options import Choice, Option, Toggle, DefaultOnToggle, Range +from Options import Choice, Option, Toggle, DefaultOnToggle, Range, ItemsAccessibility class SMLogic(Choice): """This option selects what kind of logic to use for item placement inside @@ -128,6 +128,7 @@ class EnergyBeep(DefaultOnToggle): smz3_options: typing.Dict[str, type(Option)] = { + "accessibility": ItemsAccessibility, "sm_logic": SMLogic, "sword_location": SwordLocation, "morph_location": MorphLocation, diff --git a/worlds/smz3/__init__.py b/worlds/smz3/__init__.py index d78c9f7d8224..690e5172a25c 100644 --- a/worlds/smz3/__init__.py +++ b/worlds/smz3/__init__.py @@ -244,7 +244,7 @@ def set_rules(self): set_rule(entrance, lambda state, region=region: region.CanEnter(state.smz3state[self.player])) for loc in region.Locations: l = self.locations[loc.Name] - if self.multiworld.accessibility[self.player] != 'locations': + if self.multiworld.accessibility[self.player] != 'full': l.always_allow = lambda state, item, loc=loc: \ item.game == "SMZ3" and \ loc.alwaysAllow(item.item, state.smz3state[self.player]) diff --git a/worlds/stardew_valley/presets.py b/worlds/stardew_valley/presets.py index e663241ac6af..cf6f87a1501c 100644 --- a/worlds/stardew_valley/presets.py +++ b/worlds/stardew_valley/presets.py @@ -58,7 +58,7 @@ easy_settings = { "progression_balancing": ProgressionBalancing.default, - "accessibility": Accessibility.option_items, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.option_community_center, FarmType.internal_name: "random", StartingMoney.internal_name: "very rich", @@ -104,7 +104,7 @@ medium_settings = { "progression_balancing": 25, - "accessibility": Accessibility.option_locations, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.option_community_center, FarmType.internal_name: "random", StartingMoney.internal_name: "rich", @@ -150,7 +150,7 @@ hard_settings = { "progression_balancing": 0, - "accessibility": Accessibility.option_locations, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.option_grandpa_evaluation, FarmType.internal_name: "random", StartingMoney.internal_name: "extra", @@ -196,7 +196,7 @@ nightmare_settings = { "progression_balancing": 0, - "accessibility": Accessibility.option_locations, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.option_community_center, FarmType.internal_name: "random", StartingMoney.internal_name: "vanilla", @@ -242,7 +242,7 @@ short_settings = { "progression_balancing": ProgressionBalancing.default, - "accessibility": Accessibility.option_items, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.option_bottom_of_the_mines, FarmType.internal_name: "random", StartingMoney.internal_name: "filthy rich", @@ -334,7 +334,7 @@ allsanity_settings = { "progression_balancing": ProgressionBalancing.default, - "accessibility": Accessibility.option_locations, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.default, FarmType.internal_name: "random", StartingMoney.internal_name: StartingMoney.default, From 7c8ea34a029e7e2a1683b7a318a65da5028800d3 Mon Sep 17 00:00:00 2001 From: GodlFire <46984098+GodlFire@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:32:17 -0600 Subject: [PATCH 04/98] Shivers: New features and removes two missed options using the old options API (#3287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds an option to have pot pieces placed local/non-local/anywhere Shivers nearly always finishes last in multiworld games due to the fact you need all 20 pot pieces to win and the pot pieces open very few location checks. This option allows the pieces to be placed locally. This should allow Shivers to be finished earlier. * New option: Choose how many ixupi captures are needed for goal completion New option: Choose how many ixupi captures are needed for goal completion * Fixes rule logic for location 'puzzle solved three floor elevator' Fixes rule logic for location 'puzzle solved three floor elevator'. Missing a parenthesis caused only the key requirement to be checked for the blue maze region. * Merge branch 'main' of https://github.com/GodlFire/Shivers * Revert "Merge branch 'main' of https://github.com/GodlFire/Shivers" This reverts commit bb08c3f0c2ef148fd24d7c7820cdfe936f7196e2. * Fixes issue with office elevator rule logic. * Bug fix, missing logic requirement for location 'Final Riddle: Guillotine Dropped' Bug fix, missing logic requirement for location 'Final Riddle: Guillotine Dropped' * Moves plaque location to front for better tracker referencing. * Tiki should be Shaman. * Hanging should be Gallows. * Merrick spelling. * Clarity change. * Changes new option to use new option API Changes new option to use new option API * Added sub regions for Ixupi -Added sub regions for Ixupi and moved ixupi capture checks into the sub region. -Added missing wax capture possible spot in Shaman room * Adds option for ixupi captures to be priority locations Adds option for ixupi captures to be priority locations * Consistency Consistency * Changes ixupi captures priority to default on toggle Changes ixupi captures priority to default on toggle * Docs update -Updated link to randomizer -Update some text to reflect the latest functionality -Replaced 'setting' with 'option' * New features/bug fixes -Adds an option to have completed pots in the item pool -Moved subterranean world information plaque to maze staircase * Cleanup Cleanup * Fixed name for moved location When moving a location and renaming it I forgot to fix the name in a second spot. * Squashed commit of the following: commit 630a3bdfb9414d8c57154f29253fce0cf67b6436 Merge: 8477d3c8 5e579200 Author: GodlFire <46984098+GodlFire@users.noreply.github.com> Date: Mon Apr 1 19:08:48 2024 -0600 Merge pull request #10 from ArchipelagoMW/main Merge main into branch commit 5e5792009cd3089ae61c5fdd208de1b79d183cb4 Author: Alchav <59858495+Alchav@users.noreply.github.com> Date: Mon Apr 1 12:08:21 2024 -0500 LttP: delete playerSettings.yaml (#3062) commit 9aeeeb077a9e894cd2ace51b58d537bcf7607d5b Author: CaitSith2 Date: Mon Apr 1 06:07:56 2024 -0700 ALttP: Re-mark light/dark world regions after applying plando connections (#2964) commit 35458380e6e08eab85203942b6415fd964907c84 Author: Bryce Wilson Date: Mon Apr 1 07:07:11 2024 -0600 Pokemon Emerald: Fix wonder trade race condition (#2983) commit 4ac1866689d01dc6693866ee8b1236ad6fea114b Author: Alchav <59858495+Alchav@users.noreply.github.com> Date: Mon Apr 1 08:06:31 2024 -0500 ALTTP: Skull Woods Inverted fix (#2980) commit 4aa03da66e1a8c99fc31c163c1a23fb0bd772c15 Author: Fabian Dill Date: Mon Apr 1 15:06:02 2024 +0200 Factorio: fix attempting to create savegame with not filename safe characters (#2842) commit 24a03bc8b6b406c0925eedf415dcef47e17fdbaa Author: Silvris <58583688+Silvris@users.noreply.github.com> Date: Mon Apr 1 08:02:26 2024 -0500 KDL3: fix shuffled animals not actually being random (#3060) commit f813a7005fadb1c56bb93fee6147b63d9df2b720 Author: Aaron Wagener Date: Sun Mar 31 11:11:10 2024 -0500 The Messenger: update docs formatting and fix outdated info (#3033) * The Messenger: update docs formatting and fix outdated info * address review feedback * 120 chars commit 2a0b7e0def5c00cc2ac273b22581b3cde3b6f6a6 Author: LiquidCat64 <74896918+LiquidCat64@users.noreply.github.com> Date: Sun Mar 31 09:55:55 2024 -0600 CV64: A couple of very small docs corrections. (#3057) commit 03d47e460e434b897b313c2ba452d785ecbacebe Author: Ixrec Date: Sun Mar 31 16:55:08 2024 +0100 A Short Hike: Clarify installation instructions (#3058) * Clarify installation instructions * don't mention 'config' folder since it isn't created until the game starts commit e546c0f7ff2456ddb919a1b65a437a1c61b07479 Author: Silvris <58583688+Silvris@users.noreply.github.com> Date: Sun Mar 31 10:50:31 2024 -0500 Yoshi's Island: add patch suffix (#3061) commit 2ec93ba82a969865a8addc98feb076898978c8e3 Author: Bryce Wilson Date: Sun Mar 31 09:48:59 2024 -0600 Pokemon Emerald: Fix inconsistent location name (#3065) commit 4e3d3963941934c77573e6e0b699edf9e26cd647 Author: Aaron Wagener Date: Sun Mar 31 10:47:11 2024 -0500 The Messenger: Fix precollected notes not being removed from the itempool (#3066) * The Messenger: fix precollected notes not being properly removed from pool * The Messenger: bump required client version commit 72c53513f8bdab5506ffa972c1bf6f8573f097d7 Author: Fabian Dill Date: Sun Mar 31 03:57:59 2024 +0200 WebHost: fix /check creating broken yaml files if files don't end with a newline (#3063) commit b7ac6a4cbd54d5f8e6672e4a6c6ea708e7e6d4de Author: Aaron Wagener Date: Fri Mar 29 20:14:53 2024 -0500 The Messenger: Fix various portal shuffle issues (#2976) * put constants in a bit more sensical order * fix accidental incorrect scoping * fix plando rules not being respected * add docstrings for the plando functions * fix the portal output pools being overwritten * use shuffle and pop instead of removing by content so plando can go to the same area twice * move portal pool rebuilding outside mapping creation * remove plando_connection cleansing since it isn't shared with transition shuffle commit 5f0112e78365d19f04e22af92d6ad1f52d264b1f Author: Zach Parks Date: Fri Mar 29 19:13:51 2024 -0500 Tracker: Add starting inventory to trackers and received items table. (#3051) commit bb481256de2a511d3b114f164061d440026be4c4 Author: Aaron Wagener Date: Thu Mar 28 21:48:40 2024 -0500 Core: Make fill failure error more human parseable (#3023) commit 301d9de9758e360ccec5399f3f9d922f1c034e45 Author: Aaron Wagener Date: Thu Mar 28 19:31:59 2024 -0500 Docs: adding games rework (#2892) * Docs: complete adding games.md rework * remove all the now unused images * review changes * address medic's review * address more comments commit 9dc708978bd00890afcd3426f829a5ac53cbe136 Author: Trevor L <80716066+TRPG0@users.noreply.github.com> Date: Thu Mar 28 18:26:58 2024 -0600 Hylics 2: Fix invalid multiworld data, use `self.random` instead of `self.multiworld.random` (#3001) * Hylics 2: Fixes * Rewrite loop commit 4391d1f4c13cdf2295481d8c51f9ef8f58bf8347 Author: Bryce Wilson Date: Thu Mar 28 18:05:39 2024 -0600 Pokemon Emerald: Fix opponents learning non-randomized TMs (#3025) commit 5d9d4ed9f1e44309f1b53f12413ad260f1b6c983 Author: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Fri Mar 29 01:01:31 2024 +0100 SoE: update to pyevermizer v0.48.0 (#3050) commit c97215e0e755224593fdd00894731b59aa415e19 Author: Scipio Wright Date: Thu Mar 28 17:23:37 2024 -0400 TUNIC: Minor refactor of the vanilla_portals function (#3009) * Remove unused, change an if to an elif * Remove unused import commit eb66886a908ad75bbe71fac9bb81a0177e05e816 Author: Alchav <59858495+Alchav@users.noreply.github.com> Date: Thu Mar 28 16:23:01 2024 -0500 SC2: Don't Filter Excluded Victory Locations (#3018) commit de860623d17d274289e3e4ab13650f2382e2e0b8 Author: Fabian Dill Date: Thu Mar 28 22:21:56 2024 +0100 Core: differentiate between unknown worlds and broken worlds in error message (#2903) commit 74b2bf51613a968eb57a5b138a7ad191324b2dd8 Author: Bryce Wilson Date: Thu Mar 28 15:20:55 2024 -0600 Pokemon Emerald: Exclude norman trainer location during norman goal (#3038) commit 74ac66b03228988d0885cff556f962a04873cc54 Author: BadMagic100 Date: Thu Mar 28 08:49:19 2024 -0700 Hollow Knight: 0.4.5 doc revamp and default options tweaks (#2982) Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> commit 80d7ac416493a540548aad67981202a1483b5e53 Author: Silvris <58583688+Silvris@users.noreply.github.com> Date: Thu Mar 28 09:41:32 2024 -0500 KDL3: RC1 Fixes and Enhancement (#3022) * fix cloudy park 4 rule, zero deathlink message * remove redundant door_shuffle bool when generic ER gets in, this whole function gets rewritten. So just clean it a little now. * properly fix deathlink messages, fix fill error * update docs commit 77311719fa0fa5b67fe92f437c3cfed16bd5136f Author: Ziktofel Date: Thu Mar 28 15:38:34 2024 +0100 SC2: Fix HERC upgrades (#3044) commit cfc1541be9e92f1f59b21f4a81f96fc88f4d9f7e Author: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu Mar 28 15:19:32 2024 +0100 Docs: Mention the "last received item index" paradigm in the network protocol docs (#2989) Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> commit 4d954afd9b2311248083fc389ac737995985be86 Author: Scipio Wright Date: Thu Mar 28 10:11:20 2024 -0400 TUNIC: Add link to AP plando guide to connection plando section of game page (#2993) commit 17748a4bf1cfd5cc11c6596a09ffc1f01434340f Author: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> Date: Thu Mar 28 10:00:10 2024 -0400 Launcher, Docs: Update UI and Set-Up Guide to Reference Options (#2950) commit 9182fe563fc18ed4ccaa8370cfed88407140398e Author: Entropynines <163603868+Entropynines@users.noreply.github.com> Date: Thu Mar 28 06:56:35 2024 -0700 README: Remove outdated information about launchers (#2966) Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> commit bcf223081facd030aa706dc7430a72bcf2fdadc9 Author: t3hf1gm3nt <59876300+t3hf1gm3nt@users.noreply.github.com> Date: Thu Mar 28 09:54:56 2024 -0400 TLOZ: Fix markdown issue with game info page (#2985) commit fa93488f3fceac6c2f51851766543cab3ba121e6 Author: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Thu Mar 28 09:46:00 2024 -0400 Docs: Consistent naming for "connection plando" (#2994) commit db15dd4bde442aad99048224bdb0d7dc28c26717 Author: chandler05 <66492208+chandler05@users.noreply.github.com> Date: Thu Mar 28 08:45:19 2024 -0500 A Short Hike: Fix incorrect info in docs (#3016) commit 01cdb0d761a82349afaeb7222b4b59cb1766f4a0 Author: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Thu Mar 28 09:44:23 2024 -0400 SMW: Update World Doc for v2.0 Features (#3034) Co-authored-by: Scipio Wright commit d0ac2b744eac438570e6a2333e76fa212be66534 Author: panicbit Date: Thu Mar 28 10:11:26 2024 +0100 LADX: fix local and non-local instrument placement (#2987) * LADX: fix local and non-local instrument placement * change confusing variable name commit 14f5f0127eb753eaf0431a54bebc82f5e74a1cb9 Author: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> Date: Thu Mar 28 04:42:35 2024 -0400 Stardew Valley: Fix potential soft lock with vanilla tools and entrance randomizer + Performance improvement for vanilla tool/skills (#3002) * fix vanilla tool fishing rod requiring metal bars fix vanilla skill requiring previous level (it's always the same rule or more restrictive) * add test to ensure fishing rod need fish shop * fishing rod should be indexed from 0 like a mentally sane person would do. * fishing rod 0 isn't real, but it definitely can hurt you. * reeeeeeeee commit cf133dde7275e171d388fb466b9ed719ab7ed7c8 Author: Bryce Wilson Date: Thu Mar 28 02:32:27 2024 -0600 Pokemon Emerald: Fix typo (#3020) commit ca1812181106a3645e7f7af417590024b377b25e Author: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> Date: Thu Mar 28 04:27:49 2024 -0400 Stardew Valley: Fix generation fail with SVE and entrance rando when Wizard Tower is in place of Sprite Spring (#2970) commit 1d4512590e0b78355e5c10174a9c6749e1098a72 Author: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Wed Mar 27 21:09:09 2024 +0100 requirements.txt: _ instead of - to make PyCharm happy (#3043) commit f7b415dab00338443b68eba51f42614fc40b9152 Author: agilbert1412 Date: Tue Mar 26 19:40:58 2024 +0300 Stardew valley: Game version documentation (#2990) Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> commit 702f006c848c05b847e85f7dbedeef68b70cdcc6 Author: LiquidCat64 <74896918+LiquidCat64@users.noreply.github.com> Date: Tue Mar 26 07:31:36 2024 -0600 CV64: Change all mentions of "settings" to "options" and fix a broken link (#3015) commit 98ce8f8844fd0c62214a5774609382cf6a6bc829 Author: Yussur Mustafa Oraji Date: Tue Mar 26 14:29:25 2024 +0100 sm64ex: New Options API and WebHost fix (#2979) commit ea47b90367b4a220c346d8057f3aeb4207d226a1 Author: Scipio Wright Date: Tue Mar 26 09:25:41 2024 -0400 TUNIC: You can grapple down here without the ladder, neat (#3019) commit bf3856866c5ea385d0ac58014c71addfdc92637e Author: agilbert1412 Date: Sun Mar 24 23:53:49 2024 +0300 Stardew Valley: presets with some of the new available values for existing settings to make them more accurate (#3014) commit c0368ae0d48b4b2807c5238aeb7b14937282fc3e Author: Phaneros <31861583+MatthewMarinets@users.noreply.github.com> Date: Sun Mar 24 13:53:20 2024 -0700 SC2: Fixed missing upgrade from custom tracker (#3013) commit 36c83073ad8c2ae1912d390ee3976ba0e2eb3f4a Author: Salzkorn Date: Sun Mar 24 21:52:41 2024 +0100 SC2 Tracker: Fix grouped items pointing at wrong item IDs (#2992) commit 2b24539ea5b387a3b62063c8177c373e2e3f8389 Author: Ziktofel Date: Sun Mar 24 21:52:16 2024 +0100 SC2 Tracker: Use level tinting to let the player know which level he has of Replenishable Magazine (#2986) commit 7e904a1c78c91fb502706fe030a1f1765f734de4 Author: Ziktofel Date: Sun Mar 24 21:51:46 2024 +0100 SC2: Fix Kerrigan presence resolving when deciding which races should be used (#2978) commit bdd498db2321417374d572bff8beede083fef2b2 Author: Alchav <59858495+Alchav@users.noreply.github.com> Date: Fri Mar 22 15:36:27 2024 -0500 ALTTP: Fix #2290's crashes (#2973) commit 355223b8f0af1ee729ffa8b53eb717aa5bf283a4 Author: PinkSwitch <52474902+PinkSwitch@users.noreply.github.com> Date: Fri Mar 22 15:35:00 2024 -0500 Yoshi's Island: Implement New Game (#2141) Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com> Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com> Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> commit aaa3472d5d8d8a7a710bd38386d9eb34046a5578 Author: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri Mar 22 21:30:51 2024 +0100 The Witness: Fix seed bleed issue (#3008) commit 96d93c1ae313bb031e983c0d40d8be199b302df1 Author: chandler05 <66492208+chandler05@users.noreply.github.com> Date: Fri Mar 22 15:30:23 2024 -0500 A Short Hike: Add option to customize filler coin count (#3004) Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> commit ca549df20a0a07c30ee2e1bbc2498492b919604d Author: Silvris <58583688+Silvris@users.noreply.github.com> Date: Fri Mar 22 15:29:24 2024 -0500 CommonClient: fix hint tab overlapping (#2957) Co-authored-by: Remy Jette commit 44988d430dc7d91eaeac7aad681dc024bc19ccce Author: Star Rauchenberger Date: Fri Mar 22 15:28:41 2024 -0500 Lingo: Add trap weights option (#2837) commit 11b32f17abebc08a6140506a375179f8a46bcfe6 Author: Danaël V <104455676+ReverM@users.noreply.github.com> Date: Fri Mar 22 12:46:14 2024 -0400 Docs: replacing "setting" to "option" in world docs (#2622) * Update contributing.md * Update contributing.md * Update contributing.md * Update contributing.md * Update contributing.md * Update contributing.md Added non-AP World specific information * Update contributing.md Fixed broken link * Some minor touchups * Update Contributing.md Draft for version with picture * Update contributing.md Small word change * Minor updates for conciseness, mostly * Changed all instances of settings to options in info and setup guides I combed through all world docs and swapped "setting" to "option" when this was refering to yaml options. I also changed a leftover "setting" in option.py * Update contributing.md * Update contributing.md * Update setup_en.md Woops I forgot one * Update Options.py Reverted changes regarding options.py * Update worlds/noita/docs/en_Noita.md Co-authored-by: Scipio Wright * Update worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md revert change waiting for that page to be updated * Update worlds/witness/docs/setup_en.md * Update worlds/witness/docs/en_The Witness.md * Update worlds/soe/docs/multiworld_en.md Fixed Typo Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Update worlds/witness/docs/en_The Witness.md * Update worlds/adventure/docs/en_Adventure.md * Update worlds/witness/docs/setup_en.md * Updated Stardew valley to hopefully get rid of the merge conflicts * Didn't work :dismay: * Delete worlds/sc2wol/docs/setup_en.md I think this will fix the merge issue * Now it should work * Woops --------- Co-authored-by: Scipio Wright Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> commit 218cd45844f9d733618af9088941156cd79b80bc Author: Silvris <58583688+Silvris@users.noreply.github.com> Date: Fri Mar 22 03:02:38 2024 -0500 APProcedurePatch: fix RLE/COPY incorrect sizing (#3006) * change class variables to instance variables * Update worlds/Files.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Update worlds/Files.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * move required_extensions to tuple * fix missing tuple ellipsis * fix classvar mixup * rename tokens to _tokens. use hasattr * type hint cleanup * Update Files.py * check using isinstance instead * Update Files.py --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> commit 4196bde597cdbb6186ff614294fd54ff043a0c99 Author: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Thu Mar 21 16:38:36 2024 -0400 Docs: Fixing special_range_names example (#3005) commit 40f843f54d5970302caeb2a21b76a4845cf5c0ed Author: Star Rauchenberger Date: Thu Mar 21 11:00:53 2024 -0500 Lingo: Minor game data fixes (#3003) commit da333fbb0c88feedd4821a7bade3f56028a02111 Author: GodlFire <46984098+GodlFire@users.noreply.github.com> Date: Thu Mar 21 09:52:16 2024 -0600 Shivers: Adds missing logic rule for skull dial door location (#2997) commit 43084da23c719133fcae672e20c9b046e6ef8067 Author: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu Mar 21 16:51:29 2024 +0100 The Witness: Fix newlines in Witness option tooltips (#2971) commit 14816743fca366b52422ccb19add59d4960f17a3 Author: Scipio Wright Date: Thu Mar 21 11:50:07 2024 -0400 TUNIC: Shuffle Ladders option (#2919) commit 30a0aa2c85a7015e2072b5781ed1078965f62f4b Author: Star Rauchenberger Date: Thu Mar 21 10:46:53 2024 -0500 Lingo: Add item/location groups (#2789) commit f4b7c28a33bb163768871616023a8cf3879840b4 Author: Silvris <58583688+Silvris@users.noreply.github.com> Date: Wed Mar 20 17:45:32 2024 -0500 APProcedurePatch: hotfix changing class variables to instance variables (#2996) * change class variables to instance variables * Update worlds/Files.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Update worlds/Files.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * move required_extensions to tuple * fix missing tuple ellipsis * fix classvar mixup * rename tokens to _tokens. use hasattr * type hint cleanup * Update Files.py * check using isinstance instead --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> commit 12864f7b24028fa56135e599f0fe1642c9d2d377 Author: chandler05 <66492208+chandler05@users.noreply.github.com> Date: Wed Mar 20 22:44:09 2024 +0100 A Short Hike: Implement New Game (#2577) commit db02e9d2aabc0f4c1302ac761b3f5547ef00c7c5 Author: LiquidCat64 <74896918+LiquidCat64@users.noreply.github.com> Date: Wed Mar 20 15:03:25 2024 -0600 Castlevania 64: Implement New Game (#2472) commit 32315776ac0ac1a714eb9d58688c479e2038c658 Author: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> Date: Wed Mar 20 16:57:45 2024 -0400 Stardew Valley: Fix extended family legendary fishes being locations with fishsanity set to exclude legendary (#2967) commit e9620bea777ff1008a09c24a70bf523c94f22c29 Author: Magnemania <89949176+Magnemania@users.noreply.github.com> Date: Wed Mar 20 16:56:00 2024 -0400 SM64: Goal Logic and Hint Bugfixes (#2886) commit 183ca35bbaf6c805fdb53396d21d0cba34f9cc5e Author: qwint Date: Wed Mar 20 08:39:37 2024 -0500 CommonClient: Port Casting Bug (#2975) commit fcaaa197a19a3be03965c504ca78dd2c21ce1f84 Author: TheLX5 Date: Wed Mar 20 05:56:19 2024 -0700 SMW: Fixes for Bowser being defeatable on Egg Hunt and CI2 DC room access (#2981) commit 8f7b63a787a0ef05625ae2fad1768251aced0c87 Author: TheLX5 Date: Wed Mar 20 05:56:04 2024 -0700 SMW: Blocksanity logic fixes (#2988) commit 6f64bb98693556ac2635791381cc9651c365b324 Author: Scipio Wright Date: Wed Mar 20 08:46:31 2024 -0400 Noita: Remove newline from option description so it doesn't look bad on webhost (#2969) commit d0a9d0e2d1df641668f4f806b45f9577e69229f6 Author: Bryce Wilson Date: Wed Mar 20 06:43:13 2024 -0600 Pokemon Emerald: Bump required client version (#2963) commit 94650a02de62956eee8e7e41f61e8a41506b5842 Author: Silvris <58583688+Silvris@users.noreply.github.com> Date: Tue Mar 19 17:08:29 2024 -0500 Core: implement APProcedurePatch and APTokenMixin (#2536) * initial work on procedure patch * more flexibility load default procedure for version 5 patches add args for procedure add default extension for tokens and bsdiff allow specifying additional required extensions for generation * pushing current changes to go fix tloz bug * move tokens into a separate inheritable class * forgot the commit to remove token from ProcedurePatch * further cleaning from bad commit * start on docstrings * further work on docstrings and typing * improve docstrings * fix incorrect docstring * cleanup * clean defaults and docstring * define interface that has only the bare minimum required for `Patch.create_rom_file` * change to dictionary.get * remove unnecessary if statement * update to explicitly check for procedure, restore compatible version and manual override * Update Files.py * remove struct uses * ensure returning bytes, add token type checking * Apply suggestions from code review Co-authored-by: Doug Hoskisson * pep8 --------- Co-authored-by: beauxq Co-authored-by: Doug Hoskisson * Changes pot_completed_list to a instance variable instead of global. Changes pot_completed_list to a instance variable instead of global. The global variable was unintentional and was causing missmatch in pre_fill which would cause generation error. * Removing deprecated options getter * Adds back fix from main branch Adds back fix from main branch * Removing messenger changes that somehow got on my branch? Removing messenger changes that somehow got on my branch? * Removing messenger changes that are somehow on the Shivers branch Removing messenger changes that are somehow on the Shivers branch * Still trying to remove Messenger changes on Shivers branch Still trying to remove Messenger changes on Shivers branch * Review comments addressed. Early lobby access set as default. Review comments addressed. Early lobby access set as default. * Review comments addressed Review comments addressed * Review comments addressed. Option for priority locations removed. Option to have ixupi captures a priority has been removed and can be added again if Priority Fill is changed. See Issues #3467. * Minor Change Minor Change * Fixed ID 10 T Error Fixed ID 10 T Error * Front door option added to slot data Front door option added to slot data * Add missing .value on slot data Add missing .value on slot data * Small change to slot data Small change to slot data * Small change to slot data Why didn't this change get pushed github... * Forgot list Forgot list --------- Co-authored-by: Kory Dondzila Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/shivers/Items.py | 60 ++++++++++++++++-------- worlds/shivers/Options.py | 63 ++++++++++++++++++++++---- worlds/shivers/Rules.py | 55 ++++++++++++---------- worlds/shivers/__init__.py | 73 +++++++++++++++++++++++++----- worlds/shivers/data/locations.json | 46 +++++++++++++------ worlds/shivers/data/regions.json | 72 +++++++++++++++++++++-------- worlds/shivers/docs/en_Shivers.md | 8 ++-- worlds/shivers/docs/setup_en.md | 2 +- 8 files changed, 276 insertions(+), 103 deletions(-) diff --git a/worlds/shivers/Items.py b/worlds/shivers/Items.py index 3b403be5cb76..10d234d450bb 100644 --- a/worlds/shivers/Items.py +++ b/worlds/shivers/Items.py @@ -33,28 +33,38 @@ class ItemData(typing.NamedTuple): "Lightning Pot Top": ItemData(SHIVERS_ITEM_ID_OFFSET + 17, "pot"), "Sand Pot Top": ItemData(SHIVERS_ITEM_ID_OFFSET + 18, "pot"), "Metal Pot Top": ItemData(SHIVERS_ITEM_ID_OFFSET + 19, "pot"), + "Water Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 20, "pot_type2"), + "Wax Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 21, "pot_type2"), + "Ash Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 22, "pot_type2"), + "Oil Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 23, "pot_type2"), + "Cloth Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 24, "pot_type2"), + "Wood Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 25, "pot_type2"), + "Crystal Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 26, "pot_type2"), + "Lightning Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 27, "pot_type2"), + "Sand Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 28, "pot_type2"), + "Metal Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 29, "pot_type2"), #Keys - "Key for Office Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 20, "key"), - "Key for Bedroom Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 21, "key"), - "Key for Three Floor Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 22, "key"), - "Key for Workshop": ItemData(SHIVERS_ITEM_ID_OFFSET + 23, "key"), - "Key for Office": ItemData(SHIVERS_ITEM_ID_OFFSET + 24, "key"), - "Key for Prehistoric Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 25, "key"), - "Key for Greenhouse Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 26, "key"), - "Key for Ocean Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 27, "key"), - "Key for Projector Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 28, "key"), - "Key for Generator Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 29, "key"), - "Key for Egypt Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 30, "key"), - "Key for Library Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 31, "key"), - "Key for Shaman Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 32, "key"), - "Key for UFO Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 33, "key"), - "Key for Torture Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 34, "key"), - "Key for Puzzle Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 35, "key"), - "Key for Bedroom": ItemData(SHIVERS_ITEM_ID_OFFSET + 36, "key"), - "Key for Underground Lake Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 37, "key"), - "Key for Janitor Closet": ItemData(SHIVERS_ITEM_ID_OFFSET + 38, "key"), - "Key for Front Door": ItemData(SHIVERS_ITEM_ID_OFFSET + 39, "key-optional"), + "Key for Office Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 30, "key"), + "Key for Bedroom Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 31, "key"), + "Key for Three Floor Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 32, "key"), + "Key for Workshop": ItemData(SHIVERS_ITEM_ID_OFFSET + 33, "key"), + "Key for Office": ItemData(SHIVERS_ITEM_ID_OFFSET + 34, "key"), + "Key for Prehistoric Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 35, "key"), + "Key for Greenhouse Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 36, "key"), + "Key for Ocean Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 37, "key"), + "Key for Projector Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 38, "key"), + "Key for Generator Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 39, "key"), + "Key for Egypt Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 40, "key"), + "Key for Library Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 41, "key"), + "Key for Shaman Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 42, "key"), + "Key for UFO Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 43, "key"), + "Key for Torture Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 44, "key"), + "Key for Puzzle Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 45, "key"), + "Key for Bedroom": ItemData(SHIVERS_ITEM_ID_OFFSET + 46, "key"), + "Key for Underground Lake Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 47, "key"), + "Key for Janitor Closet": ItemData(SHIVERS_ITEM_ID_OFFSET + 48, "key"), + "Key for Front Door": ItemData(SHIVERS_ITEM_ID_OFFSET + 49, "key-optional"), #Abilities "Crawling": ItemData(SHIVERS_ITEM_ID_OFFSET + 50, "ability"), @@ -83,6 +93,16 @@ class ItemData(typing.NamedTuple): "Lightning Pot Top DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 87, "potduplicate"), "Sand Pot Top DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 88, "potduplicate"), "Metal Pot Top DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 89, "potduplicate"), + "Water Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 140, "potduplicate_type2"), + "Wax Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 141, "potduplicate_type2"), + "Ash Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 142, "potduplicate_type2"), + "Oil Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 143, "potduplicate_type2"), + "Cloth Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 144, "potduplicate_type2"), + "Wood Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 145, "potduplicate_type2"), + "Crystal Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 146, "potduplicate_type2"), + "Lightning Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 147, "potduplicate_type2"), + "Sand Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 148, "potduplicate_type2"), + "Metal Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 149, "potduplicate_type2"), #Filler "Empty": ItemData(SHIVERS_ITEM_ID_OFFSET + 90, "filler"), diff --git a/worlds/shivers/Options.py b/worlds/shivers/Options.py index b70882f9a545..2f33eb18e5d1 100644 --- a/worlds/shivers/Options.py +++ b/worlds/shivers/Options.py @@ -1,21 +1,37 @@ -from Options import Choice, DefaultOnToggle, Toggle, PerGameCommonOptions +from Options import Choice, DefaultOnToggle, Toggle, PerGameCommonOptions, Range from dataclasses import dataclass +class IxupiCapturesNeeded(Range): + """ + Number of Ixupi Captures needed for goal condition. + """ + display_name = "Number of Ixupi Captures Needed" + range_start = 1 + range_end = 10 + default = 10 + class LobbyAccess(Choice): - """Chooses how keys needed to reach the lobby are placed. + """ + Chooses how keys needed to reach the lobby are placed. - Normal: Keys are placed anywhere - Early: Keys are placed early - - Local: Keys are placed locally""" + - Local: Keys are placed locally + """ display_name = "Lobby Access" option_normal = 0 option_early = 1 option_local = 2 + default = 1 class PuzzleHintsRequired(DefaultOnToggle): - """If turned on puzzle hints will be available before the corresponding puzzle is required. For example: The Shaman - Drums puzzle will be placed after access to the security cameras which give you the solution. Turning this off - allows for greater randomization.""" + """ + If turned on puzzle hints/solutions will be available before the corresponding puzzle is required. + + For example: The Red Door puzzle will be logically required only after access to the Beth's Address Book which gives you the solution. + + Turning this off allows for greater randomization. + """ display_name = "Puzzle Hints Required" class InformationPlaques(Toggle): @@ -26,7 +42,9 @@ class InformationPlaques(Toggle): display_name = "Include Information Plaques" class FrontDoorUsable(Toggle): - """Adds a key to unlock the front door of the museum.""" + """ + Adds a key to unlock the front door of the museum. + """ display_name = "Front Door Usable" class ElevatorsStaySolved(DefaultOnToggle): @@ -37,7 +55,9 @@ class ElevatorsStaySolved(DefaultOnToggle): display_name = "Elevators Stay Solved" class EarlyBeth(DefaultOnToggle): - """Beth's body is open at the start of the game. This allows any pot piece to be placed in the slide and early checks on the second half of the final riddle.""" + """ + Beth's body is open at the start of the game. This allows any pot piece to be placed in the slide and early checks on the second half of the final riddle. + """ display_name = "Early Beth" class EarlyLightning(Toggle): @@ -47,9 +67,34 @@ class EarlyLightning(Toggle): """ display_name = "Early Lightning" +class LocationPotPieces(Choice): + """ + Chooses where pot pieces will be located within the multiworld. + - Own World: Pot pieces will be located within your own world + - Different World: Pot pieces will be located in another world + - Any World: Pot pieces will be located in any world + """ + display_name = "Location of Pot Pieces" + option_own_world = 0 + option_different_world = 1 + option_any_world = 2 + +class FullPots(Choice): + """ + Chooses if pots will be in pieces or already completed + - Pieces: Only pot pieces will be added to the item pool + - Complete: Only completed pots will be added to the item pool + - Mixed: Each pot will be randomly chosen to be pieces or already completed. + """ + display_name = "Full Pots" + option_pieces = 0 + option_complete = 1 + option_mixed = 2 + @dataclass class ShiversOptions(PerGameCommonOptions): + ixupi_captures_needed: IxupiCapturesNeeded lobby_access: LobbyAccess puzzle_hints_required: PuzzleHintsRequired include_information_plaques: InformationPlaques @@ -57,3 +102,5 @@ class ShiversOptions(PerGameCommonOptions): elevators_stay_solved: ElevatorsStaySolved early_beth: EarlyBeth early_lightning: EarlyLightning + location_pot_pieces: LocationPotPieces + full_pots: FullPots diff --git a/worlds/shivers/Rules.py b/worlds/shivers/Rules.py index 3dc4f51c47a2..5288fa2c9c3f 100644 --- a/worlds/shivers/Rules.py +++ b/worlds/shivers/Rules.py @@ -8,58 +8,58 @@ def water_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Lobby", "Region", player) or (state.can_reach("Janitor Closet", "Region", player) and cloth_capturable(state, player))) \ - and state.has_all({"Water Pot Bottom", "Water Pot Top", "Water Pot Bottom DUPE", "Water Pot Top DUPE"}, player) + return state.has_all({"Water Pot Bottom", "Water Pot Top", "Water Pot Bottom DUPE", "Water Pot Top DUPE"}, player) or \ + state.has_all({"Water Pot Complete", "Water Pot Complete DUPE"}, player) def wax_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Library", "Region", player) or state.can_reach("Anansi", "Region", player)) \ - and state.has_all({"Wax Pot Bottom", "Wax Pot Top", "Wax Pot Bottom DUPE", "Wax Pot Top DUPE"}, player) + return state.has_all({"Wax Pot Bottom", "Wax Pot Top", "Wax Pot Bottom DUPE", "Wax Pot Top DUPE"}, player) or \ + state.has_all({"Wax Pot Complete", "Wax Pot Complete DUPE"}, player) def ash_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Office", "Region", player) or state.can_reach("Burial", "Region", player)) \ - and state.has_all({"Ash Pot Bottom", "Ash Pot Top", "Ash Pot Bottom DUPE", "Ash Pot Top DUPE"}, player) + return state.has_all({"Ash Pot Bottom", "Ash Pot Top", "Ash Pot Bottom DUPE", "Ash Pot Top DUPE"}, player) or \ + state.has_all({"Ash Pot Complete", "Ash Pot Complete DUPE"}, player) def oil_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Prehistoric", "Region", player) or state.can_reach("Tar River", "Region", player)) \ - and state.has_all({"Oil Pot Bottom", "Oil Pot Top", "Oil Pot Bottom DUPE", "Oil Pot Top DUPE"}, player) + return state.has_all({"Oil Pot Bottom", "Oil Pot Top", "Oil Pot Bottom DUPE", "Oil Pot Top DUPE"}, player) or \ + state.has_all({"Oil Pot Complete", "Oil Pot Complete DUPE"}, player) def cloth_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Egypt", "Region", player) or state.can_reach("Burial", "Region", player) or state.can_reach("Janitor Closet", "Region", player)) \ - and state.has_all({"Cloth Pot Bottom", "Cloth Pot Top", "Cloth Pot Bottom DUPE", "Cloth Pot Top DUPE"}, player) + return state.has_all({"Cloth Pot Bottom", "Cloth Pot Top", "Cloth Pot Bottom DUPE", "Cloth Pot Top DUPE"}, player) or \ + state.has_all({"Cloth Pot Complete", "Cloth Pot Complete DUPE"}, player) def wood_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Workshop", "Region", player) or state.can_reach("Blue Maze", "Region", player) or state.can_reach("Gods Room", "Region", player) or state.can_reach("Anansi", "Region", player)) \ - and state.has_all({"Wood Pot Bottom", "Wood Pot Top", "Wood Pot Bottom DUPE", "Wood Pot Top DUPE"}, player) + return state.has_all({"Wood Pot Bottom", "Wood Pot Top", "Wood Pot Bottom DUPE", "Wood Pot Top DUPE"}, player) or \ + state.has_all({"Wood Pot Complete", "Wood Pot Complete DUPE"}, player) def crystal_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Lobby", "Region", player) or state.can_reach("Ocean", "Region", player)) \ - and state.has_all({"Crystal Pot Bottom", "Crystal Pot Top", "Crystal Pot Bottom DUPE", "Crystal Pot Top DUPE"}, player) + return state.has_all({"Crystal Pot Bottom", "Crystal Pot Top", "Crystal Pot Bottom DUPE", "Crystal Pot Top DUPE"}, player) or \ + state.has_all({"Crystal Pot Complete", "Crystal Pot Complete DUPE"}, player) def sand_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Greenhouse", "Region", player) or state.can_reach("Ocean", "Region", player)) \ - and state.has_all({"Sand Pot Bottom", "Sand Pot Top", "Sand Pot Bottom DUPE", "Sand Pot Top DUPE"}, player) + return state.has_all({"Sand Pot Bottom", "Sand Pot Top", "Sand Pot Bottom DUPE", "Sand Pot Top DUPE"}, player) or \ + state.has_all({"Sand Pot Complete", "Sand Pot Complete DUPE"}, player) def metal_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Projector Room", "Region", player) or state.can_reach("Prehistoric", "Region", player) or state.can_reach("Bedroom", "Region", player)) \ - and state.has_all({"Metal Pot Bottom", "Metal Pot Top", "Metal Pot Bottom DUPE", "Metal Pot Top DUPE"}, player) + return state.has_all({"Metal Pot Bottom", "Metal Pot Top", "Metal Pot Bottom DUPE", "Metal Pot Top DUPE"}, player) or \ + state.has_all({"Metal Pot Complete", "Metal Pot Complete DUPE"}, player) def lightning_capturable(state: CollectionState, player: int) -> bool: - return (first_nine_ixupi_capturable or state.multiworld.early_lightning[player].value) \ - and state.can_reach("Generator", "Region", player) \ - and state.has_all({"Lightning Pot Bottom", "Lightning Pot Top", "Lightning Pot Bottom DUPE", "Lightning Pot Top DUPE"}, player) + return (first_nine_ixupi_capturable(state, player) or state.multiworld.worlds[player].options.early_lightning.value) \ + and (state.has_all({"Lightning Pot Bottom", "Lightning Pot Top", "Lightning Pot Bottom DUPE", "Lightning Pot Top DUPE"}, player) or \ + state.has_all({"Lightning Pot Complete", "Lightning Pot Complete DUPE"}, player)) def beths_body_available(state: CollectionState, player: int) -> bool: - return (first_nine_ixupi_capturable(state, player) or state.multiworld.early_beth[player].value) \ + return (first_nine_ixupi_capturable(state, player) or state.multiworld.worlds[player].options.early_beth.value) \ and state.can_reach("Generator", "Region", player) @@ -123,7 +123,8 @@ def get_rules_lookup(player: int): "To Burial From Egypt": lambda state: state.can_reach("Egypt", "Region", player), "To Gods Room From Anansi": lambda state: state.can_reach("Gods Room", "Region", player), "To Slide Room": lambda state: all_skull_dials_available(state, player), - "To Lobby From Slide Room": lambda state: (beths_body_available(state, player)) + "To Lobby From Slide Room": lambda state: beths_body_available(state, player), + "To Water Capture From Janitor Closet": lambda state: cloth_capturable(state, player) }, "locations_required": { "Puzzle Solved Anansi Musicbox": lambda state: state.can_reach("Clock Tower", "Region", player), @@ -207,8 +208,10 @@ def set_rules(world: "ShiversWorld") -> None: # forbid cloth in janitor closet and oil in tar river forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Bottom DUPE", player) forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Top DUPE", player) + forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Complete DUPE", player) forbid_item(multiworld.get_location("Accessible: Storage: Tar River", player), "Oil Pot Bottom DUPE", player) forbid_item(multiworld.get_location("Accessible: Storage: Tar River", player), "Oil Pot Top DUPE", player) + forbid_item(multiworld.get_location("Accessible: Storage: Tar River", player), "Oil Pot Complete DUPE", player) # Filler Item Forbids forbid_item(multiworld.get_location("Puzzle Solved Lyre", player), "Easier Lyre", player) @@ -234,4 +237,8 @@ def set_rules(world: "ShiversWorld") -> None: forbid_item(multiworld.get_location("Ixupi Captured Metal", player), "Metal Always Available in Prehistoric", player) # Set completion condition - multiworld.completion_condition[player] = lambda state: (first_nine_ixupi_capturable(state, player) and lightning_capturable(state, player)) + multiworld.completion_condition[player] = lambda state: (( + water_capturable(state, player) + wax_capturable(state, player) + ash_capturable(state, player) \ + + oil_capturable(state, player) + cloth_capturable(state, player) + wood_capturable(state, player) \ + + crystal_capturable(state, player) + sand_capturable(state, player) + metal_capturable(state, player) \ + + lightning_capturable(state, player)) >= world.options.ixupi_captures_needed.value) diff --git a/worlds/shivers/__init__.py b/worlds/shivers/__init__.py index e43e91fb5ae3..a2d7bc14644e 100644 --- a/worlds/shivers/__init__.py +++ b/worlds/shivers/__init__.py @@ -1,3 +1,4 @@ +from typing import List from .Items import item_table, ShiversItem from .Rules import set_rules from BaseClasses import Item, Tutorial, Region, Location @@ -22,7 +23,7 @@ class ShiversWorld(World): Shivers is a horror themed point and click adventure. Explore the mysteries of Windlenot's Museum of the Strange and Unusual. """ - game: str = "Shivers" + game = "Shivers" topology_present = False web = ShiversWeb() options_dataclass = ShiversOptions @@ -30,7 +31,13 @@ class ShiversWorld(World): item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = Constants.location_name_to_id - + shivers_item_id_offset = 27000 + pot_completed_list: List[int] + + + def generate_early(self): + self.pot_completed_list = [] + def create_item(self, name: str) -> Item: data = item_table[name] return ShiversItem(name, data.classification, data.code, self.player) @@ -78,9 +85,28 @@ def create_items(self) -> None: #Add items to item pool itempool = [] for name, data in item_table.items(): - if data.type in {"pot", "key", "ability", "filler2"}: + if data.type in {"key", "ability", "filler2"}: itempool.append(self.create_item(name)) + # Pot pieces/Completed/Mixed: + for i in range(10): + if self.options.full_pots == "pieces": + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + i])) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 10 + i])) + elif self.options.full_pots == "complete": + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 20 + i])) + else: + # Roll for if pieces or a complete pot will be used. + # Pot Pieces + if self.random.randint(0, 1) == 0: + self.pot_completed_list.append(0) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + i])) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 10 + i])) + # Completed Pot + else: + self.pot_completed_list.append(1) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 20 + i])) + #Add Filler itempool += [self.create_item("Easier Lyre") for i in range(9)] @@ -88,7 +114,6 @@ def create_items(self) -> None: filler_needed = len(self.multiworld.get_unfilled_locations(self.player)) - 24 - len(itempool) itempool += [self.random.choices([self.create_item("Heal"), self.create_item("Easier Lyre")], weights=[95, 5])[0] for i in range(filler_needed)] - #Place library escape items. Choose a location to place the escape item library_region = self.multiworld.get_region("Library", self.player) librarylocation = self.random.choice([loc for loc in library_region.locations if not loc.name.startswith("Accessible:")]) @@ -123,14 +148,14 @@ def create_items(self) -> None: self.multiworld.itempool += itempool #Lobby acess: - if self.options.lobby_access == 1: + if self.options.lobby_access == "early": if lobby_access_keys == 1: self.multiworld.early_items[self.player]["Key for Underground Lake Room"] = 1 self.multiworld.early_items[self.player]["Key for Office Elevator"] = 1 self.multiworld.early_items[self.player]["Key for Office"] = 1 elif lobby_access_keys == 2: self.multiworld.early_items[self.player]["Key for Front Door"] = 1 - if self.options.lobby_access == 2: + if self.options.lobby_access == "local": if lobby_access_keys == 1: self.multiworld.local_early_items[self.player]["Key for Underground Lake Room"] = 1 self.multiworld.local_early_items[self.player]["Key for Office Elevator"] = 1 @@ -138,6 +163,12 @@ def create_items(self) -> None: elif lobby_access_keys == 2: self.multiworld.local_early_items[self.player]["Key for Front Door"] = 1 + #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 == "pot" or data.type == "pot_type2"} + if self.options.location_pot_pieces == "different_world": + self.options.non_local_items.value |= {name for name, data in item_table.items() if data.type == "pot" or data.type == "pot_type2"} + def pre_fill(self) -> None: # Prefills event storage locations with duplicate pots storagelocs = [] @@ -149,7 +180,23 @@ def pre_fill(self) -> None: if loc_name.startswith("Accessible: "): storagelocs.append(self.multiworld.get_location(loc_name, self.player)) - storageitems += [self.create_item(name) for name, data in item_table.items() if data.type == 'potduplicate'] + #Pot pieces/Completed/Mixed: + if self.options.full_pots == "pieces": + storageitems += [self.create_item(name) for name, data in item_table.items() if data.type == 'potduplicate'] + elif self.options.full_pots == "complete": + storageitems += [self.create_item(name) for name, data in item_table.items() if data.type == 'potduplicate_type2'] + storageitems += [self.create_item("Empty") for i in range(10)] + else: + for i in range(10): + #Pieces + if self.pot_completed_list[i] == 0: + storageitems += [self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 70 + i])] + storageitems += [self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 80 + i])] + #Complete + else: + storageitems += [self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 140 + i])] + storageitems += [self.create_item("Empty")] + storageitems += [self.create_item("Empty") for i in range(3)] state = self.multiworld.get_all_state(True) @@ -166,11 +213,13 @@ def pre_fill(self) -> None: def fill_slot_data(self) -> dict: return { - "storageplacements": self.storage_placements, - "excludedlocations": {str(excluded_location).replace('ExcludeLocations(', '').replace(')', '') for excluded_location in self.multiworld.exclude_locations.values()}, - "elevatorsstaysolved": {self.options.elevators_stay_solved.value}, - "earlybeth": {self.options.early_beth.value}, - "earlylightning": {self.options.early_lightning.value}, + "StoragePlacements": self.storage_placements, + "ExcludedLocations": list(self.options.exclude_locations.value), + "IxupiCapturesNeeded": self.options.ixupi_captures_needed.value, + "ElevatorsStaySolved": self.options.elevators_stay_solved.value, + "EarlyBeth": self.options.early_beth.value, + "EarlyLightning": self.options.early_lightning.value, + "FrontDoorUsable": self.options.front_door_usable.value } diff --git a/worlds/shivers/data/locations.json b/worlds/shivers/data/locations.json index 1d62f85d2d1c..64fe3647348d 100644 --- a/worlds/shivers/data/locations.json +++ b/worlds/shivers/data/locations.json @@ -81,7 +81,7 @@ "Information Plaque: (Ocean) Poseidon", "Information Plaque: (Ocean) Colossus of Rhodes", "Information Plaque: (Ocean) Poseidon's Temple", - "Information Plaque: (Underground Maze) Subterranean World", + "Information Plaque: (Underground Maze Staircase) Subterranean World", "Information Plaque: (Underground Maze) Dero", "Information Plaque: (Egypt) Tomb of the Ixupi", "Information Plaque: (Egypt) The Sphinx", @@ -119,16 +119,6 @@ "Outside": [ "Puzzle Solved Gears", "Puzzle Solved Stone Henge", - "Ixupi Captured Water", - "Ixupi Captured Wax", - "Ixupi Captured Ash", - "Ixupi Captured Oil", - "Ixupi Captured Cloth", - "Ixupi Captured Wood", - "Ixupi Captured Crystal", - "Ixupi Captured Sand", - "Ixupi Captured Metal", - "Ixupi Captured Lightning", "Puzzle Solved Office Elevator", "Puzzle Solved Three Floor Elevator", "Puzzle Hint Found: Combo Lock in Mailbox", @@ -182,7 +172,8 @@ "Accessible: Storage: Transforming Mask" ], "Generator": [ - "Final Riddle: Beth's Body Page 17" + "Final Riddle: Beth's Body Page 17", + "Ixupi Captured Lightning" ], "Theater Back Hallways": [ "Puzzle Solved Clock Tower Door" @@ -210,6 +201,7 @@ "Information Plaque: (Ocean) Poseidon's Temple" ], "Maze Staircase": [ + "Information Plaque: (Underground Maze Staircase) Subterranean World", "Puzzle Solved Maze Door" ], "Egypt": [ @@ -305,7 +297,6 @@ ], "Tar River": [ "Accessible: Storage: Tar River", - "Information Plaque: (Underground Maze) Subterranean World", "Information Plaque: (Underground Maze) Dero" ], "Theater": [ @@ -320,6 +311,33 @@ "Skull Dial Bridge": [ "Accessible: Storage: Skull Bridge", "Puzzle Solved Skull Dial Door" + ], + "Water Capture": [ + "Ixupi Captured Water" + ], + "Wax Capture": [ + "Ixupi Captured Wax" + ], + "Ash Capture": [ + "Ixupi Captured Ash" + ], + "Oil Capture": [ + "Ixupi Captured Oil" + ], + "Cloth Capture": [ + "Ixupi Captured Cloth" + ], + "Wood Capture": [ + "Ixupi Captured Wood" + ], + "Crystal Capture": [ + "Ixupi Captured Crystal" + ], + "Sand Capture": [ + "Ixupi Captured Sand" + ], + "Metal Capture": [ + "Ixupi Captured Metal" ] } -} +} diff --git a/worlds/shivers/data/regions.json b/worlds/shivers/data/regions.json index 963d100faddb..aeb5aa737366 100644 --- a/worlds/shivers/data/regions.json +++ b/worlds/shivers/data/regions.json @@ -7,35 +7,35 @@ ["Underground Lake", ["To Underground Tunnels From Underground Lake", "To Underground Blue Tunnels From Underground Lake"]], ["Underground Blue Tunnels", ["To Underground Lake From Underground Blue Tunnels", "To Office Elevator From Underground Blue Tunnels"]], ["Office Elevator", ["To Underground Blue Tunnels From Office Elevator","To Office From Office Elevator"]], - ["Office", ["To Office Elevator From Office", "To Workshop", "To Lobby From Office", "To Bedroom Elevator From Office"]], - ["Workshop", ["To Office From Workshop"]], + ["Office", ["To Office Elevator From Office", "To Workshop", "To Lobby From Office", "To Bedroom Elevator From Office", "To Ash Capture From Office"]], + ["Workshop", ["To Office From Workshop", "To Wood Capture From Workshop"]], ["Bedroom Elevator", ["To Office From Bedroom Elevator", "To Bedroom"]], - ["Bedroom", ["To Bedroom Elevator From Bedroom"]], - ["Lobby", ["To Office From Lobby", "To Library From Lobby", "To Theater From Lobby", "To Prehistoric From Lobby", "To Egypt From Lobby", "To Tar River From Lobby", "To Outside From Lobby"]], - ["Library", ["To Lobby From Library", "To Maintenance Tunnels From Library"]], + ["Bedroom", ["To Bedroom Elevator From Bedroom", "To Metal Capture From Bedroom"]], + ["Lobby", ["To Office From Lobby", "To Library From Lobby", "To Theater From Lobby", "To Prehistoric From Lobby", "To Egypt From Lobby", "To Tar River From Lobby", "To Outside From Lobby", "To Water Capture From Lobby", "To Crystal Capture From Lobby"]], + ["Library", ["To Lobby From Library", "To Maintenance Tunnels From Library", "To Wax Capture From Library"]], ["Maintenance Tunnels", ["To Library From Maintenance Tunnels", "To Three Floor Elevator From Maintenance Tunnels", "To Generator"]], ["Generator", ["To Maintenance Tunnels From Generator"]], ["Theater", ["To Lobby From Theater", "To Theater Back Hallways From Theater"]], ["Theater Back Hallways", ["To Theater From Theater Back Hallways", "To Clock Tower Staircase From Theater Back Hallways", "To Maintenance Tunnels From Theater Back Hallways", "To Projector Room"]], ["Clock Tower Staircase", ["To Theater Back Hallways From Clock Tower Staircase", "To Clock Tower"]], ["Clock Tower", ["To Clock Tower Staircase From Clock Tower"]], - ["Projector Room", ["To Theater Back Hallways From Projector Room"]], - ["Prehistoric", ["To Lobby From Prehistoric", "To Greenhouse", "To Ocean From Prehistoric"]], - ["Greenhouse", ["To Prehistoric From Greenhouse"]], - ["Ocean", ["To Prehistoric From Ocean", "To Maze Staircase From Ocean"]], + ["Projector Room", ["To Theater Back Hallways From Projector Room", "To Metal Capture From Projector Room"]], + ["Prehistoric", ["To Lobby From Prehistoric", "To Greenhouse", "To Ocean From Prehistoric", "To Oil Capture From Prehistoric", "To Metal Capture From Prehistoric"]], + ["Greenhouse", ["To Prehistoric From Greenhouse", "To Sand Capture From Greenhouse"]], + ["Ocean", ["To Prehistoric From Ocean", "To Maze Staircase From Ocean", "To Crystal Capture From Ocean", "To Sand Capture From Ocean"]], ["Maze Staircase", ["To Ocean From Maze Staircase", "To Maze From Maze Staircase"]], ["Maze", ["To Maze Staircase From Maze", "To Tar River"]], - ["Tar River", ["To Maze From Tar River", "To Lobby From Tar River"]], - ["Egypt", ["To Lobby From Egypt", "To Burial From Egypt", "To Blue Maze From Egypt"]], - ["Burial", ["To Egypt From Burial", "To Shaman From Burial"]], - ["Shaman", ["To Burial From Shaman", "To Gods Room"]], - ["Gods Room", ["To Shaman From Gods Room", "To Anansi From Gods Room"]], - ["Anansi", ["To Gods Room From Anansi", "To Werewolf From Anansi"]], + ["Tar River", ["To Maze From Tar River", "To Lobby From Tar River", "To Oil Capture From Tar River"]], + ["Egypt", ["To Lobby From Egypt", "To Burial From Egypt", "To Blue Maze From Egypt", "To Cloth Capture From Egypt"]], + ["Burial", ["To Egypt From Burial", "To Shaman From Burial", "To Ash Capture From Burial", "To Cloth Capture From Burial"]], + ["Shaman", ["To Burial From Shaman", "To Gods Room", "To Wax Capture From Shaman"]], + ["Gods Room", ["To Shaman From Gods Room", "To Anansi From Gods Room", "To Wood Capture From Gods Room"]], + ["Anansi", ["To Gods Room From Anansi", "To Werewolf From Anansi", "To Wax Capture From Anansi", "To Wood Capture From Anansi"]], ["Werewolf", ["To Anansi From Werewolf", "To Night Staircase From Werewolf"]], ["Night Staircase", ["To Werewolf From Night Staircase", "To Janitor Closet", "To UFO"]], - ["Janitor Closet", ["To Night Staircase From Janitor Closet"]], + ["Janitor Closet", ["To Night Staircase From Janitor Closet", "To Water Capture From Janitor Closet", "To Cloth Capture From Janitor Closet"]], ["UFO", ["To Night Staircase From UFO", "To Inventions From UFO"]], - ["Blue Maze", ["To Egypt From Blue Maze", "To Three Floor Elevator From Blue Maze Bottom", "To Three Floor Elevator From Blue Maze Top", "To Fortune Teller", "To Inventions From Blue Maze"]], + ["Blue Maze", ["To Egypt From Blue Maze", "To Three Floor Elevator From Blue Maze Bottom", "To Three Floor Elevator From Blue Maze Top", "To Fortune Teller", "To Inventions From Blue Maze", "To Wood Capture From Blue Maze"]], ["Three Floor Elevator", ["To Maintenance Tunnels From Three Floor Elevator", "To Blue Maze From Three Floor Elevator"]], ["Fortune Teller", ["To Blue Maze From Fortune Teller"]], ["Inventions", ["To Blue Maze From Inventions", "To UFO From Inventions", "To Torture From Inventions"]], @@ -43,7 +43,16 @@ ["Puzzle Room Mastermind", ["To Torture", "To Puzzle Room Marbles From Puzzle Room Mastermind"]], ["Puzzle Room Marbles", ["To Puzzle Room Mastermind From Puzzle Room Marbles", "To Skull Dial Bridge From Puzzle Room Marbles"]], ["Skull Dial Bridge", ["To Puzzle Room Marbles From Skull Dial Bridge", "To Slide Room"]], - ["Slide Room", ["To Skull Dial Bridge From Slide Room", "To Lobby From Slide Room"]] + ["Slide Room", ["To Skull Dial Bridge From Slide Room", "To Lobby From Slide Room"]], + ["Water Capture", []], + ["Wax Capture", []], + ["Ash Capture", []], + ["Oil Capture", []], + ["Cloth Capture", []], + ["Wood Capture", []], + ["Crystal Capture", []], + ["Sand Capture", []], + ["Metal Capture", []] ], "mandatory_connections": [ ["To Registry", "Registry"], @@ -140,6 +149,29 @@ ["To Puzzle Room Marbles From Skull Dial Bridge", "Puzzle Room Marbles"], ["To Skull Dial Bridge From Puzzle Room Marbles", "Skull Dial Bridge"], ["To Skull Dial Bridge From Slide Room", "Skull Dial Bridge"], - ["To Slide Room", "Slide Room"] + ["To Slide Room", "Slide Room"], + ["To Wax Capture From Library", "Wax Capture"], + ["To Wax Capture From Shaman", "Wax Capture"], + ["To Wax Capture From Anansi", "Wax Capture"], + ["To Water Capture From Lobby", "Water Capture"], + ["To Water Capture From Janitor Closet", "Water Capture"], + ["To Ash Capture From Office", "Ash Capture"], + ["To Ash Capture From Burial", "Ash Capture"], + ["To Oil Capture From Prehistoric", "Oil Capture"], + ["To Oil Capture From Tar River", "Oil Capture"], + ["To Cloth Capture From Egypt", "Cloth Capture"], + ["To Cloth Capture From Burial", "Cloth Capture"], + ["To Cloth Capture From Janitor Closet", "Cloth Capture"], + ["To Wood Capture From Workshop", "Wood Capture"], + ["To Wood Capture From Gods Room", "Wood Capture"], + ["To Wood Capture From Anansi", "Wood Capture"], + ["To Wood Capture From Blue Maze", "Wood Capture"], + ["To Crystal Capture From Lobby", "Crystal Capture"], + ["To Crystal Capture From Ocean", "Crystal Capture"], + ["To Sand Capture From Greenhouse", "Sand Capture"], + ["To Sand Capture From Ocean", "Sand Capture"], + ["To Metal Capture From Bedroom", "Metal Capture"], + ["To Metal Capture From Projector Room", "Metal Capture"], + ["To Metal Capture From Prehistoric", "Metal Capture"] ] -} \ No newline at end of file +} diff --git a/worlds/shivers/docs/en_Shivers.md b/worlds/shivers/docs/en_Shivers.md index a92f8a6b7911..2c56152a7a0c 100644 --- a/worlds/shivers/docs/en_Shivers.md +++ b/worlds/shivers/docs/en_Shivers.md @@ -12,8 +12,8 @@ these are randomized. Crawling has been added and is required to use any crawl s ## What is considered a location check in Shivers? -1. All puzzle solves are location checks excluding elevator puzzles. -2. All Ixupi captures are location checks excluding Lightning. +1. All puzzle solves are location checks. +2. All Ixupi captures are location checks. 3. Puzzle hints/solutions are location checks. For example, looking at the Atlantis map. 4. Optionally information plaques are location checks. @@ -23,9 +23,9 @@ If the player receives a key then the corresponding door will be unlocked. If th ## What is the victory condition? -Victory is achieved when the player captures Lightning in the generator room. +Victory is achieved when the player has captured the required number Ixupi set in their options. ## Encountered a bug? -Please contact GodlFire on Discord for bugs related to Shivers world generation.\ +Please contact GodlFire on Discord for bugs related to Shivers world generation.
Please contact GodlFire or mouse on Discord for bugs related to the Shivers Randomizer. diff --git a/worlds/shivers/docs/setup_en.md b/worlds/shivers/docs/setup_en.md index 187382ef643c..c53edcdf2b57 100644 --- a/worlds/shivers/docs/setup_en.md +++ b/worlds/shivers/docs/setup_en.md @@ -5,7 +5,7 @@ - [Shivers (GOG version)](https://www.gog.com/en/game/shivers) or original disc - [ScummVM](https://www.scummvm.org/downloads/) version 2.7.0 or later -- [Shivers Randomizer](https://www.speedrun.com/shivers/resources) +- [Shivers Randomizer](https://github.com/GodlFire/Shivers-Randomizer-CSharp/releases/latest) Latest release version ## Setup ScummVM for Shivers From 91f7cf16de7d7ae15bc6a214273fb7f6f878fe0a Mon Sep 17 00:00:00 2001 From: Trevor L <80716066+TRPG0@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:32:51 -0600 Subject: [PATCH 05/98] Bomb Rush Cyberfunk: Fix Coil quest being in glitched logic too early (#3720) * Update Rules.py * Update Rules.py --- worlds/bomb_rush_cyberfunk/Rules.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/worlds/bomb_rush_cyberfunk/Rules.py b/worlds/bomb_rush_cyberfunk/Rules.py index f59a4285709d..8f283ee613b7 100644 --- a/worlds/bomb_rush_cyberfunk/Rules.py +++ b/worlds/bomb_rush_cyberfunk/Rules.py @@ -1006,6 +1006,8 @@ def rules(brcworld): lambda state: mataan_challenge2(state, player, limit, glitched)) set_rule(multiworld.get_location("Mataan: Score challenge reward", player), lambda state: mataan_challenge3(state, player)) + set_rule(multiworld.get_location("Mataan: Coil joins the crew", player), + lambda state: mataan_deepest(state, player, limit, glitched)) if photos: set_rule(multiworld.get_location("Mataan: Trash Polo", player), lambda state: camera(state, player)) From 53bc4ffa52578d6c6c4c289cb399ed0900ec4f30 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Wed, 31 Jul 2024 10:37:52 -0500 Subject: [PATCH 06/98] Options: Always verify keys for VerifyKeys options (#3280) * Options: Always verify keys for VerifyKeys options * fix PlandoTexts * use OptionError and give a slightly better error message for which option it is * add the player name to the error * don't create an unnecessary list --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- Options.py | 38 ++++++++++++++++++-------- worlds/stardew_valley/test/__init__.py | 4 +-- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Options.py b/Options.py index d5e0ce1a550f..d040828509d1 100644 --- a/Options.py +++ b/Options.py @@ -786,17 +786,22 @@ class VerifyKeys(metaclass=FreezeValidKeys): verify_location_name: bool = False value: typing.Any - @classmethod - def verify_keys(cls, data: typing.Iterable[str]) -> None: - if cls.valid_keys: - data = set(data) - dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data) - extra = dataset - cls._valid_keys + def verify_keys(self) -> None: + if self.valid_keys: + data = set(self.value) + dataset = set(word.casefold() for word in data) if self.valid_keys_casefold else set(data) + extra = dataset - self._valid_keys if extra: - raise Exception(f"Found unexpected key {', '.join(extra)} in {cls}. " - f"Allowed keys: {cls._valid_keys}.") + raise OptionError( + f"Found unexpected key {', '.join(extra)} in {getattr(self, 'display_name', self)}. " + f"Allowed keys: {self._valid_keys}." + ) def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None: + try: + self.verify_keys() + except OptionError as validation_error: + raise OptionError(f"Player {player_name} has invalid option keys:\n{validation_error}") if self.convert_name_groups and self.verify_item_name: new_value = type(self.value)() # empty container of whatever value is for item_name in self.value: @@ -833,7 +838,6 @@ def __init__(self, value: typing.Dict[str, typing.Any]): @classmethod def from_any(cls, data: typing.Dict[str, typing.Any]) -> OptionDict: if type(data) == dict: - cls.verify_keys(data) return cls(data) else: raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}") @@ -879,7 +883,6 @@ def from_text(cls, text: str): @classmethod def from_any(cls, data: typing.Any): if is_iterable_except_str(data): - cls.verify_keys(data) return cls(data) return cls.from_text(str(data)) @@ -905,7 +908,6 @@ def from_text(cls, text: str): @classmethod def from_any(cls, data: typing.Any): if is_iterable_except_str(data): - cls.verify_keys(data) return cls(data) return cls.from_text(str(data)) @@ -948,6 +950,19 @@ def verify(self, world: typing.Type[World], player_name: str, plando_options: "P self.value = [] logging.warning(f"The plando texts module is turned off, " f"so text for {player_name} will be ignored.") + else: + super().verify(world, player_name, plando_options) + + def verify_keys(self) -> None: + if self.valid_keys: + data = set(text.at for text in self) + dataset = set(word.casefold() for word in data) if self.valid_keys_casefold else set(data) + extra = dataset - self._valid_keys + if extra: + raise OptionError( + f"Invalid \"at\" placement {', '.join(extra)} in {getattr(self, 'display_name', self)}. " + f"Allowed placements: {self._valid_keys}." + ) @classmethod def from_any(cls, data: PlandoTextsFromAnyType) -> Self: @@ -971,7 +986,6 @@ def from_any(cls, data: PlandoTextsFromAnyType) -> Self: texts.append(text) else: raise Exception(f"Cannot create plando text from non-dictionary type, got {type(text)}") - cls.verify_keys([text.at for text in texts]) return cls(texts) else: raise NotImplementedError(f"Cannot Convert from non-list, got {type(data)}") diff --git a/worlds/stardew_valley/test/__init__.py b/worlds/stardew_valley/test/__init__.py index d077432e24ae..7e82ea91e434 100644 --- a/worlds/stardew_valley/test/__init__.py +++ b/worlds/stardew_valley/test/__init__.py @@ -6,7 +6,7 @@ from contextlib import contextmanager from typing import Dict, ClassVar, Iterable, Tuple, Optional, List, Union, Any -from BaseClasses import MultiWorld, CollectionState, get_seed, Location, Item, ItemClassification +from BaseClasses import MultiWorld, CollectionState, PlandoOptions, get_seed, Location, Item, ItemClassification from Options import VerifyKeys from test.bases import WorldTestBase from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld @@ -365,7 +365,7 @@ def setup_solo_multiworld(test_options: Optional[Dict[Union[str, StardewValleyOp if issubclass(option, VerifyKeys): # Values should already be verified, but just in case... - option.verify_keys(value.value) + value.verify(StardewValleyWorld, "Tester", PlandoOptions.bosses) setattr(args, name, {1: value}) multiworld.set_options(args) From 75b8c7891c65b16dfaa5c06abcc6b53e8299d94e Mon Sep 17 00:00:00 2001 From: wildham <64616385+wildham0@users.noreply.github.com> Date: Wed, 31 Jul 2024 11:40:45 -0400 Subject: [PATCH 07/98] Docs: Add FFMQ French Setup Guide + Minor fixes to English Guide (#3590) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add docs * Fix character * Configuration Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * ajuster Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * inclure Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * doublon Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * remplissage Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * autre Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * pouvoir Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * mappemonde Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * virgule Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * fournir Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes 2 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * snes9x Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes 3 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * options Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * lien Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * de laquelle Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * Étape de génération Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes 4 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * également Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * guillemets Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * guillemets 2 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * adresse Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * Connect Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * seed Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * Changer fichier yaml pour de configuration * Fix capitalization Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Fix capitalization 2 Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Fix typo+Add link to fr/en info page --------- Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> --- worlds/ffmq/__init__.py | 15 +- .../docs/en_Final Fantasy Mystic Quest.md | 3 + .../docs/fr_Final Fantasy Mystic Quest.md | 36 ++++ worlds/ffmq/docs/setup_en.md | 17 +- worlds/ffmq/docs/setup_fr.md | 178 ++++++++++++++++++ 5 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md create mode 100644 worlds/ffmq/docs/setup_fr.md diff --git a/worlds/ffmq/__init__.py b/worlds/ffmq/__init__.py index c464203dc6a4..3c58487265a6 100644 --- a/worlds/ffmq/__init__.py +++ b/worlds/ffmq/__init__.py @@ -25,14 +25,25 @@ class FFMQWebWorld(WebWorld): - tutorials = [Tutorial( + setup_en = Tutorial( "Multiworld Setup Guide", "A guide to playing Final Fantasy Mystic Quest with Archipelago.", "English", "setup_en.md", "setup/en", ["Alchav"] - )] + ) + + setup_fr = Tutorial( + setup_en.tutorial_name, + setup_en.description, + "Français", + "setup_fr.md", + "setup/fr", + ["Artea"] + ) + + tutorials = [setup_en, setup_fr] class FFMQWorld(World): diff --git a/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md b/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md index a652d4e5adcd..4e093930739d 100644 --- a/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md +++ b/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md @@ -1,5 +1,8 @@ # Final Fantasy Mystic Quest +## Game page in other languages: +* [Français](/games/Final%20Fantasy%20Mystic%20Quest/info/fr) + ## Where is the options page? The [player options page for this game](../player-options) contains all the options you need to configure and export a diff --git a/worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md b/worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md new file mode 100644 index 000000000000..70c2d938bfc6 --- /dev/null +++ b/worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md @@ -0,0 +1,36 @@ +# Final Fantasy Mystic Quest + +## Page d'info dans d'autres langues : +* [English](/games/Final%20Fantasy%20Mystic%20Quest/info/en) + +## Où se situe la page d'options? + +La [page de configuration](../player-options) contient toutes les options nécessaires pour créer un fichier de configuration. + +## Qu'est-ce qui est rendu aléatoire dans ce jeu? + +Outre les objets mélangés, il y a plusieurs options pour aussi mélanger les villes et donjons, les pièces dans les donjons, les téléporteurs et les champs de bataille. +Il y a aussi plusieurs autres options afin d'ajuster la difficulté du jeu et la vitesse d'une partie. + +## Quels objets et emplacements sont mélangés? + +Les objets normalement reçus des coffres rouges, des PNJ et des champs de bataille sont mélangés. Vous pouvez aussi +inclure les objets des coffres bruns (qui contiennent normalement des consommables) dans les objets mélangés. + +## Quels objets peuvent être dans les mondes des autres joueurs? + +Tous les objets qui ont été déterminés mélangés dans les options peuvent être placés dans d'autres mondes. + +## À quoi ressemblent les objets des autres joueurs dans Final Fantasy Mystic Quest? + +Les emplacements qui étaient à l'origine des coffres (rouges ou bruns si ceux-ci sont inclus) apparaîtront comme des coffres. +Les coffres rouges seront des objets utiles ou de progression, alors que les coffres bruns seront des objets de remplissage. +Les pièges peuvent apparaître comme des coffres rouges ou bruns. +Lorsque vous ouvrirez un coffre contenant un objet d'un autre joueur, vous recevrez l'icône d'Archipelago et +la boîte de dialogue vous indiquera avoir reçu un "Archipelago Item". + + +## Lorsqu'un joueur reçoit un objet, qu'arrive-t-il? + +Une boîte de dialogue apparaîtra pour vous montrer l'objet que vous avez reçu. Vous ne pourrez pas recevoir d'objet si vous êtes +en combat, dans la mappemonde ou dans les menus (à l'exception de lorsque vous fermez le menu). diff --git a/worlds/ffmq/docs/setup_en.md b/worlds/ffmq/docs/setup_en.md index 35d775f1bc9f..77569c93f0c8 100644 --- a/worlds/ffmq/docs/setup_en.md +++ b/worlds/ffmq/docs/setup_en.md @@ -17,6 +17,12 @@ The Archipelago community cannot supply you with this. ## Installation Procedures +### Linux Setup + +1. Download and install [Archipelago](). **The installer + file is located in the assets section at the bottom of the version information. You'll likely be looking for the `.AppImage`.** +2. It is recommended to use either RetroArch or BizHawk if you run on linux, as snes9x-rr isn't compatible. + ### Windows Setup 1. Download and install [Archipelago](). **The installer @@ -75,8 +81,7 @@ Manually launch the SNI Client, and run the patched ROM in your chosen software #### With an emulator -When the client launched automatically, SNI should have also automatically launched in the background. If this is its -first time launching, you may be prompted to allow it to communicate through the Windows Firewall. +If this is the first time SNI launches, you may be prompted to allow it to communicate through the Windows Firewall. ##### snes9x-rr @@ -133,10 +138,10 @@ page: [usb2snes Supported Platforms Page](http://usb2snes.com/#supported-platfor ### Connect to the Archipelago Server -The patch file which launched your client should have automatically connected you to the AP Server. There are a few -reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the -client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it -into the "Server" input field then press enter. +SNI serves as the interface between your emulator and the server. Since you launched it manually, you need to tell it what server to connect to. +If the server is hosted on Archipelago.gg, get the port the server hosts your game on at the top of the game room (last line before the worlds are listed). +In the SNI client, either type `/connect address` (where `address` is the address of the server, for example `/connect archipelago.gg:12345`), or type the address and port on the "Server" input field, then press `Connect`. +If the server is hosted locally, simply ask the host for the address of the server, and copy/paste it into the "Server" input field then press `Connect`. The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected". diff --git a/worlds/ffmq/docs/setup_fr.md b/worlds/ffmq/docs/setup_fr.md new file mode 100644 index 000000000000..12ea41c6b3a0 --- /dev/null +++ b/worlds/ffmq/docs/setup_fr.md @@ -0,0 +1,178 @@ +# Final Fantasy Mystic Quest Setup Guide + +## Logiciels requis + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) +- Une solution logicielle ou matérielle capable de charger et de lancer des fichiers ROM de SNES + - Un émulateur capable d'éxécuter des scripts Lua + - snes9x-rr de: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), + - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html), + - RetroArch 1.10.1 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Ou, + - Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), ou une autre solution matérielle + compatible +- Le fichier ROM de la v1.0 ou v1.1 NA de Final Fantasy Mystic Quest obtenu légalement, sûrement nommé `Final Fantasy - Mystic Quest (U) (V1.0).sfc` ou `Final Fantasy - Mystic Quest (U) (V1.1).sfc` +La communauté d'Archipelago ne peut vous fournir avec ce fichier. + +## Procédure d'installation + +### Installation sur Linux + +1. Téléchargez et installez [Archipelago](). +** Le fichier d'installation est situé dans la section "assets" dans le bas de la fenêtre d'information de la version. Vous voulez probablement le `.AppImage`** +2. L'utilisation de RetroArch ou BizHawk est recommandé pour les utilisateurs linux, puisque snes9x-rr n'est pas compatible. + +### Installation sur Windows + +1. Téléchargez et installez [Archipelago](). +** Le fichier d'installation est situé dans la section "assets" dans le bas de la fenêtre d'information de la version.** +2. Si vous utilisez un émulateur, il est recommandé d'assigner votre émulateur capable d'éxécuter des scripts Lua comme + programme par défaut pour ouvrir vos ROMs. + 1. Extrayez votre dossier d'émulateur sur votre Bureau, ou à un endroit dont vous vous souviendrez. + 2. Faites un clic droit sur un fichier ROM et sélectionnez **Ouvrir avec...** + 3. Cochez la case à côté de **Toujours utiliser cette application pour ouvrir les fichiers `.sfc`** + 4. Descendez jusqu'en bas de la liste et sélectionnez **Rechercher une autre application sur ce PC** + 5. Naviguez dans les dossiers jusqu'au fichier `.exe` de votre émulateur et choisissez **Ouvrir**. Ce fichier + devrait se trouver dans le dossier que vous avez extrait à la première étape. + + +## Créer son fichier de configuration (.yaml) + +### Qu'est-ce qu'un fichier de configuration et pourquoi en ai-je besoin ? + +Votre fichier de configuration contient un ensemble d'options de configuration pour indiquer au générateur +comment il devrait générer votre seed. Chaque joueur d'un multiworld devra fournir son propre fichier de configuration. Cela permet +à chaque joueur d'apprécier une expérience personalisée. Les différents joueurs d'un même multiworld +pouront avoir des options de génération différentes. +Vous pouvez lire le [guide pour créer un YAML de base](/tutorial/Archipelago/setup/en) en anglais. + +### Où est-ce que j'obtiens un fichier de configuration ? + +La [page d'options sur le site](/games/Final%20Fantasy%20Mystic%20Quest/player-options) vous permet de choisir vos +options de génération et de les exporter vers un fichier de configuration. +Il vous est aussi possible de trouver le fichier de configuration modèle de Mystic Quest dans votre répertoire d'installation d'Archipelago, +dans le dossier Players/Templates. + +### Vérifier son fichier de configuration + +Si vous voulez valider votre fichier de configuration pour être sûr qu'il fonctionne, vous pouvez le vérifier sur la page du +[Validateur de YAML](/mysterycheck). + +## Générer une partie pour un joueur + +1. Aller sur la page [Génération de partie](/games/Final%20Fantasy%20Mystic%20Quest/player-options), configurez vos options, + et cliquez sur le bouton "Generate Game". +2. Il vous sera alors présenté une page d'informations sur la seed +3. Cliquez sur le lien "Create New Room". +4. Vous verrez s'afficher la page du server, de laquelle vous pourrez télécharger votre fichier patch `.apmq`. +5. Rendez-vous sur le [site FFMQR](https://ffmqrando.net/Archipelago). +Sur cette page, sélectionnez votre ROM Final Fantasy Mystic Quest original dans le boîte "ROM", puis votre ficher patch `.apmq` dans la boîte "Load Archipelago Config File". +Cliquez sur "Generate". Un téléchargement avec votre ROM aléatoire devrait s'amorcer. +6. Puisque cette partie est à un seul joueur, vous n'avez plus besoin du client Archipelago ni du serveur, sentez-vous libre de les fermer. + +## Rejoindre un MultiWorld + +### Obtenir son patch et créer sa ROM + +Quand vous rejoignez un multiworld, il vous sera demandé de fournir votre fichier de configuration à celui qui héberge la partie ou +s'occupe de la génération. Une fois cela fait, l'hôte vous fournira soit un lien pour télécharger votre patch, soit un +fichier `.zip` contenant les patchs de tous les joueurs. Votre patch devrait avoir l'extension `.apmq`. + +Allez au [site FFMQR](https://ffmqrando.net/Archipelago) et sélectionnez votre ROM Final Fantasy Mystic Quest original dans le boîte "ROM", puis votre ficher patch `.apmq` dans la boîte "Load Archipelago Config File". +Cliquez sur "Generate". Un téléchargement avec votre ROM aléatoire devrait s'amorcer. + +Ouvrez le client SNI (sur Windows ArchipelagoSNIClient.exe, sur Linux ouvrez le `.appImage` puis cliquez sur SNI Client), puis ouvrez le ROM téléchargé avec votre émulateur choisi. + +### Se connecter au client + +#### Avec un émulateur + +Quand le client se lance automatiquement, QUsb2Snes devrait également se lancer automatiquement en arrière-plan. Si +c'est la première fois qu'il démarre, il vous sera peut-être demandé de l'autoriser à communiquer à travers le pare-feu +Windows. + +##### snes9x-rr + +1. Chargez votre ROM si ce n'est pas déjà fait. +2. Cliquez sur le menu "File" et survolez l'option **Lua Scripting** +3. Cliquez alors sur **New Lua Script Window...** +4. Dans la nouvelle fenêtre, sélectionnez **Browse...** +5. Sélectionnez le fichier connecteur lua fourni avec votre client + - Regardez dans le dossier Archipelago et cherchez `/SNI/lua/x64` ou `/SNI/lua/x86`, dépendemment de si votre emulateur + est 64-bit ou 32-bit. +6. Si vous obtenez une erreur `socket.dll missing` ou une erreur similaire lorsque vous chargez le script lua, vous devez naviguer dans le dossier +contenant le script lua, puis copier le fichier `socket.dll` dans le dossier d'installation de votre emulateur snes9x. + +##### BizHawk + +1. Assurez vous d'avoir le coeur BSNES chargé. Cela est possible en cliquant sur le menu "Tools" de BizHawk et suivant + ces options de menu : + `Config --> Cores --> SNES --> BSNES` + Une fois le coeur changé, vous devez redémarrer BizHawk. +2. Chargez votre ROM si ce n'est pas déjà fait. +3. Cliquez sur le menu "Tools" et cliquez sur **Lua Console** +4. Cliquez sur le bouton pour ouvrir un nouveau script Lua, soit par le bouton avec un icône "Ouvrir un dossier", + en cliquant `Open Script...` dans le menu Script ou en appuyant sur `ctrl-O`. +5. Sélectionnez le fichier `Connector.lua` inclus avec le client + - Regardez dans le dossier Archipelago et cherchez `/SNI/lua/x64` ou `/SNI/lua/x86`, dépendemment de si votre emulateur + est 64-bit ou 32-bit. Notez que les versions les plus récentes de BizHawk ne sont que 64-bit. + +##### RetroArch 1.10.1 ou plus récent + +Vous ne devez faire ces étapes qu'une fois. À noter que RetroArch 1.9.x ne fonctionnera pas puisqu'il s'agit d'une version moins récente que 1.10.1. + +1. Entrez dans le menu principal de RetroArch. +2. Allez dans Settings --> User Interface. Activez l'option "Show Advanced Settings". +3. Allez dans Settings --> Network. Activez l'option "Network Commands", qui se trouve sous "Request Device 16". + Laissez le "Network Command Port" à sa valeur par defaut, qui devrait être 55355. + + +![Capture d'écran du menu Network Commands setting](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-en.png) +4. Allez dans le Menu Principal --> Online Updater --> Core Downloader. Trouvez et sélectionnez "Nintendo - SNES / SFC (bsnes-mercury + Performance)". + +Lorsque vous chargez un ROM pour Archipelago, assurez vous de toujours sélectionner le coeur **bsnes-mercury**. +Ce sont les seuls coeurs qui permettent à des outils extérieurs de lire les données du ROM. + +#### Avec une solution matérielle + +Ce guide suppose que vous avez téléchargé le bon micro-logiciel pour votre appareil. Si ce n'est pas déjà le cas, faites +le maintenant. Les utilisateurs de SD2SNES et de FXPak Pro peuvent télécharger le micro-logiciel approprié +[ici](https://github.com/RedGuyyyy/sd2snes/releases). Pour les autres solutions, de l'aide peut être trouvée +[sur cette page](http://usb2snes.com/#supported-platforms). + +1. Fermez votre émulateur, qui s'est potentiellement lancé automatiquement. +2. Ouvrez votre appareil et chargez le ROM. + +### Se connecter au MultiServer + +Puisque vous avez lancé SNI manuellement, vous devrez probablement lui indiquer l'adresse à laquelle il doit se connecter. +Si le serveur est hébergé sur le site d'Archipelago, vous verrez l'adresse à laquelle vous connecter dans le haut de la page, dernière ligne avant la liste des mondes. +Tapez `/connect adresse` (ou le "adresse" est remplacé par l'adresse archipelago, par exemple `/connect archipelago.gg:12345`) dans la boîte de commande au bas de votre client SNI, ou encore écrivez l'adresse dans la boîte "server" dans le haut du client, puis cliquez `Connect`. +Si le serveur n'est pas hébergé sur le site d'Archipelago, demandez à l'hôte l'adresse du serveur, puis tapez `/connect adresse` (ou "adresse" est remplacé par l'adresse fourni par l'hôte) ou copiez/collez cette adresse dans le champ "Server" puis appuyez sur "Connect". + +Le client essaiera de vous reconnecter à la nouvelle adresse du serveur, et devrait mentionner "Server Status: +Connected". Si le client ne se connecte pas après quelques instants, il faudra peut-être rafraîchir la page de +l'interface Web. + +### Jouer au jeu + +Une fois que l'interface Web affiche que la SNES et le serveur sont connectés, vous êtes prêt à jouer. Félicitations +pour avoir rejoint un multiworld ! + +## Héberger un MultiWorld + +La méthode recommandée pour héberger une partie est d'utiliser le service d'hébergement fourni par +Archipelago. Le processus est relativement simple : + +1. Récupérez les fichiers de configuration (.yaml) des joueurs. +2. Créez une archive zip contenant ces fichiers de configuration. +3. Téléversez l'archive zip sur le lien ci-dessous. + - Generate page: [WebHost Seed Generation Page](/generate) +4. Attendez un moment que la seed soit générée. +5. Lorsque la seed est générée, vous serez redirigé vers une page d'informations "Seed Info". +6. Cliquez sur "Create New Room". Cela vous amènera à la page du serveur. Fournissez le lien de cette page aux autres + joueurs afin qu'ils puissent récupérer leurs patchs. +7. Remarquez qu'un lien vers le traqueur du MultiWorld est en haut de la page de la salle. Vous devriez également + fournir ce lien aux joueurs pour qu'ils puissent suivre la progression de la partie. N'importe quelle personne voulant + observer devrait avoir accès à ce lien. +8. Une fois que tous les joueurs ont rejoint, vous pouvez commencer à jouer. From 4620493828b73657633f4ec8d94dd91d4049c4e4 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:27:35 -0400 Subject: [PATCH 08/98] Spire: Convert options, clean up random calls, and add DeathLink (#3704) * Convert StS options * probably a bad idea * Update worlds/spire/Options.py Co-authored-by: Scipio Wright --------- Co-authored-by: Kono Tyran Co-authored-by: Scipio Wright --- worlds/spire/Options.py | 25 ++++++++++++++++++------- worlds/spire/__init__.py | 13 ++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/worlds/spire/Options.py b/worlds/spire/Options.py index 76cbc4cf37ae..9c94756600d6 100644 --- a/worlds/spire/Options.py +++ b/worlds/spire/Options.py @@ -1,5 +1,7 @@ import typing -from Options import TextChoice, Option, Range, Toggle +from dataclasses import dataclass + +from Options import TextChoice, Range, Toggle, PerGameCommonOptions class Character(TextChoice): @@ -55,9 +57,18 @@ class Downfall(Toggle): default = 0 -spire_options: typing.Dict[str, type(Option)] = { - "character": Character, - "ascension": Ascension, - "final_act": FinalAct, - "downfall": Downfall, -} +class DeathLink(Range): + """Percentage of health to lose when a death link is received.""" + display_name = "Death Link %" + range_start = 0 + range_end = 100 + default = 0 + + +@dataclass +class SpireOptions(PerGameCommonOptions): + character: Character + ascension: Ascension + final_act: FinalAct + downfall: Downfall + death_link: DeathLink diff --git a/worlds/spire/__init__.py b/worlds/spire/__init__.py index 5b0e1e17f23d..a0a6a794d8a9 100644 --- a/worlds/spire/__init__.py +++ b/worlds/spire/__init__.py @@ -3,7 +3,7 @@ from BaseClasses import Entrance, Item, ItemClassification, Location, MultiWorld, Region, Tutorial from .Items import event_item_pairs, item_pool, item_table from .Locations import location_table -from .Options import spire_options +from .Options import SpireOptions from .Regions import create_regions from .Rules import set_rules from ..AutoWorld import WebWorld, World @@ -27,7 +27,8 @@ class SpireWorld(World): immense power, and Slay the Spire! """ - option_definitions = spire_options + options_dataclass = SpireOptions + options: SpireOptions game = "Slay the Spire" topology_present = False web = SpireWeb() @@ -63,15 +64,13 @@ def create_regions(self): def fill_slot_data(self) -> dict: slot_data = { - 'seed': "".join(self.multiworld.per_slot_randoms[self.player].choice(string.ascii_letters) for i in range(16)) + 'seed': "".join(self.random.choice(string.ascii_letters) for i in range(16)) } - for option_name in spire_options: - option = getattr(self.multiworld, option_name)[self.player] - slot_data[option_name] = option.value + slot_data.update(self.options.as_dict("character", "ascension", "final_act", "downfall", "death_link")) return slot_data def get_filler_item_name(self) -> str: - return self.multiworld.random.choice(["Card Draw", "Card Draw", "Card Draw", "Relic", "Relic"]) + return self.random.choice(["Card Draw", "Card Draw", "Card Draw", "Relic", "Relic"]) def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None): From c0ef02d6faaaae07c467e007133e1dc5408f6e64 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sun, 4 Aug 2024 06:55:34 -0500 Subject: [PATCH 09/98] Core: fix missing import for `MultiWorld.link_items()` (#3731) --- BaseClasses.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index a0c243c0fd9d..81601506d084 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -290,6 +290,8 @@ def set_item_links(self): def link_items(self) -> None: """Called to link together items in the itempool related to the registered item link groups.""" + from worlds import AutoWorld + for group_id, group in self.groups.items(): def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]] @@ -300,15 +302,15 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ if item.player in counters and item.name in shared_pool: counters[item.player][item.name] += 1 classifications[item.name] |= item.classification - + for player in players.copy(): if all([counters[player][item] == 0 for item in shared_pool]): players.remove(player) del (counters[player]) - + if not players: return None, None - + for item in shared_pool: count = min(counters[player][item] for player in players) if count: @@ -318,11 +320,11 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ for player in players: del (counters[player][item]) return counters, classifications - + common_item_count, classifications = find_common_pool(group["players"], group["item_pool"]) if not common_item_count: continue - + new_itempool: List[Item] = [] for item_name, item_count in next(iter(common_item_count.values())).items(): for _ in range(item_count): @@ -330,7 +332,7 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ # mangle together all original classification bits new_item.classification |= classifications[item_name] new_itempool.append(new_item) - + region = Region("Menu", group_id, self, "ItemLink") self.regions.append(region) locations = region.locations @@ -341,16 +343,16 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ None, region) loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \ state.has(item_name, group_id_, count_) - + locations.append(loc) loc.place_locked_item(item) common_item_count[item.player][item.name] -= 1 else: new_itempool.append(item) - + itemcount = len(self.itempool) self.itempool = new_itempool - + while itemcount > len(self.itempool): items_to_add = [] for player in group["players"]: From 203c8f4d89d2740ed98e4f3c1358eb7208d96dfa Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:40:16 -0400 Subject: [PATCH 10/98] Pokemon R/B: Removing Floats from NamedRange #3717 --- worlds/pokemon_rb/options.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/pokemon_rb/options.py b/worlds/pokemon_rb/options.py index 54d486a6cf9f..9f217e82e646 100644 --- a/worlds/pokemon_rb/options.py +++ b/worlds/pokemon_rb/options.py @@ -418,10 +418,10 @@ class ExpModifier(NamedRange): """Modifier for EXP gained. When specifying a number, exp is multiplied by this amount and divided by 16.""" display_name = "Exp Modifier" default = 16 - range_start = default / 4 + range_start = default // 4 range_end = 255 special_range_names = { - "half": default / 2, + "half": default // 2, "normal": default, "double": default * 2, "triple": default * 3, @@ -960,4 +960,4 @@ class RandomizePokemonPalettes(Choice): "ice_trap_weight": IceTrapWeight, "randomize_pokemon_palettes": RandomizePokemonPalettes, "death_link": DeathLink -} \ No newline at end of file +} From 98bb8517e1d40ed6f3b4e9a06024c1470e25c014 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Mon, 5 Aug 2024 18:00:33 -0400 Subject: [PATCH 11/98] Docs: Missed Full Accessibility mention/conversion #3734 --- worlds/generic/docs/advanced_settings_en.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/generic/docs/advanced_settings_en.md b/worlds/generic/docs/advanced_settings_en.md index 37467eeb468e..2197c0708e9c 100644 --- a/worlds/generic/docs/advanced_settings_en.md +++ b/worlds/generic/docs/advanced_settings_en.md @@ -102,10 +102,10 @@ See the plando guide for more info on plando options. Plando guide: [Archipelago Plando Guide](/tutorial/Archipelago/plando/en) * `accessibility` determines the level of access to the game the generation will expect you to have in order to reach - your completion goal. This supports `items`, `locations`, and `minimal` and is set to `locations` by default. - * `locations` will guarantee all locations are accessible in your world. + your completion goal. This supports `full`, `items`, and `minimal` and is set to `full` by default. + * `full` will guarantee all locations are accessible in your world. * `items` will guarantee you can acquire all logically relevant items in your world. Some items, such as keys, may - be self-locking. + be self-locking. This value only exists in and affects some worlds. * `minimal` will only guarantee that the seed is beatable. You will be guaranteed able to finish the seed logically but may not be able to access all locations or acquire all items. A good example of this is having a big key in the big chest in a dungeon in ALTTP making it impossible to get and finish the dungeon. From 90446ad1750034c03521884acb22a084995f4e7c Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:39:56 -0400 Subject: [PATCH 12/98] ChecksFinder: Refactor/Cleaning (#3725) * Update ChecksFinder * minor cleanup * Check for compatible name * Enable APWorld * Update setup_en.md * Update en_ChecksFinder.md * The client is getting updated instead * Qwint suggestions, ' -> ", streamline fill_slot_data * Oops, too many refactors --------- Co-authored-by: SunCat --- setup.py | 1 - worlds/checksfinder/Items.py | 19 ++--- worlds/checksfinder/Locations.py | 44 ++---------- worlds/checksfinder/Options.py | 6 -- worlds/checksfinder/Rules.py | 52 +++++--------- worlds/checksfinder/__init__.py | 78 ++++++++------------- worlds/checksfinder/docs/en_ChecksFinder.md | 5 -- worlds/checksfinder/docs/setup_en.md | 34 +++------ 8 files changed, 69 insertions(+), 170 deletions(-) delete mode 100644 worlds/checksfinder/Options.py diff --git a/setup.py b/setup.py index cb4d1a7511b6..0c9ee2c29302 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,6 @@ "Adventure", "ArchipIDLE", "Archipelago", - "ChecksFinder", "Clique", "Final Fantasy", "Lufia II Ancient Cave", diff --git a/worlds/checksfinder/Items.py b/worlds/checksfinder/Items.py index 2e86267396f9..5f9be79598af 100644 --- a/worlds/checksfinder/Items.py +++ b/worlds/checksfinder/Items.py @@ -3,8 +3,8 @@ class ItemData(typing.NamedTuple): - code: typing.Optional[int] - progression: bool + code: int + progression: bool = True class ChecksFinderItem(Item): @@ -12,16 +12,9 @@ class ChecksFinderItem(Item): item_table = { - "Map Width": ItemData(80000, True), - "Map Height": ItemData(80001, True), - "Map Bombs": ItemData(80002, True), + "Map Width": ItemData(80000), + "Map Height": ItemData(80001), + "Map Bombs": ItemData(80002), } -required_items = { -} - -item_frequencies = { - -} - -lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code} +lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items()} diff --git a/worlds/checksfinder/Locations.py b/worlds/checksfinder/Locations.py index 59a96c83ea8a..aefdc3838100 100644 --- a/worlds/checksfinder/Locations.py +++ b/worlds/checksfinder/Locations.py @@ -3,46 +3,14 @@ class AdvData(typing.NamedTuple): - id: typing.Optional[int] - region: str + id: int + region: str = "Board" -class ChecksFinderAdvancement(Location): +class ChecksFinderLocation(Location): game: str = "ChecksFinder" -advancement_table = { - "Tile 1": AdvData(81000, 'Board'), - "Tile 2": AdvData(81001, 'Board'), - "Tile 3": AdvData(81002, 'Board'), - "Tile 4": AdvData(81003, 'Board'), - "Tile 5": AdvData(81004, 'Board'), - "Tile 6": AdvData(81005, 'Board'), - "Tile 7": AdvData(81006, 'Board'), - "Tile 8": AdvData(81007, 'Board'), - "Tile 9": AdvData(81008, 'Board'), - "Tile 10": AdvData(81009, 'Board'), - "Tile 11": AdvData(81010, 'Board'), - "Tile 12": AdvData(81011, 'Board'), - "Tile 13": AdvData(81012, 'Board'), - "Tile 14": AdvData(81013, 'Board'), - "Tile 15": AdvData(81014, 'Board'), - "Tile 16": AdvData(81015, 'Board'), - "Tile 17": AdvData(81016, 'Board'), - "Tile 18": AdvData(81017, 'Board'), - "Tile 19": AdvData(81018, 'Board'), - "Tile 20": AdvData(81019, 'Board'), - "Tile 21": AdvData(81020, 'Board'), - "Tile 22": AdvData(81021, 'Board'), - "Tile 23": AdvData(81022, 'Board'), - "Tile 24": AdvData(81023, 'Board'), - "Tile 25": AdvData(81024, 'Board'), -} - -exclusion_table = { -} - -events_table = { -} - -lookup_id_to_name: typing.Dict[int, str] = {data.id: item_name for item_name, data in advancement_table.items() if data.id} \ No newline at end of file +base_id = 81000 +advancement_table = {f"Tile {i+1}": AdvData(base_id+i) for i in range(25)} +lookup_id_to_name: typing.Dict[int, str] = {data.id: item_name for item_name, data in advancement_table.items()} diff --git a/worlds/checksfinder/Options.py b/worlds/checksfinder/Options.py deleted file mode 100644 index a670109362f7..000000000000 --- a/worlds/checksfinder/Options.py +++ /dev/null @@ -1,6 +0,0 @@ -import typing -from Options import Option - - -checksfinder_options: typing.Dict[str, type(Option)] = { -} diff --git a/worlds/checksfinder/Rules.py b/worlds/checksfinder/Rules.py index 38d7d77ad393..8e8809be5c13 100644 --- a/worlds/checksfinder/Rules.py +++ b/worlds/checksfinder/Rules.py @@ -1,44 +1,24 @@ -from ..generic.Rules import set_rule -from BaseClasses import MultiWorld, CollectionState +from worlds.generic.Rules import set_rule +from BaseClasses import MultiWorld -def _has_total(state: CollectionState, player: int, total: int): - return (state.count('Map Width', player) + state.count('Map Height', player) + - state.count('Map Bombs', player)) >= total +items = ["Map Width", "Map Height", "Map Bombs"] # Sets rules on entrances and advancements that are always applied -def set_rules(world: MultiWorld, player: int): - set_rule(world.get_location("Tile 6", player), lambda state: _has_total(state, player, 1)) - set_rule(world.get_location("Tile 7", player), lambda state: _has_total(state, player, 2)) - set_rule(world.get_location("Tile 8", player), lambda state: _has_total(state, player, 3)) - set_rule(world.get_location("Tile 9", player), lambda state: _has_total(state, player, 4)) - set_rule(world.get_location("Tile 10", player), lambda state: _has_total(state, player, 5)) - set_rule(world.get_location("Tile 11", player), lambda state: _has_total(state, player, 6)) - set_rule(world.get_location("Tile 12", player), lambda state: _has_total(state, player, 7)) - set_rule(world.get_location("Tile 13", player), lambda state: _has_total(state, player, 8)) - set_rule(world.get_location("Tile 14", player), lambda state: _has_total(state, player, 9)) - set_rule(world.get_location("Tile 15", player), lambda state: _has_total(state, player, 10)) - set_rule(world.get_location("Tile 16", player), lambda state: _has_total(state, player, 11)) - set_rule(world.get_location("Tile 17", player), lambda state: _has_total(state, player, 12)) - set_rule(world.get_location("Tile 18", player), lambda state: _has_total(state, player, 13)) - set_rule(world.get_location("Tile 19", player), lambda state: _has_total(state, player, 14)) - set_rule(world.get_location("Tile 20", player), lambda state: _has_total(state, player, 15)) - set_rule(world.get_location("Tile 21", player), lambda state: _has_total(state, player, 16)) - set_rule(world.get_location("Tile 22", player), lambda state: _has_total(state, player, 17)) - set_rule(world.get_location("Tile 23", player), lambda state: _has_total(state, player, 18)) - set_rule(world.get_location("Tile 24", player), lambda state: _has_total(state, player, 19)) - set_rule(world.get_location("Tile 25", player), lambda state: _has_total(state, player, 20)) +def set_rules(multiworld: MultiWorld, player: int): + for i in range(20): + set_rule(multiworld.get_location(f"Tile {i+6}", player), lambda state, i=i: state.has_from_list(items, player, i+1)) # Sets rules on completion condition -def set_completion_rules(world: MultiWorld, player: int): - - width_req = 10-5 - height_req = 10-5 - bomb_req = 20-5 - completion_requirements = lambda state: \ - state.has("Map Width", player, width_req) and \ - state.has("Map Height", player, height_req) and \ - state.has("Map Bombs", player, bomb_req) - world.completion_condition[player] = lambda state: completion_requirements(state) +def set_completion_rules(multiworld: MultiWorld, player: int): + width_req = 5 # 10 - 5 + height_req = 5 # 10 - 5 + bomb_req = 15 # 20 - 5 + multiworld.completion_condition[player] = lambda state: state.has_all_counts( + { + "Map Width": width_req, + "Map Height": height_req, + "Map Bombs": bomb_req, + }, player) diff --git a/worlds/checksfinder/__init__.py b/worlds/checksfinder/__init__.py index c8b9587f8500..e064a1c41947 100644 --- a/worlds/checksfinder/__init__.py +++ b/worlds/checksfinder/__init__.py @@ -1,9 +1,9 @@ -from BaseClasses import Region, Entrance, Item, Tutorial, ItemClassification -from .Items import ChecksFinderItem, item_table, required_items -from .Locations import ChecksFinderAdvancement, advancement_table, exclusion_table -from .Options import checksfinder_options +from BaseClasses import Region, Entrance, Tutorial, ItemClassification +from .Items import ChecksFinderItem, item_table +from .Locations import ChecksFinderLocation, advancement_table +from Options import PerGameCommonOptions from .Rules import set_rules, set_completion_rules -from ..AutoWorld import World, WebWorld +from worlds.AutoWorld import World, WebWorld client_version = 7 @@ -25,38 +25,34 @@ class ChecksFinderWorld(World): ChecksFinder is a game where you avoid mines and find checks inside the board with the mines! You win when you get all your items and beat the board! """ - game: str = "ChecksFinder" - option_definitions = checksfinder_options - topology_present = True + game = "ChecksFinder" + options_dataclass = PerGameCommonOptions web = ChecksFinderWeb() item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = {name: data.id for name, data in advancement_table.items()} - def _get_checksfinder_data(self): - return { - 'world_seed': self.multiworld.per_slot_randoms[self.player].getrandbits(32), - 'seed_name': self.multiworld.seed_name, - 'player_name': self.multiworld.get_player_name(self.player), - 'player_id': self.player, - 'client_version': client_version, - 'race': self.multiworld.is_race, - } + def create_regions(self): + menu = Region("Menu", self.player, self.multiworld) + board = Region("Board", self.player, self.multiworld) + board.locations += [ChecksFinderLocation(self.player, loc_name, loc_data.id, board) + for loc_name, loc_data in advancement_table.items()] - def create_items(self): + connection = Entrance(self.player, "New Board", menu) + menu.exits.append(connection) + connection.connect(board) + self.multiworld.regions += [menu, board] + def create_items(self): # Generate item pool itempool = [] - # Add all required progression items - for (name, num) in required_items.items(): - itempool += [name] * num # Add the map width and height stuff - itempool += ["Map Width"] * (10-5) - itempool += ["Map Height"] * (10-5) + itempool += ["Map Width"] * 5 # 10 - 5 + itempool += ["Map Height"] * 5 # 10 - 5 # Add the map bombs - itempool += ["Map Bombs"] * (20-5) + itempool += ["Map Bombs"] * 15 # 20 - 5 # Convert itempool into real items - itempool = [item for item in map(lambda name: self.create_item(name), itempool)] + itempool = [self.create_item(item) for item in itempool] self.multiworld.itempool += itempool @@ -64,28 +60,16 @@ def set_rules(self): set_rules(self.multiworld, self.player) set_completion_rules(self.multiworld, self.player) - def create_regions(self): - menu = Region("Menu", self.player, self.multiworld) - board = Region("Board", self.player, self.multiworld) - board.locations += [ChecksFinderAdvancement(self.player, loc_name, loc_data.id, board) - for loc_name, loc_data in advancement_table.items() if loc_data.region == board.name] - - connection = Entrance(self.player, "New Board", menu) - menu.exits.append(connection) - connection.connect(board) - self.multiworld.regions += [menu, board] - def fill_slot_data(self): - slot_data = self._get_checksfinder_data() - for option_name in checksfinder_options: - option = getattr(self.multiworld, option_name)[self.player] - if slot_data.get(option_name, None) is None and type(option.value) in {str, int}: - slot_data[option_name] = int(option.value) - return slot_data + return { + "world_seed": self.random.getrandbits(32), + "seed_name": self.multiworld.seed_name, + "player_name": self.player_name, + "player_id": self.player, + "client_version": client_version, + "race": self.multiworld.is_race, + } - def create_item(self, name: str) -> Item: + def create_item(self, name: str) -> ChecksFinderItem: item_data = item_table[name] - item = ChecksFinderItem(name, - ItemClassification.progression if item_data.progression else ItemClassification.filler, - item_data.code, self.player) - return item + return ChecksFinderItem(name, ItemClassification.progression, item_data.code, self.player) diff --git a/worlds/checksfinder/docs/en_ChecksFinder.md b/worlds/checksfinder/docs/en_ChecksFinder.md index c9569376c5f6..cb33ab39591a 100644 --- a/worlds/checksfinder/docs/en_ChecksFinder.md +++ b/worlds/checksfinder/docs/en_ChecksFinder.md @@ -24,8 +24,3 @@ next to an icon, the number is how many you have gotten and the icon represents Victory is achieved when the player wins a board they were given after they have received all of their Map Width, Map Height, and Map Bomb items. The game will say at the bottom of the screen how many of each you have received. -## Unique Local Commands - -The following command is only available when using the ChecksFinderClient to play with Archipelago. - -- `/resync` Manually trigger a resync. diff --git a/worlds/checksfinder/docs/setup_en.md b/worlds/checksfinder/docs/setup_en.md index 673b34900af7..e15763ab3110 100644 --- a/worlds/checksfinder/docs/setup_en.md +++ b/worlds/checksfinder/docs/setup_en.md @@ -4,7 +4,6 @@ - ChecksFinder from the [Github releases Page for the game](https://github.com/jonloveslegos/ChecksFinder/releases) (latest version) -- Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases) ## Configuring your YAML file @@ -17,28 +16,15 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) You can customize your options by visiting the [ChecksFinder Player Options Page](/games/ChecksFinder/player-options) -### Generating a ChecksFinder game +## Joining a MultiWorld Game -**ChecksFinder is meant to be played _alongside_ another game! You may not be playing it for long periods of time if -you play it by itself with another person!** - -When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done, -the host will provide you with either a link to download your data file, or with a zip file containing everyone's data -files. You do not have a file inside that zip though! - -You need to start ChecksFinder client yourself, it is located within the Archipelago folder. - -### Connect to the MultiServer - -First start ChecksFinder. - -Once both ChecksFinder and the client are started. In the client at the top type in the spot labeled `Server` type the -`Ip Address` and `Port` separated with a `:` symbol. - -The client will then ask for the username you chose, input that in the text box at the bottom of the client. - -### Play the game - -When the console tells you that you have joined the room, you're all set. Congratulations on successfully joining a -multiworld game! +1. Start ChecksFinder +2. Enter the following information: + - Enter the server url (starting from `wss://` for https connection like archipelago.gg, and starting from `ws://` for http connection and local multiserver) + - Enter server port + - Enter the name of the slot you wish to connect to + - Enter the room password (optional) + - Press `Play Online` to connect +3. Start playing! +Game options and controls are described in the readme on the github repository for the game From 8ddb49f0710244945bc9b378368f691e0810fc15 Mon Sep 17 00:00:00 2001 From: digiholic Date: Tue, 6 Aug 2024 15:13:11 -0600 Subject: [PATCH 13/98] OSRS: Implement New Game (#1976) * MMBN3: Press program now has proper color index when received remotely * Initial commit of OSRS untangled from MMBN3 branch * Fixes some broken region connections * Removes some locations * Rearranges locations to fill in slots left by removed locations * Adds starting area rando * Moves Oak and Willow trees to resource regions * Fixes various PEP8 violations * Refactor of regions * Fixes variable capture issue with region rules * Partial completion of brutal grind logic * Finishes can_reach_skill function * Adds skill requirements to location rules, fixes regions rules * Adds documentation for OSRS * Removes match statement * Updates Data Version to test mode to prevent item name caching * Fixes starting spawn logic for east varrock * Fixes river lum crossing logic to not assume you can phase across water * Prevents equipping items when you haven't unlocked them * Changes canoe logic to not require huge levels * Skeletoning out some data I'll need for variable task system * Adds csvs and parser for logic * Adds Items parsing * Fixes the spawning logic to not default to Chunksanity when you didn't pick it * Begins adding generation rules for data-driven logic * Moves region handling and location creating to different methods * Adds logic limits to Options * Begun the location generation has * Randomly generates tasks for each skill until populated * Mopping up improper names, adding custom logic, and fixes location rolling * Drastically cleans up the location rolling loop * Modifies generation to properly use local variables and pass unit tests * Game is now generating, but rules don't seem to work * Lambda capture, my old nemesis. We meet again * Fixes issue with Corsair Cove item requirement causing logic loop * Okay one more fix, another variable capture * On second thought lets not have skull sceptre tasks. 'Tis a silly place * Removes QP from item pool (they're events not items) * Removes Stronghold floor tasks, no varbit to track them * Loads CSV with pkutil so it can be used in apworld * Fixes logic of skill tasks and adds QP requirements to long grinds * Fixes pathing in pkgutil call * Better handling for empty task categories, no longer throws errors * Fixes order for progressive tasks, removes un-checkable spider task * Fixes logic issues related to stew and the Blurite caves * Fixes issues generating causing tests to sporadically fail * Adds missing task that caused off-by-one error * Updates to new Options API * Updates generation to function properly with the Universal Tracker (Thanks Faris) * Replaces runtime CSV parsing with pre-made python files generated from CSVs * Switches to self.random and uses random.choice instead of doing it manually * Fixes to typing, variable names, iterators, and continue conditions * Replaces Name classes with Enums * Fixes parse error on region special rules * Skill requirements check now returns an accessrule instead of being one that checks options * Updates documentation and setup guide * Adjusts maximum numbers for combat and general tasks * Fixes region names so dictionary lookup works for chunksanity * Update worlds/osrs/docs/en_Old School Runescape.md Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Update worlds/osrs/docs/en_Old School Runescape.md Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Updates readme.md and codeowners doc * Removes erroneous East Varrock -> Al Kharid connection * Changes to canoe logic to account for woodcutting level options * Fixes embarassing typo on 'Edgeville' * Moves Logic CSVs to separate repository, addresses suggested changes on PR * Fixes logic error in east/west lumbridge regions. Fixes incorrect List typing in main * Removes task types with weight 0 from the list of rollable tasks * Missed another place that the task type had to be removed if 0 weight * Prevents adding an empty task weight if levels are too restrictive for tasks to be added * Removes giant blank space in error message * Adds player name to error for not having enough available tasks --------- Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> --- README.md | 1 + docs/CODEOWNERS | 3 + worlds/osrs/Items.py | 85 +++ worlds/osrs/Locations.py | 21 + worlds/osrs/LogicCSV/LogicCSVToPython.py | 144 +++++ worlds/osrs/LogicCSV/items_generated.py | 43 ++ worlds/osrs/LogicCSV/locations_generated.py | 127 ++++ worlds/osrs/LogicCSV/regions_generated.py | 47 ++ worlds/osrs/LogicCSV/resources_generated.py | 54 ++ worlds/osrs/Names.py | 212 +++++++ worlds/osrs/Options.py | 474 ++++++++++++++ worlds/osrs/Regions.py | 12 + worlds/osrs/__init__.py | 657 ++++++++++++++++++++ worlds/osrs/docs/en_Old School Runescape.md | 114 ++++ worlds/osrs/docs/setup_en.md | 58 ++ 15 files changed, 2052 insertions(+) create mode 100644 worlds/osrs/Items.py create mode 100644 worlds/osrs/Locations.py create mode 100644 worlds/osrs/LogicCSV/LogicCSVToPython.py create mode 100644 worlds/osrs/LogicCSV/items_generated.py create mode 100644 worlds/osrs/LogicCSV/locations_generated.py create mode 100644 worlds/osrs/LogicCSV/regions_generated.py create mode 100644 worlds/osrs/LogicCSV/resources_generated.py create mode 100644 worlds/osrs/Names.py create mode 100644 worlds/osrs/Options.py create mode 100644 worlds/osrs/Regions.py create mode 100644 worlds/osrs/__init__.py create mode 100644 worlds/osrs/docs/en_Old School Runescape.md create mode 100644 worlds/osrs/docs/setup_en.md diff --git a/README.md b/README.md index cebd4f7e7529..5b66e3db8782 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Currently, the following games are supported: * Aquaria * Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 * A Hat in Time +* Old School Runescape For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index ab841e65ee4c..4f012c306be9 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -115,6 +115,9 @@ # Ocarina of Time /worlds/oot/ @espeon65536 +# Old School Runescape +/worlds/osrs @digiholic + # Overcooked! 2 /worlds/overcooked2/ @toasterparty diff --git a/worlds/osrs/Items.py b/worlds/osrs/Items.py new file mode 100644 index 000000000000..0679c964e772 --- /dev/null +++ b/worlds/osrs/Items.py @@ -0,0 +1,85 @@ +import typing + +from BaseClasses import Item, ItemClassification +from .Names import ItemNames + + +class ItemRow(typing.NamedTuple): + name: str + amount: int + progression: ItemClassification + + +class OSRSItem(Item): + game: str = "Old School Runescape" + + +QP_Items: typing.List[str] = [ + ItemNames.QP_Cooks_Assistant, + ItemNames.QP_Demon_Slayer, + ItemNames.QP_Restless_Ghost, + ItemNames.QP_Romeo_Juliet, + ItemNames.QP_Sheep_Shearer, + ItemNames.QP_Shield_of_Arrav, + ItemNames.QP_Ernest_the_Chicken, + ItemNames.QP_Vampyre_Slayer, + ItemNames.QP_Imp_Catcher, + ItemNames.QP_Prince_Ali_Rescue, + ItemNames.QP_Dorics_Quest, + ItemNames.QP_Black_Knights_Fortress, + ItemNames.QP_Witchs_Potion, + ItemNames.QP_Knights_Sword, + ItemNames.QP_Goblin_Diplomacy, + ItemNames.QP_Pirates_Treasure, + ItemNames.QP_Rune_Mysteries, + ItemNames.QP_Misthalin_Mystery, + ItemNames.QP_Corsair_Curse, + ItemNames.QP_X_Marks_the_Spot, + ItemNames.QP_Below_Ice_Mountain +] + +starting_area_dict: typing.Dict[int, str] = { + 0: ItemNames.Lumbridge, + 1: ItemNames.Al_Kharid, + 2: ItemNames.Central_Varrock, + 3: ItemNames.West_Varrock, + 4: ItemNames.Edgeville, + 5: ItemNames.Falador, + 6: ItemNames.Draynor_Village, + 7: ItemNames.Wilderness, +} + +chunksanity_starting_chunks: typing.List[str] = [ + ItemNames.Lumbridge, + ItemNames.Lumbridge_Swamp, + ItemNames.Lumbridge_Farms, + ItemNames.HAM_Hideout, + ItemNames.Draynor_Village, + ItemNames.Draynor_Manor, + ItemNames.Wizards_Tower, + ItemNames.Al_Kharid, + ItemNames.Citharede_Abbey, + ItemNames.South_Of_Varrock, + ItemNames.Central_Varrock, + ItemNames.Varrock_Palace, + ItemNames.East_Of_Varrock, + ItemNames.West_Varrock, + ItemNames.Edgeville, + ItemNames.Barbarian_Village, + ItemNames.Monastery, + ItemNames.Ice_Mountain, + ItemNames.Dwarven_Mines, + ItemNames.Falador, + ItemNames.Falador_Farm, + ItemNames.Crafting_Guild, + ItemNames.Rimmington, + ItemNames.Port_Sarim, + ItemNames.Mudskipper_Point, + ItemNames.Wilderness +] + +# Some starting areas contain multiple regions, so if that area is rolled for Chunksanity, we need to map it to one +chunksanity_special_region_names: typing.Dict[str, str] = { + ItemNames.Lumbridge_Farms: 'Lumbridge Farms East', + ItemNames.Crafting_Guild: 'Crafting Guild Outskirts', +} diff --git a/worlds/osrs/Locations.py b/worlds/osrs/Locations.py new file mode 100644 index 000000000000..b5827d60f2fe --- /dev/null +++ b/worlds/osrs/Locations.py @@ -0,0 +1,21 @@ +import typing + +from BaseClasses import Location + + +class SkillRequirement(typing.NamedTuple): + skill: str + level: int + + +class LocationRow(typing.NamedTuple): + name: str + category: str + regions: typing.List[str] + skills: typing.List[SkillRequirement] + items: typing.List[str] + qp: int + + +class OSRSLocation(Location): + game: str = "Old School Runescape" diff --git a/worlds/osrs/LogicCSV/LogicCSVToPython.py b/worlds/osrs/LogicCSV/LogicCSVToPython.py new file mode 100644 index 000000000000..ed8bd8172a01 --- /dev/null +++ b/worlds/osrs/LogicCSV/LogicCSVToPython.py @@ -0,0 +1,144 @@ +""" +This is a utility file that converts logic in the form of CSV files into Python files that can be imported and used +directly by the world implementation. Whenever the logic files are updated, this script should be run to re-generate +the python files containing the data. +""" +import requests + +# The CSVs are updated at this repository to be shared between generator and client. +data_repository_address = "https://raw.githubusercontent.com/digiholic/osrs-archipelago-logic/" +# The Github tag of the CSVs this was generated with +data_csv_tag = "v1.5" + +if __name__ == "__main__": + import sys + import os + import csv + import typing + + # makes this module runnable from its world folder. Shamelessly stolen from Subnautica + sys.path.remove(os.path.dirname(__file__)) + new_home = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) + os.chdir(new_home) + sys.path.append(new_home) + + + def load_location_csv(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + + with open(os.path.join(this_dir, "locations_generated.py"), 'w+') as locPyFile: + locPyFile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n') + locPyFile.write("from ..Locations import LocationRow, SkillRequirement\n") + locPyFile.write("\n") + locPyFile.write("location_rows = [\n") + + with requests.get(data_repository_address + "/" + data_csv_tag + "/locations.csv") as req: + locations_reader = csv.reader(req.text.splitlines()) + for row in locations_reader: + row_line = "LocationRow(" + row_line += str_format(row[0]) + row_line += str_format(row[1].lower()) + + region_strings = row[2].split(", ") if row[2] else [] + row_line += f"{str_list_to_py(region_strings)}, " + + skill_strings = row[3].split(", ") + row_line += "[" + if skill_strings: + split_skills = [skill.split(" ") for skill in skill_strings if skill != ""] + if split_skills: + for split in split_skills: + row_line += f"SkillRequirement('{split[0]}', {split[1]}), " + row_line += "], " + + item_strings = row[4].split(", ") if row[4] else [] + row_line += f"{str_list_to_py(item_strings)}, " + row_line += f"{row[5]})" if row[5] != "" else "0)" + locPyFile.write(f"\t{row_line},\n") + locPyFile.write("]\n") + + def load_region_csv(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + + with open(os.path.join(this_dir, "regions_generated.py"), 'w+') as regPyFile: + regPyFile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n') + regPyFile.write("from ..Regions import RegionRow\n") + regPyFile.write("\n") + regPyFile.write("region_rows = [\n") + + with requests.get(data_repository_address + "/" + data_csv_tag + "/regions.csv") as req: + regions_reader = csv.reader(req.text.splitlines()) + for row in regions_reader: + row_line = "RegionRow(" + row_line += str_format(row[0]) + row_line += str_format(row[1]) + connections = row[2].replace("'", "\\'") + row_line += f"{str_list_to_py(connections.split(', '))}, " + resources = row[3].replace("'", "\\'") + row_line += f"{str_list_to_py(resources.split(', '))})" + regPyFile.write(f"\t{row_line},\n") + regPyFile.write("]\n") + + def load_resource_csv(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + + with open(os.path.join(this_dir, "resources_generated.py"), 'w+') as resPyFile: + resPyFile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n') + resPyFile.write("from ..Regions import ResourceRow\n") + resPyFile.write("\n") + resPyFile.write("resource_rows = [\n") + + with requests.get(data_repository_address + "/" + data_csv_tag + "/resources.csv") as req: + resource_reader = csv.reader(req.text.splitlines()) + for row in resource_reader: + name = row[0].replace("'", "\\'") + row_line = f"ResourceRow('{name}')" + resPyFile.write(f"\t{row_line},\n") + resPyFile.write("]\n") + + + def load_item_csv(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + + with open(os.path.join(this_dir, "items_generated.py"), 'w+') as itemPyfile: + itemPyfile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n') + itemPyfile.write("from BaseClasses import ItemClassification\n") + itemPyfile.write("from ..Items import ItemRow\n") + itemPyfile.write("\n") + itemPyfile.write("item_rows = [\n") + + with requests.get(data_repository_address + "/" + data_csv_tag + "/items.csv") as req: + item_reader = csv.reader(req.text.splitlines()) + for row in item_reader: + row_line = "ItemRow(" + row_line += str_format(row[0]) + row_line += f"{row[1]}, " + + row_line += f"ItemClassification.{row[2]})" + + itemPyfile.write(f"\t{row_line},\n") + itemPyfile.write("]\n") + + + def str_format(s) -> str: + ret_str = s.replace("'", "\\'") + return f"'{ret_str}', " + + + def str_list_to_py(str_list) -> str: + ret_str = "[" + for s in str_list: + ret_str += f"'{s}', " + ret_str += "]" + return ret_str + + + + load_location_csv() + print("Generated locations py") + load_region_csv() + print("Generated regions py") + load_resource_csv() + print("Generated resource py") + load_item_csv() + print("Generated item py") diff --git a/worlds/osrs/LogicCSV/items_generated.py b/worlds/osrs/LogicCSV/items_generated.py new file mode 100644 index 000000000000..b5e610a6e3ab --- /dev/null +++ b/worlds/osrs/LogicCSV/items_generated.py @@ -0,0 +1,43 @@ +""" +This file was auto generated by LogicCSVToPython.py +""" +from BaseClasses import ItemClassification +from ..Items import ItemRow + +item_rows = [ + ItemRow('Area: Lumbridge', 1, ItemClassification.progression), + ItemRow('Area: Lumbridge Swamp', 1, ItemClassification.progression), + ItemRow('Area: HAM Hideout', 1, ItemClassification.progression), + ItemRow('Area: Lumbridge Farms', 1, ItemClassification.progression), + ItemRow('Area: South of Varrock', 1, ItemClassification.progression), + ItemRow('Area: East Varrock', 1, ItemClassification.progression), + ItemRow('Area: Central Varrock', 1, ItemClassification.progression), + ItemRow('Area: Varrock Palace', 1, ItemClassification.progression), + ItemRow('Area: West Varrock', 1, ItemClassification.progression), + ItemRow('Area: Edgeville', 1, ItemClassification.progression), + ItemRow('Area: Barbarian Village', 1, ItemClassification.progression), + ItemRow('Area: Draynor Manor', 1, ItemClassification.progression), + ItemRow('Area: Falador', 1, ItemClassification.progression), + ItemRow('Area: Dwarven Mines', 1, ItemClassification.progression), + ItemRow('Area: Ice Mountain', 1, ItemClassification.progression), + ItemRow('Area: Monastery', 1, ItemClassification.progression), + ItemRow('Area: Falador Farms', 1, ItemClassification.progression), + ItemRow('Area: Port Sarim', 1, ItemClassification.progression), + ItemRow('Area: Mudskipper Point', 1, ItemClassification.progression), + ItemRow('Area: Karamja', 1, ItemClassification.progression), + ItemRow('Area: Crandor', 1, ItemClassification.progression), + ItemRow('Area: Rimmington', 1, ItemClassification.progression), + ItemRow('Area: Crafting Guild', 1, ItemClassification.progression), + ItemRow('Area: Draynor Village', 1, ItemClassification.progression), + ItemRow('Area: Wizard Tower', 1, ItemClassification.progression), + ItemRow('Area: Corsair Cove', 1, ItemClassification.progression), + ItemRow('Area: Al Kharid', 1, ItemClassification.progression), + ItemRow('Area: Citharede Abbey', 1, ItemClassification.progression), + ItemRow('Area: Wilderness', 1, ItemClassification.progression), + ItemRow('Progressive Armor', 6, ItemClassification.progression), + ItemRow('Progressive Weapons', 6, ItemClassification.progression), + ItemRow('Progressive Tools', 6, ItemClassification.useful), + ItemRow('Progressive Ranged Weapons', 3, ItemClassification.useful), + ItemRow('Progressive Ranged Armor', 3, ItemClassification.useful), + ItemRow('Progressive Magic', 2, ItemClassification.useful), +] diff --git a/worlds/osrs/LogicCSV/locations_generated.py b/worlds/osrs/LogicCSV/locations_generated.py new file mode 100644 index 000000000000..073e505ad8f4 --- /dev/null +++ b/worlds/osrs/LogicCSV/locations_generated.py @@ -0,0 +1,127 @@ +""" +This file was auto generated by LogicCSVToPython.py +""" +from ..Locations import LocationRow, SkillRequirement + +location_rows = [ + LocationRow('Quest: Cook\'s Assistant', 'quest', ['Lumbridge', 'Wheat', 'Windmill', 'Egg', 'Milk', ], [], [], 0), + LocationRow('Quest: Demon Slayer', 'quest', ['Central Varrock', 'Varrock Palace', 'Wizard Tower', 'South of Varrock', ], [], [], 0), + LocationRow('Quest: The Restless Ghost', 'quest', ['Lumbridge', 'Lumbridge Swamp', 'Wizard Tower', ], [], [], 0), + LocationRow('Quest: Romeo & Juliet', 'quest', ['Central Varrock', 'Varrock Palace', 'South of Varrock', 'West Varrock', ], [], [], 0), + LocationRow('Quest: Sheep Shearer', 'quest', ['Lumbridge Farms West', 'Spinning Wheel', ], [], [], 0), + LocationRow('Quest: Shield of Arrav', 'quest', ['Central Varrock', 'Varrock Palace', 'South of Varrock', 'West Varrock', ], [], [], 0), + LocationRow('Quest: Ernest the Chicken', 'quest', ['Draynor Manor', ], [], [], 0), + LocationRow('Quest: Vampyre Slayer', 'quest', ['Draynor Village', 'Central Varrock', 'Draynor Manor', ], [], [], 0), + LocationRow('Quest: Imp Catcher', 'quest', ['Wizard Tower', 'Imps', ], [], [], 0), + LocationRow('Quest: Prince Ali Rescue', 'quest', ['Al Kharid', 'Central Varrock', 'Bronze Ores', 'Clay Ore', 'Sheep', 'Spinning Wheel', 'Draynor Village', ], [], [], 0), + LocationRow('Quest: Doric\'s Quest', 'quest', ['Dwarven Mountain Pass', 'Clay Ore', 'Iron Ore', 'Bronze Ores', ], [SkillRequirement('Mining', 15), ], [], 0), + LocationRow('Quest: Black Knights\' Fortress', 'quest', ['Dwarven Mines', 'Falador', 'Monastery', 'Ice Mountain', 'Falador Farms', ], [], ['Progressive Armor', ], 12), + LocationRow('Quest: Witch\'s Potion', 'quest', ['Rimmington', 'Port Sarim', ], [], [], 0), + LocationRow('Quest: The Knight\'s Sword', 'quest', ['Falador', 'Varrock Palace', 'Mudskipper Point', 'South of Varrock', 'Windmill', 'Pie Dish', 'Port Sarim', ], [SkillRequirement('Cooking', 10), SkillRequirement('Mining', 10), ], [], 0), + LocationRow('Quest: Goblin Diplomacy', 'quest', ['Goblin Village', 'Draynor Village', 'Falador', 'South of Varrock', 'Onion', ], [], [], 0), + LocationRow('Quest: Pirate\'s Treasure', 'quest', ['Port Sarim', 'Karamja', 'Falador', ], [], [], 0), + LocationRow('Quest: Rune Mysteries', 'quest', ['Lumbridge', 'Wizard Tower', 'Central Varrock', ], [], [], 0), + LocationRow('Quest: Misthalin Mystery', 'quest', ['Lumbridge Swamp', ], [], [], 0), + LocationRow('Quest: The Corsair Curse', 'quest', ['Rimmington', 'Falador Farms', 'Corsair Cove', ], [], [], 0), + LocationRow('Quest: X Marks the Spot', 'quest', ['Lumbridge', 'Draynor Village', 'Port Sarim', ], [], [], 0), + LocationRow('Quest: Below Ice Mountain', 'quest', ['Dwarven Mines', 'Dwarven Mountain Pass', 'Ice Mountain', 'Barbarian Village', 'Falador', 'Central Varrock', 'Edgeville', ], [], [], 16), + LocationRow('Quest: Dragon Slayer', 'goal', ['Crandor', 'South of Varrock', 'Edgeville', 'Lumbridge', 'Rimmington', 'Monastery', 'Dwarven Mines', 'Port Sarim', 'Draynor Village', ], [], [], 32), + LocationRow('Activate the "Rock Skin" Prayer', 'prayer', [], [SkillRequirement('Prayer', 10), ], [], 0), + LocationRow('Activate the "Protect Item" Prayer', 'prayer', [], [SkillRequirement('Prayer', 25), ], [], 2), + LocationRow('Pray at the Edgeville Monastery', 'prayer', ['Monastery', ], [SkillRequirement('Prayer', 31), ], [], 6), + LocationRow('Cast Bones To Bananas', 'magic', ['Nature Runes', ], [SkillRequirement('Magic', 15), ], [], 0), + LocationRow('Teleport to Varrock', 'magic', ['Central Varrock', 'Law Runes', ], [SkillRequirement('Magic', 25), ], [], 0), + LocationRow('Teleport to Lumbridge', 'magic', ['Lumbridge', 'Law Runes', ], [SkillRequirement('Magic', 31), ], [], 2), + LocationRow('Teleport to Falador', 'magic', ['Falador', 'Law Runes', ], [SkillRequirement('Magic', 37), ], [], 6), + LocationRow('Craft an Air Rune', 'runecraft', ['Rune Essence', 'Falador Farms', ], [SkillRequirement('Runecraft', 1), ], [], 0), + LocationRow('Craft runes with a Mind Core', 'runecraft', ['Camdozaal', 'Goblin Village', ], [SkillRequirement('Runecraft', 2), ], [], 0), + LocationRow('Craft runes with a Body Core', 'runecraft', ['Camdozaal', 'Dwarven Mountain Pass', ], [SkillRequirement('Runecraft', 20), ], [], 0), + LocationRow('Make an Unblessed Symbol', 'crafting', ['Silver Ore', 'Furnace', 'Al Kharid', 'Sheep', 'Spinning Wheel', ], [SkillRequirement('Crafting', 16), ], [], 0), + LocationRow('Cut a Sapphire', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 20), ], [], 0), + LocationRow('Cut an Emerald', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 27), ], [], 0), + LocationRow('Cut a Ruby', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 34), ], [], 4), + LocationRow('Cut a Diamond', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 43), ], [], 8), + LocationRow('Mine a Blurite Ore', 'mining', ['Mudskipper Point', 'Port Sarim', ], [SkillRequirement('Mining', 10), ], [], 0), + LocationRow('Crush a Barronite Deposit', 'mining', ['Camdozaal', ], [SkillRequirement('Mining', 14), ], [], 0), + LocationRow('Mine Silver', 'mining', ['Silver Ore', ], [SkillRequirement('Mining', 20), ], [], 0), + LocationRow('Mine Coal', 'mining', ['Coal Ore', ], [SkillRequirement('Mining', 30), ], [], 2), + LocationRow('Mine Gold', 'mining', ['Gold Ore', ], [SkillRequirement('Mining', 40), ], [], 6), + LocationRow('Smelt an Iron Bar', 'smithing', ['Iron Ore', 'Furnace', ], [SkillRequirement('Smithing', 15), SkillRequirement('Mining', 15), ], [], 0), + LocationRow('Smelt a Silver Bar', 'smithing', ['Silver Ore', 'Furnace', ], [SkillRequirement('Smithing', 20), SkillRequirement('Mining', 20), ], [], 0), + LocationRow('Smelt a Steel Bar', 'smithing', ['Coal Ore', 'Iron Ore', 'Furnace', ], [SkillRequirement('Smithing', 30), SkillRequirement('Mining', 30), ], [], 2), + LocationRow('Smelt a Gold Bar', 'smithing', ['Gold Ore', 'Furnace', ], [SkillRequirement('Smithing', 40), SkillRequirement('Mining', 40), ], [], 6), + LocationRow('Catch some Anchovies', 'fishing', ['Shrimp Spot', ], [SkillRequirement('Fishing', 15), ], [], 0), + LocationRow('Catch a Trout', 'fishing', ['Fly Fishing Spot', ], [SkillRequirement('Fishing', 20), ], [], 0), + LocationRow('Prepare a Tetra', 'fishing', ['Camdozaal', ], [SkillRequirement('Fishing', 33), SkillRequirement('Cooking', 33), ], [], 2), + LocationRow('Catch a Lobster', 'fishing', ['Lobster Spot', ], [SkillRequirement('Fishing', 40), ], [], 6), + LocationRow('Catch a Swordfish', 'fishing', ['Lobster Spot', ], [SkillRequirement('Fishing', 50), ], [], 12), + LocationRow('Bake a Redberry Pie', 'cooking', ['Redberry Bush', 'Wheat', 'Windmill', 'Pie Dish', ], [SkillRequirement('Cooking', 10), ], [], 0), + LocationRow('Cook some Stew', 'cooking', ['Bowl', 'Meat', 'Potato', ], [SkillRequirement('Cooking', 25), ], [], 0), + LocationRow('Bake an Apple Pie', 'cooking', ['Cooking Apple', 'Wheat', 'Windmill', 'Pie Dish', ], [SkillRequirement('Cooking', 30), ], [], 2), + LocationRow('Bake a Cake', 'cooking', ['Wheat', 'Windmill', 'Egg', 'Milk', 'Cake Tin', ], [SkillRequirement('Cooking', 40), ], [], 6), + LocationRow('Bake a Meat Pizza', 'cooking', ['Wheat', 'Windmill', 'Cheese', 'Tomato', 'Meat', ], [SkillRequirement('Cooking', 45), ], [], 8), + LocationRow('Burn some Oak Logs', 'firemaking', ['Oak Tree', ], [SkillRequirement('Firemaking', 15), ], [], 0), + LocationRow('Burn some Willow Logs', 'firemaking', ['Willow Tree', ], [SkillRequirement('Firemaking', 30), ], [], 0), + LocationRow('Travel on a Canoe', 'woodcutting', ['Canoe Tree', ], [SkillRequirement('Woodcutting', 12), ], [], 0), + LocationRow('Cut an Oak Log', 'woodcutting', ['Oak Tree', ], [SkillRequirement('Woodcutting', 15), ], [], 0), + LocationRow('Cut a Willow Log', 'woodcutting', ['Willow Tree', ], [SkillRequirement('Woodcutting', 30), ], [], 0), + LocationRow('Kill Jeff', 'combat', ['Dwarven Mountain Pass', ], [SkillRequirement('Combat', 2), ], [], 0), + LocationRow('Kill a Goblin', 'combat', ['Goblin', ], [SkillRequirement('Combat', 2), ], [], 0), + LocationRow('Kill a Monkey', 'combat', ['Karamja', ], [SkillRequirement('Combat', 3), ], [], 0), + LocationRow('Kill a Barbarian', 'combat', ['Barbarian', ], [SkillRequirement('Combat', 10), ], [], 0), + LocationRow('Kill a Giant Frog', 'combat', ['Lumbridge Swamp', ], [SkillRequirement('Combat', 13), ], [], 0), + LocationRow('Kill a Zombie', 'combat', ['Zombie', ], [SkillRequirement('Combat', 13), ], [], 0), + LocationRow('Kill a Guard', 'combat', ['Guard', ], [SkillRequirement('Combat', 21), ], [], 0), + LocationRow('Kill a Hill Giant', 'combat', ['Hill Giant', ], [SkillRequirement('Combat', 28), ], [], 2), + LocationRow('Kill a Deadly Red Spider', 'combat', ['Deadly Red Spider', ], [SkillRequirement('Combat', 34), ], [], 2), + LocationRow('Kill a Moss Giant', 'combat', ['Moss Giant', ], [SkillRequirement('Combat', 42), ], [], 2), + LocationRow('Kill a Catablepon', 'combat', ['Barbarian Village', ], [SkillRequirement('Combat', 49), ], [], 4), + LocationRow('Kill an Ice Giant', 'combat', ['Ice Giant', ], [SkillRequirement('Combat', 53), ], [], 4), + LocationRow('Kill a Lesser Demon', 'combat', ['Lesser Demon', ], [SkillRequirement('Combat', 82), ], [], 8), + LocationRow('Kill an Ogress Shaman', 'combat', ['Corsair Cove', ], [SkillRequirement('Combat', 82), ], [], 8), + LocationRow('Kill Obor', 'combat', ['Edgeville', ], [SkillRequirement('Combat', 106), ], [], 28), + LocationRow('Kill Bryophyta', 'combat', ['Central Varrock', ], [SkillRequirement('Combat', 128), ], [], 28), + LocationRow('Total XP 5,000', 'general', [], [], [], 0), + LocationRow('Combat Level 5', 'general', [], [], [], 0), + LocationRow('Total XP 10,000', 'general', [], [], [], 0), + LocationRow('Total Level 50', 'general', [], [], [], 0), + LocationRow('Total XP 25,000', 'general', [], [], [], 0), + LocationRow('Total Level 100', 'general', [], [], [], 0), + LocationRow('Total XP 50,000', 'general', [], [], [], 0), + LocationRow('Combat Level 15', 'general', [], [], [], 0), + LocationRow('Total Level 150', 'general', [], [], [], 2), + LocationRow('Total XP 75,000', 'general', [], [], [], 2), + LocationRow('Combat Level 25', 'general', [], [], [], 2), + LocationRow('Total XP 100,000', 'general', [], [], [], 6), + LocationRow('Total Level 200', 'general', [], [], [], 6), + LocationRow('Total XP 125,000', 'general', [], [], [], 6), + LocationRow('Combat Level 30', 'general', [], [], [], 10), + LocationRow('Total Level 250', 'general', [], [], [], 10), + LocationRow('Total XP 150,000', 'general', [], [], [], 10), + LocationRow('Total Level 300', 'general', [], [], [], 16), + LocationRow('Combat Level 40', 'general', [], [], [], 16), + LocationRow('Open a Simple Lockbox', 'general', ['Camdozaal', ], [], [], 0), + LocationRow('Open an Elaborate Lockbox', 'general', ['Camdozaal', ], [], [], 0), + LocationRow('Open an Ornate Lockbox', 'general', ['Camdozaal', ], [], [], 0), + LocationRow('Points: Cook\'s Assistant', 'points', [], [], [], 0), + LocationRow('Points: Demon Slayer', 'points', [], [], [], 0), + LocationRow('Points: The Restless Ghost', 'points', [], [], [], 0), + LocationRow('Points: Romeo & Juliet', 'points', [], [], [], 0), + LocationRow('Points: Sheep Shearer', 'points', [], [], [], 0), + LocationRow('Points: Shield of Arrav', 'points', [], [], [], 0), + LocationRow('Points: Ernest the Chicken', 'points', [], [], [], 0), + LocationRow('Points: Vampyre Slayer', 'points', [], [], [], 0), + LocationRow('Points: Imp Catcher', 'points', [], [], [], 0), + LocationRow('Points: Prince Ali Rescue', 'points', [], [], [], 0), + LocationRow('Points: Doric\'s Quest', 'points', [], [], [], 0), + LocationRow('Points: Black Knights\' Fortress', 'points', [], [], [], 0), + LocationRow('Points: Witch\'s Potion', 'points', [], [], [], 0), + LocationRow('Points: The Knight\'s Sword', 'points', [], [], [], 0), + LocationRow('Points: Goblin Diplomacy', 'points', [], [], [], 0), + LocationRow('Points: Pirate\'s Treasure', 'points', [], [], [], 0), + LocationRow('Points: Rune Mysteries', 'points', [], [], [], 0), + LocationRow('Points: Misthalin Mystery', 'points', [], [], [], 0), + LocationRow('Points: The Corsair Curse', 'points', [], [], [], 0), + LocationRow('Points: X Marks the Spot', 'points', [], [], [], 0), + LocationRow('Points: Below Ice Mountain', 'points', [], [], [], 0), +] diff --git a/worlds/osrs/LogicCSV/regions_generated.py b/worlds/osrs/LogicCSV/regions_generated.py new file mode 100644 index 000000000000..87b3747d938e --- /dev/null +++ b/worlds/osrs/LogicCSV/regions_generated.py @@ -0,0 +1,47 @@ +""" +This file was auto generated by LogicCSVToPython.py +""" +from ..Regions import RegionRow + +region_rows = [ + RegionRow('Lumbridge', 'Area: Lumbridge', ['Lumbridge Farms East', 'Lumbridge Farms West', 'Al Kharid', 'Lumbridge Swamp', 'HAM Hideout', 'South of Varrock', 'Barbarian Village', 'Edgeville', 'Wilderness', ], ['Mind Runes', 'Spinning Wheel', 'Furnace', 'Chisel', 'Bronze Anvil', 'Fly Fishing Spot', 'Bowl', 'Cake Tin', 'Oak Tree', 'Willow Tree', 'Canoe Tree', 'Goblin', 'Imps', ]), + RegionRow('Lumbridge Swamp', 'Area: Lumbridge Swamp', ['Lumbridge', 'HAM Hideout', ], ['Bronze Ores', 'Coal Ore', 'Shrimp Spot', 'Meat', 'Goblin', 'Imps', ]), + RegionRow('HAM Hideout', 'Area: HAM Hideout', ['Lumbridge Farms West', 'Lumbridge', 'Lumbridge Swamp', 'Draynor Village', ], ['Goblin', ]), + RegionRow('Lumbridge Farms West', 'Area: Lumbridge Farms', ['Sourhog\'s Lair', 'HAM Hideout', 'Draynor Village', ], ['Sheep', 'Meat', 'Wheat', 'Windmill', 'Egg', 'Milk', 'Willow Tree', 'Imps', 'Potato', ]), + RegionRow('Lumbridge Farms East', 'Area: Lumbridge Farms', ['South of Varrock', 'Lumbridge', ], ['Meat', 'Egg', 'Milk', 'Willow Tree', 'Goblin', 'Imps', 'Potato', ]), + RegionRow('Sourhog\'s Lair', 'Area: South of Varrock', ['Lumbridge Farms West', 'Draynor Manor Outskirts', ], ['', ]), + RegionRow('South of Varrock', 'Area: South of Varrock', ['Al Kharid', 'West Varrock', 'Central Varrock', 'East Varrock', 'Lumbridge Farms East', 'Lumbridge', 'Barbarian Village', 'Edgeville', 'Wilderness', ], ['Sheep', 'Bronze Ores', 'Iron Ore', 'Silver Ore', 'Redberry Bush', 'Meat', 'Wheat', 'Oak Tree', 'Willow Tree', 'Canoe Tree', 'Guard', 'Imps', 'Clay Ore', ]), + RegionRow('East Varrock', 'Area: East Varrock', ['Wilderness', 'South of Varrock', 'Central Varrock', 'Varrock Palace', ], ['Guard', ]), + RegionRow('Central Varrock', 'Area: Central Varrock', ['Varrock Palace', 'East Varrock', 'South of Varrock', 'West Varrock', ], ['Mind Runes', 'Chisel', 'Anvil', 'Bowl', 'Cake Tin', 'Oak Tree', 'Barbarian', 'Guard', 'Rune Essence', 'Imps', ]), + RegionRow('Varrock Palace', 'Area: Varrock Palace', ['Wilderness', 'East Varrock', 'Central Varrock', 'West Varrock', ], ['Pie Dish', 'Oak Tree', 'Zombie', 'Guard', 'Deadly Red Spider', 'Moss Giant', 'Nature Runes', 'Law Runes', ]), + RegionRow('West Varrock', 'Area: West Varrock', ['Wilderness', 'Varrock Palace', 'South of Varrock', 'Barbarian Village', 'Edgeville', 'Cook\'s Guild', ], ['Anvil', 'Wheat', 'Oak Tree', 'Goblin', 'Guard', 'Onion', ]), + RegionRow('Cook\'s Guild', 'Area: West Varrock*', ['West Varrock', ], ['Bowl', 'Cooking Apple', 'Pie Dish', 'Cake Tin', 'Windmill', ]), + RegionRow('Edgeville', 'Area: Edgeville', ['Wilderness', 'West Varrock', 'Barbarian Village', 'South of Varrock', 'Lumbridge', ], ['Furnace', 'Chisel', 'Bronze Ores', 'Iron Ore', 'Coal Ore', 'Bowl', 'Meat', 'Cake Tin', 'Willow Tree', 'Canoe Tree', 'Zombie', 'Guard', 'Hill Giant', 'Nature Runes', 'Law Runes', 'Imps', ]), + RegionRow('Barbarian Village', 'Area: Barbarian Village', ['Edgeville', 'West Varrock', 'Draynor Manor Outskirts', 'Dwarven Mountain Pass', ], ['Spinning Wheel', 'Coal Ore', 'Anvil', 'Fly Fishing Spot', 'Meat', 'Canoe Tree', 'Barbarian', 'Zombie', 'Law Runes', ]), + RegionRow('Draynor Manor Outskirts', 'Area: Draynor Manor', ['Barbarian Village', 'Sourhog\'s Lair', 'Draynor Village', 'Falador East Outskirts', ], ['Goblin', ]), + RegionRow('Draynor Manor', 'Area: Draynor Manor', ['Draynor Village', ], ['', ]), + RegionRow('Falador East Outskirts', 'Area: Falador', ['Dwarven Mountain Pass', 'Draynor Manor Outskirts', 'Falador Farms', ], ['', ]), + RegionRow('Dwarven Mountain Pass', 'Area: Dwarven Mines', ['Goblin Village', 'Monastery', 'Barbarian Village', 'Falador East Outskirts', 'Falador', ], ['Anvil*', 'Wheat', ]), + RegionRow('Dwarven Mines', 'Area: Dwarven Mines', ['Monastery', 'Ice Mountain', 'Falador', ], ['Chisel', 'Bronze Ores', 'Iron Ore', 'Coal Ore', 'Gold Ore', 'Anvil', 'Pie Dish', 'Clay Ore', ]), + RegionRow('Goblin Village', 'Area: Ice Mountain', ['Wilderness', 'Dwarven Mountain Pass', ], ['Meat', ]), + RegionRow('Ice Mountain', 'Area: Ice Mountain', ['Wilderness', 'Monastery', 'Dwarven Mines', 'Camdozaal*', ], ['', ]), + RegionRow('Camdozaal', 'Area: Ice Mountain', ['Ice Mountain', ], ['Clay Ore', ]), + RegionRow('Monastery', 'Area: Monastery', ['Wilderness', 'Dwarven Mountain Pass', 'Dwarven Mines', 'Ice Mountain', ], ['Sheep', ]), + RegionRow('Falador', 'Area: Falador', ['Dwarven Mountain Pass', 'Falador Farms', 'Dwarven Mines', ], ['Furnace', 'Chisel', 'Bowl', 'Cake Tin', 'Oak Tree', 'Guard', 'Imps', ]), + RegionRow('Falador Farms', 'Area: Falador Farms', ['Falador', 'Falador East Outskirts', 'Draynor Village', 'Port Sarim', 'Rimmington', 'Crafting Guild Outskirts', ], ['Spinning Wheel', 'Meat', 'Egg', 'Milk', 'Oak Tree', 'Imps', ]), + RegionRow('Port Sarim', 'Area: Port Sarim', ['Falador Farms', 'Mudskipper Point', 'Rimmington', 'Karamja Docks', 'Crandor', ], ['Mind Runes', 'Shrimp Spot', 'Meat', 'Cheese', 'Tomato', 'Oak Tree', 'Willow Tree', 'Goblin', 'Potato', ]), + RegionRow('Karamja Docks', 'Area: Mudskipper Point', ['Port Sarim', 'Karamja', ], ['', ]), + RegionRow('Mudskipper Point', 'Area: Mudskipper Point', ['Rimmington', 'Port Sarim', ], ['Anvil', 'Ice Giant', 'Nature Runes', 'Law Runes', ]), + RegionRow('Karamja', 'Area: Karamja', ['Karamja Docks', 'Crandor', ], ['Gold Ore', 'Lobster Spot', 'Bowl', 'Cake Tin', 'Deadly Red Spider', 'Imps', ]), + RegionRow('Crandor', 'Area: Crandor', ['Karamja', 'Port Sarim', ], ['Coal Ore', 'Gold Ore', 'Moss Giant', 'Lesser Demon', 'Nature Runes', 'Law Runes', ]), + RegionRow('Rimmington', 'Area: Rimmington', ['Falador Farms', 'Port Sarim', 'Mudskipper Point', 'Crafting Guild Peninsula', 'Corsair Cove', ], ['Chisel', 'Bronze Ores', 'Iron Ore', 'Gold Ore', 'Bowl', 'Cake Tin', 'Wheat', 'Oak Tree', 'Willow Tree', 'Crafting Moulds', 'Imps', 'Clay Ore', 'Onion', ]), + RegionRow('Crafting Guild Peninsula', 'Area: Crafting Guild', ['Falador Farms', 'Rimmington', ], ['', ]), + RegionRow('Crafting Guild Outskirts', 'Area: Crafting Guild', ['Falador Farms', 'Crafting Guild', ], ['Sheep', 'Willow Tree', 'Oak Tree', ]), + RegionRow('Crafting Guild', 'Area: Crafting Guild*', ['Crafting Guild', ], ['Spinning Wheel', 'Chisel', 'Silver Ore', 'Gold Ore', 'Meat', 'Milk', 'Clay Ore', ]), + RegionRow('Draynor Village', 'Area: Draynor Village', ['Draynor Manor', 'Lumbridge Farms West', 'HAM Hideout', 'Wizard Tower', ], ['Anvil', 'Shrimp Spot', 'Wheat', 'Cheese', 'Tomato', 'Willow Tree', 'Goblin', 'Zombie', 'Nature Runes', 'Law Runes', 'Imps', ]), + RegionRow('Wizard Tower', 'Area: Wizard Tower', ['Draynor Village', ], ['Lesser Demon', 'Rune Essence', ]), + RegionRow('Corsair Cove', 'Area: Corsair Cove*', ['Rimmington', ], ['Anvil', 'Meat', ]), + RegionRow('Al Kharid', 'Area: Al Kharid', ['South of Varrock', 'Citharede Abbey', 'Lumbridge', 'Port Sarim', ], ['Furnace', 'Chisel', 'Bronze Ores', 'Iron Ore', 'Silver Ore', 'Coal Ore', 'Gold Ore', 'Shrimp Spot', 'Bowl', 'Cake Tin', 'Cheese', 'Crafting Moulds', 'Imps', ]), + RegionRow('Citharede Abbey', 'Area: Citharede Abbey', ['Al Kharid', ], ['Iron Ore', 'Coal Ore', 'Anvil', 'Hill Giant', 'Nature Runes', 'Law Runes', ]), + RegionRow('Wilderness', 'Area: Wilderness', ['East Varrock', 'Varrock Palace', 'West Varrock', 'Edgeville', 'Monastery', 'Ice Mountain', 'Goblin Village', 'South of Varrock', 'Lumbridge', ], ['Furnace', 'Chisel', 'Iron Ore', 'Coal Ore', 'Anvil', 'Meat', 'Cake Tin', 'Cheese', 'Tomato', 'Oak Tree', 'Canoe Tree', 'Zombie', 'Hill Giant', 'Deadly Red Spider', 'Moss Giant', 'Ice Giant', 'Lesser Demon', 'Nature Runes', 'Law Runes', ]), +] diff --git a/worlds/osrs/LogicCSV/resources_generated.py b/worlds/osrs/LogicCSV/resources_generated.py new file mode 100644 index 000000000000..18c2ebe2f317 --- /dev/null +++ b/worlds/osrs/LogicCSV/resources_generated.py @@ -0,0 +1,54 @@ +""" +This file was auto generated by LogicCSVToPython.py +""" +from ..Regions import ResourceRow + +resource_rows = [ + ResourceRow('Mind Runes'), + ResourceRow('Spinning Wheel'), + ResourceRow('Sheep'), + ResourceRow('Furnace'), + ResourceRow('Chisel'), + ResourceRow('Bronze Ores'), + ResourceRow('Iron Ore'), + ResourceRow('Silver Ore'), + ResourceRow('Coal Ore'), + ResourceRow('Gold Ore'), + ResourceRow('Bronze Anvil'), + ResourceRow('Anvil'), + ResourceRow('Shrimp Spot'), + ResourceRow('Fly Fishing Spot'), + ResourceRow('Lobster Spot'), + ResourceRow('Redberry Bush'), + ResourceRow('Bowl'), + ResourceRow('Meat'), + ResourceRow('Cooking Apple'), + ResourceRow('Pie Dish'), + ResourceRow('Cake Tin'), + ResourceRow('Wheat'), + ResourceRow('Windmill'), + ResourceRow('Egg'), + ResourceRow('Milk'), + ResourceRow('Cheese'), + ResourceRow('Tomato'), + ResourceRow('Oak Tree'), + ResourceRow('Willow Tree'), + ResourceRow('Canoe Tree'), + ResourceRow('Goblin'), + ResourceRow('Barbarian'), + ResourceRow('Zombie'), + ResourceRow('Guard'), + ResourceRow('Hill Giant'), + ResourceRow('Deadly Red Spider'), + ResourceRow('Moss Giant'), + ResourceRow('Ice Giant'), + ResourceRow('Lesser Demon'), + ResourceRow('Rune Essence'), + ResourceRow('Crafting Moulds'), + ResourceRow('Nature Runes'), + ResourceRow('Law Runes'), + ResourceRow('Imps'), + ResourceRow('Clay Ore'), + ResourceRow('Onion'), + ResourceRow('Potato'), +] diff --git a/worlds/osrs/Names.py b/worlds/osrs/Names.py new file mode 100644 index 000000000000..95aed742b6f1 --- /dev/null +++ b/worlds/osrs/Names.py @@ -0,0 +1,212 @@ +from enum import Enum + + +class RegionNames(str, Enum): + Lumbridge = "Lumbridge" + Lumbridge_Swamp = "Lumbridge Swamp" + Lumbridge_Farms_East = "Lumbridge Farms East" + Lumbridge_Farms_West = "Lumbridge Farms West" + HAM_Hideout = "HAM Hideout" + Draynor_Village = "Draynor Village" + Draynor_Manor = "Draynor Manor" + Wizards_Tower = "Wizard Tower" + Al_Kharid = "Al Kharid" + Citharede_Abbey = "Citharede Abbey" + South_Of_Varrock = "South of Varrock" + Central_Varrock = "Central Varrock" + Varrock_Palace = "Varrock Palace" + East_Of_Varrock = "East Varrock" + West_Varrock = "West Varrock" + Edgeville = "Edgeville" + Barbarian_Village = "Barbarian Village" + Monastery = "Monastery" + Ice_Mountain = "Ice Mountain" + Dwarven_Mines = "Dwarven Mines" + Falador = "Falador" + Falador_Farm = "Falador Farms" + Crafting_Guild = "Crafting Guild" + Cooks_Guild = "Cook's Guild" + Rimmington = "Rimmington" + Port_Sarim = "Port Sarim" + Mudskipper_Point = "Mudskipper Point" + Karamja = "Karamja" + Corsair_Cove = "Corsair Cove" + Wilderness = "The Wilderness" + Crandor = "Crandor" + # Resource Regions + Egg = "Egg" + Sheep = "Sheep" + Milk = "Milk" + Wheat = "Wheat" + Windmill = "Windmill" + Spinning_Wheel = "Spinning Wheel" + Imp = "Imp" + Bronze_Ores = "Bronze Ores" + Clay_Rock = "Clay Ore" + Coal_Rock = "Coal Ore" + Iron_Rock = "Iron Ore" + Silver_Rock = "Silver Ore" + Gold_Rock = "Gold Ore" + Furnace = "Furnace" + Anvil = "Anvil" + Oak_Tree = "Oak Tree" + Willow_Tree = "Willow Tree" + Shrimp = "Shrimp Spot" + Fly_Fish = "Fly Fishing Spot" + Lobster = "Lobster Spot" + Mind_Runes = "Mind Runes" + Canoe_Tree = "Canoe Tree" + + __str__ = str.__str__ + + +class ItemNames(str, Enum): + Lumbridge = "Area: Lumbridge" + Lumbridge_Swamp = "Area: Lumbridge Swamp" + Lumbridge_Farms = "Area: Lumbridge Farms" + HAM_Hideout = "Area: HAM Hideout" + Draynor_Village = "Area: Draynor Village" + Draynor_Manor = "Area: Draynor Manor" + Wizards_Tower = "Area: Wizard Tower" + Al_Kharid = "Area: Al Kharid" + Citharede_Abbey = "Area: Citharede Abbey" + South_Of_Varrock = "Area: South of Varrock" + Central_Varrock = "Area: Central Varrock" + Varrock_Palace = "Area: Varrock Palace" + East_Of_Varrock = "Area: East Varrock" + West_Varrock = "Area: West Varrock" + Edgeville = "Area: Edgeville" + Barbarian_Village = "Area: Barbarian Village" + Monastery = "Area: Monastery" + Ice_Mountain = "Area: Ice Mountain" + Dwarven_Mines = "Area: Dwarven Mines" + Falador = "Area: Falador" + Falador_Farm = "Area: Falador Farms" + Crafting_Guild = "Area: Crafting Guild" + Rimmington = "Area: Rimmington" + Port_Sarim = "Area: Port Sarim" + Mudskipper_Point = "Area: Mudskipper Point" + Karamja = "Area: Karamja" + Crandor = "Area: Crandor" + Corsair_Cove = "Area: Corsair Cove" + Wilderness = "Area: Wilderness" + Progressive_Armor = "Progressive Armor" + Progressive_Weapons = "Progressive Weapons" + Progressive_Tools = "Progressive Tools" + Progressive_Range_Armor = "Progressive Range Armor" + Progressive_Range_Weapon = "Progressive Range Weapon" + Progressive_Magic = "Progressive Magic Spell" + Lobsters = "10 Lobsters" + Swordfish = "5 Swordfish" + Energy_Potions = "10 Energy Potions" + Coins = "5,000 Coins" + Mind_Runes = "50 Mind Runes" + Chaos_Runes = "25 Chaos Runes" + Death_Runes = "10 Death Runes" + Law_Runes = "10 Law Runes" + QP_Cooks_Assistant = "1 QP (Cook's Assistant)" + QP_Demon_Slayer = "3 QP (Demon Slayer)" + QP_Restless_Ghost = "1 QP (The Restless Ghost)" + QP_Romeo_Juliet = "5 QP (Romeo & Juliet)" + QP_Sheep_Shearer = "1 QP (Sheep Shearer)" + QP_Shield_of_Arrav = "1 QP (Shield of Arrav)" + QP_Ernest_the_Chicken = "4 QP (Ernest the Chicken)" + QP_Vampyre_Slayer = "3 QP (Vampyre Slayer)" + QP_Imp_Catcher = "1 QP (Imp Catcher)" + QP_Prince_Ali_Rescue = "3 QP (Prince Ali Rescue)" + QP_Dorics_Quest = "1 QP (Doric's Quest)" + QP_Black_Knights_Fortress = "3 QP (Black Knights' Fortress)" + QP_Witchs_Potion = "1 QP (Witch's Potion)" + QP_Knights_Sword = "1 QP (The Knight's Sword)" + QP_Goblin_Diplomacy = "5 QP (Goblin Diplomacy)" + QP_Pirates_Treasure = "2 QP (Pirate's Treasure)" + QP_Rune_Mysteries = "1 QP (Rune Mysteries)" + QP_Misthalin_Mystery = "1 QP (Misthalin Mystery)" + QP_Corsair_Curse = "2 QP (The Corsair Curse)" + QP_X_Marks_the_Spot = "1 QP (X Marks The Spot)" + QP_Below_Ice_Mountain = "1 QP (Below Ice Mountain)" + + __str__ = str.__str__ + + +class LocationNames(str, Enum): + Q_Cooks_Assistant = "Quest: Cook's Assistant" + Q_Demon_Slayer = "Quest: Demon Slayer" + Q_Restless_Ghost = "Quest: The Restless Ghost" + Q_Romeo_Juliet = "Quest: Romeo & Juliet" + Q_Sheep_Shearer = "Quest: Sheep Shearer" + Q_Shield_of_Arrav = "Quest: Shield of Arrav" + Q_Ernest_the_Chicken = "Quest: Ernest the Chicken" + Q_Vampyre_Slayer = "Quest: Vampyre Slayer" + Q_Imp_Catcher = "Quest: Imp Catcher" + Q_Prince_Ali_Rescue = "Quest: Prince Ali Rescue" + Q_Dorics_Quest = "Quest: Doric's Quest" + Q_Black_Knights_Fortress = "Quest: Black Knights' Fortress" + Q_Witchs_Potion = "Quest: Witch's Potion" + Q_Knights_Sword = "Quest: The Knight's Sword" + Q_Goblin_Diplomacy = "Quest: Goblin Diplomacy" + Q_Pirates_Treasure = "Quest: Pirate's Treasure" + Q_Rune_Mysteries = "Quest: Rune Mysteries" + Q_Misthalin_Mystery = "Quest: Misthalin Mystery" + Q_Corsair_Curse = "Quest: The Corsair Curse" + Q_X_Marks_the_Spot = "Quest: X Marks the Spot" + Q_Below_Ice_Mountain = "Quest: Below Ice Mountain" + QP_Cooks_Assistant = "Points: Cook's Assistant" + QP_Demon_Slayer = "Points: Demon Slayer" + QP_Restless_Ghost = "Points: The Restless Ghost" + QP_Romeo_Juliet = "Points: Romeo & Juliet" + QP_Sheep_Shearer = "Points: Sheep Shearer" + QP_Shield_of_Arrav = "Points: Shield of Arrav" + QP_Ernest_the_Chicken = "Points: Ernest the Chicken" + QP_Vampyre_Slayer = "Points: Vampyre Slayer" + QP_Imp_Catcher = "Points: Imp Catcher" + QP_Prince_Ali_Rescue = "Points: Prince Ali Rescue" + QP_Dorics_Quest = "Points: Doric's Quest" + QP_Black_Knights_Fortress = "Points: Black Knights' Fortress" + QP_Witchs_Potion = "Points: Witch's Potion" + QP_Knights_Sword = "Points: The Knight's Sword" + QP_Goblin_Diplomacy = "Points: Goblin Diplomacy" + QP_Pirates_Treasure = "Points: Pirate's Treasure" + QP_Rune_Mysteries = "Points: Rune Mysteries" + QP_Misthalin_Mystery = "Points: Misthalin Mystery" + QP_Corsair_Curse = "Points: The Corsair Curse" + QP_X_Marks_the_Spot = "Points: X Marks the Spot" + QP_Below_Ice_Mountain = "Points: Below Ice Mountain" + Guppy = "Prepare a Guppy" + Cavefish = "Prepare a Cavefish" + Tetra = "Prepare a Tetra" + Barronite_Deposit = "Crush a Barronite Deposit" + Oak_Log = "Cut an Oak Log" + Willow_Log = "Cut a Willow Log" + Catch_Lobster = "Catch a Lobster" + Mine_Silver = "Mine Silver" + Mine_Coal = "Mine Coal" + Mine_Gold = "Mine Gold" + Smelt_Silver = "Smelt a Silver Bar" + Smelt_Steel = "Smelt a Steel Bar" + Smelt_Gold = "Smelt a Gold Bar" + Cut_Sapphire = "Cut a Sapphire" + Cut_Emerald = "Cut an Emerald" + Cut_Ruby = "Cut a Ruby" + Cut_Diamond = "Cut a Diamond" + K_Lesser_Demon = "Kill a Lesser Demon" + K_Ogress_Shaman = "Kill an Ogress Shaman" + Bake_Apple_Pie = "Bake an Apple Pie" + Bake_Cake = "Bake a Cake" + Bake_Meat_Pizza = "Bake a Meat Pizza" + Total_XP_5000 = "5,000 Total XP" + Total_XP_10000 = "10,000 Total XP" + Total_XP_25000 = "25,000 Total XP" + Total_XP_50000 = "50,000 Total XP" + Total_XP_100000 = "100,000 Total XP" + Total_Level_50 = "Total Level 50" + Total_Level_100 = "Total Level 100" + Total_Level_150 = "Total Level 150" + Total_Level_200 = "Total Level 200" + Combat_Level_5 = "Combat Level 5" + Combat_Level_15 = "Combat Level 15" + Combat_Level_25 = "Combat Level 25" + Travel_on_a_Canoe = "Travel on a Canoe" + Q_Dragon_Slayer = "Quest: Dragon Slayer" + + __str__ = str.__str__ diff --git a/worlds/osrs/Options.py b/worlds/osrs/Options.py new file mode 100644 index 000000000000..520cd8e8b06b --- /dev/null +++ b/worlds/osrs/Options.py @@ -0,0 +1,474 @@ +from dataclasses import dataclass + +from Options import Choice, Toggle, Range, PerGameCommonOptions + +MAX_COMBAT_TASKS = 16 +MAX_PRAYER_TASKS = 3 +MAX_MAGIC_TASKS = 4 +MAX_RUNECRAFT_TASKS = 3 +MAX_CRAFTING_TASKS = 5 +MAX_MINING_TASKS = 5 +MAX_SMITHING_TASKS = 4 +MAX_FISHING_TASKS = 5 +MAX_COOKING_TASKS = 5 +MAX_FIREMAKING_TASKS = 2 +MAX_WOODCUTTING_TASKS = 3 + +NON_QUEST_LOCATION_COUNT = 22 + + +class StartingArea(Choice): + """ + Which chunks are available at the start. The player may need to move through locked chunks to reach the starting + area, but any areas that require quests, skills, or coins are not available as a starting location. + + "Any Bank" rolls a random region that contains a bank. + Chunksanity can start you in any chunk. Hope you like woodcutting! + """ + display_name = "Starting Region" + option_lumbridge = 0 + option_al_kharid = 1 + option_varrock_east = 2 + option_varrock_west = 3 + option_edgeville = 4 + option_falador = 5 + option_draynor = 6 + option_wilderness = 7 + option_any_bank = 8 + option_chunksanity = 9 + default = 0 + + +class BrutalGrinds(Toggle): + """ + Whether to allow skill tasks without having reasonable access to the usual skill training path. + For example, if enabled, you could be forced to train smithing without an anvil purely by smelting bars, + or training fishing to high levels entirely on shrimp. + """ + display_name = "Allow Brutal Grinds" + + +class ProgressiveTasks(Toggle): + """ + Whether skill tasks should always be generated in order of easiest to hardest. + If enabled, you would not be assigned "Mine Gold" without also being assigned + "Mine Silver", "Mine Coal", and "Mine Iron". Enabling this will result in a generally shorter seed, but with + a lower variety of tasks. + """ + display_name = "Progressive Tasks" + + +class MaxCombatLevel(Range): + """ + The highest combat level of monster to possibly be assigned as a task. + If set to 0, no combat tasks will be generated. + """ + range_start = 0 + range_end = 1520 + default = 50 + + +class MaxCombatTasks(Range): + """ + The maximum number of Combat Tasks to possibly be assigned. + If set to 0, no combat tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_COMBAT_TASKS + default = MAX_COMBAT_TASKS + + +class CombatTaskWeight(Range): + """ + How much to favor generating combat tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxPrayerLevel(Range): + """ + The highest Prayer requirement of any task generated. + If set to 0, no Prayer tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxPrayerTasks(Range): + """ + The maximum number of Prayer Tasks to possibly be assigned. + If set to 0, no Prayer tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_PRAYER_TASKS + default = MAX_PRAYER_TASKS + + +class PrayerTaskWeight(Range): + """ + How much to favor generating Prayer tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxMagicLevel(Range): + """ + The highest Magic requirement of any task generated. + If set to 0, no Magic tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxMagicTasks(Range): + """ + The maximum number of Magic Tasks to possibly be assigned. + If set to 0, no Magic tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_MAGIC_TASKS + default = MAX_MAGIC_TASKS + + +class MagicTaskWeight(Range): + """ + How much to favor generating Magic tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxRunecraftLevel(Range): + """ + The highest Runecraft requirement of any task generated. + If set to 0, no Runecraft tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxRunecraftTasks(Range): + """ + The maximum number of Runecraft Tasks to possibly be assigned. + If set to 0, no Runecraft tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_RUNECRAFT_TASKS + default = MAX_RUNECRAFT_TASKS + + +class RunecraftTaskWeight(Range): + """ + How much to favor generating Runecraft tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxCraftingLevel(Range): + """ + The highest Crafting requirement of any task generated. + If set to 0, no Crafting tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxCraftingTasks(Range): + """ + The maximum number of Crafting Tasks to possibly be assigned. + If set to 0, no Crafting tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_CRAFTING_TASKS + default = MAX_CRAFTING_TASKS + + +class CraftingTaskWeight(Range): + """ + How much to favor generating Crafting tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxMiningLevel(Range): + """ + The highest Mining requirement of any task generated. + If set to 0, no Mining tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxMiningTasks(Range): + """ + The maximum number of Mining Tasks to possibly be assigned. + If set to 0, no Mining tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_MINING_TASKS + default = MAX_MINING_TASKS + + +class MiningTaskWeight(Range): + """ + How much to favor generating Mining tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxSmithingLevel(Range): + """ + The highest Smithing requirement of any task generated. + If set to 0, no Smithing tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxSmithingTasks(Range): + """ + The maximum number of Smithing Tasks to possibly be assigned. + If set to 0, no Smithing tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_SMITHING_TASKS + default = MAX_SMITHING_TASKS + + +class SmithingTaskWeight(Range): + """ + How much to favor generating Smithing tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxFishingLevel(Range): + """ + The highest Fishing requirement of any task generated. + If set to 0, no Fishing tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxFishingTasks(Range): + """ + The maximum number of Fishing Tasks to possibly be assigned. + If set to 0, no Fishing tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_FISHING_TASKS + default = MAX_FISHING_TASKS + + +class FishingTaskWeight(Range): + """ + How much to favor generating Fishing tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxCookingLevel(Range): + """ + The highest Cooking requirement of any task generated. + If set to 0, no Cooking tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxCookingTasks(Range): + """ + The maximum number of Cooking Tasks to possibly be assigned. + If set to 0, no Cooking tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_COOKING_TASKS + default = MAX_COOKING_TASKS + + +class CookingTaskWeight(Range): + """ + How much to favor generating Cooking tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxFiremakingLevel(Range): + """ + The highest Firemaking requirement of any task generated. + If set to 0, no Firemaking tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxFiremakingTasks(Range): + """ + The maximum number of Firemaking Tasks to possibly be assigned. + If set to 0, no Firemaking tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_FIREMAKING_TASKS + default = MAX_FIREMAKING_TASKS + + +class FiremakingTaskWeight(Range): + """ + How much to favor generating Firemaking tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxWoodcuttingLevel(Range): + """ + The highest Woodcutting requirement of any task generated. + If set to 0, no Woodcutting tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxWoodcuttingTasks(Range): + """ + The maximum number of Woodcutting Tasks to possibly be assigned. + If set to 0, no Woodcutting tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_WOODCUTTING_TASKS + default = MAX_WOODCUTTING_TASKS + + +class WoodcuttingTaskWeight(Range): + """ + How much to favor generating Woodcutting tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MinimumGeneralTasks(Range): + """ + How many guaranteed general progression tasks to be assigned (total level, total XP, etc.). + General progression tasks will be used to fill out any holes caused by having fewer possible tasks than needed, so + there is no maximum. + """ + range_start = 0 + range_end = NON_QUEST_LOCATION_COUNT + default = 10 + + +class GeneralTaskWeight(Range): + """ + How much to favor generating General tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +@dataclass +class OSRSOptions(PerGameCommonOptions): + starting_area: StartingArea + brutal_grinds: BrutalGrinds + progressive_tasks: ProgressiveTasks + max_combat_level: MaxCombatLevel + max_combat_tasks: MaxCombatTasks + combat_task_weight: CombatTaskWeight + max_prayer_level: MaxPrayerLevel + max_prayer_tasks: MaxPrayerTasks + prayer_task_weight: PrayerTaskWeight + max_magic_level: MaxMagicLevel + max_magic_tasks: MaxMagicTasks + magic_task_weight: MagicTaskWeight + max_runecraft_level: MaxRunecraftLevel + max_runecraft_tasks: MaxRunecraftTasks + runecraft_task_weight: RunecraftTaskWeight + max_crafting_level: MaxCraftingLevel + max_crafting_tasks: MaxCraftingTasks + crafting_task_weight: CraftingTaskWeight + max_mining_level: MaxMiningLevel + max_mining_tasks: MaxMiningTasks + mining_task_weight: MiningTaskWeight + max_smithing_level: MaxSmithingLevel + max_smithing_tasks: MaxSmithingTasks + smithing_task_weight: SmithingTaskWeight + max_fishing_level: MaxFishingLevel + max_fishing_tasks: MaxFishingTasks + fishing_task_weight: FishingTaskWeight + max_cooking_level: MaxCookingLevel + max_cooking_tasks: MaxCookingTasks + cooking_task_weight: CookingTaskWeight + max_firemaking_level: MaxFiremakingLevel + max_firemaking_tasks: MaxFiremakingTasks + firemaking_task_weight: FiremakingTaskWeight + max_woodcutting_level: MaxWoodcuttingLevel + max_woodcutting_tasks: MaxWoodcuttingTasks + woodcutting_task_weight: WoodcuttingTaskWeight + minimum_general_tasks: MinimumGeneralTasks + general_task_weight: GeneralTaskWeight diff --git a/worlds/osrs/Regions.py b/worlds/osrs/Regions.py new file mode 100644 index 000000000000..436cdf3c7c78 --- /dev/null +++ b/worlds/osrs/Regions.py @@ -0,0 +1,12 @@ +import typing + + +class RegionRow(typing.NamedTuple): + name: str + itemReq: str + connections: typing.List[str] + resources: typing.List[str] + + +class ResourceRow(typing.NamedTuple): + name: str diff --git a/worlds/osrs/__init__.py b/worlds/osrs/__init__.py new file mode 100644 index 000000000000..f726b4b81bf2 --- /dev/null +++ b/worlds/osrs/__init__.py @@ -0,0 +1,657 @@ +import typing + +from BaseClasses import Item, Tutorial, ItemClassification, Region, MultiWorld +from worlds.AutoWorld import WebWorld, World +from worlds.generic.Rules import add_rule, CollectionRule +from .Items import OSRSItem, starting_area_dict, chunksanity_starting_chunks, QP_Items, ItemRow, \ + chunksanity_special_region_names +from .Locations import OSRSLocation, LocationRow + +from .Options import OSRSOptions, StartingArea +from .Names import LocationNames, ItemNames, RegionNames + +from .LogicCSV.LogicCSVToPython import data_csv_tag +from .LogicCSV.items_generated import item_rows +from .LogicCSV.locations_generated import location_rows +from .LogicCSV.regions_generated import region_rows +from .LogicCSV.resources_generated import resource_rows +from .Regions import RegionRow, ResourceRow + + +class OSRSWeb(WebWorld): + theme = "stone" + + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Old School Runescape Randomizer connected to an Archipelago Multiworld", + "English", + "docs/setup_en.md", + "setup/en", + ["digiholic"] + ) + tutorials = [setup_en] + + +class OSRSWorld(World): + game = "Old School Runescape" + options_dataclass = OSRSOptions + options: OSRSOptions + topology_present = True + web = OSRSWeb() + base_id = 0x070000 + data_version = 1 + + item_name_to_id = {item_rows[i].name: 0x070000 + i for i in range(len(item_rows))} + location_name_to_id = {location_rows[i].name: 0x070000 + i for i in range(len(location_rows))} + + region_name_to_data: typing.Dict[str, Region] + location_name_to_data: typing.Dict[str, OSRSLocation] + + location_rows_by_name: typing.Dict[str, LocationRow] + region_rows_by_name: typing.Dict[str, RegionRow] + resource_rows_by_name: typing.Dict[str, ResourceRow] + item_rows_by_name: typing.Dict[str, ItemRow] + + starting_area_item: str + + locations_by_category: typing.Dict[str, typing.List[LocationRow]] + + def __init__(self, world: MultiWorld, player: int): + super().__init__(world, player) + self.region_name_to_data = {} + self.location_name_to_data = {} + + self.location_rows_by_name = {} + self.region_rows_by_name = {} + self.resource_rows_by_name = {} + self.item_rows_by_name = {} + + self.starting_area_item = "" + + self.locations_by_category = {} + + def generate_early(self) -> None: + location_categories = [location_row.category for location_row in location_rows] + self.locations_by_category = {category: + [location_row for location_row in location_rows if + location_row.category == category] + for category in location_categories} + + self.location_rows_by_name = {loc_row.name: loc_row for loc_row in location_rows} + self.region_rows_by_name = {reg_row.name: reg_row for reg_row in region_rows} + self.resource_rows_by_name = {rec_row.name: rec_row for rec_row in resource_rows} + self.item_rows_by_name = {it_row.name: it_row for it_row in item_rows} + + rnd = self.random + starting_area = self.options.starting_area + + if starting_area.value == StartingArea.option_any_bank: + self.starting_area_item = rnd.choice(starting_area_dict) + elif starting_area.value < StartingArea.option_chunksanity: + self.starting_area_item = starting_area_dict[starting_area.value] + else: + self.starting_area_item = rnd.choice(chunksanity_starting_chunks) + + # Set Starting Chunk + self.multiworld.push_precollected(self.create_item(self.starting_area_item)) + + """ + This function pulls from LogicCSVToPython so that it sends the correct tag of the repository to the client. + _Make sure to update that value whenever the CSVs change!_ + """ + + def fill_slot_data(self): + data = self.options.as_dict("brutal_grinds") + data["data_csv_tag"] = data_csv_tag + return data + + def create_regions(self) -> None: + """ + called to place player's regions into the MultiWorld's regions list. If it's hard to separate, this can be done + during generate_early or basic as well. + """ + + # First, create the "Menu" region to start + menu_region = self.create_region("Menu") + + for region_row in region_rows: + self.create_region(region_row.name) + + for resource_row in resource_rows: + self.create_region(resource_row.name) + + # Removes the word "Area: " from the item name to get the region it applies to. + # I figured tacking "Area: " at the beginning would make it _easier_ to tell apart. Turns out it made it worse + if self.starting_area_item in chunksanity_special_region_names: + starting_area_region = chunksanity_special_region_names[self.starting_area_item] + else: + starting_area_region = self.starting_area_item[6:] # len("Area: ") + starting_entrance = menu_region.create_exit(f"Start->{starting_area_region}") + starting_entrance.access_rule = lambda state: state.has(self.starting_area_item, self.player) + starting_entrance.connect(self.region_name_to_data[starting_area_region]) + + # Create entrances between regions + for region_row in region_rows: + region = self.region_name_to_data[region_row.name] + + for outbound_region_name in region_row.connections: + parsed_outbound = outbound_region_name.replace('*', '') + entrance = region.create_exit(f"{region_row.name}->{parsed_outbound}") + entrance.connect(self.region_name_to_data[parsed_outbound]) + + item_name = self.region_rows_by_name[parsed_outbound].itemReq + if "*" not in outbound_region_name and "*" not in item_name: + entrance.access_rule = lambda state, item_name=item_name: state.has(item_name, self.player) + continue + + self.generate_special_rules_for(entrance, region_row, outbound_region_name) + + for resource_region in region_row.resources: + if not resource_region: + continue + + entrance = region.create_exit(f"{region_row.name}->{resource_region.replace('*', '')}") + if "*" not in resource_region: + entrance.connect(self.region_name_to_data[resource_region]) + else: + self.generate_special_rules_for(entrance, region_row, resource_region) + entrance.connect(self.region_name_to_data[resource_region.replace('*', '')]) + + self.roll_locations() + + def generate_special_rules_for(self, entrance, region_row, outbound_region_name): + # print(f"Special rules required to access region {outbound_region_name} from {region_row.name}") + if outbound_region_name == RegionNames.Cooks_Guild: + item_name = self.region_rows_by_name[outbound_region_name].itemReq.replace('*', '') + cooking_level_rule = self.get_skill_rule("cooking", 32) + entrance.access_rule = lambda state: state.has(item_name, self.player) and \ + cooking_level_rule(state) + return + if outbound_region_name == RegionNames.Crafting_Guild: + item_name = self.region_rows_by_name[outbound_region_name].itemReq.replace('*', '') + crafting_level_rule = self.get_skill_rule("crafting", 40) + entrance.access_rule = lambda state: state.has(item_name, self.player) and \ + crafting_level_rule(state) + return + if outbound_region_name == RegionNames.Corsair_Cove: + item_name = self.region_rows_by_name[outbound_region_name].itemReq.replace('*', '') + # Need to be able to start Corsair Curse in addition to having the item + entrance.access_rule = lambda state: state.has(item_name, self.player) and \ + state.can_reach(RegionNames.Falador_Farm, "Region", self.player) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Falador_Farm, self.player), entrance) + + return + if outbound_region_name == "Camdozaal*": + item_name = self.region_rows_by_name[outbound_region_name.replace('*', '')].itemReq + entrance.access_rule = lambda state: state.has(item_name, self.player) and \ + state.has(ItemNames.QP_Below_Ice_Mountain, self.player) + return + if region_row.name == "Dwarven Mountain Pass" and outbound_region_name == "Anvil*": + entrance.access_rule = lambda state: state.has(ItemNames.QP_Dorics_Quest, self.player) + return + # Special logic for canoes + canoe_regions = [RegionNames.Lumbridge, RegionNames.South_Of_Varrock, RegionNames.Barbarian_Village, + RegionNames.Edgeville, RegionNames.Wilderness] + if region_row.name in canoe_regions: + # Skill rules for greater distances + woodcutting_rule_d1 = self.get_skill_rule("woodcutting", 12) + woodcutting_rule_d2 = self.get_skill_rule("woodcutting", 27) + woodcutting_rule_d3 = self.get_skill_rule("woodcutting", 42) + woodcutting_rule_all = self.get_skill_rule("woodcutting", 57) + + if region_row.name == RegionNames.Lumbridge: + # Canoe Tree access for the Location + if outbound_region_name == RegionNames.Canoe_Tree: + entrance.access_rule = \ + lambda state: (state.can_reach_region(RegionNames.South_Of_Varrock, self.player) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Barbarian_Village) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \ + (state.can_reach_region(RegionNames.Edgeville) + and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) or \ + (state.can_reach_region(RegionNames.Wilderness) + and woodcutting_rule_all(state) and self.options.max_woodcutting_level >= 57) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance) + # Access to other chunks based on woodcutting settings + # South of Varrock does not need to be checked, because it's already adjacent + if outbound_region_name == RegionNames.Barbarian_Village: + entrance.access_rule = lambda state: woodcutting_rule_d2(state) \ + and self.options.max_woodcutting_level >= 27 + if outbound_region_name == RegionNames.Edgeville: + entrance.access_rule = lambda state: woodcutting_rule_d3(state) \ + and self.options.max_woodcutting_level >= 42 + if outbound_region_name == RegionNames.Wilderness: + entrance.access_rule = lambda state: woodcutting_rule_all(state) \ + and self.options.max_woodcutting_level >= 57 + + if region_row.name == RegionNames.South_Of_Varrock: + if outbound_region_name == RegionNames.Canoe_Tree: + entrance.access_rule = \ + lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Barbarian_Village) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Edgeville) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \ + (state.can_reach_region(RegionNames.Wilderness) + and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance) + # Access to other chunks based on woodcutting settings + # Lumbridge does not need to be checked, because it's already adjacent + if outbound_region_name == RegionNames.Barbarian_Village: + entrance.access_rule = lambda state: woodcutting_rule_d1(state) \ + and self.options.max_woodcutting_level >= 12 + if outbound_region_name == RegionNames.Edgeville: + entrance.access_rule = lambda state: woodcutting_rule_d3(state) \ + and self.options.max_woodcutting_level >= 27 + if outbound_region_name == RegionNames.Wilderness: + entrance.access_rule = lambda state: woodcutting_rule_all(state) \ + and self.options.max_woodcutting_level >= 42 + if region_row.name == RegionNames.Barbarian_Village: + if outbound_region_name == RegionNames.Canoe_Tree: + entrance.access_rule = \ + lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \ + (state.can_reach_region(RegionNames.South_Of_Varrock) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Edgeville) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Wilderness) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance) + # Access to other chunks based on woodcutting settings + if outbound_region_name == RegionNames.Lumbridge: + entrance.access_rule = lambda state: woodcutting_rule_d2(state) \ + and self.options.max_woodcutting_level >= 27 + if outbound_region_name == RegionNames.South_Of_Varrock: + entrance.access_rule = lambda state: woodcutting_rule_d1(state) \ + and self.options.max_woodcutting_level >= 12 + # Edgeville does not need to be checked, because it's already adjacent + if outbound_region_name == RegionNames.Wilderness: + entrance.access_rule = lambda state: woodcutting_rule_d3(state) \ + and self.options.max_woodcutting_level >= 42 + if region_row.name == RegionNames.Edgeville: + if outbound_region_name == RegionNames.Canoe_Tree: + entrance.access_rule = \ + lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player) + and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) or \ + (state.can_reach_region(RegionNames.South_Of_Varrock) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \ + (state.can_reach_region(RegionNames.Barbarian_Village) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Wilderness) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance) + # Access to other chunks based on woodcutting settings + if outbound_region_name == RegionNames.Lumbridge: + entrance.access_rule = lambda state: woodcutting_rule_d3(state) \ + and self.options.max_woodcutting_level >= 42 + if outbound_region_name == RegionNames.South_Of_Varrock: + entrance.access_rule = lambda state: woodcutting_rule_d2(state) \ + and self.options.max_woodcutting_level >= 27 + # Barbarian Village does not need to be checked, because it's already adjacent + # Wilderness does not need to be checked, because it's already adjacent + if region_row.name == RegionNames.Wilderness: + if outbound_region_name == RegionNames.Canoe_Tree: + entrance.access_rule = \ + lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player) + and woodcutting_rule_all(state) and self.options.max_woodcutting_level >= 57) or \ + (state.can_reach_region(RegionNames.South_Of_Varrock) + and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) or \ + (state.can_reach_region(RegionNames.Barbarian_Village) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \ + (state.can_reach_region(RegionNames.Edgeville) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance) + # Access to other chunks based on woodcutting settings + if outbound_region_name == RegionNames.Lumbridge: + entrance.access_rule = lambda state: woodcutting_rule_all(state) \ + and self.options.max_woodcutting_level >= 57 + if outbound_region_name == RegionNames.South_Of_Varrock: + entrance.access_rule = lambda state: woodcutting_rule_d3(state) \ + and self.options.max_woodcutting_level >= 42 + if outbound_region_name == RegionNames.Barbarian_Village: + entrance.access_rule = lambda state: woodcutting_rule_d2(state) \ + and self.options.max_woodcutting_level >= 27 + # Edgeville does not need to be checked, because it's already adjacent + + def roll_locations(self): + locations_required = 0 + generation_is_fake = hasattr(self.multiworld, "generation_is_fake") # UT specific override + for item_row in item_rows: + locations_required += item_row.amount + + locations_added = 1 # At this point we've already added the starting area, so we start at 1 instead of 0 + + # Quests are always added + for i, location_row in enumerate(location_rows): + if location_row.category in {"quest", "points", "goal"}: + self.create_and_add_location(i) + if location_row.category == "quest": + locations_added += 1 + + # Build up the weighted Task Pool + rnd = self.random + + # Start with the minimum general tasks + general_tasks = [task for task in self.locations_by_category["general"]] + if not self.options.progressive_tasks: + rnd.shuffle(general_tasks) + else: + general_tasks.reverse() + for i in range(self.options.minimum_general_tasks): + task = general_tasks.pop() + self.add_location(task) + locations_added += 1 + + general_weight = self.options.general_task_weight if len(general_tasks) > 0 else 0 + + tasks_per_task_type: typing.Dict[str, typing.List[LocationRow]] = {} + weights_per_task_type: typing.Dict[str, int] = {} + + task_types = ["prayer", "magic", "runecraft", "mining", "crafting", + "smithing", "fishing", "cooking", "firemaking", "woodcutting", "combat"] + for task_type in task_types: + max_level_for_task_type = getattr(self.options, f"max_{task_type}_level") + max_amount_for_task_type = getattr(self.options, f"max_{task_type}_tasks") + tasks_for_this_type = [task for task in self.locations_by_category[task_type] + if task.skills[0].level <= max_level_for_task_type] + if not self.options.progressive_tasks: + rnd.shuffle(tasks_for_this_type) + else: + tasks_for_this_type.reverse() + + tasks_for_this_type = tasks_for_this_type[:max_amount_for_task_type] + weight_for_this_type = getattr(self.options, + f"{task_type}_task_weight") + if weight_for_this_type > 0 and tasks_for_this_type: + tasks_per_task_type[task_type] = tasks_for_this_type + weights_per_task_type[task_type] = weight_for_this_type + + # Build a list of collections and weights in a matching order for rnd.choices later + all_tasks = [] + all_weights = [] + for task_type in task_types: + if task_type in tasks_per_task_type: + all_tasks.append(tasks_per_task_type[task_type]) + all_weights.append(weights_per_task_type[task_type]) + + # Even after the initial forced generals, they can still be rolled randomly + if general_weight > 0: + all_tasks.append(general_tasks) + all_weights.append(general_weight) + + while locations_added < locations_required or (generation_is_fake and len(all_tasks) > 0): + if all_tasks: + chosen_task = rnd.choices(all_tasks, all_weights)[0] + if chosen_task: + task = chosen_task.pop() + self.add_location(task) + locations_added += 1 + + # This isn't an else because chosen_task can become empty in the process of resolving the above block + # We still want to clear this list out while we're doing that + if not chosen_task: + index = all_tasks.index(chosen_task) + del all_tasks[index] + del all_weights[index] + + else: + if len(general_tasks) == 0: + raise Exception(f"There are not enough available tasks to fill the remaining pool for OSRS " + + f"Please adjust {self.player_name}'s settings to be less restrictive of tasks.") + task = general_tasks.pop() + self.add_location(task) + locations_added += 1 + + def add_location(self, location): + index = [i for i in range(len(location_rows)) if location_rows[i].name == location.name][0] + self.create_and_add_location(index) + + def create_items(self) -> None: + for item_row in item_rows: + if item_row.name != self.starting_area_item: + for c in range(item_row.amount): + item = self.create_item(item_row.name) + self.multiworld.itempool.append(item) + + def get_filler_item_name(self) -> str: + return self.random.choice( + [ItemNames.Progressive_Armor, ItemNames.Progressive_Weapons, ItemNames.Progressive_Magic, + ItemNames.Progressive_Tools, ItemNames.Progressive_Range_Armor, ItemNames.Progressive_Range_Weapon]) + + def create_and_add_location(self, row_index) -> None: + location_row = location_rows[row_index] + # print(f"Adding task {location_row.name}") + + # Create Location + location_id = self.base_id + row_index + if location_row.category == "points" or location_row.category == "goal": + location_id = None + location = OSRSLocation(self.player, location_row.name, location_id) + self.location_name_to_data[location_row.name] = location + + # Add the location to its first region, or if it doesn't belong to one, to Menu + region = self.region_name_to_data["Menu"] + if location_row.regions: + region = self.region_name_to_data[location_row.regions[0]] + location.parent_region = region + region.locations.append(location) + + def set_rules(self) -> None: + """ + called to set access and item rules on locations and entrances. + """ + quest_attr_names = ["Cooks_Assistant", "Demon_Slayer", "Restless_Ghost", "Romeo_Juliet", + "Sheep_Shearer", "Shield_of_Arrav", "Ernest_the_Chicken", "Vampyre_Slayer", + "Imp_Catcher", "Prince_Ali_Rescue", "Dorics_Quest", "Black_Knights_Fortress", + "Witchs_Potion", "Knights_Sword", "Goblin_Diplomacy", "Pirates_Treasure", + "Rune_Mysteries", "Misthalin_Mystery", "Corsair_Curse", "X_Marks_the_Spot", + "Below_Ice_Mountain"] + for qp_attr_name in quest_attr_names: + loc_name = getattr(LocationNames, f"QP_{qp_attr_name}") + item_name = getattr(ItemNames, f"QP_{qp_attr_name}") + self.multiworld.get_location(loc_name, self.player) \ + .place_locked_item(self.create_event(item_name)) + + for quest_attr_name in quest_attr_names: + qp_loc_name = getattr(LocationNames, f"QP_{quest_attr_name}") + q_loc_name = getattr(LocationNames, f"Q_{quest_attr_name}") + add_rule(self.multiworld.get_location(qp_loc_name, self.player), lambda state, q_loc_name=q_loc_name: ( + self.multiworld.get_location(q_loc_name, self.player).can_reach(state) + )) + + # place "Victory" at "Dragon Slayer" and set collection as win condition + self.multiworld.get_location(LocationNames.Q_Dragon_Slayer, self.player) \ + .place_locked_item(self.create_event("Victory")) + self.multiworld.completion_condition[self.player] = lambda state: (state.has("Victory", self.player)) + + for location_name, location in self.location_name_to_data.items(): + location_row = self.location_rows_by_name[location_name] + # Set up requirements for region + for region_required_name in location_row.regions: + region_required = self.region_name_to_data[region_required_name] + add_rule(location, + lambda state, region_required=region_required: state.can_reach(region_required, "Region", + self.player)) + for skill_req in location_row.skills: + add_rule(location, self.get_skill_rule(skill_req.skill, skill_req.level)) + for item_req in location_row.items: + add_rule(location, lambda state, item_req=item_req: state.has(item_req, self.player)) + if location_row.qp: + add_rule(location, lambda state, location_row=location_row: self.quest_points(state) > location_row.qp) + + def create_region(self, name: str) -> "Region": + region = Region(name, self.player, self.multiworld) + self.region_name_to_data[name] = region + self.multiworld.regions.append(region) + return region + + def create_item(self, item_name: str) -> "Item": + item = [item for item in item_rows if item.name == item_name][0] + index = item_rows.index(item) + return OSRSItem(item.name, item.progression, self.base_id + index, self.player) + + def create_event(self, event: str): + # while we are at it, we can also add a helper to create events + return OSRSItem(event, ItemClassification.progression, None, self.player) + + def quest_points(self, state): + qp = 0 + for qp_event in QP_Items: + if state.has(qp_event, self.player): + qp += int(qp_event[0]) + return qp + + """ + Ensures a target level can be reached with available resources + """ + + def get_skill_rule(self, skill, level) -> CollectionRule: + if skill.lower() == "fishing": + if self.options.brutal_grinds or level < 5: + return lambda state: state.can_reach(RegionNames.Shrimp, "Region", self.player) + if level < 20: + return lambda state: state.can_reach(RegionNames.Shrimp, "Region", self.player) and \ + state.can_reach(RegionNames.Port_Sarim, "Region", self.player) + else: + return lambda state: state.can_reach(RegionNames.Shrimp, "Region", self.player) and \ + state.can_reach(RegionNames.Port_Sarim, "Region", self.player) and \ + state.can_reach(RegionNames.Fly_Fish, "Region", self.player) + if skill.lower() == "mining": + if self.options.brutal_grinds or level < 15: + return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) or \ + state.can_reach(RegionNames.Clay_Rock, "Region", self.player) + else: + # Iron is the best way to train all the way to 99, so having access to iron is all you need to check for + return lambda state: (state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) or + state.can_reach(RegionNames.Clay_Rock, "Region", self.player)) and \ + state.can_reach(RegionNames.Iron_Rock, "Region", self.player) + if skill.lower() == "woodcutting": + if self.options.brutal_grinds or level < 15: + # I've checked. There is not a single chunk in the f2p that does not have at least one normal tree. + # Even the desert. + return lambda state: True + if level < 30: + return lambda state: state.can_reach(RegionNames.Oak_Tree, "Region", self.player) + else: + return lambda state: state.can_reach(RegionNames.Oak_Tree, "Region", self.player) and \ + state.can_reach(RegionNames.Willow_Tree, "Region", self.player) + if skill.lower() == "smithing": + if self.options.brutal_grinds: + return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) + if level < 15: + # Lumbridge has a special bronze-only anvil. This is the only anvil of its type so it's not included + # in the "Anvil" resource region. We still need to check for it though. + return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) and \ + (state.can_reach(RegionNames.Anvil, "Region", self.player) or + state.can_reach(RegionNames.Lumbridge, "Region", self.player)) + if level < 30: + # For levels between 15 and 30, the lumbridge anvil won't cut it. Only a real one will do + return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \ + state.can_reach(RegionNames.Iron_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) and \ + state.can_reach(RegionNames.Anvil, "Region", self.player) + else: + return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \ + state.can_reach(RegionNames.Iron_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Coal_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) and \ + state.can_reach(RegionNames.Anvil, "Region", self.player) + if skill.lower() == "crafting": + # Crafting is really complex. Need a lot of sub-rules to make this even remotely readable + def can_spin(state): + return state.can_reach(RegionNames.Sheep, "Region", self.player) and \ + state.can_reach(RegionNames.Spinning_Wheel, "Region", self.player) + + def can_pot(state): + return state.can_reach(RegionNames.Clay_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Barbarian_Village, "Region", self.player) + + def can_tan(state): + return state.can_reach(RegionNames.Milk, "Region", self.player) and \ + state.can_reach(RegionNames.Al_Kharid, "Region", self.player) + + def mould_access(state): + return state.can_reach(RegionNames.Al_Kharid, "Region", self.player) or \ + state.can_reach(RegionNames.Rimmington, "Region", self.player) + + def can_silver(state): + + return state.can_reach(RegionNames.Silver_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) and mould_access(state) + + def can_gold(state): + return state.can_reach(RegionNames.Gold_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) and mould_access(state) + + if self.options.brutal_grinds or level < 5: + return lambda state: can_spin(state) or can_pot(state) or can_tan(state) + + can_smelt_gold = self.get_skill_rule("smithing", 40) + can_smelt_silver = self.get_skill_rule("smithing", 20) + if level < 16: + return lambda state: can_pot(state) or can_tan(state) or (can_gold(state) and can_smelt_gold(state)) + else: + return lambda state: can_tan(state) or (can_silver(state) and can_smelt_silver(state)) or \ + (can_gold(state) and can_smelt_gold(state)) + if skill.lower() == "Cooking": + if self.options.brutal_grinds or level < 15: + return lambda state: state.can_reach(RegionNames.Milk, "Region", self.player) or \ + state.can_reach(RegionNames.Egg, "Region", self.player) or \ + state.can_reach(RegionNames.Shrimp, "Region", self.player) or \ + (state.can_reach(RegionNames.Wheat, "Region", self.player) and + state.can_reach(RegionNames.Windmill, "Region", self.player)) + else: + can_catch_fly_fish = self.get_skill_rule("fishing", 20) + return lambda state: state.can_reach(RegionNames.Fly_Fish, "Region", self.player) and \ + can_catch_fly_fish(state) and \ + (state.can_reach(RegionNames.Milk, "Region", self.player) or + state.can_reach(RegionNames.Egg, "Region", self.player) or + state.can_reach(RegionNames.Shrimp, "Region", self.player) or + (state.can_reach(RegionNames.Wheat, "Region", self.player) and + state.can_reach(RegionNames.Windmill, "Region", self.player))) + if skill.lower() == "runecraft": + return lambda state: state.has(ItemNames.QP_Rune_Mysteries, self.player) + if skill.lower() == "magic": + return lambda state: state.can_reach(RegionNames.Mind_Runes, "Region", self.player) + + return lambda state: True diff --git a/worlds/osrs/docs/en_Old School Runescape.md b/worlds/osrs/docs/en_Old School Runescape.md new file mode 100644 index 000000000000..d367082b2274 --- /dev/null +++ b/worlds/osrs/docs/en_Old School Runescape.md @@ -0,0 +1,114 @@ +# Old School Runescape + +## What is the Goal of this Randomizer? +The goal is to complete the quest "Dragon Slayer I" with limited access to gear and map chunks while following normal +Ironman/Group Ironman restrictions on a fresh free-to-play account. + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export a +config file. OSRS contains many options for a highly customizable experience. The options available to you are: + +* **Starting Area** - The starting region of your run. This is the first region you will have available, and you can always +freely return to it (see the section below for when it is allowed to cross locked regions to access it) + * You may select a starting city from the list of Lumbridge, Al Kharid, Varrock (East or West), Edgeville, Falador, +Draynor Village, or The Wilderness (Ferox Enclave) + * The option "Any Bank" will choose one of the above regions at random + * The option "Chunksanity" can start you in _any_ chunk, regardless of whether it has access to a bank. +* **Brutal Grinds** - If enabled, the logic will assume you are willing to go to great lengths to train skills. + * As an example, when enabled, it might be in logic to obtain tin and copper from mob drops and smelt bronze bars to +reach Smithing Level 40 to smelt gold for a task. + * If left disabled, the logic will always ensure you have a reasonable method for training a skill to reach a specific +task, such as having access to intermediate-level training options +* **Progressive Tasks** - If enabled, tasks for a skill are generated in order from earliest to latest. + * For example, your first Smithing task would always be "Smelt an Iron Bar", then "Smelt a Silver Bar", and so on. +You would never have the task "Smelt a Gold Bar" without having every previous Smithing task as well. +This can lead to a more consistent length of run, and is generally shorter than disabling it, but with less variety. +* **Skill Category Weighting Options** + * These are available in each task category (all trainable skills plus "Combat" and "General") + * **Max [Category] Level** - The highest level you intend to have to reach in order to complete all tasks for this +category. For the Combat category, this is the max level of monster you are willing to fight. +General tasks do not have a level and thus do not have this option. + * **Max [Category] Tasks** - The highest number of tasks in this category you are willing to be assigned. +Note that you can end up with _less_ than this amount, but never more. The "General" category is used to fill remaining +spots so a maximum is not specified, instead it has a _minimum_ count. + * **[Category] Task Weighting** - The relative weighting of this category to all of the others. Increase this to make +tasks in this category more likely. + +## What does randomization do to this game? +The OSRS Archipelago Randomizer takes the form of a "Chunkman" account, a form of challenge account +where you are limited to specific regions of the map (known as "chunks") until you complete tasks to unlock +more. The plugin will interface with the [Region Locker Plugin](https://github.com/slaytostay/region-locker) to +visually display these chunk borders and highlight them as locked or unlocked. The optional included GPU plugin for the +Region Locker can tint the locked areas gray, but is incompatible with other GPU plugins such as 117's HD OSRS. +If you choose not to include it, the world map will show locked and unlocked regions instead. + +In order to access a region, you will need to access it entirely through unlocked regions. At no point are you +ever allowed to cross through locked regions, with the following exceptions: +* If your starting region is not Lumbridge, when you complete Tutorial Island, you will need to traverse locked regions +to reach your intended starting location. +* If your starting region is not Lumbridge, you are allowed to "Home Teleport" to your starting region by using the +Lumbridge Home Teleport Spell and then walking to your start location. This is to prevent you from getting "stuck" after +using one-way transportation such as the Port Sarim Jail Teleport from Shantay Pass and being locked out of progression. +* All of your starting Tutorial Island items are assumed to be available at all times. If you have lost an important +item such as a Tinderbox, and cannot re-obtain it in your unlocked region, you are allowed to enter locked regions to +replace it in the least obtrusive way possible. +* If you need to adjust Group Ironman settings, such as adding or removing a member, you may freely access The Node +to do so. + +When passing through locked regions for such exceptions, do not interact with any NPCs, items, or enemies and attempt +to spend as little time in them as possible. + +The plugin will prevent equipping items that you have not unlocked the ability to wield. For example, attempting +to equip an Iron Platebody before the first Progressive Armor unlock will display a chat message and will not +equip the item. + +The plugin will show a list of your current tasks in the sidebar. The plugin will be able to detect the completion +of most tasks, but in the case that a task cannot be detected (for example, killing an enemy with no +drop table such as Deadly Red Spiders), the task can be marked as complete manually by clicking +on the button. This button can also be used to mark completed tasks you have done while playing OSRS mobile or +on a different client without having the plugin available. Simply click the button the next time you are logged in to +Runelite and connected to send the check. + +Due to the nature of randomizing a live MMO with no ability to freely edit the character or adjust game logic or +balancing, this randomizer relies heavily on **the honor system**. The plugin cannot prevent you from walking through +locked regions or equipping locked items with the plugin disabled before connecting. It is important +to acknowledge before starting that the entire purpose of the randomizer is a self-imposed challenge, and there +is little point in cheating by circumventing the plugin's restrictions or marking a task complete without actually +completing it. If you wish to play OSRS with no restrictions, that is always available without the plugin. + +In order to access the AP Text Client commands (such as `!hint` or to chat with other players in the seed), enter your +command in chat prefaced by the string `!ap`. Example commands: + +`!ap buying gf 100k` -> Sends the message "buying gf 100k" to the server +`!ap !hint Area: Lumbridge` -> Attempts to hint for the "Area: Lumbridge" item. Results will appear in your chat box. + +Other server messages, such as chat, will appear in your chat box, prefaced by the Archipelago icon. + +## What items and locations get shuffled? +Items: +- Every map region (at least one chunk but sometimes more) +- Weapon tiers from iron to Rune (bronze is available from the start) +- Armor tiers from iron to Rune (bronze is available from the start) +- Two Spell Tiers (bolt and blast spells) +- Three tiers of Ranged Armor (leather, studded leather + vambraces, green dragonhide) +- Three tiers of Ranged Weapons (oak, willow, maple bows and their respective highest tier of arrows) + +Locations: +* Every Quest is a location that will always be included in every seed +* A random assortment of tasks, separated into categories based on the skill required. +These task categories can have different weights, minimums, and maximums based on your options. + * For a full list of Locations, items, and regions, see the +[Logic Document](https://docs.google.com/spreadsheets/d/1R8Cm8L6YkRWeiN7uYrdru8Vc1DlJ0aFAinH_fwhV8aU/edit?usp=sharing) + +## Which items can be in another player's world? +Any item or region unlock can be found in any player's world. + +## What does another world's item look like in Old School Runescape? +Upon completing a task, the item and recipient will be listed in the player's chatbox. + +## When the player receives an item, what happens? +In addition to the message appearing in the chatbox, a UI window will appear listing the item and who sent it. +These boxes also appear when connecting to a seed already in progress to list the items you have acquired while offline. +The sidebar will list all received items below the task list, starting with regions, then showing the highest tier of +equipment in each category. \ No newline at end of file diff --git a/worlds/osrs/docs/setup_en.md b/worlds/osrs/docs/setup_en.md new file mode 100644 index 000000000000..47c1c8f16fd7 --- /dev/null +++ b/worlds/osrs/docs/setup_en.md @@ -0,0 +1,58 @@ +# Setup Guide for Old School Runescape + +## Required Software + +- [RuneLite](https://runelite.net/) +- If the account being used has been migrated to a Jagex Account, the [Jagex Launcher](https://www.jagex.com/en-GB/launcher) +will also be necessary to run RuneLite + +## Configuring your YAML file + +### What is a YAML file and why do I need one? + +Your YAML file contains a set of configuration options which provide the generator with information about how it should +generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy +an experience customized for their taste, and different players in the same multiworld can all have different options. + +### Where do I get a YAML file? + +You can customize your settings by visiting the +[Old School Runescape Player Options Page](/games/Old%20School%20Runescape/player-options). + +## Joining a MultiWorld Game + +### Install the RuneLite Plugins +Open RuneLite and click on the wrench icon on the right side. From there, click on the plug icon to access the +Plugin Hub. You will need to install the [Archipelago Plugin](https://github.com/digiholic/osrs-archipelago) +and [Region Locker Plugin](https://github.com/slaytostay/region-locker). The Region Locker plugin +will include three plugins; only the `Region Locker` plugin itself is required. The `Region Locker GPU` plugin can be +used to display locked chunks in gray, but is incompatible with other GPU plugins such as 117's HD OSRS and can be +disabled. + +### Create a new OSRS Account +The OSRS Randomizer assumes you are playing on a newly created f2p Ironman account. As such, you will need to [create a +new Runescape account](https://secure.runescape.com/m=account-creation/create_account?theme=oldschool). + +If you already have a [Jagex Account](https://www.jagex.com/en-GB/accounts) you can add up to 20 characters on +one account through the Jagex Launcher. Note that there is currently no way to _remove_ characters +from a Jagex Account, as such, you might want to create a separate account to hold your Archipelago +characters if you intend to use your main Jagex account for more characters in the future. + +**Protip**: In order to avoid having to remember random email addresses for many accounts, take advantage of an email +alias, a feature supported by most email providers. Any text after a `+` in your email address will redirect to your +normal address, but the email will be recognized by the Jagex login as a new email address. For example, if your email +were `Archipelago@gmail.com`, entering `Archipelago+OSRSRandomizer@gmail.com` would cause the confirmation email to +be sent to your primary address, but the alias can be used to create a new account. One recommendation would be to +include the date of generation in the account, such as `Archipelago+APYYMMDD@gmail.com` for easy memorability. + +After creating an account, you may run through Tutorial Island without connecting; the randomizer has no +effect on the Tutorial. + +### Connect to the Multiserver +In the Archipelago Plugin, enter your server information. The `Auto Reconnect on Login For` field should remain blank; +it will be populated by the character name you first connect with, and it will reconnect to the AP server whenever that +character logs in. Open the Archipelago panel on the right-hand side to connect to the multiworld while logged in to +a game world to associate this character to the randomizer. + +For further information about how to connect to the server in the RuneLite plugin, +please see the [Archipelago Plugin](https://github.com/digiholic/osrs-archipelago) instructions. \ No newline at end of file From 6297a4efa552173f8a83f6dfa3b7f4fca828f4e4 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Wed, 7 Aug 2024 12:01:41 -0400 Subject: [PATCH 14/98] TUNIC: Fix missing traversal req #3740 --- worlds/tunic/er_data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index f49e7dff3e58..78a934b4904c 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -1183,6 +1183,8 @@ class DeadEnd(IntEnum): [], "Library Hero's Grave Region": [], + "Library Hall to Rotunda": + [], }, "Library Hero's Grave Region": { "Library Hall": From cf6661439e006d17aaca3fb814da927e7a0bae09 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Wed, 7 Aug 2024 12:18:50 -0400 Subject: [PATCH 15/98] TUNIC: Sort entrances in the spoiler log (#3733) * Sort entrances in spoiler log * Rearrange portal list to closer match the vanilla game order, for better spoiler and because I already did this mod-side * Add break (thanks vi) --- worlds/tunic/er_data.py | 230 ++++++++++++++++++------------------- worlds/tunic/er_scripts.py | 32 +++++- 2 files changed, 144 insertions(+), 118 deletions(-) diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index 78a934b4904c..e999026dec78 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -169,100 +169,16 @@ def destination_scene(self) -> str: # the vanilla connection destination="Overworld Redux", tag="_rafters"), Portal(name="Temple Door Exit", region="Sealed Temple", destination="Overworld Redux", tag="_main"), - - Portal(name="Well Ladder Exit", region="Beneath the Well Ladder Exit", - destination="Overworld Redux", tag="_entrance"), - Portal(name="Well to Well Boss", region="Beneath the Well Back", - destination="Sewer_Boss", tag="_"), - Portal(name="Well Exit towards Furnace", region="Beneath the Well Back", - destination="Overworld Redux", tag="_west_aqueduct"), - - Portal(name="Well Boss to Well", region="Well Boss", - destination="Sewer", tag="_"), - Portal(name="Checkpoint to Dark Tomb", region="Dark Tomb Checkpoint", - destination="Crypt Redux", tag="_"), - - Portal(name="Dark Tomb to Overworld", region="Dark Tomb Entry Point", + + Portal(name="Forest Belltower to Fortress", region="Forest Belltower Main", + destination="Fortress Courtyard", tag="_"), + Portal(name="Forest Belltower to Forest", region="Forest Belltower Lower", + destination="East Forest Redux", tag="_"), + Portal(name="Forest Belltower to Overworld", region="Forest Belltower Main", destination="Overworld Redux", tag="_"), - Portal(name="Dark Tomb to Furnace", region="Dark Tomb Dark Exit", - destination="Furnace", tag="_"), - Portal(name="Dark Tomb to Checkpoint", region="Dark Tomb Entry Point", - destination="Sewer_Boss", tag="_"), - - Portal(name="West Garden Exit near Hero's Grave", region="West Garden", - destination="Overworld Redux", tag="_lower"), - Portal(name="West Garden to Magic Dagger House", region="West Garden", - destination="archipelagos_house", tag="_"), - Portal(name="West Garden Exit after Boss", region="West Garden after Boss", - destination="Overworld Redux", tag="_upper"), - Portal(name="West Garden Shop", region="West Garden", - destination="Shop", tag="_"), - Portal(name="West Garden Laurels Exit", region="West Garden Laurels Exit Region", - destination="Overworld Redux", tag="_lowest"), - Portal(name="West Garden Hero's Grave", region="West Garden Hero's Grave Region", - destination="RelicVoid", tag="_teleporter_relic plinth"), - Portal(name="West Garden to Far Shore", region="West Garden Portal", - destination="Transit", tag="_teleporter_archipelagos_teleporter"), - - Portal(name="Magic Dagger House Exit", region="Magic Dagger House", - destination="Archipelagos Redux", tag="_"), - - Portal(name="Atoll Upper Exit", region="Ruined Atoll", - destination="Overworld Redux", tag="_upper"), - Portal(name="Atoll Lower Exit", region="Ruined Atoll Lower Entry Area", - destination="Overworld Redux", tag="_lower"), - Portal(name="Atoll Shop", region="Ruined Atoll", - destination="Shop", tag="_"), - Portal(name="Atoll to Far Shore", region="Ruined Atoll Portal", - destination="Transit", tag="_teleporter_atoll"), - Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Statue", - destination="Library Exterior", tag="_"), - Portal(name="Frog Stairs Eye Entrance", region="Ruined Atoll Frog Eye", - destination="Frog Stairs", tag="_eye"), - Portal(name="Frog Stairs Mouth Entrance", region="Ruined Atoll Frog Mouth", - destination="Frog Stairs", tag="_mouth"), - - Portal(name="Frog Stairs Eye Exit", region="Frog Stairs Eye Exit", - destination="Atoll Redux", tag="_eye"), - Portal(name="Frog Stairs Mouth Exit", region="Frog Stairs Upper", - destination="Atoll Redux", tag="_mouth"), - Portal(name="Frog Stairs to Frog's Domain's Entrance", region="Frog Stairs to Frog's Domain", - destination="frog cave main", tag="_Entrance"), - Portal(name="Frog Stairs to Frog's Domain's Exit", region="Frog Stairs Lower", - destination="frog cave main", tag="_Exit"), - - Portal(name="Frog's Domain Ladder Exit", region="Frog's Domain Entry", - destination="Frog Stairs", tag="_Entrance"), - Portal(name="Frog's Domain Orb Exit", region="Frog's Domain Back", - destination="Frog Stairs", tag="_Exit"), - - Portal(name="Library Exterior Tree", region="Library Exterior Tree Region", - destination="Atoll Redux", tag="_"), - Portal(name="Library Exterior Ladder", region="Library Exterior Ladder Region", - destination="Library Hall", tag="_"), - - Portal(name="Library Hall Bookshelf Exit", region="Library Hall Bookshelf", - destination="Library Exterior", tag="_"), - Portal(name="Library Hero's Grave", region="Library Hero's Grave Region", - destination="RelicVoid", tag="_teleporter_relic plinth"), - Portal(name="Library Hall to Rotunda", region="Library Hall to Rotunda", - destination="Library Rotunda", tag="_"), - - Portal(name="Library Rotunda Lower Exit", region="Library Rotunda to Hall", - destination="Library Hall", tag="_"), - Portal(name="Library Rotunda Upper Exit", region="Library Rotunda to Lab", - destination="Library Lab", tag="_"), - - Portal(name="Library Lab to Rotunda", region="Library Lab Lower", - destination="Library Rotunda", tag="_"), - Portal(name="Library to Far Shore", region="Library Portal", - destination="Transit", tag="_teleporter_library teleporter"), - Portal(name="Library Lab to Librarian Arena", region="Library Lab to Librarian", - destination="Library Arena", tag="_"), - - Portal(name="Librarian Arena Exit", region="Library Arena", - destination="Library Lab", tag="_"), - + Portal(name="Forest Belltower to Guard Captain Room", region="Forest Belltower Upper", + destination="Forest Boss Room", tag="_"), + Portal(name="Forest to Belltower", region="East Forest", destination="Forest Belltower", tag="_"), Portal(name="Forest Guard House 1 Lower Entrance", region="East Forest", @@ -281,7 +197,14 @@ def destination_scene(self) -> str: # the vanilla connection destination="Sword Access", tag="_lower"), Portal(name="Forest Grave Path Upper Entrance", region="East Forest", destination="Sword Access", tag="_upper"), - + + Portal(name="Forest Grave Path Upper Exit", region="Forest Grave Path Upper", + destination="East Forest Redux", tag="_upper"), + Portal(name="Forest Grave Path Lower Exit", region="Forest Grave Path Main", + destination="East Forest Redux", tag="_lower"), + Portal(name="East Forest Hero's Grave", region="Forest Hero's Grave", + destination="RelicVoid", tag="_teleporter_relic plinth"), + Portal(name="Guard House 1 Dance Fox Exit", region="Guard House 1 West", destination="East Forest Redux", tag="_upper"), Portal(name="Guard House 1 Lower Exit", region="Guard House 1 West", @@ -290,33 +213,54 @@ def destination_scene(self) -> str: # the vanilla connection destination="East Forest Redux", tag="_gate"), Portal(name="Guard House 1 to Guard Captain Room", region="Guard House 1 East", destination="Forest Boss Room", tag="_"), - - Portal(name="Forest Grave Path Upper Exit", region="Forest Grave Path Upper", - destination="East Forest Redux", tag="_upper"), - Portal(name="Forest Grave Path Lower Exit", region="Forest Grave Path Main", - destination="East Forest Redux", tag="_lower"), - Portal(name="East Forest Hero's Grave", region="Forest Hero's Grave", - destination="RelicVoid", tag="_teleporter_relic plinth"), - + Portal(name="Guard House 2 Lower Exit", region="Guard House 2 Lower", destination="East Forest Redux", tag="_lower"), Portal(name="Guard House 2 Upper Exit", region="Guard House 2 Upper", destination="East Forest Redux", tag="_upper"), - + Portal(name="Guard Captain Room Non-Gate Exit", region="Forest Boss Room", destination="East Forest Redux Laddercave", tag="_"), Portal(name="Guard Captain Room Gate Exit", region="Forest Boss Room", destination="Forest Belltower", tag="_"), + + Portal(name="Well Ladder Exit", region="Beneath the Well Ladder Exit", + destination="Overworld Redux", tag="_entrance"), + Portal(name="Well to Well Boss", region="Beneath the Well Back", + destination="Sewer_Boss", tag="_"), + Portal(name="Well Exit towards Furnace", region="Beneath the Well Back", + destination="Overworld Redux", tag="_west_aqueduct"), - Portal(name="Forest Belltower to Fortress", region="Forest Belltower Main", - destination="Fortress Courtyard", tag="_"), - Portal(name="Forest Belltower to Forest", region="Forest Belltower Lower", - destination="East Forest Redux", tag="_"), - Portal(name="Forest Belltower to Overworld", region="Forest Belltower Main", + Portal(name="Well Boss to Well", region="Well Boss", + destination="Sewer", tag="_"), + Portal(name="Checkpoint to Dark Tomb", region="Dark Tomb Checkpoint", + destination="Crypt Redux", tag="_"), + + Portal(name="Dark Tomb to Overworld", region="Dark Tomb Entry Point", destination="Overworld Redux", tag="_"), - Portal(name="Forest Belltower to Guard Captain Room", region="Forest Belltower Upper", - destination="Forest Boss Room", tag="_"), + Portal(name="Dark Tomb to Furnace", region="Dark Tomb Dark Exit", + destination="Furnace", tag="_"), + Portal(name="Dark Tomb to Checkpoint", region="Dark Tomb Entry Point", + destination="Sewer_Boss", tag="_"), + Portal(name="West Garden Exit near Hero's Grave", region="West Garden", + destination="Overworld Redux", tag="_lower"), + Portal(name="West Garden to Magic Dagger House", region="West Garden", + destination="archipelagos_house", tag="_"), + Portal(name="West Garden Exit after Boss", region="West Garden after Boss", + destination="Overworld Redux", tag="_upper"), + Portal(name="West Garden Shop", region="West Garden", + destination="Shop", tag="_"), + Portal(name="West Garden Laurels Exit", region="West Garden Laurels Exit Region", + destination="Overworld Redux", tag="_lowest"), + Portal(name="West Garden Hero's Grave", region="West Garden Hero's Grave Region", + destination="RelicVoid", tag="_teleporter_relic plinth"), + Portal(name="West Garden to Far Shore", region="West Garden Portal", + destination="Transit", tag="_teleporter_archipelagos_teleporter"), + + Portal(name="Magic Dagger House Exit", region="Magic Dagger House", + destination="Archipelagos Redux", tag="_"), + Portal(name="Fortress Courtyard to Fortress Grave Path Lower", region="Fortress Courtyard", destination="Fortress Reliquary", tag="_Lower"), Portal(name="Fortress Courtyard to Fortress Grave Path Upper", region="Fortress Courtyard Upper", @@ -333,12 +277,12 @@ def destination_scene(self) -> str: # the vanilla connection destination="Overworld Redux", tag="_"), Portal(name="Fortress Courtyard Shop", region="Fortress Exterior near cave", destination="Shop", tag="_"), - + Portal(name="Beneath the Vault to Fortress Interior", region="Beneath the Vault Back", destination="Fortress Main", tag="_"), Portal(name="Beneath the Vault to Fortress Courtyard", region="Beneath the Vault Ladder Exit", destination="Fortress Courtyard", tag="_"), - + Portal(name="Fortress Interior Main Exit", region="Eastern Vault Fortress", destination="Fortress Courtyard", tag="_Big Door"), Portal(name="Fortress Interior to Beneath the Earth", region="Eastern Vault Fortress", @@ -351,14 +295,14 @@ def destination_scene(self) -> str: # the vanilla connection destination="Fortress East", tag="_upper"), Portal(name="Fortress Interior to East Fortress Lower", region="Eastern Vault Fortress", destination="Fortress East", tag="_lower"), - + Portal(name="East Fortress to Interior Lower", region="Fortress East Shortcut Lower", destination="Fortress Main", tag="_lower"), Portal(name="East Fortress to Courtyard", region="Fortress East Shortcut Upper", destination="Fortress Courtyard", tag="_"), Portal(name="East Fortress to Interior Upper", region="Fortress East Shortcut Upper", destination="Fortress Main", tag="_upper"), - + Portal(name="Fortress Grave Path Lower Exit", region="Fortress Grave Path", destination="Fortress Courtyard", tag="_Lower"), Portal(name="Fortress Hero's Grave", region="Fortress Hero's Grave Region", @@ -370,11 +314,67 @@ def destination_scene(self) -> str: # the vanilla connection Portal(name="Dusty Exit", region="Fortress Leaf Piles", destination="Fortress Reliquary", tag="_"), - + Portal(name="Siege Engine Arena to Fortress", region="Fortress Arena", destination="Fortress Main", tag="_"), Portal(name="Fortress to Far Shore", region="Fortress Arena Portal", destination="Transit", tag="_teleporter_spidertank"), + + Portal(name="Atoll Upper Exit", region="Ruined Atoll", + destination="Overworld Redux", tag="_upper"), + Portal(name="Atoll Lower Exit", region="Ruined Atoll Lower Entry Area", + destination="Overworld Redux", tag="_lower"), + Portal(name="Atoll Shop", region="Ruined Atoll", + destination="Shop", tag="_"), + Portal(name="Atoll to Far Shore", region="Ruined Atoll Portal", + destination="Transit", tag="_teleporter_atoll"), + Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Statue", + destination="Library Exterior", tag="_"), + Portal(name="Frog Stairs Eye Entrance", region="Ruined Atoll Frog Eye", + destination="Frog Stairs", tag="_eye"), + Portal(name="Frog Stairs Mouth Entrance", region="Ruined Atoll Frog Mouth", + destination="Frog Stairs", tag="_mouth"), + + Portal(name="Frog Stairs Eye Exit", region="Frog Stairs Eye Exit", + destination="Atoll Redux", tag="_eye"), + Portal(name="Frog Stairs Mouth Exit", region="Frog Stairs Upper", + destination="Atoll Redux", tag="_mouth"), + Portal(name="Frog Stairs to Frog's Domain's Entrance", region="Frog Stairs to Frog's Domain", + destination="frog cave main", tag="_Entrance"), + Portal(name="Frog Stairs to Frog's Domain's Exit", region="Frog Stairs Lower", + destination="frog cave main", tag="_Exit"), + + Portal(name="Frog's Domain Ladder Exit", region="Frog's Domain Entry", + destination="Frog Stairs", tag="_Entrance"), + Portal(name="Frog's Domain Orb Exit", region="Frog's Domain Back", + destination="Frog Stairs", tag="_Exit"), + + Portal(name="Library Exterior Tree", region="Library Exterior Tree Region", + destination="Atoll Redux", tag="_"), + Portal(name="Library Exterior Ladder", region="Library Exterior Ladder Region", + destination="Library Hall", tag="_"), + + Portal(name="Library Hall Bookshelf Exit", region="Library Hall Bookshelf", + destination="Library Exterior", tag="_"), + Portal(name="Library Hero's Grave", region="Library Hero's Grave Region", + destination="RelicVoid", tag="_teleporter_relic plinth"), + Portal(name="Library Hall to Rotunda", region="Library Hall to Rotunda", + destination="Library Rotunda", tag="_"), + + Portal(name="Library Rotunda Lower Exit", region="Library Rotunda to Hall", + destination="Library Hall", tag="_"), + Portal(name="Library Rotunda Upper Exit", region="Library Rotunda to Lab", + destination="Library Lab", tag="_"), + + Portal(name="Library Lab to Rotunda", region="Library Lab Lower", + destination="Library Rotunda", tag="_"), + Portal(name="Library to Far Shore", region="Library Portal", + destination="Transit", tag="_teleporter_library teleporter"), + Portal(name="Library Lab to Librarian Arena", region="Library Lab to Librarian", + destination="Library Arena", tag="_"), + + Portal(name="Librarian Arena Exit", region="Library Arena", + destination="Library Lab", tag="_"), Portal(name="Stairs to Top of the Mountain", region="Lower Mountain Stairs", destination="Mountaintop", tag="_"), diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index 0bd8c5e80681..a4295cf9f2a4 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -24,10 +24,10 @@ def create_er_regions(world: "TunicWorld") -> Dict[Portal, Portal]: regions: Dict[str, Region] = {} if world.options.entrance_rando: portal_pairs = pair_portals(world) - # output the entrances to the spoiler log here for convenience - for portal1, portal2 in portal_pairs.items(): - world.multiworld.spoiler.set_entrance(portal1.name, portal2.name, "both", world.player) + sorted_portal_pairs = sort_portals(portal_pairs) + for portal1, portal2 in sorted_portal_pairs.items(): + world.multiworld.spoiler.set_entrance(portal1, portal2, "both", world.player) else: portal_pairs = vanilla_portals() @@ -504,3 +504,29 @@ def update_reachable_regions(connected_regions: Set[str], traversal_reqs: Dict[s connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic) return connected_regions + + +# sort the portal dict by the name of the first portal, referring to the portal order in the master portal list +def sort_portals(portal_pairs: Dict[Portal, Portal]) -> Dict[str, str]: + sorted_pairs: Dict[str, str] = {} + reference_list: List[str] = [portal.name for portal in portal_mapping] + reference_list.append("Shop Portal") + + # note: this is not necessary yet since the shop portals aren't numbered yet -- they will be when decoupled happens + # due to plando, there can be a variable number of shops + # I could either do it like this, or just go up to like 200, this seemed better + # shop_count = 0 + # for portal1, portal2 in portal_pairs.items(): + # if portal1.name.startswith("Shop"): + # shop_count += 1 + # if portal2.name.startswith("Shop"): + # shop_count += 1 + # reference_list.extend([f"Shop Portal {i + 1}" for i in range(shop_count)]) + + for name in reference_list: + for portal1, portal2 in portal_pairs.items(): + if name == portal1.name: + sorted_pairs[portal1.name] = portal2.name + break + return sorted_pairs + From 74697b679ea4bd376647c69107891bb79b7b9c56 Mon Sep 17 00:00:00 2001 From: JaredWeakStrike <96694163+JaredWeakStrike@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:56:22 -0400 Subject: [PATCH 16/98] KH2: Update the docs to support steam in the setup guide (#3711) * doc updates * add steam link * Update worlds/kh2/docs/setup_en.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Update setup_en.md * Forgot to include these * Consistent styling * :) * version 3.3.0 --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/kh2/docs/setup_en.md | 53 +++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/worlds/kh2/docs/setup_en.md b/worlds/kh2/docs/setup_en.md index c6fdb020b8a4..ed4d90bb54fb 100644 --- a/worlds/kh2/docs/setup_en.md +++ b/worlds/kh2/docs/setup_en.md @@ -1,22 +1,25 @@ # Kingdom Hearts 2 Archipelago Setup Guide +

Quick Links

- [Game Info Page](../../../../games/Kingdom%20Hearts%202/info/en) - [Player Options Page](../../../../games/Kingdom%20Hearts%202/player-options)

Required Software:

- `Kingdom Hearts II Final Mix` from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts) -- Follow this Guide to set up these requirements [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/)
- 1. `3.2.0 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`
+`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` - 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`
+ 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` +

Required: Archipelago Companion Mod

Load this mod just like the GoA ROM you did during the KH2 Rando setup. `JaredWeakStrike/APCompanion`
@@ -24,6 +27,7 @@ Have this mod second-highest priority below the .zip seed.
This mod is based upon Num's Garden of Assemblege Mod and requires it to work. Without Num this could not be possible.

Required: Auto Save Mod

+ 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

Installing A Seed

@@ -33,33 +37,33 @@ Make sure the seed is on the top of the list (Highest Priority)
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.

What the Mod Manager Should Look Like.

+ ![image](https://i.imgur.com/Si4oZ8w.png)

Using the KH2 Client

-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).
+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).
When you successfully connect to the server the client will automatically hook into the game to send/receive checks.
If the client ever loses connection to the game, it will also disconnect from the server and you will need to reconnect.
`Make sure the game is open whenever you try to connect the client to the server otherwise it will immediately disconnect you.`
Most checks will be sent to you anywhere outside a load or cutscene.
`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.` -
+

KH2 Client should look like this:

+ ![image](https://i.imgur.com/qP6CmV8.png) -
-Enter `The room's port number` into the top box where the x's are and press "Connect". Follow the prompts there and you should be connected +Enter `The room's port number` into the top box where the x's are and press "Connect". Follow the prompts there and you should be connected

Common Pitfalls

+ - Having an old GOA Lua Script in your `C:\Users\*YourName*\Documents\KINGDOM HEARTS HD 1.5+2.5 ReMIX\scripts\kh2` folder. - - Pressing F2 while in game should look like this. ![image](https://i.imgur.com/ABSdtPC.png) -
+ - Pressing F2 while in game should look like this. ![image](https://i.imgur.com/ABSdtPC.png) - Not having Lua Backend Configured Correctly. - - To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Lua Backend Configuration Step. -
-- Loading into Simulated Twilight Town Instead of the GOA. - - To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Panacea and Lua Backend Steps. + - To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Lua Backend Configuration Step. +- Loading into Simulated Twilight Town Instead of the GOA. + - To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Panacea and Lua Backend Steps.

Best Practices

@@ -70,8 +74,11 @@ Enter `The room's port number` into the top box where the x's are and pr - Make sure to save in a different save slot when playing in an async or disconnecting from the server to play a different seed

Logic Sheet

+ Have any questions on what's in logic? This spreadsheet made by Bulcon has the answer [Requirements/logic sheet](https://docs.google.com/spreadsheets/d/1nNi8ohEs1fv-sDQQRaP45o6NoRcMlLJsGckBonweDMY/edit?usp=sharing) +

F.A.Q.

+ - 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? @@ -83,11 +90,13 @@ Have any questions on what's in logic? This spreadsheet made by Bulcon has the a - 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 `only` the most up to date version on the Epic Game Store is supported: version `1.0.0.8_WW`. + - Currently the `only` supported versions are `Epic Games Version 1.0.0.9_WW` and `Steam Build Version 14716933`. - 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? + - This is normally caused by having an outdated GOA mod or having an outdated panacea and/or luabackend. To fix this rerun the setup wizard and reinstall luabackend and panacea. Also make sure all your mods are up-to-date. - Why did I crash? - The port of Kingdom Hearts 2 can and will randomly crash, this is the fault of the game not the randomizer or the archipelago client. - If you have a continuous/constant crash (in the same area/event every time) you will want to reverify your installed files. This can be done by doing the following: Open Epic Game Store --> Library --> Click Triple Dots --> Manage --> Verify @@ -99,5 +108,3 @@ Have any questions on what's in logic? This spreadsheet made by Bulcon has the a - Because Kingdom Hearts 2 is prone to crashes and will keep you from losing your progress. - 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. - - From 05ce29f7dcad5af14cd2ffb89798695fc1c7c688 Mon Sep 17 00:00:00 2001 From: Mysteryem Date: Wed, 7 Aug 2024 22:57:07 +0100 Subject: [PATCH 17/98] RoR2: Remove recursion from explore mode access rules (#3681) The access rules for " Chest n", " Shrine n" etc. locations recursively called state.can_reach() for the n-1 location name, with the n=1 location being the only location to have the actual access rule set. This patch removes the recursion, instead setting the actual access rule directly on each location, increasing the performance of checking accessibility of n>1 locations. Risk of Rain 2 was already quite fast to generate despite the recursion in the access rules, but with this patch, generating a multiworld with 200 copies of the template RoR2 yaml (and progression balancing disabled through a meta.yaml) goes from about 18s to about 6s for me. From generating the same seed before and after this patch, the same result is produced. --- worlds/ror2/rules.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/worlds/ror2/rules.py b/worlds/ror2/rules.py index 2e6b018f42fb..f0ab9f28313f 100644 --- a/worlds/ror2/rules.py +++ b/worlds/ror2/rules.py @@ -31,23 +31,17 @@ def has_all_items(multiworld: MultiWorld, items: Set[str], region: str, player: # Checks to see if chest/shrine are accessible def has_location_access_rule(multiworld: MultiWorld, environment: str, player: int, item_number: int, item_type: str)\ -> None: - if item_number == 1: - multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \ - lambda state: state.has(environment, player) + location_name = f"{environment}: {item_type} {item_number}" + if item_type == "Scavenger": # scavengers need to be locked till after a full loop since that is when they are capable of spawning. # (While technically the requirement is just beating 5 stages, this will ensure that the player will have # a long enough run to have enough director credits for scavengers and # help prevent being stuck in the same stages until that point). - if item_type == "Scavenger": - multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \ - lambda state: state.has(environment, player) and state.has("Stage 5", player) + multiworld.get_location(location_name, player).access_rule = \ + lambda state: state.has(environment, player) and state.has("Stage 5", player) else: - multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \ - lambda state: check_location(state, environment, player, item_number, item_type) - - -def check_location(state, environment: str, player: int, item_number: int, item_name: str) -> bool: - return state.can_reach(f"{environment}: {item_name} {item_number - 1}", "Location", player) + multiworld.get_location(location_name, player).access_rule = \ + lambda state: state.has(environment, player) def set_rules(ror2_world: "RiskOfRainWorld") -> None: From 575c338aa3c895aa4d10824be5380b4e094b55e1 Mon Sep 17 00:00:00 2001 From: Louis M Date: Wed, 7 Aug 2024 18:19:52 -0400 Subject: [PATCH 18/98] Aquaria: Logic bug fixes (#3679) * Fixing logic bugs * Require energy attack in the cathedral and energy form in the body * King Jelly can be beaten easily with only the Dual Form * I think that I have a problem with my left and right... * There is a monster that is blocking the path, soo need attack to pass * The Li cage is not accessible without the Sunken city boss * Removing useless space. Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Two more minors logic modification * Adapting tests to af9b6cd * Reformat the Region file --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/aquaria/Items.py | 2 +- worlds/aquaria/Locations.py | 20 +- worlds/aquaria/Regions.py | 446 +++++++++--------- worlds/aquaria/test/__init__.py | 2 +- worlds/aquaria/test/test_beast_form_access.py | 24 +- ...test_beast_form_or_arnassi_armor_access.py | 39 ++ .../aquaria/test/test_energy_form_access.py | 53 +-- .../test_energy_form_or_dual_form_access.py | 92 ++++ worlds/aquaria/test/test_fish_form_access.py | 4 +- worlds/aquaria/test/test_light_access.py | 1 - .../aquaria/test/test_spirit_form_access.py | 1 - 11 files changed, 396 insertions(+), 288 deletions(-) create mode 100644 worlds/aquaria/test/test_beast_form_or_arnassi_armor_access.py create mode 100644 worlds/aquaria/test/test_energy_form_or_dual_form_access.py diff --git a/worlds/aquaria/Items.py b/worlds/aquaria/Items.py index 34557d95d00d..f822d675e6e7 100644 --- a/worlds/aquaria/Items.py +++ b/worlds/aquaria/Items.py @@ -99,7 +99,7 @@ def __init__(self, id: int, count: int, type: ItemType, group: ItemGroup): "Mutant Costume": ItemData(698020, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mutant_costume "Baby Nautilus": ItemData(698021, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_nautilus "Baby Piranha": ItemData(698022, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_piranha - "Arnassi Armor": ItemData(698023, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_seahorse_costume + "Arnassi Armor": ItemData(698023, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_seahorse_costume "Seed Bag": ItemData(698024, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_seed_bag "King's Skull": ItemData(698025, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_skull "Song Plant Spore": ItemData(698026, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_spore_seed diff --git a/worlds/aquaria/Locations.py b/worlds/aquaria/Locations.py index 2eb9d1e9a29d..f6e098103fdc 100644 --- a/worlds/aquaria/Locations.py +++ b/worlds/aquaria/Locations.py @@ -45,7 +45,7 @@ class AquariaLocations: "Home Water, bulb below the grouper fish": 698058, "Home Water, bulb in the path below Nautilus Prime": 698059, "Home Water, bulb in the little room above the grouper fish": 698060, - "Home Water, bulb in the end of the left path from the Verse Cave": 698061, + "Home Water, bulb in the end of the path close to the Verse Cave": 698061, "Home Water, bulb in the top left path": 698062, "Home Water, bulb in the bottom left room": 698063, "Home Water, bulb close to Naija's Home": 698064, @@ -67,7 +67,7 @@ class AquariaLocations: locations_song_cave = { "Song Cave, Erulian spirit": 698206, - "Song Cave, bulb in the top left part": 698071, + "Song Cave, bulb in the top right part": 698071, "Song Cave, bulb in the big anemone room": 698072, "Song Cave, bulb in the path to the singing statues": 698073, "Song Cave, bulb under the rock in the path to the singing statues": 698074, @@ -152,6 +152,9 @@ class AquariaLocations: locations_arnassi_path = { "Arnassi Ruins, Arnassi Statue": 698164, + } + + locations_arnassi_cave_transturtle = { "Arnassi Ruins, Transturtle": 698217, } @@ -269,9 +272,12 @@ class AquariaLocations: } locations_forest_bl = { + "Kelp Forest bottom left area, Transturtle": 698212, + } + + locations_forest_bl_sc = { "Kelp Forest bottom left area, bulb close to the spirit crystals": 698054, "Kelp Forest bottom left area, Walker Baby": 698186, - "Kelp Forest bottom left area, Transturtle": 698212, } locations_forest_br = { @@ -370,7 +376,7 @@ class AquariaLocations: locations_sun_temple_r = { "Sun Temple, first bulb of the temple": 698091, - "Sun Temple, bulb on the left part": 698092, + "Sun Temple, bulb on the right part": 698092, "Sun Temple, bulb in the hidden room of the right part": 698093, "Sun Temple, Sun Key": 698182, } @@ -402,6 +408,9 @@ class AquariaLocations: "Abyss right area, bulb in the middle path": 698110, "Abyss right area, bulb behind the rock in the middle path": 698111, "Abyss right area, bulb in the left green room": 698112, + } + + locations_abyss_r_transturtle = { "Abyss right area, Transturtle": 698214, } @@ -499,6 +508,7 @@ class AquariaLocations: **AquariaLocations.locations_skeleton_path_sc, **AquariaLocations.locations_arnassi, **AquariaLocations.locations_arnassi_path, + **AquariaLocations.locations_arnassi_cave_transturtle, **AquariaLocations.locations_arnassi_crab_boss, **AquariaLocations.locations_sun_temple_l, **AquariaLocations.locations_sun_temple_r, @@ -509,6 +519,7 @@ class AquariaLocations: **AquariaLocations.locations_abyss_l, **AquariaLocations.locations_abyss_lb, **AquariaLocations.locations_abyss_r, + **AquariaLocations.locations_abyss_r_transturtle, **AquariaLocations.locations_energy_temple_1, **AquariaLocations.locations_energy_temple_2, **AquariaLocations.locations_energy_temple_3, @@ -530,6 +541,7 @@ class AquariaLocations: **AquariaLocations.locations_forest_tr, **AquariaLocations.locations_forest_tr_fp, **AquariaLocations.locations_forest_bl, + **AquariaLocations.locations_forest_bl_sc, **AquariaLocations.locations_forest_br, **AquariaLocations.locations_forest_boss, **AquariaLocations.locations_forest_boss_entrance, diff --git a/worlds/aquaria/Regions.py b/worlds/aquaria/Regions.py index 93c02d4e6766..3ec1fb880e13 100755 --- a/worlds/aquaria/Regions.py +++ b/worlds/aquaria/Regions.py @@ -14,97 +14,112 @@ # Every condition to connect regions -def _has_hot_soup(state:CollectionState, player: int) -> bool: +def _has_hot_soup(state: CollectionState, player: int) -> bool: """`player` in `state` has the hotsoup item""" - return state.has("Hot soup", player) + return state.has_any({"Hot soup", "Hot soup x 2"}, player) -def _has_tongue_cleared(state:CollectionState, player: int) -> bool: +def _has_tongue_cleared(state: CollectionState, player: int) -> bool: """`player` in `state` has the Body tongue cleared item""" return state.has("Body tongue cleared", player) -def _has_sun_crystal(state:CollectionState, player: int) -> bool: +def _has_sun_crystal(state: CollectionState, player: int) -> bool: """`player` in `state` has the Sun crystal item""" return state.has("Has sun crystal", player) and _has_bind_song(state, player) -def _has_li(state:CollectionState, player: int) -> bool: +def _has_li(state: CollectionState, player: int) -> bool: """`player` in `state` has Li in its team""" return state.has("Li and Li song", player) -def _has_damaging_item(state:CollectionState, player: int) -> bool: +def _has_damaging_item(state: CollectionState, player: int) -> bool: """`player` in `state` has the shield song item""" - return state.has_any({"Energy form", "Nature form", "Beast form", "Li and Li song", "Baby Nautilus", - "Baby Piranha", "Baby Blaster"}, player) + return state.has_any({"Energy form", "Nature form", "Beast form", "Li and Li song", "Baby Nautilus", + "Baby Piranha", "Baby Blaster"}, player) -def _has_shield_song(state:CollectionState, player: int) -> bool: +def _has_energy_attack_item(state: CollectionState, player: int) -> bool: + """`player` in `state` has items that can do a lot of damage (enough to beat bosses)""" + return _has_energy_form(state, player) or _has_dual_form(state, player) + + +def _has_shield_song(state: CollectionState, player: int) -> bool: """`player` in `state` has the shield song item""" return state.has("Shield song", player) -def _has_bind_song(state:CollectionState, player: int) -> bool: +def _has_bind_song(state: CollectionState, player: int) -> bool: """`player` in `state` has the bind song item""" return state.has("Bind song", player) -def _has_energy_form(state:CollectionState, player: int) -> bool: +def _has_energy_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the energy form item""" return state.has("Energy form", player) -def _has_beast_form(state:CollectionState, player: int) -> bool: +def _has_beast_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the beast form item""" return state.has("Beast form", player) -def _has_nature_form(state:CollectionState, player: int) -> bool: +def _has_beast_and_soup_form(state: CollectionState, player: int) -> bool: + """`player` in `state` has the beast form item""" + return _has_beast_form(state, player) and _has_hot_soup(state, player) + + +def _has_beast_form_or_arnassi_armor(state: CollectionState, player: int) -> bool: + """`player` in `state` has the beast form item""" + return _has_beast_form(state, player) or state.has("Arnassi Armor", player) + + +def _has_nature_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the nature form item""" return state.has("Nature form", player) -def _has_sun_form(state:CollectionState, player: int) -> bool: +def _has_sun_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the sun form item""" return state.has("Sun form", player) -def _has_light(state:CollectionState, player: int) -> bool: +def _has_light(state: CollectionState, player: int) -> bool: """`player` in `state` has the light item""" return state.has("Baby Dumbo", player) or _has_sun_form(state, player) -def _has_dual_form(state:CollectionState, player: int) -> bool: +def _has_dual_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the dual form item""" return _has_li(state, player) and state.has("Dual form", player) -def _has_fish_form(state:CollectionState, player: int) -> bool: +def _has_fish_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the fish form item""" return state.has("Fish form", player) -def _has_spirit_form(state:CollectionState, player: int) -> bool: +def _has_spirit_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the spirit form item""" return state.has("Spirit form", player) -def _has_big_bosses(state:CollectionState, player: int) -> bool: +def _has_big_bosses(state: CollectionState, player: int) -> bool: """`player` in `state` has beated every big bosses""" return state.has_all({"Fallen God beated", "Mithalan God beated", "Drunian God beated", - "Sun God beated", "The Golem beated"}, player) + "Sun God beated", "The Golem beated"}, player) -def _has_mini_bosses(state:CollectionState, player: int) -> bool: +def _has_mini_bosses(state: CollectionState, player: int) -> bool: """`player` in `state` has beated every big bosses""" return state.has_all({"Nautilus Prime beated", "Blaster Peg Prime beated", "Mergog beated", - "Mithalan priests beated", "Octopus Prime beated", "Crabbius Maximus beated", - "Mantis Shrimp Prime beated", "King Jellyfish God Prime beated"}, player) + "Mithalan priests beated", "Octopus Prime beated", "Crabbius Maximus beated", + "Mantis Shrimp Prime beated", "King Jellyfish God Prime beated"}, player) -def _has_secrets(state:CollectionState, player: int) -> bool: - return state.has_all({"First secret obtained", "Second secret obtained", "Third secret obtained"},player) +def _has_secrets(state: CollectionState, player: int) -> bool: + return state.has_all({"First secret obtained", "Second secret obtained", "Third secret obtained"}, player) class AquariaRegions: @@ -134,6 +149,7 @@ class AquariaRegions: skeleton_path: Region skeleton_path_sc: Region arnassi: Region + arnassi_cave_transturtle: Region arnassi_path: Region arnassi_crab_boss: Region simon: Region @@ -152,6 +168,7 @@ class AquariaRegions: forest_tr: Region forest_tr_fp: Region forest_bl: Region + forest_bl_sc: Region forest_br: Region forest_boss: Region forest_boss_entrance: Region @@ -179,6 +196,7 @@ class AquariaRegions: abyss_l: Region abyss_lb: Region abyss_r: Region + abyss_r_transturtle: Region ice_cave: Region bubble_cave: Region bubble_cave_boss: Region @@ -213,7 +231,7 @@ class AquariaRegions: """ def __add_region(self, hint: str, - locations: Optional[Dict[str, Optional[int]]]) -> Region: + locations: Optional[Dict[str, int]]) -> Region: """ Create a new Region, add it to the `world` regions and return it. Be aware that this function have a side effect on ``world`.`regions` @@ -236,7 +254,7 @@ def __create_home_water_area(self) -> None: self.home_water_nautilus = self.__add_region("Home Water, Nautilus nest", AquariaLocations.locations_home_water_nautilus) self.home_water_transturtle = self.__add_region("Home Water, turtle room", - AquariaLocations.locations_home_water_transturtle) + AquariaLocations.locations_home_water_transturtle) self.naija_home = self.__add_region("Naija's Home", AquariaLocations.locations_naija_home) self.song_cave = self.__add_region("Song Cave", AquariaLocations.locations_song_cave) @@ -280,6 +298,8 @@ def __create_openwater(self) -> None: self.arnassi = self.__add_region("Arnassi Ruins", AquariaLocations.locations_arnassi) self.arnassi_path = self.__add_region("Arnassi Ruins, back entrance path", AquariaLocations.locations_arnassi_path) + self.arnassi_cave_transturtle = self.__add_region("Arnassi Ruins, transturtle area", + AquariaLocations.locations_arnassi_cave_transturtle) self.arnassi_crab_boss = self.__add_region("Arnassi Ruins, Crabbius Maximus lair", AquariaLocations.locations_arnassi_crab_boss) @@ -302,9 +322,9 @@ def __create_mithalas(self) -> None: AquariaLocations.locations_cathedral_r) self.cathedral_underground = self.__add_region("Mithalas Cathedral underground", AquariaLocations.locations_cathedral_underground) - self.cathedral_boss_r = self.__add_region("Mithalas Cathedral, Mithalan God room", + self.cathedral_boss_r = self.__add_region("Mithalas Cathedral, Mithalan God room", None) + self.cathedral_boss_l = self.__add_region("Mithalas Cathedral, after Mithalan God room", AquariaLocations.locations_cathedral_boss) - self.cathedral_boss_l = self.__add_region("Mithalas Cathedral, after Mithalan God room", None) def __create_forest(self) -> None: """ @@ -320,6 +340,8 @@ def __create_forest(self) -> None: AquariaLocations.locations_forest_tr_fp) self.forest_bl = self.__add_region("Kelp Forest bottom left area", AquariaLocations.locations_forest_bl) + self.forest_bl_sc = self.__add_region("Kelp Forest bottom left area, spirit crystals", + AquariaLocations.locations_forest_bl_sc) self.forest_br = self.__add_region("Kelp Forest bottom right area", AquariaLocations.locations_forest_br) self.forest_sprite_cave = self.__add_region("Kelp Forest spirit cave", @@ -375,9 +397,9 @@ def __create_sun_temple(self) -> None: self.sun_temple_r = self.__add_region("Sun Temple right area", AquariaLocations.locations_sun_temple_r) self.sun_temple_boss_path = self.__add_region("Sun Temple before boss area", - AquariaLocations.locations_sun_temple_boss_path) + AquariaLocations.locations_sun_temple_boss_path) self.sun_temple_boss = self.__add_region("Sun Temple boss area", - AquariaLocations.locations_sun_temple_boss) + AquariaLocations.locations_sun_temple_boss) def __create_abyss(self) -> None: """ @@ -388,6 +410,8 @@ def __create_abyss(self) -> None: AquariaLocations.locations_abyss_l) self.abyss_lb = self.__add_region("Abyss left bottom area", AquariaLocations.locations_abyss_lb) self.abyss_r = self.__add_region("Abyss right area", AquariaLocations.locations_abyss_r) + self.abyss_r_transturtle = self.__add_region("Abyss right area, transturtle", + AquariaLocations.locations_abyss_r_transturtle) self.ice_cave = self.__add_region("Ice Cave", AquariaLocations.locations_ice_cave) self.bubble_cave = self.__add_region("Bubble Cave", AquariaLocations.locations_bubble_cave) self.bubble_cave_boss = self.__add_region("Bubble Cave boss area", AquariaLocations.locations_bubble_cave_boss) @@ -407,7 +431,7 @@ def __create_sunken_city(self) -> None: self.sunken_city_r = self.__add_region("Sunken City right area", AquariaLocations.locations_sunken_city_r) self.sunken_city_boss = self.__add_region("Sunken City boss area", - AquariaLocations.locations_sunken_city_boss) + AquariaLocations.locations_sunken_city_boss) def __create_body(self) -> None: """ @@ -427,7 +451,7 @@ def __create_body(self) -> None: self.final_boss_tube = self.__add_region("The Body, final boss area turtle room", AquariaLocations.locations_final_boss_tube) self.final_boss = self.__add_region("The Body, final boss", - AquariaLocations.locations_final_boss) + AquariaLocations.locations_final_boss) self.final_boss_end = self.__add_region("The Body, final boss area", None) def __connect_one_way_regions(self, source_name: str, destination_name: str, @@ -455,8 +479,8 @@ def __connect_home_water_regions(self) -> None: """ Connect entrances of the different regions around `home_water` """ - self.__connect_regions("Menu", "Verse Cave right area", - self.menu, self.verse_cave_r) + self.__connect_one_way_regions("Menu", "Verse Cave right area", + self.menu, self.verse_cave_r) self.__connect_regions("Verse Cave left area", "Verse Cave right area", self.verse_cave_l, self.verse_cave_r) self.__connect_regions("Verse Cave", "Home Water", self.verse_cave_l, self.home_water) @@ -464,7 +488,8 @@ def __connect_home_water_regions(self) -> None: self.__connect_regions("Home Water", "Song Cave", self.home_water, self.song_cave) self.__connect_regions("Home Water", "Home Water, nautilus nest", self.home_water, self.home_water_nautilus, - lambda state: _has_energy_form(state, self.player) and _has_bind_song(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player) and + _has_bind_song(state, self.player)) self.__connect_regions("Home Water", "Home Water transturtle room", self.home_water, self.home_water_transturtle) self.__connect_regions("Home Water", "Energy Temple first area", @@ -472,7 +497,7 @@ def __connect_home_water_regions(self) -> None: lambda state: _has_bind_song(state, self.player)) self.__connect_regions("Home Water", "Energy Temple_altar", self.home_water, self.energy_temple_altar, - lambda state: _has_energy_form(state, self.player) and + lambda state: _has_energy_attack_item(state, self.player) and _has_bind_song(state, self.player)) self.__connect_regions("Energy Temple first area", "Energy Temple second area", self.energy_temple_1, self.energy_temple_2, @@ -482,28 +507,28 @@ def __connect_home_water_regions(self) -> None: lambda state: _has_fish_form(state, self.player)) self.__connect_regions("Energy Temple idol room", "Energy Temple boss area", self.energy_temple_idol, self.energy_temple_boss, - lambda state: _has_energy_form(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player) and + _has_fish_form(state, self.player)) self.__connect_one_way_regions("Energy Temple first area", "Energy Temple boss area", self.energy_temple_1, self.energy_temple_boss, lambda state: _has_beast_form(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) self.__connect_one_way_regions("Energy Temple boss area", "Energy Temple first area", self.energy_temple_boss, self.energy_temple_1, - lambda state: _has_energy_form(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player)) self.__connect_regions("Energy Temple second area", "Energy Temple third area", self.energy_temple_2, self.energy_temple_3, - lambda state: _has_bind_song(state, self.player) and - _has_energy_form(state, self.player)) + lambda state: _has_energy_form(state, self.player)) self.__connect_regions("Energy Temple boss area", "Energy Temple blaster room", self.energy_temple_boss, self.energy_temple_blaster_room, lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) self.__connect_regions("Energy Temple first area", "Energy Temple blaster room", self.energy_temple_1, self.energy_temple_blaster_room, lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player) and - _has_energy_form(state, self.player) and + _has_energy_attack_item(state, self.player) and _has_beast_form(state, self.player)) self.__connect_regions("Home Water", "Open Water top left area", self.home_water, self.openwater_tl) @@ -520,7 +545,7 @@ def __connect_open_water_regions(self) -> None: self.openwater_tl, self.forest_br) self.__connect_regions("Open Water top right area", "Open Water top right area, turtle room", self.openwater_tr, self.openwater_tr_turtle, - lambda state: _has_beast_form(state, self.player)) + lambda state: _has_beast_form_or_arnassi_armor(state, self.player)) self.__connect_regions("Open Water top right area", "Open Water bottom right area", self.openwater_tr, self.openwater_br) self.__connect_regions("Open Water top right area", "Mithalas City", @@ -529,10 +554,9 @@ def __connect_open_water_regions(self) -> None: self.openwater_tr, self.veil_bl) self.__connect_one_way_regions("Open Water top right area", "Veil bottom right", self.openwater_tr, self.veil_br, - lambda state: _has_beast_form(state, self.player)) + lambda state: _has_beast_form_or_arnassi_armor(state, self.player)) self.__connect_one_way_regions("Veil bottom right", "Open Water top right area", - self.veil_br, self.openwater_tr, - lambda state: _has_beast_form(state, self.player)) + self.veil_br, self.openwater_tr) self.__connect_regions("Open Water bottom left area", "Open Water bottom right area", self.openwater_bl, self.openwater_br) self.__connect_regions("Open Water bottom left area", "Skeleton path", @@ -551,10 +575,14 @@ def __connect_open_water_regions(self) -> None: self.arnassi, self.openwater_br) self.__connect_regions("Arnassi", "Arnassi path", self.arnassi, self.arnassi_path) + self.__connect_regions("Arnassi ruins, transturtle area", "Arnassi path", + self.arnassi_cave_transturtle, self.arnassi_path, + lambda state: _has_fish_form(state, self.player)) self.__connect_one_way_regions("Arnassi path", "Arnassi crab boss area", self.arnassi_path, self.arnassi_crab_boss, - lambda state: _has_beast_form(state, self.player) and - _has_energy_form(state, self.player)) + lambda state: _has_beast_form_or_arnassi_armor(state, self.player) and + (_has_energy_attack_item(state, self.player) or + _has_nature_form(state, self.player))) self.__connect_one_way_regions("Arnassi crab boss area", "Arnassi path", self.arnassi_crab_boss, self.arnassi_path) @@ -564,61 +592,62 @@ def __connect_mithalas_regions(self) -> None: """ self.__connect_one_way_regions("Mithalas City", "Mithalas City top path", self.mithalas_city, self.mithalas_city_top_path, - lambda state: _has_beast_form(state, self.player)) + lambda state: _has_beast_form_or_arnassi_armor(state, self.player)) self.__connect_one_way_regions("Mithalas City_top_path", "Mithalas City", self.mithalas_city_top_path, self.mithalas_city) self.__connect_regions("Mithalas City", "Mithalas City home with fishpass", self.mithalas_city, self.mithalas_city_fishpass, lambda state: _has_fish_form(state, self.player)) self.__connect_regions("Mithalas City", "Mithalas castle", - self.mithalas_city, self.cathedral_l, - lambda state: _has_fish_form(state, self.player)) + self.mithalas_city, self.cathedral_l) self.__connect_one_way_regions("Mithalas City top path", "Mithalas castle, flower tube", self.mithalas_city_top_path, self.cathedral_l_tube, lambda state: _has_nature_form(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) self.__connect_one_way_regions("Mithalas castle, flower tube area", "Mithalas City top path", self.cathedral_l_tube, self.mithalas_city_top_path, - lambda state: _has_beast_form(state, self.player) and - _has_nature_form(state, self.player)) + lambda state: _has_nature_form(state, self.player)) self.__connect_one_way_regions("Mithalas castle flower tube area", "Mithalas castle, spirit crystals", - self.cathedral_l_tube, self.cathedral_l_sc, - lambda state: _has_spirit_form(state, self.player)) + self.cathedral_l_tube, self.cathedral_l_sc, + lambda state: _has_spirit_form(state, self.player)) self.__connect_one_way_regions("Mithalas castle_flower tube area", "Mithalas castle", - self.cathedral_l_tube, self.cathedral_l, - lambda state: _has_spirit_form(state, self.player)) + self.cathedral_l_tube, self.cathedral_l, + lambda state: _has_spirit_form(state, self.player)) self.__connect_regions("Mithalas castle", "Mithalas castle, spirit crystals", self.cathedral_l, self.cathedral_l_sc, lambda state: _has_spirit_form(state, self.player)) - self.__connect_regions("Mithalas castle", "Cathedral boss left area", - self.cathedral_l, self.cathedral_boss_l, - lambda state: _has_beast_form(state, self.player) and - _has_energy_form(state, self.player) and - _has_bind_song(state, self.player)) + self.__connect_one_way_regions("Mithalas castle", "Cathedral boss right area", + self.cathedral_l, self.cathedral_boss_r, + lambda state: _has_beast_form(state, self.player)) + self.__connect_one_way_regions("Cathedral boss left area", "Mithalas castle", + self.cathedral_boss_l, self.cathedral_l, + lambda state: _has_beast_form(state, self.player)) self.__connect_regions("Mithalas castle", "Mithalas Cathedral underground", self.cathedral_l, self.cathedral_underground, - lambda state: _has_beast_form(state, self.player) and - _has_bind_song(state, self.player)) - self.__connect_regions("Mithalas castle", "Mithalas Cathedral", - self.cathedral_l, self.cathedral_r, - lambda state: _has_bind_song(state, self.player) and - _has_energy_form(state, self.player)) - self.__connect_regions("Mithalas Cathedral", "Mithalas Cathedral underground", - self.cathedral_r, self.cathedral_underground, - lambda state: _has_energy_form(state, self.player)) - self.__connect_one_way_regions("Mithalas Cathedral underground", "Cathedral boss left area", - self.cathedral_underground, self.cathedral_boss_r, - lambda state: _has_energy_form(state, self.player) and - _has_bind_song(state, self.player)) - self.__connect_one_way_regions("Cathedral boss left area", "Mithalas Cathedral underground", + lambda state: _has_beast_form(state, self.player)) + self.__connect_one_way_regions("Mithalas castle", "Mithalas Cathedral", + self.cathedral_l, self.cathedral_r, + lambda state: _has_bind_song(state, self.player) and + _has_energy_attack_item(state, self.player)) + self.__connect_one_way_regions("Mithalas Cathedral", "Mithalas Cathedral underground", + self.cathedral_r, self.cathedral_underground) + self.__connect_one_way_regions("Mithalas Cathedral underground", "Mithalas Cathedral", + self.cathedral_underground, self.cathedral_r, + lambda state: _has_beast_form(state, self.player) and + _has_energy_attack_item(state, self.player)) + self.__connect_one_way_regions("Mithalas Cathedral underground", "Cathedral boss right area", + self.cathedral_underground, self.cathedral_boss_r) + self.__connect_one_way_regions("Cathedral boss right area", "Mithalas Cathedral underground", self.cathedral_boss_r, self.cathedral_underground, lambda state: _has_beast_form(state, self.player)) - self.__connect_regions("Cathedral boss right area", "Cathedral boss left area", + self.__connect_one_way_regions("Cathedral boss right area", "Cathedral boss left area", self.cathedral_boss_r, self.cathedral_boss_l, lambda state: _has_bind_song(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) + self.__connect_one_way_regions("Cathedral boss left area", "Cathedral boss right area", + self.cathedral_boss_l, self.cathedral_boss_r) def __connect_forest_regions(self) -> None: """ @@ -628,6 +657,12 @@ def __connect_forest_regions(self) -> None: self.forest_br, self.veil_bl) self.__connect_regions("Forest bottom right", "Forest bottom left area", self.forest_br, self.forest_bl) + self.__connect_one_way_regions("Forest bottom left area", "Forest bottom left area, spirit crystals", + self.forest_bl, self.forest_bl_sc, + lambda state: _has_energy_attack_item(state, self.player) or + _has_fish_form(state, self.player)) + self.__connect_one_way_regions("Forest bottom left area, spirit crystals", "Forest bottom left area", + self.forest_bl_sc, self.forest_bl) self.__connect_regions("Forest bottom right", "Forest top right area", self.forest_br, self.forest_tr) self.__connect_regions("Forest bottom left area", "Forest fish cave", @@ -641,7 +676,7 @@ def __connect_forest_regions(self) -> None: self.forest_tl, self.forest_tl_fp, lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player) and - _has_energy_form(state, self.player) and + _has_energy_attack_item(state, self.player) and _has_fish_form(state, self.player)) self.__connect_regions("Forest top left area", "Forest top right area", self.forest_tl, self.forest_tr) @@ -649,7 +684,7 @@ def __connect_forest_regions(self) -> None: self.forest_tl, self.forest_boss_entrance) self.__connect_regions("Forest boss area", "Forest boss entrance", self.forest_boss, self.forest_boss_entrance, - lambda state: _has_energy_form(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player)) self.__connect_regions("Forest top right area", "Forest top right area fish pass", self.forest_tr, self.forest_tr_fp, lambda state: _has_fish_form(state, self.player)) @@ -663,7 +698,7 @@ def __connect_forest_regions(self) -> None: self.__connect_regions("Fermog cave", "Fermog boss", self.mermog_cave, self.mermog_boss, lambda state: _has_beast_form(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) def __connect_veil_regions(self) -> None: """ @@ -681,8 +716,7 @@ def __connect_veil_regions(self) -> None: self.veil_b_sc, self.veil_br, lambda state: _has_spirit_form(state, self.player)) self.__connect_regions("Veil bottom right", "Veil top left area", - self.veil_br, self.veil_tl, - lambda state: _has_beast_form(state, self.player)) + self.veil_br, self.veil_tl) self.__connect_regions("Veil top left area", "Veil_top left area, fish pass", self.veil_tl, self.veil_tl_fp, lambda state: _has_fish_form(state, self.player)) @@ -691,20 +725,25 @@ def __connect_veil_regions(self) -> None: self.__connect_regions("Veil top left area", "Turtle cave", self.veil_tl, self.turtle_cave) self.__connect_regions("Turtle cave", "Turtle cave Bubble Cliff", - self.turtle_cave, self.turtle_cave_bubble, - lambda state: _has_beast_form(state, self.player)) + self.turtle_cave, self.turtle_cave_bubble) self.__connect_regions("Veil right of sun temple", "Sun Temple right area", self.veil_tr_r, self.sun_temple_r) - self.__connect_regions("Sun Temple right area", "Sun Temple left area", - self.sun_temple_r, self.sun_temple_l, - lambda state: _has_bind_song(state, self.player)) + self.__connect_one_way_regions("Sun Temple right area", "Sun Temple left area", + self.sun_temple_r, self.sun_temple_l, + lambda state: _has_bind_song(state, self.player) or + _has_light(state, self.player)) + self.__connect_one_way_regions("Sun Temple left area", "Sun Temple right area", + self.sun_temple_l, self.sun_temple_r, + lambda state: _has_light(state, self.player)) self.__connect_regions("Sun Temple left area", "Veil left of sun temple", self.sun_temple_l, self.veil_tr_l) self.__connect_regions("Sun Temple left area", "Sun Temple before boss area", - self.sun_temple_l, self.sun_temple_boss_path) + self.sun_temple_l, self.sun_temple_boss_path, + lambda state: _has_light(state, self.player) or + _has_sun_crystal(state, self.player)) self.__connect_regions("Sun Temple before boss area", "Sun Temple boss area", self.sun_temple_boss_path, self.sun_temple_boss, - lambda state: _has_energy_form(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player)) self.__connect_one_way_regions("Sun Temple boss area", "Veil left of sun temple", self.sun_temple_boss, self.veil_tr_l) self.__connect_regions("Veil left of sun temple", "Octo cave top path", @@ -712,7 +751,7 @@ def __connect_veil_regions(self) -> None: lambda state: _has_fish_form(state, self.player) and _has_sun_form(state, self.player) and _has_beast_form(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) self.__connect_regions("Veil left of sun temple", "Octo cave bottom path", self.veil_tr_l, self.octo_cave_b, lambda state: _has_fish_form(state, self.player)) @@ -728,16 +767,22 @@ def __connect_abyss_regions(self) -> None: self.abyss_lb, self.sunken_city_r, lambda state: _has_li(state, self.player)) self.__connect_one_way_regions("Abyss left bottom area", "Body center area", - self.abyss_lb, self.body_c, - lambda state: _has_tongue_cleared(state, self.player)) + self.abyss_lb, self.body_c, + lambda state: _has_tongue_cleared(state, self.player)) self.__connect_one_way_regions("Body center area", "Abyss left bottom area", - self.body_c, self.abyss_lb) + self.body_c, self.abyss_lb) self.__connect_regions("Abyss left area", "King jellyfish cave", self.abyss_l, self.king_jellyfish_cave, - lambda state: _has_energy_form(state, self.player) and - _has_beast_form(state, self.player)) + lambda state: (_has_energy_form(state, self.player) and + _has_beast_form(state, self.player)) or + _has_dual_form(state, self.player)) self.__connect_regions("Abyss left area", "Abyss right area", self.abyss_l, self.abyss_r) + self.__connect_one_way_regions("Abyss right area", "Abyss right area, transturtle", + self.abyss_r, self.abyss_r_transturtle) + self.__connect_one_way_regions("Abyss right area, transturtle", "Abyss right area", + self.abyss_r_transturtle, self.abyss_r, + lambda state: _has_light(state, self.player)) self.__connect_regions("Abyss right area", "Inside the whale", self.abyss_r, self.whale, lambda state: _has_spirit_form(state, self.player) and @@ -747,13 +792,14 @@ def __connect_abyss_regions(self) -> None: lambda state: _has_spirit_form(state, self.player) and _has_sun_form(state, self.player) and _has_bind_song(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) self.__connect_regions("Abyss right area", "Ice Cave", self.abyss_r, self.ice_cave, lambda state: _has_spirit_form(state, self.player)) - self.__connect_regions("Abyss right area", "Bubble Cave", + self.__connect_regions("Ice cave", "Bubble Cave", self.ice_cave, self.bubble_cave, - lambda state: _has_beast_form(state, self.player)) + lambda state: _has_beast_form(state, self.player) or + _has_hot_soup(state, self.player)) self.__connect_regions("Bubble Cave boss area", "Bubble Cave", self.bubble_cave, self.bubble_cave_boss, lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player) @@ -772,7 +818,7 @@ def __connect_sunken_city_regions(self) -> None: self.sunken_city_l, self.sunken_city_boss, lambda state: _has_beast_form(state, self.player) and _has_sun_form(state, self.player) and - _has_energy_form(state, self.player) and + _has_energy_attack_item(state, self.player) and _has_bind_song(state, self.player)) def __connect_body_regions(self) -> None: @@ -780,11 +826,13 @@ def __connect_body_regions(self) -> None: Connect entrances of the different regions around The Body """ self.__connect_regions("Body center area", "Body left area", - self.body_c, self.body_l) + self.body_c, self.body_l, + lambda state: _has_energy_form(state, self.player)) self.__connect_regions("Body center area", "Body right area top path", self.body_c, self.body_rt) self.__connect_regions("Body center area", "Body right area bottom path", - self.body_c, self.body_rb) + self.body_c, self.body_rb, + lambda state: _has_energy_form(state, self.player)) self.__connect_regions("Body center area", "Body bottom area", self.body_c, self.body_b, lambda state: _has_dual_form(state, self.player)) @@ -803,22 +851,12 @@ def __connect_body_regions(self) -> None: self.__connect_one_way_regions("final boss third form area", "final boss end", self.final_boss, self.final_boss_end) - def __connect_transturtle(self, item_source: str, item_target: str, region_source: Region, region_target: Region, - rule=None) -> None: + def __connect_transturtle(self, item_source: str, item_target: str, region_source: Region, + region_target: Region) -> None: """Connect a single transturtle to another one""" if item_source != item_target: - if rule is None: - self.__connect_one_way_regions(item_source, item_target, region_source, region_target, - lambda state: state.has(item_target, self.player)) - else: - self.__connect_one_way_regions(item_source, item_target, region_source, region_target, rule) - - def __connect_arnassi_path_transturtle(self, item_source: str, item_target: str, region_source: Region, - region_target: Region) -> None: - """Connect the Arnassi Ruins transturtle to another one""" - self.__connect_one_way_regions(item_source, item_target, region_source, region_target, - lambda state: state.has(item_target, self.player) and - _has_fish_form(state, self.player)) + self.__connect_one_way_regions(item_source, item_target, region_source, region_target, + lambda state: state.has(item_target, self.player)) def _connect_transturtle_to_other(self, item: str, region: Region) -> None: """Connect a single transturtle to all others""" @@ -827,24 +865,10 @@ def _connect_transturtle_to_other(self, item: str, region: Region) -> None: self.__connect_transturtle(item, "Transturtle Open Water top right", region, self.openwater_tr_turtle) self.__connect_transturtle(item, "Transturtle Forest bottom left", region, self.forest_bl) self.__connect_transturtle(item, "Transturtle Home Water", region, self.home_water_transturtle) - self.__connect_transturtle(item, "Transturtle Abyss right", region, self.abyss_r) + self.__connect_transturtle(item, "Transturtle Abyss right", region, self.abyss_r_transturtle) self.__connect_transturtle(item, "Transturtle Final Boss", region, self.final_boss_tube) self.__connect_transturtle(item, "Transturtle Simon Says", region, self.simon) - self.__connect_transturtle(item, "Transturtle Arnassi Ruins", region, self.arnassi_path, - lambda state: state.has("Transturtle Arnassi Ruins", self.player) and - _has_fish_form(state, self.player)) - - def _connect_arnassi_path_transturtle_to_other(self, item: str, region: Region) -> None: - """Connect the Arnassi Ruins transturtle to all others""" - self.__connect_arnassi_path_transturtle(item, "Transturtle Veil top left", region, self.veil_tl) - self.__connect_arnassi_path_transturtle(item, "Transturtle Veil top right", region, self.veil_tr_l) - self.__connect_arnassi_path_transturtle(item, "Transturtle Open Water top right", region, - self.openwater_tr_turtle) - self.__connect_arnassi_path_transturtle(item, "Transturtle Forest bottom left", region, self.forest_bl) - self.__connect_arnassi_path_transturtle(item, "Transturtle Home Water", region, self.home_water_transturtle) - self.__connect_arnassi_path_transturtle(item, "Transturtle Abyss right", region, self.abyss_r) - self.__connect_arnassi_path_transturtle(item, "Transturtle Final Boss", region, self.final_boss_tube) - self.__connect_arnassi_path_transturtle(item, "Transturtle Simon Says", region, self.simon) + self.__connect_transturtle(item, "Transturtle Arnassi Ruins", region, self.arnassi_cave_transturtle) def __connect_transturtles(self) -> None: """Connect every transturtle with others""" @@ -853,10 +877,10 @@ def __connect_transturtles(self) -> None: self._connect_transturtle_to_other("Transturtle Open Water top right", self.openwater_tr_turtle) self._connect_transturtle_to_other("Transturtle Forest bottom left", self.forest_bl) self._connect_transturtle_to_other("Transturtle Home Water", self.home_water_transturtle) - self._connect_transturtle_to_other("Transturtle Abyss right", self.abyss_r) + self._connect_transturtle_to_other("Transturtle Abyss right", self.abyss_r_transturtle) self._connect_transturtle_to_other("Transturtle Final Boss", self.final_boss_tube) self._connect_transturtle_to_other("Transturtle Simon Says", self.simon) - self._connect_arnassi_path_transturtle_to_other("Transturtle Arnassi Ruins", self.arnassi_path) + self._connect_transturtle_to_other("Transturtle Arnassi Ruins", self.arnassi_cave_transturtle) def connect_regions(self) -> None: """ @@ -893,7 +917,7 @@ def __add_event_big_bosses(self) -> None: self.__add_event_location(self.energy_temple_boss, "Beating Fallen God", "Fallen God beated") - self.__add_event_location(self.cathedral_boss_r, + self.__add_event_location(self.cathedral_boss_l, "Beating Mithalan God", "Mithalan God beated") self.__add_event_location(self.forest_boss, @@ -970,8 +994,9 @@ def __adjusting_urns_rules(self) -> None: """Since Urns need to be broken, add a damaging item to rules""" add_rule(self.multiworld.get_location("Open Water top right area, first urn in the Mithalas exit", self.player), lambda state: _has_damaging_item(state, self.player)) - add_rule(self.multiworld.get_location("Open Water top right area, second urn in the Mithalas exit", self.player), - lambda state: _has_damaging_item(state, self.player)) + add_rule( + self.multiworld.get_location("Open Water top right area, second urn in the Mithalas exit", self.player), + lambda state: _has_damaging_item(state, self.player)) add_rule(self.multiworld.get_location("Open Water top right area, third urn in the Mithalas exit", self.player), lambda state: _has_damaging_item(state, self.player)) add_rule(self.multiworld.get_location("Mithalas City, first urn in one of the homes", self.player), @@ -1019,66 +1044,46 @@ def __adjusting_soup_rules(self) -> None: Modify rules for location that need soup """ add_rule(self.multiworld.get_location("Turtle cave, Urchin Costume", self.player), - lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) - add_rule(self.multiworld.get_location("Sun Worm path, first cliff bulb", self.player), - lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) - add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player), - lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) + lambda state: _has_hot_soup(state, self.player)) add_rule(self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall", self.player), - lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) + lambda state: _has_beast_and_soup_form(state, self.player)) def __adjusting_under_rock_location(self) -> None: """ Modify rules implying bind song needed for bulb under rocks """ add_rule(self.multiworld.get_location("Home Water, bulb under the rock in the left path from the Verse Cave", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Verse Cave left area, bulb under the rock at the end of the path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Naija's Home, bulb under the rock at the right of the main path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Song Cave, bulb under the rock in the path to the singing statues", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Song Cave, bulb under the rock close to the song door", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Energy Temple second area, bulb under the rock", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Open Water top left area, bulb under the rock in the right path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Open Water top left area, bulb under the rock in the left path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Kelp Forest top right area, bulb under the rock in the right path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("The Veil top left area, bulb under the rock in the top right path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Abyss right area, bulb behind the rock in the whale room", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Abyss right area, bulb in the middle path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("The Veil top left area, bulb under the rock in the top right path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) def __adjusting_light_in_dark_place_rules(self) -> None: add_rule(self.multiworld.get_location("Kelp Forest top right area, Black Pearl", self.player), lambda state: _has_light(state, self.player)) add_rule(self.multiworld.get_location("Kelp Forest bottom right area, Odd Container", self.player), lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Veil top left to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Open Water top right to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Veil top right to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Forest bottom left to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Home Water to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Final Boss to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Simon Says to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Arnassi Ruins to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) add_rule(self.multiworld.get_entrance("Body center area to Abyss left bottom area", self.player), lambda state: _has_light(state, self.player)) add_rule(self.multiworld.get_entrance("Veil left of sun temple to Octo cave top path", self.player), @@ -1097,12 +1102,14 @@ def __adjusting_light_in_dark_place_rules(self) -> None: def __adjusting_manual_rules(self) -> None: add_rule(self.multiworld.get_location("Mithalas Cathedral, Mithalan Dress", self.player), lambda state: _has_beast_form(state, self.player)) - add_rule(self.multiworld.get_location("Open Water bottom left area, bulb inside the lowest fish pass", self.player), - lambda state: _has_fish_form(state, self.player)) + add_rule( + self.multiworld.get_location("Open Water bottom left area, bulb inside the lowest fish pass", self.player), + lambda state: _has_fish_form(state, self.player)) add_rule(self.multiworld.get_location("Kelp Forest bottom left area, Walker Baby", self.player), lambda state: _has_spirit_form(state, self.player)) - add_rule(self.multiworld.get_location("The Veil top left area, bulb hidden behind the blocking rock", self.player), - lambda state: _has_bind_song(state, self.player)) + add_rule( + self.multiworld.get_location("The Veil top left area, bulb hidden behind the blocking rock", self.player), + lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Turtle cave, Turtle Egg", self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Abyss left area, bulb in the bottom fish pass", self.player), @@ -1114,103 +1121,119 @@ def __adjusting_manual_rules(self) -> None: add_rule(self.multiworld.get_location("Verse Cave right area, Big Seed", self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Arnassi Ruins, Song Plant Spore", self.player), - lambda state: _has_beast_form(state, self.player)) + lambda state: _has_beast_form_or_arnassi_armor(state, self.player)) add_rule(self.multiworld.get_location("Energy Temple first area, bulb in the bottom room blocked by a rock", - self.player), lambda state: _has_energy_form(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Home Water, bulb in the bottom left room", self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Home Water, bulb in the path below Nautilus Prime", self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Naija's Home, bulb after the energy door", self.player), - lambda state: _has_energy_form(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player)) add_rule(self.multiworld.get_location("Abyss right area, bulb behind the rock in the whale room", self.player), lambda state: _has_spirit_form(state, self.player) and _has_sun_form(state, self.player)) add_rule(self.multiworld.get_location("Arnassi Ruins, Arnassi Armor", self.player), - lambda state: _has_fish_form(state, self.player) and - _has_spirit_form(state, self.player)) + lambda state: _has_fish_form(state, self.player) or + _has_beast_and_soup_form(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, urn inside a home fish pass", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, urn in the Castle flower tube entrance", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location( + "The Veil top right area, bulb in the middle of the wall jump cliff", self.player + ), lambda state: _has_beast_form_or_arnassi_armor(state, self.player)) + add_rule(self.multiworld.get_location("Kelp Forest top left area, Jelly Egg", self.player), + lambda state: _has_beast_form(state, self.player)) + add_rule(self.multiworld.get_location("Sun Worm path, first cliff bulb", self.player), + lambda state: state.has("Sun God beated", self.player)) + add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player), + lambda state: state.has("Sun God beated", self.player)) + add_rule(self.multiworld.get_location("The Body center area, breaking Li's cage", self.player), + lambda state: _has_tongue_cleared(state, self.player)) def __no_progression_hard_or_hidden_location(self) -> None: self.multiworld.get_location("Energy Temple boss area, Fallen God Tooth", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Mithalas boss area, beating Mithalan God", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Kelp Forest boss area, beating Drunian God", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sun Temple boss area, beating Sun God", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sunken City, bulb on top of the boss area", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Home Water, Nautilus Egg", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Energy Temple blaster room, Blaster Egg", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Mithalas City Castle, beating the Priests", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Mermog cave, Piranha Egg", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Octopus Cave, Dumbo Egg", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("King Jellyfish Cave, bulb in the right path from King Jelly", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("King Jellyfish Cave, Jellyfish Costume", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Final Boss area, bulb in the boss third form room", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sun Worm path, first cliff bulb", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sun Worm path, second cliff bulb", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Bubble Cave, bulb in the left cave wall", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Bubble Cave, bulb in the right cave wall (behind the ice crystal)", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Bubble Cave, Verse Egg", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Kelp Forest bottom left area, bulb close to the spirit crystals", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Kelp Forest bottom left area, Walker Baby", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sun Temple, Sun Key", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("The Body bottom area, Mutant Costume", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sun Temple, bulb in the hidden room of the right part", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Arnassi Ruins, Arnassi Armor", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression def adjusting_rules(self, options: AquariaOptions) -> None: """ Modify rules for single location or optional rules """ + self.multiworld.get_entrance("Before Final Boss to Final Boss", self.player) self.__adjusting_urns_rules() self.__adjusting_crates_rules() self.__adjusting_soup_rules() @@ -1234,7 +1257,7 @@ def adjusting_rules(self, options: AquariaOptions) -> None: lambda state: _has_bind_song(state, self.player)) if options.unconfine_home_water.value in [0, 2]: add_rule(self.multiworld.get_entrance("Home Water to Open Water top left area", self.player), - lambda state: _has_bind_song(state, self.player) and _has_energy_form(state, self.player)) + lambda state: _has_bind_song(state, self.player) and _has_energy_attack_item(state, self.player)) if options.early_energy_form: self.multiworld.early_items[self.player]["Energy form"] = 1 @@ -1274,6 +1297,7 @@ def __add_open_water_regions_to_world(self) -> None: self.multiworld.regions.append(self.arnassi) self.multiworld.regions.append(self.arnassi_path) self.multiworld.regions.append(self.arnassi_crab_boss) + self.multiworld.regions.append(self.arnassi_cave_transturtle) self.multiworld.regions.append(self.simon) def __add_mithalas_regions_to_world(self) -> None: @@ -1300,6 +1324,7 @@ def __add_forest_regions_to_world(self) -> None: self.multiworld.regions.append(self.forest_tr) self.multiworld.regions.append(self.forest_tr_fp) self.multiworld.regions.append(self.forest_bl) + self.multiworld.regions.append(self.forest_bl_sc) self.multiworld.regions.append(self.forest_br) self.multiworld.regions.append(self.forest_boss) self.multiworld.regions.append(self.forest_boss_entrance) @@ -1337,6 +1362,7 @@ def __add_abyss_regions_to_world(self) -> None: self.multiworld.regions.append(self.abyss_l) self.multiworld.regions.append(self.abyss_lb) self.multiworld.regions.append(self.abyss_r) + self.multiworld.regions.append(self.abyss_r_transturtle) self.multiworld.regions.append(self.ice_cave) self.multiworld.regions.append(self.bubble_cave) self.multiworld.regions.append(self.bubble_cave_boss) diff --git a/worlds/aquaria/test/__init__.py b/worlds/aquaria/test/__init__.py index 029db691b66b..8c4f64c3452c 100644 --- a/worlds/aquaria/test/__init__.py +++ b/worlds/aquaria/test/__init__.py @@ -141,7 +141,7 @@ "Sun Temple, bulb at the top of the high dark room", "Sun Temple, Golden Gear", "Sun Temple, first bulb of the temple", - "Sun Temple, bulb on the left part", + "Sun Temple, bulb on the right part", "Sun Temple, bulb in the hidden room of the right part", "Sun Temple, Sun Key", "Sun Worm path, first path bulb", diff --git a/worlds/aquaria/test/test_beast_form_access.py b/worlds/aquaria/test/test_beast_form_access.py index 0efc3e7388fe..c09586269d38 100644 --- a/worlds/aquaria/test/test_beast_form_access.py +++ b/worlds/aquaria/test/test_beast_form_access.py @@ -13,36 +13,16 @@ class BeastFormAccessTest(AquariaTestBase): def test_beast_form_location(self) -> None: """Test locations that require beast form""" locations = [ - "Mithalas City Castle, beating the Priests", - "Arnassi Ruins, Crab Armor", - "Arnassi Ruins, Song Plant Spore", - "Mithalas City, first bulb at the end of the top path", - "Mithalas City, second bulb at the end of the top path", - "Mithalas City, bulb in the top path", - "Mithalas City, Mithalas Pot", - "Mithalas City, urn in the Castle flower tube entrance", "Mermog cave, Piranha Egg", + "Kelp Forest top left area, Jelly Egg", "Mithalas Cathedral, Mithalan Dress", - "Turtle cave, bulb in Bubble Cliff", - "Turtle cave, Urchin Costume", - "Sun Worm path, first cliff bulb", - "Sun Worm path, second cliff bulb", "The Veil top right area, bulb at the top of the waterfall", - "Bubble Cave, bulb in the left cave wall", - "Bubble Cave, bulb in the right cave wall (behind the ice crystal)", - "Bubble Cave, Verse Egg", "Sunken City, bulb on top of the boss area", "Octopus Cave, Dumbo Egg", "Beating the Golem", "Beating Mergog", - "Beating Crabbius Maximus", "Beating Octopus Prime", - "Beating Mantis Shrimp Prime", - "King Jellyfish Cave, Jellyfish Costume", - "King Jellyfish Cave, bulb in the right path from King Jelly", - "Beating King Jellyfish God Prime", - "Beating Mithalan priests", - "Sunken City cleared" + "Sunken City cleared", ] items = [["Beast form"]] self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_beast_form_or_arnassi_armor_access.py b/worlds/aquaria/test/test_beast_form_or_arnassi_armor_access.py new file mode 100644 index 000000000000..fa4c6923400a --- /dev/null +++ b/worlds/aquaria/test/test_beast_form_or_arnassi_armor_access.py @@ -0,0 +1,39 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the beast form or arnassi armor +""" + +from . import AquariaTestBase + + +class BeastForArnassiArmormAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the beast form or arnassi armor""" + + def test_beast_form_arnassi_armor_location(self) -> None: + """Test locations that require beast form or arnassi armor""" + locations = [ + "Mithalas City Castle, beating the Priests", + "Arnassi Ruins, Crab Armor", + "Arnassi Ruins, Song Plant Spore", + "Mithalas City, first bulb at the end of the top path", + "Mithalas City, second bulb at the end of the top path", + "Mithalas City, bulb in the top path", + "Mithalas City, Mithalas Pot", + "Mithalas City, urn in the Castle flower tube entrance", + "Mermog cave, Piranha Egg", + "Mithalas Cathedral, Mithalan Dress", + "Kelp Forest top left area, Jelly Egg", + "The Veil top right area, bulb in the middle of the wall jump cliff", + "The Veil top right area, bulb at the top of the waterfall", + "Sunken City, bulb on top of the boss area", + "Octopus Cave, Dumbo Egg", + "Beating the Golem", + "Beating Mergog", + "Beating Crabbius Maximus", + "Beating Octopus Prime", + "Beating Mithalan priests", + "Sunken City cleared" + ] + items = [["Beast form", "Arnassi Armor"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_energy_form_access.py b/worlds/aquaria/test/test_energy_form_access.py index 82d8e89a0066..b443166823bc 100644 --- a/worlds/aquaria/test/test_energy_form_access.py +++ b/worlds/aquaria/test/test_energy_form_access.py @@ -17,55 +17,16 @@ class EnergyFormAccessTest(AquariaTestBase): def test_energy_form_location(self) -> None: """Test locations that require Energy form""" locations = [ - "Home Water, Nautilus Egg", - "Naija's Home, bulb after the energy door", - "Energy Temple first area, bulb in the bottom room blocked by a rock", "Energy Temple second area, bulb under the rock", - "Energy Temple bottom entrance, Krotite Armor", "Energy Temple third area, bulb in the bottom path", - "Energy Temple boss area, Fallen God Tooth", - "Energy Temple blaster room, Blaster Egg", - "Mithalas City Castle, beating the Priests", - "Mithalas Cathedral, first urn in the top right room", - "Mithalas Cathedral, second urn in the top right room", - "Mithalas Cathedral, third urn in the top right room", - "Mithalas Cathedral, urn in the flesh room with fleas", - "Mithalas Cathedral, first urn in the bottom right path", - "Mithalas Cathedral, second urn in the bottom right path", - "Mithalas Cathedral, urn behind the flesh vein", - "Mithalas Cathedral, urn in the top left eyes boss room", - "Mithalas Cathedral, first urn in the path behind the flesh vein", - "Mithalas Cathedral, second urn in the path behind the flesh vein", - "Mithalas Cathedral, third urn in the path behind the flesh vein", - "Mithalas Cathedral, fourth urn in the top right room", - "Mithalas Cathedral, Mithalan Dress", - "Mithalas Cathedral, urn below the left entrance", - "Mithalas boss area, beating Mithalan God", - "Kelp Forest top left area, bulb close to the Verse Egg", - "Kelp Forest top left area, Verse Egg", - "Kelp Forest boss area, beating Drunian God", - "Mermog cave, Piranha Egg", - "Octopus Cave, Dumbo Egg", - "Sun Temple boss area, beating Sun God", - "Arnassi Ruins, Crab Armor", - "King Jellyfish Cave, bulb in the right path from King Jelly", - "King Jellyfish Cave, Jellyfish Costume", - "Sunken City, bulb on top of the boss area", + "The Body left area, first bulb in the top face room", + "The Body left area, second bulb in the top face room", + "The Body left area, bulb below the water stream", + "The Body left area, bulb in the top path to the top face room", + "The Body left area, bulb in the bottom face room", + "The Body right area, bulb in the top path to the bottom face room", + "The Body right area, bulb in the bottom face room", "Final Boss area, bulb in the boss third form room", - "Beating Fallen God", - "Beating Mithalan God", - "Beating Drunian God", - "Beating Sun God", - "Beating the Golem", - "Beating Nautilus Prime", - "Beating Blaster Peg Prime", - "Beating Mergog", - "Beating Mithalan priests", - "Beating Octopus Prime", - "Beating Crabbius Maximus", - "Beating King Jellyfish God Prime", - "First secret", - "Sunken City cleared", "Objective complete", ] items = [["Energy form"]] diff --git a/worlds/aquaria/test/test_energy_form_or_dual_form_access.py b/worlds/aquaria/test/test_energy_form_or_dual_form_access.py new file mode 100644 index 000000000000..8a765bc4e4e2 --- /dev/null +++ b/worlds/aquaria/test/test_energy_form_or_dual_form_access.py @@ -0,0 +1,92 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the energy form and dual form (and Li) +""" + +from . import AquariaTestBase + + +class EnergyFormDualFormAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the energy form and dual form (and Li)""" + options = { + "early_energy_form": False, + } + + def test_energy_form_or_dual_form_location(self) -> None: + """Test locations that require Energy form or dual form""" + locations = [ + "Naija's Home, bulb after the energy door", + "Home Water, Nautilus Egg", + "Energy Temple second area, bulb under the rock", + "Energy Temple bottom entrance, Krotite Armor", + "Energy Temple third area, bulb in the bottom path", + "Energy Temple blaster room, Blaster Egg", + "Energy Temple boss area, Fallen God Tooth", + "Mithalas City Castle, beating the Priests", + "Mithalas boss area, beating Mithalan God", + "Mithalas Cathedral, first urn in the top right room", + "Mithalas Cathedral, second urn in the top right room", + "Mithalas Cathedral, third urn in the top right room", + "Mithalas Cathedral, urn in the flesh room with fleas", + "Mithalas Cathedral, first urn in the bottom right path", + "Mithalas Cathedral, second urn in the bottom right path", + "Mithalas Cathedral, urn behind the flesh vein", + "Mithalas Cathedral, urn in the top left eyes boss room", + "Mithalas Cathedral, first urn in the path behind the flesh vein", + "Mithalas Cathedral, second urn in the path behind the flesh vein", + "Mithalas Cathedral, third urn in the path behind the flesh vein", + "Mithalas Cathedral, fourth urn in the top right room", + "Mithalas Cathedral, Mithalan Dress", + "Mithalas Cathedral, urn below the left entrance", + "Kelp Forest top left area, bulb close to the Verse Egg", + "Kelp Forest top left area, Verse Egg", + "Kelp Forest boss area, beating Drunian God", + "Mermog cave, Piranha Egg", + "Octopus Cave, Dumbo Egg", + "Sun Temple boss area, beating Sun God", + "King Jellyfish Cave, bulb in the right path from King Jelly", + "King Jellyfish Cave, Jellyfish Costume", + "Sunken City right area, crate close to the save crystal", + "Sunken City right area, crate in the left bottom room", + "Sunken City left area, crate in the little pipe room", + "Sunken City left area, crate close to the save crystal", + "Sunken City left area, crate before the bedroom", + "Sunken City left area, Girl Costume", + "Sunken City, bulb on top of the boss area", + "The Body center area, breaking Li's cage", + "The Body center area, bulb on the main path blocking tube", + "The Body left area, first bulb in the top face room", + "The Body left area, second bulb in the top face room", + "The Body left area, bulb below the water stream", + "The Body left area, bulb in the top path to the top face room", + "The Body left area, bulb in the bottom face room", + "The Body right area, bulb in the top face room", + "The Body right area, bulb in the top path to the bottom face room", + "The Body right area, bulb in the bottom face room", + "The Body bottom area, bulb in the Jelly Zap room", + "The Body bottom area, bulb in the nautilus room", + "The Body bottom area, Mutant Costume", + "Final Boss area, bulb in the boss third form room", + "Final Boss area, first bulb in the turtle room", + "Final Boss area, second bulb in the turtle room", + "Final Boss area, third bulb in the turtle room", + "Final Boss area, Transturtle", + "Beating Fallen God", + "Beating Blaster Peg Prime", + "Beating Mithalan God", + "Beating Drunian God", + "Beating Sun God", + "Beating the Golem", + "Beating Nautilus Prime", + "Beating Mergog", + "Beating Mithalan priests", + "Beating Octopus Prime", + "Beating King Jellyfish God Prime", + "Beating the Golem", + "Sunken City cleared", + "First secret", + "Objective complete" + ] + items = [["Energy form", "Dual form", "Li and Li song", "Body tongue cleared"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_fish_form_access.py b/worlds/aquaria/test/test_fish_form_access.py index c98a53e92438..40b15a87cd35 100644 --- a/worlds/aquaria/test/test_fish_form_access.py +++ b/worlds/aquaria/test/test_fish_form_access.py @@ -17,6 +17,7 @@ def test_fish_form_location(self) -> None: """Test locations that require fish form""" locations = [ "The Veil top left area, bulb inside the fish pass", + "Energy Temple first area, Energy Idol", "Mithalas City, Doll", "Mithalas City, urn inside a home fish pass", "Kelp Forest top right area, bulb in the top fish pass", @@ -30,8 +31,7 @@ def test_fish_form_location(self) -> None: "Octopus Cave, Dumbo Egg", "Octopus Cave, bulb in the path below the Octopus Cave path", "Beating Octopus Prime", - "Abyss left area, bulb in the bottom fish pass", - "Arnassi Ruins, Arnassi Armor" + "Abyss left area, bulb in the bottom fish pass" ] items = [["Fish form"]] self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_light_access.py b/worlds/aquaria/test/test_light_access.py index b5d7cf99fea2..29d37d790b20 100644 --- a/worlds/aquaria/test/test_light_access.py +++ b/worlds/aquaria/test/test_light_access.py @@ -39,7 +39,6 @@ def test_light_location(self) -> None: "Abyss right area, bulb in the middle path", "Abyss right area, bulb behind the rock in the middle path", "Abyss right area, bulb in the left green room", - "Abyss right area, Transturtle", "Ice Cave, bulb in the room to the right", "Ice Cave, first bulb in the top exit room", "Ice Cave, second bulb in the top exit room", diff --git a/worlds/aquaria/test/test_spirit_form_access.py b/worlds/aquaria/test/test_spirit_form_access.py index 3bcbd7d72e02..7e31de9905e9 100644 --- a/worlds/aquaria/test/test_spirit_form_access.py +++ b/worlds/aquaria/test/test_spirit_form_access.py @@ -30,7 +30,6 @@ def test_spirit_form_location(self) -> None: "Sunken City left area, Girl Costume", "Beating Mantis Shrimp Prime", "First secret", - "Arnassi Ruins, Arnassi Armor", ] items = [["Spirit form"]] self.assertAccessDependency(locations, items) From 6803c373e5ff738914c362b5e7a158fd528f54f7 Mon Sep 17 00:00:00 2001 From: qwint Date: Thu, 8 Aug 2024 13:33:13 -0500 Subject: [PATCH 19/98] HK: add grub hunt goal (#3203) * makes grub hunt goal option that calculates the total available grubs (including item link replacements) and requires all of them to be gathered for goal completion * update slot data name for grub count * add option to set number needed for grub hub * updates to grub hunt goal based on review * copy/paste fix * account for 'any' goal and fix overriding non-grub goals * making sure godhome is in logic for any and removing redundancy on completion condition * fix typing * i hate typing * move to stage_pre_fill * modify "any" goal so all goals are in logic under minimal settings * rewrite grub counting to create lookups for grubs and groups that can be reused * use generator instead of list comprehension * fix whitespace merging wrong * minor code cleanup --- worlds/hk/Options.py | 13 ++++++++- worlds/hk/__init__.py | 68 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/worlds/hk/Options.py b/worlds/hk/Options.py index e2602036a24e..c1206d41ee2c 100644 --- a/worlds/hk/Options.py +++ b/worlds/hk/Options.py @@ -405,9 +405,20 @@ class Goal(Choice): option_radiance = 3 option_godhome = 4 option_godhome_flower = 5 + option_grub_hunt = 6 default = 0 +class GrubHuntGoal(NamedRange): + """The amount of grubs required to finish Grub Hunt. + On 'All' any grubs from item links replacements etc. will be counted""" + display_name = "Grub Hunt Goal" + range_start = 1 + range_end = 46 + special_range_names = {"all": -1} + default = 46 + + class WhitePalace(Choice): """ Whether or not to include White Palace or not. Note: Even if excluded, the King Fragment check may still be @@ -522,7 +533,7 @@ class CostSanityHybridChance(Range): **{ option.__name__: option for option in ( - StartLocation, Goal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo, + StartLocation, Goal, GrubHuntGoal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo, DeathLink, DeathLinkShade, DeathLinkBreaksFragileCharms, MinimumGeoPrice, MaximumGeoPrice, MinimumGrubPrice, MaximumGrubPrice, diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index e5065876ddf3..99277378a162 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -5,6 +5,7 @@ from copy import deepcopy import itertools import operator +from collections import defaultdict, Counter logger = logging.getLogger("Hollow Knight") @@ -12,12 +13,12 @@ from .Regions import create_regions 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, \ - shop_to_option, HKOptions + shop_to_option, HKOptions, GrubHuntGoal from .ExtractedData import locations, starts, multi_locations, location_to_region_lookup, \ event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs from .Charms import names as charm_names -from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification +from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification, CollectionState from worlds.AutoWorld import World, LogicMixin, WebWorld path_of_pain_locations = { @@ -155,6 +156,7 @@ class HKWorld(World): ranges: typing.Dict[str, typing.Tuple[int, int]] charm_costs: typing.List[int] cached_filler_items = {} + grub_count: int def __init__(self, multiworld, player): super(HKWorld, self).__init__(multiworld, player) @@ -164,6 +166,7 @@ def __init__(self, multiworld, player): self.ranges = {} self.created_shop_items = 0 self.vanilla_shop_costs = deepcopy(vanilla_shop_costs) + self.grub_count = 0 def generate_early(self): options = self.options @@ -201,7 +204,7 @@ def create_regions(self): # check for any goal that godhome events are relevant to all_event_names = event_names.copy() - if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower]: + if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower, Goal.option_any]: from .GodhomeData import godhome_event_names all_event_names.update(set(godhome_event_names)) @@ -441,12 +444,67 @@ def set_rules(self): multiworld.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player) elif goal == Goal.option_godhome_flower: multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player) + elif goal == Goal.option_grub_hunt: + pass # will set in stage_pre_fill() else: # Any goal - multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) or _hk_can_beat_radiance(state, player) + multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player) and \ + _hk_can_beat_radiance(state, player) and state.count("Godhome_Flower_Quest", player) set_rules(self) + @classmethod + def stage_pre_fill(cls, multiworld: "MultiWorld"): + def set_goal(player, grub_rule: typing.Callable[[CollectionState], bool]): + world = multiworld.worlds[player] + + if world.options.Goal == "grub_hunt": + multiworld.completion_condition[player] = grub_rule + else: + old_rule = multiworld.completion_condition[player] + multiworld.completion_condition[player] = lambda state: old_rule(state) and grub_rule(state) + + worlds = [world for world in multiworld.get_game_worlds(cls.game) if world.options.Goal in ["any", "grub_hunt"]] + if worlds: + grubs = [item for item in multiworld.get_items() if item.name == "Grub"] + all_grub_players = [world.player for world in multiworld.worlds.values() if world.options.GrubHuntGoal == GrubHuntGoal.special_range_names["all"]] + + if all_grub_players: + group_lookup = defaultdict(set) + for group_id, group in multiworld.groups.items(): + for player in group["players"]: + group_lookup[group_id].add(player) + + grub_count_per_player = Counter() + per_player_grubs_per_player = defaultdict(Counter) + + for grub in grubs: + player = grub.player + if player in group_lookup: + for real_player in group_lookup[player]: + per_player_grubs_per_player[real_player][player] += 1 + else: + per_player_grubs_per_player[player][player] += 1 + + if grub.location and grub.location.player in group_lookup.keys(): + for real_player in group_lookup[grub.location.player]: + grub_count_per_player[real_player] += 1 + else: + grub_count_per_player[player] += 1 + + for player, count in grub_count_per_player.items(): + multiworld.worlds[player].grub_count = count + + for player, grub_player_count in per_player_grubs_per_player.items(): + if player in all_grub_players: + set_goal(player, lambda state, g=grub_player_count: all(state.has("Grub", owner, count) for owner, count in g.items())) + + for world in worlds: + if world.player not in all_grub_players: + world.grub_count = world.options.GrubHuntGoal.value + player = world.player + set_goal(player, lambda state, p=player, c=world.grub_count: state.has("Grub", p, c)) + def fill_slot_data(self): slot_data = {} @@ -484,6 +542,8 @@ def fill_slot_data(self): slot_data["notch_costs"] = self.charm_costs + slot_data["grub_count"] = self.grub_count + return slot_data def create_item(self, name: str) -> HKItem: From 5efb3fd2b0450f68dc95f3b79a0f48746b5e732d Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 9 Aug 2024 03:14:26 -0700 Subject: [PATCH 20/98] DS3: Version 3.0.0 (#3128) * Update worlds/dark_souls_3/Locations.py Co-authored-by: Scipio Wright * Fix Covetous Silver Serpent Ring location * Update location groups This should cover pretty much all of the seriously hidden items. It also splits out miniboss drops, mimic drops, and hostile NPC drops. * Remove the "Guarded by Keys" group On reflection, I don't think this is actually that useful. It'll also get a lot muddier once we can randomize shops and ashes become pseudo-"keys". * Restore Knight Slayer's Ring classification * Support infusions/upgrades in the new DS3 mod system * Support random starting loadouts * Make an item's NPC status orthogonal to its category * Track location groups with flags * Track Archipelago/Offline mismatches on the server Also fix a few incorrect item names. * Add additional locations that are now randomizable * Don't put soul and multiple items in shops * Add an option to enable whether NG+ items/locations are included * Clean up useful item categorization There are so many weapons in the game now, it doesn't make sense to treat them all as useful * Add more variety to filler items * Iron out a few bugs and incompatibilities * Fix more silly bugs * Get tests passing * Update options to cover new item types Also recategorize some items. * Verify the default values of `Option`s. Since `Option.verify()` can handle normalization of option names, this allows options to define defaults which rely on that normalization. For example, it allows a world to exclude certain locations by default. This also makes it easier to catch errors if a world author accidentally sets an invalid default. * Make a few more improvements and fixes * Randomize Path of the Dragon * Mark items that unlock checks as useful These items all unlock missable checks, but they're still good to ahve in the game for variety's sake. * Guarantee more NPC quests are completable * Fix a syntax error * Fix rule definition * Support enemy randomization * Support online Yhorm randomization * Remove a completed TODO * Fix tests * Fix force_unique * Add an option to smooth out upgrade item progression * Add helpers for setting location/entrance rules * Support smoother soul item progression * Fill extra smoothing items into conditional locations as well as other worlds * Add health item smoothing * Handle infusions at item generation time * Handle item upgrades at genreation time * Fix Grave Warden's Ashes * Don't overwrite old rules * Randomize items based on spheres instead of DS3 locations * Add a smoothing option for weapon upgrades * Add rules for crow trades * Small fixes * Fix a few more bugs * Fix more bugs * Try to prevent Path of the Dragon from going somewhere it doesn't work * Add the ability to provide enemy presets * Various fixes and features * Bug fixes * Better Coiled Sword placement * Structure DarkSouls3Location more like DarkSouls3Item * Add events to make DS3's spheres more even * Restructure locations to work like items do now * Add rules for more missable locations * Don't add two Storm Rulers * Place Hawk Ring in Farron Keep * Mark the Grass Crest Shield as useful * Mark new progression items * Fix a bug * Support newer better Path of the Dragon code * Don't lock the player out of Coiled Sword * Don't create events for missable locations * Don't throw strings * Don't smooth event items * Properly categorize Butcher Knife * Be more careful about placing Yhorm in low-randomization scenarios * Don't try to smooth DLC items with DLC disabled * Fix another Yhorm bug * Fix upgrade/infusion logic * Remove the PoolType option This distinction is no longer meaningful now that every location in the game of each type is randomized * Categorize HWL: Red Eye Orb as an NPC location * Don't place Storm Ruler on CA: Coiled Sword * Define flatten() locally to make this APWorld capable * Fix some more Leonhard weirdness * Fix unique item randomization * Don't double Twin Dragon Greatshield * Remove debugging print * Don't add double Storm Ruler Also remove now-redundant item sorting by category in create_items. * Don't add double Storm Ruler Also remove now-redundant item sorting by category in create_items. * Add a missing dlc_enabled check * Use nicer options syntax * Bump data_version * Mention where Yhorm is in which world * Better handle excluded events * Add a newline to Yhorm location * Better way of handling excluded unradomized progression locations * Fix a squidge of nondeterminism * Only smooth items from this world * Don't smooth progression weapons * Remove a location that doesn't actually exist in-game * Classify Power Within as useful * Clarify location names * Fix location requirements * Clean up randomization options * Properly name Coiled Sword location * Add an option for configuring how missable items are handled * Fix some bugs from location name updates * Fix location guide link * Fix a couple locations that were busted offline * Update detailed location descriptions * Fix some bugs when generating for a multiworld * Inject Large Leather Shield * Fix a few location issues * Don't allow progression_skip_balancing for unnecessary locs * Update some location info * Don't uniquify the wrong items * Fix some more location issues * More location fixes * Use hyphens instead of parens for location descriptions * Update and fix more locations * Fix Soul of Cinder boss name * Fix some logic issues * Add item groups and document item/location groups * Fix the display name for "Impatient Mimics" * Properly handle Transposing Kiln and Pyromancer's Flame * Testing * Some fixes to NPC quests, late basin, and transposing kiln * Improve a couple location names * Split out and improve missable NPC item logic * Don't allow crow trades to have foreign items * Fix a variable capture bug * Make sure early items are accessible early even with early Castle * Mark ID giant slave drops as missable * Make sure late basin means that early items aren't behind it * Make is_location_available explicitly private * Add an _add_item_rule utility that checks availability * Clear excluded items if excluded_locations == "unnecessary" * Don't allow upgrades/infusions in crow trades * Fix the documentation for deprecated options * Create events for all excluded locations This allows `can_reach` logic to work even if the locations are randomized. * Fix up Patches' and Siegward's logic based on some manual testing * Factor out more sub-methods for setting location rules * Oops, left these in * Fixing name * Left that in too * Changing to NamedRange to support special_range_names * Alphabetizing * Don't call _is_location_available on foreign locations * Add missing Leonhard items * Changing late basin to have a post-small-doll option * Update basin option, add logic for some of Leonhard Hawkwood and Orbeck * Simplifying an option, fixing a copy-paste error * Removing trailing whitespace * Changing lost items to go into start inventory * Revert Basin changes * Oops * Update Options.py * Reverting small doll changes * Farron Keep boss requirement logic * Add Scroll for late_dlc * Fixing excluded unnecessary locations * Adding Priestess Ring as being after UG boss * Removing missable from Corvian Titanite Slab * Adding KFF Yhorm boss locks * Screams about Creighton * Elite Knight Set isn't permanently missable * Adding Kiln requirement to KFF * fixing valid_keys and item groups * Fixing an option-checker * Throwing unplaceable Storm Ruler into start inventory * Update locations * Refactor item injection * Update setup doc * Small fixes * Fix another location name * Fix injection calculation * Inject guaranteed items along with progression items * Mark boss souls as required for access to regions This allows us to set quest requirements for boss souls and have them automatically propagated to regions, means we need less machinery for Yhorm bosses, and allows us to get rid of a few region-transition events. * Make sure Sirris's quest can be completed before Pontiff * Removing unused list * Changing dict to list * Removing unused test * Update __init__.py * self.multiworld.random -> self.random (#9) * Fix some miscellaneous location issues * Rewrite the DS3 intro page/FAQ * Removing modifying the itempool after fill (#7) Co-authored-by: Natalie Weizenbaum * Small fixes to the setup guide (#10) Small fixes, adding an example for connecting * Expanded Late Basin of Vows and Late DLC (#6) * Add proper requirements for CD: Black Eye Orb * Fix Aldrich's name * Document the differences with the 2.x.x branch * Don't crash if there are more items than locations in smoothing * Apply suggestions from code review Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Code review * Fix _replace_with_filler * Don't use the shared flatten function in SM * Track local items separately rather than iterating the multiworld * Various formatting/docs changes suggested by PyCharm (#12) * Drop deprecated options * Rename "offline randomizer" to "static randomizer" which is clearer * Move `enable_*_locations` under removed options. * Avoid excluded locations for locally-filled items * Adding Removed options to error (#14) * Changes for WebHost options display and the options overhaul * unpack iterators in item list (#13) * Allow worlds to add options to prebuilt groups Previously, this crashed because `typing.NamedTuple` fields such as `group.name` aren't assignable. Now it will only fail for group names that are actually incorrectly cased, and will fail with a better error message. * Style changes, rename exclude behavior options, remove guaranteed items option * Spacing/Formatting (#18) * Various Fixes (#19) * Universally Track Yhorm (#20) * Account for excluded and missable * These are behaviors now * This is singular, apparently * Oops * Fleshing out the priority process * Missable Titanite Lizards and excluded locations (#22) * Small style/efficiency changes * Final passthrough fixes (#24) * Use rich option formatting * Make the behavior option values actual behaviors (#25) * Use != * Remove unused flatten utility * Some changes from review (#28) * Fixing determinism and making smooth faster (#29) * Style change * PyCharm and Mypy fixes (#26) Co-authored-by: Scipio Wright * Change yhorm default (#30) * Add indirect condition (#27) * Update worlds/dark_souls_3/docs/locations_en.md Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Ship all item IDs to the client This avoids issues where items might get skipped if, for instance, they're only in the starting inventory. * Make sure to send AP IDs for infused/upgraded weapons * Make `RandomEnemyPresetOption` compatible with ArchipelagoMW/Archipelago#3280 (#31) * Fix cast * More typing and small fixes (#32) --------- Co-authored-by: Scipio Wright Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: Exempt-Medic Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> Co-authored-by: Doug Hoskisson Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- worlds/dark_souls_3/Bosses.py | 264 ++ worlds/dark_souls_3/Items.py | 2812 +++++++------ worlds/dark_souls_3/Locations.py | 3686 ++++++++++++++--- worlds/dark_souls_3/Options.py | 507 ++- worlds/dark_souls_3/__init__.py | 1785 ++++++-- .../detailed_location_descriptions.py | 97 + worlds/dark_souls_3/docs/en_Dark Souls III.md | 203 +- worlds/dark_souls_3/docs/items_en.md | 24 + worlds/dark_souls_3/docs/locations_en.md | 2276 ++++++++++ worlds/dark_souls_3/docs/setup_en.md | 61 +- worlds/dark_souls_3/test/TestDarkSouls3.py | 27 + worlds/dark_souls_3/test/__init__.py | 0 12 files changed, 9354 insertions(+), 2388 deletions(-) create mode 100644 worlds/dark_souls_3/Bosses.py create mode 100644 worlds/dark_souls_3/detailed_location_descriptions.py create mode 100644 worlds/dark_souls_3/docs/items_en.md create mode 100644 worlds/dark_souls_3/docs/locations_en.md create mode 100644 worlds/dark_souls_3/test/TestDarkSouls3.py create mode 100644 worlds/dark_souls_3/test/__init__.py diff --git a/worlds/dark_souls_3/Bosses.py b/worlds/dark_souls_3/Bosses.py new file mode 100644 index 000000000000..008a29713202 --- /dev/null +++ b/worlds/dark_souls_3/Bosses.py @@ -0,0 +1,264 @@ +# In almost all cases, we leave boss and enemy randomization up to the static randomizer. But for +# Yhorm specifically we need to know where he ends up in order to ensure that the Storm Ruler is +# available before his fight. + +from dataclasses import dataclass, field +from typing import Set + + +@dataclass +class DS3BossInfo: + """The set of locations a given boss location blocks access to.""" + + name: str + """The boss's name.""" + + id: int + """The game's ID for this particular boss.""" + + dlc: bool = False + """This boss appears in one of the game's DLCs.""" + + before_storm_ruler: bool = False + """Whether this location appears before it's possible to get Storm Ruler in vanilla. + + This is used to determine whether it's safe to place Yhorm here if weapons + aren't randomized. + """ + + locations: Set[str] = field(default_factory=set) + """Additional individual locations that can't be accessed until the boss is dead.""" + + +# Note: the static randomizer splits up some bosses into separate fights for separate phases, each +# of which can be individually replaced by Yhorm. +all_bosses = [ + DS3BossInfo("Iudex Gundyr", 4000800, before_storm_ruler = True, locations = { + "CA: Coiled Sword - boss drop" + }), + DS3BossInfo("Vordt of the Boreal Valley", 3000800, before_storm_ruler = True, locations = { + "HWL: Soul of Boreal Valley Vordt" + }), + DS3BossInfo("Curse-rotted Greatwood", 3100800, locations = { + "US: Soul of the Rotted Greatwood", + "US: Transposing Kiln - boss drop", + "US: Wargod Wooden Shield - Pit of Hollows", + "FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + "FS: Sunset Shield - by grave after killing Hodrick w/Sirris", + "US: Sunset Helm - Pit of Hollows after killing Hodrick w/Sirris", + "US: Sunset Armor - pit of hollows after killing Hodrick w/Sirris", + "US: Sunset Gauntlets - pit of hollows after killing Hodrick w/Sirris", + "US: Sunset Leggings - pit of hollows after killing Hodrick w/Sirris", + "FS: Sunless Talisman - Sirris, kill GA boss", + "FS: Sunless Veil - shop, Sirris quest, kill GA boss", + "FS: Sunless Armor - shop, Sirris quest, kill GA boss", + "FS: Sunless Gauntlets - shop, Sirris quest, kill GA boss", + "FS: Sunless Leggings - shop, Sirris quest, kill GA boss", + }), + DS3BossInfo("Crystal Sage", 3300850, locations = { + "RS: Soul of a Crystal Sage", + "FS: Sage's Big Hat - shop after killing RS boss", + "FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + }), + DS3BossInfo("Deacons of the Deep", 3500800, locations = { + "CD: Soul of the Deacons of the Deep", + "CD: Small Doll - boss drop", + "FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + }), + DS3BossInfo("Abyss Watchers", 3300801, before_storm_ruler = True, locations = { + "FK: Soul of the Blood of the Wolf", + "FK: Cinders of a Lord - Abyss Watcher", + "FS: Undead Legion Helm - shop after killing FK boss", + "FS: Undead Legion Armor - shop after killing FK boss", + "FS: Undead Legion Gauntlet - shop after killing FK boss", + "FS: Undead Legion Leggings - shop after killing FK boss", + "FS: Farron Ring - Hawkwood", + "FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + }), + DS3BossInfo("High Lord Wolnir", 3800800, before_storm_ruler = True, locations = { + "CC: Soul of High Lord Wolnir", + "FS: Wolnir's Crown - shop after killing CC boss", + "CC: Homeward Bone - Irithyll bridge", + "CC: Pontiff's Right Eye - Irithyll bridge, miniboss drop", + }), + DS3BossInfo("Pontiff Sulyvahn", 3700850, locations = { + "IBV: Soul of Pontiff Sulyvahn", + }), + DS3BossInfo("Old Demon King", 3800830, locations = { + "SL: Soul of the Old Demon King", + }), + DS3BossInfo("Aldrich, Devourer of Gods", 3700800, locations = { + "AL: Soul of Aldrich", + "AL: Cinders of a Lord - Aldrich", + "FS: Smough's Helm - shop after killing AL boss", + "FS: Smough's Armor - shop after killing AL boss", + "FS: Smough's Gauntlets - shop after killing AL boss", + "FS: Smough's Leggings - shop after killing AL boss", + "AL: Sun Princess Ring - dark cathedral, after boss", + "FS: Leonhard's Garb - shop after killing Leonhard", + "FS: Leonhard's Gauntlets - shop after killing Leonhard", + "FS: Leonhard's Trousers - shop after killing Leonhard", + }), + DS3BossInfo("Dancer of the Boreal Valley", 3000899, locations = { + "HWL: Soul of the Dancer", + "FS: Dancer's Crown - shop after killing LC entry boss", + "FS: Dancer's Armor - shop after killing LC entry boss", + "FS: Dancer's Gauntlets - shop after killing LC entry boss", + "FS: Dancer's Leggings - shop after killing LC entry boss", + }), + DS3BossInfo("Dragonslayer Armour", 3010800, locations = { + "LC: Soul of Dragonslayer Armour", + "FS: Morne's Helm - shop after killing Eygon or LC boss", + "FS: Morne's Armor - shop after killing Eygon or LC boss", + "FS: Morne's Gauntlets - shop after killing Eygon or LC boss", + "FS: Morne's Leggings - shop after killing Eygon or LC boss", + "LC: Titanite Chunk - down stairs after boss", + }), + DS3BossInfo("Consumed King Oceiros", 3000830, locations = { + "CKG: Soul of Consumed Oceiros", + "CKG: Titanite Scale - tomb, chest #1", + "CKG: Titanite Scale - tomb, chest #2", + "CKG: Drakeblood Helm - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Armor - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Gauntlets - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Leggings - tomb, after killing AP mausoleum NPC", + }), + DS3BossInfo("Champion Gundyr", 4000830, locations = { + "UG: Soul of Champion Gundyr", + "FS: Gundyr's Helm - shop after killing UG boss", + "FS: Gundyr's Armor - shop after killing UG boss", + "FS: Gundyr's Gauntlets - shop after killing UG boss", + "FS: Gundyr's Leggings - shop after killing UG boss", + "UG: Hornet Ring - environs, right of main path after killing FK boss", + "UG: Chaos Blade - environs, left of shrine", + "UG: Blacksmith Hammer - shrine, Andre's room", + "UG: Eyes of a Fire Keeper - shrine, Irina's room", + "UG: Coiled Sword Fragment - shrine, dead bonfire", + "UG: Soul of a Crestfallen Knight - environs, above shrine entrance", + "UG: Life Ring+3 - shrine, behind big throne", + "UG: Ring of Steel Protection+1 - environs, behind bell tower", + "FS: Ring of Sacrifice - Yuria shop", + "UG: Ember - shop", + "UG: Priestess Ring - shop", + "UG: Wolf Knight Helm - shop after killing FK boss", + "UG: Wolf Knight Armor - shop after killing FK boss", + "UG: Wolf Knight Gauntlets - shop after killing FK boss", + "UG: Wolf Knight Leggings - shop after killing FK boss", + }), + DS3BossInfo("Ancient Wyvern", 3200800), + DS3BossInfo("King of the Storm", 3200850, locations = { + "AP: Soul of the Nameless King", + "FS: Golden Crown - shop after killing AP boss", + "FS: Dragonscale Armor - shop after killing AP boss", + "FS: Golden Bracelets - shop after killing AP boss", + "FS: Dragonscale Waistcloth - shop after killing AP boss", + "AP: Titanite Slab - plaza", + "AP: Covetous Gold Serpent Ring+2 - plaza", + "AP: Dragonslayer Helm - plaza", + "AP: Dragonslayer Armor - plaza", + "AP: Dragonslayer Gauntlets - plaza", + "AP: Dragonslayer Leggings - plaza", + }), + DS3BossInfo("Nameless King", 3200851, locations = { + "AP: Soul of the Nameless King", + "FS: Golden Crown - shop after killing AP boss", + "FS: Dragonscale Armor - shop after killing AP boss", + "FS: Golden Bracelets - shop after killing AP boss", + "FS: Dragonscale Waistcloth - shop after killing AP boss", + "AP: Titanite Slab - plaza", + "AP: Covetous Gold Serpent Ring+2 - plaza", + "AP: Dragonslayer Helm - plaza", + "AP: Dragonslayer Armor - plaza", + "AP: Dragonslayer Gauntlets - plaza", + "AP: Dragonslayer Leggings - plaza", + }), + DS3BossInfo("Lothric, Younger Prince", 3410830, locations = { + "GA: Soul of the Twin Princes", + "GA: Cinders of a Lord - Lothric Prince", + }), + DS3BossInfo("Lorian, Elder Prince", 3410832, locations = { + "GA: Soul of the Twin Princes", + "GA: Cinders of a Lord - Lothric Prince", + "FS: Lorian's Helm - shop after killing GA boss", + "FS: Lorian's Armor - shop after killing GA boss", + "FS: Lorian's Gauntlets - shop after killing GA boss", + "FS: Lorian's Leggings - shop after killing GA boss", + }), + DS3BossInfo("Champion's Gravetender and Gravetender Greatwolf", 4500860, dlc = True, + locations = {"PW1: Valorheart - boss drop"}), + DS3BossInfo("Sister Friede", 4500801, dlc = True, locations = { + "PW2: Soul of Sister Friede", + "PW2: Titanite Slab - boss drop", + "PW1: Titanite Slab - Corvian", + "FS: Ordained Hood - shop after killing PW2 boss", + "FS: Ordained Dress - shop after killing PW2 boss", + "FS: Ordained Trousers - shop after killing PW2 boss", + }), + DS3BossInfo("Blackflame Friede", 4500800, dlc = True, locations = { + "PW2: Soul of Sister Friede", + "PW1: Titanite Slab - Corvian", + "FS: Ordained Hood - shop after killing PW2 boss", + "FS: Ordained Dress - shop after killing PW2 boss", + "FS: Ordained Trousers - shop after killing PW2 boss", + }), + DS3BossInfo("Demon Prince", 5000801, dlc = True, locations = { + "DH: Soul of the Demon Prince", + "DH: Small Envoy Banner - boss drop", + }), + DS3BossInfo("Halflight, Spear of the Church", 5100800, dlc = True, locations = { + "RC: Titanite Slab - mid boss drop", + "RC: Titanite Slab - ashes, NPC drop", + "RC: Titanite Slab - ashes, mob drop", + "RC: Filianore's Spear Ornament - mid boss drop", + "RC: Crucifix of the Mad King - ashes, NPC drop", + "RC: Shira's Crown - Shira's room after killing ashes NPC", + "RC: Shira's Armor - Shira's room after killing ashes NPC", + "RC: Shira's Gloves - Shira's room after killing ashes NPC", + "RC: Shira's Trousers - Shira's room after killing ashes NPC", + }), + DS3BossInfo("Darkeater Midir", 5100850, dlc = True, locations = { + "RC: Soul of Darkeater Midir", + "RC: Spears of the Church - hidden boss drop", + }), + DS3BossInfo("Slave Knight Gael 1", 5110801, dlc = True, locations = { + "RC: Soul of Slave Knight Gael", + "RC: Blood of the Dark Soul - end boss drop", + # These are accessible before you trigger the boss, but once you do you + # have to beat it before getting them. + "RC: Titanite Slab - ashes, mob drop", + "RC: Titanite Slab - ashes, NPC drop", + "RC: Sacred Chime of Filianore - ashes, NPC drop", + "RC: Crucifix of the Mad King - ashes, NPC drop", + "RC: Shira's Crown - Shira's room after killing ashes NPC", + "RC: Shira's Armor - Shira's room after killing ashes NPC", + "RC: Shira's Gloves - Shira's room after killing ashes NPC", + "RC: Shira's Trousers - Shira's room after killing ashes NPC", + }), + DS3BossInfo("Slave Knight Gael 2", 5110800, dlc = True, locations = { + "RC: Soul of Slave Knight Gael", + "RC: Blood of the Dark Soul - end boss drop", + # These are accessible before you trigger the boss, but once you do you + # have to beat it before getting them. + "RC: Titanite Slab - ashes, mob drop", + "RC: Titanite Slab - ashes, NPC drop", + "RC: Sacred Chime of Filianore - ashes, NPC drop", + "RC: Crucifix of the Mad King - ashes, NPC drop", + "RC: Shira's Crown - Shira's room after killing ashes NPC", + "RC: Shira's Armor - Shira's room after killing ashes NPC", + "RC: Shira's Gloves - Shira's room after killing ashes NPC", + "RC: Shira's Trousers - Shira's room after killing ashes NPC", + }), + DS3BossInfo("Lords of Cinder", 4100800, locations = { + "KFF: Soul of the Lords", + "FS: Billed Mask - Yuria after killing KFF boss", + "FS: Black Dress - Yuria after killing KFF boss", + "FS: Black Gauntlets - Yuria after killing KFF boss", + "FS: Black Leggings - Yuria after killing KFF boss" + }), +] + +default_yhorm_location = DS3BossInfo("Yhorm the Giant", 3900800, locations = { + "PC: Soul of Yhorm the Giant", + "PC: Cinders of a Lord - Yhorm the Giant", + "PC: Siegbräu - Siegward after killing boss", +}) diff --git a/worlds/dark_souls_3/Items.py b/worlds/dark_souls_3/Items.py index 3dd5cb2d3c3f..19cd79a99414 100644 --- a/worlds/dark_souls_3/Items.py +++ b/worlds/dark_souls_3/Items.py @@ -1,7 +1,9 @@ +from dataclasses import dataclass +import dataclasses from enum import IntEnum -from typing import NamedTuple +from typing import Any, cast, ClassVar, Dict, Generator, List, Optional, Set -from BaseClasses import Item +from BaseClasses import Item, ItemClassification class DS3ItemCategory(IntEnum): @@ -14,1267 +16,1677 @@ class DS3ItemCategory(IntEnum): RING = 6 SPELL = 7 MISC = 8 - KEY = 9 + UNIQUE = 9 BOSS = 10 - SKIP = 11 + SOUL = 11 + UPGRADE = 12 + HEALING = 13 + @property + def is_infusible(self) -> bool: + """Returns whether this category can be infused.""" + return self in [ + DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE, + DS3ItemCategory.SHIELD_INFUSIBLE + ] + + @property + def upgrade_level(self) -> Optional[int]: + """The maximum upgrade level for this category, or None if it's not upgradable.""" + if self == DS3ItemCategory.WEAPON_UPGRADE_5: return 5 + if self in [ + DS3ItemCategory.WEAPON_UPGRADE_10, + DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE + ]: return 10 + return None + + +@dataclass +class Infusion(IntEnum): + """Infusions supported by Dark Souls III. + + The value of each infusion is the number added to the base weapon's ID to get the infused ID. + """ + + HEAVY = 100 + SHARP = 200 + REFINED = 300 + SIMPLE = 400 + CRYSTAL = 500 + FIRE = 600 + CHAOS = 700 + LIGHTNING = 800 + DEEP = 900 + DARK = 1000 + POISON = 1100 + BLOOD = 1200 + RAW = 1300 + BLESSED = 1400 + HOLLOW = 1500 + + @property + def prefix(self): + """The prefix to add to a weapon name with this infusion.""" + return self.name.title() + + +class UsefulIf(IntEnum): + """An enum that indicates when an item should be upgraded to ItemClassification.useful. + + This is used for rings with +x variants that may or may not be the best in class depending on + the player's settings. + """ + + DEFAULT = 0 + """Follows DS3ItemData.classification as written.""" + + BASE = 1 + """Useful only if the DLC and NG+ locations are disabled.""" + + NO_DLC = 2 + """Useful if the DLC is disabled, whether or not NG+ locations are.""" + + NO_NGP = 3 + """Useful if NG+ locations is disabled, whether or not the DLC is.""" + + +@dataclass +class DS3ItemData: + __item_id: ClassVar[int] = 100000 + """The next item ID to use when creating item data.""" -class DS3ItemData(NamedTuple): name: str - ds3_code: int - is_dlc: bool + ds3_code: Optional[int] category: DS3ItemCategory + base_ds3_code: Optional[int] = None + """If this is an upgradable weapon, the base ID of the weapon it upgrades from. + + Otherwise, or if the weapon isn't upgraded, this is the same as ds3_code. + """ + + base_name: Optional[str] = None + """The name of the individual item, if this is a multi-item group.""" + + classification: ItemClassification = ItemClassification.filler + """How important this item is to the game progression.""" + + ap_code: Optional[int] = None + """The Archipelago ID for this item.""" + + is_dlc: bool = False + """Whether this item is only found in one of the two DLC packs.""" + + count: int = 1 + """The number of copies of this item included in each drop.""" + + inject: bool = False + """If this is set, the randomizer will try to inject this item into the game. + + This is used for items such as covenant rewards that aren't realistically reachable in a + randomizer run, but are still fun to have available to the player. If there are more locations + available than there are items in the item pool, these items will be used to help make up the + difference. + """ + + souls: Optional[int] = None + """If this is a consumable item that gives souls, the number of souls it gives.""" + + useful_if: UsefulIf = UsefulIf.DEFAULT + """Whether and when this item should be marked as "useful".""" + + filler: bool = False + """Whether this is a candidate for a filler item to be added to fill out extra locations.""" + + skip: bool = False + """Whether to omit this item from randomization and replace it with filler or unique items.""" + + @property + def unique(self): + """Whether this item should be unique, appearing only once in the randomizer.""" + return self.category not in { + DS3ItemCategory.MISC, DS3ItemCategory.SOUL, DS3ItemCategory.UPGRADE, + DS3ItemCategory.HEALING, + } + + def __post_init__(self): + self.ap_code = self.ap_code or DS3ItemData.__item_id + if not self.base_name: self.base_name = self.name + if not self.base_ds3_code: self.base_ds3_code = self.ds3_code + DS3ItemData.__item_id += 1 + + def item_groups(self) -> List[str]: + """The names of item groups this item should appear in. + + This is computed from the properties assigned to this item.""" + names = [] + if self.classification == ItemClassification.progression: names.append("Progression") + if self.name.startswith("Cinders of a Lord -"): names.append("Cinders") + + names.append({ + DS3ItemCategory.WEAPON_UPGRADE_5: "Weapons", + DS3ItemCategory.WEAPON_UPGRADE_10: "Weapons", + DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE: "Weapons", + DS3ItemCategory.SHIELD: "Shields", + DS3ItemCategory.SHIELD_INFUSIBLE: "Shields", + DS3ItemCategory.ARMOR: "Armor", + DS3ItemCategory.RING: "Rings", + DS3ItemCategory.SPELL: "Spells", + DS3ItemCategory.MISC: "Miscellaneous", + DS3ItemCategory.UNIQUE: "Unique", + DS3ItemCategory.BOSS: "Boss Souls", + DS3ItemCategory.SOUL: "Small Souls", + DS3ItemCategory.UPGRADE: "Upgrade", + DS3ItemCategory.HEALING: "Healing", + }[self.category]) + + return names + + def counts(self, counts: List[int]) -> Generator["DS3ItemData", None, None]: + """Returns an iterable of copies of this item with the given counts.""" + yield self + for count in counts: + yield dataclasses.replace( + self, + ap_code = None, + name = "{} x{}".format(self.base_name, count), + base_name = self.base_name, + count = count, + filler = False, # Don't count multiples as filler by default + ) + + @property + def is_infused(self) -> bool: + """Returns whether this item is an infused weapon.""" + return cast(int, self.ds3_code) - cast(int, self.base_ds3_code) >= 100 + + def infuse(self, infusion: Infusion) -> "DS3ItemData": + """Returns this item with the given infusion applied.""" + if not self.category.is_infusible: raise RuntimeError(f"{self.name} is not infusible.") + if self.is_infused: + raise RuntimeError(f"{self.name} is already infused.") + + # We can't change the name or AP code when infusing/upgrading weapons, because they both + # need to match what's in item_name_to_id. We don't want to add every possible + # infusion/upgrade combination to that map because it's way too many items. + return dataclasses.replace( + self, + name = self.name, + ds3_code = cast(int, self.ds3_code) + infusion.value, + filler = False, + ) + + @property + def is_upgraded(self) -> bool: + """Returns whether this item is a weapon that's upgraded beyond level 0.""" + return (cast(int, self.ds3_code) - cast(int, self.base_ds3_code)) % 100 != 0 + + def upgrade(self, level: int) -> "DS3ItemData": + """Upgrades this item to the given level.""" + if not self.category.upgrade_level: raise RuntimeError(f"{self.name} is not upgradable.") + if level > self.category.upgrade_level: + raise RuntimeError(f"{self.name} can't be upgraded to +{level}.") + if self.is_upgraded: + raise RuntimeError(f"{self.name} is already upgraded.") + + # We can't change the name or AP code when infusing/upgrading weapons, because they both + # need to match what's in item_name_to_id. We don't want to add every possible + # infusion/upgrade combination to that map because it's way too many items. + return dataclasses.replace( + self, + name = self.name, + ds3_code = cast(int, self.ds3_code) + level, + filler = False, + ) + + def __hash__(self) -> int: + return (self.name, self.ds3_code).__hash__() + + def __eq__(self, other: Any) -> bool: + if isinstance(other, self.__class__): + return self.name == other.name and self.ds3_code == other.ds3_code + else: + return False + class DarkSouls3Item(Item): game: str = "Dark Souls III" + data: DS3ItemData + + @property + def level(self) -> Optional[int]: + """This item's upgrade level, if it's a weapon.""" + return cast(int, self.data.ds3_code) % 100 if self.data.category.upgrade_level else None + + def __init__( + self, + player: int, + data: DS3ItemData, + classification = None): + super().__init__(data.name, classification or data.classification, data.ap_code, player) + self.data = data @staticmethod - def get_name_to_id() -> dict: - base_id = 100000 - return {item_data.name: id for id, item_data in enumerate(_all_items, base_id)} - - -key_item_names = { - "Small Lothric Banner", - "Basin of Vows", - "Small Doll", - "Storm Ruler", - "Grand Archives Key", - "Cinders of a Lord - Abyss Watcher", - "Cinders of a Lord - Yhorm the Giant", - "Cinders of a Lord - Aldrich", - "Cinders of a Lord - Lothric Prince", - "Mortician's Ashes", - "Cell Key", - #"Tower Key", #Not a relevant key item atm - "Jailbreaker's Key", - "Prisoner Chief's Ashes", - "Old Cell Key", - "Jailer's Key Ring", - "Contraption Key", - "Small Envoy Banner" -} + def event(name: str, player: int) -> "DarkSouls3Item": + data = DS3ItemData(name, None, DS3ItemCategory.MISC, + skip = True, classification = ItemClassification.progression) + data.ap_code = None + return DarkSouls3Item(player, data) -_vanilla_items = [DS3ItemData(row[0], row[1], False, row[2]) for row in [ +_vanilla_items = [ # Ammunition - ("Standard Arrow", 0x00061A80, DS3ItemCategory.SKIP), - ("Fire Arrow", 0x00061AE4, DS3ItemCategory.SKIP), - ("Poison Arrow", 0x00061B48, DS3ItemCategory.SKIP), - ("Large Arrow", 0x00061BAC, DS3ItemCategory.SKIP), - ("Feather Arrow", 0x00061C10, DS3ItemCategory.SKIP), - ("Moonlight Arrow", 0x00061C74, DS3ItemCategory.SKIP), - ("Wood Arrow", 0x00061CD8, DS3ItemCategory.SKIP), - ("Dark Arrow", 0x00061D3C, DS3ItemCategory.SKIP), - ("Dragonslayer Greatarrow", 0x00062250, DS3ItemCategory.SKIP), - ("Dragonslayer Lightning Arrow", 0x00062318, DS3ItemCategory.SKIP), - ("Onislayer Greatarrow", 0x0006237C, DS3ItemCategory.SKIP), - ("Standard Bolt", 0x00062A20, DS3ItemCategory.SKIP), - ("Heavy Bolt", 0x00062A84, DS3ItemCategory.SKIP), - ("Sniper Bolt", 0x00062AE8, DS3ItemCategory.SKIP), - ("Wood Bolt", 0x00062B4C, DS3ItemCategory.SKIP), - ("Lightning Bolt", 0x00062BB0, DS3ItemCategory.SKIP), - ("Splintering Bolt", 0x00062C14, DS3ItemCategory.SKIP), - ("Exploding Bolt", 0x00062C78, DS3ItemCategory.SKIP), + *DS3ItemData("Standard Arrow", 0x00061A80, DS3ItemCategory.MISC).counts([12]), + DS3ItemData("Standard Arrow x8", 0x00061A80, DS3ItemCategory.MISC, count = 8, filler = True), + DS3ItemData("Fire Arrow", 0x00061AE4, DS3ItemCategory.MISC), + DS3ItemData("Fire Arrow x8", 0x00061AE4, DS3ItemCategory.MISC, count = 8, filler = True), + *DS3ItemData("Poison Arrow", 0x00061B48, DS3ItemCategory.MISC).counts([18]), + DS3ItemData("Poison Arrow x8", 0x00061B48, DS3ItemCategory.MISC, count = 8, filler = True), + DS3ItemData("Large Arrow", 0x00061BAC, DS3ItemCategory.MISC), + DS3ItemData("Feather Arrow", 0x00061C10, DS3ItemCategory.MISC), + *DS3ItemData("Moonlight Arrow", 0x00061C74, DS3ItemCategory.MISC).counts([6]), + DS3ItemData("Wood Arrow", 0x00061CD8, DS3ItemCategory.MISC), + DS3ItemData("Dark Arrow", 0x00061D3C, DS3ItemCategory.MISC), + *DS3ItemData("Dragonslayer Greatarrow", 0x00062250, DS3ItemCategory.MISC).counts([5]), + *DS3ItemData("Dragonslayer Lightning Arrow", 0x00062318, DS3ItemCategory.MISC).counts([10]), + *DS3ItemData("Onislayer Greatarrow", 0x0006237C, DS3ItemCategory.MISC).counts([8]), + DS3ItemData("Standard Bolt", 0x00062A20, DS3ItemCategory.MISC), + DS3ItemData("Heavy Bolt", 0x00062A84, DS3ItemCategory.MISC), + *DS3ItemData("Sniper Bolt", 0x00062AE8, DS3ItemCategory.MISC).counts([11]), + DS3ItemData("Wood Bolt", 0x00062B4C, DS3ItemCategory.MISC), + *DS3ItemData("Lightning Bolt", 0x00062BB0, DS3ItemCategory.MISC).counts([9]), + *DS3ItemData("Lightning Bolt", 0x00062BB0, DS3ItemCategory.MISC).counts([12]), + DS3ItemData("Splintering Bolt", 0x00062C14, DS3ItemCategory.MISC), + *DS3ItemData("Exploding Bolt", 0x00062C78, DS3ItemCategory.MISC).counts([6]), # Weapons - ("Dagger", 0x000F4240, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Bandit's Knife", 0x000F6950, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Parrying Dagger", 0x000F9060, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Rotten Ghru Dagger", 0x000FDE80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Harpe", 0x00102CA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Scholar's Candlestick", 0x001053B0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Tailbone Short Sword", 0x00107AC0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Corvian Greatknife", 0x0010A1D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Handmaid's Dagger", 0x00111700, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Shortsword", 0x001E8480, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Longsword", 0x001EAB90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Broadsword", 0x001ED2A0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Broken Straight Sword", 0x001EF9B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Lothric Knight Sword", 0x001F6EE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Sunlight Straight Sword", 0x00203230, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Rotten Ghru Curved Sword", 0x00205940, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Irithyll Straight Sword", 0x0020A760, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Cleric's Candlestick", 0x0020F580, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Morion Blade", 0x002143A0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Astora's Straight Sword", 0x002191C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Barbed Straight Sword", 0x0021B8D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Executioner's Greatsword", 0x0021DFE0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Anri's Straight Sword", 0x002206F0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Estoc", 0x002DC6C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Mail Breaker", 0x002DEDD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Rapier", 0x002E14E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Ricard's Rapier", 0x002E3BF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Crystal Sage's Rapier", 0x002E6300, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Irithyll Rapier", 0x002E8A10, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Shotel", 0x003D3010, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Scimitar", 0x003D7E30, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Falchion", 0x003DA540, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Carthus Curved Sword", 0x003DCC50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Carthus Curved Greatsword", 0x003DF360, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Pontiff Knight Curved Sword", 0x003E1A70, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Storm Curved Sword", 0x003E4180, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Painting Guardian's Curved Sword", 0x003E6890, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Crescent Moon Sword", 0x003E8FA0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Carthus Shotel", 0x003EB6B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Uchigatana", 0x004C4B40, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Washing Pole", 0x004C7250, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Chaos Blade", 0x004C9960, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Black Blade", 0x004CC070, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Bloodlust", 0x004CE780, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Darkdrift", 0x004D0E90, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Bastard Sword", 0x005B8D80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Claymore", 0x005BDBA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Zweihander", 0x005C29C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Greatsword", 0x005C50D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Astora Greatsword", 0x005C9EF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Murakumo", 0x005CC600, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Lothric Knight Greatsword", 0x005D1420, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Flamberge", 0x005DB060, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Exile Greatsword", 0x005DD770, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Greatsword of Judgment", 0x005E2590, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Profaned Greatsword", 0x005E4CA0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Cathedral Knight Greatsword", 0x005E73B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Farron Greatsword", 0x005E9AC0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Yhorm's Great Machete", 0x005F0FF0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Dark Sword", 0x005F3700, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Black Knight Sword", 0x005F5E10, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Lorian's Greatsword", 0x005F8520, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Twin Princes' Greatsword", 0x005FAC30, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Lothric's Holy Sword", 0x005FD340, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Wolnir's Holy Sword", 0x005FFA50, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Wolf Knight's Greatsword", 0x00602160, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Greatsword of Artorias", 0x0060216A, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Hollowslayer Greatsword", 0x00604870, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Moonlight Greatsword", 0x00606F80, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Drakeblood Greatsword", 0x00609690, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Firelink Greatsword", 0x0060BDA0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Fume Ultra Greatsword", 0x0060E4B0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Old Wolf Curved Sword", 0x00610BC0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Storm Ruler", 0x006132D0, DS3ItemCategory.KEY), - ("Hand Axe", 0x006ACFC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Battle Axe", 0x006AF6D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Deep Battle Axe", 0x006AFA54, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Brigand Axe", 0x006B1DE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Crescent Axe", 0x006B6C00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Great Axe", 0x006B9310, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Butcher Knife", 0x006BE130, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Dragonslayer's Axe", 0x006C0840, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Thrall Axe", 0x006C5660, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Dragonslayer Greataxe", 0x006C7D70, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Demon's Greataxe", 0x006CA480, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Eleonora", 0x006CCB90, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Man Serpent Hatchet", 0x006D19B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Club", 0x007A1200, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Mace", 0x007A3910, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Morning Star", 0x007A6020, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Reinforced Club", 0x007A8730, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Large Club", 0x007AFC60, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Great Club", 0x007B4A80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Great Mace", 0x007BBFB0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Great Wooden Hammer", 0x007C8300, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Gargoyle Flame Hammer", 0x007CAA10, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Vordt's Great Hammer", 0x007CD120, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Old King's Great Hammer", 0x007CF830, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Heysel Pick", 0x007D6D60, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Warpick", 0x007DBB80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Pickaxe", 0x007DE290, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Dragon Tooth", 0x007E09A0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Smough's Great Hammer", 0x007E30B0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Blacksmith Hammer", 0x007E57C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Morne's Great Hammer", 0x007E7ED0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Spiked Mace", 0x007EA5E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Spear", 0x00895440, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Winged Spear", 0x00897B50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Partizan", 0x0089C970, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Greatlance", 0x008A8CC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Lothric Knight Long Spear", 0x008AB3D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Gargoyle Flame Spear", 0x008B01F0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Rotten Ghru Spear", 0x008B2900, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Tailbone Spear", 0x008B5010, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Soldering Iron", 0x008B7720, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Arstor's Spear", 0x008BEC50, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Saint Bident", 0x008C1360, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Yorshka's Spear", 0x008C3A70, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Pike", 0x008C6180, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Heavy Four-pronged Plow", 0x008ADAE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Dragonslayer Spear", 0x008CAFA0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Great Scythe", 0x00989680, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Lucerne", 0x0098BD90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Glaive", 0x0098E4A0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Halberd", 0x00990BB0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Black Knight Greataxe", 0x009959D0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Pontiff Knight Great Scythe", 0x0099A7F0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Great Corvian Scythe", 0x0099CF00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Winged Knight Halberd", 0x0099F610, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Gundyr's Halberd", 0x009A1D20, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Red Hilted Halberd", 0x009AB960, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Black Knight Glaive", 0x009AE070, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Immolation Tinder", 0x009B0780, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Claw", 0x00A7D8C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Caestus", 0x00A7FFD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Manikin Claws", 0x00A826E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Demon's Fist", 0x00A84DF0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Dark Hand", 0x00A87500, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Whip", 0x00B71B00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Witch's Locks", 0x00B7B740, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Notched Whip", 0x00B7DE50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Spotted Whip", 0x00B80560, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Talisman", 0x00C72090, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sorcerer's Staff", 0x00C747A0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Storyteller's Staff", 0x00C76EB0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Mendicant's Staff", 0x00C795C0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Man-grub's Staff", 0x00C7E3E0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Archdeacon's Great Staff", 0x00C80AF0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Golden Ritual Spear", 0x00C83200, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Yorshka's Chime", 0x00C88020, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sage's Crystal Staff", 0x00C8CE40, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Heretic's Staff", 0x00C8F550, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Court Sorcerer's Staff", 0x00C91C60, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Witchtree Branch", 0x00C94370, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Izalith Staff", 0x00C96A80, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Cleric's Sacred Chime", 0x00C99190, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Priest's Chime", 0x00C9B8A0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Saint-tree Bellvine", 0x00C9DFB0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Caitha's Chime", 0x00CA06C0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Crystal Chime", 0x00CA2DD0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Sunlight Talisman", 0x00CA54E0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Canvas Talisman", 0x00CA7BF0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sunless Talisman", 0x00CAA300, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Saint's Talisman", 0x00CACA10, DS3ItemCategory.WEAPON_UPGRADE_10), - ("White Hair Talisman", 0x00CAF120, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Pyromancy Flame", 0x00CC77C0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Dragonslayer Greatbow", 0x00CF8500, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Short Bow", 0x00D5C690, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Composite Bow", 0x00D5EDA0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Light Crossbow", 0x00D63BC0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Arbalest", 0x00D662D0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Longbow", 0x00D689E0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Dragonrider Bow", 0x00D6B0F0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Avelyn", 0x00D6FF10, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Knight's Crossbow", 0x00D72620, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Heavy Crossbow", 0x00D74D30, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Darkmoon Longbow", 0x00D79B50, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Onislayer Greatbow", 0x00D7C260, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Black Bow of Pharis", 0x00D7E970, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sniper Crossbow", 0x00D83790, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sellsword Twinblades", 0x00F42400, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Warden Twinblades", 0x00F47220, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Winged Knight Twinaxes", 0x00F49930, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Dancer's Enchanted Swords", 0x00F4C040, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Great Machete", 0x00F4E750, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Brigand Twindaggers", 0x00F50E60, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Gotthard Twinswords", 0x00F53570, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Onikiri and Ubadachi", 0x00F58390, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Drang Twinspears", 0x00F5AAA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Drang Hammers", 0x00F61FD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Dagger", 0x000F4240, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Bandit's Knife", 0x000F6950, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Parrying Dagger", 0x000F9060, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Rotten Ghru Dagger", 0x000FDE80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Harpe", 0x00102CA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Scholar's Candlestick", 0x001053B0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Tailbone Short Sword", 0x00107AC0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Corvian Greatknife", 0x0010A1D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Handmaid's Dagger", 0x00111700, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Shortsword", 0x001E8480, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Longsword", 0x001EAB90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Broadsword", 0x001ED2A0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Broken Straight Sword", 0x001EF9B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Lothric Knight Sword", 0x001F6EE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Sunlight Straight Sword", 0x00203230, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Rotten Ghru Curved Sword", 0x00205940, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Irithyll Straight Sword", 0x0020A760, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Cleric's Candlestick", 0x0020F580, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Morion Blade", 0x002143A0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Astora Straight Sword", 0x002191C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Barbed Straight Sword", 0x0021B8D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Executioner's Greatsword", 0x0021DFE0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Anri's Straight Sword", 0x002206F0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Estoc", 0x002DC6C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Mail Breaker", 0x002DEDD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Rapier", 0x002E14E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Ricard's Rapier", 0x002E3BF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Crystal Sage's Rapier", 0x002E6300, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Irithyll Rapier", 0x002E8A10, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Shotel", 0x003D3010, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Scimitar", 0x003D7E30, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Falchion", 0x003DA540, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Carthus Curved Sword", 0x003DCC50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Carthus Curved Greatsword", 0x003DF360, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Pontiff Knight Curved Sword", 0x003E1A70, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Storm Curved Sword", 0x003E4180, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Painting Guardian's Curved Sword", 0x003E6890, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Crescent Moon Sword", 0x003E8FA0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Carthus Shotel", 0x003EB6B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Uchigatana", 0x004C4B40, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Washing Pole", 0x004C7250, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Chaos Blade", 0x004C9960, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Black Blade", 0x004CC070, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Bloodlust", 0x004CE780, DS3ItemCategory.WEAPON_UPGRADE_5, + inject = True), # Covenant reward + DS3ItemData("Darkdrift", 0x004D0E90, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Bastard Sword", 0x005B8D80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Claymore", 0x005BDBA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Zweihander", 0x005C29C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Greatsword", 0x005C50D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Astora Greatsword", 0x005C9EF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Murakumo", 0x005CC600, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Lothric Knight Greatsword", 0x005D1420, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Flamberge", 0x005DB060, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Exile Greatsword", 0x005DD770, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Greatsword of Judgment", 0x005E2590, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Profaned Greatsword", 0x005E4CA0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Cathedral Knight Greatsword", 0x005E73B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Farron Greatsword", 0x005E9AC0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Yhorm's Great Machete", 0x005F0FF0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Dark Sword", 0x005F3700, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Black Knight Sword", 0x005F5E10, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Lorian's Greatsword", 0x005F8520, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Twin Princes' Greatsword", 0x005FAC30, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Lothric's Holy Sword", 0x005FD340, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Wolnir's Holy Sword", 0x005FFA50, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Wolf Knight's Greatsword", 0x00602160, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Greatsword of Artorias", 0x0060216A, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Hollowslayer Greatsword", 0x00604870, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Moonlight Greatsword", 0x00606F80, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Drakeblood Greatsword", 0x00609690, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Firelink Greatsword", 0x0060BDA0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Fume Ultra Greatsword", 0x0060E4B0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Old Wolf Curved Sword", 0x00610BC0, DS3ItemCategory.WEAPON_UPGRADE_5, + inject = True), # Covenant reward + DS3ItemData("Storm Ruler", 0x006132D0, DS3ItemCategory.WEAPON_UPGRADE_5, + classification = ItemClassification.progression), + DS3ItemData("Hand Axe", 0x006ACFC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Battle Axe", 0x006AF6D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Deep Battle Axe", 0x006AFA54, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Brigand Axe", 0x006B1DE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Crescent Axe", 0x006B6C00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Greataxe", 0x006B9310, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Butcher Knife", 0x006BE130, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Dragonslayer's Axe", 0x006C0840, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Thrall Axe", 0x006C5660, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Dragonslayer Greataxe", 0x006C7D70, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Demon's Greataxe", 0x006CA480, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Eleonora", 0x006CCB90, DS3ItemCategory.WEAPON_UPGRADE_5, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Man Serpent Hatchet", 0x006D19B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Club", 0x007A1200, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Mace", 0x007A3910, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Morning Star", 0x007A6020, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Reinforced Club", 0x007A8730, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Large Club", 0x007AFC60, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Great Club", 0x007B4A80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Great Mace", 0x007BBFB0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Great Wooden Hammer", 0x007C8300, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Gargoyle Flame Hammer", 0x007CAA10, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Vordt's Great Hammer", 0x007CD120, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Old King's Great Hammer", 0x007CF830, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Heysel Pick", 0x007D6D60, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Warpick", 0x007DBB80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Pickaxe", 0x007DE290, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Dragon Tooth", 0x007E09A0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Smough's Great Hammer", 0x007E30B0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Blacksmith Hammer", 0x007E57C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Morne's Great Hammer", 0x007E7ED0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Spiked Mace", 0x007EA5E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Spear", 0x00895440, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Winged Spear", 0x00897B50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Partizan", 0x0089C970, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Greatlance", 0x008A8CC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Lothric Knight Long Spear", 0x008AB3D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Gargoyle Flame Spear", 0x008B01F0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Rotten Ghru Spear", 0x008B2900, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Tailbone Spear", 0x008B5010, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Soldering Iron", 0x008B7720, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Dragonslayer Swordspear", 0x008BC540, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Arstor's Spear", 0x008BEC50, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Saint Bident", 0x008C1360, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Yorshka's Spear", 0x008C3A70, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Pike", 0x008C6180, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Heavy Four-pronged Plow", 0x008ADAE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Dragonslayer Spear", 0x008CAFA0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Great Scythe", 0x00989680, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Lucerne", 0x0098BD90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Glaive", 0x0098E4A0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Halberd", 0x00990BB0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Black Knight Greataxe", 0x009959D0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Pontiff Knight Great Scythe", 0x0099A7F0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Great Corvian Scythe", 0x0099CF00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Winged Knight Halberd", 0x0099F610, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Gundyr's Halberd", 0x009A1D20, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Red Hilted Halberd", 0x009AB960, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Black Knight Glaive", 0x009AE070, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Immolation Tinder", 0x009B0780, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Claw", 0x00A7D8C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Caestus", 0x00A7FFD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Manikin Claws", 0x00A826E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Demon's Fist", 0x00A84DF0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Dark Hand", 0x00A87500, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Whip", 0x00B71B00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Witch's Locks", 0x00B7B740, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Notched Whip", 0x00B7DE50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Spotted Whip", 0x00B80560, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Talisman", 0x00C72090, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sorcerer's Staff", 0x00C747A0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Storyteller's Staff", 0x00C76EB0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Mendicant's Staff", 0x00C795C0, DS3ItemCategory.WEAPON_UPGRADE_10, + classification = ItemClassification.progression, # Crow trade + inject = True), # This is just a random drop normally, but we need it in-logic + DS3ItemData("Man-grub's Staff", 0x00C7E3E0, DS3ItemCategory.WEAPON_UPGRADE_5, + inject = True), # Covenant reward + DS3ItemData("Archdeacon's Great Staff", 0x00C80AF0, DS3ItemCategory.WEAPON_UPGRADE_5, + inject = True), # Covenant reward + DS3ItemData("Golden Ritual Spear", 0x00C83200, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Yorshka's Chime", 0x00C88020, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sage's Crystal Staff", 0x00C8CE40, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Heretic's Staff", 0x00C8F550, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Court Sorcerer's Staff", 0x00C91C60, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Witchtree Branch", 0x00C94370, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Izalith Staff", 0x00C96A80, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Cleric's Sacred Chime", 0x00C99190, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Priest's Chime", 0x00C9B8A0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Saint-tree Bellvine", 0x00C9DFB0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Caitha's Chime", 0x00CA06C0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Crystal Chime", 0x00CA2DD0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Sunlight Talisman", 0x00CA54E0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Canvas Talisman", 0x00CA7BF0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sunless Talisman", 0x00CAA300, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Saint's Talisman", 0x00CACA10, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("White Hair Talisman", 0x00CAF120, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Pyromancy Flame", 0x00CC77C0, DS3ItemCategory.WEAPON_UPGRADE_10, + classification = ItemClassification.progression), + DS3ItemData("Dragonslayer Greatbow", 0x00CF8500, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Short Bow", 0x00D5C690, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Composite Bow", 0x00D5EDA0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Light Crossbow", 0x00D63BC0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Arbalest", 0x00D662D0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Longbow", 0x00D689E0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Dragonrider Bow", 0x00D6B0F0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Avelyn", 0x00D6FF10, DS3ItemCategory.WEAPON_UPGRADE_10, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Knight's Crossbow", 0x00D72620, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Heavy Crossbow", 0x00D74D30, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Darkmoon Longbow", 0x00D79B50, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Onislayer Greatbow", 0x00D7C260, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Black Bow of Pharis", 0x00D7E970, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sniper Crossbow", 0x00D83790, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sellsword Twinblades", 0x00F42400, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Warden Twinblades", 0x00F47220, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Winged Knight Twinaxes", 0x00F49930, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Dancer's Enchanted Swords", 0x00F4C040, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Great Machete", 0x00F4E750, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Brigand Twindaggers", 0x00F50E60, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Gotthard Twinswords", 0x00F53570, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Onikiri and Ubadachi", 0x00F58390, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Drang Twinspears", 0x00F5AAA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Drang Hammers", 0x00F61FD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), # Shields - ("Buckler", 0x01312D00, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Small Leather Shield", 0x01315410, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Round Shield", 0x0131A230, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Large Leather Shield", 0x0131C940, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Hawkwood's Shield", 0x01323E70, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Iron Round Shield", 0x01326580, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Wooden Shield", 0x0132DAB0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Kite Shield", 0x013301C0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Ghru Rotshield", 0x013328D0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Havel's Greatshield", 0x013376F0, DS3ItemCategory.SHIELD), - ("Target Shield", 0x01339E00, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Elkhorn Round Shield", 0x0133C510, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Warrior's Round Shield", 0x0133EC20, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Caduceus Round Shield", 0x01341330, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Red and White Shield", 0x01343A40, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Blessed Red and White Shield+1", 0x01343FB9, DS3ItemCategory.SHIELD), - ("Plank Shield", 0x01346150, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Leather Shield", 0x01348860, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Crimson Parma", 0x0134AF70, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Eastern Iron Shield", 0x0134D680, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Llewellyn Shield", 0x0134FD90, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Golden Falcon Shield", 0x01354BB0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Sacred Bloom Shield", 0x013572C0, DS3ItemCategory.SHIELD), - ("Ancient Dragon Greatshield", 0x013599D0, DS3ItemCategory.SHIELD), - ("Lothric Knight Shield", 0x01409650, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Knight Shield", 0x01410B80, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Pontiff Knight Shield", 0x014159A0, DS3ItemCategory.SHIELD), - ("Carthus Shield", 0x014180B0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Black Knight Shield", 0x0141F5E0, DS3ItemCategory.SHIELD), - ("Silver Knight Shield", 0x01424400, DS3ItemCategory.SHIELD), - ("Spiked Shield", 0x01426B10, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Pierce Shield", 0x01429220, DS3ItemCategory.SHIELD_INFUSIBLE), - ("East-West Shield", 0x0142B930, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Sunlight Shield", 0x0142E040, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Crest Shield", 0x01430750, DS3ItemCategory.SHIELD), - ("Dragon Crest Shield", 0x01432E60, DS3ItemCategory.SHIELD), - ("Spider Shield", 0x01435570, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Grass Crest Shield", 0x01437C80, DS3ItemCategory.SHIELD), - ("Sunset Shield", 0x0143A390, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Golden Wing Crest Shield", 0x0143CAA0, DS3ItemCategory.SHIELD), - ("Blue Wooden Shield", 0x0143F1B0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Silver Eagle Kite Shield", 0x014418C0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Stone Parma", 0x01443FD0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Spirit Tree Crest Shield", 0x014466E0, DS3ItemCategory.SHIELD), - ("Porcine Shield", 0x01448DF0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Shield of Want", 0x0144B500, DS3ItemCategory.SHIELD), - ("Wargod Wooden Shield", 0x0144DC10, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Lothric Knight Greatshield", 0x014FD890, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Cathedral Knight Greatshield", 0x014FFFA0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Dragonslayer Greatshield", 0x01504DC0, DS3ItemCategory.SHIELD), - ("Moaning Shield", 0x015074D0, DS3ItemCategory.SHIELD), - ("Yhorm's Greatshield", 0x0150C2F0, DS3ItemCategory.SHIELD), - ("Black Iron Greatshield", 0x0150EA00, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Wolf Knight's Greatshield", 0x01511110, DS3ItemCategory.SHIELD), - ("Twin Dragon Greatshield", 0x01513820, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Greatshield of Glory", 0x01515F30, DS3ItemCategory.SHIELD), - ("Curse Ward Greatshield", 0x01518640, DS3ItemCategory.SHIELD), - ("Bonewheel Shield", 0x0151AD50, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Stone Greatshield", 0x0151D460, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Buckler", 0x01312D00, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Small Leather Shield", 0x01315410, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Round Shield", 0x0131A230, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Large Leather Shield", 0x0131C940, DS3ItemCategory.SHIELD_INFUSIBLE, + classification = ItemClassification.progression, # Crow trade + inject = True), # This is a shop/infinite drop item, but we need it in logic + DS3ItemData("Hawkwood's Shield", 0x01323E70, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Iron Round Shield", 0x01326580, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Wooden Shield", 0x0132DAB0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Kite Shield", 0x013301C0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Ghru Rotshield", 0x013328D0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Havel's Greatshield", 0x013376F0, DS3ItemCategory.SHIELD), + DS3ItemData("Target Shield", 0x01339E00, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Elkhorn Round Shield", 0x0133C510, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Warrior's Round Shield", 0x0133EC20, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Caduceus Round Shield", 0x01341330, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Red and White Shield", 0x01343A40, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Blessed Red and White Shield+1", 0x01343FB9, DS3ItemCategory.SHIELD), + DS3ItemData("Plank Shield", 0x01346150, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Leather Shield", 0x01348860, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Crimson Parma", 0x0134AF70, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Eastern Iron Shield", 0x0134D680, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Llewellyn Shield", 0x0134FD90, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Golden Falcon Shield", 0x01354BB0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Sacred Bloom Shield", 0x013572C0, DS3ItemCategory.SHIELD), + DS3ItemData("Ancient Dragon Greatshield", 0x013599D0, DS3ItemCategory.SHIELD), + DS3ItemData("Lothric Knight Shield", 0x01409650, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Knight Shield", 0x01410B80, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Pontiff Knight Shield", 0x014159A0, DS3ItemCategory.SHIELD), + DS3ItemData("Carthus Shield", 0x014180B0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Black Knight Shield", 0x0141F5E0, DS3ItemCategory.SHIELD), + DS3ItemData("Silver Knight Shield", 0x01424400, DS3ItemCategory.SHIELD), + DS3ItemData("Spiked Shield", 0x01426B10, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Pierce Shield", 0x01429220, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("East-West Shield", 0x0142B930, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Sunlight Shield", 0x0142E040, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Crest Shield", 0x01430750, DS3ItemCategory.SHIELD), + DS3ItemData("Dragon Crest Shield", 0x01432E60, DS3ItemCategory.SHIELD), + DS3ItemData("Spider Shield", 0x01435570, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Grass Crest Shield", 0x01437C80, DS3ItemCategory.SHIELD, + classification = ItemClassification.useful), + DS3ItemData("Sunset Shield", 0x0143A390, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Golden Wing Crest Shield", 0x0143CAA0, DS3ItemCategory.SHIELD), + DS3ItemData("Blue Wooden Shield", 0x0143F1B0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Silver Eagle Kite Shield", 0x014418C0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Stone Parma", 0x01443FD0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Spirit Tree Crest Shield", 0x014466E0, DS3ItemCategory.SHIELD), + DS3ItemData("Porcine Shield", 0x01448DF0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Shield of Want", 0x0144B500, DS3ItemCategory.SHIELD), + DS3ItemData("Wargod Wooden Shield", 0x0144DC10, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Lothric Knight Greatshield", 0x014FD890, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Cathedral Knight Greatshield", 0x014FFFA0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Dragonslayer Greatshield", 0x01504DC0, DS3ItemCategory.SHIELD), + DS3ItemData("Moaning Shield", 0x015074D0, DS3ItemCategory.SHIELD, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Yhorm's Greatshield", 0x0150C2F0, DS3ItemCategory.SHIELD), + DS3ItemData("Black Iron Greatshield", 0x0150EA00, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Wolf Knight's Greatshield", 0x01511110, DS3ItemCategory.SHIELD, + inject = True), # Covenant reward + DS3ItemData("Twin Dragon Greatshield", 0x01513820, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Greatshield of Glory", 0x01515F30, DS3ItemCategory.SHIELD), + DS3ItemData("Curse Ward Greatshield", 0x01518640, DS3ItemCategory.SHIELD), + DS3ItemData("Bonewheel Shield", 0x0151AD50, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Stone Greatshield", 0x0151D460, DS3ItemCategory.SHIELD_INFUSIBLE), # Armor - ("Fallen Knight Helm", 0x1121EAC0, DS3ItemCategory.ARMOR), - ("Fallen Knight Armor", 0x1121EEA8, DS3ItemCategory.ARMOR), - ("Fallen Knight Gauntlets", 0x1121F290, DS3ItemCategory.ARMOR), - ("Fallen Knight Trousers", 0x1121F678, DS3ItemCategory.ARMOR), - ("Knight Helm", 0x11298BE0, DS3ItemCategory.ARMOR), - ("Knight Armor", 0x11298FC8, DS3ItemCategory.ARMOR), - ("Knight Gauntlets", 0x112993B0, DS3ItemCategory.ARMOR), - ("Knight Leggings", 0x11299798, DS3ItemCategory.ARMOR), - ("Firelink Helm", 0x11406F40, DS3ItemCategory.ARMOR), - ("Firelink Armor", 0x11407328, DS3ItemCategory.ARMOR), - ("Firelink Gauntlets", 0x11407710, DS3ItemCategory.ARMOR), - ("Firelink Leggings", 0x11407AF8, DS3ItemCategory.ARMOR), - ("Sellsword Helm", 0x11481060, DS3ItemCategory.ARMOR), - ("Sellsword Armor", 0x11481448, DS3ItemCategory.ARMOR), - ("Sellsword Gauntlet", 0x11481830, DS3ItemCategory.ARMOR), - ("Sellsword Trousers", 0x11481C18, DS3ItemCategory.ARMOR), - ("Herald Helm", 0x114FB180, DS3ItemCategory.ARMOR), - ("Herald Armor", 0x114FB568, DS3ItemCategory.ARMOR), - ("Herald Gloves", 0x114FB950, DS3ItemCategory.ARMOR), - ("Herald Trousers", 0x114FBD38, DS3ItemCategory.ARMOR), - ("Sunless Veil", 0x115752A0, DS3ItemCategory.ARMOR), - ("Sunless Armor", 0x11575688, DS3ItemCategory.ARMOR), - ("Sunless Gauntlets", 0x11575A70, DS3ItemCategory.ARMOR), - ("Sunless Leggings", 0x11575E58, DS3ItemCategory.ARMOR), - ("Black Hand Hat", 0x115EF3C0, DS3ItemCategory.ARMOR), - ("Black Hand Armor", 0x115EF7A8, DS3ItemCategory.ARMOR), - ("Assassin Gloves", 0x115EFB90, DS3ItemCategory.ARMOR), - ("Assassin Trousers", 0x115EFF78, DS3ItemCategory.ARMOR), - ("Assassin Hood", 0x11607A60, DS3ItemCategory.ARMOR), - ("Assassin Armor", 0x11607E48, DS3ItemCategory.ARMOR), - ("Xanthous Crown", 0x116694E0, DS3ItemCategory.ARMOR), - ("Xanthous Overcoat", 0x116698C8, DS3ItemCategory.ARMOR), - ("Xanthous Gloves", 0x11669CB0, DS3ItemCategory.ARMOR), - ("Xanthous Trousers", 0x1166A098, DS3ItemCategory.ARMOR), - ("Northern Helm", 0x116E3600, DS3ItemCategory.ARMOR), - ("Northern Armor", 0x116E39E8, DS3ItemCategory.ARMOR), - ("Northern Gloves", 0x116E3DD0, DS3ItemCategory.ARMOR), - ("Northern Trousers", 0x116E41B8, DS3ItemCategory.ARMOR), - ("Morne's Helm", 0x1175D720, DS3ItemCategory.ARMOR), - ("Morne's Armor", 0x1175DB08, DS3ItemCategory.ARMOR), - ("Morne's Gauntlets", 0x1175DEF0, DS3ItemCategory.ARMOR), - ("Morne's Leggings", 0x1175E2D8, DS3ItemCategory.ARMOR), - ("Silver Mask", 0x117D7840, DS3ItemCategory.ARMOR), - ("Leonhard's Garb", 0x117D7C28, DS3ItemCategory.ARMOR), - ("Leonhard's Gauntlets", 0x117D8010, DS3ItemCategory.ARMOR), - ("Leonhard's Trousers", 0x117D83F8, DS3ItemCategory.ARMOR), - ("Sneering Mask", 0x11851960, DS3ItemCategory.ARMOR), - ("Pale Shade Robe", 0x11851D48, DS3ItemCategory.ARMOR), - ("Pale Shade Gloves", 0x11852130, DS3ItemCategory.ARMOR), - ("Pale Shade Trousers", 0x11852518, DS3ItemCategory.ARMOR), - ("Sunset Helm", 0x118CBA80, DS3ItemCategory.ARMOR), - ("Sunset Armor", 0x118CBE68, DS3ItemCategory.ARMOR), - ("Sunset Gauntlets", 0x118CC250, DS3ItemCategory.ARMOR), - ("Sunset Leggings", 0x118CC638, DS3ItemCategory.ARMOR), - ("Old Sage's Blindfold", 0x11945BA0, DS3ItemCategory.ARMOR), - ("Cornyx's Garb", 0x11945F88, DS3ItemCategory.ARMOR), - ("Cornyx's Wrap", 0x11946370, DS3ItemCategory.ARMOR), - ("Cornyx's Skirt", 0x11946758, DS3ItemCategory.ARMOR), - ("Executioner Helm", 0x119BFCC0, DS3ItemCategory.ARMOR), - ("Executioner Armor", 0x119C00A8, DS3ItemCategory.ARMOR), - ("Executioner Gauntlets", 0x119C0490, DS3ItemCategory.ARMOR), - ("Executioner Leggings", 0x119C0878, DS3ItemCategory.ARMOR), - ("Billed Mask", 0x11A39DE0, DS3ItemCategory.ARMOR), - ("Black Dress", 0x11A3A1C8, DS3ItemCategory.ARMOR), - ("Black Gauntlets", 0x11A3A5B0, DS3ItemCategory.ARMOR), - ("Black Leggings", 0x11A3A998, DS3ItemCategory.ARMOR), - ("Pyromancer Crown", 0x11AB3F00, DS3ItemCategory.ARMOR), - ("Pyromancer Garb", 0x11AB42E8, DS3ItemCategory.ARMOR), - ("Pyromancer Wrap", 0x11AB46D0, DS3ItemCategory.ARMOR), - ("Pyromancer Trousers", 0x11AB4AB8, DS3ItemCategory.ARMOR), - ("Court Sorcerer Hood", 0x11BA8140, DS3ItemCategory.ARMOR), - ("Court Sorcerer Robe", 0x11BA8528, DS3ItemCategory.ARMOR), - ("Court Sorcerer Gloves", 0x11BA8910, DS3ItemCategory.ARMOR), - ("Court Sorcerer Trousers", 0x11BA8CF8, DS3ItemCategory.ARMOR), - ("Sorcerer Hood", 0x11C9C380, DS3ItemCategory.ARMOR), - ("Sorcerer Robe", 0x11C9C768, DS3ItemCategory.ARMOR), - ("Sorcerer Gloves", 0x11C9CB50, DS3ItemCategory.ARMOR), - ("Sorcerer Trousers", 0x11C9CF38, DS3ItemCategory.ARMOR), - ("Clandestine Coat", 0x11CB4E08, DS3ItemCategory.ARMOR), - ("Cleric Hat", 0x11D905C0, DS3ItemCategory.ARMOR), - ("Cleric Blue Robe", 0x11D909A8, DS3ItemCategory.ARMOR), - ("Cleric Gloves", 0x11D90D90, DS3ItemCategory.ARMOR), - ("Cleric Trousers", 0x11D91178, DS3ItemCategory.ARMOR), - ("Steel Soldier Helm", 0x12625A00, DS3ItemCategory.ARMOR), - ("Deserter Armor", 0x12625DE8, DS3ItemCategory.ARMOR), - ("Deserter Trousers", 0x126265B8, DS3ItemCategory.ARMOR), - ("Thief Mask", 0x12656740, DS3ItemCategory.ARMOR), - ("Sage's Big Hat", 0x129020C0, DS3ItemCategory.ARMOR), - ("Aristocrat's Mask", 0x129F6300, DS3ItemCategory.ARMOR), - ("Jailer Robe", 0x129F66E8, DS3ItemCategory.ARMOR), - ("Jailer Gloves", 0x129F6AD0, DS3ItemCategory.ARMOR), - ("Jailer Trousers", 0x129F6EB8, DS3ItemCategory.ARMOR), - ("Grave Warden Hood", 0x12BDE780, DS3ItemCategory.ARMOR), - ("Grave Warden Robe", 0x12BDEB68, DS3ItemCategory.ARMOR), - ("Grave Warden Wrap", 0x12BDEF50, DS3ItemCategory.ARMOR), - ("Grave Warden Skirt", 0x12BDF338, DS3ItemCategory.ARMOR), - ("Worker Hat", 0x12CD29C0, DS3ItemCategory.ARMOR), - ("Worker Garb", 0x12CD2DA8, DS3ItemCategory.ARMOR), - ("Worker Gloves", 0x12CD3190, DS3ItemCategory.ARMOR), - ("Worker Trousers", 0x12CD3578, DS3ItemCategory.ARMOR), - ("Thrall Hood", 0x12D4CAE0, DS3ItemCategory.ARMOR), - ("Evangelist Hat", 0x12DC6C00, DS3ItemCategory.ARMOR), - ("Evangelist Robe", 0x12DC6FE8, DS3ItemCategory.ARMOR), - ("Evangelist Gloves", 0x12DC73D0, DS3ItemCategory.ARMOR), - ("Evangelist Trousers", 0x12DC77B8, DS3ItemCategory.ARMOR), - ("Scholar's Robe", 0x12E41108, DS3ItemCategory.ARMOR), - ("Winged Knight Helm", 0x12EBAE40, DS3ItemCategory.ARMOR), - ("Winged Knight Armor", 0x12EBB228, DS3ItemCategory.ARMOR), - ("Winged Knight Gauntlets", 0x12EBB610, DS3ItemCategory.ARMOR), - ("Winged Knight Leggings", 0x12EBB9F8, DS3ItemCategory.ARMOR), - ("Cathedral Knight Helm", 0x130291A0, DS3ItemCategory.ARMOR), - ("Cathedral Knight Armor", 0x13029588, DS3ItemCategory.ARMOR), - ("Cathedral Knight Gauntlets", 0x13029970, DS3ItemCategory.ARMOR), - ("Cathedral Knight Leggings", 0x13029D58, DS3ItemCategory.ARMOR), - ("Lothric Knight Helm", 0x13197500, DS3ItemCategory.ARMOR), - ("Lothric Knight Armor", 0x131978E8, DS3ItemCategory.ARMOR), - ("Lothric Knight Gauntlets", 0x13197CD0, DS3ItemCategory.ARMOR), - ("Lothric Knight Leggings", 0x131980B8, DS3ItemCategory.ARMOR), - ("Outrider Knight Helm", 0x1328B740, DS3ItemCategory.ARMOR), - ("Outrider Knight Armor", 0x1328BB28, DS3ItemCategory.ARMOR), - ("Outrider Knight Gauntlets", 0x1328BF10, DS3ItemCategory.ARMOR), - ("Outrider Knight Leggings", 0x1328C2F8, DS3ItemCategory.ARMOR), - ("Black Knight Helm", 0x1337F980, DS3ItemCategory.ARMOR), - ("Black Knight Armor", 0x1337FD68, DS3ItemCategory.ARMOR), - ("Black Knight Gauntlets", 0x13380150, DS3ItemCategory.ARMOR), - ("Black Knight Leggings", 0x13380538, DS3ItemCategory.ARMOR), - ("Dark Mask", 0x133F9AA0, DS3ItemCategory.ARMOR), - ("Dark Armor", 0x133F9E88, DS3ItemCategory.ARMOR), - ("Dark Gauntlets", 0x133FA270, DS3ItemCategory.ARMOR), - ("Dark Leggings", 0x133FA658, DS3ItemCategory.ARMOR), - ("Exile Mask", 0x13473BC0, DS3ItemCategory.ARMOR), - ("Exile Armor", 0x13473FA8, DS3ItemCategory.ARMOR), - ("Exile Gauntlets", 0x13474390, DS3ItemCategory.ARMOR), - ("Exile Leggings", 0x13474778, DS3ItemCategory.ARMOR), - ("Pontiff Knight Crown", 0x13567E00, DS3ItemCategory.ARMOR), - ("Pontiff Knight Armor", 0x135681E8, DS3ItemCategory.ARMOR), - ("Pontiff Knight Gauntlets", 0x135685D0, DS3ItemCategory.ARMOR), - ("Pontiff Knight Leggings", 0x135689B8, DS3ItemCategory.ARMOR), - ("Golden Crown", 0x1365C040, DS3ItemCategory.ARMOR), - ("Dragonscale Armor", 0x1365C428, DS3ItemCategory.ARMOR), - ("Golden Bracelets", 0x1365C810, DS3ItemCategory.ARMOR), - ("Dragonscale Waistcloth", 0x1365CBF8, DS3ItemCategory.ARMOR), - ("Wolnir's Crown", 0x136D6160, DS3ItemCategory.ARMOR), - ("Undead Legion Helm", 0x13750280, DS3ItemCategory.ARMOR), - ("Undead Legion Armor", 0x13750668, DS3ItemCategory.ARMOR), - ("Undead Legion Gauntlets", 0x13750A50, DS3ItemCategory.ARMOR), - ("Undead Legion Leggings", 0x13750E38, DS3ItemCategory.ARMOR), - ("Fire Witch Helm", 0x13938700, DS3ItemCategory.ARMOR), - ("Fire Witch Armor", 0x13938AE8, DS3ItemCategory.ARMOR), - ("Fire Witch Gauntlets", 0x13938ED0, DS3ItemCategory.ARMOR), - ("Fire Witch Leggings", 0x139392B8, DS3ItemCategory.ARMOR), - ("Lorian's Helm", 0x13A2C940, DS3ItemCategory.ARMOR), - ("Lorian's Armor", 0x13A2CD28, DS3ItemCategory.ARMOR), - ("Lorian's Gauntlets", 0x13A2D110, DS3ItemCategory.ARMOR), - ("Lorian's Leggings", 0x13A2D4F8, DS3ItemCategory.ARMOR), - ("Hood of Prayer", 0x13AA6A60, DS3ItemCategory.ARMOR), - ("Robe of Prayer", 0x13AA6E48, DS3ItemCategory.ARMOR), - ("Skirt of Prayer", 0x13AA7618, DS3ItemCategory.ARMOR), - ("Dancer's Crown", 0x13C14DC0, DS3ItemCategory.ARMOR), - ("Dancer's Armor", 0x13C151A8, DS3ItemCategory.ARMOR), - ("Dancer's Gauntlets", 0x13C15590, DS3ItemCategory.ARMOR), - ("Dancer's Leggings", 0x13C15978, DS3ItemCategory.ARMOR), - ("Gundyr's Helm", 0x13D09000, DS3ItemCategory.ARMOR), - ("Gundyr's Armor", 0x13D093E8, DS3ItemCategory.ARMOR), - ("Gundyr's Gauntlets", 0x13D097D0, DS3ItemCategory.ARMOR), - ("Gundyr's Leggings", 0x13D09BB8, DS3ItemCategory.ARMOR), - ("Archdeacon White Crown", 0x13EF1480, DS3ItemCategory.ARMOR), - ("Archdeacon Holy Garb", 0x13EF1868, DS3ItemCategory.ARMOR), - ("Archdeacon Skirt", 0x13EF2038, DS3ItemCategory.ARMOR), - ("Deacon Robe", 0x13F6B988, DS3ItemCategory.ARMOR), - ("Deacon Skirt", 0x13F6C158, DS3ItemCategory.ARMOR), - ("Fire Keeper Robe", 0x140D9CE8, DS3ItemCategory.ARMOR), - ("Fire Keeper Gloves", 0x140DA0D0, DS3ItemCategory.ARMOR), - ("Fire Keeper Skirt", 0x140DA4B8, DS3ItemCategory.ARMOR), - ("Chain Helm", 0x142C1D80, DS3ItemCategory.ARMOR), - ("Chain Armor", 0x142C2168, DS3ItemCategory.ARMOR), - ("Leather Gauntlets", 0x142C2550, DS3ItemCategory.ARMOR), - ("Chain Leggings", 0x142C2938, DS3ItemCategory.ARMOR), - ("Nameless Knight Helm", 0x143B5FC0, DS3ItemCategory.ARMOR), - ("Nameless Knight Armor", 0x143B63A8, DS3ItemCategory.ARMOR), - ("Nameless Knight Gauntlets", 0x143B6790, DS3ItemCategory.ARMOR), - ("Nameless Knight Leggings", 0x143B6B78, DS3ItemCategory.ARMOR), - ("Elite Knight Helm", 0x144AA200, DS3ItemCategory.ARMOR), - ("Elite Knight Armor", 0x144AA5E8, DS3ItemCategory.ARMOR), - ("Elite Knight Gauntlets", 0x144AA9D0, DS3ItemCategory.ARMOR), - ("Elite Knight Leggings", 0x144AADB8, DS3ItemCategory.ARMOR), - ("Faraam Helm", 0x1459E440, DS3ItemCategory.ARMOR), - ("Faraam Armor", 0x1459E828, DS3ItemCategory.ARMOR), - ("Faraam Gauntlets", 0x1459EC10, DS3ItemCategory.ARMOR), - ("Faraam Boots", 0x1459EFF8, DS3ItemCategory.ARMOR), - ("Catarina Helm", 0x14692680, DS3ItemCategory.ARMOR), - ("Catarina Armor", 0x14692A68, DS3ItemCategory.ARMOR), - ("Catarina Gauntlets", 0x14692E50, DS3ItemCategory.ARMOR), - ("Catarina Leggings", 0x14693238, DS3ItemCategory.ARMOR), - ("Standard Helm", 0x1470C7A0, DS3ItemCategory.ARMOR), - ("Hard Leather Armor", 0x1470CB88, DS3ItemCategory.ARMOR), - ("Hard Leather Gauntlets", 0x1470CF70, DS3ItemCategory.ARMOR), - ("Hard Leather Boots", 0x1470D358, DS3ItemCategory.ARMOR), - ("Havel's Helm", 0x147868C0, DS3ItemCategory.ARMOR), - ("Havel's Armor", 0x14786CA8, DS3ItemCategory.ARMOR), - ("Havel's Gauntlets", 0x14787090, DS3ItemCategory.ARMOR), - ("Havel's Leggings", 0x14787478, DS3ItemCategory.ARMOR), - ("Brigand Hood", 0x148009E0, DS3ItemCategory.ARMOR), - ("Brigand Armor", 0x14800DC8, DS3ItemCategory.ARMOR), - ("Brigand Gauntlets", 0x148011B0, DS3ItemCategory.ARMOR), - ("Brigand Trousers", 0x14801598, DS3ItemCategory.ARMOR), - ("Pharis's Hat", 0x1487AB00, DS3ItemCategory.ARMOR), - ("Leather Armor", 0x1487AEE8, DS3ItemCategory.ARMOR), - ("Leather Gloves", 0x1487B2D0, DS3ItemCategory.ARMOR), - ("Leather Boots", 0x1487B6B8, DS3ItemCategory.ARMOR), - ("Ragged Mask", 0x148F4C20, DS3ItemCategory.ARMOR), - ("Master's Attire", 0x148F5008, DS3ItemCategory.ARMOR), - ("Master's Gloves", 0x148F53F0, DS3ItemCategory.ARMOR), - ("Loincloth", 0x148F57D8, DS3ItemCategory.ARMOR), - ("Old Sorcerer Hat", 0x1496ED40, DS3ItemCategory.ARMOR), - ("Old Sorcerer Coat", 0x1496F128, DS3ItemCategory.ARMOR), - ("Old Sorcerer Gauntlets", 0x1496F510, DS3ItemCategory.ARMOR), - ("Old Sorcerer Boots", 0x1496F8F8, DS3ItemCategory.ARMOR), - ("Conjurator Hood", 0x149E8E60, DS3ItemCategory.ARMOR), - ("Conjurator Robe", 0x149E9248, DS3ItemCategory.ARMOR), - ("Conjurator Manchettes", 0x149E9630, DS3ItemCategory.ARMOR), - ("Conjurator Boots", 0x149E9A18, DS3ItemCategory.ARMOR), - ("Black Leather Armor", 0x14A63368, DS3ItemCategory.ARMOR), - ("Black Leather Gloves", 0x14A63750, DS3ItemCategory.ARMOR), - ("Black Leather Boots", 0x14A63B38, DS3ItemCategory.ARMOR), - ("Symbol of Avarice", 0x14ADD0A0, DS3ItemCategory.ARMOR), - ("Creighton's Steel Mask", 0x14B571C0, DS3ItemCategory.ARMOR), - ("Mirrah Chain Mail", 0x14B575A8, DS3ItemCategory.ARMOR), - ("Mirrah Chain Gloves", 0x14B57990, DS3ItemCategory.ARMOR), - ("Mirrah Chain Leggings", 0x14B57D78, DS3ItemCategory.ARMOR), - ("Maiden Hood", 0x14BD12E0, DS3ItemCategory.ARMOR), - ("Maiden Robe", 0x14BD16C8, DS3ItemCategory.ARMOR), - ("Maiden Gloves", 0x14BD1AB0, DS3ItemCategory.ARMOR), - ("Maiden Skirt", 0x14BD1E98, DS3ItemCategory.ARMOR), - ("Alva Helm", 0x14C4B400, DS3ItemCategory.ARMOR), - ("Alva Armor", 0x14C4B7E8, DS3ItemCategory.ARMOR), - ("Alva Gauntlets", 0x14C4BBD0, DS3ItemCategory.ARMOR), - ("Alva Leggings", 0x14C4BFB8, DS3ItemCategory.ARMOR), - ("Shadow Mask", 0x14D3F640, DS3ItemCategory.ARMOR), - ("Shadow Garb", 0x14D3FA28, DS3ItemCategory.ARMOR), - ("Shadow Gauntlets", 0x14D3FE10, DS3ItemCategory.ARMOR), - ("Shadow Leggings", 0x14D401F8, DS3ItemCategory.ARMOR), - ("Eastern Helm", 0x14E33880, DS3ItemCategory.ARMOR), - ("Eastern Armor", 0x14E33C68, DS3ItemCategory.ARMOR), - ("Eastern Gauntlets", 0x14E34050, DS3ItemCategory.ARMOR), - ("Eastern Leggings", 0x14E34438, DS3ItemCategory.ARMOR), - ("Helm of Favor", 0x14F27AC0, DS3ItemCategory.ARMOR), - ("Embraced Armor of Favor", 0x14F27EA8, DS3ItemCategory.ARMOR), - ("Gauntlets of Favor", 0x14F28290, DS3ItemCategory.ARMOR), - ("Leggings of Favor", 0x14F28678, DS3ItemCategory.ARMOR), - ("Brass Helm", 0x1501BD00, DS3ItemCategory.ARMOR), - ("Brass Armor", 0x1501C0E8, DS3ItemCategory.ARMOR), - ("Brass Gauntlets", 0x1501C4D0, DS3ItemCategory.ARMOR), - ("Brass Leggings", 0x1501C8B8, DS3ItemCategory.ARMOR), - ("Silver Knight Helm", 0x1510FF40, DS3ItemCategory.ARMOR), - ("Silver Knight Armor", 0x15110328, DS3ItemCategory.ARMOR), - ("Silver Knight Gauntlets", 0x15110710, DS3ItemCategory.ARMOR), - ("Silver Knight Leggings", 0x15110AF8, DS3ItemCategory.ARMOR), - ("Lucatiel's Mask", 0x15204180, DS3ItemCategory.ARMOR), - ("Mirrah Vest", 0x15204568, DS3ItemCategory.ARMOR), - ("Mirrah Gloves", 0x15204950, DS3ItemCategory.ARMOR), - ("Mirrah Trousers", 0x15204D38, DS3ItemCategory.ARMOR), - ("Iron Helm", 0x152F83C0, DS3ItemCategory.ARMOR), - ("Armor of the Sun", 0x152F87A8, DS3ItemCategory.ARMOR), - ("Iron Bracelets", 0x152F8B90, DS3ItemCategory.ARMOR), - ("Iron Leggings", 0x152F8F78, DS3ItemCategory.ARMOR), - ("Drakeblood Helm", 0x153EC600, DS3ItemCategory.ARMOR), - ("Drakeblood Armor", 0x153EC9E8, DS3ItemCategory.ARMOR), - ("Drakeblood Gauntlets", 0x153ECDD0, DS3ItemCategory.ARMOR), - ("Drakeblood Leggings", 0x153ED1B8, DS3ItemCategory.ARMOR), - ("Drang Armor", 0x154E0C28, DS3ItemCategory.ARMOR), - ("Drang Gauntlets", 0x154E1010, DS3ItemCategory.ARMOR), - ("Drang Shoes", 0x154E13F8, DS3ItemCategory.ARMOR), - ("Black Iron Helm", 0x155D4A80, DS3ItemCategory.ARMOR), - ("Black Iron Armor", 0x155D4E68, DS3ItemCategory.ARMOR), - ("Black Iron Gauntlets", 0x155D5250, DS3ItemCategory.ARMOR), - ("Black Iron Leggings", 0x155D5638, DS3ItemCategory.ARMOR), - ("Painting Guardian Hood", 0x156C8CC0, DS3ItemCategory.ARMOR), - ("Painting Guardian Gown", 0x156C90A8, DS3ItemCategory.ARMOR), - ("Painting Guardian Gloves", 0x156C9490, DS3ItemCategory.ARMOR), - ("Painting Guardian Waistcloth", 0x156C9878, DS3ItemCategory.ARMOR), - ("Wolf Knight Helm", 0x157BCF00, DS3ItemCategory.ARMOR), - ("Wolf Knight Armor", 0x157BD2E8, DS3ItemCategory.ARMOR), - ("Wolf Knight Gauntlets", 0x157BD6D0, DS3ItemCategory.ARMOR), - ("Wolf Knight Leggings", 0x157BDAB8, DS3ItemCategory.ARMOR), - ("Dragonslayer Helm", 0x158B1140, DS3ItemCategory.ARMOR), - ("Dragonslayer Armor", 0x158B1528, DS3ItemCategory.ARMOR), - ("Dragonslayer Gauntlets", 0x158B1910, DS3ItemCategory.ARMOR), - ("Dragonslayer Leggings", 0x158B1CF8, DS3ItemCategory.ARMOR), - ("Smough's Helm", 0x159A5380, DS3ItemCategory.ARMOR), - ("Smough's Armor", 0x159A5768, DS3ItemCategory.ARMOR), - ("Smough's Gauntlets", 0x159A5B50, DS3ItemCategory.ARMOR), - ("Smough's Leggings", 0x159A5F38, DS3ItemCategory.ARMOR), - ("Helm of Thorns", 0x15B8D800, DS3ItemCategory.ARMOR), - ("Armor of Thorns", 0x15B8DBE8, DS3ItemCategory.ARMOR), - ("Gauntlets of Thorns", 0x15B8DFD0, DS3ItemCategory.ARMOR), - ("Leggings of Thorns", 0x15B8E3B8, DS3ItemCategory.ARMOR), - ("Crown of Dusk", 0x15D75C80, DS3ItemCategory.ARMOR), - ("Antiquated Dress", 0x15D76068, DS3ItemCategory.ARMOR), - ("Antiquated Gloves", 0x15D76450, DS3ItemCategory.ARMOR), - ("Antiquated Skirt", 0x15D76838, DS3ItemCategory.ARMOR), - ("Karla's Pointed Hat", 0x15E69EC0, DS3ItemCategory.ARMOR), - ("Karla's Coat", 0x15E6A2A8, DS3ItemCategory.ARMOR), - ("Karla's Gloves", 0x15E6A690, DS3ItemCategory.ARMOR), - ("Karla's Trousers", 0x15E6AA78, DS3ItemCategory.ARMOR), + DS3ItemData("Fallen Knight Helm", 0x1121EAC0, DS3ItemCategory.ARMOR), + DS3ItemData("Fallen Knight Armor", 0x1121EEA8, DS3ItemCategory.ARMOR), + DS3ItemData("Fallen Knight Gauntlets", 0x1121F290, DS3ItemCategory.ARMOR), + DS3ItemData("Fallen Knight Trousers", 0x1121F678, DS3ItemCategory.ARMOR), + DS3ItemData("Knight Helm", 0x11298BE0, DS3ItemCategory.ARMOR), + DS3ItemData("Knight Armor", 0x11298FC8, DS3ItemCategory.ARMOR), + DS3ItemData("Knight Gauntlets", 0x112993B0, DS3ItemCategory.ARMOR), + DS3ItemData("Knight Leggings", 0x11299798, DS3ItemCategory.ARMOR), + DS3ItemData("Firelink Helm", 0x11406F40, DS3ItemCategory.ARMOR), + DS3ItemData("Firelink Armor", 0x11407328, DS3ItemCategory.ARMOR), + DS3ItemData("Firelink Gauntlets", 0x11407710, DS3ItemCategory.ARMOR), + DS3ItemData("Firelink Leggings", 0x11407AF8, DS3ItemCategory.ARMOR), + DS3ItemData("Sellsword Helm", 0x11481060, DS3ItemCategory.ARMOR), + DS3ItemData("Sellsword Armor", 0x11481448, DS3ItemCategory.ARMOR), + DS3ItemData("Sellsword Gauntlet", 0x11481830, DS3ItemCategory.ARMOR), + DS3ItemData("Sellsword Trousers", 0x11481C18, DS3ItemCategory.ARMOR), + DS3ItemData("Herald Helm", 0x114FB180, DS3ItemCategory.ARMOR), + DS3ItemData("Herald Armor", 0x114FB568, DS3ItemCategory.ARMOR), + DS3ItemData("Herald Gloves", 0x114FB950, DS3ItemCategory.ARMOR), + DS3ItemData("Herald Trousers", 0x114FBD38, DS3ItemCategory.ARMOR), + DS3ItemData("Sunless Veil", 0x115752A0, DS3ItemCategory.ARMOR), + DS3ItemData("Sunless Armor", 0x11575688, DS3ItemCategory.ARMOR), + DS3ItemData("Sunless Gauntlets", 0x11575A70, DS3ItemCategory.ARMOR), + DS3ItemData("Sunless Leggings", 0x11575E58, DS3ItemCategory.ARMOR), + DS3ItemData("Black Hand Hat", 0x115EF3C0, DS3ItemCategory.ARMOR), + DS3ItemData("Black Hand Armor", 0x115EF7A8, DS3ItemCategory.ARMOR), + DS3ItemData("Assassin Gloves", 0x115EFB90, DS3ItemCategory.ARMOR), + DS3ItemData("Assassin Trousers", 0x115EFF78, DS3ItemCategory.ARMOR), + DS3ItemData("Assassin Hood", 0x11607A60, DS3ItemCategory.ARMOR), + DS3ItemData("Assassin Armor", 0x11607E48, DS3ItemCategory.ARMOR), + DS3ItemData("Xanthous Crown", 0x116694E0, DS3ItemCategory.ARMOR, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Xanthous Overcoat", 0x116698C8, DS3ItemCategory.ARMOR), + DS3ItemData("Xanthous Gloves", 0x11669CB0, DS3ItemCategory.ARMOR), + DS3ItemData("Xanthous Trousers", 0x1166A098, DS3ItemCategory.ARMOR), + DS3ItemData("Northern Helm", 0x116E3600, DS3ItemCategory.ARMOR), + DS3ItemData("Northern Armor", 0x116E39E8, DS3ItemCategory.ARMOR), + DS3ItemData("Northern Gloves", 0x116E3DD0, DS3ItemCategory.ARMOR), + DS3ItemData("Northern Trousers", 0x116E41B8, DS3ItemCategory.ARMOR), + DS3ItemData("Morne's Helm", 0x1175D720, DS3ItemCategory.ARMOR), + DS3ItemData("Morne's Armor", 0x1175DB08, DS3ItemCategory.ARMOR), + DS3ItemData("Morne's Gauntlets", 0x1175DEF0, DS3ItemCategory.ARMOR), + DS3ItemData("Morne's Leggings", 0x1175E2D8, DS3ItemCategory.ARMOR), + DS3ItemData("Silver Mask", 0x117D7840, DS3ItemCategory.ARMOR), + DS3ItemData("Leonhard's Garb", 0x117D7C28, DS3ItemCategory.ARMOR), + DS3ItemData("Leonhard's Gauntlets", 0x117D8010, DS3ItemCategory.ARMOR), + DS3ItemData("Leonhard's Trousers", 0x117D83F8, DS3ItemCategory.ARMOR), + DS3ItemData("Sneering Mask", 0x11851960, DS3ItemCategory.ARMOR), + DS3ItemData("Pale Shade Robe", 0x11851D48, DS3ItemCategory.ARMOR), + DS3ItemData("Pale Shade Gloves", 0x11852130, DS3ItemCategory.ARMOR), + DS3ItemData("Pale Shade Trousers", 0x11852518, DS3ItemCategory.ARMOR), + DS3ItemData("Sunset Helm", 0x118CBA80, DS3ItemCategory.ARMOR), + DS3ItemData("Sunset Armor", 0x118CBE68, DS3ItemCategory.ARMOR), + DS3ItemData("Sunset Gauntlets", 0x118CC250, DS3ItemCategory.ARMOR), + DS3ItemData("Sunset Leggings", 0x118CC638, DS3ItemCategory.ARMOR), + DS3ItemData("Old Sage's Blindfold", 0x11945BA0, DS3ItemCategory.ARMOR), + DS3ItemData("Cornyx's Garb", 0x11945F88, DS3ItemCategory.ARMOR), + DS3ItemData("Cornyx's Wrap", 0x11946370, DS3ItemCategory.ARMOR), + DS3ItemData("Cornyx's Skirt", 0x11946758, DS3ItemCategory.ARMOR), + DS3ItemData("Executioner Helm", 0x119BFCC0, DS3ItemCategory.ARMOR), + DS3ItemData("Executioner Armor", 0x119C00A8, DS3ItemCategory.ARMOR), + DS3ItemData("Executioner Gauntlets", 0x119C0490, DS3ItemCategory.ARMOR), + DS3ItemData("Executioner Leggings", 0x119C0878, DS3ItemCategory.ARMOR), + DS3ItemData("Billed Mask", 0x11A39DE0, DS3ItemCategory.ARMOR), + DS3ItemData("Black Dress", 0x11A3A1C8, DS3ItemCategory.ARMOR), + DS3ItemData("Black Gauntlets", 0x11A3A5B0, DS3ItemCategory.ARMOR), + DS3ItemData("Black Leggings", 0x11A3A998, DS3ItemCategory.ARMOR), + DS3ItemData("Pyromancer Crown", 0x11AB3F00, DS3ItemCategory.ARMOR), + DS3ItemData("Pyromancer Garb", 0x11AB42E8, DS3ItemCategory.ARMOR), + DS3ItemData("Pyromancer Wrap", 0x11AB46D0, DS3ItemCategory.ARMOR), + DS3ItemData("Pyromancer Trousers", 0x11AB4AB8, DS3ItemCategory.ARMOR), + DS3ItemData("Court Sorcerer Hood", 0x11BA8140, DS3ItemCategory.ARMOR), + DS3ItemData("Court Sorcerer Robe", 0x11BA8528, DS3ItemCategory.ARMOR), + DS3ItemData("Court Sorcerer Gloves", 0x11BA8910, DS3ItemCategory.ARMOR), + DS3ItemData("Court Sorcerer Trousers", 0x11BA8CF8, DS3ItemCategory.ARMOR), + DS3ItemData("Sorcerer Hood", 0x11C9C380, DS3ItemCategory.ARMOR), + DS3ItemData("Sorcerer Robe", 0x11C9C768, DS3ItemCategory.ARMOR), + DS3ItemData("Sorcerer Gloves", 0x11C9CB50, DS3ItemCategory.ARMOR), + DS3ItemData("Sorcerer Trousers", 0x11C9CF38, DS3ItemCategory.ARMOR), + DS3ItemData("Clandestine Coat", 0x11CB4E08, DS3ItemCategory.ARMOR), + DS3ItemData("Cleric Hat", 0x11D905C0, DS3ItemCategory.ARMOR), + DS3ItemData("Cleric Blue Robe", 0x11D909A8, DS3ItemCategory.ARMOR), + DS3ItemData("Cleric Gloves", 0x11D90D90, DS3ItemCategory.ARMOR), + DS3ItemData("Cleric Trousers", 0x11D91178, DS3ItemCategory.ARMOR), + DS3ItemData("Steel Soldier Helm", 0x12625A00, DS3ItemCategory.ARMOR), + DS3ItemData("Deserter Armor", 0x12625DE8, DS3ItemCategory.ARMOR), + DS3ItemData("Deserter Trousers", 0x126265B8, DS3ItemCategory.ARMOR), + DS3ItemData("Thief Mask", 0x12656740, DS3ItemCategory.ARMOR), + DS3ItemData("Sage's Big Hat", 0x129020C0, DS3ItemCategory.ARMOR), + DS3ItemData("Aristocrat's Mask", 0x129F6300, DS3ItemCategory.ARMOR), + DS3ItemData("Jailer Robe", 0x129F66E8, DS3ItemCategory.ARMOR), + DS3ItemData("Jailer Gloves", 0x129F6AD0, DS3ItemCategory.ARMOR), + DS3ItemData("Jailer Trousers", 0x129F6EB8, DS3ItemCategory.ARMOR), + DS3ItemData("Grave Warden Hood", 0x12BDE780, DS3ItemCategory.ARMOR), + DS3ItemData("Grave Warden Robe", 0x12BDEB68, DS3ItemCategory.ARMOR), + DS3ItemData("Grave Warden Wrap", 0x12BDEF50, DS3ItemCategory.ARMOR), + DS3ItemData("Grave Warden Skirt", 0x12BDF338, DS3ItemCategory.ARMOR), + DS3ItemData("Worker Hat", 0x12CD29C0, DS3ItemCategory.ARMOR), + DS3ItemData("Worker Garb", 0x12CD2DA8, DS3ItemCategory.ARMOR), + DS3ItemData("Worker Gloves", 0x12CD3190, DS3ItemCategory.ARMOR), + DS3ItemData("Worker Trousers", 0x12CD3578, DS3ItemCategory.ARMOR), + DS3ItemData("Thrall Hood", 0x12D4CAE0, DS3ItemCategory.ARMOR), + DS3ItemData("Evangelist Hat", 0x12DC6C00, DS3ItemCategory.ARMOR), + DS3ItemData("Evangelist Robe", 0x12DC6FE8, DS3ItemCategory.ARMOR), + DS3ItemData("Evangelist Gloves", 0x12DC73D0, DS3ItemCategory.ARMOR), + DS3ItemData("Evangelist Trousers", 0x12DC77B8, DS3ItemCategory.ARMOR), + DS3ItemData("Scholar's Robe", 0x12E41108, DS3ItemCategory.ARMOR), + DS3ItemData("Winged Knight Helm", 0x12EBAE40, DS3ItemCategory.ARMOR), + DS3ItemData("Winged Knight Armor", 0x12EBB228, DS3ItemCategory.ARMOR), + DS3ItemData("Winged Knight Gauntlets", 0x12EBB610, DS3ItemCategory.ARMOR), + DS3ItemData("Winged Knight Leggings", 0x12EBB9F8, DS3ItemCategory.ARMOR), + DS3ItemData("Cathedral Knight Helm", 0x130291A0, DS3ItemCategory.ARMOR), + DS3ItemData("Cathedral Knight Armor", 0x13029588, DS3ItemCategory.ARMOR), + DS3ItemData("Cathedral Knight Gauntlets", 0x13029970, DS3ItemCategory.ARMOR), + DS3ItemData("Cathedral Knight Leggings", 0x13029D58, DS3ItemCategory.ARMOR), + DS3ItemData("Lothric Knight Helm", 0x13197500, DS3ItemCategory.ARMOR), + DS3ItemData("Lothric Knight Armor", 0x131978E8, DS3ItemCategory.ARMOR), + DS3ItemData("Lothric Knight Gauntlets", 0x13197CD0, DS3ItemCategory.ARMOR), + DS3ItemData("Lothric Knight Leggings", 0x131980B8, DS3ItemCategory.ARMOR), + DS3ItemData("Outrider Knight Helm", 0x1328B740, DS3ItemCategory.ARMOR), + DS3ItemData("Outrider Knight Armor", 0x1328BB28, DS3ItemCategory.ARMOR), + DS3ItemData("Outrider Knight Gauntlets", 0x1328BF10, DS3ItemCategory.ARMOR), + DS3ItemData("Outrider Knight Leggings", 0x1328C2F8, DS3ItemCategory.ARMOR), + DS3ItemData("Black Knight Helm", 0x1337F980, DS3ItemCategory.ARMOR), + DS3ItemData("Black Knight Armor", 0x1337FD68, DS3ItemCategory.ARMOR), + DS3ItemData("Black Knight Gauntlets", 0x13380150, DS3ItemCategory.ARMOR), + DS3ItemData("Black Knight Leggings", 0x13380538, DS3ItemCategory.ARMOR), + DS3ItemData("Dark Mask", 0x133F9AA0, DS3ItemCategory.ARMOR), + DS3ItemData("Dark Armor", 0x133F9E88, DS3ItemCategory.ARMOR), + DS3ItemData("Dark Gauntlets", 0x133FA270, DS3ItemCategory.ARMOR), + DS3ItemData("Dark Leggings", 0x133FA658, DS3ItemCategory.ARMOR), + DS3ItemData("Exile Mask", 0x13473BC0, DS3ItemCategory.ARMOR), + DS3ItemData("Exile Armor", 0x13473FA8, DS3ItemCategory.ARMOR), + DS3ItemData("Exile Gauntlets", 0x13474390, DS3ItemCategory.ARMOR), + DS3ItemData("Exile Leggings", 0x13474778, DS3ItemCategory.ARMOR), + DS3ItemData("Pontiff Knight Crown", 0x13567E00, DS3ItemCategory.ARMOR), + DS3ItemData("Pontiff Knight Armor", 0x135681E8, DS3ItemCategory.ARMOR), + DS3ItemData("Pontiff Knight Gauntlets", 0x135685D0, DS3ItemCategory.ARMOR), + DS3ItemData("Pontiff Knight Leggings", 0x135689B8, DS3ItemCategory.ARMOR), + DS3ItemData("Golden Crown", 0x1365C040, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonscale Armor", 0x1365C428, DS3ItemCategory.ARMOR), + DS3ItemData("Golden Bracelets", 0x1365C810, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonscale Waistcloth", 0x1365CBF8, DS3ItemCategory.ARMOR), + DS3ItemData("Wolnir's Crown", 0x136D6160, DS3ItemCategory.ARMOR), + DS3ItemData("Undead Legion Helm", 0x13750280, DS3ItemCategory.ARMOR), + DS3ItemData("Undead Legion Armor", 0x13750668, DS3ItemCategory.ARMOR), + DS3ItemData("Undead Legion Gauntlet", 0x13750A50, DS3ItemCategory.ARMOR), + DS3ItemData("Undead Legion Leggings", 0x13750E38, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Witch Helm", 0x13938700, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Witch Armor", 0x13938AE8, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Witch Gauntlets", 0x13938ED0, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Witch Leggings", 0x139392B8, DS3ItemCategory.ARMOR), + DS3ItemData("Lorian's Helm", 0x13A2C940, DS3ItemCategory.ARMOR), + DS3ItemData("Lorian's Armor", 0x13A2CD28, DS3ItemCategory.ARMOR), + DS3ItemData("Lorian's Gauntlets", 0x13A2D110, DS3ItemCategory.ARMOR), + DS3ItemData("Lorian's Leggings", 0x13A2D4F8, DS3ItemCategory.ARMOR), + DS3ItemData("Hood of Prayer", 0x13AA6A60, DS3ItemCategory.ARMOR), + DS3ItemData("Robe of Prayer", 0x13AA6E48, DS3ItemCategory.ARMOR), + DS3ItemData("Skirt of Prayer", 0x13AA7618, DS3ItemCategory.ARMOR), + DS3ItemData("Dancer's Crown", 0x13C14DC0, DS3ItemCategory.ARMOR), + DS3ItemData("Dancer's Armor", 0x13C151A8, DS3ItemCategory.ARMOR), + DS3ItemData("Dancer's Gauntlets", 0x13C15590, DS3ItemCategory.ARMOR), + DS3ItemData("Dancer's Leggings", 0x13C15978, DS3ItemCategory.ARMOR), + DS3ItemData("Gundyr's Helm", 0x13D09000, DS3ItemCategory.ARMOR), + DS3ItemData("Gundyr's Armor", 0x13D093E8, DS3ItemCategory.ARMOR), + DS3ItemData("Gundyr's Gauntlets", 0x13D097D0, DS3ItemCategory.ARMOR), + DS3ItemData("Gundyr's Leggings", 0x13D09BB8, DS3ItemCategory.ARMOR), + DS3ItemData("Archdeacon White Crown", 0x13EF1480, DS3ItemCategory.ARMOR), + DS3ItemData("Archdeacon Holy Garb", 0x13EF1868, DS3ItemCategory.ARMOR), + DS3ItemData("Archdeacon Skirt", 0x13EF2038, DS3ItemCategory.ARMOR), + DS3ItemData("Deacon Robe", 0x13F6B988, DS3ItemCategory.ARMOR), + DS3ItemData("Deacon Skirt", 0x13F6C158, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Keeper Robe", 0x140D9CE8, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Keeper Gloves", 0x140DA0D0, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Keeper Skirt", 0x140DA4B8, DS3ItemCategory.ARMOR), + DS3ItemData("Chain Helm", 0x142C1D80, DS3ItemCategory.ARMOR), + DS3ItemData("Chain Armor", 0x142C2168, DS3ItemCategory.ARMOR), + DS3ItemData("Leather Gauntlets", 0x142C2550, DS3ItemCategory.ARMOR), + DS3ItemData("Chain Leggings", 0x142C2938, DS3ItemCategory.ARMOR), + DS3ItemData("Nameless Knight Helm", 0x143B5FC0, DS3ItemCategory.ARMOR), + DS3ItemData("Nameless Knight Armor", 0x143B63A8, DS3ItemCategory.ARMOR), + DS3ItemData("Nameless Knight Gauntlets", 0x143B6790, DS3ItemCategory.ARMOR), + DS3ItemData("Nameless Knight Leggings", 0x143B6B78, DS3ItemCategory.ARMOR), + DS3ItemData("Elite Knight Helm", 0x144AA200, DS3ItemCategory.ARMOR), + DS3ItemData("Elite Knight Armor", 0x144AA5E8, DS3ItemCategory.ARMOR), + DS3ItemData("Elite Knight Gauntlets", 0x144AA9D0, DS3ItemCategory.ARMOR), + DS3ItemData("Elite Knight Leggings", 0x144AADB8, DS3ItemCategory.ARMOR), + DS3ItemData("Faraam Helm", 0x1459E440, DS3ItemCategory.ARMOR), + DS3ItemData("Faraam Armor", 0x1459E828, DS3ItemCategory.ARMOR), + DS3ItemData("Faraam Gauntlets", 0x1459EC10, DS3ItemCategory.ARMOR), + DS3ItemData("Faraam Boots", 0x1459EFF8, DS3ItemCategory.ARMOR), + DS3ItemData("Catarina Helm", 0x14692680, DS3ItemCategory.ARMOR), + DS3ItemData("Catarina Armor", 0x14692A68, DS3ItemCategory.ARMOR), + DS3ItemData("Catarina Gauntlets", 0x14692E50, DS3ItemCategory.ARMOR), + DS3ItemData("Catarina Leggings", 0x14693238, DS3ItemCategory.ARMOR), + DS3ItemData("Standard Helm", 0x1470C7A0, DS3ItemCategory.ARMOR), + DS3ItemData("Hard Leather Armor", 0x1470CB88, DS3ItemCategory.ARMOR), + DS3ItemData("Hard Leather Gauntlets", 0x1470CF70, DS3ItemCategory.ARMOR), + DS3ItemData("Hard Leather Boots", 0x1470D358, DS3ItemCategory.ARMOR), + DS3ItemData("Havel's Helm", 0x147868C0, DS3ItemCategory.ARMOR), + DS3ItemData("Havel's Armor", 0x14786CA8, DS3ItemCategory.ARMOR), + DS3ItemData("Havel's Gauntlets", 0x14787090, DS3ItemCategory.ARMOR), + DS3ItemData("Havel's Leggings", 0x14787478, DS3ItemCategory.ARMOR), + DS3ItemData("Brigand Hood", 0x148009E0, DS3ItemCategory.ARMOR), + DS3ItemData("Brigand Armor", 0x14800DC8, DS3ItemCategory.ARMOR), + DS3ItemData("Brigand Gauntlets", 0x148011B0, DS3ItemCategory.ARMOR), + DS3ItemData("Brigand Trousers", 0x14801598, DS3ItemCategory.ARMOR), + DS3ItemData("Pharis's Hat", 0x1487AB00, DS3ItemCategory.ARMOR), + DS3ItemData("Leather Armor", 0x1487AEE8, DS3ItemCategory.ARMOR), + DS3ItemData("Leather Gloves", 0x1487B2D0, DS3ItemCategory.ARMOR), + DS3ItemData("Leather Boots", 0x1487B6B8, DS3ItemCategory.ARMOR), + DS3ItemData("Ragged Mask", 0x148F4C20, DS3ItemCategory.ARMOR), + DS3ItemData("Master's Attire", 0x148F5008, DS3ItemCategory.ARMOR), + DS3ItemData("Master's Gloves", 0x148F53F0, DS3ItemCategory.ARMOR), + DS3ItemData("Loincloth", 0x148F57D8, DS3ItemCategory.ARMOR), + DS3ItemData("Old Sorcerer Hat", 0x1496ED40, DS3ItemCategory.ARMOR), + DS3ItemData("Old Sorcerer Coat", 0x1496F128, DS3ItemCategory.ARMOR), + DS3ItemData("Old Sorcerer Gauntlets", 0x1496F510, DS3ItemCategory.ARMOR), + DS3ItemData("Old Sorcerer Boots", 0x1496F8F8, DS3ItemCategory.ARMOR), + DS3ItemData("Conjurator Hood", 0x149E8E60, DS3ItemCategory.ARMOR), + DS3ItemData("Conjurator Robe", 0x149E9248, DS3ItemCategory.ARMOR), + DS3ItemData("Conjurator Manchettes", 0x149E9630, DS3ItemCategory.ARMOR), + DS3ItemData("Conjurator Boots", 0x149E9A18, DS3ItemCategory.ARMOR), + DS3ItemData("Black Leather Armor", 0x14A63368, DS3ItemCategory.ARMOR), + DS3ItemData("Black Leather Gloves", 0x14A63750, DS3ItemCategory.ARMOR), + DS3ItemData("Black Leather Boots", 0x14A63B38, DS3ItemCategory.ARMOR), + DS3ItemData("Symbol of Avarice", 0x14ADD0A0, DS3ItemCategory.ARMOR), + DS3ItemData("Creighton's Steel Mask", 0x14B571C0, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Chain Mail", 0x14B575A8, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Chain Gloves", 0x14B57990, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Chain Leggings", 0x14B57D78, DS3ItemCategory.ARMOR), + DS3ItemData("Maiden Hood", 0x14BD12E0, DS3ItemCategory.ARMOR), + DS3ItemData("Maiden Robe", 0x14BD16C8, DS3ItemCategory.ARMOR), + DS3ItemData("Maiden Gloves", 0x14BD1AB0, DS3ItemCategory.ARMOR), + DS3ItemData("Maiden Skirt", 0x14BD1E98, DS3ItemCategory.ARMOR), + DS3ItemData("Alva Helm", 0x14C4B400, DS3ItemCategory.ARMOR), + DS3ItemData("Alva Armor", 0x14C4B7E8, DS3ItemCategory.ARMOR), + DS3ItemData("Alva Gauntlets", 0x14C4BBD0, DS3ItemCategory.ARMOR), + DS3ItemData("Alva Leggings", 0x14C4BFB8, DS3ItemCategory.ARMOR), + DS3ItemData("Shadow Mask", 0x14D3F640, DS3ItemCategory.ARMOR), + DS3ItemData("Shadow Garb", 0x14D3FA28, DS3ItemCategory.ARMOR), + DS3ItemData("Shadow Gauntlets", 0x14D3FE10, DS3ItemCategory.ARMOR), + DS3ItemData("Shadow Leggings", 0x14D401F8, DS3ItemCategory.ARMOR), + DS3ItemData("Eastern Helm", 0x14E33880, DS3ItemCategory.ARMOR), + DS3ItemData("Eastern Armor", 0x14E33C68, DS3ItemCategory.ARMOR), + DS3ItemData("Eastern Gauntlets", 0x14E34050, DS3ItemCategory.ARMOR), + DS3ItemData("Eastern Leggings", 0x14E34438, DS3ItemCategory.ARMOR), + DS3ItemData("Helm of Favor", 0x14F27AC0, DS3ItemCategory.ARMOR), + DS3ItemData("Embraced Armor of Favor", 0x14F27EA8, DS3ItemCategory.ARMOR), + DS3ItemData("Gauntlets of Favor", 0x14F28290, DS3ItemCategory.ARMOR), + DS3ItemData("Leggings of Favor", 0x14F28678, DS3ItemCategory.ARMOR), + DS3ItemData("Brass Helm", 0x1501BD00, DS3ItemCategory.ARMOR), + DS3ItemData("Brass Armor", 0x1501C0E8, DS3ItemCategory.ARMOR), + DS3ItemData("Brass Gauntlets", 0x1501C4D0, DS3ItemCategory.ARMOR), + DS3ItemData("Brass Leggings", 0x1501C8B8, DS3ItemCategory.ARMOR), + DS3ItemData("Silver Knight Helm", 0x1510FF40, DS3ItemCategory.ARMOR), + DS3ItemData("Silver Knight Armor", 0x15110328, DS3ItemCategory.ARMOR), + DS3ItemData("Silver Knight Gauntlets", 0x15110710, DS3ItemCategory.ARMOR), + DS3ItemData("Silver Knight Leggings", 0x15110AF8, DS3ItemCategory.ARMOR), + DS3ItemData("Lucatiel's Mask", 0x15204180, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Vest", 0x15204568, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Gloves", 0x15204950, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Trousers", 0x15204D38, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Helm", 0x152F83C0, DS3ItemCategory.ARMOR), + DS3ItemData("Armor of the Sun", 0x152F87A8, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Bracelets", 0x152F8B90, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Leggings", 0x152F8F78, DS3ItemCategory.ARMOR), + DS3ItemData("Drakeblood Helm", 0x153EC600, DS3ItemCategory.ARMOR), + DS3ItemData("Drakeblood Armor", 0x153EC9E8, DS3ItemCategory.ARMOR), + DS3ItemData("Drakeblood Gauntlets", 0x153ECDD0, DS3ItemCategory.ARMOR), + DS3ItemData("Drakeblood Leggings", 0x153ED1B8, DS3ItemCategory.ARMOR), + DS3ItemData("Drang Armor", 0x154E0C28, DS3ItemCategory.ARMOR), + DS3ItemData("Drang Gauntlets", 0x154E1010, DS3ItemCategory.ARMOR), + DS3ItemData("Drang Shoes", 0x154E13F8, DS3ItemCategory.ARMOR), + DS3ItemData("Black Iron Helm", 0x155D4A80, DS3ItemCategory.ARMOR), + DS3ItemData("Black Iron Armor", 0x155D4E68, DS3ItemCategory.ARMOR), + DS3ItemData("Black Iron Gauntlets", 0x155D5250, DS3ItemCategory.ARMOR), + DS3ItemData("Black Iron Leggings", 0x155D5638, DS3ItemCategory.ARMOR), + DS3ItemData("Painting Guardian Hood", 0x156C8CC0, DS3ItemCategory.ARMOR), + DS3ItemData("Painting Guardian Gown", 0x156C90A8, DS3ItemCategory.ARMOR), + DS3ItemData("Painting Guardian Gloves", 0x156C9490, DS3ItemCategory.ARMOR), + DS3ItemData("Painting Guardian Waistcloth", 0x156C9878, DS3ItemCategory.ARMOR), + DS3ItemData("Wolf Knight Helm", 0x157BCF00, DS3ItemCategory.ARMOR), + DS3ItemData("Wolf Knight Armor", 0x157BD2E8, DS3ItemCategory.ARMOR), + DS3ItemData("Wolf Knight Gauntlets", 0x157BD6D0, DS3ItemCategory.ARMOR), + DS3ItemData("Wolf Knight Leggings", 0x157BDAB8, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonslayer Helm", 0x158B1140, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonslayer Armor", 0x158B1528, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonslayer Gauntlets", 0x158B1910, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonslayer Leggings", 0x158B1CF8, DS3ItemCategory.ARMOR), + DS3ItemData("Smough's Helm", 0x159A5380, DS3ItemCategory.ARMOR), + DS3ItemData("Smough's Armor", 0x159A5768, DS3ItemCategory.ARMOR), + DS3ItemData("Smough's Gauntlets", 0x159A5B50, DS3ItemCategory.ARMOR), + DS3ItemData("Smough's Leggings", 0x159A5F38, DS3ItemCategory.ARMOR), + DS3ItemData("Helm of Thorns", 0x15B8D800, DS3ItemCategory.ARMOR), + DS3ItemData("Armor of Thorns", 0x15B8DBE8, DS3ItemCategory.ARMOR), + DS3ItemData("Gauntlets of Thorns", 0x15B8DFD0, DS3ItemCategory.ARMOR), + DS3ItemData("Leggings of Thorns", 0x15B8E3B8, DS3ItemCategory.ARMOR), + DS3ItemData("Crown of Dusk", 0x15D75C80, DS3ItemCategory.ARMOR), + DS3ItemData("Antiquated Dress", 0x15D76068, DS3ItemCategory.ARMOR), + DS3ItemData("Antiquated Gloves", 0x15D76450, DS3ItemCategory.ARMOR), + DS3ItemData("Antiquated Skirt", 0x15D76838, DS3ItemCategory.ARMOR), + DS3ItemData("Karla's Pointed Hat", 0x15E69EC0, DS3ItemCategory.ARMOR), + DS3ItemData("Karla's Coat", 0x15E6A2A8, DS3ItemCategory.ARMOR), + DS3ItemData("Karla's Gloves", 0x15E6A690, DS3ItemCategory.ARMOR), + DS3ItemData("Karla's Trousers", 0x15E6AA78, DS3ItemCategory.ARMOR), # Covenants - ("Blade of the Darkmoon", 0x20002710, DS3ItemCategory.SKIP), - ("Watchdogs of Farron", 0x20002724, DS3ItemCategory.SKIP), - ("Aldrich Faithful", 0x2000272E, DS3ItemCategory.SKIP), - ("Warrior of Sunlight", 0x20002738, DS3ItemCategory.SKIP), - ("Mound-makers", 0x20002742, DS3ItemCategory.SKIP), - ("Way of Blue", 0x2000274C, DS3ItemCategory.SKIP), - ("Blue Sentinels", 0x20002756, DS3ItemCategory.SKIP), - ("Rosaria's Fingers", 0x20002760, DS3ItemCategory.SKIP), + DS3ItemData("Blade of the Darkmoon", 0x20002710, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Watchdogs of Farron", 0x20002724, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Aldrich Faithful", 0x2000272E, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Warrior of Sunlight", 0x20002738, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Mound-makers", 0x20002742, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Way of Blue", 0x2000274C, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Blue Sentinels", 0x20002756, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Rosaria's Fingers", 0x20002760, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Spears of the Church", 0x2000276A, DS3ItemCategory.UNIQUE, skip = True), # Rings - ("Life Ring", 0x20004E20, DS3ItemCategory.RING), - ("Life Ring+1", 0x20004E21, DS3ItemCategory.RING), - ("Life Ring+2", 0x20004E22, DS3ItemCategory.RING), - ("Life Ring+3", 0x20004E23, DS3ItemCategory.RING), - ("Chloranthy Ring", 0x20004E2A, DS3ItemCategory.RING), - ("Chloranthy Ring+1", 0x20004E2B, DS3ItemCategory.RING), - ("Chloranthy Ring+2", 0x20004E2C, DS3ItemCategory.RING), - ("Havel's Ring", 0x20004E34, DS3ItemCategory.RING), - ("Havel's Ring+1", 0x20004E35, DS3ItemCategory.RING), - ("Havel's Ring+2", 0x20004E36, DS3ItemCategory.RING), - ("Ring of Favor", 0x20004E3E, DS3ItemCategory.RING), - ("Ring of Favor+1", 0x20004E3F, DS3ItemCategory.RING), - ("Ring of Favor+2", 0x20004E40, DS3ItemCategory.RING), - ("Ring of Steel Protection", 0x20004E48, DS3ItemCategory.RING), - ("Ring of Steel Protection+1", 0x20004E49, DS3ItemCategory.RING), - ("Ring of Steel Protection+2", 0x20004E4A, DS3ItemCategory.RING), - ("Flame Stoneplate Ring", 0x20004E52, DS3ItemCategory.RING), - ("Flame Stoneplate Ring+1", 0x20004E53, DS3ItemCategory.RING), - ("Flame Stoneplate Ring+2", 0x20004E54, DS3ItemCategory.RING), - ("Thunder Stoneplate Ring", 0x20004E5C, DS3ItemCategory.RING), - ("Thunder Stoneplate Ring+1", 0x20004E5D, DS3ItemCategory.RING), - ("Thunder Stoneplate Ring+2", 0x20004E5E, DS3ItemCategory.RING), - ("Magic Stoneplate Ring", 0x20004E66, DS3ItemCategory.RING), - ("Magic Stoneplate Ring+1", 0x20004E67, DS3ItemCategory.RING), - ("Magic Stoneplate Ring+2", 0x20004E68, DS3ItemCategory.RING), - ("Dark Stoneplate Ring", 0x20004E70, DS3ItemCategory.RING), - ("Dark Stoneplate Ring+1", 0x20004E71, DS3ItemCategory.RING), - ("Dark Stoneplate Ring+2", 0x20004E72, DS3ItemCategory.RING), - ("Speckled Stoneplate Ring", 0x20004E7A, DS3ItemCategory.RING), - ("Speckled Stoneplate Ring+1", 0x20004E7B, DS3ItemCategory.RING), - ("Bloodbite Ring", 0x20004E84, DS3ItemCategory.RING), - ("Bloodbite Ring+1", 0x20004E85, DS3ItemCategory.RING), - ("Poisonbite Ring", 0x20004E8E, DS3ItemCategory.RING), - ("Poisonbite Ring+1", 0x20004E8F, DS3ItemCategory.RING), - ("Cursebite Ring", 0x20004E98, DS3ItemCategory.RING), - ("Fleshbite Ring", 0x20004EA2, DS3ItemCategory.RING), - ("Fleshbite Ring+1", 0x20004EA3, DS3ItemCategory.RING), - ("Wood Grain Ring", 0x20004EAC, DS3ItemCategory.RING), - ("Wood Grain Ring+1", 0x20004EAD, DS3ItemCategory.RING), - ("Wood Grain Ring+2", 0x20004EAE, DS3ItemCategory.RING), - ("Scholar Ring", 0x20004EB6, DS3ItemCategory.RING), - ("Priestess Ring", 0x20004EC0, DS3ItemCategory.RING), - ("Red Tearstone Ring", 0x20004ECA, DS3ItemCategory.RING), - ("Blue Tearstone Ring", 0x20004ED4, DS3ItemCategory.RING), - ("Wolf Ring", 0x20004EDE, DS3ItemCategory.RING), - ("Wolf Ring+1", 0x20004EDF, DS3ItemCategory.RING), - ("Wolf Ring+2", 0x20004EE0, DS3ItemCategory.RING), - ("Leo Ring", 0x20004EE8, DS3ItemCategory.RING), - ("Ring of Sacrifice", 0x20004EF2, DS3ItemCategory.RING), - ("Young Dragon Ring", 0x20004F06, DS3ItemCategory.RING), - ("Bellowing Dragoncrest Ring", 0x20004F07, DS3ItemCategory.RING), - ("Great Swamp Ring", 0x20004F10, DS3ItemCategory.RING), - ("Witch's Ring", 0x20004F11, DS3ItemCategory.RING), - ("Morne's Ring", 0x20004F1A, DS3ItemCategory.RING), - ("Ring of the Sun's First Born", 0x20004F1B, DS3ItemCategory.RING), - ("Lingering Dragoncrest Ring", 0x20004F2E, DS3ItemCategory.RING), - ("Lingering Dragoncrest Ring+1", 0x20004F2F, DS3ItemCategory.RING), - ("Lingering Dragoncrest Ring+2", 0x20004F30, DS3ItemCategory.RING), - ("Sage Ring", 0x20004F38, DS3ItemCategory.RING), - ("Sage Ring+1", 0x20004F39, DS3ItemCategory.RING), - ("Sage Ring+2", 0x20004F3A, DS3ItemCategory.RING), - ("Slumbering Dragoncrest Ring", 0x20004F42, DS3ItemCategory.RING), - ("Dusk Crown Ring", 0x20004F4C, DS3ItemCategory.RING), - ("Saint's Ring", 0x20004F56, DS3ItemCategory.RING), - ("Deep Ring", 0x20004F60, DS3ItemCategory.RING), - ("Darkmoon Ring", 0x20004F6A, DS3ItemCategory.RING), - ("Hawk Ring", 0x20004F92, DS3ItemCategory.RING), - ("Hornet Ring", 0x20004F9C, DS3ItemCategory.RING), - ("Covetous Gold Serpent Ring", 0x20004FA6, DS3ItemCategory.RING), - ("Covetous Gold Serpent Ring+1", 0x20004FA7, DS3ItemCategory.RING), - ("Covetous Gold Serpent Ring+2", 0x20004FA8, DS3ItemCategory.RING), - ("Covetous Silver Serpent Ring", 0x20004FB0, DS3ItemCategory.RING), - ("Covetous Silver Serpent Ring+1", 0x20004FB1, DS3ItemCategory.RING), - ("Covetous Silver Serpent Ring+2", 0x20004FB2, DS3ItemCategory.RING), - ("Sun Princess Ring", 0x20004FBA, DS3ItemCategory.RING), - ("Silvercat Ring", 0x20004FC4, DS3ItemCategory.RING), - ("Skull Ring", 0x20004FCE, DS3ItemCategory.RING), - ("Untrue White Ring", 0x20004FD8, DS3ItemCategory.RING), - ("Carthus Milkring", 0x20004FE2, DS3ItemCategory.RING), - ("Knight's Ring", 0x20004FEC, DS3ItemCategory.RING), - ("Hunter's Ring", 0x20004FF6, DS3ItemCategory.RING), - ("Knight Slayer's Ring", 0x20005000, DS3ItemCategory.RING), - ("Magic Clutch Ring", 0x2000500A, DS3ItemCategory.RING), - ("Lightning Clutch Ring", 0x20005014, DS3ItemCategory.RING), - ("Fire Clutch Ring", 0x2000501E, DS3ItemCategory.RING), - ("Dark Clutch Ring", 0x20005028, DS3ItemCategory.RING), - ("Flynn's Ring", 0x2000503C, DS3ItemCategory.RING), - ("Prisoner's Chain", 0x20005046, DS3ItemCategory.RING), - ("Untrue Dark Ring", 0x20005050, DS3ItemCategory.RING), - ("Obscuring Ring", 0x20005064, DS3ItemCategory.RING), - ("Ring of the Evil Eye", 0x2000506E, DS3ItemCategory.RING), - ("Ring of the Evil Eye+1", 0x2000506F, DS3ItemCategory.RING), - ("Ring of the Evil Eye+2", 0x20005070, DS3ItemCategory.RING), - ("Calamity Ring", 0x20005078, DS3ItemCategory.RING), - ("Farron Ring", 0x20005082, DS3ItemCategory.RING), - ("Aldrich's Ruby", 0x2000508C, DS3ItemCategory.RING), - ("Aldrich's Sapphire", 0x20005096, DS3ItemCategory.RING), - ("Lloyd's Sword Ring", 0x200050B4, DS3ItemCategory.RING), - ("Lloyd's Shield Ring", 0x200050BE, DS3ItemCategory.RING), - ("Estus Ring", 0x200050DC, DS3ItemCategory.RING), - ("Ashen Estus Ring", 0x200050E6, DS3ItemCategory.RING), - ("Carthus Bloodring", 0x200050FA, DS3ItemCategory.RING), - ("Reversal Ring", 0x20005104, DS3ItemCategory.RING), - ("Pontiff's Right Eye", 0x2000510E, DS3ItemCategory.RING), - ("Pontiff's Left Eye", 0x20005136, DS3ItemCategory.RING), - ("Dragonscale Ring", 0x2000515E, DS3ItemCategory.RING), + DS3ItemData("Life Ring", 0x20004E20, DS3ItemCategory.RING), + DS3ItemData("Life Ring+1", 0x20004E21, DS3ItemCategory.RING), + DS3ItemData("Life Ring+2", 0x20004E22, DS3ItemCategory.RING), + DS3ItemData("Life Ring+3", 0x20004E23, DS3ItemCategory.RING), + DS3ItemData("Chloranthy Ring", 0x20004E2A, DS3ItemCategory.RING, + useful_if = UsefulIf.BASE), + DS3ItemData("Chloranthy Ring+1", 0x20004E2B, DS3ItemCategory.RING), + DS3ItemData("Chloranthy Ring+2", 0x20004E2C, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_DLC), + DS3ItemData("Havel's Ring", 0x20004E34, DS3ItemCategory.RING, + useful_if = UsefulIf.BASE), + DS3ItemData("Havel's Ring+1", 0x20004E35, DS3ItemCategory.RING), + DS3ItemData("Havel's Ring+2", 0x20004E36, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_DLC), + DS3ItemData("Ring of Favor", 0x20004E3E, DS3ItemCategory.RING, + useful_if = UsefulIf.BASE), + DS3ItemData("Ring of Favor+1", 0x20004E3F, DS3ItemCategory.RING), + DS3ItemData("Ring of Favor+2", 0x20004E40, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_DLC), + DS3ItemData("Ring of Steel Protection", 0x20004E48, DS3ItemCategory.RING, + useful_if = UsefulIf.BASE), + DS3ItemData("Ring of Steel Protection+1", 0x20004E49, DS3ItemCategory.RING), + DS3ItemData("Ring of Steel Protection+2", 0x20004E4A, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_DLC), + DS3ItemData("Flame Stoneplate Ring", 0x20004E52, DS3ItemCategory.RING), + DS3ItemData("Flame Stoneplate Ring+1", 0x20004E53, DS3ItemCategory.RING), + DS3ItemData("Flame Stoneplate Ring+2", 0x20004E54, DS3ItemCategory.RING), + DS3ItemData("Thunder Stoneplate Ring", 0x20004E5C, DS3ItemCategory.RING), + DS3ItemData("Thunder Stoneplate Ring+1", 0x20004E5D, DS3ItemCategory.RING), + DS3ItemData("Thunder Stoneplate Ring+2", 0x20004E5E, DS3ItemCategory.RING), + DS3ItemData("Magic Stoneplate Ring", 0x20004E66, DS3ItemCategory.RING), + DS3ItemData("Magic Stoneplate Ring+1", 0x20004E67, DS3ItemCategory.RING), + DS3ItemData("Magic Stoneplate Ring+2", 0x20004E68, DS3ItemCategory.RING), + DS3ItemData("Dark Stoneplate Ring", 0x20004E70, DS3ItemCategory.RING), + DS3ItemData("Dark Stoneplate Ring+1", 0x20004E71, DS3ItemCategory.RING), + DS3ItemData("Dark Stoneplate Ring+2", 0x20004E72, DS3ItemCategory.RING), + DS3ItemData("Speckled Stoneplate Ring", 0x20004E7A, DS3ItemCategory.RING), + DS3ItemData("Speckled Stoneplate Ring+1", 0x20004E7B, DS3ItemCategory.RING), + DS3ItemData("Bloodbite Ring", 0x20004E84, DS3ItemCategory.RING), + DS3ItemData("Bloodbite Ring+1", 0x20004E85, DS3ItemCategory.RING), + DS3ItemData("Poisonbite Ring", 0x20004E8E, DS3ItemCategory.RING), + DS3ItemData("Poisonbite Ring+1", 0x20004E8F, DS3ItemCategory.RING), + DS3ItemData("Cursebite Ring", 0x20004E98, DS3ItemCategory.RING), + DS3ItemData("Fleshbite Ring", 0x20004EA2, DS3ItemCategory.RING), + DS3ItemData("Fleshbite Ring+1", 0x20004EA3, DS3ItemCategory.RING), + DS3ItemData("Wood Grain Ring", 0x20004EAC, DS3ItemCategory.RING), + DS3ItemData("Wood Grain Ring+1", 0x20004EAD, DS3ItemCategory.RING), + DS3ItemData("Wood Grain Ring+2", 0x20004EAE, DS3ItemCategory.RING), + DS3ItemData("Scholar Ring", 0x20004EB6, DS3ItemCategory.RING), + DS3ItemData("Priestess Ring", 0x20004EC0, DS3ItemCategory.RING), + DS3ItemData("Red Tearstone Ring", 0x20004ECA, DS3ItemCategory.RING), + DS3ItemData("Blue Tearstone Ring", 0x20004ED4, DS3ItemCategory.RING), + DS3ItemData("Wolf Ring", 0x20004EDE, DS3ItemCategory.RING, + inject = True), # Covenant reward + DS3ItemData("Wolf Ring+1", 0x20004EDF, DS3ItemCategory.RING), + DS3ItemData("Wolf Ring+2", 0x20004EE0, DS3ItemCategory.RING), + DS3ItemData("Leo Ring", 0x20004EE8, DS3ItemCategory.RING), + DS3ItemData("Ring of Sacrifice", 0x20004EF2, DS3ItemCategory.RING, filler = True), + DS3ItemData("Young Dragon Ring", 0x20004F06, DS3ItemCategory.RING), + DS3ItemData("Bellowing Dragoncrest Ring", 0x20004F07, DS3ItemCategory.RING), + DS3ItemData("Great Swamp Ring", 0x20004F10, DS3ItemCategory.RING), + DS3ItemData("Witch's Ring", 0x20004F11, DS3ItemCategory.RING), + DS3ItemData("Morne's Ring", 0x20004F1A, DS3ItemCategory.RING), + DS3ItemData("Ring of the Sun's First Born", 0x20004F1B, DS3ItemCategory.RING), + DS3ItemData("Lingering Dragoncrest Ring", 0x20004F2E, DS3ItemCategory.RING), + DS3ItemData("Lingering Dragoncrest Ring+1", 0x20004F2F, DS3ItemCategory.RING), + DS3ItemData("Lingering Dragoncrest Ring+2", 0x20004F30, DS3ItemCategory.RING), + DS3ItemData("Sage Ring", 0x20004F38, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_NGP), + DS3ItemData("Sage Ring+1", 0x20004F39, DS3ItemCategory.RING), + DS3ItemData("Sage Ring+2", 0x20004F3A, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Slumbering Dragoncrest Ring", 0x20004F42, DS3ItemCategory.RING), + DS3ItemData("Dusk Crown Ring", 0x20004F4C, DS3ItemCategory.RING), + DS3ItemData("Saint's Ring", 0x20004F56, DS3ItemCategory.RING), + DS3ItemData("Deep Ring", 0x20004F60, DS3ItemCategory.RING), + DS3ItemData("Darkmoon Ring", 0x20004F6A, DS3ItemCategory.RING, + inject = True), # Covenant reward + DS3ItemData("Hawk Ring", 0x20004F92, DS3ItemCategory.RING), + DS3ItemData("Hornet Ring", 0x20004F9C, DS3ItemCategory.RING), + DS3ItemData("Covetous Gold Serpent Ring", 0x20004FA6, DS3ItemCategory.RING), + DS3ItemData("Covetous Gold Serpent Ring+1", 0x20004FA7, DS3ItemCategory.RING), + DS3ItemData("Covetous Gold Serpent Ring+2", 0x20004FA8, DS3ItemCategory.RING), + DS3ItemData("Covetous Silver Serpent Ring", 0x20004FB0, DS3ItemCategory.RING, + useful_if = UsefulIf.BASE), + DS3ItemData("Covetous Silver Serpent Ring+1", 0x20004FB1, DS3ItemCategory.RING), + DS3ItemData("Covetous Silver Serpent Ring+2", 0x20004FB2, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_DLC), + DS3ItemData("Sun Princess Ring", 0x20004FBA, DS3ItemCategory.RING), + DS3ItemData("Silvercat Ring", 0x20004FC4, DS3ItemCategory.RING), + DS3ItemData("Skull Ring", 0x20004FCE, DS3ItemCategory.RING), + DS3ItemData("Untrue White Ring", 0x20004FD8, DS3ItemCategory.RING, skip = True), + DS3ItemData("Carthus Milkring", 0x20004FE2, DS3ItemCategory.RING), + DS3ItemData("Knight's Ring", 0x20004FEC, DS3ItemCategory.RING), + DS3ItemData("Hunter's Ring", 0x20004FF6, DS3ItemCategory.RING), + DS3ItemData("Knight Slayer's Ring", 0x20005000, DS3ItemCategory.RING), + DS3ItemData("Magic Clutch Ring", 0x2000500A, DS3ItemCategory.RING), + DS3ItemData("Lightning Clutch Ring", 0x20005014, DS3ItemCategory.RING), + DS3ItemData("Fire Clutch Ring", 0x2000501E, DS3ItemCategory.RING), + DS3ItemData("Dark Clutch Ring", 0x20005028, DS3ItemCategory.RING), + DS3ItemData("Flynn's Ring", 0x2000503C, DS3ItemCategory.RING), + DS3ItemData("Prisoner's Chain", 0x20005046, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Untrue Dark Ring", 0x20005050, DS3ItemCategory.RING), + DS3ItemData("Obscuring Ring", 0x20005064, DS3ItemCategory.RING), + DS3ItemData("Ring of the Evil Eye", 0x2000506E, DS3ItemCategory.RING), + DS3ItemData("Ring of the Evil Eye+1", 0x2000506F, DS3ItemCategory.RING), + DS3ItemData("Ring of the Evil Eye+2", 0x20005070, DS3ItemCategory.RING), + DS3ItemData("Calamity Ring", 0x20005078, DS3ItemCategory.RING), + DS3ItemData("Farron Ring", 0x20005082, DS3ItemCategory.RING), + DS3ItemData("Aldrich's Ruby", 0x2000508C, DS3ItemCategory.RING), + DS3ItemData("Aldrich's Sapphire", 0x20005096, DS3ItemCategory.RING), + DS3ItemData("Lloyd's Sword Ring", 0x200050B4, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Lloyd's Shield Ring", 0x200050BE, DS3ItemCategory.RING), + DS3ItemData("Estus Ring", 0x200050DC, DS3ItemCategory.RING), + DS3ItemData("Ashen Estus Ring", 0x200050E6, DS3ItemCategory.RING), + DS3ItemData("Horsehoof Ring", 0x200050F0, DS3ItemCategory.RING), + DS3ItemData("Carthus Bloodring", 0x200050FA, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Reversal Ring", 0x20005104, DS3ItemCategory.RING), + DS3ItemData("Pontiff's Right Eye", 0x2000510E, DS3ItemCategory.RING), + DS3ItemData("Pontiff's Left Eye", 0x20005136, DS3ItemCategory.RING), + DS3ItemData("Dragonscale Ring", 0x2000515E, DS3ItemCategory.RING), # Items - ("Roster of Knights", 0x4000006C, DS3ItemCategory.SKIP), - ("Cracked Red Eye Orb", 0x4000006F, DS3ItemCategory.SKIP), - ("Divine Blessing", 0x400000F0, DS3ItemCategory.MISC), - ("Hidden Blessing", 0x400000F1, DS3ItemCategory.MISC), - ("Silver Pendant", 0x400000F2, DS3ItemCategory.SKIP), - ("Green Blossom", 0x40000104, DS3ItemCategory.MISC), - ("Budding Green Blossom", 0x40000106, DS3ItemCategory.MISC), - ("Bloodred Moss Clump", 0x4000010E, DS3ItemCategory.SKIP), - ("Purple Moss Clump", 0x4000010F, DS3ItemCategory.MISC), - ("Blooming Purple Moss Clump", 0x40000110, DS3ItemCategory.SKIP), - ("Purging Stone", 0x40000112, DS3ItemCategory.SKIP), - ("Rime-blue Moss Clump", 0x40000114, DS3ItemCategory.SKIP), - ("Repair Powder", 0x40000118, DS3ItemCategory.MISC), - ("Kukri", 0x40000122, DS3ItemCategory.SKIP), - ("Firebomb", 0x40000124, DS3ItemCategory.MISC), - ("Dung Pie", 0x40000125, DS3ItemCategory.SKIP), - ("Alluring Skull", 0x40000126, DS3ItemCategory.MISC), - ("Undead Hunter Charm", 0x40000128, DS3ItemCategory.MISC), - ("Black Firebomb", 0x40000129, DS3ItemCategory.MISC), - ("Rope Firebomb", 0x4000012B, DS3ItemCategory.MISC), - ("Lightning Urn", 0x4000012C, DS3ItemCategory.MISC), - ("Rope Black Firebomb", 0x4000012E, DS3ItemCategory.MISC), - ("Stalk Dung Pie", 0x4000012F, DS3ItemCategory.SKIP), - ("Duel Charm", 0x40000130, DS3ItemCategory.MISC), - ("Throwing Knife", 0x40000136, DS3ItemCategory.MISC), - ("Poison Throwing Knife", 0x40000137, DS3ItemCategory.MISC), - ("Charcoal Pine Resin", 0x4000014A, DS3ItemCategory.MISC), - ("Gold Pine Resin", 0x4000014B, DS3ItemCategory.MISC), - ("Human Pine Resin", 0x4000014E, DS3ItemCategory.MISC), - ("Carthus Rouge", 0x4000014F, DS3ItemCategory.MISC), - ("Pale Pine Resin", 0x40000150, DS3ItemCategory.MISC), - ("Charcoal Pine Bundle", 0x40000154, DS3ItemCategory.MISC), - ("Gold Pine Bundle", 0x40000155, DS3ItemCategory.MISC), - ("Rotten Pine Resin", 0x40000157, DS3ItemCategory.MISC), - ("Homeward Bone", 0x4000015E, DS3ItemCategory.MISC), - ("Coiled Sword Fragment", 0x4000015F, DS3ItemCategory.MISC), - ("Wolf's Blood Swordgrass", 0x4000016E, DS3ItemCategory.MISC), - ("Human Dregs", 0x4000016F, DS3ItemCategory.SKIP), - ("Forked Pale Tongue", 0x40000170, DS3ItemCategory.MISC), - ("Proof of a Concord Well Kept", 0x40000171, DS3ItemCategory.SKIP), - ("Prism Stone", 0x40000172, DS3ItemCategory.SKIP), - ("Binoculars", 0x40000173, DS3ItemCategory.MISC), - ("Proof of a Concord Kept", 0x40000174, DS3ItemCategory.SKIP), - ("Pale Tongue", 0x40000175, DS3ItemCategory.MISC), - ("Vertebra Shackle", 0x40000176, DS3ItemCategory.SKIP), - ("Sunlight Medal", 0x40000177, DS3ItemCategory.SKIP), - ("Dragon Head Stone", 0x40000179, DS3ItemCategory.MISC), - ("Dragon Torso Stone", 0x4000017A, DS3ItemCategory.MISC), - ("Rubbish", 0x4000017C, DS3ItemCategory.SKIP), - ("Dried Finger", 0x40000181, DS3ItemCategory.SKIP), - ("Twinkling Dragon Head Stone", 0x40000183, DS3ItemCategory.MISC), - ("Twinkling Dragon Torso Stone", 0x40000184, DS3ItemCategory.MISC), - ("Fire Keeper Soul", 0x40000186, DS3ItemCategory.MISC), - ("Fading Soul", 0x40000190, DS3ItemCategory.MISC), - ("Soul of a Deserted Corpse", 0x40000191, DS3ItemCategory.MISC), - ("Large Soul of a Deserted Corpse", 0x40000192, DS3ItemCategory.MISC), - ("Soul of an Unknown Traveler", 0x40000193, DS3ItemCategory.MISC), - ("Large Soul of an Unknown Traveler", 0x40000194, DS3ItemCategory.MISC), - ("Soul of a Nameless Soldier", 0x40000195, DS3ItemCategory.MISC), - ("Large Soul of a Nameless Soldier", 0x40000196, DS3ItemCategory.MISC), - ("Soul of a Weary Warrior", 0x40000197, DS3ItemCategory.MISC), - ("Large Soul of a Weary Warrior", 0x40000198, DS3ItemCategory.MISC), - ("Soul of a Crestfallen Knight", 0x40000199, DS3ItemCategory.MISC), - ("Large Soul of a Crestfallen Knight", 0x4000019A, DS3ItemCategory.MISC), - ("Soul of a Proud Paladin", 0x4000019B, DS3ItemCategory.MISC), - ("Large Soul of a Proud Paladin", 0x4000019C, DS3ItemCategory.MISC), - ("Soul of an Intrepid Hero", 0x4000019D, DS3ItemCategory.MISC), - ("Large Soul of an Intrepid Hero", 0x4000019E, DS3ItemCategory.MISC), - ("Soul of a Seasoned Warrior", 0x4000019F, DS3ItemCategory.MISC), - ("Large Soul of a Seasoned Warrior", 0x400001A0, DS3ItemCategory.MISC), - ("Soul of an Old Hand", 0x400001A1, DS3ItemCategory.MISC), - ("Soul of a Venerable Old Hand", 0x400001A2, DS3ItemCategory.MISC), - ("Soul of a Champion", 0x400001A3, DS3ItemCategory.MISC), - ("Soul of a Great Champion", 0x400001A4, DS3ItemCategory.MISC), - ("Seed of a Giant Tree", 0x400001B8, DS3ItemCategory.SKIP), - ("Young White Branch", 0x400001C6, DS3ItemCategory.SKIP), - ("Rusted Coin", 0x400001C7, DS3ItemCategory.MISC), - ("Siegbräu", 0x400001C8, DS3ItemCategory.SKIP), - ("Rusted Gold Coin", 0x400001C9, DS3ItemCategory.MISC), - ("Blue Bug Pellet", 0x400001CA, DS3ItemCategory.SKIP), - ("Red Bug Pellet", 0x400001CB, DS3ItemCategory.SKIP), - ("Yellow Bug Pellet", 0x400001CC, DS3ItemCategory.SKIP), - ("Black Bug Pellet", 0x400001CD, DS3ItemCategory.SKIP), - ("Young White Branch", 0x400001CF, DS3ItemCategory.SKIP), - ("Dark Sigil", 0x400001EA, DS3ItemCategory.SKIP), - ("Ember", 0x400001F4, DS3ItemCategory.MISC), - ("Soul of Champion Gundyr", 0x400002C8, DS3ItemCategory.BOSS), - ("Soul of the Dancer", 0x400002CA, DS3ItemCategory.BOSS), - ("Soul of a Crystal Sage", 0x400002CB, DS3ItemCategory.BOSS), - ("Soul of the Blood of the Wolf", 0x400002CD, DS3ItemCategory.BOSS), - ("Soul of Consumed Oceiros", 0x400002CE, DS3ItemCategory.BOSS), - ("Soul of Boreal Valley Vordt", 0x400002CF, DS3ItemCategory.BOSS), - ("Soul of the Old Demon King", 0x400002D0, DS3ItemCategory.BOSS), - ("Soul of Dragonslayer Armour", 0x400002D1, DS3ItemCategory.BOSS), - ("Soul of the Nameless King", 0x400002D2, DS3ItemCategory.BOSS), - ("Soul of Pontiff Sulyvahn", 0x400002D4, DS3ItemCategory.BOSS), - ("Soul of Aldrich", 0x400002D5, DS3ItemCategory.BOSS), - ("Soul of High Lord Wolnir", 0x400002D6, DS3ItemCategory.BOSS), - ("Soul of the Rotted Greatwood", 0x400002D7, DS3ItemCategory.BOSS), - ("Soul of Rosaria", 0x400002D8, DS3ItemCategory.MISC), - ("Soul of the Deacons of the Deep", 0x400002D9, DS3ItemCategory.BOSS), - ("Soul of the Twin Princes", 0x400002DB, DS3ItemCategory.BOSS), - ("Soul of Yhorm the Giant", 0x400002DC, DS3ItemCategory.BOSS), - ("Soul of the Lords", 0x400002DD, DS3ItemCategory.MISC), - ("Soul of a Demon", 0x400002E3, DS3ItemCategory.BOSS), - ("Soul of a Stray Demon", 0x400002E7, DS3ItemCategory.BOSS), - ("Titanite Shard", 0x400003E8, DS3ItemCategory.MISC), - ("Large Titanite Shard", 0x400003E9, DS3ItemCategory.MISC), - ("Titanite Chunk", 0x400003EA, DS3ItemCategory.MISC), - ("Titanite Slab", 0x400003EB, DS3ItemCategory.MISC), - ("Titanite Scale", 0x400003FC, DS3ItemCategory.MISC), - ("Twinkling Titanite", 0x40000406, DS3ItemCategory.MISC), - ("Heavy Gem", 0x4000044C, DS3ItemCategory.MISC), - ("Sharp Gem", 0x40000456, DS3ItemCategory.MISC), - ("Refined Gem", 0x40000460, DS3ItemCategory.MISC), - ("Crystal Gem", 0x4000046A, DS3ItemCategory.MISC), - ("Simple Gem", 0x40000474, DS3ItemCategory.MISC), - ("Fire Gem", 0x4000047E, DS3ItemCategory.MISC), - ("Chaos Gem", 0x40000488, DS3ItemCategory.MISC), - ("Lightning Gem", 0x40000492, DS3ItemCategory.MISC), - ("Deep Gem", 0x4000049C, DS3ItemCategory.MISC), - ("Dark Gem", 0x400004A6, DS3ItemCategory.MISC), - ("Poison Gem", 0x400004B0, DS3ItemCategory.MISC), - ("Blood Gem", 0x400004BA, DS3ItemCategory.MISC), - ("Raw Gem", 0x400004C4, DS3ItemCategory.MISC), - ("Blessed Gem", 0x400004CE, DS3ItemCategory.MISC), - ("Hollow Gem", 0x400004D8, DS3ItemCategory.MISC), - ("Shriving Stone", 0x400004E2, DS3ItemCategory.MISC), - ("Lift Chamber Key", 0x400007D1, DS3ItemCategory.KEY), - ("Small Doll", 0x400007D5, DS3ItemCategory.KEY), - ("Jailbreaker's Key", 0x400007D7, DS3ItemCategory.KEY), - ("Jailer's Key Ring", 0x400007D8, DS3ItemCategory.KEY), - ("Grave Key", 0x400007D9, DS3ItemCategory.KEY), - ("Cell Key", 0x400007DA, DS3ItemCategory.KEY), - ("Dungeon Ground Floor Key", 0x400007DB, DS3ItemCategory.KEY), - ("Old Cell Key", 0x400007DC, DS3ItemCategory.KEY), - ("Grand Archives Key", 0x400007DE, DS3ItemCategory.KEY), - ("Tower Key", 0x400007DF, DS3ItemCategory.KEY), - ("Small Lothric Banner", 0x40000836, DS3ItemCategory.KEY), - ("Farron Coal", 0x40000837, DS3ItemCategory.MISC), - ("Sage's Coal", 0x40000838, DS3ItemCategory.MISC), - ("Giant's Coal", 0x40000839, DS3ItemCategory.MISC), - ("Profaned Coal", 0x4000083A, DS3ItemCategory.MISC), - ("Mortician's Ashes", 0x4000083B, DS3ItemCategory.MISC), - ("Dreamchaser's Ashes", 0x4000083C, DS3ItemCategory.MISC), - ("Paladin's Ashes", 0x4000083D, DS3ItemCategory.MISC), - ("Grave Warden's Ashes", 0x4000083E, DS3ItemCategory.MISC), - ("Greirat's Ashes", 0x4000083F, DS3ItemCategory.MISC), - ("Orbeck's Ashes", 0x40000840, DS3ItemCategory.MISC), - ("Cornyx's Ashes", 0x40000841, DS3ItemCategory.MISC), - ("Karla's Ashes", 0x40000842, DS3ItemCategory.MISC), - ("Irina's Ashes", 0x40000843, DS3ItemCategory.MISC), - ("Yuria's Ashes", 0x40000844, DS3ItemCategory.MISC), - ("Basin of Vows", 0x40000845, DS3ItemCategory.KEY), - ("Loretta's Bone", 0x40000846, DS3ItemCategory.KEY), - ("Braille Divine Tome of Carim", 0x40000847, DS3ItemCategory.MISC), - ("Braille Divine Tome of Lothric", 0x40000848, DS3ItemCategory.MISC), - ("Cinders of a Lord - Abyss Watcher", 0x4000084B, DS3ItemCategory.KEY), - ("Cinders of a Lord - Aldrich", 0x4000084C, DS3ItemCategory.KEY), - ("Cinders of a Lord - Yhorm the Giant", 0x4000084D, DS3ItemCategory.KEY), - ("Cinders of a Lord - Lothric Prince", 0x4000084E, DS3ItemCategory.KEY), - ("Great Swamp Pyromancy Tome", 0x4000084F, DS3ItemCategory.MISC), - ("Carthus Pyromancy Tome", 0x40000850, DS3ItemCategory.MISC), - ("Izalith Pyromancy Tome", 0x40000851, DS3ItemCategory.MISC), - ("Quelana Pyromancy Tome", 0x40000852, DS3ItemCategory.MISC), - ("Grave Warden Pyromancy Tome", 0x40000853, DS3ItemCategory.MISC), - ("Sage's Scroll", 0x40000854, DS3ItemCategory.MISC), - ("Logan's Scroll", 0x40000855, DS3ItemCategory.MISC), - ("Crystal Scroll", 0x40000856, DS3ItemCategory.MISC), - ("Transposing Kiln", 0x40000857, DS3ItemCategory.MISC), - ("Coiled Sword", 0x40000859, DS3ItemCategory.SKIP), # Useless - ("Eyes of a Fire Keeper", 0x4000085A, DS3ItemCategory.KEY), - ("Sword of Avowal", 0x4000085B, DS3ItemCategory.KEY), - ("Golden Scroll", 0x4000085C, DS3ItemCategory.MISC), - ("Estus Shard", 0x4000085D, DS3ItemCategory.MISC), - ("Hawkwood's Swordgrass", 0x4000085E, DS3ItemCategory.SKIP), - ("Undead Bone Shard", 0x4000085F, DS3ItemCategory.MISC), - ("Deep Braille Divine Tome", 0x40000860, DS3ItemCategory.MISC), - ("Londor Braille Divine Tome", 0x40000861, DS3ItemCategory.MISC), - ("Excrement-covered Ashes", 0x40000862, DS3ItemCategory.MISC), - ("Prisoner Chief's Ashes", 0x40000863, DS3ItemCategory.MISC), - ("Xanthous Ashes", 0x40000864, DS3ItemCategory.MISC), - ("Hollow's Ashes", 0x40000865, DS3ItemCategory.MISC), - ("Patches' Ashes", 0x40000866, DS3ItemCategory.MISC), - ("Dragon Chaser's Ashes", 0x40000867, DS3ItemCategory.MISC), - ("Easterner's Ashes", 0x40000868, DS3ItemCategory.MISC), + DS3ItemData("White Sign Soapstone", 0x40000064, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Red Sign Soapstone", 0x40000066, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Red Eye Orb", 0x40000066, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Roster of Knights", 0x4000006C, DS3ItemCategory.UNIQUE, skip = True), + *DS3ItemData("Cracked Red Eye Orb", 0x4000006F, DS3ItemCategory.MISC, skip = True).counts([5]), + DS3ItemData("Black Eye Orb", 0x40000073, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Divine Blessing", 0x400000F0, DS3ItemCategory.MISC), + DS3ItemData("Hidden Blessing", 0x400000F1, DS3ItemCategory.MISC), + *DS3ItemData("Green Blossom", 0x40000104, DS3ItemCategory.MISC, filler = True).counts([2, 3, 4]), + *DS3ItemData("Budding Green Blossom", 0x40000106, DS3ItemCategory.MISC).counts([2, 3]), + *DS3ItemData("Bloodred Moss Clump", 0x4000010E, DS3ItemCategory.MISC, filler = True).counts([3]), + *DS3ItemData("Purple Moss Clump", 0x4000010F, DS3ItemCategory.MISC, filler = True).counts([2, 3, 4]), + *DS3ItemData("Blooming Purple Moss Clump", 0x40000110, DS3ItemCategory.MISC).counts([3]), + *DS3ItemData("Purging Stone", 0x40000112, DS3ItemCategory.MISC, skip = True).counts([2, 3]), + *DS3ItemData("Rime-blue Moss Clump", 0x40000114, DS3ItemCategory.MISC, filler = True).counts([2, 4]), + *DS3ItemData("Repair Powder", 0x40000118, DS3ItemCategory.MISC, filler = True).counts([2, 3, 4]), + *DS3ItemData("Kukri", 0x40000122, DS3ItemCategory.MISC).counts([8, 9]), + DS3ItemData("Kukri x5", 0x40000122, DS3ItemCategory.MISC, count = 5, filler = True), + *DS3ItemData("Firebomb", 0x40000124, DS3ItemCategory.MISC).counts([3, 5, 6]), + DS3ItemData("Firebomb x2", 0x40000124, DS3ItemCategory.MISC, count = 2, filler = True), + *DS3ItemData("Dung Pie", 0x40000125, DS3ItemCategory.MISC).counts([2, 4]), + DS3ItemData("Dung Pie x3", 0x40000125, DS3ItemCategory.MISC, count = 3, filler = True), + *DS3ItemData("Alluring Skull", 0x40000126, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + *DS3ItemData("Undead Hunter Charm", 0x40000128, DS3ItemCategory.MISC).counts([2, 3]), + *DS3ItemData("Black Firebomb", 0x40000129, DS3ItemCategory.MISC, filler = True).counts([2, 3, 4]), + DS3ItemData("Rope Firebomb", 0x4000012B, DS3ItemCategory.MISC), + *DS3ItemData("Lightning Urn", 0x4000012C, DS3ItemCategory.MISC, filler = True).counts([3, 4, 6]), + DS3ItemData("Rope Black Firebomb", 0x4000012E, DS3ItemCategory.MISC), + *DS3ItemData("Stalk Dung Pie", 0x4000012F, DS3ItemCategory.MISC).counts([6]), + *DS3ItemData("Duel Charm", 0x40000130, DS3ItemCategory.MISC).counts([3]), + *DS3ItemData("Throwing Knife", 0x40000136, DS3ItemCategory.MISC).counts([6, 8]), + DS3ItemData("Throwing Knife x5", 0x40000136, DS3ItemCategory.MISC, count = 5, filler = True), + DS3ItemData("Poison Throwing Knife", 0x40000137, DS3ItemCategory.MISC), + *DS3ItemData("Charcoal Pine Resin", 0x4000014A, DS3ItemCategory.MISC, filler = True).counts([2]), + *DS3ItemData("Gold Pine Resin", 0x4000014B, DS3ItemCategory.MISC, filler = True).counts([2]), + *DS3ItemData("Human Pine Resin", 0x4000014E, DS3ItemCategory.MISC, filler = True).counts([2, 4]), + *DS3ItemData("Carthus Rouge", 0x4000014F, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + *DS3ItemData("Pale Pine Resin", 0x40000150, DS3ItemCategory.MISC, filler = True).counts([2]), + *DS3ItemData("Charcoal Pine Bundle", 0x40000154, DS3ItemCategory.MISC).counts([2]), + *DS3ItemData("Gold Pine Bundle", 0x40000155, DS3ItemCategory.MISC).counts([6]), + *DS3ItemData("Rotten Pine Resin", 0x40000157, DS3ItemCategory.MISC).counts([2, 4]), + *DS3ItemData("Homeward Bone", 0x4000015E, DS3ItemCategory.MISC, filler = True).counts([2, 3, 6]), + DS3ItemData("Coiled Sword Fragment", 0x4000015F, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Wolf's Blood Swordgrass", 0x4000016E, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Human Dregs", 0x4000016F, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Forked Pale Tongue", 0x40000170, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Proof of a Concord Well Kept", 0x40000171, DS3ItemCategory.MISC, skip = True), + *DS3ItemData("Prism Stone", 0x40000172, DS3ItemCategory.MISC, skip = True).counts([4, 6, 10]), + DS3ItemData("Binoculars", 0x40000173, DS3ItemCategory.MISC), + DS3ItemData("Proof of a Concord Kept", 0x40000174, DS3ItemCategory.MISC, skip = True), + # One is needed for Leonhard's quest, others are useful for restatting. + DS3ItemData("Pale Tongue", 0x40000175, DS3ItemCategory.MISC, + classification = ItemClassification.progression), + DS3ItemData("Vertebra Shackle", 0x40000176, DS3ItemCategory.MISC, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Sunlight Medal", 0x40000177, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Dragon Head Stone", 0x40000179, DS3ItemCategory.UNIQUE), + DS3ItemData("Dragon Torso Stone", 0x4000017A, DS3ItemCategory.UNIQUE), + DS3ItemData("Rubbish", 0x4000017C, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Dried Finger", 0x40000181, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Twinkling Dragon Head Stone", 0x40000183, DS3ItemCategory.UNIQUE), + DS3ItemData("Twinkling Dragon Torso Stone", 0x40000184, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Fire Keeper Soul", 0x40000186, DS3ItemCategory.UNIQUE), + # Allow souls up to 2k in value to be used as filler + DS3ItemData("Fading Soul", 0x40000190, DS3ItemCategory.SOUL, souls = 50), + DS3ItemData("Soul of a Deserted Corpse", 0x40000191, DS3ItemCategory.SOUL, souls = 200), + DS3ItemData("Large Soul of a Deserted Corpse", 0x40000192, DS3ItemCategory.SOUL, souls = 400), + DS3ItemData("Soul of an Unknown Traveler", 0x40000193, DS3ItemCategory.SOUL, souls = 800), + DS3ItemData("Large Soul of an Unknown Traveler", 0x40000194, DS3ItemCategory.SOUL, souls = 1000), + DS3ItemData("Soul of a Nameless Soldier", 0x40000195, DS3ItemCategory.SOUL, souls = 2000), + DS3ItemData("Large Soul of a Nameless Soldier", 0x40000196, DS3ItemCategory.SOUL, souls = 3000), + DS3ItemData("Soul of a Weary Warrior", 0x40000197, DS3ItemCategory.SOUL, souls = 5000), + DS3ItemData("Large Soul of a Weary Warrior", 0x40000198, DS3ItemCategory.SOUL, souls = 8000), + DS3ItemData("Soul of a Crestfallen Knight", 0x40000199, DS3ItemCategory.SOUL, souls = 10000), + DS3ItemData("Large Soul of a Crestfallen Knight", 0x4000019A, DS3ItemCategory.SOUL, souls = 20000), + DS3ItemData("Soul of a Proud Paladin", 0x4000019B, DS3ItemCategory.SOUL, souls = 500), + DS3ItemData("Large Soul of a Proud Paladin", 0x4000019C, DS3ItemCategory.SOUL, souls = 1000), + DS3ItemData("Soul of an Intrepid Hero", 0x4000019D, DS3ItemCategory.SOUL, souls = 2000), + DS3ItemData("Large Soul of an Intrepid Hero", 0x4000019E, DS3ItemCategory.SOUL, souls = 2500), + DS3ItemData("Soul of a Seasoned Warrior", 0x4000019F, DS3ItemCategory.SOUL, souls = 5000), + DS3ItemData("Large Soul of a Seasoned Warrior", 0x400001A0, DS3ItemCategory.SOUL, souls = 7500), + DS3ItemData("Soul of an Old Hand", 0x400001A1, DS3ItemCategory.SOUL, souls = 12500), + DS3ItemData("Soul of a Venerable Old Hand", 0x400001A2, DS3ItemCategory.SOUL, souls = 20000), + DS3ItemData("Soul of a Champion", 0x400001A3, DS3ItemCategory.SOUL, souls = 25000), + DS3ItemData("Soul of a Great Champion", 0x400001A4, DS3ItemCategory.SOUL, souls = 50000), + DS3ItemData("Seed of a Giant Tree", 0x400001B8, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression, inject = True), # Crow trade + *DS3ItemData("Mossfruit", 0x400001C4, DS3ItemCategory.MISC, filler = True).counts([2]), + DS3ItemData("Young White Branch", 0x400001C6, DS3ItemCategory.MISC), + *DS3ItemData("Rusted Coin", 0x400001C7, DS3ItemCategory.MISC, filler = True).counts([2]), + DS3ItemData("Siegbräu", 0x400001C8, DS3ItemCategory.MISC, + classification = ItemClassification.progression), # Crow trade + *DS3ItemData("Rusted Gold Coin", 0x400001C9, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + *DS3ItemData("Blue Bug Pellet", 0x400001CA, DS3ItemCategory.MISC, filler = True).counts([2]), + *DS3ItemData("Red Bug Pellet", 0x400001CB, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + *DS3ItemData("Yellow Bug Pellet", 0x400001CC, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + *DS3ItemData("Black Bug Pellet", 0x400001CD, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + DS3ItemData("Young White Branch", 0x400001CF, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Dark Sigil", 0x400001EA, DS3ItemCategory.MISC, skip = True), + *DS3ItemData("Ember", 0x400001F4, DS3ItemCategory.MISC, filler = True).counts([2]), + DS3ItemData("Hello Carving", 0x40000208, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Thank you Carving", 0x40000209, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Very good! Carving", 0x4000020A, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("I'm sorry Carving", 0x4000020B, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Help me! Carving", 0x4000020C, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Soul of Champion Gundyr", 0x400002C8, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Dancer", 0x400002CA, DS3ItemCategory.BOSS, souls = 10000, + classification = ItemClassification.progression), + DS3ItemData("Soul of a Crystal Sage", 0x400002CB, DS3ItemCategory.BOSS, souls = 3000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Blood of the Wolf", 0x400002CD, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Consumed Oceiros", 0x400002CE, DS3ItemCategory.BOSS, souls = 12000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Boreal Valley Vordt", 0x400002CF, DS3ItemCategory.BOSS, souls = 2000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Old Demon King", 0x400002D0, DS3ItemCategory.BOSS, souls = 10000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Dragonslayer Armour", 0x400002D1, DS3ItemCategory.BOSS, souls = 15000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Nameless King", 0x400002D2, DS3ItemCategory.BOSS, souls = 16000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Pontiff Sulyvahn", 0x400002D4, DS3ItemCategory.BOSS, souls = 12000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Aldrich", 0x400002D5, DS3ItemCategory.BOSS, souls = 15000, + classification = ItemClassification.progression), + DS3ItemData("Soul of High Lord Wolnir", 0x400002D6, DS3ItemCategory.BOSS, souls = 10000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Rotted Greatwood", 0x400002D7, DS3ItemCategory.BOSS, souls = 3000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Rosaria", 0x400002D8, DS3ItemCategory.BOSS, souls = 5000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Deacons of the Deep", 0x400002D9, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Twin Princes", 0x400002DB, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Yhorm the Giant", 0x400002DC, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Lords", 0x400002DD, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of a Demon", 0x400002E3, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of a Stray Demon", 0x400002E7, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + *DS3ItemData("Titanite Shard", 0x400003E8, DS3ItemCategory.UPGRADE).counts([2]), + *DS3ItemData("Large Titanite Shard", 0x400003E9, DS3ItemCategory.UPGRADE).counts([2, 3]), + *DS3ItemData("Titanite Chunk", 0x400003EA, DS3ItemCategory.UPGRADE).counts([2, 6]), + DS3ItemData("Titanite Slab", 0x400003EB, DS3ItemCategory.UPGRADE, + classification = ItemClassification.useful), + *DS3ItemData("Titanite Scale", 0x400003FC, DS3ItemCategory.UPGRADE).counts([2, 3]), + *DS3ItemData("Twinkling Titanite", 0x40000406, DS3ItemCategory.UPGRADE).counts([2, 3]), + DS3ItemData("Heavy Gem", 0x4000044C, DS3ItemCategory.UPGRADE), + DS3ItemData("Sharp Gem", 0x40000456, DS3ItemCategory.UPGRADE), + DS3ItemData("Refined Gem", 0x40000460, DS3ItemCategory.UPGRADE), + DS3ItemData("Crystal Gem", 0x4000046A, DS3ItemCategory.UPGRADE), + DS3ItemData("Simple Gem", 0x40000474, DS3ItemCategory.UPGRADE), + DS3ItemData("Fire Gem", 0x4000047E, DS3ItemCategory.UPGRADE), + DS3ItemData("Chaos Gem", 0x40000488, DS3ItemCategory.UPGRADE), + DS3ItemData("Lightning Gem", 0x40000492, DS3ItemCategory.UPGRADE), + DS3ItemData("Deep Gem", 0x4000049C, DS3ItemCategory.UPGRADE), + DS3ItemData("Dark Gem", 0x400004A6, DS3ItemCategory.UPGRADE), + DS3ItemData("Poison Gem", 0x400004B0, DS3ItemCategory.UPGRADE), + DS3ItemData("Blood Gem", 0x400004BA, DS3ItemCategory.UPGRADE), + DS3ItemData("Raw Gem", 0x400004C4, DS3ItemCategory.UPGRADE), + DS3ItemData("Blessed Gem", 0x400004CE, DS3ItemCategory.UPGRADE), + DS3ItemData("Hollow Gem", 0x400004D8, DS3ItemCategory.UPGRADE), + DS3ItemData("Shriving Stone", 0x400004E2, DS3ItemCategory.UPGRADE), + DS3ItemData("Lift Chamber Key", 0x400007D1, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Small Doll", 0x400007D5, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Jailbreaker's Key", 0x400007D7, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Jailer's Key Ring", 0x400007D8, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Grave Key", 0x400007D9, DS3ItemCategory.UNIQUE), + DS3ItemData("Cell Key", 0x400007DA, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Dungeon Ground Floor Key", 0x400007DB, DS3ItemCategory.UNIQUE), + DS3ItemData("Old Cell Key", 0x400007DC, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Grand Archives Key", 0x400007DE, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Tower Key", 0x400007DF, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Small Lothric Banner", 0x40000836, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Farron Coal", 0x40000837, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Sage's Coal", 0x40000838, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Giant's Coal", 0x40000839, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Profaned Coal", 0x4000083A, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Mortician's Ashes", 0x4000083B, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Dreamchaser's Ashes", 0x4000083C, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Paladin's Ashes", 0x4000083D, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Grave Warden's Ashes", 0x4000083E, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Greirat's Ashes", 0x4000083F, DS3ItemCategory.UNIQUE), + DS3ItemData("Orbeck's Ashes", 0x40000840, DS3ItemCategory.UNIQUE), + DS3ItemData("Cornyx's Ashes", 0x40000841, DS3ItemCategory.UNIQUE), + DS3ItemData("Karla's Ashes", 0x40000842, DS3ItemCategory.UNIQUE), + DS3ItemData("Irina's Ashes", 0x40000843, DS3ItemCategory.UNIQUE), + DS3ItemData("Yuria's Ashes", 0x40000844, DS3ItemCategory.UNIQUE), + DS3ItemData("Basin of Vows", 0x40000845, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Loretta's Bone", 0x40000846, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Braille Divine Tome of Carim", 0x40000847, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Braille Divine Tome of Lothric", 0x40000848, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Cinders of a Lord - Abyss Watcher", 0x4000084B, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Cinders of a Lord - Aldrich", 0x4000084C, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Cinders of a Lord - Yhorm the Giant", 0x4000084D, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Cinders of a Lord - Lothric Prince", 0x4000084E, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Great Swamp Pyromancy Tome", 0x4000084F, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Carthus Pyromancy Tome", 0x40000850, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Izalith Pyromancy Tome", 0x40000851, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Quelana Pyromancy Tome", 0x40000852, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Grave Warden Pyromancy Tome", 0x40000853, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Sage's Scroll", 0x40000854, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Logan's Scroll", 0x40000855, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Crystal Scroll", 0x40000856, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Transposing Kiln", 0x40000857, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Coiled Sword", 0x40000859, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Eyes of a Fire Keeper", 0x4000085A, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), # Allow players to do any ending + DS3ItemData("Sword of Avowal", 0x4000085B, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Golden Scroll", 0x4000085C, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Estus Shard", 0x4000085D, DS3ItemCategory.HEALING, + classification = ItemClassification.useful), + DS3ItemData("Hawkwood's Swordgrass", 0x4000085E, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Undead Bone Shard", 0x4000085F, DS3ItemCategory.HEALING, + classification = ItemClassification.useful), + DS3ItemData("Deep Braille Divine Tome", 0x40000860, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Londor Braille Divine Tome", 0x40000861, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Excrement-covered Ashes", 0x40000862, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Prisoner Chief's Ashes", 0x40000863, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Xanthous Ashes", 0x40000864, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Hollow's Ashes", 0x40000865, DS3ItemCategory.UNIQUE), + DS3ItemData("Patches' Ashes", 0x40000866, DS3ItemCategory.UNIQUE), + DS3ItemData("Dragon Chaser's Ashes", 0x40000867, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Easterner's Ashes", 0x40000868, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + + # Fake item for controlling access to Archdragon Peak. The real drop isn't actually an item as + # such, so we have to inject this because there's no slot for it to come from. + DS3ItemData("Path of the Dragon", 0x40002346, DS3ItemCategory.UNIQUE, + inject = True, classification = ItemClassification.progression), # Spells - ("Farron Dart", 0x40124F80, DS3ItemCategory.SPELL), - ("Great Farron Dart", 0x40127690, DS3ItemCategory.SPELL), - ("Soul Arrow", 0x4013D620, DS3ItemCategory.SPELL), - ("Great Soul Arrow", 0x4013DA08, DS3ItemCategory.SPELL), - ("Heavy Soul Arrow", 0x4013DDF0, DS3ItemCategory.SPELL), - ("Great Heavy Soul Arrow", 0x4013E1D8, DS3ItemCategory.SPELL), - ("Homing Soulmass", 0x4013E5C0, DS3ItemCategory.SPELL), - ("Homing Crystal Soulmass", 0x4013E9A8, DS3ItemCategory.SPELL), - ("Soul Spear", 0x4013ED90, DS3ItemCategory.SPELL), - ("Crystal Soul Spear", 0x4013F178, DS3ItemCategory.SPELL), - ("Deep Soul", 0x4013F560, DS3ItemCategory.SPELL), - ("Great Deep Soul", 0x4013F948, DS3ItemCategory.SPELL), - ("Magic Weapon", 0x4013FD30, DS3ItemCategory.SPELL), - ("Great Magic Weapon", 0x40140118, DS3ItemCategory.SPELL), - ("Crystal Magic Weapon", 0x40140500, DS3ItemCategory.SPELL), - ("Magic Shield", 0x40144B50, DS3ItemCategory.SPELL), - ("Great Magic Shield", 0x40144F38, DS3ItemCategory.SPELL), - ("Hidden Weapon", 0x40147260, DS3ItemCategory.SPELL), - ("Hidden Body", 0x40147648, DS3ItemCategory.SPELL), - ("Cast Light", 0x40149970, DS3ItemCategory.SPELL), - ("Repair", 0x4014A528, DS3ItemCategory.SPELL), - ("Spook", 0x4014A910, DS3ItemCategory.SPELL), - ("Chameleon", 0x4014ACF8, DS3ItemCategory.SPELL), - ("Aural Decoy", 0x4014B0E0, DS3ItemCategory.SPELL), - ("White Dragon Breath", 0x4014E790, DS3ItemCategory.SPELL), - ("Farron Hail", 0x4014EF60, DS3ItemCategory.SPELL), - ("Crystal Hail", 0x4014F348, DS3ItemCategory.SPELL), - ("Soul Greatsword", 0x4014F730, DS3ItemCategory.SPELL), - ("Farron Flashsword", 0x4014FB18, DS3ItemCategory.SPELL), - ("Affinity", 0x401875B8, DS3ItemCategory.SPELL), - ("Dark Edge", 0x40189CC8, DS3ItemCategory.SPELL), - ("Soul Stream", 0x4018B820, DS3ItemCategory.SPELL), - ("Twisted Wall of Light", 0x40193138, DS3ItemCategory.SPELL), - ("Pestilent Mist", 0x401A8CE0, DS3ItemCategory.SPELL), # Originally called "Pestilent Mercury" pre 1.15 - ("Fireball", 0x40249F00, DS3ItemCategory.SPELL), - ("Fire Orb", 0x4024A6D0, DS3ItemCategory.SPELL), - ("Firestorm", 0x4024AAB8, DS3ItemCategory.SPELL), - ("Fire Surge", 0x4024B288, DS3ItemCategory.SPELL), - ("Black Serpent", 0x4024BA58, DS3ItemCategory.SPELL), - ("Combustion", 0x4024C610, DS3ItemCategory.SPELL), - ("Great Combustion", 0x4024C9F8, DS3ItemCategory.SPELL), - ("Poison Mist", 0x4024ED20, DS3ItemCategory.SPELL), - ("Toxic Mist", 0x4024F108, DS3ItemCategory.SPELL), - ("Acid Surge", 0x4024F4F0, DS3ItemCategory.SPELL), - ("Iron Flesh", 0x40251430, DS3ItemCategory.SPELL), - ("Flash Sweat", 0x40251818, DS3ItemCategory.SPELL), - ("Carthus Flame Arc", 0x402527B8, DS3ItemCategory.SPELL), - ("Rapport", 0x40252BA0, DS3ItemCategory.SPELL), - ("Power Within", 0x40253B40, DS3ItemCategory.SPELL), - ("Great Chaos Fire Orb", 0x40256250, DS3ItemCategory.SPELL), - ("Chaos Storm", 0x40256638, DS3ItemCategory.SPELL), - ("Fire Whip", 0x40256A20, DS3ItemCategory.SPELL), - ("Black Flame", 0x40256E08, DS3ItemCategory.SPELL), - ("Profaned Flame", 0x402575D8, DS3ItemCategory.SPELL), - ("Chaos Bed Vestiges", 0x402579C0, DS3ItemCategory.SPELL), - ("Warmth", 0x4025B070, DS3ItemCategory.SPELL), - ("Profuse Sweat", 0x402717D0, DS3ItemCategory.SPELL), - ("Black Fire Orb", 0x4027D350, DS3ItemCategory.SPELL), - ("Bursting Fireball", 0x4027FA60, DS3ItemCategory.SPELL), - ("Boulder Heave", 0x40282170, DS3ItemCategory.SPELL), - ("Sacred Flame", 0x40284880, DS3ItemCategory.SPELL), - ("Carthus Beacon", 0x40286F90, DS3ItemCategory.SPELL), - ("Heal Aid", 0x403540D0, DS3ItemCategory.SPELL), - ("Heal", 0x403567E0, DS3ItemCategory.SPELL), - ("Med Heal", 0x40356BC8, DS3ItemCategory.SPELL), - ("Great Heal", 0x40356FB0, DS3ItemCategory.SPELL), - ("Soothing Sunlight", 0x40357398, DS3ItemCategory.SPELL), - ("Replenishment", 0x40357780, DS3ItemCategory.SPELL), - ("Bountiful Sunlight", 0x40357B68, DS3ItemCategory.SPELL), - ("Bountiful Light", 0x40358338, DS3ItemCategory.SPELL), - ("Caressing Tears", 0x40358720, DS3ItemCategory.SPELL), - ("Tears of Denial", 0x4035B600, DS3ItemCategory.SPELL), - ("Homeward", 0x4035B9E8, DS3ItemCategory.SPELL), - ("Force", 0x4035DD10, DS3ItemCategory.SPELL), - ("Wrath of the Gods", 0x4035E0F8, DS3ItemCategory.SPELL), - ("Emit Force", 0x4035E4E0, DS3ItemCategory.SPELL), - ("Seek Guidance", 0x40360420, DS3ItemCategory.SPELL), - ("Lightning Spear", 0x40362B30, DS3ItemCategory.SPELL), - ("Great Lightning Spear", 0x40362F18, DS3ItemCategory.SPELL), - ("Sunlight Spear", 0x40363300, DS3ItemCategory.SPELL), - ("Lightning Storm", 0x403636E8, DS3ItemCategory.SPELL), - ("Gnaw", 0x40363AD0, DS3ItemCategory.SPELL), - ("Dorhys' Gnawing", 0x40363EB8, DS3ItemCategory.SPELL), - ("Magic Barrier", 0x40365240, DS3ItemCategory.SPELL), - ("Great Magic Barrier", 0x40365628, DS3ItemCategory.SPELL), - ("Sacred Oath", 0x40365DF8, DS3ItemCategory.SPELL), - ("Vow of Silence", 0x4036A448, DS3ItemCategory.SPELL), - ("Lightning Blade", 0x4036C770, DS3ItemCategory.SPELL), - ("Darkmoon Blade", 0x4036CB58, DS3ItemCategory.SPELL), - ("Dark Blade", 0x40378AC0, DS3ItemCategory.SPELL), - ("Dead Again", 0x40387520, DS3ItemCategory.SPELL), - ("Lightning Stake", 0x40389C30, DS3ItemCategory.SPELL), - ("Divine Pillars of Light", 0x4038C340, DS3ItemCategory.SPELL), - ("Lifehunt Scythe", 0x4038EA50, DS3ItemCategory.SPELL), - ("Blessed Weapon", 0x40395F80, DS3ItemCategory.SPELL), - ("Deep Protection", 0x40398690, DS3ItemCategory.SPELL), - ("Atonement", 0x4039ADA0, DS3ItemCategory.SPELL), -]] - -_dlc_items = [DS3ItemData(row[0], row[1], True, row[2]) for row in [ + DS3ItemData("Farron Dart", 0x40124F80, DS3ItemCategory.SPELL), + DS3ItemData("Great Farron Dart", 0x40127690, DS3ItemCategory.SPELL), + DS3ItemData("Soul Arrow", 0x4013D620, DS3ItemCategory.SPELL), + DS3ItemData("Great Soul Arrow", 0x4013DA08, DS3ItemCategory.SPELL), + DS3ItemData("Heavy Soul Arrow", 0x4013DDF0, DS3ItemCategory.SPELL), + DS3ItemData("Great Heavy Soul Arrow", 0x4013E1D8, DS3ItemCategory.SPELL), + DS3ItemData("Homing Soulmass", 0x4013E5C0, DS3ItemCategory.SPELL), + DS3ItemData("Homing Crystal Soulmass", 0x4013E9A8, DS3ItemCategory.SPELL), + DS3ItemData("Soul Spear", 0x4013ED90, DS3ItemCategory.SPELL), + DS3ItemData("Crystal Soul Spear", 0x4013F178, DS3ItemCategory.SPELL), + DS3ItemData("Deep Soul", 0x4013F560, DS3ItemCategory.SPELL), + DS3ItemData("Great Deep Soul", 0x4013F948, DS3ItemCategory.SPELL, + inject = True), # Covenant reward + DS3ItemData("Magic Weapon", 0x4013FD30, DS3ItemCategory.SPELL), + DS3ItemData("Great Magic Weapon", 0x40140118, DS3ItemCategory.SPELL), + DS3ItemData("Crystal Magic Weapon", 0x40140500, DS3ItemCategory.SPELL), + DS3ItemData("Magic Shield", 0x40144B50, DS3ItemCategory.SPELL), + DS3ItemData("Great Magic Shield", 0x40144F38, DS3ItemCategory.SPELL), + DS3ItemData("Hidden Weapon", 0x40147260, DS3ItemCategory.SPELL), + DS3ItemData("Hidden Body", 0x40147648, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Cast Light", 0x40149970, DS3ItemCategory.SPELL), + DS3ItemData("Repair", 0x4014A528, DS3ItemCategory.SPELL), + DS3ItemData("Spook", 0x4014A910, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Chameleon", 0x4014ACF8, DS3ItemCategory.SPELL, + classification = ItemClassification.progression), + DS3ItemData("Aural Decoy", 0x4014B0E0, DS3ItemCategory.SPELL), + DS3ItemData("White Dragon Breath", 0x4014E790, DS3ItemCategory.SPELL), + DS3ItemData("Farron Hail", 0x4014EF60, DS3ItemCategory.SPELL), + DS3ItemData("Crystal Hail", 0x4014F348, DS3ItemCategory.SPELL), + DS3ItemData("Soul Greatsword", 0x4014F730, DS3ItemCategory.SPELL), + DS3ItemData("Farron Flashsword", 0x4014FB18, DS3ItemCategory.SPELL), + DS3ItemData("Affinity", 0x401875B8, DS3ItemCategory.SPELL), + DS3ItemData("Dark Edge", 0x40189CC8, DS3ItemCategory.SPELL), + DS3ItemData("Soul Stream", 0x4018B820, DS3ItemCategory.SPELL), + DS3ItemData("Twisted Wall of Light", 0x40193138, DS3ItemCategory.SPELL), + DS3ItemData("Pestilent Mist", 0x401A8CE0, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), # Originally called "Pestilent Mercury" pre 1.15 + DS3ItemData("Fireball", 0x40249F00, DS3ItemCategory.SPELL), + DS3ItemData("Fire Orb", 0x4024A6D0, DS3ItemCategory.SPELL), + DS3ItemData("Firestorm", 0x4024AAB8, DS3ItemCategory.SPELL), + DS3ItemData("Fire Surge", 0x4024B288, DS3ItemCategory.SPELL), + DS3ItemData("Black Serpent", 0x4024BA58, DS3ItemCategory.SPELL), + DS3ItemData("Combustion", 0x4024C610, DS3ItemCategory.SPELL), + DS3ItemData("Great Combustion", 0x4024C9F8, DS3ItemCategory.SPELL), + DS3ItemData("Poison Mist", 0x4024ED20, DS3ItemCategory.SPELL), + DS3ItemData("Toxic Mist", 0x4024F108, DS3ItemCategory.SPELL), + DS3ItemData("Acid Surge", 0x4024F4F0, DS3ItemCategory.SPELL), + DS3ItemData("Iron Flesh", 0x40251430, DS3ItemCategory.SPELL), + DS3ItemData("Flash Sweat", 0x40251818, DS3ItemCategory.SPELL), + DS3ItemData("Carthus Flame Arc", 0x402527B8, DS3ItemCategory.SPELL), + DS3ItemData("Rapport", 0x40252BA0, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Power Within", 0x40253B40, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Great Chaos Fire Orb", 0x40256250, DS3ItemCategory.SPELL), + DS3ItemData("Chaos Storm", 0x40256638, DS3ItemCategory.SPELL), + DS3ItemData("Fire Whip", 0x40256A20, DS3ItemCategory.SPELL), + DS3ItemData("Black Flame", 0x40256E08, DS3ItemCategory.SPELL), + DS3ItemData("Profaned Flame", 0x402575D8, DS3ItemCategory.SPELL), + DS3ItemData("Chaos Bed Vestiges", 0x402579C0, DS3ItemCategory.SPELL), + DS3ItemData("Warmth", 0x4025B070, DS3ItemCategory.SPELL, + inject = True), # Covenant reward + DS3ItemData("Profuse Sweat", 0x402717D0, DS3ItemCategory.SPELL), + DS3ItemData("Black Fire Orb", 0x4027D350, DS3ItemCategory.SPELL), + DS3ItemData("Bursting Fireball", 0x4027FA60, DS3ItemCategory.SPELL), + DS3ItemData("Boulder Heave", 0x40282170, DS3ItemCategory.SPELL), + DS3ItemData("Sacred Flame", 0x40284880, DS3ItemCategory.SPELL), + DS3ItemData("Carthus Beacon", 0x40286F90, DS3ItemCategory.SPELL), + DS3ItemData("Heal Aid", 0x403540D0, DS3ItemCategory.SPELL), + DS3ItemData("Heal", 0x403567E0, DS3ItemCategory.SPELL), + DS3ItemData("Med Heal", 0x40356BC8, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Great Heal", 0x40356FB0, DS3ItemCategory.SPELL), + DS3ItemData("Soothing Sunlight", 0x40357398, DS3ItemCategory.SPELL), + DS3ItemData("Replenishment", 0x40357780, DS3ItemCategory.SPELL), + DS3ItemData("Bountiful Sunlight", 0x40357B68, DS3ItemCategory.SPELL), + DS3ItemData("Bountiful Light", 0x40358338, DS3ItemCategory.SPELL), + DS3ItemData("Caressing Tears", 0x40358720, DS3ItemCategory.SPELL), + DS3ItemData("Tears of Denial", 0x4035B600, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Homeward", 0x4035B9E8, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Force", 0x4035DD10, DS3ItemCategory.SPELL), + DS3ItemData("Wrath of the Gods", 0x4035E0F8, DS3ItemCategory.SPELL), + DS3ItemData("Emit Force", 0x4035E4E0, DS3ItemCategory.SPELL), + DS3ItemData("Seek Guidance", 0x40360420, DS3ItemCategory.SPELL), + DS3ItemData("Lightning Spear", 0x40362B30, DS3ItemCategory.SPELL), + DS3ItemData("Great Lightning Spear", 0x40362F18, DS3ItemCategory.SPELL, + inject = True), # Covenant reward + DS3ItemData("Sunlight Spear", 0x40363300, DS3ItemCategory.SPELL), + DS3ItemData("Lightning Storm", 0x403636E8, DS3ItemCategory.SPELL), + DS3ItemData("Gnaw", 0x40363AD0, DS3ItemCategory.SPELL), + DS3ItemData("Dorhys' Gnawing", 0x40363EB8, DS3ItemCategory.SPELL), + DS3ItemData("Magic Barrier", 0x40365240, DS3ItemCategory.SPELL), + DS3ItemData("Great Magic Barrier", 0x40365628, DS3ItemCategory.SPELL), + DS3ItemData("Sacred Oath", 0x40365DF8, DS3ItemCategory.SPELL, + inject = True), # Covenant reward + DS3ItemData("Vow of Silence", 0x4036A448, DS3ItemCategory.SPELL), + DS3ItemData("Lightning Blade", 0x4036C770, DS3ItemCategory.SPELL), + DS3ItemData("Darkmoon Blade", 0x4036CB58, DS3ItemCategory.SPELL, + inject = True), # Covenant reward + DS3ItemData("Dark Blade", 0x40378AC0, DS3ItemCategory.SPELL), + DS3ItemData("Dead Again", 0x40387520, DS3ItemCategory.SPELL), + DS3ItemData("Lightning Stake", 0x40389C30, DS3ItemCategory.SPELL), + DS3ItemData("Divine Pillars of Light", 0x4038C340, DS3ItemCategory.SPELL), + DS3ItemData("Lifehunt Scythe", 0x4038EA50, DS3ItemCategory.SPELL), + DS3ItemData("Blessed Weapon", 0x40395F80, DS3ItemCategory.SPELL), + DS3ItemData("Deep Protection", 0x40398690, DS3ItemCategory.SPELL), + DS3ItemData("Atonement", 0x4039ADA0, DS3ItemCategory.SPELL), +] + +_dlc_items = [ # Ammunition - ("Millwood Greatarrow", 0x000623E0, DS3ItemCategory.SKIP), + *DS3ItemData("Millwood Greatarrow", 0x000623E0, DS3ItemCategory.MISC).counts([5]), # Weapons - ("Aquamarine Dagger", 0x00116520, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Murky Hand Scythe", 0x00118C30, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Onyx Blade", 0x00222E00, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Ringed Knight Straight Sword", 0x00225510, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Gael's Greatsword", 0x00227C20, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Follower Sabre", 0x003EDDC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Demon's Scar", 0x003F04D0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Frayed Blade", 0x004D35A0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Herald Curved Greatsword", 0x006159E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Millwood Battle Axe", 0x006D67D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Earth Seeker", 0x006D8EE0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Quakestone Hammer", 0x007ECCF0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Ledo's Great Hammer", 0x007EF400, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Follower Javelin", 0x008CD6B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Ringed Knight Spear", 0x008CFDC0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Lothric War Banner", 0x008D24D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Crucifix of the Mad King", 0x008D4BE0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Splitleaf Greatsword", 0x009B2E90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Friede's Great Scythe", 0x009B55A0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Crow Talons", 0x00A89C10, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Rose of Ariandel", 0x00B82C70, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Pyromancer's Parting Flame", 0x00CC9ED0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Murky Longstaff", 0x00CCC5E0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sacred Chime of Filianore", 0x00CCECF0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Preacher's Right Arm", 0x00CD1400, DS3ItemCategory.WEAPON_UPGRADE_5), - ("White Birch Bow", 0x00D77440, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Millwood Greatbow", 0x00D85EA0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Repeating Crossbow", 0x00D885B0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Giant Door Shield", 0x00F5F8C0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Valorheart", 0x00F646E0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Crow Quills", 0x00F66DF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Ringed Knight Paired Greatswords", 0x00F69500, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Aquamarine Dagger", 0x00116520, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Murky Hand Scythe", 0x00118C30, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Onyx Blade", 0x00222E00, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Ringed Knight Straight Sword", 0x00225510, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Gael's Greatsword", 0x00227C20, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Follower Sabre", 0x003EDDC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Demon's Scar", 0x003F04D0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Frayed Blade", 0x004D35A0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Harald Curved Greatsword", 0x006159E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Millwood Battle Axe", 0x006D67D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Earth Seeker", 0x006D8EE0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Quakestone Hammer", 0x007ECCF0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Ledo's Great Hammer", 0x007EF400, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Follower Javelin", 0x008CD6B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Ringed Knight Spear", 0x008CFDC0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Lothric War Banner", 0x008D24D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Crucifix of the Mad King", 0x008D4BE0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Splitleaf Greatsword", 0x009B2E90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Friede's Great Scythe", 0x009B55A0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Crow Talons", 0x00A89C10, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Rose of Ariandel", 0x00B82C70, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Pyromancer's Parting Flame", 0x00CC9ED0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Murky Longstaff", 0x00CCC5E0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sacred Chime of Filianore", 0x00CCECF0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Preacher's Right Arm", 0x00CD1400, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("White Birch Bow", 0x00D77440, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Millwood Greatbow", 0x00D85EA0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Repeating Crossbow", 0x00D885B0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Giant Door Shield", 0x00F5F8C0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Valorheart", 0x00F646E0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Crow Quills", 0x00F66DF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Ringed Knight Paired Greatswords", 0x00F69500, DS3ItemCategory.WEAPON_UPGRADE_5), # Shields - ("Follower Shield", 0x0135C0E0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Dragonhead Shield", 0x0135E7F0, DS3ItemCategory.SHIELD), - ("Ethereal Oak Shield", 0x01450320, DS3ItemCategory.SHIELD), - ("Dragonhead Greatshield", 0x01452A30, DS3ItemCategory.SHIELD), - ("Follower Torch", 0x015F1AD0, DS3ItemCategory.SHIELD), + DS3ItemData("Follower Shield", 0x0135C0E0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Dragonhead Shield", 0x0135E7F0, DS3ItemCategory.SHIELD), + DS3ItemData("Ethereal Oak Shield", 0x01450320, DS3ItemCategory.SHIELD), + DS3ItemData("Dragonhead Greatshield", 0x01452A30, DS3ItemCategory.SHIELD), + DS3ItemData("Follower Torch", 0x015F1AD0, DS3ItemCategory.SHIELD), # Armor - ("Vilhelm's Helm", 0x11312D00, DS3ItemCategory.ARMOR), - ("Vilhelm's Armor", 0x113130E8, DS3ItemCategory.ARMOR), - ("Vilhelm's Gauntlets", 0x113134D0, DS3ItemCategory.ARMOR), - ("Vilhelm's Leggings", 0x113138B8, DS3ItemCategory.ARMOR), - ("Antiquated Plain Garb", 0x11B2E408, DS3ItemCategory.ARMOR), - ("Violet Wrappings", 0x11B2E7F0, DS3ItemCategory.ARMOR), - ("Loincloth 2", 0x11B2EBD8, DS3ItemCategory.ARMOR), - ("Shira's Crown", 0x11C22260, DS3ItemCategory.ARMOR), - ("Shira's Armor", 0x11C22648, DS3ItemCategory.ARMOR), - ("Shira's Gloves", 0x11C22A30, DS3ItemCategory.ARMOR), - ("Shira's Trousers", 0x11C22E18, DS3ItemCategory.ARMOR), - ("Lapp's Helm", 0x11E84800, DS3ItemCategory.ARMOR), - ("Lapp's Armor", 0x11E84BE8, DS3ItemCategory.ARMOR), - ("Lapp's Gauntlets", 0x11E84FD0, DS3ItemCategory.ARMOR), - ("Lapp's Leggings", 0x11E853B8, DS3ItemCategory.ARMOR), - ("Slave Knight Hood", 0x134EDCE0, DS3ItemCategory.ARMOR), - ("Slave Knight Armor", 0x134EE0C8, DS3ItemCategory.ARMOR), - ("Slave Knight Gauntlets", 0x134EE4B0, DS3ItemCategory.ARMOR), - ("Slave Knight Leggings", 0x134EE898, DS3ItemCategory.ARMOR), - ("Ordained Hood", 0x135E1F20, DS3ItemCategory.ARMOR), - ("Ordained Dress", 0x135E2308, DS3ItemCategory.ARMOR), - ("Ordained Trousers", 0x135E2AD8, DS3ItemCategory.ARMOR), - ("Follower Helm", 0x137CA3A0, DS3ItemCategory.ARMOR), - ("Follower Armor", 0x137CA788, DS3ItemCategory.ARMOR), - ("Follower Gloves", 0x137CAB70, DS3ItemCategory.ARMOR), - ("Follower Boots", 0x137CAF58, DS3ItemCategory.ARMOR), - ("Millwood Knight Helm", 0x139B2820, DS3ItemCategory.ARMOR), - ("Millwood Knight Armor", 0x139B2C08, DS3ItemCategory.ARMOR), - ("Millwood Knight Gauntlets", 0x139B2FF0, DS3ItemCategory.ARMOR), - ("Millwood Knight Leggings", 0x139B33D8, DS3ItemCategory.ARMOR), - ("Ringed Knight Hood", 0x13C8EEE0, DS3ItemCategory.ARMOR), - ("Ringed Knight Armor", 0x13C8F2C8, DS3ItemCategory.ARMOR), - ("Ringed Knight Gauntlets", 0x13C8F6B0, DS3ItemCategory.ARMOR), - ("Ringed Knight Leggings", 0x13C8FA98, DS3ItemCategory.ARMOR), - ("Harald Legion Armor", 0x13D83508, DS3ItemCategory.ARMOR), - ("Harald Legion Gauntlets", 0x13D838F0, DS3ItemCategory.ARMOR), - ("Harald Legion Leggings", 0x13D83CD8, DS3ItemCategory.ARMOR), - ("Iron Dragonslayer Helm", 0x1405F7E0, DS3ItemCategory.ARMOR), - ("Iron Dragonslayer Armor", 0x1405FBC8, DS3ItemCategory.ARMOR), - ("Iron Dragonslayer Gauntlets", 0x1405FFB0, DS3ItemCategory.ARMOR), - ("Iron Dragonslayer Leggings", 0x14060398, DS3ItemCategory.ARMOR), - ("White Preacher Head", 0x14153A20, DS3ItemCategory.ARMOR), - ("Ruin Sentinel Helm", 0x14CC5520, DS3ItemCategory.ARMOR), - ("Ruin Sentinel Armor", 0x14CC5908, DS3ItemCategory.ARMOR), - ("Ruin Sentinel Gauntlets", 0x14CC5CF0, DS3ItemCategory.ARMOR), - ("Ruin Sentinel Leggings", 0x14CC60D8, DS3ItemCategory.ARMOR), - ("Desert Pyromancer Hood", 0x14DB9760, DS3ItemCategory.ARMOR), - ("Desert Pyromancer Garb", 0x14DB9B48, DS3ItemCategory.ARMOR), - ("Desert Pyromancer Gloves", 0x14DB9F30, DS3ItemCategory.ARMOR), - ("Desert Pyromancer Skirt", 0x14DBA318, DS3ItemCategory.ARMOR), - ("Black Witch Hat", 0x14EAD9A0, DS3ItemCategory.ARMOR), - ("Black Witch Garb", 0x14EADD88, DS3ItemCategory.ARMOR), - ("Black Witch Wrappings", 0x14EAE170, DS3ItemCategory.ARMOR), - ("Black Witch Trousers", 0x14EAE558, DS3ItemCategory.ARMOR), - ("Black Witch Veil", 0x14FA1BE0, DS3ItemCategory.ARMOR), - ("Blindfold Mask", 0x15095E20, DS3ItemCategory.ARMOR), + DS3ItemData("Vilhelm's Helm", 0x11312D00, DS3ItemCategory.ARMOR), + DS3ItemData("Vilhelm's Armor", 0x113130E8, DS3ItemCategory.ARMOR), + DS3ItemData("Vilhelm's Gauntlets", 0x113134D0, DS3ItemCategory.ARMOR), + DS3ItemData("Vilhelm's Leggings", 0x113138B8, DS3ItemCategory.ARMOR), + DS3ItemData("Antiquated Plain Garb", 0x11B2E408, DS3ItemCategory.ARMOR), + DS3ItemData("Violet Wrappings", 0x11B2E7F0, DS3ItemCategory.ARMOR), + DS3ItemData("Loincloth 2", 0x11B2EBD8, DS3ItemCategory.ARMOR), + DS3ItemData("Shira's Crown", 0x11C22260, DS3ItemCategory.ARMOR), + DS3ItemData("Shira's Armor", 0x11C22648, DS3ItemCategory.ARMOR), + DS3ItemData("Shira's Gloves", 0x11C22A30, DS3ItemCategory.ARMOR), + DS3ItemData("Shira's Trousers", 0x11C22E18, DS3ItemCategory.ARMOR), + DS3ItemData("Lapp's Helm", 0x11E84800, DS3ItemCategory.ARMOR), + DS3ItemData("Lapp's Armor", 0x11E84BE8, DS3ItemCategory.ARMOR), + DS3ItemData("Lapp's Gauntlets", 0x11E84FD0, DS3ItemCategory.ARMOR), + DS3ItemData("Lapp's Leggings", 0x11E853B8, DS3ItemCategory.ARMOR), + DS3ItemData("Slave Knight Hood", 0x134EDCE0, DS3ItemCategory.ARMOR), + DS3ItemData("Slave Knight Armor", 0x134EE0C8, DS3ItemCategory.ARMOR), + DS3ItemData("Slave Knight Gauntlets", 0x134EE4B0, DS3ItemCategory.ARMOR), + DS3ItemData("Slave Knight Leggings", 0x134EE898, DS3ItemCategory.ARMOR), + DS3ItemData("Ordained Hood", 0x135E1F20, DS3ItemCategory.ARMOR), + DS3ItemData("Ordained Dress", 0x135E2308, DS3ItemCategory.ARMOR), + DS3ItemData("Ordained Trousers", 0x135E2AD8, DS3ItemCategory.ARMOR), + DS3ItemData("Follower Helm", 0x137CA3A0, DS3ItemCategory.ARMOR), + DS3ItemData("Follower Armor", 0x137CA788, DS3ItemCategory.ARMOR), + DS3ItemData("Follower Gloves", 0x137CAB70, DS3ItemCategory.ARMOR), + DS3ItemData("Follower Boots", 0x137CAF58, DS3ItemCategory.ARMOR), + DS3ItemData("Millwood Knight Helm", 0x139B2820, DS3ItemCategory.ARMOR), + DS3ItemData("Millwood Knight Armor", 0x139B2C08, DS3ItemCategory.ARMOR), + DS3ItemData("Millwood Knight Gauntlets", 0x139B2FF0, DS3ItemCategory.ARMOR), + DS3ItemData("Millwood Knight Leggings", 0x139B33D8, DS3ItemCategory.ARMOR), + DS3ItemData("Ringed Knight Hood", 0x13C8EEE0, DS3ItemCategory.ARMOR), + DS3ItemData("Ringed Knight Armor", 0x13C8F2C8, DS3ItemCategory.ARMOR), + DS3ItemData("Ringed Knight Gauntlets", 0x13C8F6B0, DS3ItemCategory.ARMOR), + DS3ItemData("Ringed Knight Leggings", 0x13C8FA98, DS3ItemCategory.ARMOR), + DS3ItemData("Harald Legion Armor", 0x13D83508, DS3ItemCategory.ARMOR), + DS3ItemData("Harald Legion Gauntlets", 0x13D838F0, DS3ItemCategory.ARMOR), + DS3ItemData("Harald Legion Leggings", 0x13D83CD8, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Dragonslayer Helm", 0x1405F7E0, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Dragonslayer Armor", 0x1405FBC8, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Dragonslayer Gauntlets", 0x1405FFB0, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Dragonslayer Leggings", 0x14060398, DS3ItemCategory.ARMOR), + DS3ItemData("White Preacher Head", 0x14153A20, DS3ItemCategory.ARMOR), + DS3ItemData("Ruin Helm", 0x14CC5520, DS3ItemCategory.ARMOR), + DS3ItemData("Ruin Armor", 0x14CC5908, DS3ItemCategory.ARMOR), + DS3ItemData("Ruin Gauntlets", 0x14CC5CF0, DS3ItemCategory.ARMOR), + DS3ItemData("Ruin Leggings", 0x14CC60D8, DS3ItemCategory.ARMOR), + DS3ItemData("Desert Pyromancer Hood", 0x14DB9760, DS3ItemCategory.ARMOR), + DS3ItemData("Desert Pyromancer Garb", 0x14DB9B48, DS3ItemCategory.ARMOR), + DS3ItemData("Desert Pyromancer Gloves", 0x14DB9F30, DS3ItemCategory.ARMOR), + DS3ItemData("Desert Pyromancer Skirt", 0x14DBA318, DS3ItemCategory.ARMOR), + DS3ItemData("Black Witch Hat", 0x14EAD9A0, DS3ItemCategory.ARMOR), + DS3ItemData("Black Witch Garb", 0x14EADD88, DS3ItemCategory.ARMOR), + DS3ItemData("Black Witch Wrappings", 0x14EAE170, DS3ItemCategory.ARMOR), + DS3ItemData("Black Witch Trousers", 0x14EAE558, DS3ItemCategory.ARMOR), + DS3ItemData("Black Witch Veil", 0x14FA1BE0, DS3ItemCategory.ARMOR), + DS3ItemData("Blindfold Mask", 0x15095E20, DS3ItemCategory.ARMOR), # Covenants - ("Spear of the Church", 0x2000276A, DS3ItemCategory.SKIP), + DS3ItemData("Spear of the Church", 0x2000276A, DS3ItemCategory.UNIQUE, skip = True), # Rings - ("Chloranthy Ring+3", 0x20004E2D, DS3ItemCategory.RING), - ("Havel's Ring+3", 0x20004E37, DS3ItemCategory.RING), - ("Ring of Favor+3", 0x20004E41, DS3ItemCategory.RING), - ("Ring of Steel Protection+3", 0x20004E4B, DS3ItemCategory.RING), - ("Wolf Ring+3", 0x20004EE1, DS3ItemCategory.RING), - ("Covetous Gold Serpent Ring+3", 0x20004FA9, DS3ItemCategory.RING), - ("Covetous Silver Serpent Ring+3", 0x20004FB3, DS3ItemCategory.RING), - ("Ring of the Evil Eye+3", 0x20005071, DS3ItemCategory.RING), - ("Chillbite Ring", 0x20005208, DS3ItemCategory.RING), + DS3ItemData("Chloranthy Ring+3", 0x20004E2D, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Havel's Ring+3", 0x20004E37, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Ring of Favor+3", 0x20004E41, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Ring of Steel Protection+3", 0x20004E4B, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Wolf Ring+3", 0x20004EE1, DS3ItemCategory.RING), + DS3ItemData("Covetous Gold Serpent Ring+3", 0x20004FA9, DS3ItemCategory.RING), + DS3ItemData("Covetous Silver Serpent Ring+3", 0x20004FB3, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Ring of the Evil Eye+3", 0x20005071, DS3ItemCategory.RING), + DS3ItemData("Chillbite Ring", 0x20005208, DS3ItemCategory.RING), # Items - ("Church Guardian Shiv", 0x4000013B, DS3ItemCategory.MISC), - ("Filianore's Spear Ornament", 0x4000017B, DS3ItemCategory.SKIP), - ("Ritual Spear Fragment", 0x4000028A, DS3ItemCategory.MISC), - ("Divine Spear Fragment", 0x4000028B, DS3ItemCategory.SKIP), - ("Soul of Sister Friede", 0x400002E8, DS3ItemCategory.BOSS), - ("Soul of Slave Knight Gael", 0x400002E9, DS3ItemCategory.BOSS), - ("Soul of the Demon Prince", 0x400002EA, DS3ItemCategory.BOSS), - ("Soul of Darkeater Midir", 0x400002EB, DS3ItemCategory.BOSS), - ("Champion's Bones", 0x40000869, DS3ItemCategory.SKIP), - ("Captain's Ashes", 0x4000086A, DS3ItemCategory.MISC), - ("Contraption Key", 0x4000086B, DS3ItemCategory.KEY), - ("Small Envoy Banner", 0x4000086C, DS3ItemCategory.KEY), - ("Old Woman's Ashes", 0x4000086D, DS3ItemCategory.SKIP), - ("Blood of the Dark Soul", 0x4000086E, DS3ItemCategory.SKIP), + DS3ItemData("Church Guardian Shiv", 0x4000013B, DS3ItemCategory.MISC), + DS3ItemData("Filianore's Spear Ornament", 0x4000017B, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Ritual Spear Fragment", 0x4000028A, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Divine Spear Fragment", 0x4000028B, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Soul of Sister Friede", 0x400002E8, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Slave Knight Gael", 0x400002E9, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Demon Prince", 0x400002EA, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Darkeater Midir", 0x400002EB, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Champion's Bones", 0x40000869, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Captain's Ashes", 0x4000086A, DS3ItemCategory.MISC, + classification = ItemClassification.progression), + DS3ItemData("Contraption Key", 0x4000086B, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Small Envoy Banner", 0x4000086C, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Old Woman's Ashes", 0x4000086D, DS3ItemCategory.UNIQUE), + DS3ItemData("Blood of the Dark Soul", 0x4000086E, DS3ItemCategory.UNIQUE, skip = True), # Spells - ("Frozen Weapon", 0x401408E8, DS3ItemCategory.SPELL), - ("Old Moonlight", 0x4014FF00, DS3ItemCategory.SPELL), - ("Great Soul Dregs", 0x401879A0, DS3ItemCategory.SPELL), - ("Snap Freeze", 0x401A90C8, DS3ItemCategory.SPELL), - ("Floating Chaos", 0x40257DA8, DS3ItemCategory.SPELL), - ("Flame Fan", 0x40258190, DS3ItemCategory.SPELL), - ("Seething Chaos", 0x402896A0, DS3ItemCategory.SPELL), - ("Lightning Arrow", 0x40358B08, DS3ItemCategory.SPELL), - ("Way of White Corona", 0x403642A0, DS3ItemCategory.SPELL), - ("Projected Heal", 0x40364688, DS3ItemCategory.SPELL), -]] + DS3ItemData("Frozen Weapon", 0x401408E8, DS3ItemCategory.SPELL), + DS3ItemData("Old Moonlight", 0x4014FF00, DS3ItemCategory.SPELL), + DS3ItemData("Great Soul Dregs", 0x401879A0, DS3ItemCategory.SPELL), + DS3ItemData("Snap Freeze", 0x401A90C8, DS3ItemCategory.SPELL), + DS3ItemData("Floating Chaos", 0x40257DA8, DS3ItemCategory.SPELL), + DS3ItemData("Flame Fan", 0x40258190, DS3ItemCategory.SPELL), + DS3ItemData("Seething Chaos", 0x402896A0, DS3ItemCategory.SPELL), + DS3ItemData("Lightning Arrow", 0x40358B08, DS3ItemCategory.SPELL), + DS3ItemData("Way of White Corona", 0x403642A0, DS3ItemCategory.SPELL), + DS3ItemData("Projected Heal", 0x40364688, DS3ItemCategory.SPELL), +] +for item in _dlc_items: + item.is_dlc = True # Unused list for future reference # These items exist to some degree in the code, but aren't accessible # in-game and can't be picked up without modifications -_cut_content_items = [DS3ItemData(row[0], row[1], False, row[2]) for row in [ +_cut_content_items = [ # Weapons - ("Blood-stained Short Sword", 0x00100590, DS3ItemCategory.SKIP), - ("Missionary's Axe", 0x006C2F50, DS3ItemCategory.SKIP), - ("Dragon King Greataxe", 0x006D40C0, DS3ItemCategory.SKIP), - ("Four Knights Hammer", 0x007D4650, DS3ItemCategory.SKIP), - ("Hammer of the Great Tree", 0x007D9470, DS3ItemCategory.SKIP), - ("Lothric's Scythe", 0x009A4430, DS3ItemCategory.SKIP), - ("Ancient Dragon Halberd", 0x009A6B40, DS3ItemCategory.SKIP), - ("Scythe of Want", 0x009A9250, DS3ItemCategory.SKIP), - ("Sacred Beast Catalyst", 0x00C8A730, DS3ItemCategory.SKIP), - ("Deep Pyromancy Flame", 0x00CC9ED0, DS3ItemCategory.SKIP), # Duplicate? - ("Flickering Pyromancy Flame", 0x00CD3B10, DS3ItemCategory.SKIP), - ("Strong Pyromancy Flame", 0x00CD6220, DS3ItemCategory.SKIP), - ("Deep Pyromancy Flame", 0x00CDFE60, DS3ItemCategory.SKIP), # Duplicate? - ("Pitch-Dark Pyromancy Flame", 0x00CE2570, DS3ItemCategory.SKIP), - ("Dancer's Short Bow", 0x00D77440, DS3ItemCategory.SKIP), - ("Shield Crossbow", 0x00D81080, DS3ItemCategory.SKIP), - ("Golden Dual Swords", 0x00F55C80, DS3ItemCategory.SKIP), - ("Channeler's Trident", 0x008C8890, DS3ItemCategory.SKIP), + DS3ItemData("Blood-stained Short Sword", 0x00100590, DS3ItemCategory.UNIQUE), + DS3ItemData("Missionary's Axe", 0x006C2F50, DS3ItemCategory.UNIQUE), + DS3ItemData("Dragon King Greataxe", 0x006D40C0, DS3ItemCategory.UNIQUE), + DS3ItemData("Four Knights Hammer", 0x007D4650, DS3ItemCategory.UNIQUE), + DS3ItemData("Hammer of the Great Tree", 0x007D9470, DS3ItemCategory.UNIQUE), + DS3ItemData("Lothric's Scythe", 0x009A4430, DS3ItemCategory.UNIQUE), + DS3ItemData("Ancient Dragon Halberd", 0x009A6B40, DS3ItemCategory.UNIQUE), + DS3ItemData("Scythe of Want", 0x009A9250, DS3ItemCategory.UNIQUE), + DS3ItemData("Sacred Beast Catalyst", 0x00C8A730, DS3ItemCategory.UNIQUE), + DS3ItemData("Deep Pyromancy Flame", 0x00CC9ED0, DS3ItemCategory.UNIQUE), # Duplicate? + DS3ItemData("Flickering Pyromancy Flame", 0x00CD3B10, DS3ItemCategory.UNIQUE), + DS3ItemData("Strong Pyromancy Flame", 0x00CD6220, DS3ItemCategory.UNIQUE), + DS3ItemData("Deep Pyromancy Flame", 0x00CDFE60, DS3ItemCategory.UNIQUE), # Duplicate? + DS3ItemData("Pitch-Dark Pyromancy Flame", 0x00CE2570, DS3ItemCategory.UNIQUE), + DS3ItemData("Dancer's Short Bow", 0x00D77440, DS3ItemCategory.UNIQUE), + DS3ItemData("Shield Crossbow", 0x00D81080, DS3ItemCategory.UNIQUE), + DS3ItemData("Golden Dual Swords", 0x00F55C80, DS3ItemCategory.UNIQUE), + DS3ItemData("Channeler's Trident", 0x008C8890, DS3ItemCategory.UNIQUE), # Shields - ("Cleric's Parma", 0x013524A0, DS3ItemCategory.SKIP), - ("Prince's Shield", 0x01421CF0, DS3ItemCategory.SKIP), + DS3ItemData("Cleric's Parma", 0x013524A0, DS3ItemCategory.UNIQUE), + DS3ItemData("Prince's Shield", 0x01421CF0, DS3ItemCategory.UNIQUE), # Armor - ("Dingy Maiden's Overcoat", 0x11DA9048, DS3ItemCategory.SKIP), - ("Grotto Hat", 0x11F78A40, DS3ItemCategory.SKIP), - ("Grotto Robe", 0x11F78E28, DS3ItemCategory.SKIP), - ("Grotto Wrap", 0x11F79210, DS3ItemCategory.SKIP), - ("Grotto Trousers", 0x11F795F8, DS3ItemCategory.SKIP), - ("Soldier's Gauntlets", 0x126261D0, DS3ItemCategory.SKIP), - ("Soldier's Hood", 0x1263E0A0, DS3ItemCategory.SKIP), - ("Elder's Robe", 0x129024A8, DS3ItemCategory.SKIP), - ("Saint's Veil", 0x12A70420, DS3ItemCategory.SKIP), - ("Saint's Dress", 0x12A70808, DS3ItemCategory.SKIP), - ("Footman's Hood", 0x12AEA540, DS3ItemCategory.SKIP), - ("Footman's Overcoat", 0x12AEA928, DS3ItemCategory.SKIP), - ("Footman's Bracelets", 0x12AEAD10, DS3ItemCategory.SKIP), - ("Footman's Trousers", 0x12AEB0F8, DS3ItemCategory.SKIP), - ("Scholar's Shed Skin", 0x12E40D20, DS3ItemCategory.SKIP), - ("Man Serpent's Mask", 0x138BE5E0, DS3ItemCategory.SKIP), - ("Man Serpent's Robe", 0x138BE9C8, DS3ItemCategory.SKIP), - ("Old Monarch's Crown", 0x13DFD240, DS3ItemCategory.SKIP), - ("Old Monarch's Robe", 0x13DFD628, DS3ItemCategory.SKIP), - ("Frigid Valley Mask", 0x13FE56C0, DS3ItemCategory.SKIP), - ("Dingy Hood", 0x140D9900, DS3ItemCategory.SKIP), - ("Hexer's Hood", 0x15A995C0, DS3ItemCategory.SKIP), - ("Hexer's Robes", 0x15A999A8, DS3ItemCategory.SKIP), - ("Hexer's Gloves", 0x15A99D90, DS3ItemCategory.SKIP), - ("Hexer's Boots", 0x15A9A178, DS3ItemCategory.SKIP), - ("Varangian Helm", 0x15C81A40, DS3ItemCategory.SKIP), - ("Varangian Armor", 0x15C81E28, DS3ItemCategory.SKIP), - ("Varangian Cuffs", 0x15C82210, DS3ItemCategory.SKIP), - ("Varangian Leggings", 0x15C825F8, DS3ItemCategory.SKIP), + DS3ItemData("Dingy Maiden's Overcoat", 0x11DA9048, DS3ItemCategory.UNIQUE), + DS3ItemData("Grotto Hat", 0x11F78A40, DS3ItemCategory.UNIQUE), + DS3ItemData("Grotto Robe", 0x11F78E28, DS3ItemCategory.UNIQUE), + DS3ItemData("Grotto Wrap", 0x11F79210, DS3ItemCategory.UNIQUE), + DS3ItemData("Grotto Trousers", 0x11F795F8, DS3ItemCategory.UNIQUE), + DS3ItemData("Soldier's Gauntlets", 0x126261D0, DS3ItemCategory.UNIQUE), + DS3ItemData("Soldier's Hood", 0x1263E0A0, DS3ItemCategory.UNIQUE), + DS3ItemData("Elder's Robe", 0x129024A8, DS3ItemCategory.UNIQUE), + DS3ItemData("Saint's Veil", 0x12A70420, DS3ItemCategory.UNIQUE), + DS3ItemData("Saint's Dress", 0x12A70808, DS3ItemCategory.UNIQUE), + DS3ItemData("Footman's Hood", 0x12AEA540, DS3ItemCategory.UNIQUE), + DS3ItemData("Footman's Overcoat", 0x12AEA928, DS3ItemCategory.UNIQUE), + DS3ItemData("Footman's Bracelets", 0x12AEAD10, DS3ItemCategory.UNIQUE), + DS3ItemData("Footman's Trousers", 0x12AEB0F8, DS3ItemCategory.UNIQUE), + DS3ItemData("Scholar's Shed Skin", 0x12E40D20, DS3ItemCategory.UNIQUE), + DS3ItemData("Man Serpent's Mask", 0x138BE5E0, DS3ItemCategory.UNIQUE), + DS3ItemData("Man Serpent's Robe", 0x138BE9C8, DS3ItemCategory.UNIQUE), + DS3ItemData("Old Monarch's Crown", 0x13DFD240, DS3ItemCategory.UNIQUE), + DS3ItemData("Old Monarch's Robe", 0x13DFD628, DS3ItemCategory.UNIQUE), + DS3ItemData("Frigid Valley Mask", 0x13FE56C0, DS3ItemCategory.UNIQUE), + DS3ItemData("Dingy Hood", 0x140D9900, DS3ItemCategory.UNIQUE), + DS3ItemData("Hexer's Hood", 0x15A995C0, DS3ItemCategory.UNIQUE), + DS3ItemData("Hexer's Robes", 0x15A999A8, DS3ItemCategory.UNIQUE), + DS3ItemData("Hexer's Gloves", 0x15A99D90, DS3ItemCategory.UNIQUE), + DS3ItemData("Hexer's Boots", 0x15A9A178, DS3ItemCategory.UNIQUE), + DS3ItemData("Varangian Helm", 0x15C81A40, DS3ItemCategory.UNIQUE), + DS3ItemData("Varangian Armor", 0x15C81E28, DS3ItemCategory.UNIQUE), + DS3ItemData("Varangian Cuffs", 0x15C82210, DS3ItemCategory.UNIQUE), + DS3ItemData("Varangian Leggings", 0x15C825F8, DS3ItemCategory.UNIQUE), # Rings - ("Rare Ring of Sacrifice", 0x20004EFC, DS3ItemCategory.SKIP), - ("Baneful Bird Ring", 0x20005032, DS3ItemCategory.SKIP), - ("Darkmoon Blade Covenant Ring", 0x20004F7E, DS3ItemCategory.SKIP), - ("Yorgh's Ring", 0x2000505A, DS3ItemCategory.SKIP), - ("Ring of Hiding", 0x200050D2, DS3ItemCategory.SKIP), - ("Ring of Sustained Toughness", 0x20005118, DS3ItemCategory.SKIP), - ("Ring of Sustained Energy", 0x20005122, DS3ItemCategory.SKIP), - ("Ring of Sustained Magic", 0x2000512C, DS3ItemCategory.SKIP), - ("Ring of Sustained Essence", 0x20005140, DS3ItemCategory.SKIP), - ("Ring of Sustained Might", 0x2000514A, DS3ItemCategory.SKIP), - ("Ring of Sustained Fortune", 0x20005154, DS3ItemCategory.SKIP), + DS3ItemData("Rare Ring of Sacrifice", 0x20004EFC, DS3ItemCategory.UNIQUE), + DS3ItemData("Baneful Bird Ring", 0x20005032, DS3ItemCategory.UNIQUE), + DS3ItemData("Darkmoon Blade Covenant Ring", 0x20004F7E, DS3ItemCategory.UNIQUE), + DS3ItemData("Yorgh's Ring", 0x2000505A, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Hiding", 0x200050D2, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Toughness", 0x20005118, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Energy", 0x20005122, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Magic", 0x2000512C, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Essence", 0x20005140, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Might", 0x2000514A, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Fortune", 0x20005154, DS3ItemCategory.UNIQUE), # Items - ("Soul of a Wicked Spirit", 0x400002C9, DS3ItemCategory.SKIP), + DS3ItemData("Soul of a Wicked Spirit", 0x400002C9, DS3ItemCategory.UNIQUE), # Spells - ("Dark Orb", 0x4027AC40, DS3ItemCategory.SKIP), - ("Morbid Temptation", 0x40359AA8, DS3ItemCategory.SKIP), - ("Dorris Swarm", 0x40393870, DS3ItemCategory.SKIP), -]] + DS3ItemData("Dark Orb", 0x4027AC40, DS3ItemCategory.UNIQUE), + DS3ItemData("Morbid Temptation", 0x40359AA8, DS3ItemCategory.UNIQUE), + DS3ItemData("Dorris Swarm", 0x40393870, DS3ItemCategory.UNIQUE), +] + + +item_name_groups: Dict[str, Set] = { + "Progression": set(), + "Cinders": set(), + "Weapons": set(), + "Shields": set(), + "Armor": set(), + "Rings": set(), + "Spells": set(), + "Miscellaneous": set(), + "Unique": set(), + "Boss Souls": set(), + "Small Souls": set(), + "Upgrade": set(), + "Healing": set(), +} + item_descriptions = { + "Progression": "Items which unlock locations.", "Cinders": "All four Cinders of a Lord.\n\nOnce you have these four, you can fight Soul of Cinder and win the game.", + "Miscellaneous": "Generic stackable items, such as arrows, firebombs, buffs, and so on.", + "Unique": "Items that are unique per NG cycle, such as scrolls, keys, ashes, and so on. Doesn't include equipment, spells, or souls.", + "Boss Souls": "Souls that can be traded with Ludleth, including Soul of Rosaria.", + "Small Souls": "Soul items, not including boss souls.", + "Upgrade": "Upgrade items, including titanite, gems, and Shriving Stones.", + "Healing": "Undead Bone Shards and Estus Shards.", } + _all_items = _vanilla_items + _dlc_items +for item_data in _all_items: + for group_name in item_data.item_groups(): + item_name_groups[group_name].add(item_data.name) + +filler_item_names = [item_data.name for item_data in _all_items if item_data.filler] item_dictionary = {item_data.name: item_data for item_data in _all_items} diff --git a/worlds/dark_souls_3/Locations.py b/worlds/dark_souls_3/Locations.py index df241a5fd1fb..08f4b7cd1a80 100644 --- a/worlds/dark_souls_3/Locations.py +++ b/worlds/dark_souls_3/Locations.py @@ -1,693 +1,3119 @@ -from enum import IntEnum -from typing import Optional, NamedTuple, Dict +from typing import cast, ClassVar, Optional, Dict, List, Set +from dataclasses import dataclass -from BaseClasses import Location, Region +from BaseClasses import ItemClassification, Location, Region +from .Items import DS3ItemCategory, item_dictionary +# Regions in approximate order of reward, mostly measured by how high-quality the upgrade items are +# in each region. +region_order = [ + "Cemetery of Ash", + "Firelink Shrine", + "High Wall of Lothric", + "Greirat's Shop", + "Undead Settlement", + "Road of Sacrifices", + "Farron Keep", + "Cathedral of the Deep", + "Catacombs of Carthus", + "Smouldering Lake", + "Irithyll of the Boreal Valley", + "Irithyll Dungeon", + "Karla's Shop", + # The first half of Painted World has one Titanite Slab but mostly Large Titanite Shards, + # much like Irithyll Dungeon. + "Painted World of Ariandel (Before Contraption)", + "Anor Londo", + "Profaned Capital", + # The second half of Painted World has two Titanite Chunks and two Titanite Slabs, which + # puts it on the low end of the post-Lothric Castle areas in terms of rewards. + "Painted World of Ariandel (After Contraption)", + "Lothric Castle", + "Consumed King's Garden", + "Untended Graves", + # List this late because it contains a Titanite Slab in the base game + "Firelink Shrine Bell Tower", + "Grand Archives", + "Archdragon Peak", + "Kiln of the First Flame", + # Both areas of DLC2 have premium rewards. + "Dreg Heap", + "Ringed City", +] -class DS3LocationCategory(IntEnum): - WEAPON = 0 - SHIELD = 1 - ARMOR = 2 - RING = 3 - SPELL = 4 - NPC = 5 - KEY = 6 - BOSS = 7 - MISC = 8 - HEALTH = 9 - PROGRESSIVE_ITEM = 10 - EVENT = 11 +@dataclass +class DS3LocationData: + __location_id: ClassVar[int] = 100000 + """The next location ID to use when creating location data.""" -class DS3LocationData(NamedTuple): name: str - default_item: str - category: DS3LocationCategory + """The name of this location according to Archipelago. + + This needs to be unique within this world.""" + + default_item_name: Optional[str] + """The name of the item that appears by default in this location. + + If this is None, that indicates that this location is an "event" that's + automatically considered accessed as soon as it's available. Events are used + to indicate major game transitions that aren't otherwise gated by items so + that progression balancing and item smoothing is more accurate for DS3. + """ + + ap_code: Optional[int] = None + """Archipelago's internal ID for this location (also known as its "address").""" + + region_value: int = 0 + """The relative value of items in this location's region. + + This is used to sort locations when placing items like the base game. + """ + + static: Optional[str] = None + """The key in the static randomizer's Slots table that corresponds to this location. + + By default, the static randomizer chooses its location based on the region and the item name. + If the item name is unique across the whole game, it can also look it up based on that alone. If + there are multiple instances of the same item type in the same region, it will assume its order + (in annotations.txt) matches Archipelago's order. + + In cases where this heuristic doesn't work, such as when Archipelago's region categorization or + item name disagrees with the static randomizer's, this field is used to provide an explicit + association instead. + """ + + missable: bool = False + """Whether this item is possible to permanently lose access to. + + This is also used for items that are *technically* possible to get at any time, but are + prohibitively difficult without blocking off other checks (items dropped by NPCs on death + generally fall into this category). + + Missable locations are always marked as excluded, so they will never contain + progression or useful items. + """ + + dlc: bool = False + """Whether this location is only accessible if the DLC is enabled.""" + + ngp: bool = False + """Whether this location only contains an item in NG+ and later. + + By default, these items aren't randomized or included in the randomization pool, but an option + can be set to enable them even for NG runs.""" + + npc: bool = False + """Whether this item is contingent on killing an NPC or following their quest.""" + + prominent: bool = False + """Whether this is one of few particularly prominent places for items to appear. + + This is a small number of locations (boss drops and progression locations) + intended to be set as priority locations for players who don't want a lot of + mandatory checks. + + For bosses with multiple drops, only one should be marked prominent. + """ + + progression: bool = False + """Whether this location normally contains an item that blocks forward progress.""" + + boss: bool = False + """Whether this location is a reward for defeating a full boss.""" + + miniboss: bool = False + """Whether this location is a reward for defeating a miniboss. + + The classification of "miniboss" is a bit fuzzy, but we consider them to be enemies that are + visually distinctive in their locations, usually bigger than normal enemies, with a guaranteed + item drop. NPCs are never considered minibosses, and some normal-looking enemies with guaranteed + drops aren't either (these are instead classified as hidden locations).""" + + drop: bool = False + """Whether this is an item dropped by a (non-boss) enemy. + + This is automatically set to True if miniboss, mimic, lizard, or hostile_npc is True. + """ + + mimic: bool = False + """Whether this location is dropped by a mimic.""" + + hostile_npc: bool = False + """Whether this location is dropped by a hostile NPC. + + An "NPC" is specifically a human (or rather, ash) is built like a player character rather than a + monster. This includes both scripted invaders and NPCs who are always on the overworld. It does + not include initially-friendly NPCs who become hostile as part of a quest or because you attack + them. + """ + + lizard: bool = False + """Whether this location is dropped by a (small) Crystal Lizard.""" + + shop: bool = False + """Whether this location can appear in an NPC's shop. + + Items like Lapp's Set which can appear both in the overworld and in a shop + should still be tagged as shop. + """ + + conditional: bool = False + """Whether this location is conditional on a progression item. + + This is used to track locations that won't become available until an unknown amount of time into + the run, and as such shouldn't have "similar to the base game" items placed in them. + """ + + hidden: bool = False + """Whether this location is particularly tricky to find. + + This is for players without an encyclopedic knowledge of DS3 who don't want to get stuck looking + for an illusory wall or one random mob with a guaranteed drop. + """ + + @property + def is_event(self) -> bool: + """Whether this location represents an event rather than a specific item pickup.""" + return self.default_item_name is None + + def __post_init__(self): + if not self.is_event: + self.ap_code = self.ap_code or DS3LocationData.__location_id + DS3LocationData.__location_id += 1 + if self.miniboss or self.mimic or self.lizard or self.hostile_npc: self.drop = True + + def location_groups(self) -> List[str]: + """The names of location groups this location should appear in. + + This is computed from the properties assigned to this location.""" + names = [] + if self.prominent: names.append("Prominent") + if self.progression: names.append("Progression") + if self.boss: names.append("Boss Rewards") + if self.miniboss: names.append("Miniboss Rewards") + if self.mimic: names.append("Mimic Rewards") + if self.hostile_npc: names.append("Hostile NPC Rewards") + if self.npc: names.append("Friendly NPC Rewards") + if self.lizard: names.append("Small Crystal Lizards") + if self.hidden: names.append("Hidden") + + default_item = item_dictionary[cast(str, self.default_item_name)] + names.append({ + DS3ItemCategory.WEAPON_UPGRADE_5: "Weapons", + DS3ItemCategory.WEAPON_UPGRADE_10: "Weapons", + DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE: "Weapons", + DS3ItemCategory.SHIELD: "Shields", + DS3ItemCategory.SHIELD_INFUSIBLE: "Shields", + DS3ItemCategory.ARMOR: "Armor", + DS3ItemCategory.RING: "Rings", + DS3ItemCategory.SPELL: "Spells", + DS3ItemCategory.MISC: "Miscellaneous", + DS3ItemCategory.UNIQUE: "Unique", + DS3ItemCategory.BOSS: "Boss Souls", + DS3ItemCategory.SOUL: "Small Souls", + DS3ItemCategory.UPGRADE: "Upgrade", + DS3ItemCategory.HEALING: "Healing", + }[default_item.category]) + if default_item.classification == ItemClassification.progression: + names.append("Progression") + + return names class DarkSouls3Location(Location): game: str = "Dark Souls III" - category: DS3LocationCategory - default_item_name: str + data: DS3LocationData def __init__( self, player: int, - name: str, - category: DS3LocationCategory, - default_item_name: str, - address: Optional[int] = None, - parent: Optional[Region] = None): - super().__init__(player, name, address, parent) - self.default_item_name = default_item_name - self.category = category - - @staticmethod - def get_name_to_id() -> dict: - base_id = 100000 - table_offset = 100 - - table_order = [ - "Firelink Shrine", - "Firelink Shrine Bell Tower", - "High Wall of Lothric", - "Undead Settlement", - "Road of Sacrifices", - "Cathedral of the Deep", - "Farron Keep", - "Catacombs of Carthus", - "Smouldering Lake", - "Irithyll of the Boreal Valley", - "Irithyll Dungeon", - "Profaned Capital", - "Anor Londo", - "Lothric Castle", - "Consumed King's Garden", - "Grand Archives", - "Untended Graves", - "Archdragon Peak", - - "Painted World of Ariandel 1", - "Painted World of Ariandel 2", - "Dreg Heap", - "Ringed City", - - "Progressive Items 1", - "Progressive Items 2", - "Progressive Items 3", - "Progressive Items 4", - "Progressive Items DLC", - "Progressive Items Health", - ] - - output = {} - for i, region_name in enumerate(table_order): - if len(location_tables[region_name]) > table_offset: - raise Exception("A location table has {} entries, that is more than {} entries (table #{})".format(len(location_tables[region_name]), table_offset, i)) - - output.update({location_data.name: id for id, location_data in enumerate(location_tables[region_name], base_id + (table_offset * i))}) - - return output - - -location_tables = { + data: DS3LocationData, + parent: Optional[Region] = None, + event: bool = False): + super().__init__(player, data.name, None if event else data.ap_code, parent) + self.data = data + + +# Naming conventions: +# +# * The regions in item names should match the physical region where the item is +# acquired, even if its logical region is different. For example, Irina's +# inventory appears in the "Undead Settlement" region because she's not +# accessible until there, but it begins with "FS:" because that's where her +# items are purchased. +# +# * Avoid using vanilla enemy placements as landmarks, because these are +# randomized by the enemizer by default. Instead, use generic terms like +# "mob", "boss", and "miniboss". +# +# * Location descriptions don't need to direct the player to the precise spot. +# You can assume the player is broadly familiar with Dark Souls III or willing +# to look at a vanilla guide. Just give a general area to look in or an idea +# of what quest a check is connected to. Terseness is valuable: try to keep +# each location description short enough that the whole line doesn't exceed +# 100 characters. +# +# * Use "[name] drop" for items that require killing an NPC who becomes hostile +# as part of their normal quest, "kill [name]" for items that require killing +# them even when they aren't hostile, and just "[name]" for items that are +# naturally available as part of their quest. +location_tables: Dict[str, List[DS3LocationData]] = { + "Cemetery of Ash": [ + DS3LocationData("CA: Soul of a Deserted Corpse - right of spawn", + "Soul of a Deserted Corpse"), + DS3LocationData("CA: Firebomb - down the cliff edge", "Firebomb x5"), + DS3LocationData("CA: Titanite Shard - jump to coffin", "Titanite Shard"), + DS3LocationData("CA: Soul of an Unknown Traveler - by miniboss", + "Soul of an Unknown Traveler"), + DS3LocationData("CA: Speckled Stoneplate Ring+1 - by miniboss", + "Speckled Stoneplate Ring+1", ngp=True), + DS3LocationData("CA: Titanite Scale - miniboss drop", "Titanite Scale", miniboss=True), + DS3LocationData("CA: Coiled Sword - boss drop", "Coiled Sword", prominent=True, + progression=True, boss=True), + ], "Firelink Shrine": [ - DS3LocationData("FS: Broken Straight Sword", "Broken Straight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("FS: East-West Shield", "East-West Shield", DS3LocationCategory.SHIELD), - DS3LocationData("FS: Uchigatana", "Uchigatana", DS3LocationCategory.WEAPON), - DS3LocationData("FS: Master's Attire", "Master's Attire", DS3LocationCategory.ARMOR), - DS3LocationData("FS: Master's Gloves", "Master's Gloves", DS3LocationCategory.ARMOR), + # Ludleth drop, does not permanently die + DS3LocationData("FS: Skull Ring - kill Ludleth", "Skull Ring", hidden=True, drop=True, + npc=True), + + # Sword Master drops + DS3LocationData("FS: Uchigatana - NPC drop", "Uchigatana", hostile_npc=True), + DS3LocationData("FS: Master's Attire - NPC drop", "Master's Attire", hostile_npc=True), + DS3LocationData("FS: Master's Gloves - NPC drop", "Master's Gloves", hostile_npc=True), + + DS3LocationData("FS: Broken Straight Sword - gravestone after boss", + "Broken Straight Sword"), + DS3LocationData("FS: Homeward Bone - cliff edge after boss", "Homeward Bone"), + DS3LocationData("FS: Ember - path right of Firelink entrance", "Ember"), + DS3LocationData("FS: Soul of a Deserted Corpse - bell tower door", + "Soul of a Deserted Corpse"), + DS3LocationData("FS: East-West Shield - tree by shrine entrance", "East-West Shield"), + DS3LocationData("FS: Homeward Bone - path above shrine entrance", "Homeward Bone"), + DS3LocationData("FS: Ember - above shrine entrance", "Ember"), + DS3LocationData("FS: Wolf Ring+2 - left of boss room exit", "Wolf Ring+2", ngp=True), + # Leonhard (quest) + DS3LocationData("FS: Cracked Red Eye Orb - Leonhard", "Cracked Red Eye Orb x5", + missable=True, npc=True), + # Leonhard (kill or quest), missable because he can disappear sometimes + DS3LocationData("FS: Lift Chamber Key - Leonhard", "Lift Chamber Key", missable=True, + npc=True, drop=True), + + # Shrine Handmaid shop + DS3LocationData("FS: White Sign Soapstone - shop", "White Sign Soapstone", shop=True), + DS3LocationData("FS: Dried Finger - shop", "Dried Finger", shop=True), + DS3LocationData("FS: Tower Key - shop", "Tower Key", progression=True, shop=True), + DS3LocationData("FS: Ember - shop", "Ember", static='99,0:-1:110000:', shop=True), + DS3LocationData("FS: Farron Dart - shop", "Farron Dart", static='99,0:-1:110000:', + shop=True), + DS3LocationData("FS: Soul Arrow - shop", "Soul Arrow", static='99,0:-1:110000:', + shop=True), + DS3LocationData("FS: Heal Aid - shop", "Heal Aid", shop=True), + DS3LocationData("FS: Alluring Skull - Mortician's Ashes", "Alluring Skull", shop=True, + conditional=True), + DS3LocationData("FS: Ember - Mortician's Ashes", "Ember", + static='99,0:-1:110000,70000100:', shop=True, conditional=True), + DS3LocationData("FS: Grave Key - Mortician's Ashes", "Grave Key", shop=True, + conditional=True), + DS3LocationData("FS: Life Ring - Dreamchaser's Ashes", "Life Ring", shop=True, + conditional=True), + # Only if you say where the ashes were found + DS3LocationData("FS: Hidden Blessing - Dreamchaser's Ashes", "Hidden Blessing", + missable=True, shop=True), + DS3LocationData("FS: Lloyd's Shield Ring - Paladin's Ashes", "Lloyd's Shield Ring", + shop=True, conditional=True), + DS3LocationData("FS: Ember - Grave Warden's Ashes", "Ember", + static='99,0:-1:110000,70000103:', shop=True, conditional=True), + # Prisoner Chief's Ashes + DS3LocationData("FS: Karla's Pointed Hat - Prisoner Chief's Ashes", "Karla's Pointed Hat", + static='99,0:-1:110000,70000105:', shop=True, conditional=True), + DS3LocationData("FS: Karla's Coat - Prisoner Chief's Ashes", "Karla's Coat", + static='99,0:-1:110000,70000105:', shop=True, conditional=True), + DS3LocationData("FS: Karla's Gloves - Prisoner Chief's Ashes", "Karla's Gloves", + static='99,0:-1:110000,70000105:', shop=True, conditional=True), + DS3LocationData("FS: Karla's Trousers - Prisoner Chief's Ashes", "Karla's Trousers", + static='99,0:-1:110000,70000105:', shop=True, conditional=True), + DS3LocationData("FS: Xanthous Overcoat - Xanthous Ashes", "Xanthous Overcoat", shop=True, + conditional=True), + DS3LocationData("FS: Xanthous Gloves - Xanthous Ashes", "Xanthous Gloves", shop=True, + conditional=True), + DS3LocationData("FS: Xanthous Trousers - Xanthous Ashes", "Xanthous Trousers", shop=True, + conditional=True), + DS3LocationData("FS: Ember - Dragon Chaser's Ashes", "Ember", + static='99,0:-1:110000,70000108:', shop=True, conditional=True), + DS3LocationData("FS: Washing Pole - Easterner's Ashes", "Washing Pole", shop=True, + conditional=True), + DS3LocationData("FS: Eastern Helm - Easterner's Ashes", "Eastern Helm", shop=True, + conditional=True), + DS3LocationData("FS: Eastern Armor - Easterner's Ashes", "Eastern Armor", shop=True, + conditional=True), + DS3LocationData("FS: Eastern Gauntlets - Easterner's Ashes", "Eastern Gauntlets", + shop=True, conditional=True), + DS3LocationData("FS: Eastern Leggings - Easterner's Ashes", "Eastern Leggings", shop=True, + conditional=True), + DS3LocationData("FS: Wood Grain Ring - Easterner's Ashes", "Wood Grain Ring", shop=True, + conditional=True), + DS3LocationData("FS: Millwood Knight Helm - Captain's Ashes", "Millwood Knight Helm", + dlc=True, shop=True, conditional=True), + DS3LocationData("FS: Millwood Knight Armor - Captain's Ashes", "Millwood Knight Armor", + dlc=True, shop=True, conditional=True), + DS3LocationData("FS: Millwood Knight Gauntlets - Captain's Ashes", + "Millwood Knight Gauntlets", dlc=True, shop=True, conditional=True), + DS3LocationData("FS: Millwood Knight Leggings - Captain's Ashes", + "Millwood Knight Leggings", dlc=True, shop=True, conditional=True), + DS3LocationData("FS: Refined Gem - Captain's Ashes", "Refined Gem", dlc=True, shop=True, + conditional=True), + + # Ludleth Shop + DS3LocationData("FS: Vordt's Great Hammer - Ludleth for Vordt", "Vordt's Great Hammer", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Pontiff's Left Eye - Ludleth for Vordt", "Pontiff's Left Eye", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Bountiful Sunlight - Ludleth for Rosaria", "Bountiful Sunlight", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Darkmoon Longbow - Ludleth for Aldrich", "Darkmoon Longbow", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Lifehunt Scythe - Ludleth for Aldrich", "Lifehunt Scythe", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Hollowslayer Greatsword - Ludleth for Greatwood", + "Hollowslayer Greatsword", missable=True, boss=True, shop=True), + DS3LocationData("FS: Arstor's Spear - Ludleth for Greatwood", "Arstor's Spear", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Crystal Sage's Rapier - Ludleth for Sage", "Crystal Sage's Rapier", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Crystal Hail - Ludleth for Sage", "Crystal Hail", missable=True, + boss=True, shop=True), + DS3LocationData("FS: Cleric's Candlestick - Ludleth for Deacons", "Cleric's Candlestick", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Deep Soul - Ludleth for Deacons", "Deep Soul", missable=True, + boss=True, shop=True), + DS3LocationData("FS: Havel's Ring - Ludleth for Stray Demon", "Havel's Ring", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Boulder Heave - Ludleth for Stray Demon", "Boulder Heave", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Farron Greatsword - Ludleth for Abyss Watchers", "Farron Greatsword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Wolf Knight's Greatsword - Ludleth for Abyss Watchers", + "Wolf Knight's Greatsword", missable=True, boss=True, shop=True), + DS3LocationData("FS: Wolnir's Holy Sword - Ludleth for Wolnir", "Wolnir's Holy Sword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Black Serpent - Ludleth for Wolnir", "Black Serpent", missable=True, + boss=True, shop=True), + DS3LocationData("FS: Demon's Greataxe - Ludleth for Fire Demon", "Demon's Greataxe", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Demon's Fist - Ludleth for Fire Demon", "Demon's Fist", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Old King's Great Hammer - Ludleth for Old Demon King", + "Old King's Great Hammer", missable=True, boss=True, shop=True), + DS3LocationData("FS: Chaos Bed Vestiges - Ludleth for Old Demon King", "Chaos Bed Vestiges", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Greatsword of Judgment - Ludleth for Pontiff", + "Greatsword of Judgment", missable=True, boss=True, shop=True), + DS3LocationData("FS: Profaned Greatsword - Ludleth for Pontiff", "Profaned Greatsword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Yhorm's Great Machete - Ludleth for Yhorm", "Yhorm's Great Machete", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Yhorm's Greatshield - Ludleth for Yhorm", "Yhorm's Greatshield", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Dancer's Enchanted Swords - Ludleth for Dancer", + "Dancer's Enchanted Swords", missable=True, boss=True, shop=True), + DS3LocationData("FS: Soothing Sunlight - Ludleth for Dancer", "Soothing Sunlight", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Dragonslayer Greataxe - Ludleth for Dragonslayer", + "Dragonslayer Greataxe", missable=True, boss=True, shop=True), + DS3LocationData("FS: Dragonslayer Greatshield - Ludleth for Dragonslayer", + "Dragonslayer Greatshield", missable=True, boss=True, shop=True), + DS3LocationData("FS: Moonlight Greatsword - Ludleth for Oceiros", "Moonlight Greatsword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: White Dragon Breath - Ludleth for Oceiros", "White Dragon Breath", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Lorian's Greatsword - Ludleth for Princes", "Lorian's Greatsword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Lothric's Holy Sword - Ludleth for Princes", "Lothric's Holy Sword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Gundyr's Halberd - Ludleth for Champion", "Gundyr's Halberd", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Prisoner's Chain - Ludleth for Champion", "Prisoner's Chain", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Storm Curved Sword - Ludleth for Nameless", "Storm Curved Sword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Dragonslayer Swordspear - Ludleth for Nameless", + "Dragonslayer Swordspear", missable=True, boss=True, shop=True), + DS3LocationData("FS: Lightning Storm - Ludleth for Nameless", "Lightning Storm", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Firelink Greatsword - Ludleth for Cinder", "Firelink Greatsword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Sunlight Spear - Ludleth for Cinder", "Sunlight Spear", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Friede's Great Scythe - Ludleth for Friede", "Friede's Great Scythe", + missable=True, dlc=True, boss=True, shop=True), + DS3LocationData("FS: Rose of Ariandel - Ludleth for Friede", "Rose of Ariandel", + missable=True, dlc=True, boss=True, shop=True), + DS3LocationData("FS: Demon's Scar - Ludleth for Demon Prince", "Demon's Scar", + missable=True, dlc=True, boss=True, shop=True), + DS3LocationData("FS: Seething Chaos - Ludleth for Demon Prince", "Seething Chaos", + missable=True, dlc=True, boss=True, shop=True), + DS3LocationData("FS: Frayed Blade - Ludleth for Midir", "Frayed Blade", missable=True, + dlc=True, boss=True, shop=True), + DS3LocationData("FS: Old Moonlight - Ludleth for Midir", "Old Moonlight", missable=True, + dlc=True, boss=True, shop=True), + DS3LocationData("FS: Gael's Greatsword - Ludleth for Gael", "Gael's Greatsword", + missable=True, dlc=True, boss=True, shop=True), + DS3LocationData("FS: Repeating Crossbow - Ludleth for Gael", "Repeating Crossbow", + missable=True, dlc=True, boss=True, shop=True), ], "Firelink Shrine Bell Tower": [ - DS3LocationData("FSBT: Covetous Silver Serpent Ring", "Covetous Silver Serpent Ring", DS3LocationCategory.RING), - DS3LocationData("FSBT: Fire Keeper Robe", "Fire Keeper Robe", DS3LocationCategory.ARMOR), - DS3LocationData("FSBT: Fire Keeper Gloves", "Fire Keeper Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("FSBT: Fire Keeper Skirt", "Fire Keeper Skirt", DS3LocationCategory.ARMOR), - DS3LocationData("FSBT: Estus Ring", "Estus Ring", DS3LocationCategory.RING), - DS3LocationData("FSBT: Fire Keeper Soul", "Fire Keeper Soul", DS3LocationCategory.MISC), + # Guarded by Tower Key + DS3LocationData("FSBT: Homeward Bone - roof", "Homeward Bone x3"), + DS3LocationData("FSBT: Estus Ring - tower base", "Estus Ring"), + DS3LocationData("FSBT: Estus Shard - rafters", "Estus Shard"), + DS3LocationData("FSBT: Fire Keeper Soul - tower top", "Fire Keeper Soul"), + DS3LocationData("FSBT: Fire Keeper Robe - partway down tower", "Fire Keeper Robe"), + DS3LocationData("FSBT: Fire Keeper Gloves - partway down tower", "Fire Keeper Gloves"), + DS3LocationData("FSBT: Fire Keeper Skirt - partway down tower", "Fire Keeper Skirt"), + DS3LocationData("FSBT: Covetous Silver Serpent Ring - illusory wall past rafters", + "Covetous Silver Serpent Ring", hidden=True), + DS3LocationData("FSBT: Twinkling Titanite - lizard behind Firelink", + "Twinkling Titanite", lizard=True), + + # Mark all crow trades as missable since no one wants to have to try trading everything just + # in case it gives a progression item. + DS3LocationData("FSBT: Iron Bracelets - crow for Homeward Bone", "Iron Bracelets", + missable=True), + DS3LocationData("FSBT: Ring of Sacrifice - crow for Loretta's Bone", "Ring of Sacrifice", + missable=True), + DS3LocationData("FSBT: Porcine Shield - crow for Undead Bone Shard", "Porcine Shield", + missable=True), + DS3LocationData("FSBT: Lucatiel's Mask - crow for Vertebra Shackle", "Lucatiel's Mask", + missable=True), + DS3LocationData("FSBT: Very good! Carving - crow for Divine Blessing", + "Very good! Carving", missable=True), + DS3LocationData("FSBT: Thank you Carving - crow for Hidden Blessing", "Thank you Carving", + missable=True), + DS3LocationData("FSBT: I'm sorry Carving - crow for Shriving Stone", "I'm sorry Carving", + missable=True), + DS3LocationData("FSBT: Sunlight Shield - crow for Mendicant's Staff", "Sunlight Shield", + missable=True), + DS3LocationData("FSBT: Hollow Gem - crow for Eleonora", "Hollow Gem", + missable=True), + DS3LocationData("FSBT: Titanite Scale - crow for Blacksmith Hammer", "Titanite Scale x3", + static='99,0:50004330::', missable=True), + DS3LocationData("FSBT: Help me! Carving - crow for any sacred chime", "Help me! Carving", + missable=True), + DS3LocationData("FSBT: Titanite Slab - crow for Coiled Sword Fragment", "Titanite Slab", + missable=True), + DS3LocationData("FSBT: Hello Carving - crow for Alluring Skull", "Hello Carving", + missable=True), + DS3LocationData("FSBT: Armor of the Sun - crow for Siegbräu", "Armor of the Sun", + missable=True), + DS3LocationData("FSBT: Large Titanite Shard - crow for Firebomb", "Large Titanite Shard", + missable=True), + DS3LocationData("FSBT: Titanite Chunk - crow for Black Firebomb", "Titanite Chunk", + missable=True), + DS3LocationData("FSBT: Iron Helm - crow for Lightning Urn", "Iron Helm", missable=True), + DS3LocationData("FSBT: Twinkling Titanite - crow for Prism Stone", "Twinkling Titanite", + missable=True), + DS3LocationData("FSBT: Iron Leggings - crow for Seed of a Giant Tree", "Iron Leggings", + missable=True), + DS3LocationData("FSBT: Lightning Gem - crow for Xanthous Crown", "Lightning Gem", + missable=True), + DS3LocationData("FSBT: Twinkling Titanite - crow for Large Leather Shield", + "Twinkling Titanite", missable=True), + DS3LocationData("FSBT: Blessed Gem - crow for Moaning Shield", "Blessed Gem", + missable=True), ], "High Wall of Lothric": [ - DS3LocationData("HWL: Deep Battle Axe", "Deep Battle Axe", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Club", "Club", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Claymore", "Claymore", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Binoculars", "Binoculars", DS3LocationCategory.MISC), - DS3LocationData("HWL: Longbow", "Longbow", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Mail Breaker", "Mail Breaker", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Broadsword", "Broadsword", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Silver Eagle Kite Shield", "Silver Eagle Kite Shield", DS3LocationCategory.SHIELD), - DS3LocationData("HWL: Astora's Straight Sword", "Astora's Straight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Cell Key", "Cell Key", DS3LocationCategory.KEY), - DS3LocationData("HWL: Rapier", "Rapier", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Lucerne", "Lucerne", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Small Lothric Banner", "Small Lothric Banner", DS3LocationCategory.KEY), - DS3LocationData("HWL: Basin of Vows", "Basin of Vows", DS3LocationCategory.KEY), - DS3LocationData("HWL: Soul of Boreal Valley Vordt", "Soul of Boreal Valley Vordt", DS3LocationCategory.BOSS), - DS3LocationData("HWL: Soul of the Dancer", "Soul of the Dancer", DS3LocationCategory.BOSS), - DS3LocationData("HWL: Way of Blue", "Way of Blue", DS3LocationCategory.MISC), - DS3LocationData("HWL: Greirat's Ashes", "Greirat's Ashes", DS3LocationCategory.NPC), - DS3LocationData("HWL: Blue Tearstone Ring", "Blue Tearstone Ring", DS3LocationCategory.NPC), + DS3LocationData("HWL: Soul of Boreal Valley Vordt", "Soul of Boreal Valley Vordt", + prominent=True, boss=True), + DS3LocationData("HWL: Soul of the Dancer", "Soul of the Dancer", prominent=True, + boss=True), + DS3LocationData("HWL: Basin of Vows - Emma", "Basin of Vows", prominent=True, + progression=True, conditional=True), + DS3LocationData("HWL: Small Lothric Banner - Emma", "Small Lothric Banner", + prominent=True, progression=True), + DS3LocationData("HWL: Green Blossom - fort walkway, hall behind wheel", "Green Blossom x2", + hidden=True), + DS3LocationData("HWL: Gold Pine Resin - corpse tower, drop", "Gold Pine Resin x2", + hidden=True), + DS3LocationData("HWL: Large Soul of a Deserted Corpse - flame plaza", + "Large Soul of a Deserted Corpse"), + DS3LocationData("HWL: Soul of a Deserted Corpse - by wall tower door", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Standard Arrow - back tower", "Standard Arrow x12"), + DS3LocationData("HWL: Longbow - back tower", "Longbow"), + DS3LocationData("HWL: Firebomb - wall tower, beam", "Firebomb x3"), + DS3LocationData("HWL: Throwing Knife - wall tower, path to Greirat", "Throwing Knife x8"), + DS3LocationData("HWL: Soul of a Deserted Corpse - corpse tower, bottom floor", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Club - flame plaza", "Club"), + DS3LocationData("HWL: Claymore - flame plaza", "Claymore"), + DS3LocationData("HWL: Ember - flame plaza", "Ember"), + DS3LocationData("HWL: Firebomb - corpse tower, under table", "Firebomb x2"), + DS3LocationData("HWL: Titanite Shard - wall tower, corner by bonfire", "Titanite Shard", + hidden=True), + DS3LocationData("HWL: Undead Hunter Charm - fort, room off entry, in pot", + "Undead Hunter Charm x2", hidden=True), + DS3LocationData("HWL: Firebomb - top of ladder to fountain", "Firebomb x3"), + DS3LocationData("HWL: Cell Key - fort ground, down stairs", "Cell Key"), + DS3LocationData("HWL: Ember - fountain #1", "Ember"), + DS3LocationData("HWL: Soul of a Deserted Corpse - fort entry, corner", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Lucerne - promenade, side path", "Lucerne"), + DS3LocationData("HWL: Mail Breaker - wall tower, path to Greirat", "Mail Breaker"), + DS3LocationData("HWL: Titanite Shard - fort ground behind crates", "Titanite Shard", + hidden=True), + DS3LocationData("HWL: Rapier - fountain, corner", "Rapier"), + DS3LocationData("HWL: Titanite Shard - fort, room off entry", "Titanite Shard"), + DS3LocationData("HWL: Large Soul of a Deserted Corpse - fort roof", + "Large Soul of a Deserted Corpse"), + DS3LocationData("HWL: Black Firebomb - small roof over fountain", "Black Firebomb x3"), + DS3LocationData("HWL: Soul of a Deserted Corpse - path to corpse tower", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Ember - fountain #2", "Ember"), + DS3LocationData("HWL: Large Soul of a Deserted Corpse - platform by fountain", + "Large Soul of a Deserted Corpse", hidden=True), # Easily missed turnoff + DS3LocationData("HWL: Binoculars - corpse tower, upper platform", "Binoculars"), + DS3LocationData("HWL: Ring of Sacrifice - awning by fountain", + "Ring of Sacrifice", hidden=True), # Easily missed turnoff + DS3LocationData("HWL: Throwing Knife - shortcut, lift top", "Throwing Knife x6"), + DS3LocationData("HWL: Soul of a Deserted Corpse - path to back tower, by lift door", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Green Blossom - shortcut, lower courtyard", "Green Blossom x3"), + DS3LocationData("HWL: Broadsword - fort, room off walkway", "Broadsword"), + DS3LocationData("HWL: Soul of a Deserted Corpse - fountain, path to promenade", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Firebomb - fort roof", "Firebomb x3"), + DS3LocationData("HWL: Soul of a Deserted Corpse - wall tower, right of exit", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Estus Shard - fort ground, on anvil", "Estus Shard"), + DS3LocationData("HWL: Fleshbite Ring+1 - fort roof, jump to other roof", + "Fleshbite Ring+1", ngp=True, hidden=True), # Hidden jump + DS3LocationData("HWL: Ring of the Evil Eye+2 - fort ground, far wall", + "Ring of the Evil Eye+2", ngp=True, hidden=True), # In barrels + DS3LocationData("HWL: Silver Eagle Kite Shield - fort mezzanine", + "Silver Eagle Kite Shield"), + DS3LocationData("HWL: Astora Straight Sword - fort walkway, drop down", + "Astora Straight Sword", hidden=True), # Hidden fall + DS3LocationData("HWL: Battle Axe - flame tower, mimic", "Battle Axe", + static='01,0:53000960::', mimic=True), + + # Only dropped after transformation + DS3LocationData("HWL: Ember - fort roof, transforming hollow", "Ember", hidden=True), + DS3LocationData("HWL: Titanite Shard - fort roof, transforming hollow", "Titanite Shard", + hidden=True), + DS3LocationData("HWL: Ember - back tower, transforming hollow", "Ember", hidden=True), + DS3LocationData("HWL: Titanite Shard - back tower, transforming hollow", "Titanite Shard", + hidden=True), + + DS3LocationData("HWL: Refined Gem - promenade miniboss", "Refined Gem", miniboss=True), + DS3LocationData("HWL: Way of Blue - Emma", "Way of Blue"), + # Categorize this as an NPC item so that it doesn't get randomized if the Lift Chamber Key + # isn't randomized, since in that case it's missable. + DS3LocationData("HWL: Red Eye Orb - wall tower, miniboss", "Red Eye Orb", + conditional=True, miniboss=True, npc=True), + DS3LocationData("HWL: Raw Gem - fort roof, lizard", "Raw Gem", lizard=True), ], "Undead Settlement": [ - DS3LocationData("US: Small Leather Shield", "Small Leather Shield", DS3LocationCategory.SHIELD), - DS3LocationData("US: Whip", "Whip", DS3LocationCategory.WEAPON), - DS3LocationData("US: Reinforced Club", "Reinforced Club", DS3LocationCategory.WEAPON), - DS3LocationData("US: Blue Wooden Shield", "Blue Wooden Shield", DS3LocationCategory.SHIELD), - DS3LocationData("US: Cleric Hat", "Cleric Hat", DS3LocationCategory.ARMOR), - DS3LocationData("US: Cleric Blue Robe", "Cleric Blue Robe", DS3LocationCategory.ARMOR), - DS3LocationData("US: Cleric Gloves", "Cleric Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("US: Cleric Trousers", "Cleric Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("US: Mortician's Ashes", "Mortician's Ashes", DS3LocationCategory.KEY), - DS3LocationData("US: Caestus", "Caestus", DS3LocationCategory.WEAPON), - DS3LocationData("US: Plank Shield", "Plank Shield", DS3LocationCategory.SHIELD), - DS3LocationData("US: Flame Stoneplate Ring", "Flame Stoneplate Ring", DS3LocationCategory.RING), - DS3LocationData("US: Caduceus Round Shield", "Caduceus Round Shield", DS3LocationCategory.SHIELD), - DS3LocationData("US: Fire Clutch Ring", "Fire Clutch Ring", DS3LocationCategory.RING), - DS3LocationData("US: Partizan", "Partizan", DS3LocationCategory.WEAPON), - DS3LocationData("US: Bloodbite Ring", "Bloodbite Ring", DS3LocationCategory.RING), - DS3LocationData("US: Red Hilted Halberd", "Red Hilted Halberd", DS3LocationCategory.WEAPON), - DS3LocationData("US: Saint's Talisman", "Saint's Talisman", DS3LocationCategory.WEAPON), - DS3LocationData("US: Irithyll Straight Sword", "Irithyll Straight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("US: Large Club", "Large Club", DS3LocationCategory.WEAPON), - DS3LocationData("US: Northern Helm", "Northern Helm", DS3LocationCategory.ARMOR), - DS3LocationData("US: Northern Armor", "Northern Armor", DS3LocationCategory.ARMOR), - DS3LocationData("US: Northern Gloves", "Northern Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("US: Northern Trousers", "Northern Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("US: Flynn's Ring", "Flynn's Ring", DS3LocationCategory.RING), - DS3LocationData("US: Mirrah Vest", "Mirrah Vest", DS3LocationCategory.ARMOR), - DS3LocationData("US: Mirrah Gloves", "Mirrah Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("US: Mirrah Trousers", "Mirrah Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("US: Chloranthy Ring", "Chloranthy Ring", DS3LocationCategory.RING), - DS3LocationData("US: Loincloth", "Loincloth", DS3LocationCategory.ARMOR), - DS3LocationData("US: Wargod Wooden Shield", "Wargod Wooden Shield", DS3LocationCategory.SHIELD), - DS3LocationData("US: Loretta's Bone", "Loretta's Bone", DS3LocationCategory.KEY), - DS3LocationData("US: Hand Axe", "Hand Axe", DS3LocationCategory.WEAPON), - DS3LocationData("US: Great Scythe", "Great Scythe", DS3LocationCategory.WEAPON), - DS3LocationData("US: Soul of the Rotted Greatwood", "Soul of the Rotted Greatwood", DS3LocationCategory.BOSS), - DS3LocationData("US: Hawk Ring", "Hawk Ring", DS3LocationCategory.RING), - DS3LocationData("US: Warrior of Sunlight", "Warrior of Sunlight", DS3LocationCategory.MISC), - DS3LocationData("US: Blessed Red and White Shield+1", "Blessed Red and White Shield+1", DS3LocationCategory.SHIELD), - DS3LocationData("US: Irina's Ashes", "Irina's Ashes", DS3LocationCategory.NPC), - DS3LocationData("US: Cornyx's Ashes", "Cornyx's Ashes", DS3LocationCategory.NPC), - DS3LocationData("US: Cornyx's Wrap", "Cornyx's Wrap", DS3LocationCategory.NPC), - DS3LocationData("US: Cornyx's Garb", "Cornyx's Garb", DS3LocationCategory.NPC), - DS3LocationData("US: Cornyx's Skirt", "Cornyx's Skirt", DS3LocationCategory.NPC), - DS3LocationData("US: Pyromancy Flame", "Pyromancy Flame", DS3LocationCategory.NPC), - DS3LocationData("US: Transposing Kiln", "Transposing Kiln", DS3LocationCategory.MISC), - DS3LocationData("US: Tower Key", "Tower Key", DS3LocationCategory.NPC), + DS3LocationData("US: Soul of the Rotted Greatwood", "Soul of the Rotted Greatwood", + prominent=True, boss=True), + DS3LocationData("US: Transposing Kiln - boss drop", "Transposing Kiln", boss=True), + # Missable because it's unavailable if you start as a Pyromancer + DS3LocationData("US: Pyromancy Flame - Cornyx", "Pyromancy Flame", missable=True, + npc=True), + DS3LocationData("US: Old Sage's Blindfold - kill Cornyx", "Old Sage's Blindfold", + npc=True), + DS3LocationData("US: Cornyx's Garb - kill Cornyx", "Cornyx's Garb", + static='02,0:50006141::', npc=True), + DS3LocationData("US: Cornyx's Wrap - kill Cornyx", "Cornyx's Wrap", + static='02,0:50006141::', npc=True), + DS3LocationData("US: Cornyx's Skirt - kill Cornyx", "Cornyx's Skirt", + static='02,0:50006141::', npc=True), + DS3LocationData("US: Tower Key - kill Irina", "Tower Key", missable=True, npc=True), + DS3LocationData("US: Flynn's Ring - tower village, rooftop", "Flynn's Ring"), + DS3LocationData("US: Undead Bone Shard - by white tree", "Undead Bone Shard"), + DS3LocationData("US: Alluring Skull - foot, behind carriage", "Alluring Skull x2"), + DS3LocationData("US: Mortician's Ashes - graveyard by white tree", "Mortician's Ashes", + progression=True), + DS3LocationData("US: Homeward Bone - tower village, jump from roof", "Homeward Bone x2", + static='02,0:53100040::', hidden=True), # Hidden fall + DS3LocationData("US: Caduceus Round Shield - right after stable exit", + "Caduceus Round Shield"), + DS3LocationData("US: Ember - tower basement, miniboss", "Ember"), + DS3LocationData("US: Soul of an Unknown Traveler - chasm crypt", + "Soul of an Unknown Traveler"), + DS3LocationData("US: Repair Powder - first building, balcony", "Repair Powder x2"), + DS3LocationData("US: Homeward Bone - stable roof", "Homeward Bone x2", + static='02,0:53100090::'), + DS3LocationData("US: Titanite Shard - back alley, side path", "Titanite Shard"), + DS3LocationData("US: Wargod Wooden Shield - Pit of Hollows", "Wargod Wooden Shield"), + DS3LocationData("US: Large Soul of a Deserted Corpse - on the way to tower, by well", + "Large Soul of a Deserted Corpse"), + DS3LocationData("US: Ember - bridge on the way to tower", "Ember"), + DS3LocationData("US: Large Soul of a Deserted Corpse - stable", + "Large Soul of a Deserted Corpse"), + DS3LocationData("US: Titanite Shard - porch after burning tree", "Titanite Shard"), + DS3LocationData("US: Alluring Skull - tower village building, upstairs", + "Alluring Skull x2"), + DS3LocationData("US: Charcoal Pine Bundle - first building, middle floor", + "Charcoal Pine Bundle x2"), + DS3LocationData("US: Blue Wooden Shield - graveyard by white tree", "Blue Wooden Shield"), + DS3LocationData("US: Cleric Hat - graveyard by white tree", "Cleric Hat"), + DS3LocationData("US: Cleric Blue Robe - graveyard by white tree", "Cleric Blue Robe"), + DS3LocationData("US: Cleric Gloves - graveyard by white tree", "Cleric Gloves"), + DS3LocationData("US: Cleric Trousers - graveyard by white tree", "Cleric Trousers"), + DS3LocationData("US: Soul of an Unknown Traveler - portcullis by burning tree", + "Soul of an Unknown Traveler"), + DS3LocationData("US: Charcoal Pine Resin - hanging corpse room", "Charcoal Pine Resin x2"), + DS3LocationData("US: Loincloth - by Velka statue", "Loincloth"), + DS3LocationData("US: Bloodbite Ring - miniboss in sewer", "Bloodbite Ring", + miniboss=True), # Giant Rat drop + DS3LocationData("US: Charcoal Pine Bundle - first building, bottom floor", + "Charcoal Pine Bundle x2"), + DS3LocationData("US: Soul of an Unknown Traveler - back alley, past crates", + "Soul of an Unknown Traveler", hidden=True), + DS3LocationData("US: Titanite Shard - back alley, up ladder", "Titanite Shard"), + DS3LocationData("US: Red Hilted Halberd - chasm crypt", "Red Hilted Halberd"), + DS3LocationData("US: Rusted Coin - awning above Dilapidated Bridge", "Rusted Coin x2"), + DS3LocationData("US: Caestus - sewer", "Caestus"), + DS3LocationData("US: Saint's Talisman - chasm, by ladder", "Saint's Talisman"), + DS3LocationData("US: Alluring Skull - on the way to tower, behind building", + "Alluring Skull x3"), + DS3LocationData("US: Large Club - tower village, by miniboss", "Large Club"), + DS3LocationData("US: Titanite Shard - chasm #1", "Titanite Shard"), + DS3LocationData("US: Titanite Shard - chasm #2", "Titanite Shard"), + DS3LocationData("US: Fading Soul - outside stable", "Fading Soul"), + DS3LocationData("US: Titanite Shard - lower path to Cliff Underside", "Titanite Shard", + hidden=True), # hidden fall + DS3LocationData("US: Hand Axe - by Cornyx", "Hand Axe"), + DS3LocationData("US: Soul of an Unknown Traveler - pillory past stable", + "Soul of an Unknown Traveler"), + DS3LocationData("US: Ember - by stairs to boss", "Ember"), + DS3LocationData("US: Mirrah Vest - tower village, jump from roof", "Mirrah Vest", + hidden=True), # Hidden fall + DS3LocationData("US: Mirrah Gloves - tower village, jump from roof", "Mirrah Gloves", + hidden=True), # Hidden fall + DS3LocationData("US: Mirrah Trousers - tower village, jump from roof", "Mirrah Trousers", + hidden=True), # Hidden fall + DS3LocationData("US: Plank Shield - outside stable, by NPC", "Plank Shield"), + DS3LocationData("US: Red Bug Pellet - tower village building, basement", + "Red Bug Pellet x2"), + DS3LocationData("US: Chloranthy Ring - tower village, jump from roof", "Chloranthy Ring", + hidden=True), # Hidden fall + DS3LocationData("US: Fire Clutch Ring - wooden walkway past stable", "Fire Clutch Ring"), + DS3LocationData("US: Estus Shard - under burning tree", "Estus Shard"), + DS3LocationData("US: Firebomb - stable roof", "Firebomb x6"), + # In enemy rando, the enemy may not burst through the wall and make this room obvious + DS3LocationData("US: Whip - back alley, behind wooden wall", "Whip", hidden=True), + DS3LocationData("US: Great Scythe - building by white tree, balcony", "Great Scythe"), + DS3LocationData("US: Homeward Bone - foot, drop overlook", "Homeward Bone", + static='02,0:53100540::'), + DS3LocationData("US: Large Soul of a Deserted Corpse - around corner by Cliff Underside", + "Large Soul of a Deserted Corpse", hidden=True), # Hidden corner + DS3LocationData("US: Ember - behind burning tree", "Ember"), + DS3LocationData("US: Large Soul of a Deserted Corpse - across from Foot of the High Wall", + "Large Soul of a Deserted Corpse"), + DS3LocationData("US: Fading Soul - by white tree", "Fading Soul"), + DS3LocationData("US: Young White Branch - by white tree #1", "Young White Branch"), + DS3LocationData("US: Ember - by white tree", "Ember"), + DS3LocationData("US: Large Soul of a Deserted Corpse - by white tree", + "Large Soul of a Deserted Corpse"), + DS3LocationData("US: Young White Branch - by white tree #2", "Young White Branch"), + DS3LocationData("US: Reinforced Club - by white tree", "Reinforced Club"), + DS3LocationData("US: Soul of a Nameless Soldier - top of tower", + "Soul of a Nameless Soldier"), + DS3LocationData("US: Loretta's Bone - first building, hanging corpse on balcony", + "Loretta's Bone"), + DS3LocationData("US: Northern Helm - tower village, hanging corpse", "Northern Helm"), + DS3LocationData("US: Northern Armor - tower village, hanging corpse", "Northern Armor"), + DS3LocationData("US: Northern Gloves - tower village, hanging corpse", "Northern Gloves"), + DS3LocationData("US: Northern Trousers - tower village, hanging corpse", + "Northern Trousers"), + DS3LocationData("US: Partizan - hanging corpse above Cliff Underside", "Partizan", + missable=True), # requires projectile + DS3LocationData("US: Flame Stoneplate Ring - hanging corpse by Mound-Maker transport", + "Flame Stoneplate Ring"), + DS3LocationData("US: Red and White Shield - chasm, hanging corpse", "Red and White Shield", + static="02,0:53100740::", missable=True), # requires projectile + DS3LocationData("US: Small Leather Shield - first building, hanging corpse by entrance", + "Small Leather Shield"), + DS3LocationData("US: Pale Tongue - tower village, hanging corpse", "Pale Tongue"), + DS3LocationData("US: Large Soul of a Deserted Corpse - hanging corpse room, over stairs", + "Large Soul of a Deserted Corpse"), + DS3LocationData("US: Kukri - hanging corpse above burning tree", "Kukri x9", + missable=True), # requires projectile + DS3LocationData("US: Life Ring+1 - tower on the way to village", "Life Ring+1", ngp=True), + DS3LocationData("US: Poisonbite Ring+1 - graveyard by white tree, near well", + "Poisonbite Ring+1", ngp=True), + DS3LocationData("US: Covetous Silver Serpent Ring+2 - tower village, drop down from roof", + "Covetous Silver Serpent Ring+2", ngp=True, hidden=True), # Hidden fall + DS3LocationData("US: Human Pine Resin - tower village building, chest upstairs", + "Human Pine Resin x4"), + DS3LocationData("US: Homeward Bone - tower village, right at start", "Homeward Bone", + static='02,0:53100540::'), + DS3LocationData("US: Irithyll Straight Sword - miniboss drop, by Road of Sacrifices", + "Irithyll Straight Sword", miniboss=True), + DS3LocationData("US: Fire Gem - tower village, miniboss drop", "Fire Gem", miniboss=True), + DS3LocationData("US: Warrior of Sunlight - hanging corpse room, drop through hole", + "Warrior of Sunlight", hidden=True), # hidden fall + DS3LocationData("US: Mound-makers - Hodrick", "Mound-makers", missable=True), + DS3LocationData("US: Sharp Gem - lizard by Dilapidated Bridge", "Sharp Gem", lizard=True), + DS3LocationData("US: Heavy Gem - chasm, lizard", "Heavy Gem", lizard=True), + DS3LocationData("US: Siegbräu - Siegward", "Siegbräu", missable=True, npc=True), + DS3LocationData("US: Heavy Gem - Hawkwood", "Heavy Gem", static='00,0:50006070::', + missable=True, npc=True), # Hawkwood (quest, after Greatwood or Sage) + DS3LocationData("US -> RS", None), + + # Yoel/Yuria of Londor + DS3LocationData("FS: Soul Arrow - Yoel/Yuria", "Soul Arrow", + static='99,0:-1:50000,110000,70000116:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Heavy Soul Arrow - Yoel/Yuria", "Heavy Soul Arrow", + static='99,0:-1:50000,110000,70000116:', + missable=True, npc=True, shop=True), + DS3LocationData("FS: Magic Weapon - Yoel/Yuria", "Magic Weapon", + static='99,0:-1:50000,110000,70000116:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Magic Shield - Yoel/Yuria", "Magic Shield", + static='99,0:-1:50000,110000,70000116:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Soul Greatsword - Yoel/Yuria", "Soul Greatsword", + static='99,0:-1:50000,110000,70000450,70000475:', missable=True, + npc=True, shop=True), + DS3LocationData("FS: Dark Hand - Yoel/Yuria", "Dark Hand", missable=True, npc=True), + DS3LocationData("FS: Untrue White Ring - Yoel/Yuria", "Untrue White Ring", missable=True, + npc=True), + DS3LocationData("FS: Untrue Dark Ring - Yoel/Yuria", "Untrue Dark Ring", missable=True, + npc=True), + DS3LocationData("FS: Londor Braille Divine Tome - Yoel/Yuria", "Londor Braille Divine Tome", + static='99,0:-1:40000,110000,70000116:', missable=True, npc=True), + DS3LocationData("FS: Darkdrift - Yoel/Yuria", "Darkdrift", missable=True, drop=True, + npc=True), # kill her or kill Soul of Cinder + + # Cornyx of the Great Swamp + # These aren't missable because the Shrine Handmaid will carry them if you kill Cornyx. + DS3LocationData("FS: Fireball - Cornyx", "Fireball", npc=True, shop=True), + DS3LocationData("FS: Fire Surge - Cornyx", "Fire Surge", npc=True, shop=True), + DS3LocationData("FS: Great Combustion - Cornyx", "Great Combustion", npc=True, + shop=True), + DS3LocationData("FS: Flash Sweat - Cornyx", "Flash Sweat", npc=True, shop=True), + # These are missable if you kill Cornyx before giving him the right tomes. + DS3LocationData("FS: Poison Mist - Cornyx for Great Swamp Tome", "Poison Mist", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Fire Orb - Cornyx for Great Swamp Tome", "Fire Orb", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Profuse Sweat - Cornyx for Great Swamp Tome", "Profuse Sweat", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Bursting Fireball - Cornyx for Great Swamp Tome", "Bursting Fireball", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Acid Surge - Cornyx for Carthus Tome", "Acid Surge", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Carthus Flame Arc - Cornyx for Carthus Tome", "Carthus Flame Arc", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Carthus Beacon - Cornyx for Carthus Tome", "Carthus Beacon", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Great Chaos Fire Orb - Cornyx for Izalith Tome", + "Great Chaos Fire Orb", missable=True, npc=True, shop=True), + DS3LocationData("FS: Chaos Storm - Cornyx for Izalith Tome", "Chaos Storm", missable=True, + npc=True, shop=True), + + # Irina of Carim + # These aren't in their own location because you don't actually need the Grave Key to access + # Irena—you can just fall down the cliff near Eygon. + DS3LocationData("FS: Saint's Ring - Irina", "Saint's Ring", npc=True, shop=True), + DS3LocationData("FS: Heal - Irina", "Heal", npc=True, shop=True), + DS3LocationData("FS: Replenishment - Irina", "Replenishment", npc=True, shop=True), + DS3LocationData("FS: Caressing Tears - Irina", "Caressing Tears", npc=True, shop=True), + DS3LocationData("FS: Homeward - Irina", "Homeward", npc=True, shop=True), + DS3LocationData("FS: Med Heal - Irina for Tome of Carim", "Med Heal", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Tears of Denial - Irina for Tome of Carim", "Tears of Denial", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Force - Irina for Tome of Carim", "Force", missable=True, npc=True, + shop=True), + DS3LocationData("FS: Bountiful Light - Irina for Tome of Lothric", "Bountiful Light", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Magic Barrier - Irina for Tome of Lothric", "Magic Barrier", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Blessed Weapon - Irina for Tome of Lothric", "Blessed Weapon", + missable=True, npc=True, shop=True), ], "Road of Sacrifices": [ - DS3LocationData("RS: Brigand Twindaggers", "Brigand Twindaggers", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Brigand Hood", "Brigand Hood", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Brigand Armor", "Brigand Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Brigand Gauntlets", "Brigand Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Brigand Trousers", "Brigand Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Butcher Knife", "Butcher Knife", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Brigand Axe", "Brigand Axe", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Braille Divine Tome of Carim", "Braille Divine Tome of Carim", DS3LocationCategory.MISC), - DS3LocationData("RS: Morne's Ring", "Morne's Ring", DS3LocationCategory.RING), - DS3LocationData("RS: Twin Dragon Greatshield", "Twin Dragon Greatshield", DS3LocationCategory.SHIELD), - DS3LocationData("RS: Heretic's Staff", "Heretic's Staff", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Sorcerer Hood", "Sorcerer Hood", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sorcerer Robe", "Sorcerer Robe", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sorcerer Gloves", "Sorcerer Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sorcerer Trousers", "Sorcerer Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sage Ring", "Sage Ring", DS3LocationCategory.RING), - DS3LocationData("RS: Fallen Knight Helm", "Fallen Knight Helm", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Fallen Knight Armor", "Fallen Knight Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Fallen Knight Gauntlets", "Fallen Knight Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Fallen Knight Trousers", "Fallen Knight Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Conjurator Hood", "Conjurator Hood", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Conjurator Robe", "Conjurator Robe", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Conjurator Manchettes", "Conjurator Manchettes", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Conjurator Boots", "Conjurator Boots", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Great Swamp Pyromancy Tome", "Great Swamp Pyromancy Tome", DS3LocationCategory.MISC), - DS3LocationData("RS: Great Club", "Great Club", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Exile Greatsword", "Exile Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Farron Coal", "Farron Coal", DS3LocationCategory.MISC), - DS3LocationData("RS: Sellsword Twinblades", "Sellsword Twinblades", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Sellsword Helm", "Sellsword Helm", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sellsword Armor", "Sellsword Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sellsword Gauntlet", "Sellsword Gauntlet", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sellsword Trousers", "Sellsword Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Golden Falcon Shield", "Golden Falcon Shield", DS3LocationCategory.SHIELD), - DS3LocationData("RS: Herald Helm", "Herald Helm", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Herald Armor", "Herald Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Herald Gloves", "Herald Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Herald Trousers", "Herald Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Grass Crest Shield", "Grass Crest Shield", DS3LocationCategory.SHIELD), - DS3LocationData("RS: Soul of a Crystal Sage", "Soul of a Crystal Sage", DS3LocationCategory.BOSS), - DS3LocationData("RS: Great Swamp Ring", "Great Swamp Ring", DS3LocationCategory.RING), - DS3LocationData("RS: Orbeck's Ashes", "Orbeck's Ashes", DS3LocationCategory.NPC), + DS3LocationData("RS: Soul of a Crystal Sage", "Soul of a Crystal Sage", prominent=True, + boss=True), + DS3LocationData("RS: Exile Greatsword - NPC drop by Farron Keep", "Exile Greatsword", + hostile_npc=True), # Exile Knight #2 drop + DS3LocationData("RS: Great Club - NPC drop by Farron Keep", "Great Club", + hostile_npc=True), # Exile Knight #1 drop + DS3LocationData("RS: Heysel Pick - Heysel drop", "Heysel Pick", missable=True, + hostile_npc=True), + DS3LocationData("RS: Xanthous Crown - Heysel drop", "Xanthous Crown", missable=True, + hostile_npc=True), + DS3LocationData("RS: Butcher Knife - NPC drop beneath road", "Butcher Knife", + hostile_npc=True), # Madwoman + DS3LocationData("RS: Titanite Shard - water by Halfway Fortress", "Titanite Shard"), + DS3LocationData("RS: Titanite Shard - woods, left of path from Halfway Fortress", + "Titanite Shard"), + DS3LocationData("RS: Green Blossom - by deep water", "Green Blossom x4"), + DS3LocationData("RS: Estus Shard - left of fire behind stronghold left room", + "Estus Shard"), + DS3LocationData("RS: Ring of Sacrifice - stronghold, drop from right room balcony", + "Ring of Sacrifice", hidden=True), # hidden fall + DS3LocationData("RS: Soul of an Unknown Traveler - drop along wall from Halfway Fortress", + "Soul of an Unknown Traveler"), + DS3LocationData("RS: Fallen Knight Helm - water's edge by Farron Keep", + "Fallen Knight Helm"), + DS3LocationData("RS: Fallen Knight Armor - water's edge by Farron Keep", + "Fallen Knight Armor"), + DS3LocationData("RS: Fallen Knight Gauntlets - water's edge by Farron Keep", + "Fallen Knight Gauntlets"), + DS3LocationData("RS: Fallen Knight Trousers - water's edge by Farron Keep", + "Fallen Knight Trousers"), + DS3LocationData("RS: Heretic's Staff - stronghold left room", "Heretic's Staff"), + DS3LocationData("RS: Large Soul of an Unknown Traveler - left of stairs to Farron Keep", + "Large Soul of an Unknown Traveler"), + DS3LocationData("RS: Conjurator Hood - deep water", "Conjurator Hood"), + DS3LocationData("RS: Conjurator Robe - deep water", "Conjurator Robe"), + DS3LocationData("RS: Conjurator Manchettes - deep water", "Conjurator Manchettes"), + DS3LocationData("RS: Conjurator Boots - deep water", "Conjurator Boots"), + DS3LocationData("RS: Soul of an Unknown Traveler - right of door to stronghold left", + "Soul of an Unknown Traveler"), + DS3LocationData("RS: Green Blossom - water beneath stronghold", "Green Blossom x2"), + DS3LocationData("RS: Great Swamp Pyromancy Tome - deep water", + "Great Swamp Pyromancy Tome"), + DS3LocationData("RS: Homeward Bone - balcony by Farron Keep", "Homeward Bone x2"), + DS3LocationData("RS: Titanite Shard - woods, surrounded by enemies", "Titanite Shard"), + DS3LocationData("RS: Twin Dragon Greatshield - woods by Crucifixion Woods bonfire", + "Twin Dragon Greatshield"), + DS3LocationData("RS: Sorcerer Hood - water beneath stronghold", "Sorcerer Hood", + hidden=True), # Hidden fall + DS3LocationData("RS: Sorcerer Robe - water beneath stronghold", "Sorcerer Robe", + hidden=True), # Hidden fall + DS3LocationData("RS: Sorcerer Gloves - water beneath stronghold", "Sorcerer Gloves", + hidden=True), # Hidden fall + DS3LocationData("RS: Sorcerer Trousers - water beneath stronghold", "Sorcerer Trousers", + hidden=True), # Hidden fall + DS3LocationData("RS: Sage Ring - water beneath stronghold", "Sage Ring", + hidden=True), # Hidden fall + DS3LocationData("RS: Grass Crest Shield - water by Crucifixion Woods bonfire", + "Grass Crest Shield"), + DS3LocationData("RS: Ember - right of fire behind stronghold left room", "Ember"), + DS3LocationData("RS: Blue Bug Pellet - broken stairs by Orbeck", "Blue Bug Pellet x2"), + DS3LocationData("RS: Soul of an Unknown Traveler - road, by wagon", + "Soul of an Unknown Traveler"), + DS3LocationData("RS: Shriving Stone - road, by start", "Shriving Stone"), + DS3LocationData("RS: Titanite Shard - road, on bridge after you go under", + "Titanite Shard"), + DS3LocationData("RS: Brigand Twindaggers - beneath road", "Brigand Twindaggers"), + DS3LocationData("RS: Braille Divine Tome of Carim - drop from bridge to Halfway Fortress", + "Braille Divine Tome of Carim", hidden=True), # Hidden fall + DS3LocationData("RS: Ember - right of Halfway Fortress entrance", "Ember"), + DS3LocationData("RS: Sellsword Twinblades - keep perimeter", "Sellsword Twinblades"), + DS3LocationData("RS: Golden Falcon Shield - path from stronghold right room to Farron Keep", + "Golden Falcon Shield"), + DS3LocationData("RS: Brigand Axe - beneath road", "Brigand Axe"), + DS3LocationData("RS: Brigand Hood - beneath road", "Brigand Hood"), + DS3LocationData("RS: Brigand Armor - beneath road", "Brigand Armor"), + DS3LocationData("RS: Brigand Gauntlets - beneath road", "Brigand Gauntlets"), + DS3LocationData("RS: Brigand Trousers - beneath road", "Brigand Trousers"), + DS3LocationData("RS: Morne's Ring - drop from bridge to Halfway Fortress", "Morne's Ring", + hidden=True), # Hidden fall + DS3LocationData("RS: Sellsword Helm - keep perimeter balcony", "Sellsword Helm"), + DS3LocationData("RS: Sellsword Armor - keep perimeter balcony", "Sellsword Armor"), + DS3LocationData("RS: Sellsword Gauntlet - keep perimeter balcony", "Sellsword Gauntlet"), + DS3LocationData("RS: Sellsword Trousers - keep perimeter balcony", "Sellsword Trousers"), + DS3LocationData("RS: Farron Coal - keep perimeter", "Farron Coal"), + DS3LocationData("RS: Chloranthy Ring+2 - road, drop across from carriage", + "Chloranthy Ring+2", hidden=True, ngp=True), # Hidden fall + DS3LocationData("RS: Lingering Dragoncrest Ring+1 - water", "Lingering Dragoncrest Ring+1", + ngp=True), + DS3LocationData("RS: Great Swamp Ring - miniboss drop, by Farron Keep", + "Great Swamp Ring", miniboss=True), # Giant Crab drop + DS3LocationData("RS: Blue Sentinels - Horace", "Blue Sentinels", + missable=True, npc=True), # Horace quest + DS3LocationData("RS: Crystal Gem - stronghold, lizard", "Crystal Gem"), + DS3LocationData("RS: Fading Soul - woods by Crucifixion Woods bonfire", "Fading Soul", + static='03,0:53300210::'), + + # Orbeck shop, all missable because he'll disappear if you don't talk to him for too long or + # if you don't give him a scroll. + DS3LocationData("FS: Farron Dart - Orbeck", "Farron Dart", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Soul Arrow - Orbeck", "Soul Arrow", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Great Soul Arrow - Orbeck", "Great Soul Arrow", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Heavy Soul Arrow - Orbeck", "Heavy Soul Arrow", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Great Heavy Soul Arrow - Orbeck", "Great Heavy Soul Arrow", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Magic Weapon - Orbeck", "Magic Weapon", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Magic Shield - Orbeck", "Magic Shield", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Spook - Orbeck", "Spook", missable=True, npc=True, shop=True), + DS3LocationData("FS: Aural Decoy - Orbeck", "Aural Decoy", missable=True, npc=True, + shop=True), + DS3LocationData("FS: Soul Greatsword - Orbeck", "Soul Greatsword", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True), + DS3LocationData("FS: Farron Flashsword - Orbeck", "Farron Flashsword", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Pestilent Mist - Orbeck for any scroll", "Pestilent Mist", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Great Farron Dart - Orbeck for Sage's Scroll", "Great Farron Dart", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Farron Hail - Orbeck for Sage's Scroll", "Farron Hail", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Homing Soulmass - Orbeck for Logan's Scroll", "Homing Soulmass", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Soul Spear - Orbeck for Logan's Scroll", "Soul Spear", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Homing Crystal Soulmass - Orbeck for Crystal Scroll", + "Homing Crystal Soulmass", missable=True, npc=True, shop=True), + DS3LocationData("FS: Crystal Soul Spear - Orbeck for Crystal Scroll", "Crystal Soul Spear", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Crystal Magic Weapon - Orbeck for Crystal Scroll", + "Crystal Magic Weapon", missable=True, npc=True, shop=True), + DS3LocationData("FS: Cast Light - Orbeck for Golden Scroll", "Cast Light", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Twisted Wall of Light - Orbeck for Golden Scroll", + "Twisted Wall of Light", missable=True, npc=True, shop=True), + DS3LocationData("FS: Hidden Weapon - Orbeck for Golden Scroll", "Hidden Weapon", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Hidden Body - Orbeck for Golden Scroll", "Hidden Body", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Repair - Orbeck for Golden Scroll", "Repair", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Clandestine Coat - shop with Orbeck's Ashes", "Clandestine Coat", + missable=True, npc=True, + shop=True), # Shrine Handmaid with Orbeck's Ashes + reload + DS3LocationData("FS: Young Dragon Ring - Orbeck for one scroll and buying three spells", + "Young Dragon Ring", missable=True, npc=True), + DS3LocationData("FS: Slumbering Dragoncrest Ring - Orbeck for buying four specific spells", + "Slumbering Dragoncrest Ring", missable=True, npc=True), + DS3LocationData("RS -> FK", None), + + # Shrine Handmaid after killing exiles + DS3LocationData("FS: Exile Mask - shop after killing NPCs in RS", "Exile Mask", + hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Exile Armor - shop after killing NPCs in RS", "Exile Armor", + hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Exile Gauntlets - shop after killing NPCs in RS", "Exile Gauntlets", + hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Exile Leggings - shop after killing NPCs in RS", "Exile Leggings", + hostile_npc=True, shop=True, hidden=True), + + # Shrine Handmaid after killing Crystal Sage + DS3LocationData("FS: Sage's Big Hat - shop after killing RS boss", "Sage's Big Hat", + boss=True, shop=True), + + # Yuria of Londor for Orbeck's Ashes + DS3LocationData("FS: Morion Blade - Yuria for Orbeck's Ashes", "Morion Blade", + missable=True, npc=True), ], "Cathedral of the Deep": [ - DS3LocationData("CD: Paladin's Ashes", "Paladin's Ashes", DS3LocationCategory.MISC), - DS3LocationData("CD: Spider Shield", "Spider Shield", DS3LocationCategory.SHIELD), - DS3LocationData("CD: Crest Shield", "Crest Shield", DS3LocationCategory.SHIELD), - DS3LocationData("CD: Notched Whip", "Notched Whip", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Astora Greatsword", "Astora Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Executioner's Greatsword", "Executioner's Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Curse Ward Greatshield", "Curse Ward Greatshield", DS3LocationCategory.SHIELD), - DS3LocationData("CD: Saint-tree Bellvine", "Saint-tree Bellvine", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Poisonbite Ring", "Poisonbite Ring", DS3LocationCategory.RING), - DS3LocationData("CD: Lloyd's Sword Ring", "Lloyd's Sword Ring", DS3LocationCategory.RING), - DS3LocationData("CD: Seek Guidance", "Seek Guidance", DS3LocationCategory.SPELL), - DS3LocationData("CD: Aldrich's Sapphire", "Aldrich's Sapphire", DS3LocationCategory.RING), - DS3LocationData("CD: Deep Braille Divine Tome", "Deep Braille Divine Tome", DS3LocationCategory.MISC), - DS3LocationData("CD: Saint Bident", "Saint Bident", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Maiden Hood", "Maiden Hood", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Maiden Robe", "Maiden Robe", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Maiden Gloves", "Maiden Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Maiden Skirt", "Maiden Skirt", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Drang Armor", "Drang Armor", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Drang Gauntlets", "Drang Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Drang Shoes", "Drang Shoes", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Drang Hammers", "Drang Hammers", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Deep Ring", "Deep Ring", DS3LocationCategory.RING), - DS3LocationData("CD: Archdeacon White Crown", "Archdeacon White Crown", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Archdeacon Holy Garb", "Archdeacon Holy Garb", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Archdeacon Skirt", "Archdeacon Skirt", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Arbalest", "Arbalest", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Small Doll", "Small Doll", DS3LocationCategory.KEY), - DS3LocationData("CD: Soul of the Deacons of the Deep", "Soul of the Deacons of the Deep", DS3LocationCategory.BOSS), - DS3LocationData("CD: Rosaria's Fingers", "Rosaria's Fingers", DS3LocationCategory.MISC) + DS3LocationData("CD: Herald Helm - path, by fire", "Herald Helm"), + DS3LocationData("CD: Herald Armor - path, by fire", "Herald Armor"), + DS3LocationData("CD: Herald Gloves - path, by fire", "Herald Gloves"), + DS3LocationData("CD: Herald Trousers - path, by fire", "Herald Trousers"), + DS3LocationData("CD: Twinkling Titanite - path, lizard #1", "Twinkling Titanite", + lizard=True), + DS3LocationData("CD: Twinkling Titanite - path, lizard #2", "Twinkling Titanite", + lizard=True), + DS3LocationData("CD: Small Doll - boss drop", "Small Doll", prominent=True, + progression=True, boss=True), + DS3LocationData("CD: Soul of the Deacons of the Deep", "Soul of the Deacons of the Deep", + boss=True), + DS3LocationData("CD: Black Eye Orb - Rosaria from Leonhard's quest", "Black Eye Orb", + missable=True, npc=True), + DS3LocationData("CD: Winged Spear - kill Patches", "Winged Spear", drop=True, + missable=True), # Patches (kill) + DS3LocationData("CD: Spider Shield - NPC drop on path", "Spider Shield", + hostile_npc=True), # Brigand + DS3LocationData("CD: Notched Whip - Cleansing Chapel", "Notched Whip"), + DS3LocationData("CD: Titanite Shard - Cleansing Chapel windowsill, by miniboss", + "Titanite Shard"), + DS3LocationData("CD: Astora Greatsword - graveyard, left of entrance", "Astora Greatsword"), + DS3LocationData("CD: Executioner's Greatsword - graveyard, far end", + "Executioner's Greatsword"), + DS3LocationData("CD: Undead Bone Shard - gravestone by white tree", "Undead Bone Shard"), + DS3LocationData("CD: Curse Ward Greatshield - by ladder from white tree to moat", + "Curse Ward Greatshield"), + DS3LocationData("CD: Titanite Shard - moat, far end", "Titanite Shard"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - lower roofs, semicircle balcony", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CD: Paladin's Ashes - path, guarded by lower NPC", "Paladin's Ashes", + progression=True), + DS3LocationData("CD: Arbalest - upper roofs, end of furthest buttress", "Arbalest"), + DS3LocationData("CD: Ember - by back door", "Ember"), + DS3LocationData("CD: Ember - side chapel upstairs, up ladder", "Ember"), + DS3LocationData("CD: Poisonbite Ring - moat, hall past miniboss", "Poisonbite Ring"), + DS3LocationData("CD: Drang Armor - main hall, east", "Drang Armor"), + DS3LocationData("CD: Ember - edge of platform before boss", "Ember"), + DS3LocationData("CD: Duel Charm - next to Patches in onion armor", "Duel Charm x3"), + DS3LocationData("CD: Seek Guidance - side chapel upstairs", "Seek Guidance"), + DS3LocationData("CD: Estus Shard - monument outside Cleansing Chapel", "Estus Shard"), + DS3LocationData("CD: Maiden Hood - main hall south", "Maiden Hood"), + DS3LocationData("CD: Maiden Robe - main hall south", "Maiden Robe"), + DS3LocationData("CD: Maiden Gloves - main hall south", "Maiden Gloves"), + DS3LocationData("CD: Maiden Skirt - main hall south", "Maiden Skirt"), + DS3LocationData("CD: Pale Tongue - upper roofs, outdoors far end", "Pale Tongue"), + DS3LocationData("CD: Fading Soul - graveyard, far end", "Fading Soul"), + DS3LocationData("CD: Blessed Gem - upper roofs, rafters", "Blessed Gem"), + DS3LocationData("CD: Red Bug Pellet - right of cathedral front doors", "Red Bug Pellet"), + DS3LocationData("CD: Soul of a Nameless Soldier - main hall south", + "Soul of a Nameless Soldier"), + DS3LocationData("CD: Duel Charm - by first elevator", "Duel Charm"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - main hall south, side path", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CD: Ember - side chapel, miniboss room", "Ember"), + DS3LocationData("CD: Repair Powder - by white tree", "Repair Powder x3"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - by white tree #1", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - by white tree #2", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CD: Undead Hunter Charm - lower roofs, up stairs between buttresses", + "Undead Hunter Charm x3"), + DS3LocationData("CD: Red Bug Pellet - lower roofs, up stairs between buttresses", + "Red Bug Pellet x3"), + DS3LocationData("CD: Titanite Shard - outside building by white tree", "Titanite Shard", + hidden=True), # Easily missable side path + DS3LocationData("CD: Titanite Shard - moat, up a slope", "Titanite Shard"), + DS3LocationData("CD: Rusted Coin - left of cathedral front doors, behind crates", + "Rusted Coin x2", hidden=True), + DS3LocationData("CD: Drang Hammers - main hall east", "Drang Hammers"), + DS3LocationData("CD: Drang Shoes - main hall east", "Drang Shoes"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - main hall east", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CD: Pale Tongue - main hall east", "Pale Tongue"), + DS3LocationData("CD: Drang Gauntlets - main hall east", "Drang Gauntlets"), + DS3LocationData("CD: Soul of a Nameless Soldier - lower roofs, side room", + "Soul of a Nameless Soldier"), + DS3LocationData("CD: Exploding Bolt - ledge above main hall south", "Exploding Bolt x6"), + DS3LocationData("CD: Lloyd's Sword Ring - ledge above main hall south", + "Lloyd's Sword Ring"), + DS3LocationData("CD: Soul of a Nameless Soldier - ledge above main hall south", + "Soul of a Nameless Soldier"), + DS3LocationData("CD: Homeward Bone - outside main hall south door", "Homeward Bone x2"), + DS3LocationData("CD: Deep Gem - down stairs by first elevator", "Deep Gem"), + DS3LocationData("CD: Titanite Shard - path, side path by Cathedral of the Deep bonfire", + "Titanite Shard"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - path, against outer wall", + "Large Soul of an Unknown Traveler"), + # Before the stairs leading down into the Deacons fight + DS3LocationData("CD: Ring of the Evil Eye+1 - by stairs to boss", "Ring of the Evil Eye+1", + ngp=True), + DS3LocationData("CD: Ring of Favor+2 - upper roofs, on buttress", "Ring of Favor+2", + hidden=True, ngp=True), # Hidden fall + DS3LocationData("CD: Crest Shield - path, drop down by Cathedral of the Deep bonfire", + "Crest Shield", hidden=True), # Hidden fall + DS3LocationData("CD: Young White Branch - by white tree #1", "Young White Branch"), + DS3LocationData("CD: Young White Branch - by white tree #2", "Young White Branch"), + DS3LocationData("CD: Saint-tree Bellvine - moat, by water", "Saint-tree Bellvine"), + DS3LocationData("CD: Saint Bident - outside main hall south door", "Saint Bident"), + # Archdeacon set is hidden because you have to return to a cleared area + DS3LocationData("CD: Archdeacon White Crown - boss room after killing boss", + "Archdeacon White Crown", boss=True, hidden=True), + DS3LocationData("CD: Archdeacon Holy Garb - boss room after killing boss", + "Archdeacon Holy Garb", boss=True, hidden=True), + DS3LocationData("CD: Archdeacon Skirt - boss room after killing boss", "Archdeacon Skirt", + boss=True, hidden=True), + # Heysel items may not be missable, but it's not clear what causes them to trigger + DS3LocationData("CD: Heysel Pick - Heysel Corpse-Grub in Rosaria's Bed Chamber", + "Heysel Pick", missable=True), + DS3LocationData("CD: Xanthous Crown - Heysel Corpse-Grub in Rosaria's Bed Chamber", + "Xanthous Crown", missable=True), + DS3LocationData("CD: Deep Ring - upper roofs, passive mob drop in first tower", "Deep Ring", + drop=True, hidden=True), + DS3LocationData("CD: Deep Braille Divine Tome - mimic by side chapel", + "Deep Braille Divine Tome", mimic=True), + DS3LocationData("CD: Red Sign Soapstone - passive mob drop by Rosaria's Bed Chamber", + "Red Sign Soapstone", drop=True, hidden=True), + DS3LocationData("CD: Aldrich's Sapphire - side chapel, miniboss drop", "Aldrich's Sapphire", + miniboss=True), # Deep Accursed Drop + DS3LocationData("CD: Titanite Scale - moat, miniboss drop", "Titanite Scale", + miniboss=True), # Ravenous Crystal Lizard drop + DS3LocationData("CD: Twinkling Titanite - moat, lizard #1", "Twinkling Titanite", + lizard=True), + DS3LocationData("CD: Twinkling Titanite - moat, lizard #2", "Twinkling Titanite", + lizard=True), + DS3LocationData("CD: Rosaria's Fingers - Rosaria", "Rosaria's Fingers", + hidden=True), # Hidden fall + DS3LocationData("CD -> PW1", None), + + # Longfinger Kirk drops + DS3LocationData("CD: Barbed Straight Sword - Kirk drop", "Barbed Straight Sword", + missable=True, hostile_npc=True), + DS3LocationData("CD: Spiked Shield - Kirk drop", "Spiked Shield", missable=True, + hostile_npc=True), + # In Rosaria's Bed Chamber + DS3LocationData("CD: Helm of Thorns - Rosaria's Bed Chamber after killing Kirk", + "Helm of Thorns", missable=True, hostile_npc=True), + DS3LocationData("CD: Armor of Thorns - Rosaria's Bed Chamber after killing Kirk", + "Armor of Thorns", missable=True, hostile_npc=True), + DS3LocationData("CD: Gauntlets of Thorns - Rosaria's Bed Chamber after killing Kirk", + "Gauntlets of Thorns", missable=True, hostile_npc=True), + DS3LocationData("CD: Leggings of Thorns - Rosaria's Bed Chamber after killing Kirk", + "Leggings of Thorns", missable=True, hostile_npc=True), + + # Unbreakable Patches + DS3LocationData("CD: Rusted Coin - don't forgive Patches", "Rusted Coin", + missable=True, npc=True), + DS3LocationData("FS: Rusted Gold Coin - don't forgive Patches", "Rusted Gold Coin", + static='99,0:50006201::', missable=True, + npc=True), # Don't forgive Patches + DS3LocationData("CD: Shotel - Patches", "Shotel", missable=True, npc=True, shop=True), + DS3LocationData("CD: Ember - Patches", "Ember", missable=True, npc=True, shop=True), + DS3LocationData("CD: Horsehoof Ring - Patches", "Horsehoof Ring", missable=True, + npc=True, drop=True, shop=True), # (kill or buy) ], "Farron Keep": [ - DS3LocationData("FK: Ragged Mask", "Ragged Mask", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Iron Flesh", "Iron Flesh", DS3LocationCategory.SPELL), - DS3LocationData("FK: Golden Scroll", "Golden Scroll", DS3LocationCategory.MISC), - DS3LocationData("FK: Antiquated Dress", "Antiquated Dress", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Antiquated Gloves", "Antiquated Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Antiquated Skirt", "Antiquated Skirt", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Nameless Knight Helm", "Nameless Knight Helm", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Nameless Knight Armor", "Nameless Knight Armor", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Nameless Knight Gauntlets", "Nameless Knight Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Nameless Knight Leggings", "Nameless Knight Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Sunlight Talisman", "Sunlight Talisman", DS3LocationCategory.WEAPON), - DS3LocationData("FK: Wolf's Blood Swordgrass", "Wolf's Blood Swordgrass", DS3LocationCategory.MISC), - DS3LocationData("FK: Greatsword", "Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("FK: Sage's Coal", "Sage's Coal", DS3LocationCategory.MISC), - DS3LocationData("FK: Stone Parma", "Stone Parma", DS3LocationCategory.SHIELD), - DS3LocationData("FK: Sage's Scroll", "Sage's Scroll", DS3LocationCategory.MISC), - DS3LocationData("FK: Crown of Dusk", "Crown of Dusk", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Lingering Dragoncrest Ring", "Lingering Dragoncrest Ring", DS3LocationCategory.RING), - DS3LocationData("FK: Pharis's Hat", "Pharis's Hat", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Black Bow of Pharis", "Black Bow of Pharis", DS3LocationCategory.WEAPON), - DS3LocationData("FK: Dreamchaser's Ashes", "Dreamchaser's Ashes", DS3LocationCategory.MISC), - DS3LocationData("FK: Great Axe", "Great Axe", DS3LocationCategory.WEAPON), - DS3LocationData("FK: Dragon Crest Shield", "Dragon Crest Shield", DS3LocationCategory.SHIELD), - DS3LocationData("FK: Lightning Spear", "Lightning Spear", DS3LocationCategory.SPELL), - DS3LocationData("FK: Atonement", "Atonement", DS3LocationCategory.SPELL), - DS3LocationData("FK: Great Magic Weapon", "Great Magic Weapon", DS3LocationCategory.SPELL), - DS3LocationData("FK: Cinders of a Lord - Abyss Watcher", "Cinders of a Lord - Abyss Watcher", DS3LocationCategory.KEY), - DS3LocationData("FK: Soul of the Blood of the Wolf", "Soul of the Blood of the Wolf", DS3LocationCategory.BOSS), - DS3LocationData("FK: Soul of a Stray Demon", "Soul of a Stray Demon", DS3LocationCategory.BOSS), - DS3LocationData("FK: Watchdogs of Farron", "Watchdogs of Farron", DS3LocationCategory.MISC), + DS3LocationData("FK: Lightning Spear - upper keep, far side of the wall", + "Lightning Spear"), + DS3LocationData("FK: Dragon Crest Shield - upper keep, far side of the wall", + "Dragon Crest Shield"), + DS3LocationData("FK: Soul of the Blood of the Wolf", "Soul of the Blood of the Wolf", + boss=True), + DS3LocationData("FK: Cinders of a Lord - Abyss Watcher", + "Cinders of a Lord - Abyss Watcher", + static="03,0:50002100::", prominent=True, progression=True, + boss=True), + DS3LocationData("FK: Manikin Claws - Londor Pale Shade drop", "Manikin Claws", + missable=True, hostile_npc=True, + npc=True), # Londor Pale Shade (if Yoel/Yuria hostile) + DS3LocationData("FK: Purple Moss Clump - keep ruins, ritual island", + "Purple Moss Clump x2"), + DS3LocationData("FK: Purple Moss Clump - ramp directly in front of Farron Keep bonfire", + "Purple Moss Clump x4"), + DS3LocationData("FK: Greatsword - ramp by keep ruins ritual island", "Greatsword"), + DS3LocationData("FK: Hollow Gem - perimeter, drop down into swamp", "Hollow Gem", + hidden=True), + DS3LocationData("FK: Purple Moss Clump - Farron Keep bonfire, around right corner", + "Purple Moss Clump x3"), + DS3LocationData("FK: Undead Bone Shard - pavilion by keep ruins bonfire island", + "Undead Bone Shard"), + DS3LocationData("FK: Atonement - perimeter, drop down into swamp", "Atonement", + hidden=True), + DS3LocationData("FK: Titanite Shard - by ladder to keep proper", "Titanite Shard"), + DS3LocationData("FK: Iron Flesh - Farron Keep bonfire, right after exit", "Iron Flesh"), + DS3LocationData("FK: Stone Parma - near wall by left island", "Stone Parma"), + DS3LocationData("FK: Rotten Pine Resin - left island, behind fire", "Rotten Pine Resin x2"), + DS3LocationData("FK: Titanite Shard - between left island and keep ruins", "Titanite Shard"), + DS3LocationData("FK: Rusted Gold Coin - right island, behind wall", "Rusted Gold Coin", + hidden=True), + DS3LocationData("FK: Nameless Knight Helm - corner of keep and right island", + "Nameless Knight Helm"), + DS3LocationData("FK: Nameless Knight Armor - corner of keep and right island", + "Nameless Knight Armor"), + DS3LocationData("FK: Nameless Knight Gauntlets - corner of keep and right island", + "Nameless Knight Gauntlets"), + DS3LocationData("FK: Nameless Knight Leggings - corner of keep and right island", + "Nameless Knight Leggings"), + DS3LocationData("FK: Shriving Stone - perimeter, just past stone doors", "Shriving Stone"), + DS3LocationData("FK: Repair Powder - outside hidden cave", "Repair Powder x4", + hidden=True), + DS3LocationData("FK: Golden Scroll - hidden cave", "Golden Scroll", hidden=True), + DS3LocationData("FK: Sage's Scroll - near wall by keep ruins bonfire island", + "Sage's Scroll"), + DS3LocationData("FK: Dreamchaser's Ashes - keep proper, illusory wall", + "Dreamchaser's Ashes", progression=True, hidden=True), + DS3LocationData("FK: Titanite Shard - keep ruins bonfire island, under ramp", + "Titanite Shard"), + DS3LocationData("FK: Wolf's Blood Swordgrass - by ladder to keep proper", + "Wolf's Blood Swordgrass"), + DS3LocationData("FK: Great Magic Weapon - perimeter, by door to Road of Sacrifices", + "Great Magic Weapon"), + DS3LocationData("FK: Ember - perimeter, path to boss", "Ember"), + DS3LocationData("FK: Titanite Shard - swamp by right island", "Titanite Shard x2"), + DS3LocationData("FK: Titanite Shard - by left island stairs", "Titanite Shard"), + DS3LocationData("FK: Titanite Shard - by keep ruins ritual island stairs", "Titanite Shard"), + DS3LocationData("FK: Black Bug Pellet - perimeter, hill by boss door", + "Black Bug Pellet x3"), + DS3LocationData("FK: Rotten Pine Resin - outside pavilion by left island", + "Rotten Pine Resin x4"), + DS3LocationData("FK: Poison Gem - near wall by keep ruins bridge", "Poison Gem"), + DS3LocationData("FK: Ragged Mask - Farron Keep bonfire, around left corner", "Ragged Mask"), + DS3LocationData("FK: Estus Shard - between Farron Keep bonfire and left island", + "Estus Shard"), + DS3LocationData("FK: Homeward Bone - right island, behind fire", "Homeward Bone x2"), + DS3LocationData("FK: Titanite Shard - Farron Keep bonfire, left after exit", + "Titanite Shard"), + DS3LocationData("FK: Large Soul of a Nameless Soldier - corner of keep and right island", + "Large Soul of a Nameless Soldier", hidden=True), # Tricky corner to spot + DS3LocationData("FK: Prism Stone - by left island stairs", "Prism Stone x10"), + DS3LocationData("FK: Large Soul of a Nameless Soldier - near wall by right island", + "Large Soul of a Nameless Soldier"), + DS3LocationData("FK: Sage's Coal - pavilion by left island", "Sage's Coal"), + DS3LocationData("FK: Gold Pine Bundle - by white tree", "Gold Pine Bundle x6"), + DS3LocationData("FK: Ember - by white tree", "Ember"), + DS3LocationData("FK: Soul of a Nameless Soldier - by white tree", "Soul of a Nameless Soldier"), + DS3LocationData("FK: Large Soul of an Unknown Traveler - by white tree", + "Large Soul of an Unknown Traveler"), + DS3LocationData("FK: Greataxe - upper keep, by miniboss", "Greataxe"), + DS3LocationData("FK: Ember - upper keep, by miniboss #1", "Ember"), + DS3LocationData("FK: Ember - upper keep, by miniboss #2", "Ember"), + DS3LocationData("FK: Dark Stoneplate Ring+2 - keep ruins ritual island, behind wall", + "Dark Stoneplate Ring+2", ngp=True, hidden=True), + DS3LocationData("FK: Magic Stoneplate Ring+1 - between right island and wall", + "Magic Stoneplate Ring+1", ngp=True), + DS3LocationData("FK: Wolf Ring+1 - keep ruins bonfire island, outside building", + "Wolf Ring+1", ngp=True), + DS3LocationData("FK: Antiquated Dress - hidden cave", "Antiquated Dress", hidden=True), + DS3LocationData("FK: Antiquated Gloves - hidden cave", "Antiquated Gloves", hidden=True), + DS3LocationData("FK: Antiquated Skirt - hidden cave", "Antiquated Skirt", hidden=True), + DS3LocationData("FK: Sunlight Talisman - estus soup island, by ladder to keep proper", + "Sunlight Talisman"), + DS3LocationData("FK: Young White Branch - by white tree #1", "Young White Branch"), + DS3LocationData("FK: Young White Branch - by white tree #2", "Young White Branch"), + DS3LocationData("FK: Crown of Dusk - by white tree", "Crown of Dusk"), + DS3LocationData("FK: Lingering Dragoncrest Ring - by white tree, miniboss drop", + "Lingering Dragoncrest Ring", miniboss=True), # Great Crab drop + DS3LocationData("FK: Pharis's Hat - miniboss drop, by keep ruins near wall", + "Pharis's Hat", miniboss=True), # Elder Ghru drop + DS3LocationData("FK: Black Bow of Pharis - miniboss drop, by keep ruins near wall", + "Black Bow of Pharis", miniboss=True), # Elder Ghru drop + DS3LocationData("FK: Titanite Scale - perimeter, miniboss drop", "Titanite Scale x2", + miniboss=True), # Ravenous Crystal Lizard drop + DS3LocationData("FK: Large Titanite Shard - upper keep, lizard in open", + "Large Titanite Shard", lizard=True), + DS3LocationData("FK: Large Titanite Shard - upper keep, lizard by wyvern", + "Large Titanite Shard", lizard=True), + DS3LocationData("FK: Heavy Gem - upper keep, lizard on stairs", "Heavy Gem", lizard=True), + DS3LocationData("FK: Twinkling Titanite - keep proper, lizard", "Twinkling Titanite", + lizard=True), + DS3LocationData("FK: Soul of a Stray Demon - upper keep, miniboss drop", + "Soul of a Stray Demon", miniboss=True), + DS3LocationData("FK: Watchdogs of Farron - Old Wolf", "Watchdogs of Farron"), + DS3LocationData("FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + "Hawkwood's Shield", missable=True, + npc=True), # Hawkwood (quest, after Greatwood, Sage, Watchers, and Deacons) + DS3LocationData("US: Hawk Ring - Giant Archer", "Hawk Ring", drop=True, + npc=True), # Giant archer (kill or quest), here because you need to + # collect all seven White Branch locations to get it peacefully + + # Hawkwood after killing Abyss Watchers + DS3LocationData("FS: Farron Ring - Hawkwood", "Farron Ring", + missable=True, npc=True), + + # Shrine Handmaid after killing Abyss Watchers + DS3LocationData("FS: Undead Legion Helm - shop after killing FK boss", "Undead Legion Helm", + boss=True, shop=True), + DS3LocationData("FS: Undead Legion Armor - shop after killing FK boss", + "Undead Legion Armor", boss=True, shop=True), + DS3LocationData("FS: Undead Legion Gauntlet - shop after killing FK boss", + "Undead Legion Gauntlet", boss=True, shop=True), + DS3LocationData("FS: Undead Legion Leggings - shop after killing FK boss", + "Undead Legion Leggings", boss=True, shop=True), + + # Appears after killing Havel Knight in Archdragon Peak + DS3LocationData("FK: Havel's Helm - upper keep, after killing AP belfry roof NPC", + "Havel's Helm", hidden=True, hostile_npc=True), + DS3LocationData("FK: Havel's Armor - upper keep, after killing AP belfry roof NPC", + "Havel's Armor", hidden=True, hostile_npc=True), + DS3LocationData("FK: Havel's Gauntlets - upper keep, after killing AP belfry roof NPC", + "Havel's Gauntlets", hidden=True, hostile_npc=True), + DS3LocationData("FK: Havel's Leggings - upper keep, after killing AP belfry roof NPC", + "Havel's Leggings", hidden=True, hostile_npc=True), ], "Catacombs of Carthus": [ - DS3LocationData("CC: Carthus Pyromancy Tome", "Carthus Pyromancy Tome", DS3LocationCategory.MISC), - DS3LocationData("CC: Carthus Milkring", "Carthus Milkring", DS3LocationCategory.RING), - DS3LocationData("CC: Grave Warden's Ashes", "Grave Warden's Ashes", DS3LocationCategory.MISC), - DS3LocationData("CC: Carthus Bloodring", "Carthus Bloodring", DS3LocationCategory.RING), - DS3LocationData("CC: Grave Warden Pyromancy Tome", "Grave Warden Pyromancy Tome", DS3LocationCategory.MISC), - DS3LocationData("CC: Old Sage's Blindfold", "Old Sage's Blindfold", DS3LocationCategory.ARMOR), - DS3LocationData("CC: Witch's Ring", "Witch's Ring", DS3LocationCategory.RING), - DS3LocationData("CC: Black Blade", "Black Blade", DS3LocationCategory.WEAPON), - DS3LocationData("CC: Soul of High Lord Wolnir", "Soul of High Lord Wolnir", DS3LocationCategory.BOSS), - DS3LocationData("CC: Soul of a Demon", "Soul of a Demon", DS3LocationCategory.BOSS), + DS3LocationData("CC: Soul of High Lord Wolnir", "Soul of High Lord Wolnir", + prominent=True, boss=True), + DS3LocationData("CC: Carthus Rouge - atrium upper, left after entrance", + "Carthus Rouge x2"), + DS3LocationData("CC: Sharp Gem - atrium lower, right before exit", "Sharp Gem"), + DS3LocationData("CC: Soul of a Nameless Soldier - atrium lower, down hall", + "Soul of a Nameless Soldier"), + DS3LocationData("CC: Titanite Shard - atrium lower, corner by stairs", "Titanite Shard x2"), + DS3LocationData("CC: Bloodred Moss Clump - atrium lower, down more stairs", + "Bloodred Moss Clump x3"), + DS3LocationData("CC: Carthus Milkring - crypt upper, among pots", "Carthus Milkring"), + DS3LocationData("CC: Ember - atrium, on long stairway", "Ember"), + DS3LocationData("CC: Carthus Rouge - crypt across, corner", "Carthus Rouge x3"), + DS3LocationData("CC: Ember - crypt upper, end of hall past hole", "Ember"), + DS3LocationData("CC: Carthus Bloodring - crypt lower, end of side hall", "Carthus Bloodring"), + DS3LocationData("CC: Titanite Shard - crypt lower, left of entrance", "Titanite Shard x2"), + DS3LocationData("CC: Titanite Shard - crypt lower, start of side hall", "Titanite Shard x2"), + DS3LocationData("CC: Ember - crypt lower, shortcut to cavern", "Ember"), + DS3LocationData("CC: Carthus Pyromancy Tome - atrium lower, jump from bridge", + "Carthus Pyromancy Tome", + hidden=True), # Behind illusory wall or hidden drop + DS3LocationData("CC: Large Titanite Shard - crypt upper, skeleton ball hall", + "Large Titanite Shard"), + DS3LocationData("CC: Large Titanite Shard - crypt across, middle hall", + "Large Titanite Shard"), + DS3LocationData("CC: Yellow Bug Pellet - cavern, on overlook", "Yellow Bug Pellet x3"), + DS3LocationData("CC: Large Soul of a Nameless Soldier - cavern, before bridge", + "Large Soul of a Nameless Soldier"), + DS3LocationData("CC: Black Bug Pellet - cavern, before bridge", "Black Bug Pellet x2"), + DS3LocationData("CC: Grave Warden's Ashes - crypt across, corner", "Grave Warden's Ashes", + progression=True), + DS3LocationData("CC: Large Titanite Shard - tomb lower", "Large Titanite Shard"), + DS3LocationData("CC: Large Soul of a Nameless Soldier - tomb lower", + "Large Soul of a Nameless Soldier"), + DS3LocationData("CC: Old Sage's Blindfold - tomb, hall before bonfire", + "Old Sage's Blindfold"), + DS3LocationData("CC: Witch's Ring - tomb, hall before bonfire", "Witch's Ring"), + DS3LocationData("CC: Soul of a Nameless Soldier - atrium upper, up more stairs", + "Soul of a Nameless Soldier"), + DS3LocationData("CC: Grave Warden Pyromancy Tome - boss arena", + "Grave Warden Pyromancy Tome"), + DS3LocationData("CC: Large Soul of an Unknown Traveler - crypt upper, hall middle", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CC: Ring of Steel Protection+2 - atrium upper, drop onto pillar", + "Ring of Steel Protection+2", ngp=True), + DS3LocationData("CC: Thunder Stoneplate Ring+1 - crypt upper, among pots", + "Thunder Stoneplate Ring+1", ngp=True), + DS3LocationData("CC: Undead Bone Shard - crypt upper, skeleton ball drop", + "Undead Bone Shard", hidden=True), # Skeleton Ball puzzle + DS3LocationData("CC: Dark Gem - crypt lower, skeleton ball drop", "Dark Gem", + hidden=True), # Skeleton Ball puzzle + DS3LocationData("CC: Black Blade - tomb, mimic", "Black Blade", mimic=True), + DS3LocationData("CC: Soul of a Demon - tomb, miniboss drop", "Soul of a Demon", + miniboss=True), + DS3LocationData("CC: Twinkling Titanite - atrium lower, lizard down more stairs", + "Twinkling Titanite", lizard=True), + DS3LocationData("CC: Fire Gem - cavern, lizard", "Fire Gem", lizard=True), + DS3LocationData("CC: Homeward Bone - Irithyll bridge", "Homeward Bone"), + DS3LocationData("CC: Pontiff's Right Eye - Irithyll bridge, miniboss drop", + "Pontiff's Right Eye", miniboss=True), # Sulyvahn's Beast drop + + # Shrine Handmaid after killing High Lord Wolnir + DS3LocationData("FS: Wolnir's Crown - shop after killing CC boss", "Wolnir's Crown", + boss=True, shop=True), ], "Smouldering Lake": [ - DS3LocationData("SL: Shield of Want", "Shield of Want", DS3LocationCategory.SHIELD), - DS3LocationData("SL: Speckled Stoneplate Ring", "Speckled Stoneplate Ring", DS3LocationCategory.RING), - DS3LocationData("SL: Dragonrider Bow", "Dragonrider Bow", DS3LocationCategory.WEAPON), - DS3LocationData("SL: Lightning Stake", "Lightning Stake", DS3LocationCategory.SPELL), - DS3LocationData("SL: Izalith Pyromancy Tome", "Izalith Pyromancy Tome", DS3LocationCategory.MISC), - DS3LocationData("SL: Black Knight Sword", "Black Knight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("SL: Quelana Pyromancy Tome", "Quelana Pyromancy Tome", DS3LocationCategory.MISC), - DS3LocationData("SL: Toxic Mist", "Toxic Mist", DS3LocationCategory.SPELL), - DS3LocationData("SL: White Hair Talisman", "White Hair Talisman", DS3LocationCategory.WEAPON), - DS3LocationData("SL: Izalith Staff", "Izalith Staff", DS3LocationCategory.WEAPON), - DS3LocationData("SL: Sacred Flame", "Sacred Flame", DS3LocationCategory.SPELL), - DS3LocationData("SL: Fume Ultra Greatsword", "Fume Ultra Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("SL: Black Iron Greatshield", "Black Iron Greatshield", DS3LocationCategory.SHIELD), - DS3LocationData("SL: Soul of the Old Demon King", "Soul of the Old Demon King", DS3LocationCategory.BOSS), - DS3LocationData("SL: Knight Slayer's Ring", "Knight Slayer's Ring", DS3LocationCategory.RING), + DS3LocationData("SL: Soul of the Old Demon King", "Soul of the Old Demon King", + prominent=True, boss=True), + DS3LocationData("SL: Fume Ultra Greatsword - ruins basement, NPC drop", + "Fume Ultra Greatsword", hostile_npc=True), # Knight Slayer Tsorig drop + DS3LocationData("SL: Black Iron Greatshield - ruins basement, NPC drop", + "Black Iron Greatshield", hostile_npc=True), # Knight Slayer Tsorig drop + DS3LocationData("SL: Large Titanite Shard - ledge by Demon Ruins bonfire", + "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - lake, by entrance", "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - lake, straight from entrance", + "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - lake, by tree #1", "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - lake, by miniboss", "Large Titanite Shard"), + DS3LocationData("SL: Yellow Bug Pellet - side lake", "Yellow Bug Pellet x2"), + DS3LocationData("SL: Large Titanite Shard - side lake #1", "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - side lake #2", "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - lake, by tree #2", "Large Titanite Shard"), + DS3LocationData("SL: Speckled Stoneplate Ring - lake, ballista breaks bricks", + "Speckled Stoneplate Ring", hidden=True), # Requires careful ballista shot + DS3LocationData("SL: Homeward Bone - path to ballista", "Homeward Bone x2"), + DS3LocationData("SL: Ember - ruins main upper, hall end by hole", "Ember"), + DS3LocationData("SL: Chaos Gem - lake, far end by mob", "Chaos Gem"), + DS3LocationData("SL: Ember - ruins main lower, path to antechamber", "Ember"), + DS3LocationData("SL: Izalith Pyromancy Tome - antechamber, room near bonfire", + "Izalith Pyromancy Tome"), + DS3LocationData("SL: Black Knight Sword - ruins main lower, illusory wall in far hall", + "Black Knight Sword", hidden=True), + DS3LocationData("SL: Ember - ruins main upper, just after entrance", "Ember"), + DS3LocationData("SL: Quelana Pyromancy Tome - ruins main lower, illusory wall in grey room", + "Quelana Pyromancy Tome", hidden=True), + DS3LocationData("SL: Izalith Staff - ruins basement, second illusory wall behind chest", + "Izalith Staff", hidden=True), + DS3LocationData("SL: White Hair Talisman - ruins main lower, in lava", + "White Hair Talisman", + missable=True), # This may not even be possible to get without enough fire + # protection gear which the player may not have + DS3LocationData("SL: Toxic Mist - ruins main lower, in lava", "Toxic Mist", + missable=True), # This is _probably_ reachable with normal gear, but it + # still sucks and will probably force a death. + DS3LocationData("SL: Undead Bone Shard - ruins main lower, left after stairs", + "Undead Bone Shard"), + DS3LocationData("SL: Titanite Scale - ruins basement, path to lava", "Titanite Scale"), + DS3LocationData("SL: Shield of Want - lake, by miniboss", "Shield of Want"), + DS3LocationData("SL: Soul of a Crestfallen Knight - ruins basement, above lava", + "Soul of a Crestfallen Knight"), + + # Lava items are missable because they require a complex set of armor, rings, spells, and + # undead bone shards to reliably access without dying. + DS3LocationData("SL: Ember - ruins basement, in lava", "Ember", missable=True), # In lava + DS3LocationData("SL: Sacred Flame - ruins basement, in lava", "Sacred Flame", + missable=True), # In lava + + DS3LocationData("SL: Dragonrider Bow - by ladder from ruins basement to ballista", + "Dragonrider Bow", hidden=True), # Hidden fall + DS3LocationData("SL: Estus Shard - antechamber, illusory wall", "Estus Shard", + hidden=True), + DS3LocationData("SL: Bloodbite Ring+1 - behind ballista", "Bloodbite Ring+1", ngp=True), + DS3LocationData("SL: Flame Stoneplate Ring+2 - ruins main lower, illusory wall in far hall", + "Flame Stoneplate Ring+2", ngp=True, hidden=True), + DS3LocationData("SL: Large Titanite Shard - ruins basement, illusory wall in upper hall", + "Large Titanite Shard x3", hidden=True), + DS3LocationData("SL: Undead Bone Shard - lake, miniboss drop", "Undead Bone Shard", + miniboss=True), # Sand Worm drop + DS3LocationData("SL: Lightning Stake - lake, miniboss drop", "Lightning Stake", + miniboss=True), # Sand Worm drop + DS3LocationData("SL: Twinkling Titanite - path to side lake, lizard", "Twinkling Titanite", + lizard=True), + DS3LocationData("SL: Titanite Chunk - path to side lake, lizard", "Titanite Chunk", + lizard=True), + DS3LocationData("SL: Chaos Gem - antechamber, lizard at end of long hall", "Chaos Gem", + lizard=True), + DS3LocationData("SL: Knight Slayer's Ring - ruins basement, NPC drop", + "Knight Slayer's Ring", hostile_npc=True), # Knight Slayer Tsorig drop + + # Horace the Hushed + # These are listed here even though you can kill Horace in the Road of Sacrifices because + # the player may want to complete his and Anri's quest first. + DS3LocationData("SL: Llewellyn Shield - Horace drop", "Llewellyn Shield", npc=True, + hostile_npc=True), + DS3LocationData("FS: Executioner Helm - shop after killing Horace", "Executioner Helm", + npc=True, hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Executioner Armor - shop after killing Horace", "Executioner Armor", + npc=True, hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Executioner Gauntlets - shop after killing Horace", + "Executioner Gauntlets", hostile_npc=True, npc=True, shop=True, + hidden=True), + DS3LocationData("FS: Executioner Leggings - shop after killing Horace", + "Executioner Leggings", hostile_npc=True, npc=True, shop=True, + hidden=True), + + # Shrine Handmaid after killing Knight Slayer Tsorig + DS3LocationData("FS: Black Iron Helm - shop after killing Tsorig", "Black Iron Helm", + hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Black Iron Armor - shop after killing Tsorig", "Black Iron Armor", + hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Black Iron Gauntlets - shop after killing Tsorig", + "Black Iron Gauntlets", hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Black Iron Leggings - shop after killing Tsorig", + "Black Iron Leggings", hostile_npc=True, shop=True, hidden=True), + + # Near Cornyx's cage after killing Old Demon King with Cuculus + DS3LocationData("US: Spotted Whip - by Cornyx's cage after Cuculus quest", "Spotted Whip", + missable=True, boss=True, npc=True), + DS3LocationData("US: Cornyx's Garb - by Cornyx's cage after Cuculus quest", + "Cornyx's Garb", static='02,0:53100100::', missable=True, boss=True, + npc=True), + DS3LocationData("US: Cornyx's Wrap - by Cornyx's cage after Cuculus quest", "Cornyx's Wrap", + static='02,0:53100100::', missable=True, boss=True, npc=True), + DS3LocationData("US: Cornyx's Skirt - by Cornyx's cage after Cuculus quest", + "Cornyx's Skirt", static='02,0:53100100::', missable=True, boss=True, + npc=True), ], "Irithyll of the Boreal Valley": [ - DS3LocationData("IBV: Dorhys' Gnawing", "Dorhys' Gnawing", DS3LocationCategory.SPELL), - DS3LocationData("IBV: Witchtree Branch", "Witchtree Branch", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Magic Clutch Ring", "Magic Clutch Ring", DS3LocationCategory.RING), - DS3LocationData("IBV: Ring of the Sun's First Born", "Ring of the Sun's First Born", DS3LocationCategory.RING), - DS3LocationData("IBV: Roster of Knights", "Roster of Knights", DS3LocationCategory.MISC), - DS3LocationData("IBV: Pontiff's Right Eye", "Pontiff's Right Eye", DS3LocationCategory.RING), - DS3LocationData("IBV: Yorshka's Spear", "Yorshka's Spear", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Great Heal", "Great Heal", DS3LocationCategory.SPELL), - DS3LocationData("IBV: Smough's Great Hammer", "Smough's Great Hammer", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Leo Ring", "Leo Ring", DS3LocationCategory.RING), - DS3LocationData("IBV: Excrement-covered Ashes", "Excrement-covered Ashes", DS3LocationCategory.MISC), - DS3LocationData("IBV: Dark Stoneplate Ring", "Dark Stoneplate Ring", DS3LocationCategory.RING), - DS3LocationData("IBV: Easterner's Ashes", "Easterner's Ashes", DS3LocationCategory.MISC), - DS3LocationData("IBV: Painting Guardian's Curved Sword", "Painting Guardian's Curved Sword", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Painting Guardian Hood", "Painting Guardian Hood", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Painting Guardian Gown", "Painting Guardian Gown", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Painting Guardian Gloves", "Painting Guardian Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Painting Guardian Waistcloth", "Painting Guardian Waistcloth", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Dragonslayer Greatbow", "Dragonslayer Greatbow", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Reversal Ring", "Reversal Ring", DS3LocationCategory.RING), - DS3LocationData("IBV: Brass Helm", "Brass Helm", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Brass Armor", "Brass Armor", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Brass Gauntlets", "Brass Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Brass Leggings", "Brass Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Ring of Favor", "Ring of Favor", DS3LocationCategory.RING), - DS3LocationData("IBV: Golden Ritual Spear", "Golden Ritual Spear", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Soul of Pontiff Sulyvahn", "Soul of Pontiff Sulyvahn", DS3LocationCategory.BOSS), - DS3LocationData("IBV: Aldrich Faithful", "Aldrich Faithful", DS3LocationCategory.MISC), - DS3LocationData("IBV: Drang Twinspears", "Drang Twinspears", DS3LocationCategory.WEAPON), + DS3LocationData("IBV: Soul of Pontiff Sulyvahn", "Soul of Pontiff Sulyvahn", + prominent=True, boss=True), + DS3LocationData("IBV: Large Soul of a Nameless Soldier - central, by bonfire", + "Large Soul of a Nameless Soldier"), + DS3LocationData("IBV: Large Titanite Shard - ascent, down ladder in last building", + "Large Titanite Shard"), + DS3LocationData("IBV: Soul of a Weary Warrior - central, by first fountain", + "Soul of a Weary Warrior"), + DS3LocationData("IBV: Soul of a Weary Warrior - central, railing by first fountain", + "Soul of a Weary Warrior"), + DS3LocationData("IBV: Rime-blue Moss Clump - central, by bonfire", "Rime-blue Moss Clump"), + DS3LocationData("IBV: Witchtree Branch - by Dorhys", "Witchtree Branch", + hidden=True), # Behind illusory wall + DS3LocationData("IBV: Large Titanite Shard - central, side path after first fountain", + "Large Titanite Shard"), + DS3LocationData("IBV: Budding Green Blossom - central, by second fountain", + "Budding Green Blossom"), + DS3LocationData("IBV: Rime-blue Moss Clump - central, past second fountain", + "Rime-blue Moss Clump x2"), + DS3LocationData("IBV: Large Titanite Shard - central, balcony just before plaza", + "Large Titanite Shard"), + DS3LocationData("IBV: Large Titanite Shard - path to Dorhys", "Large Titanite Shard", + hidden=True), # Behind illusory wall + DS3LocationData("IBV: Ring of the Sun's First Born - fall from in front of cathedral", + "Ring of the Sun's First Born", + hidden=True), # Hidden fall + DS3LocationData("IBV: Large Soul of a Nameless Soldier - path to plaza", + "Large Soul of a Nameless Soldier"), + DS3LocationData("IBV: Large Titanite Shard - plaza, balcony overlooking ascent", + "Large Titanite Shard"), + DS3LocationData("IBV: Large Titanite Shard - plaza, by stairs to church", + "Large Titanite Shard"), + DS3LocationData("IBV: Soul of a Weary Warrior - plaza, side room lower", + "Soul of a Weary Warrior"), + DS3LocationData("IBV: Magic Clutch Ring - plaza, illusory wall", "Magic Clutch Ring", + hidden=True), # Behind illusory wall + DS3LocationData("IBV: Fading Soul - descent, cliff edge #1", "Fading Soul"), + DS3LocationData("IBV: Fading Soul - descent, cliff edge #2", "Fading Soul"), + DS3LocationData("IBV: Homeward Bone - descent, before gravestone", "Homeward Bone x3"), + DS3LocationData("IBV: Undead Bone Shard - descent, behind gravestone", "Undead Bone Shard", + hidden=True), # Hidden behind gravestone + DS3LocationData("IBV: Kukri - descent, side path", "Kukri x8"), + DS3LocationData("IBV: Rusted Gold Coin - descent, side path", "Rusted Gold Coin"), + DS3LocationData("IBV: Blue Bug Pellet - descent, dark room", "Blue Bug Pellet x2"), + DS3LocationData("IBV: Shriving Stone - descent, dark room rafters", "Shriving Stone"), + DS3LocationData("IBV: Blood Gem - descent, platform before lake", "Blood Gem"), + DS3LocationData("IBV: Green Blossom - lake, by stairs from descent", "Green Blossom x3"), + DS3LocationData("IBV: Ring of Sacrifice - lake, right of stairs from descent", + "Ring of Sacrifice"), + DS3LocationData("IBV: Great Heal - lake, dead Corpse-Grub", "Great Heal"), + DS3LocationData("IBV: Large Soul of a Nameless Soldier - lake island", + "Large Soul of a Nameless Soldier"), + DS3LocationData("IBV: Green Blossom - lake wall", "Green Blossom x3"), + DS3LocationData("IBV: Dung Pie - sewer #1", "Dung Pie x3"), + DS3LocationData("IBV: Dung Pie - sewer #2", "Dung Pie x3"), + # These don't actually guard any single item sales. Maybe we can inject one manually? + DS3LocationData("IBV: Excrement-covered Ashes - sewer, by stairs", + "Excrement-covered Ashes"), + DS3LocationData("IBV: Large Soul of a Nameless Soldier - ascent, after great hall", + "Large Soul of a Nameless Soldier"), + DS3LocationData("IBV: Soul of a Weary Warrior - ascent, by final staircase", + "Soul of a Weary Warrior"), + DS3LocationData("IBV: Large Titanite Shard - ascent, by elevator door", + "Large Titanite Shard"), + DS3LocationData("IBV: Blue Bug Pellet - ascent, in last building", "Blue Bug Pellet x2"), + DS3LocationData("IBV: Ember - shortcut from church to cathedral", "Ember"), + DS3LocationData("IBV: Green Blossom - lake, by Distant Manor", "Green Blossom"), + DS3LocationData("IBV: Lightning Gem - plaza center", "Lightning Gem"), + DS3LocationData("IBV: Large Soul of a Nameless Soldier - central, by second fountain", + "Large Soul of a Nameless Soldier"), + DS3LocationData("IBV: Soul of a Weary Warrior - plaza, side room upper", + "Soul of a Weary Warrior"), + DS3LocationData("IBV: Proof of a Concord Kept - Church of Yorshka altar", + "Proof of a Concord Kept"), + DS3LocationData("IBV: Rusted Gold Coin - Distant Manor, drop after stairs", + "Rusted Gold Coin"), + DS3LocationData("IBV: Chloranthy Ring+1 - plaza, behind altar", "Chloranthy Ring+1", + ngp=True), + DS3LocationData("IBV: Covetous Gold Serpent Ring+1 - descent, drop after dark room", + "Covetous Gold Serpent Ring+1", ngp=True, hidden=True), # Hidden fall + DS3LocationData("IBV: Wood Grain Ring+2 - ascent, right after great hall", "Wood Grain Ring+2", + ngp=True), + DS3LocationData("IBV: Divine Blessing - great hall, chest", "Divine Blessing"), + DS3LocationData("IBV: Smough's Great Hammer - great hall, chest", + "Smough's Great Hammer"), + DS3LocationData("IBV: Yorshka's Spear - descent, dark room rafters chest", "Yorshka's Spear"), + DS3LocationData("IBV: Leo Ring - great hall, chest", "Leo Ring"), + DS3LocationData("IBV: Dorhys' Gnawing - Dorhys drop", "Dorhys' Gnawing", + hidden=True), # Behind illusory wall + DS3LocationData("IBV: Divine Blessing - great hall, mob drop", + "Divine Blessing", drop=True, + hidden=True), # Guaranteed drop from normal-looking Silver Knight + DS3LocationData("IBV: Large Titanite Shard - great hall, main floor mob drop", + "Large Titanite Shard", drop=True, + hidden=True), # Guaranteed drop from normal-looking Silver Knight + DS3LocationData("IBV: Large Titanite Shard - great hall, upstairs mob drop #1", + "Large Titanite Shard x2", drop=True, + hidden=True), # Guaranteed drop from normal-looking Silver Knight + DS3LocationData("IBV: Large Titanite Shard - great hall, upstairs mob drop #2", + "Large Titanite Shard x2", drop=True, + hidden=True), # Guaranteed drop from normal-looking Silver Knight + DS3LocationData("IBV: Roster of Knights - descent, first landing", "Roster of Knights"), + DS3LocationData("IBV: Twinkling Titanite - descent, lizard behind illusory wall", + "Twinkling Titanite", lizard=True, hidden=True), # Behind illusory wall + DS3LocationData("IBV: Twinkling Titanite - central, lizard before plaza", + "Twinkling Titanite", lizard=True), + DS3LocationData("IBV: Large Titanite Shard - Distant Manor, under overhang", + "Large Titanite Shard"), + DS3LocationData("IBV: Siegbräu - Siegward", "Siegbräu", missable=True, npc=True), + DS3LocationData("IBV: Emit Force - Siegward", "Emit Force", missable=True, npc=True), + DS3LocationData("IBV -> ID", None), + + # After winning both Londor Pale Shade invasions + DS3LocationData("FS: Sneering Mask - Yoel's room, kill Londor Pale Shade twice", + "Sneering Mask", missable=True, hostile_npc=True), + DS3LocationData("FS: Pale Shade Robe - Yoel's room, kill Londor Pale Shade twice", + "Pale Shade Robe", missable=True, hostile_npc=True), + DS3LocationData("FS: Pale Shade Gloves - Yoel's room, kill Londor Pale Shade twice", + "Pale Shade Gloves", missable=True, hostile_npc=True), + DS3LocationData("FS: Pale Shade Trousers - Yoel's room, kill Londor Pale Shade twice", + "Pale Shade Trousers", missable=True, hostile_npc=True), + + # Anri of Astora + DS3LocationData("IBV: Ring of the Evil Eye - Anri", "Ring of the Evil Eye", missable=True, + npc=True), + + # Sirris quest after killing Creighton + DS3LocationData("FS: Mail Breaker - Sirris for killing Creighton", "Mail Breaker", + static='99,0:50006080::', missable=True, hostile_npc=True, + npc=True), + DS3LocationData("FS: Silvercat Ring - Sirris for killing Creighton", "Silvercat Ring", + missable=True, hostile_npc=True, npc=True), + DS3LocationData("IBV: Dragonslayer's Axe - Creighton drop", "Dragonslayer's Axe", + missable=True, hostile_npc=True, npc=True), + DS3LocationData("IBV: Creighton's Steel Mask - bridge after killing Creighton", + "Creighton's Steel Mask", missable=True, hostile_npc=True, npc=True), + DS3LocationData("IBV: Mirrah Chain Mail - bridge after killing Creighton", + "Mirrah Chain Mail", missable=True, hostile_npc=True, npc=True), + DS3LocationData("IBV: Mirrah Chain Gloves - bridge after killing Creighton", + "Mirrah Chain Gloves", missable=True, hostile_npc=True, npc=True), + DS3LocationData("IBV: Mirrah Chain Leggings - bridge after killing Creighton", + "Mirrah Chain Leggings", missable=True, hostile_npc=True, npc=True), ], "Irithyll Dungeon": [ - DS3LocationData("ID: Bellowing Dragoncrest Ring", "Bellowing Dragoncrest Ring", DS3LocationCategory.RING), - DS3LocationData("ID: Jailbreaker's Key", "Jailbreaker's Key", DS3LocationCategory.KEY), - DS3LocationData("ID: Prisoner Chief's Ashes", "Prisoner Chief's Ashes", DS3LocationCategory.KEY), - DS3LocationData("ID: Old Sorcerer Hat", "Old Sorcerer Hat", DS3LocationCategory.ARMOR), - DS3LocationData("ID: Old Sorcerer Coat", "Old Sorcerer Coat", DS3LocationCategory.ARMOR), - DS3LocationData("ID: Old Sorcerer Gauntlets", "Old Sorcerer Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("ID: Old Sorcerer Boots", "Old Sorcerer Boots", DS3LocationCategory.ARMOR), - DS3LocationData("ID: Great Magic Shield", "Great Magic Shield", DS3LocationCategory.SPELL), - DS3LocationData("ID: Dragon Torso Stone", "Dragon Torso Stone", DS3LocationCategory.MISC), - DS3LocationData("ID: Lightning Blade", "Lightning Blade", DS3LocationCategory.SPELL), - DS3LocationData("ID: Profaned Coal", "Profaned Coal", DS3LocationCategory.MISC), - DS3LocationData("ID: Xanthous Ashes", "Xanthous Ashes", DS3LocationCategory.MISC), - DS3LocationData("ID: Old Cell Key", "Old Cell Key", DS3LocationCategory.KEY), - DS3LocationData("ID: Pickaxe", "Pickaxe", DS3LocationCategory.WEAPON), - DS3LocationData("ID: Profaned Flame", "Profaned Flame", DS3LocationCategory.SPELL), - DS3LocationData("ID: Covetous Gold Serpent Ring", "Covetous Gold Serpent Ring", DS3LocationCategory.RING), - DS3LocationData("ID: Jailer's Key Ring", "Jailer's Key Ring", DS3LocationCategory.KEY), - DS3LocationData("ID: Dusk Crown Ring", "Dusk Crown Ring", DS3LocationCategory.RING), - DS3LocationData("ID: Dark Clutch Ring", "Dark Clutch Ring", DS3LocationCategory.RING), - DS3LocationData("ID: Karla's Ashes", "Karla's Ashes", DS3LocationCategory.NPC), - DS3LocationData("ID: Karla's Pointed Hat", "Karla's Pointed Hat", DS3LocationCategory.NPC), - DS3LocationData("ID: Karla's Coat", "Karla's Coat", DS3LocationCategory.NPC), - DS3LocationData("ID: Karla's Gloves", "Karla's Gloves", DS3LocationCategory.NPC), - DS3LocationData("ID: Karla's Trousers", "Karla's Trousers", DS3LocationCategory.NPC), + DS3LocationData("ID: Titanite Slab - Siegward", "Titanite Slab", missable=True, + npc=True), + DS3LocationData("ID: Murakumo - Alva drop", "Murakumo", missable=True, + hostile_npc=True), + DS3LocationData("ID: Large Titanite Shard - after bonfire, second cell on left", + "Large Titanite Shard"), + DS3LocationData("ID: Fading Soul - B1 near, main hall", "Fading Soul"), + DS3LocationData("ID: Large Soul of a Nameless Soldier - B2, hall by stairs", + "Large Soul of a Nameless Soldier"), + DS3LocationData("ID: Jailbreaker's Key - B1 far, cell after gate", "Jailbreaker's Key"), + DS3LocationData("ID: Pale Pine Resin - B1 far, cell with broken wall", + "Pale Pine Resin x2"), + DS3LocationData("ID: Simple Gem - B2 far, cell by stairs", "Simple Gem"), + DS3LocationData("ID: Large Soul of a Nameless Soldier - B2 far, by lift", + "Large Soul of a Nameless Soldier"), + DS3LocationData("ID: Large Titanite Shard - B1 far, rightmost cell", + "Large Titanite Shard"), + DS3LocationData("ID: Homeward Bone - path from B2 to pit", "Homeward Bone x2"), + DS3LocationData("ID: Bellowing Dragoncrest Ring - drop from B1 towards pit", + "Bellowing Dragoncrest Ring", conditional=True), + DS3LocationData("ID: Soul of a Weary Warrior - by drop to pit", "Soul of a Weary Warrior"), + DS3LocationData("ID: Soul of a Crestfallen Knight - balcony above pit", + "Soul of a Crestfallen Knight"), + DS3LocationData("ID: Lightning Bolt - awning over pit", "Lightning Bolt x9"), + DS3LocationData("ID: Large Titanite Shard - pit #1", "Large Titanite Shard"), + DS3LocationData("ID: Profaned Flame - pit", "Profaned Flame"), + DS3LocationData("ID: Large Titanite Shard - pit #2", "Large Titanite Shard"), + DS3LocationData("ID: Soul of a Weary Warrior - stairs between pit and B3", + "Soul of a Weary Warrior"), + DS3LocationData("ID: Dung Pie - B3, by path from pit", "Dung Pie x4"), + DS3LocationData("ID: Ember - B3 center", "Ember"), + DS3LocationData("ID: Ember - B3 far right", "Ember"), + DS3LocationData("ID: Profaned Coal - B3 far, left cell", "Profaned Coal"), + DS3LocationData("ID: Large Titanite Shard - B3 near, right corner", "Large Titanite Shard"), + DS3LocationData("ID: Old Sorcerer Hat - B2 near, middle cell", "Old Sorcerer Hat"), + DS3LocationData("ID: Old Sorcerer Coat - B2 near, middle cell", "Old Sorcerer Coat"), + DS3LocationData("ID: Old Sorcerer Gauntlets - B2 near, middle cell", + "Old Sorcerer Gauntlets"), + DS3LocationData("ID: Old Sorcerer Boots - B2 near, middle cell", "Old Sorcerer Boots"), + DS3LocationData("ID: Large Soul of a Weary Warrior - just before Profaned Capital", + "Large Soul of a Weary Warrior"), + DS3LocationData("ID: Covetous Gold Serpent Ring - Siegward's cell", + "Covetous Gold Serpent Ring", conditional=True), + DS3LocationData("ID: Lightning Blade - B3 lift, middle platform", "Lightning Blade"), + DS3LocationData("ID: Rusted Coin - after bonfire, first cell on left", "Rusted Coin"), + DS3LocationData("ID: Dusk Crown Ring - B3 far, right cell", "Dusk Crown Ring"), + DS3LocationData("ID: Pickaxe - path from pit to B3", "Pickaxe"), + DS3LocationData("ID: Xanthous Ashes - B3 far, right cell", "Xanthous Ashes", + progression=True), + DS3LocationData("ID: Large Titanite Shard - B1 near, by door", "Large Titanite Shard"), + DS3LocationData("ID: Rusted Gold Coin - after bonfire, last cell on right", + "Rusted Gold Coin"), + DS3LocationData("ID: Old Cell Key - stairs between pit and B3", "Old Cell Key"), + DS3LocationData("ID: Covetous Silver Serpent Ring+1 - pit lift, middle platform", + "Covetous Silver Serpent Ring+1", ngp=True), + DS3LocationData("ID: Dragon Torso Stone - B3, outside lift", "Dragon Torso Stone"), + DS3LocationData("ID: Prisoner Chief's Ashes - B2 near, locked cell by stairs", + "Prisoner Chief's Ashes", progression=True), + DS3LocationData("ID: Great Magic Shield - B2 near, mob drop in far left cell", + "Great Magic Shield", drop=True, + hidden=True), # Guaranteed drop from a normal-looking Corpse-Grub + DS3LocationData("ID: Dragonslayer Lightning Arrow - pit, mimic in hall", + "Dragonslayer Lightning Arrow x10", mimic=True), + DS3LocationData("ID: Titanite Scale - B3 far, mimic in hall", "Titanite Scale x2", + mimic=True), + DS3LocationData("ID: Dark Clutch Ring - stairs between pit and B3, mimic", + "Dark Clutch Ring", mimic=True), + DS3LocationData("ID: Estus Shard - mimic on path from B2 to pit", "Estus Shard", + mimic=True), + DS3LocationData("ID: Titanite Chunk - balcony above pit, lizard", "Titanite Chunk", + lizard=True), + DS3LocationData("ID: Titanite Scale - B2 far, lizard", "Titanite Scale", lizard=True), + + # These are missable because of a bug that causes them to be dropped wherever the giant is + # randomized to, instead of where the miniboss is in vanilla. + DS3LocationData("ID: Dung Pie - pit, miniboss drop", "Dung Pie x4", + miniboss=True, missable=True), # Giant slave drop + DS3LocationData("ID: Titanite Chunk - pit, miniboss drop", "Titanite Chunk", + miniboss=True, missable=True), # Giant Slave Drop + + # Alva (requires ember) + DS3LocationData("ID: Alva Helm - B3 near, by Karla's cell, after killing Alva", "Alva Helm", + missable=True, npc=True), + DS3LocationData("ID: Alva Armor - B3 near, by Karla's cell, after killing Alva", + "Alva Armor", missable=True, npc=True), + DS3LocationData("ID: Alva Gauntlets - B3 near, by Karla's cell, after killing Alva", + "Alva Gauntlets", missable=True, npc=True), + DS3LocationData("ID: Alva Leggings - B3 near, by Karla's cell, after killing Alva", + "Alva Leggings", missable=True, npc=True), ], "Profaned Capital": [ - DS3LocationData("PC: Cursebite Ring", "Cursebite Ring", DS3LocationCategory.RING), - DS3LocationData("PC: Court Sorcerer Hood", "Court Sorcerer Hood", DS3LocationCategory.ARMOR), - DS3LocationData("PC: Court Sorcerer Robe", "Court Sorcerer Robe", DS3LocationCategory.ARMOR), - DS3LocationData("PC: Court Sorcerer Gloves", "Court Sorcerer Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("PC: Court Sorcerer Trousers", "Court Sorcerer Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("PC: Wrath of the Gods", "Wrath of the Gods", DS3LocationCategory.SPELL), - DS3LocationData("PC: Logan's Scroll", "Logan's Scroll", DS3LocationCategory.MISC), - DS3LocationData("PC: Eleonora", "Eleonora", DS3LocationCategory.WEAPON), - DS3LocationData("PC: Court Sorcerer's Staff", "Court Sorcerer's Staff", DS3LocationCategory.WEAPON), - DS3LocationData("PC: Greatshield of Glory", "Greatshield of Glory", DS3LocationCategory.SHIELD), - DS3LocationData("PC: Storm Ruler", "Storm Ruler", DS3LocationCategory.KEY), - DS3LocationData("PC: Cinders of a Lord - Yhorm the Giant", "Cinders of a Lord - Yhorm the Giant", DS3LocationCategory.KEY), - DS3LocationData("PC: Soul of Yhorm the Giant", "Soul of Yhorm the Giant", DS3LocationCategory.BOSS), + DS3LocationData("PC: Soul of Yhorm the Giant", "Soul of Yhorm the Giant", boss=True), + DS3LocationData("PC: Cinders of a Lord - Yhorm the Giant", + "Cinders of a Lord - Yhorm the Giant", static="07,0:50002170::", + prominent=True, progression=True, boss=True), + DS3LocationData("PC: Logan's Scroll - chapel roof, NPC drop", "Logan's Scroll", + hostile_npc=True), # Sorcerer + DS3LocationData("PC: Purging Stone - chapel ground floor", "Purging Stone x3"), + DS3LocationData("PC: Rusted Coin - tower exterior", "Rusted Coin x2"), + DS3LocationData("PC: Rusted Gold Coin - halls above swamp", "Rusted Gold Coin"), + DS3LocationData("PC: Purging Stone - swamp, by chapel ladder", "Purging Stone"), + DS3LocationData("PC: Cursebite Ring - swamp, below halls", "Cursebite Ring"), + DS3LocationData("PC: Poison Gem - swamp, below halls", "Poison Gem"), + DS3LocationData("PC: Shriving Stone - swamp, by chapel door", "Shriving Stone"), + DS3LocationData("PC: Poison Arrow - chapel roof", "Poison Arrow x18"), + DS3LocationData("PC: Rubbish - chapel, down stairs from second floor", "Rubbish"), + DS3LocationData("PC: Onislayer Greatarrow - bridge", "Onislayer Greatarrow x8"), + DS3LocationData("PC: Large Soul of a Weary Warrior - bridge, far end", + "Large Soul of a Weary Warrior"), + DS3LocationData("PC: Rusted Coin - below bridge #1", "Rusted Coin"), + DS3LocationData("PC: Rusted Coin - below bridge #2", "Rusted Coin"), + DS3LocationData("PC: Blooming Purple Moss Clump - walkway above swamp", + "Blooming Purple Moss Clump x3"), + DS3LocationData("PC: Wrath of the Gods - chapel, drop from roof", "Wrath of the Gods"), + DS3LocationData("PC: Onislayer Greatbow - drop from bridge", "Onislayer Greatbow", + hidden=True), # Hidden fall + DS3LocationData("PC: Jailer's Key Ring - hall past chapel", "Jailer's Key Ring", + progression=True), + DS3LocationData("PC: Ember - palace, far room", "Ember"), + DS3LocationData("PC: Flame Stoneplate Ring+1 - chapel, drop from roof towards entrance", + "Flame Stoneplate Ring+1", ngp=True, hidden=True), # Hidden fall + DS3LocationData("PC: Magic Stoneplate Ring+2 - tower base", "Magic Stoneplate Ring+2", + ngp=True), + DS3LocationData("PC: Court Sorcerer Hood - chapel, second floor", "Court Sorcerer Hood"), + DS3LocationData("PC: Court Sorcerer Robe - chapel, second floor", "Court Sorcerer Robe"), + DS3LocationData("PC: Court Sorcerer Gloves - chapel, second floor", "Court Sorcerer Gloves"), + DS3LocationData("PC: Court Sorcerer Trousers - chapel, second floor", + "Court Sorcerer Trousers"), + DS3LocationData("PC: Storm Ruler - boss room", "Storm Ruler"), + DS3LocationData("PC: Undead Bone Shard - by bonfire", "Undead Bone Shard"), + DS3LocationData("PC: Eleonora - chapel ground floor, kill mob", "Eleonora", + drop=True, + hidden=True), # Guaranteed drop from a normal-looking Monstrosity of Sin + DS3LocationData("PC: Rusted Gold Coin - palace, mimic in far room", "Rusted Gold Coin x2", + mimic=True), + DS3LocationData("PC: Court Sorcerer's Staff - chapel, mimic on second floor", + "Court Sorcerer's Staff", mimic=True), + DS3LocationData("PC: Greatshield of Glory - palace, mimic in far room", + "Greatshield of Glory", mimic=True), + DS3LocationData("PC: Twinkling Titanite - halls above swamp, lizard #1", + "Twinkling Titanite", lizard=True), + DS3LocationData("PC: Twinkling Titanite - halls above swamp, lizard #2", + "Twinkling Titanite", lizard=True), + DS3LocationData("PC: Siegbräu - Siegward after killing boss", "Siegbräu", + missable=True, npc=True), + + # Siegward drops (kill or quest) + DS3LocationData("PC: Storm Ruler - Siegward", "Storm Ruler", static='02,0:50006218::', + missable=True, drop=True, npc=True), + DS3LocationData("PC: Pierce Shield - Siegward", "Pierce Shield", missable=True, + drop=True, npc=True), ], + # We consider "Anor Londo" to be everything accessible only after killing Pontiff. This doesn't + # match up one-to-one with where the game pops up the region name, but it balances items better + # and covers the region that's full of DS1 Anor Londo references. "Anor Londo": [ - DS3LocationData("AL: Giant's Coal", "Giant's Coal", DS3LocationCategory.MISC), - DS3LocationData("AL: Sun Princess Ring", "Sun Princess Ring", DS3LocationCategory.RING), - DS3LocationData("AL: Aldrich's Ruby", "Aldrich's Ruby", DS3LocationCategory.RING), - DS3LocationData("AL: Cinders of a Lord - Aldrich", "Cinders of a Lord - Aldrich", DS3LocationCategory.KEY), - DS3LocationData("AL: Soul of Aldrich", "Soul of Aldrich", DS3LocationCategory.BOSS), + DS3LocationData("AL: Soul of Aldrich", "Soul of Aldrich", boss=True), + DS3LocationData("AL: Cinders of a Lord - Aldrich", "Cinders of a Lord - Aldrich", + static='06,0:50002130::', prominent=True, progression=True, + boss=True), + DS3LocationData("AL: Yorshka's Chime - kill Yorshka", "Yorshka's Chime", missable=True, + drop=True, + npc=True), # Hidden walkway, missable because it will break Sirris's quest + DS3LocationData("AL: Drang Twinspears - plaza, NPC drop", "Drang Twinspears", drop=True, + hidden=True), + DS3LocationData("AL: Estus Shard - dark cathedral, by left stairs", "Estus Shard"), + DS3LocationData("AL: Painting Guardian's Curved Sword - prison tower rafters", + "Painting Guardian's Curved Sword", hidden=True), # Invisible walkway + DS3LocationData("AL: Brass Helm - tomb", "Brass Helm", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Brass Armor - tomb", "Brass Armor", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Brass Gauntlets - tomb", "Brass Gauntlets", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Brass Leggings - tomb", "Brass Leggings", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Human Dregs - water reserves", "Human Dregs", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Ember - spiral staircase, bottom", "Ember"), + DS3LocationData("AL: Large Titanite Shard - bottom of the furthest buttress", + "Large Titanite Shard"), + DS3LocationData("AL: Large Titanite Shard - right after light cathedral", + "Large Titanite Shard"), + DS3LocationData("AL: Large Titanite Shard - walkway, side path by cathedral", + "Large Titanite Shard"), + DS3LocationData("AL: Soul of a Weary Warrior - plaza, nearer", "Soul of a Weary Warrior"), + DS3LocationData("AL: Ember - plaza, right side", "Ember"), + DS3LocationData("AL: Ember - plaza, further", "Ember"), + DS3LocationData("AL: Large Titanite Shard - balcony by dead giants", + "Large Titanite Shard"), + DS3LocationData("AL: Dark Stoneplate Ring - by dark stairs up from plaza", + "Dark Stoneplate Ring"), + DS3LocationData("AL: Large Titanite Shard - bottom of the nearest buttress", + "Large Titanite Shard"), + DS3LocationData("AL: Deep Gem - water reserves", "Deep Gem"), + DS3LocationData("AL: Titanite Scale - top of ladder up to buttresses", "Titanite Scale"), + DS3LocationData("AL: Dragonslayer Greatarrow - drop from nearest buttress", + "Dragonslayer Greatarrow x5", static='06,0:53700620::', + hidden=True), # Hidden fall + DS3LocationData("AL: Dragonslayer Greatbow - drop from nearest buttress", + "Dragonslayer Greatbow", static='06,0:53700620::', + hidden=True), # Hidden fall + DS3LocationData("AL: Easterner's Ashes - below top of furthest buttress", + "Easterner's Ashes", progression=True), + DS3LocationData("AL: Painting Guardian Hood - prison tower, rafters", + "Painting Guardian Hood", hidden=True), # Invisible walkway + DS3LocationData("AL: Painting Guardian Gown - prison tower, rafters", + "Painting Guardian Gown", hidden=True), # Invisible walkway + DS3LocationData("AL: Painting Guardian Gloves - prison tower, rafters", + "Painting Guardian Gloves", hidden=True), # Invisible walkway + DS3LocationData("AL: Painting Guardian Waistcloth - prison tower, rafters", + "Painting Guardian Waistcloth", hidden=True), # Invisible walkway + DS3LocationData("AL: Soul of a Crestfallen Knight - right of dark cathedral entrance", + "Soul of a Crestfallen Knight"), + DS3LocationData("AL: Moonlight Arrow - dark cathedral, up right stairs", + "Moonlight Arrow x6"), + DS3LocationData("AL: Proof of a Concord Kept - dark cathedral, up left stairs", + "Proof of a Concord Kept"), + DS3LocationData("AL: Large Soul of a Weary Warrior - left of dark cathedral entrance", + "Large Soul of a Weary Warrior"), + DS3LocationData("AL: Giant's Coal - by giant near dark cathedral", "Giant's Coal"), + DS3LocationData("AL: Havel's Ring+2 - prison tower, rafters", "Havel's Ring+2", ngp=True, + hidden=True), # Invisible walkway + DS3LocationData("AL: Ring of Favor+1 - light cathedral, upstairs", "Ring of Favor+1", + ngp=True), + DS3LocationData("AL: Sun Princess Ring - dark cathedral, after boss", "Sun Princess Ring"), + DS3LocationData("AL: Reversal Ring - tomb, chest in corner", "Reversal Ring", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Golden Ritual Spear - light cathedral, mimic upstairs", + "Golden Ritual Spear", mimic=True), + DS3LocationData("AL: Ring of Favor - water reserves, both minibosses", "Ring of Favor", + miniboss=True, + hidden=True), # Sulyvahn's Beast Duo drop, behind illusory wall + DS3LocationData("AL: Blade of the Darkmoon - Yorshka with Darkmoon Loyalty", + "Blade of the Darkmoon", missable=True, drop=True, + npc=True), # Hidden walkway, missable because it will break Sirris's quest + DS3LocationData("AL: Simple Gem - light cathedral, lizard upstairs", "Simple Gem", + lizard=True), + DS3LocationData("AL: Twinkling Titanite - lizard after light cathedral #1", + "Twinkling Titanite", lizard=True), + DS3LocationData("AL: Twinkling Titanite - lizard after light cathedral #2", + "Twinkling Titanite", lizard=True), + DS3LocationData("AL: Aldrich's Ruby - dark cathedral, miniboss", "Aldrich's Ruby", + miniboss=True), # Deep Accursed drop + DS3LocationData("AL: Aldrich Faithful - water reserves, talk to McDonnel", "Aldrich Faithful", + hidden=True), # Behind illusory wall + + DS3LocationData("FS: Budding Green Blossom - shop after killing Creighton and AL boss", + "Budding Green Blossom", static='99,0:-1:110000,70000118:', + missable=True, npc=True, + shop=True), # sold by Shrine Maiden after killing Aldrich and helping + # Sirris defeat Creighton + + # Sirris (quest completion) + DS3LocationData("FS: Sunset Shield - by grave after killing Hodrick w/Sirris", + "Sunset Shield", missable=True, hostile_npc=True, npc=True), + # In Pit of Hollows after killing Hodrick + DS3LocationData("US: Sunset Helm - Pit of Hollows after killing Hodrick w/Sirris", + "Sunset Helm", missable=True, hostile_npc=True, npc=True), + DS3LocationData("US: Sunset Armor - pit of hollows after killing Hodrick w/Sirris", + "Sunset Armor", missable=True, hostile_npc=True, npc=True), + DS3LocationData("US: Sunset Gauntlets - pit of hollows after killing Hodrick w/Sirris", + "Sunset Gauntlets", missable=True, hostile_npc=True, npc=True), + DS3LocationData("US: Sunset Leggings - pit of hollows after killing Hodrick w/Sirris", + "Sunset Leggings", missable=True, hostile_npc=True, npc=True), + + # Shrine Handmaid after killing Sulyvahn's Beast Duo + DS3LocationData("FS: Helm of Favor - shop after killing water reserve minibosses", + "Helm of Favor", hidden=True, miniboss=True, shop=True), + DS3LocationData("FS: Embraced Armor of Favor - shop after killing water reserve minibosses", + "Embraced Armor of Favor", hidden=True, miniboss=True, shop=True), + DS3LocationData("FS: Gauntlets of Favor - shop after killing water reserve minibosses", + "Gauntlets of Favor", hidden=True, miniboss=True, shop=True), + DS3LocationData("FS: Leggings of Favor - shop after killing water reserve minibosses", + "Leggings of Favor", hidden=True, miniboss=True, shop=True), + + # Anri of Astora + DS3LocationData("AL: Chameleon - tomb after marrying Anri", "Chameleon", missable=True, + npc=True), + DS3LocationData("AL: Anri's Straight Sword - Anri quest", "Anri's Straight Sword", + missable=True, npc=True), + + # Shrine Handmaid after killing Ringfinger Leonhard + # This is listed here even though you can kill Leonhard immediately because we want the + # logic to assume people will do his full quest. Missable because he can disappear forever + # if you use up all your Pale Tongues. + DS3LocationData("FS: Leonhard's Garb - shop after killing Leonhard", + "Leonhard's Garb", hidden=True, npc=True, shop=True, missable=True), + DS3LocationData("FS: Leonhard's Gauntlets - shop after killing Leonhard", + "Leonhard's Gauntlets", hidden=True, npc=True, shop=True, + missable=True), + DS3LocationData("FS: Leonhard's Trousers - shop after killing Leonhard", + "Leonhard's Trousers", hidden=True, npc=True, shop=True, + missable=True), + + # Shrine Handmaid after killing Alrich, Devourer of Gods + DS3LocationData("FS: Smough's Helm - shop after killing AL boss", "Smough's Helm", + boss=True, shop=True), + DS3LocationData("FS: Smough's Armor - shop after killing AL boss", "Smough's Armor", + boss=True, shop=True), + DS3LocationData("FS: Smough's Gauntlets - shop after killing AL boss", "Smough's Gauntlets", + boss=True, shop=True), + DS3LocationData("FS: Smough's Leggings - shop after killing AL boss", "Smough's Leggings", + boss=True, shop=True), + + # Ringfinger Leonhard (quest or kill) + DS3LocationData("AL: Crescent Moon Sword - Leonhard drop", "Crescent Moon Sword", + missable=True, npc=True), + DS3LocationData("AL: Silver Mask - Leonhard drop", "Silver Mask", missable=True, + npc=True), + DS3LocationData("AL: Soul of Rosaria - Leonhard drop", "Soul of Rosaria", missable=True, + npc=True), + + # Shrine Handmaid after killing Anri or completing their quest + DS3LocationData("FS: Elite Knight Helm - shop after Anri quest", "Elite Knight Helm", + npc=True, shop=True), + DS3LocationData("FS: Elite Knight Armor - shop after Anri quest", "Elite Knight Armor", + npc=True, shop=True), + DS3LocationData("FS: Elite Knight Gauntlets - shop after Anri quest", + "Elite Knight Gauntlets", npc=True, shop=True), + DS3LocationData("FS: Elite Knight Leggings - shop after Anri quest", + "Elite Knight Leggings", npc=True, shop=True), ], "Lothric Castle": [ - DS3LocationData("LC: Hood of Prayer", "Hood of Prayer", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Robe of Prayer", "Robe of Prayer", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Skirt of Prayer", "Skirt of Prayer", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Sacred Bloom Shield", "Sacred Bloom Shield", DS3LocationCategory.SHIELD), - DS3LocationData("LC: Winged Knight Helm", "Winged Knight Helm", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Winged Knight Armor", "Winged Knight Armor", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Winged Knight Gauntlets", "Winged Knight Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Winged Knight Leggings", "Winged Knight Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Greatlance", "Greatlance", DS3LocationCategory.WEAPON), - DS3LocationData("LC: Sniper Crossbow", "Sniper Crossbow", DS3LocationCategory.WEAPON), - DS3LocationData("LC: Spirit Tree Crest Shield", "Spirit Tree Crest Shield", DS3LocationCategory.SHIELD), - DS3LocationData("LC: Red Tearstone Ring", "Red Tearstone Ring", DS3LocationCategory.RING), - DS3LocationData("LC: Caitha's Chime", "Caitha's Chime", DS3LocationCategory.WEAPON), - DS3LocationData("LC: Braille Divine Tome of Lothric", "Braille Divine Tome of Lothric", DS3LocationCategory.MISC), - DS3LocationData("LC: Knight's Ring", "Knight's Ring", DS3LocationCategory.RING), - DS3LocationData("LC: Irithyll Rapier", "Irithyll Rapier", DS3LocationCategory.WEAPON), - DS3LocationData("LC: Sunlight Straight Sword", "Sunlight Straight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("LC: Soul of Dragonslayer Armour", "Soul of Dragonslayer Armour", DS3LocationCategory.BOSS), - DS3LocationData("LC: Grand Archives Key", "Grand Archives Key", DS3LocationCategory.KEY), - DS3LocationData("LC: Gotthard Twinswords", "Gotthard Twinswords", DS3LocationCategory.WEAPON), + DS3LocationData("LC: Soul of Dragonslayer Armour", "Soul of Dragonslayer Armour", + prominent=True, boss=True), + DS3LocationData("LC: Sniper Bolt - moat, right path end", "Sniper Bolt x11"), + DS3LocationData("LC: Sniper Crossbow - moat, right path end", "Sniper Crossbow"), + DS3LocationData("LC: Titanite Scale - dark room, upper balcony", "Titanite Scale"), + DS3LocationData("LC: Titanite Chunk - dark room mid, out door opposite wyvern", + "Titanite Chunk"), + DS3LocationData("LC: Greatlance - overlooking Dragon Barracks bonfire", "Greatlance"), + DS3LocationData("LC: Titanite Chunk - ascent, first balcony", "Titanite Chunk"), + DS3LocationData("LC: Titanite Chunk - ascent, turret before barricades", "Titanite Chunk"), + DS3LocationData("LC: Sacred Bloom Shield - ascent, behind illusory wall", + "Sacred Bloom Shield", hidden=True), # Behind illusory wall + DS3LocationData("LC: Titanite Chunk - ascent, final turret", "Titanite Chunk x2"), + DS3LocationData("LC: Refined Gem - plaza", "Refined Gem"), + DS3LocationData("LC: Soul of a Crestfallen Knight - by lift bottom", + "Soul of a Crestfallen Knight"), + DS3LocationData("LC: Undead Bone Shard - moat, far ledge", "Undead Bone Shard"), + DS3LocationData("LC: Lightning Urn - moat, right path, first room", "Lightning Urn x3"), + DS3LocationData("LC: Titanite Chunk - moat #1", "Titanite Chunk"), + DS3LocationData("LC: Titanite Chunk - moat #2", "Titanite Chunk"), + DS3LocationData("LC: Titanite Chunk - moat, near ledge", "Titanite Chunk"), + DS3LocationData("LC: Caitha's Chime - chapel, drop onto roof", "Caitha's Chime"), + DS3LocationData("LC: Lightning Urn - plaza", "Lightning Urn x6"), + DS3LocationData("LC: Ember - plaza, by gate", "Ember"), + DS3LocationData("LC: Raw Gem - plaza left", "Raw Gem"), + DS3LocationData("LC: Black Firebomb - dark room lower", "Black Firebomb x3"), + DS3LocationData("LC: Pale Pine Resin - dark room upper, by mimic", "Pale Pine Resin"), + DS3LocationData("LC: Large Soul of a Weary Warrior - main hall, by lever", + "Large Soul of a Weary Warrior"), + DS3LocationData("LC: Sunlight Medal - by lift top", "Sunlight Medal"), + DS3LocationData("LC: Soul of a Crestfallen Knight - wyvern room, balcony", + "Soul of a Crestfallen Knight", hidden=True), # Hidden fall + DS3LocationData("LC: Titanite Chunk - altar roof", "Titanite Chunk"), + DS3LocationData("LC: Titanite Scale - dark room mid, out door opposite wyvern", + "Titanite Scale"), + DS3LocationData("LC: Large Soul of a Nameless Soldier - moat, right path", + "Large Soul of a Nameless Soldier"), + DS3LocationData("LC: Knight's Ring - altar", "Knight's Ring"), + DS3LocationData("LC: Ember - main hall, left of stairs", "Ember"), + DS3LocationData("LC: Large Soul of a Weary Warrior - ascent, last turret", + "Large Soul of a Weary Warrior"), + DS3LocationData("LC: Ember - by Dragon Barracks bonfire", "Ember"), + DS3LocationData("LC: Twinkling Titanite - ascent, side room", "Twinkling Titanite"), + DS3LocationData("LC: Large Soul of a Nameless Soldier - dark room mid", + "Large Soul of a Nameless Soldier"), + DS3LocationData("LC: Ember - plaza center", "Ember"), + DS3LocationData("LC: Winged Knight Helm - ascent, behind illusory wall", + "Winged Knight Helm", hidden=True), + DS3LocationData("LC: Winged Knight Armor - ascent, behind illusory wall", + "Winged Knight Armor", hidden=True), + DS3LocationData("LC: Winged Knight Gauntlets - ascent, behind illusory wall", + "Winged Knight Gauntlets", hidden=True), + DS3LocationData("LC: Winged Knight Leggings - ascent, behind illusory wall", + "Winged Knight Leggings", hidden=True), + DS3LocationData("LC: Rusted Coin - chapel", "Rusted Coin x2"), + DS3LocationData("LC: Braille Divine Tome of Lothric - wyvern room", + "Braille Divine Tome of Lothric", hidden=True), # Hidden fall + DS3LocationData("LC: Red Tearstone Ring - chapel, drop onto roof", "Red Tearstone Ring"), + DS3LocationData("LC: Twinkling Titanite - moat, left side", "Twinkling Titanite x2"), + DS3LocationData("LC: Large Soul of a Nameless Soldier - plaza left, by pillar", + "Large Soul of a Nameless Soldier"), + DS3LocationData("LC: Titanite Scale - altar", "Titanite Scale x3"), + DS3LocationData("LC: Titanite Scale - chapel, chest", "Titanite Scale"), + DS3LocationData("LC: Hood of Prayer", "Hood of Prayer"), + DS3LocationData("LC: Robe of Prayer - ascent, chest at beginning", "Robe of Prayer"), + DS3LocationData("LC: Skirt of Prayer - ascent, chest at beginning", "Skirt of Prayer"), + DS3LocationData("LC: Spirit Tree Crest Shield - basement, chest", + "Spirit Tree Crest Shield"), + DS3LocationData("LC: Titanite Scale - basement, chest", "Titanite Scale"), + DS3LocationData("LC: Twinkling Titanite - basement, chest #1", "Twinkling Titanite"), + DS3LocationData("LC: Twinkling Titanite - basement, chest #2", "Twinkling Titanite x2"), + DS3LocationData("LC: Life Ring+2 - dark room mid, out door opposite wyvern, drop down", + "Life Ring+2", ngp=True, hidden=True), # Hidden fall + DS3LocationData("LC: Dark Stoneplate Ring+1 - wyvern room, balcony", + "Dark Stoneplate Ring+1", ngp=True, hidden=True), # Hidden fall + DS3LocationData("LC: Thunder Stoneplate Ring+2 - chapel, drop onto roof", + "Thunder Stoneplate Ring+2", ngp=True), + DS3LocationData("LC: Sunlight Straight Sword - wyvern room, mimic", + "Sunlight Straight Sword", mimic=True, hidden=True), # Hidden fall + DS3LocationData("LC: Titanite Scale - dark room, upper, mimic", "Titanite Scale x3", + mimic=True), + DS3LocationData("LC: Ember - wyvern room, wyvern foot mob drop", "Ember x2", + drop=True, hidden=True), # Hidden fall, Pus of Man Wyvern drop + DS3LocationData("LC: Titanite Chunk - wyvern room, wyvern foot mob drop", "Titanite Chunk x2", + drop=True, hidden=True), # Hidden fall, Pus of Man Wyvern drop + DS3LocationData("LC: Ember - dark room mid, pus of man mob drop", "Ember x2", + drop=True), # Pus of Man Wyvern drop + DS3LocationData("LC: Titanite Chunk - dark room mid, pus of man mob drop", + "Titanite Chunk x2"), + DS3LocationData("LC: Irithyll Rapier - basement, miniboss drop", "Irithyll Rapier", + miniboss=True), # Boreal Outrider drop + DS3LocationData("LC: Twinkling Titanite - dark room mid, out door opposite wyvern, lizard", + "Twinkling Titanite x2", lizard=True, missable=True), + DS3LocationData("LC: Twinkling Titanite - moat, right path, lizard", + "Twinkling Titanite x2", lizard=True, missable=True), + DS3LocationData("LC: Gotthard Twinswords - by Grand Archives door, after PC and AL bosses", + "Gotthard Twinswords", conditional=True), + DS3LocationData("LC: Grand Archives Key - by Grand Archives door, after PC and AL bosses", + "Grand Archives Key", prominent=True, progression=True, + conditional=True), + DS3LocationData("LC: Titanite Chunk - down stairs after boss", "Titanite Chunk"), + + # Eygon of Carim (kill or quest) + DS3LocationData("FS: Morne's Great Hammer - Eygon", "Morne's Great Hammer", npc=True), + DS3LocationData("FS: Moaning Shield - Eygon", "Moaning Shield", npc=True), + + # Shrine Handmaid after killing Dragonslayer Armour (or Eygon of Carim) + DS3LocationData("FS: Dancer's Crown - shop after killing LC entry boss", "Dancer's Crown", + boss=True, shop=True), + DS3LocationData("FS: Dancer's Armor - shop after killing LC entry boss", "Dancer's Armor", + boss=True, shop=True), + DS3LocationData("FS: Dancer's Gauntlets - shop after killing LC entry boss", + "Dancer's Gauntlets", boss=True, shop=True), + DS3LocationData("FS: Dancer's Leggings - shop after killing LC entry boss", + "Dancer's Leggings", boss=True, shop=True), + + # Shrine Handmaid after killing Dragonslayer Armour (or Eygon of Carim) + DS3LocationData("FS: Morne's Helm - shop after killing Eygon or LC boss", "Morne's Helm", + boss=True, shop=True), + DS3LocationData("FS: Morne's Armor - shop after killing Eygon or LC boss", "Morne's Armor", + boss=True, shop=True), + DS3LocationData("FS: Morne's Gauntlets - shop after killing Eygon or LC boss", + "Morne's Gauntlets", boss=True, shop=True), + DS3LocationData("FS: Morne's Leggings - shop after killing Eygon or LC boss", + "Morne's Leggings", boss=True, shop=True), ], "Consumed King's Garden": [ - DS3LocationData("CKG: Dragonscale Ring", "Dragonscale Ring", DS3LocationCategory.RING), - DS3LocationData("CKG: Shadow Mask", "Shadow Mask", DS3LocationCategory.ARMOR), - DS3LocationData("CKG: Shadow Garb", "Shadow Garb", DS3LocationCategory.ARMOR), - DS3LocationData("CKG: Shadow Gauntlets", "Shadow Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("CKG: Shadow Leggings", "Shadow Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("CKG: Claw", "Claw", DS3LocationCategory.WEAPON), - DS3LocationData("CKG: Soul of Consumed Oceiros", "Soul of Consumed Oceiros", DS3LocationCategory.BOSS), - DS3LocationData("CKG: Magic Stoneplate Ring", "Magic Stoneplate Ring", DS3LocationCategory.RING), + DS3LocationData("CKG: Soul of Consumed Oceiros", "Soul of Consumed Oceiros", + prominent=True, boss=True), + # Could classify this as "hidden" because it's midway down an elevator, but the elevator is + # so slow and the midway point is so obvious that it's not actually hard to find. + DS3LocationData("CKG: Estus Shard - balcony", "Estus Shard"), + DS3LocationData("CKG: Shadow Mask - under center platform", "Shadow Mask"), + DS3LocationData("CKG: Shadow Garb - under rotunda", "Shadow Garb"), + DS3LocationData("CKG: Shadow Gauntlets - under rotunda", "Shadow Gauntlets"), + DS3LocationData("CKG: Shadow Leggings - under rotunda", "Shadow Leggings"), + DS3LocationData("CKG: Black Firebomb - under rotunda", "Black Firebomb x2"), + DS3LocationData("CKG: Claw - under rotunda", "Claw"), + DS3LocationData("CKG: Titanite Chunk - up lone stairway", "Titanite Chunk"), + DS3LocationData("CKG: Dragonscale Ring - shortcut, leave halfway down lift", + "Dragonscale Ring"), + DS3LocationData("CKG: Human Pine Resin - toxic pool, past rotunda", "Human Pine Resin"), + DS3LocationData("CKG: Titanite Chunk - shortcut", "Titanite Chunk"), + DS3LocationData("CKG: Titanite Chunk - balcony, drop onto rubble", "Titanite Chunk"), + DS3LocationData("CKG: Soul of a Weary Warrior - before first lift", + "Soul of a Weary Warrior"), + DS3LocationData("CKG: Dark Gem - under lone stairway", "Dark Gem"), + DS3LocationData("CKG: Titanite Scale - shortcut", "Titanite Scale"), + DS3LocationData("CKG: Human Pine Resin - pool by lift", "Human Pine Resin x2"), + DS3LocationData("CKG: Titanite Chunk - right of shortcut lift bottom", "Titanite Chunk"), + DS3LocationData("CKG: Ring of Sacrifice - under balcony", "Ring of Sacrifice"), + DS3LocationData("CKG: Wood Grain Ring+1 - by first elevator bottom", "Wood Grain Ring+1", + ngp=True), + DS3LocationData("CKG: Sage Ring+2 - balcony, drop onto rubble, jump back", "Sage Ring+2", + ngp=True, hidden=True), + DS3LocationData("CKG: Titanite Scale - tomb, chest #1", "Titanite Scale"), + DS3LocationData("CKG: Titanite Scale - tomb, chest #2", "Titanite Scale"), + DS3LocationData("CKG: Magic Stoneplate Ring - mob drop before boss", + "Magic Stoneplate Ring", drop=True, + hidden=True), # Guaranteed drop from a normal-looking Cathedral Knight + + # After Oceiros's boss room, only once the Drakeblood summon in AP has been killed + DS3LocationData("CKG: Drakeblood Helm - tomb, after killing AP mausoleum NPC", + "Drakeblood Helm", hostile_npc=True, hidden=True), + DS3LocationData("CKG: Drakeblood Armor - tomb, after killing AP mausoleum NPC", + "Drakeblood Armor", hostile_npc=True, hidden=True), + DS3LocationData("CKG: Drakeblood Gauntlets - tomb, after killing AP mausoleum NPC", + "Drakeblood Gauntlets", hostile_npc=True, hidden=True), + DS3LocationData("CKG: Drakeblood Leggings - tomb, after killing AP mausoleum NPC", + "Drakeblood Leggings", hostile_npc=True, hidden=True), ], "Grand Archives": [ - DS3LocationData("GA: Avelyn", "Avelyn", DS3LocationCategory.WEAPON), - DS3LocationData("GA: Witch's Locks", "Witch's Locks", DS3LocationCategory.WEAPON), - DS3LocationData("GA: Power Within", "Power Within", DS3LocationCategory.SPELL), - DS3LocationData("GA: Scholar Ring", "Scholar Ring", DS3LocationCategory.RING), - DS3LocationData("GA: Soul Stream", "Soul Stream", DS3LocationCategory.SPELL), - DS3LocationData("GA: Fleshbite Ring", "Fleshbite Ring", DS3LocationCategory.RING), - DS3LocationData("GA: Crystal Chime", "Crystal Chime", DS3LocationCategory.WEAPON), - DS3LocationData("GA: Golden Wing Crest Shield", "Golden Wing Crest Shield", DS3LocationCategory.SHIELD), - DS3LocationData("GA: Onikiri and Ubadachi", "Onikiri and Ubadachi", DS3LocationCategory.WEAPON), - DS3LocationData("GA: Hunter's Ring", "Hunter's Ring", DS3LocationCategory.RING), - DS3LocationData("GA: Divine Pillars of Light", "Divine Pillars of Light", DS3LocationCategory.SPELL), - DS3LocationData("GA: Cinders of a Lord - Lothric Prince", "Cinders of a Lord - Lothric Prince", DS3LocationCategory.KEY), - DS3LocationData("GA: Soul of the Twin Princes", "Soul of the Twin Princes", DS3LocationCategory.BOSS), - DS3LocationData("GA: Sage's Crystal Staff", "Sage's Crystal Staff", DS3LocationCategory.WEAPON), - DS3LocationData("GA: Outrider Knight Helm", "Outrider Knight Helm", DS3LocationCategory.ARMOR), - DS3LocationData("GA: Outrider Knight Armor", "Outrider Knight Armor", DS3LocationCategory.ARMOR), - DS3LocationData("GA: Outrider Knight Gauntlets", "Outrider Knight Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("GA: Outrider Knight Leggings", "Outrider Knight Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("GA: Crystal Scroll", "Crystal Scroll", DS3LocationCategory.MISC), + DS3LocationData("GA: Titanite Slab - final elevator secret", "Titanite Slab", + hidden=True), + DS3LocationData("GA: Soul of the Twin Princes", "Soul of the Twin Princes", boss=True), + DS3LocationData("GA: Cinders of a Lord - Lothric Prince", + "Cinders of a Lord - Lothric Prince", + static="09,0:50002040::", prominent=True, progression=True, + boss=True), + DS3LocationData("GA: Onikiri and Ubadachi - outside 5F, NPC drop", "Onikiri and Ubadachi", + hostile_npc=True, # Black Hand Kamui drop + missable=True), # This is placed at the location the NPC gets randomized + # to, which makes it hard to include in logic. + DS3LocationData("GA: Golden Wing Crest Shield - outside 5F, NPC drop", + "Golden Wing Crest Shield", + hostile_npc=True), # Lion Knight Albert drop + DS3LocationData("GA: Sage's Crystal Staff - outside 5F, NPC drop", + "Sage's Crystal Staff", + hostile_npc=True), # Daughter of Crystal Kriemhild drop + DS3LocationData("GA: Titanite Chunk - 1F, up right stairs", "Titanite Chunk"), + DS3LocationData("GA: Titanite Chunk - 1F, path from wax pool", "Titanite Chunk"), + DS3LocationData("GA: Soul of a Crestfallen Knight - 1F, loop left after drop", + "Soul of a Crestfallen Knight"), + DS3LocationData("GA: Titanite Chunk - 1F, balcony", "Titanite Chunk"), + DS3LocationData("GA: Fleshbite Ring - up stairs from 4F", "Fleshbite Ring"), + DS3LocationData("GA: Soul of a Crestfallen Knight - path to dome", + "Soul of a Crestfallen Knight"), + DS3LocationData("GA: Soul of a Nameless Soldier - dark room", "Soul of a Nameless Soldier"), + DS3LocationData("GA: Crystal Chime - 1F, path from wax pool", "Crystal Chime"), + DS3LocationData("GA: Titanite Scale - dark room, upstairs", "Titanite Scale"), + DS3LocationData("GA: Estus Shard - dome, far balcony", "Estus Shard"), + DS3LocationData("GA: Homeward Bone - 2F early balcony", "Homeward Bone x3"), + DS3LocationData("GA: Titanite Scale - 2F, titanite scale atop bookshelf", "Titanite Scale", + hidden=True), # Hidden fall + DS3LocationData("GA: Titanite Chunk - 2F, by wax pool", "Titanite Chunk"), + DS3LocationData("GA: Hollow Gem - rooftops lower, in hall", "Hollow Gem", + hidden=True), # Hidden fall + DS3LocationData("GA: Titanite Scale - 3F, corner up stairs", "Titanite Scale"), + DS3LocationData("GA: Titanite Scale - 1F, up stairs on bookshelf", "Titanite Scale"), + DS3LocationData("GA: Titanite Scale - 3F, by ladder to 2F late", "Titanite Scale", + hidden=True), # Hidden by a table + DS3LocationData("GA: Shriving Stone - 2F late, by ladder from 3F", "Shriving Stone"), + DS3LocationData("GA: Large Soul of a Crestfallen Knight - 4F, back", + "Large Soul of a Crestfallen Knight"), + DS3LocationData("GA: Titanite Chunk - rooftops, balcony", "Titanite Chunk"), + DS3LocationData("GA: Titanite Scale - rooftops lower, path to 2F", "Titanite Scale x3", + hidden=True), # Hidden fall + DS3LocationData("GA: Titanite Chunk - rooftops lower, ledge by buttress", "Titanite Chunk", + hidden=True), # Hidden fall + DS3LocationData("GA: Soul of a Weary Warrior - rooftops, by lizards", + "Soul of a Weary Warrior"), + DS3LocationData("GA: Titanite Chunk - rooftops, just before 5F", "Titanite Chunk"), + DS3LocationData("GA: Ember - 5F, by entrance", "Ember"), + DS3LocationData("GA: Blessed Gem - rafters", "Blessed Gem"), + DS3LocationData("GA: Titanite Chunk - 5F, far balcony", "Titanite Chunk x2"), + DS3LocationData("GA: Large Soul of a Crestfallen Knight - outside 5F", + "Large Soul of a Crestfallen Knight"), + DS3LocationData("GA: Avelyn - 1F, drop from 3F onto bookshelves", "Avelyn", + hidden=True), # Hidden fall + DS3LocationData("GA: Titanite Chunk - 2F, right after dark room", "Titanite Chunk"), + DS3LocationData("GA: Hunter's Ring - dome, very top", "Hunter's Ring"), + DS3LocationData("GA: Divine Pillars of Light - cage above rafters", + "Divine Pillars of Light"), + DS3LocationData("GA: Power Within - dark room, behind retractable bookshelf", + "Power Within", hidden=True), # Switch in darkened room + DS3LocationData("GA: Sage Ring+1 - rafters, second level down", "Sage Ring+1", ngp=True), + DS3LocationData("GA: Lingering Dragoncrest Ring+2 - dome, room behind spire", + "Lingering Dragoncrest Ring+2", ngp=True), + DS3LocationData("GA: Divine Blessing - rafters, down lower level ladder", + "Divine Blessing"), + DS3LocationData("GA: Twinkling Titanite - rafters, down lower level ladder", + "Twinkling Titanite x3"), + DS3LocationData("GA: Witch's Locks - dark room, behind retractable bookshelf", + "Witch's Locks", hidden=True), # Switch in darkened room + DS3LocationData("GA: Titanite Slab - 1F, after pulling 2F switch", "Titanite Slab", + hidden=True), + DS3LocationData("GA: Titanite Scale - 4F, chest by exit", "Titanite Scale x3"), + DS3LocationData("GA: Soul Stream - 3F, behind illusory wall", "Soul Stream", + hidden=True), # Behind illusory wall + DS3LocationData("GA: Scholar Ring - 2F, between late and early", "Scholar Ring"), + DS3LocationData("GA: Undead Bone Shard - 5F, by entrance", "Undead Bone Shard"), + DS3LocationData("GA: Titanite Slab - dome, kill all mobs", "Titanite Slab", + drop=True, + hidden=True), # Guaranteed drop from killing all Winged Knights + DS3LocationData("GA: Outrider Knight Helm - 3F, behind illusory wall, miniboss drop", + "Outrider Knight Helm", miniboss=True, + hidden=True), # Behind illusory wall, Outrider Knight drop + DS3LocationData("GA: Outrider Knight Armor - 3F, behind illusory wall, miniboss drop", + "Outrider Knight Armor", miniboss=True, + hidden=True), # Behind illusory wall, Outrider Knight drop + DS3LocationData("GA: Outrider Knight Gauntlets - 3F, behind illusory wall, miniboss drop", + "Outrider Knight Gauntlets", miniboss=True, + hidden=True), # Behind illusory wall, Outrider Knight drop + DS3LocationData("GA: Outrider Knight Leggings - 3F, behind illusory wall, miniboss drop", + "Outrider Knight Leggings", miniboss=True, + hidden=True), # Behind illusory wall, Outrider Knight drop + DS3LocationData("GA: Crystal Scroll - 2F late, miniboss drop", "Crystal Scroll", + miniboss=True), # Crystal Sage drop + DS3LocationData("GA: Twinkling Titanite - dark room, lizard #1", "Twinkling Titanite", + lizard=True), + DS3LocationData("GA: Chaos Gem - dark room, lizard", "Chaos Gem", lizard=True), + DS3LocationData("GA: Twinkling Titanite - 1F, lizard by drop", "Twinkling Titanite", + lizard=True), + DS3LocationData("GA: Crystal Gem - 1F, lizard by drop", "Crystal Gem", lizard=True), + DS3LocationData("GA: Twinkling Titanite - 2F, lizard by entrance", "Twinkling Titanite x2", + lizard=True), + DS3LocationData("GA: Titanite Scale - 1F, drop from 2F late onto bookshelves, lizard", + "Titanite Scale x2", lizard=True, hidden=True), # Hidden fall + DS3LocationData("GA: Twinkling Titanite - rooftops, lizard #1", "Twinkling Titanite", + lizard=True), + DS3LocationData("GA: Heavy Gem - rooftops, lizard", "Heavy Gem", lizard=True), + DS3LocationData("GA: Twinkling Titanite - rooftops, lizard #2", "Twinkling Titanite", + lizard=True), + DS3LocationData("GA: Sharp Gem - rooftops, lizard", "Sharp Gem", lizard=True), + DS3LocationData("GA: Twinkling Titanite - up stairs from 4F, lizard", "Twinkling Titanite", + lizard=True), + DS3LocationData("GA: Refined Gem - up stairs from 4F, lizard", "Refined Gem", + lizard=True), + DS3LocationData("GA: Twinkling Titanite - dark room, lizard #2", "Twinkling Titanite x2", + lizard=True), + + # Shrine Handmaid after killing NPCs + DS3LocationData("FS: Faraam Helm - shop after killing GA NPC", "Faraam Helm", + hidden=True, hostile_npc=True, shop=True), + DS3LocationData("FS: Faraam Armor - shop after killing GA NPC", "Faraam Armor", + hidden=True, hostile_npc=True, shop=True), + DS3LocationData("FS: Faraam Gauntlets - shop after killing GA NPC", "Faraam Gauntlets", + hidden=True, hostile_npc=True, shop=True), + DS3LocationData("FS: Faraam Boots - shop after killing GA NPC", "Faraam Boots", + hidden=True, hostile_npc=True, shop=True), + DS3LocationData("FS: Black Hand Hat - shop after killing GA NPC", "Black Hand Hat", + hidden=True, hostile_npc=True, shop=True), + DS3LocationData("FS: Black Hand Armor - shop after killing GA NPC", "Black Hand Armor", + hidden=True, hostile_npc=True, shop=True), + + # Shrine Handmaid after killing Lothric, Younger Prince + DS3LocationData("FS: Lorian's Helm - shop after killing GA boss", "Lorian's Helm", + boss=True, shop=True), + DS3LocationData("FS: Lorian's Armor - shop after killing GA boss", "Lorian's Armor", + boss=True, shop=True), + DS3LocationData("FS: Lorian's Gauntlets - shop after killing GA boss", "Lorian's Gauntlets", + boss=True, shop=True), + DS3LocationData("FS: Lorian's Leggings - shop after killing GA boss", "Lorian's Leggings", + boss=True, shop=True), + + # Sirris quest completion + beat Twin Princes + DS3LocationData("FS: Sunless Talisman - Sirris, kill GA boss", "Sunless Talisman", + missable=True, npc=True), + DS3LocationData("FS: Sunless Veil - shop, Sirris quest, kill GA boss", "Sunless Veil", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Sunless Armor - shop, Sirris quest, kill GA boss", "Sunless Armor", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Sunless Gauntlets - shop, Sirris quest, kill GA boss", + "Sunless Gauntlets", missable=True, npc=True, shop=True), + DS3LocationData("FS: Sunless Leggings - shop, Sirris quest, kill GA boss", + "Sunless Leggings", missable=True, npc=True, shop=True), + + # Unbreakable Patches + DS3LocationData("FS: Hidden Blessing - Patches after searching GA", "Hidden Blessing", + missable=True, npc=True, shop=True), ], "Untended Graves": [ - DS3LocationData("UG: Ashen Estus Ring", "Ashen Estus Ring", DS3LocationCategory.RING), - DS3LocationData("UG: Black Knight Glaive", "Black Knight Glaive", DS3LocationCategory.WEAPON), - DS3LocationData("UG: Hornet Ring", "Hornet Ring", DS3LocationCategory.RING), - DS3LocationData("UG: Chaos Blade", "Chaos Blade", DS3LocationCategory.WEAPON), - DS3LocationData("UG: Blacksmith Hammer", "Blacksmith Hammer", DS3LocationCategory.WEAPON), - DS3LocationData("UG: Eyes of a Fire Keeper", "Eyes of a Fire Keeper", DS3LocationCategory.KEY), - DS3LocationData("UG: Coiled Sword Fragment", "Coiled Sword Fragment", DS3LocationCategory.MISC), - DS3LocationData("UG: Soul of Champion Gundyr", "Soul of Champion Gundyr", DS3LocationCategory.BOSS), + DS3LocationData("UG: Soul of Champion Gundyr", "Soul of Champion Gundyr", prominent=True, + boss=True), + DS3LocationData("UG: Priestess Ring - shop", "Priestess Ring", shop=True), + DS3LocationData("UG: Shriving Stone - swamp, by bonfire", "Shriving Stone"), + DS3LocationData("UG: Titanite Chunk - swamp, left path by fountain", "Titanite Chunk"), + DS3LocationData("UG: Soul of a Crestfallen Knight - swamp, center", + "Soul of a Crestfallen Knight"), + DS3LocationData("UG: Titanite Chunk - swamp, right path by fountain", "Titanite Chunk"), + DS3LocationData("UG: Ashen Estus Ring - swamp, path opposite bonfire", "Ashen Estus Ring"), + DS3LocationData("UG: Black Knight Glaive - boss arena", "Black Knight Glaive"), + DS3LocationData("UG: Hidden Blessing - cemetery, behind coffin", "Hidden Blessing"), + DS3LocationData("UG: Eyes of a Fire Keeper - shrine, Irina's room", "Eyes of a Fire Keeper", + hidden=True), # Illusory wall + DS3LocationData("UG: Soul of a Crestfallen Knight - environs, above shrine entrance", + "Soul of a Crestfallen Knight"), + DS3LocationData("UG: Blacksmith Hammer - shrine, Andre's room", "Blacksmith Hammer"), + DS3LocationData("UG: Chaos Blade - environs, left of shrine", "Chaos Blade"), + DS3LocationData("UG: Hornet Ring - environs, right of main path after killing FK boss", + "Hornet Ring", conditional=True), + DS3LocationData("UG: Coiled Sword Fragment - shrine, dead bonfire", "Coiled Sword Fragment", + boss=True), + DS3LocationData("UG: Life Ring+3 - shrine, behind big throne", "Life Ring+3", ngp=True), + DS3LocationData("UG: Ring of Steel Protection+1 - environs, behind bell tower", + "Ring of Steel Protection+1", ngp=True), + + # Yuria shop, or Shrine Handmaiden with Hollow's Ashes + # This is here because this is where the ashes end up if you kill Yoel or Yuria + DS3LocationData("FS: Ring of Sacrifice - Yuria shop", "Ring of Sacrifice", + static='99,0:-1:40000,110000,70000107,70000116:', npc=True, + shop=True), + + # Untended Graves Handmaid + # All shop items are missable because she can be killed, except Priestess ring because she + # drops it on death anyway. + DS3LocationData("UG: Ember - shop", "Ember", shop=True, missable=True), + # Untended Graves Handmaid after killing Abyss Watchers + DS3LocationData("UG: Wolf Knight Helm - shop after killing FK boss", "Wolf Knight Helm", + boss=True, shop=True, conditional=True, + missable=True), + DS3LocationData("UG: Wolf Knight Armor - shop after killing FK boss", + "Wolf Knight Armor", boss=True, shop=True, missable=True), + DS3LocationData("UG: Wolf Knight Gauntlets - shop after killing FK boss", + "Wolf Knight Gauntlets", boss=True, shop=True, missable=True), + DS3LocationData("UG: Wolf Knight Leggings - shop after killing FK boss", + "Wolf Knight Leggings", boss=True, shop=True, missable=True), + + # Shrine Handmaid after killing Champion Gundyr + DS3LocationData("FS: Gundyr's Helm - shop after killing UG boss", "Gundyr's Helm", + boss=True, shop=True), + DS3LocationData("FS: Gundyr's Armor - shop after killing UG boss", "Gundyr's Armor", + boss=True, shop=True), + DS3LocationData("FS: Gundyr's Gauntlets - shop after killing UG boss", "Gundyr's Gauntlets", + boss=True, shop=True), + DS3LocationData("FS: Gundyr's Leggings - shop after killing UG boss", "Gundyr's Leggings", + boss=True, shop=True), ], "Archdragon Peak": [ - DS3LocationData("AP: Lightning Clutch Ring", "Lightning Clutch Ring", DS3LocationCategory.RING), - DS3LocationData("AP: Ancient Dragon Greatshield", "Ancient Dragon Greatshield", DS3LocationCategory.SHIELD), - DS3LocationData("AP: Ring of Steel Protection", "Ring of Steel Protection", DS3LocationCategory.RING), - DS3LocationData("AP: Calamity Ring", "Calamity Ring", DS3LocationCategory.RING), - DS3LocationData("AP: Drakeblood Greatsword", "Drakeblood Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("AP: Dragonslayer Spear", "Dragonslayer Spear", DS3LocationCategory.WEAPON), - DS3LocationData("AP: Thunder Stoneplate Ring", "Thunder Stoneplate Ring", DS3LocationCategory.RING), - DS3LocationData("AP: Great Magic Barrier", "Great Magic Barrier", DS3LocationCategory.SPELL), - DS3LocationData("AP: Dragon Chaser's Ashes", "Dragon Chaser's Ashes", DS3LocationCategory.MISC), - DS3LocationData("AP: Twinkling Dragon Torso Stone", "Twinkling Dragon Torso Stone", DS3LocationCategory.MISC), - DS3LocationData("AP: Dragonslayer Helm", "Dragonslayer Helm", DS3LocationCategory.ARMOR), - DS3LocationData("AP: Dragonslayer Armor", "Dragonslayer Armor", DS3LocationCategory.ARMOR), - DS3LocationData("AP: Dragonslayer Gauntlets", "Dragonslayer Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("AP: Dragonslayer Leggings", "Dragonslayer Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("AP: Ricard's Rapier", "Ricard's Rapier", DS3LocationCategory.WEAPON), - DS3LocationData("AP: Soul of the Nameless King", "Soul of the Nameless King", DS3LocationCategory.BOSS), - DS3LocationData("AP: Dragon Tooth", "Dragon Tooth", DS3LocationCategory.WEAPON), - DS3LocationData("AP: Havel's Greatshield", "Havel's Greatshield", DS3LocationCategory.SHIELD), + DS3LocationData("AP: Dragon Head Stone - fort, boss drop", "Dragon Head Stone", + prominent=True, boss=True), + DS3LocationData("AP: Soul of the Nameless King", "Soul of the Nameless King", + prominent=True, boss=True), + DS3LocationData("AP: Dragon Tooth - belfry roof, NPC drop", "Dragon Tooth", + hostile_npc=True), # Havel Knight drop + DS3LocationData("AP: Havel's Greatshield - belfry roof, NPC drop", "Havel's Greatshield", + hostile_npc=True), # Havel Knight drop + DS3LocationData("AP: Drakeblood Greatsword - mausoleum, NPC drop", "Drakeblood Greatsword", + hostile_npc=True), + DS3LocationData("AP: Ricard's Rapier - belfry, NPC drop", "Ricard's Rapier", + hostile_npc=True), + DS3LocationData("AP: Lightning Clutch Ring - intro, left of boss door", + "Lightning Clutch Ring"), + DS3LocationData("AP: Stalk Dung Pie - fort overlook", "Stalk Dung Pie x6"), + DS3LocationData("AP: Titanite Chunk - fort, second room balcony", "Titanite Chunk"), + DS3LocationData("AP: Titanite Scale - mausoleum, downstairs balcony #1", + "Titanite Scale"), + DS3LocationData("AP: Soul of a Weary Warrior - intro, first cliff edge", + "Soul of a Weary Warrior"), + DS3LocationData("AP: Titanite Chunk - intro, left before archway", "Titanite Chunk"), + DS3LocationData("AP: Lightning Gem - intro, side rise", "Lightning Gem"), + DS3LocationData("AP: Homeward Bone - intro, path to bonfire", "Homeward Bone x2"), + DS3LocationData("AP: Soul of a Nameless Soldier - intro, right before archway", + "Soul of a Nameless Soldier"), + DS3LocationData("AP: Titanite Chunk - intro, archway corner", "Titanite Chunk"), + DS3LocationData("AP: Ember - fort overlook #1", "Ember"), + DS3LocationData("AP: Large Soul of a Weary Warrior - fort, center", + "Large Soul of a Weary Warrior"), + DS3LocationData("AP: Large Soul of a Nameless Soldier - fort, by stairs to first room", + "Large Soul of a Nameless Soldier"), + DS3LocationData("AP: Lightning Urn - fort, left of first room entrance", + "Lightning Urn x4"), + DS3LocationData("AP: Lightning Bolt - rotunda", "Lightning Bolt x12"), + DS3LocationData("AP: Titanite Chunk - rotunda", "Titanite Chunk x2"), + # Not 100% sure about this location name, can't find this on any maps + DS3LocationData("AP: Dung Pie - fort, landing after second room", "Dung Pie x3"), + DS3LocationData("AP: Titanite Scale - mausoleum, downstairs balcony #2", "Titanite Scale"), + DS3LocationData("AP: Soul of a Weary Warrior - walkway, building window", + "Soul of a Weary Warrior"), + DS3LocationData("AP: Soul of a Crestfallen Knight - mausoleum, upstairs", + "Soul of a Crestfallen Knight"), + DS3LocationData("AP: Titanite Chunk - intro, behind rock", "Titanite Chunk"), + DS3LocationData("AP: Ember - fort overlook #2", "Ember"), + DS3LocationData("AP: Thunder Stoneplate Ring - walkway, up ladder", + "Thunder Stoneplate Ring"), + DS3LocationData("AP: Titanite Scale - mausoleum, upstairs balcony", "Titanite Scale"), + DS3LocationData("AP: Ember - belfry, below bell", "Ember"), + DS3LocationData("AP: Ancient Dragon Greatshield - intro, on archway", + "Ancient Dragon Greatshield"), + DS3LocationData("AP: Large Soul of a Crestfallen Knight - summit, by fountain", + "Large Soul of a Crestfallen Knight"), + DS3LocationData("AP: Dragon Chaser's Ashes - summit, side path", "Dragon Chaser's Ashes", + progression=True), + DS3LocationData("AP: Ember - intro, by bonfire", "Ember"), + DS3LocationData("AP: Dragonslayer Spear - gate after mausoleum", "Dragonslayer Spear"), + DS3LocationData("AP: Dragonslayer Helm - plaza", "Dragonslayer Helm"), + DS3LocationData("AP: Dragonslayer Armor - plaza", "Dragonslayer Armor"), + DS3LocationData("AP: Dragonslayer Gauntlets - plaza", "Dragonslayer Gauntlets"), + DS3LocationData("AP: Dragonslayer Leggings - plaza", "Dragonslayer Leggings"), + DS3LocationData("AP: Twinkling Titanite - fort, end of rafters", "Twinkling Titanite x2"), + DS3LocationData("AP: Twinkling Titanite - fort, down second room balcony ladder", + "Twinkling Titanite x2"), + DS3LocationData("AP: Titanite Slab - belfry roof", "Titanite Slab"), + DS3LocationData("AP: Great Magic Barrier - drop off belfry roof", "Great Magic Barrier", + hidden=True), # Hidden fall + DS3LocationData("AP: Titanite Slab - plaza", "Titanite Slab"), + DS3LocationData("AP: Ring of Steel Protection - fort overlook, beside stairs", + "Ring of Steel Protection"), + DS3LocationData("AP: Havel's Ring+1 - summit, after building", "Havel's Ring+1", + ngp=True), + DS3LocationData("AP: Covetous Gold Serpent Ring+2 - plaza", "Covetous Gold Serpent Ring+2", + ngp=True), + DS3LocationData("AP: Titanite Scale - walkway building", "Titanite Scale x3"), + DS3LocationData("AP: Twinkling Titanite - belfry, by ladder to roof", + "Twinkling Titanite x3"), + DS3LocationData("AP: Twinkling Dragon Torso Stone - summit, gesture at altar", + "Twinkling Dragon Torso Stone", hidden=True), # Requires gesture + DS3LocationData("AP: Calamity Ring - mausoleum, gesture at altar", "Calamity Ring", + hidden=True), # Requires gesture + DS3LocationData("AP: Twinkling Titanite - walkway building, lizard", + "Twinkling Titanite x3", lizard=True), + DS3LocationData("AP: Titanite Chunk - walkway, miniboss drop", "Titanite Chunk x6", + miniboss=True), # Wyvern miniboss drop + DS3LocationData("AP: Titanite Scale - walkway, miniboss drop", "Titanite Scale x3", + miniboss=True), # Wyvern miniboss drop + DS3LocationData("AP: Twinkling Titanite - walkway, miniboss drop", "Twinkling Titanite x3", + miniboss=True), # Wyvern miniboss drop + DS3LocationData("FS: Hawkwood's Swordgrass - Andre after gesture in AP summit", + "Hawkwood's Swordgrass", conditional=True, hidden=True), + + # Shrine Handmaid after killing Nameless King + DS3LocationData("FS: Golden Crown - shop after killing AP boss", "Golden Crown", + boss=True, shop=True), + DS3LocationData("FS: Dragonscale Armor - shop after killing AP boss", "Dragonscale Armor", + boss=True, shop=True), + DS3LocationData("FS: Golden Bracelets - shop after killing AP boss", "Golden Bracelets", + boss=True, shop=True), + DS3LocationData("FS: Dragonscale Waistcloth - shop after killing AP boss", + "Dragonscale Waistcloth", boss=True, shop=True), + DS3LocationData("FK: Twinkling Dragon Head Stone - Hawkwood drop", + "Twinkling Dragon Head Stone", missable=True, + npc=True), # Hawkwood (quest) + ], + "Kiln of the First Flame": [ + DS3LocationData("KFF: Soul of the Lords", "Soul of the Lords", boss=True), + + # Shrine Handmaid after placing all Cinders of a Lord + DS3LocationData("FS: Titanite Slab - shop after placing all Cinders", "Titanite Slab", + static='99,0:-1:9210,110000:', hidden=True), + DS3LocationData("FS: Firelink Helm - shop after placing all Cinders", "Firelink Helm", + boss=True, shop=True), + DS3LocationData("FS: Firelink Armor - shop after placing all Cinders", "Firelink Armor", + boss=True, shop=True), + DS3LocationData("FS: Firelink Gauntlets - shop after placing all Cinders", + "Firelink Gauntlets", boss=True, shop=True), + DS3LocationData("FS: Firelink Leggings - shop after placing all Cinders", + "Firelink Leggings", boss=True, shop=True), + + # Yuria (quest, after Soul of Cinder) + DS3LocationData("FS: Billed Mask - Yuria after killing KFF boss", "Billed Mask", + missable=True, npc=True), + DS3LocationData("FS: Black Dress - Yuria after killing KFF boss", "Black Dress", + missable=True, npc=True), + DS3LocationData("FS: Black Gauntlets - Yuria after killing KFF boss", "Black Gauntlets", + missable=True, npc=True), + DS3LocationData("FS: Black Leggings - Yuria after killing KFF boss", "Black Leggings", + missable=True, npc=True), ], - "Kiln of the First Flame": [], # DLC - "Painted World of Ariandel 1": [ - DS3LocationData("PW: Follower Javelin", "Follower Javelin", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Frozen Weapon", "Frozen Weapon", DS3LocationCategory.SPELL), - DS3LocationData("PW: Millwood Greatbow", "Millwood Greatbow", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Captain's Ashes", "Captain's Ashes", DS3LocationCategory.MISC), - DS3LocationData("PW: Millwood Battle Axe", "Millwood Battle Axe", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Ethereal Oak Shield", "Ethereal Oak Shield", DS3LocationCategory.SHIELD), - DS3LocationData("PW: Crow Quills", "Crow Quills", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Slave Knight Hood", "Slave Knight Hood", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Slave Knight Armor", "Slave Knight Armor", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Slave Knight Gauntlets", "Slave Knight Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Slave Knight Leggings", "Slave Knight Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Way of White Corona", "Way of White Corona", DS3LocationCategory.SPELL), - DS3LocationData("PW: Crow Talons", "Crow Talons", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Onyx Blade", "Onyx Blade", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Contraption Key", "Contraption Key", DS3LocationCategory.KEY), + "Painted World of Ariandel (Before Contraption)": [ + DS3LocationData("PW1: Valorheart - boss drop", "Valorheart", prominent=True, boss=True), + DS3LocationData("PW1: Contraption Key - library, NPC drop", "Contraption Key", + prominent=True, progression=True, + hostile_npc=True), # Sir Vilhelm drop + DS3LocationData("PW1: Onyx Blade - library, NPC drop", "Onyx Blade", + hostile_npc=True), # Sir Vilhelm drop + DS3LocationData("PW1: Chillbite Ring - Friede", "Chillbite Ring", + npc=True), # Friede conversation + DS3LocationData("PW1: Rime-blue Moss Clump - snowfield upper, starting cave", + "Rime-blue Moss Clump x2"), + DS3LocationData("PW1: Poison Gem - snowfield upper, forward from bonfire", "Poison Gem"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - snowfield lower, path back up", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Follower Javelin - snowfield lower, path back up", "Follower Javelin"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - snowfield lower, path to village", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Homeward Bone - snowfield village, outcropping", "Homeward Bone x6"), + DS3LocationData("PW1: Blessed Gem - snowfield, behind tower", "Blessed Gem", + hidden=True), # Hidden behind a tower + DS3LocationData("PW1: Captain's Ashes - snowfield tower, 6F", "Captain's Ashes", + progression=True), + DS3LocationData("PW1: Black Firebomb - snowfield lower, path to bonfire", + "Black Firebomb x2"), + DS3LocationData("PW1: Shriving Stone - below bridge near", "Shriving Stone"), + DS3LocationData("PW1: Millwood Greatarrow - snowfield village, loop back to lower", + "Millwood Greatarrow x5"), + DS3LocationData("PW1: Millwood Greatbow - snowfield village, loop back to lower", + "Millwood Greatbow"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - snowfield upper", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Rusted Coin - snowfield lower, straight from fall", "Rusted Coin"), + DS3LocationData("PW1: Large Titanite Shard - snowfield lower, left from fall", + "Large Titanite Shard"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - settlement courtyard, cliff", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Crow Quills - settlement loop, jump into courtyard", "Crow Quills", + hidden=True), # Hidden fall + DS3LocationData("PW1: Simple Gem - settlement, lowest level, behind gate", "Simple Gem"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - settlement, by ladder to bonfire", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Slave Knight Hood - settlement roofs, drop by ladder", + "Slave Knight Hood"), + DS3LocationData("PW1: Slave Knight Armor - settlement roofs, drop by ladder", + "Slave Knight Armor"), + DS3LocationData("PW1: Slave Knight Gauntlets - settlement roofs, drop by ladder", + "Slave Knight Gauntlets"), + DS3LocationData("PW1: Slave Knight Leggings - settlement roofs, drop by ladder", + "Slave Knight Leggings"), + DS3LocationData("PW1: Ember - settlement main, left building after bridge", "Ember"), + DS3LocationData("PW1: Dark Gem - settlement back, egg building", "Dark Gem"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - settlement roofs, balcony", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - settlement loop, by bonfire", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Rusted Gold Coin - settlement roofs, roof near second ladder", + "Rusted Gold Coin x3"), + DS3LocationData("PW1: Soul of a Crestfallen Knight - settlement hall, rafters", + "Soul of a Crestfallen Knight"), + DS3LocationData("PW1: Way of White Corona - settlement hall, by altar", + "Way of White Corona"), + DS3LocationData("PW1: Rusted Coin - right of library", "Rusted Coin x2"), + DS3LocationData("PW1: Young White Branch - right of library", "Young White Branch"), + DS3LocationData("PW1: Budding Green Blossom - settlement courtyard, ledge", + "Budding Green Blossom x3"), + DS3LocationData("PW1: Crow Talons - settlement roofs, near bonfire", "Crow Talons"), + DS3LocationData("PW1: Hollow Gem - beside chapel", "Hollow Gem"), + DS3LocationData("PW1: Rime-blue Moss Clump - below bridge far", "Rime-blue Moss Clump x4"), + DS3LocationData("PW1: Follower Sabre - roots above depths", "Follower Sabre"), + DS3LocationData("PW1: Ember - roots above depths", "Ember"), + DS3LocationData("PW1: Snap Freeze - depths, far end, mob drop", "Snap Freeze", drop=True, + hidden=True), # Guaranteed drop from normal-looking Tree Woman + DS3LocationData("PW1: Rime-blue Moss Clump - snowfield upper, overhang", + "Rime-blue Moss Clump"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - snowfield lower, by cliff", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Ember - settlement, building near bonfire", "Ember"), + DS3LocationData("PW1: Frozen Weapon - snowfield lower, egg zone", "Frozen Weapon"), + DS3LocationData("PW1: Titanite Slab - depths, up secret ladder", "Titanite Slab", + static='11,0:54500640::', + hidden=True), # Must kill normal-looking Tree Woman + DS3LocationData("PW1: Homeward Bone - depths, up hill", "Homeward Bone x2"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - below snowfield village overhang", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Large Soul of a Weary Warrior - settlement hall roof", + "Large Soul of a Weary Warrior"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - settlement back", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Heavy Gem - snowfield village", "Heavy Gem"), + DS3LocationData("PW1: Large Soul of a Weary Warrior - snowfield tower, 6F", + "Large Soul of a Weary Warrior"), + DS3LocationData("PW1: Millwood Battle Axe - snowfield tower, 5F", "Millwood Battle Axe"), + DS3LocationData("PW1: Ethereal Oak Shield - snowfield tower, 3F", "Ethereal Oak Shield"), + DS3LocationData("PW1: Soul of a Weary Warrior - snowfield tower, 1F", + "Soul of a Weary Warrior"), + DS3LocationData("PW1: Twinkling Titanite - snowfield tower, 3F lizard", + "Twinkling Titanite", lizard=True), + DS3LocationData("PW1: Large Titanite Shard - lizard under bridge near", + "Large Titanite Shard", lizard=True), + DS3LocationData("PW1: Twinkling Titanite - roots, lizard", "Twinkling Titanite", + lizard=True), + DS3LocationData("PW1: Twinkling Titanite - settlement roofs, lizard before hall", + "Twinkling Titanite", lizard=True), + DS3LocationData("PW1: Large Titanite Shard - settlement loop, lizard", + "Large Titanite Shard x2", lizard=True), ], - "Painted World of Ariandel 2": [ - DS3LocationData("PW: Quakestone Hammer", "Quakestone Hammer", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Earth Seeker", "Earth Seeker", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Follower Torch", "Follower Torch", DS3LocationCategory.SHIELD), - DS3LocationData("PW: Follower Shield", "Follower Shield", DS3LocationCategory.SHIELD), - DS3LocationData("PW: Follower Sabre", "Follower Sabre", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Snap Freeze", "Snap Freeze", DS3LocationCategory.SPELL), - DS3LocationData("PW: Floating Chaos", "Floating Chaos", DS3LocationCategory.SPELL), - DS3LocationData("PW: Pyromancer's Parting Flame", "Pyromancer's Parting Flame", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Vilhelm's Helm", "Vilhelm's Helm", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Vilhelm's Armor", "Vilhelm's Armor", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Vilhelm's Gauntlets", "Vilhelm's Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Vilhelm's Leggings", "Vilhelm's Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Valorheart", "Valorheart", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Champion's Bones", "Champion's Bones", DS3LocationCategory.MISC), - DS3LocationData("PW: Soul of Sister Friede", "Soul of Sister Friede", DS3LocationCategory.BOSS), - DS3LocationData("PW: Chillbite Ring", "Chillbite Ring", DS3LocationCategory.RING), + "Painted World of Ariandel (After Contraption)": [ + DS3LocationData("PW2: Soul of Sister Friede", "Soul of Sister Friede", prominent=True, + boss=True), + DS3LocationData("PW2: Titanite Slab - boss drop", "Titanite Slab", + static='11,0:50004700::', + boss=True), # One-time drop after Friede Phase 2 + DS3LocationData("PW2: Floating Chaos - NPC drop", "Floating Chaos", hostile_npc=True, + hidden=True), # Livid Pyromancer Dunnel drop (requires ember) + DS3LocationData("PW2: Prism Stone - pass, tree by beginning", "Prism Stone x10"), + DS3LocationData("PW2: Titanite Chunk - pass, cliff overlooking bonfire", "Titanite Chunk"), + DS3LocationData("PW2: Titanite Chunk - pass, by kickable tree", "Titanite Chunk"), + DS3LocationData("PW2: Follower Shield - pass, far cliffside", "Follower Shield"), + DS3LocationData("PW2: Large Titanite Shard - pass, just before B1", + "Large Titanite Shard x2"), + DS3LocationData("PW2: Quakestone Hammer - pass, side path near B1", "Quakestone Hammer"), + DS3LocationData("PW2: Ember - pass, central alcove", "Ember"), + DS3LocationData("PW2: Large Titanite Shard - pass, far side path", + "Large Titanite Shard x2"), + DS3LocationData("PW2: Soul of a Crestfallen Knight - pit edge #1", + "Soul of a Crestfallen Knight"), + DS3LocationData("PW2: Soul of a Crestfallen Knight - pit edge #2", + "Soul of a Crestfallen Knight"), + DS3LocationData("PW2: Large Soul of a Crestfallen Knight - pit, by tree", + "Large Soul of a Crestfallen Knight"), + DS3LocationData("PW2: Earth Seeker - pit cave", "Earth Seeker"), + DS3LocationData("PW2: Follower Torch - pass, far side path", "Follower Torch"), + DS3LocationData("PW2: Dung Pie - B1", "Dung Pie x2"), + DS3LocationData("PW2: Vilhelm's Helm", "Vilhelm's Helm"), + DS3LocationData("PW2: Vilhelm's Armor - B2, along wall", "Vilhelm's Armor"), + DS3LocationData("PW2: Vilhelm's Gauntlets - B2, along wall", "Vilhelm's Gauntlets"), + DS3LocationData("PW2: Vilhelm's Leggings - B2, along wall", "Vilhelm's Leggings"), + DS3LocationData("PW2: Blood Gem - B2, center", "Blood Gem"), + DS3LocationData("PW2: Pyromancer's Parting Flame - rotunda", + "Pyromancer's Parting Flame", hidden=True), # Behind illusory wall + DS3LocationData("PW2: Homeward Bone - rotunda", "Homeward Bone x2", + hidden=True), # Behind illusory wall + DS3LocationData("PW2: Twinkling Titanite - B3, lizard #1", "Twinkling Titanite", + lizard=True), + DS3LocationData("PW2: Twinkling Titanite - B3, lizard #2", "Twinkling Titanite", + lizard=True), + + # Corvian Settler after killing Friede + DS3LocationData("PW1: Titanite Slab - Corvian", "Titanite Slab", npc=True), + + # Shrine Handmaid after killing Sister Friede + DS3LocationData("FS: Ordained Hood - shop after killing PW2 boss", "Ordained Hood", + boss=True, shop=True), + DS3LocationData("FS: Ordained Dress - shop after killing PW2 boss", "Ordained Dress", + boss=True, shop=True), + DS3LocationData("FS: Ordained Trousers - shop after killing PW2 boss", "Ordained Trousers", + boss=True, shop=True), ], "Dreg Heap": [ - DS3LocationData("DH: Loincloth", "Loincloth", DS3LocationCategory.ARMOR), - DS3LocationData("DH: Aquamarine Dagger", "Aquamarine Dagger", DS3LocationCategory.WEAPON), - DS3LocationData("DH: Murky Hand Scythe", "Murky Hand Scythe", DS3LocationCategory.WEAPON), - DS3LocationData("DH: Murky Longstaff", "Murky Longstaff", DS3LocationCategory.WEAPON), - DS3LocationData("DH: Great Soul Dregs", "Great Soul Dregs", DS3LocationCategory.SPELL), - DS3LocationData("DH: Lothric War Banner", "Lothric War Banner", DS3LocationCategory.WEAPON), - DS3LocationData("DH: Projected Heal", "Projected Heal", DS3LocationCategory.SPELL), - DS3LocationData("DH: Desert Pyromancer Hood", "Desert Pyromancer Hood", DS3LocationCategory.ARMOR), - DS3LocationData("DH: Desert Pyromancer Garb", "Desert Pyromancer Garb", DS3LocationCategory.ARMOR), - DS3LocationData("DH: Desert Pyromancer Gloves", "Desert Pyromancer Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("DH: Desert Pyromancer Skirt", "Desert Pyromancer Skirt", DS3LocationCategory.ARMOR), - DS3LocationData("DH: Giant Door Shield", "Giant Door Shield", DS3LocationCategory.SHIELD), - DS3LocationData("DH: Herald Curved Greatsword", "Herald Curved Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("DH: Flame Fan", "Flame Fan", DS3LocationCategory.SPELL), - DS3LocationData("DH: Soul of the Demon Prince", "Soul of the Demon Prince", DS3LocationCategory.BOSS), - DS3LocationData("DH: Small Envoy Banner", "Small Envoy Banner", DS3LocationCategory.KEY), - DS3LocationData("DH: Ring of Favor+3", "Ring of Favor+3", DS3LocationCategory.RING), - DS3LocationData("DH: Covetous Silver Serpent Ring+3", "Covetous Silver Serpent Ring+3", DS3LocationCategory.RING), - DS3LocationData("DH: Ring of Steel Protection+3", "Ring of Steel Protection+3", DS3LocationCategory.RING), + DS3LocationData("DH: Soul of the Demon Prince", "Soul of the Demon Prince", + prominent=True, boss=True), + DS3LocationData("DH: Siegbräu - Lapp", "Siegbräu", missable=True, drop=True, + npc=True), # Lapp (quest or kill) + DS3LocationData("DH: Flame Fan - swamp upper, NPC drop", "Flame Fan", + hostile_npc=True), # Desert Pyromancer Zoey drop + DS3LocationData("DH: Ember - castle, behind spire", "Ember"), + DS3LocationData("DH: Soul of a Weary Warrior - castle overhang", "Soul of a Weary Warrior"), + DS3LocationData("DH: Titanite Chunk - castle, up stairs", "Titanite Chunk"), + DS3LocationData("DH: Aquamarine Dagger - castle, up stairs", "Aquamarine Dagger"), + DS3LocationData("DH: Twinkling Titanite - library, chandelier", "Twinkling Titanite"), + DS3LocationData("DH: Murky Hand Scythe - library, behind bookshelves", "Murky Hand Scythe"), + DS3LocationData("DH: Divine Blessing - library, after drop", "Divine Blessing"), + DS3LocationData("DH: Ring of Steel Protection+3 - ledge before church", + "Ring of Steel Protection+3"), + DS3LocationData("DH: Soul of a Crestfallen Knight - church, altar", + "Soul of a Crestfallen Knight"), + DS3LocationData("DH: Rusted Coin - behind fountain after church", "Rusted Coin x2"), + DS3LocationData("DH: Titanite Chunk - pantry, first room", "Titanite Chunk"), + DS3LocationData("DH: Murky Longstaff - pantry, last room", "Murky Longstaff"), + DS3LocationData("DH: Ember - pantry, behind crates just before upstairs", "Ember", + hidden=True), # Behind illusory wall + DS3LocationData("DH: Great Soul Dregs - pantry upstairs", "Great Soul Dregs", + hidden=True), # Behind illusory wall + DS3LocationData("DH: Covetous Silver Serpent Ring+3 - pantry upstairs, drop down", + "Covetous Silver Serpent Ring+3", hidden=True), # Behind illusory wall + DS3LocationData("DH: Titanite Chunk - path from church, by pillar", "Titanite Chunk"), + DS3LocationData("DH: Homeward Bone - end of path from church", "Homeward Bone x3"), + DS3LocationData("DH: Lightning Urn - wall outside church", "Lightning Urn x4"), + DS3LocationData("DH: Projected Heal - parapets balcony", "Projected Heal"), + DS3LocationData("DH: Large Soul of a Weary Warrior - parapets, hall", + "Large Soul of a Weary Warrior"), + DS3LocationData("DH: Lothric War Banner - parapets, end of hall", "Lothric War Banner"), + DS3LocationData("DH: Titanite Scale - library, back of room", "Titanite Scale"), + DS3LocationData("DH: Black Firebomb - ruins, up windmill from bonfire", "Black Firebomb x4"), + DS3LocationData("DH: Titanite Chunk - ruins, path from bonfire", "Titanite Chunk"), + DS3LocationData("DH: Twinkling Titanite - ruins, root near bonfire", "Twinkling Titanite"), + DS3LocationData("DH: Desert Pyromancer Garb - ruins, by shack near cliff", + "Desert Pyromancer Garb"), + DS3LocationData("DH: Titanite Chunk - ruins, by far shack", "Titanite Chunk x2"), + DS3LocationData("DH: Giant Door Shield - ruins, path below far shack", "Giant Door Shield"), + DS3LocationData("DH: Ember - ruins, alcove before swamp", "Ember"), + DS3LocationData("DH: Desert Pyromancer Gloves - swamp, far right", + "Desert Pyromancer Gloves"), + DS3LocationData("DH: Desert Pyromancer Skirt - swamp right, by roots", + "Desert Pyromancer Skirt"), + DS3LocationData("DH: Titanite Scale - swamp upper, drop and jump into tower", + "Titanite Scale"), + DS3LocationData("DH: Purple Moss Clump - swamp shack", "Purple Moss Clump x4"), + DS3LocationData("DH: Ring of Favor+3 - swamp right, up root", "Ring of Favor+3"), + DS3LocationData("DH: Titanite Chunk - swamp right, drop partway up root", "Titanite Chunk"), + DS3LocationData("DH: Large Soul of a Weary Warrior - swamp, under overhang", + "Large Soul of a Weary Warrior"), + DS3LocationData("DH: Titanite Slab - swamp, path under overhang", "Titanite Slab"), + DS3LocationData("DH: Titanite Chunk - swamp, along buildings", "Titanite Chunk"), + DS3LocationData("DH: Loincloth - swamp, left edge", "Loincloth"), + DS3LocationData("DH: Titanite Chunk - swamp, path to upper", "Titanite Chunk"), + DS3LocationData("DH: Large Soul of a Weary Warrior - swamp center", + "Large Soul of a Weary Warrior"), + DS3LocationData("DH: Harald Curved Greatsword - swamp left, under root", + "Harald Curved Greatsword"), + DS3LocationData("DH: Homeward Bone - swamp left, on root", "Homeward Bone"), + DS3LocationData("DH: Prism Stone - swamp upper, tunnel start", "Prism Stone x6"), + DS3LocationData("DH: Desert Pyromancer Hood - swamp upper, tunnel end", + "Desert Pyromancer Hood"), + DS3LocationData("DH: Twinkling Titanite - swamp upper, drop onto root", + "Twinkling Titanite", hidden=True), # Hidden fall + DS3LocationData("DH: Divine Blessing - swamp upper, building roof", "Divine Blessing"), + DS3LocationData("DH: Ember - ruins, alcove on cliff", "Ember", hidden=True), # Hidden fall + DS3LocationData("DH: Small Envoy Banner - boss drop", "Small Envoy Banner", + progression=True, boss=True), + DS3LocationData("DH: Twinkling Titanite - ruins, alcove on cliff, mob drop", + "Twinkling Titanite x2", drop=True, + hidden=True), # Hidden fall, also guaranteed drop from killing normal-looking pilgrim + DS3LocationData("DH: Twinkling Titanite - swamp upper, mob drop on roof", + "Twinkling Titanite x2", drop=True, + hidden=True), # Hidden fall, also guaranteed drop from killing normal-looking pilgrim + DS3LocationData("DH: Twinkling Titanite - path after church, mob drop", + "Twinkling Titanite x2", drop=True, + hidden=True), # Guaranteed drop from killing normal-looking pilgrim + + # Stone-humped Hag's shop + DS3LocationData("DH: Splitleaf Greatsword - shop", "Splitleaf Greatsword", shop=True), + DS3LocationData("DH: Divine Blessing - shop", "Divine Blessing", shop=True), + DS3LocationData("DH: Hidden Blessing - shop", "Hidden Blessing", shop=True), + DS3LocationData("DH: Rusted Gold Coin - shop", "Rusted Gold Coin", shop=True), + DS3LocationData("DH: Ember - shop", "Ember", shop=True), ], "Ringed City": [ - DS3LocationData("RC: Ruin Sentinel Helm", "Ruin Sentinel Helm", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Ruin Sentinel Armor", "Ruin Sentinel Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Ruin Sentinel Gauntlets", "Ruin Sentinel Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Ruin Sentinel Leggings", "Ruin Sentinel Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Black Witch Veil", "Black Witch Veil", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Black Witch Hat", "Black Witch Hat", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Black Witch Garb", "Black Witch Garb", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Black Witch Wrappings", "Black Witch Wrappings", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Black Witch Trousers", "Black Witch Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RC: White Preacher Head", "White Preacher Head", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Havel's Ring+3", "Havel's Ring+3", DS3LocationCategory.RING), - DS3LocationData("RC: Ringed Knight Spear", "Ringed Knight Spear", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Dragonhead Shield", "Dragonhead Shield", DS3LocationCategory.SHIELD), - DS3LocationData("RC: Ringed Knight Straight Sword", "Ringed Knight Straight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Preacher's Right Arm", "Preacher's Right Arm", DS3LocationCategory.WEAPON), - DS3LocationData("RC: White Birch Bow", "White Birch Bow", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Church Guardian Shiv", "Church Guardian Shiv", DS3LocationCategory.MISC), - DS3LocationData("RC: Dragonhead Greatshield", "Dragonhead Greatshield", DS3LocationCategory.SHIELD), - DS3LocationData("RC: Ringed Knight Paired Greatswords", "Ringed Knight Paired Greatswords", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Shira's Crown", "Shira's Crown", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Shira's Armor", "Shira's Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Shira's Gloves", "Shira's Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Shira's Trousers", "Shira's Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Crucifix of the Mad King", "Crucifix of the Mad King", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Sacred Chime of Filianore", "Sacred Chime of Filianore", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Iron Dragonslayer Helm", "Iron Dragonslayer Helm", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Iron Dragonslayer Armor", "Iron Dragonslayer Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Iron Dragonslayer Gauntlets", "Iron Dragonslayer Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Iron Dragonslayer Leggings", "Iron Dragonslayer Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Lightning Arrow", "Lightning Arrow", DS3LocationCategory.SPELL), - DS3LocationData("RC: Ritual Spear Fragment", "Ritual Spear Fragment", DS3LocationCategory.MISC), - DS3LocationData("RC: Antiquated Plain Garb", "Antiquated Plain Garb", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Violet Wrappings", "Violet Wrappings", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Soul of Darkeater Midir", "Soul of Darkeater Midir", DS3LocationCategory.BOSS), - DS3LocationData("RC: Soul of Slave Knight Gael", "Soul of Slave Knight Gael", DS3LocationCategory.BOSS), - DS3LocationData("RC: Blood of the Dark Soul", "Blood of the Dark Soul", DS3LocationCategory.KEY), - DS3LocationData("RC: Chloranthy Ring+3", "Chloranthy Ring+3", DS3LocationCategory.RING), - DS3LocationData("RC: Covetous Gold Serpent Ring+3", "Covetous Gold Serpent Ring+3", DS3LocationCategory.RING), - DS3LocationData("RC: Ring of the Evil Eye+3", "Ring of the Evil Eye+3", DS3LocationCategory.RING), - DS3LocationData("RC: Wolf Ring+3", "Wolf Ring+3", DS3LocationCategory.RING), + DS3LocationData("RC: Titanite Slab - mid boss drop", "Titanite Slab", + prominent=True, boss=True), # Halflight drop, only once + DS3LocationData("RC: Filianore's Spear Ornament - mid boss drop", + "Filianore's Spear Ornament"), + DS3LocationData("RC: Soul of Darkeater Midir", "Soul of Darkeater Midir", prominent=True, + boss=True), + DS3LocationData("RC: Sacred Chime of Filianore - ashes, NPC drop", + "Sacred Chime of Filianore", + hostile_npc=True), # Shira (kill or quest) + DS3LocationData("RC: Titanite Slab - ashes, NPC drop", "Titanite Slab", + hostile_npc=True), # Shira (kill or quest) + DS3LocationData("RC: Crucifix of the Mad King - ashes, NPC drop", + "Crucifix of the Mad King", hostile_npc=True), # Shira drop + DS3LocationData("RC: Ledo's Great Hammer - streets high, opposite building, NPC drop", + "Ledo's Great Hammer", hostile_npc=True, + missable=True), # Silver Knight Ledo drop, doesn't invade once Halflight + # is defeated + DS3LocationData("RC: Wolf Ring+3 - street gardens, NPC drop", "Wolf Ring+3", + hostile_npc=True, + missable=True), # Alva drop, doesn't invade once Halflight is defeated + DS3LocationData("RC: Blindfold Mask - grave, NPC drop", "Blindfold Mask", + hostile_npc=True), # Moaning Knight drop + DS3LocationData("RC: Titanite Scale - wall top, behind spawn", "Titanite Scale"), # wrong + DS3LocationData("RC: Ruin Helm - wall top, under stairs to bonfire", "Ruin Helm"), + DS3LocationData("RC: Ruin Armor - wall top, under stairs to bonfire", "Ruin Armor"), + DS3LocationData("RC: Ruin Gauntlets - wall top, under stairs to bonfire", "Ruin Gauntlets"), + DS3LocationData("RC: Ruin Leggings - wall top, under stairs to bonfire", "Ruin Leggings"), + DS3LocationData("RC: Budding Green Blossom - wall top, in flower cluster", + "Budding Green Blossom x2"), + DS3LocationData("RC: Titanite Chunk - wall top, among graves", "Titanite Chunk x2"), + DS3LocationData("RC: Ember - wall top, by statue", "Ember"), + DS3LocationData("RC: Budding Green Blossom - wall top, flowers by stairs", + "Budding Green Blossom x2"), + DS3LocationData("RC: Hidden Blessing - wall top, tomb under platform", "Hidden Blessing", + hidden=True), # hidden fall + DS3LocationData("RC: Soul of a Crestfallen Knight - wall top, under drop", + "Soul of a Crestfallen Knight", hidden=True), # hidden fall + DS3LocationData("RC: Large Soul of a Weary Warrior - wall top, right of small tomb", + "Large Soul of a Weary Warrior"), + DS3LocationData("RC: Ember - wall upper, balcony", "Ember"), + DS3LocationData("RC: Purging Stone - wall top, by door to upper", "Purging Stone x2"), + DS3LocationData("RC: Hollow Gem - wall upper, path to tower", "Hollow Gem"), + DS3LocationData("RC: Titanite Chunk - wall upper, courtyard alcove", "Titanite Chunk"), + DS3LocationData("RC: Twinkling Titanite - wall tower, jump from chandelier", + "Twinkling Titanite", hidden=True), # Hidden fall + DS3LocationData("RC: Shriving Stone - wall tower, bottom floor center", "Shriving Stone"), + DS3LocationData("RC: Shira's Crown - Shira's room after killing ashes NPC", "Shira's Crown", + hidden=True), # Have to return to a cleared area + DS3LocationData("RC: Shira's Armor - Shira's room after killing ashes NPC", "Shira's Armor", + hidden=True), # Have to return to a cleared area + DS3LocationData("RC: Shira's Gloves - Shira's room after killing ashes NPC", + "Shira's Gloves", hidden=True), # Have to return to a cleared area + DS3LocationData("RC: Shira's Trousers - Shira's room after killing ashes NPC", + "Shira's Trousers", hidden=True), # Have to return to a cleared area + DS3LocationData("RC: Mossfruit - streets near left, path to garden", "Mossfruit x2"), + DS3LocationData("RC: Large Soul of a Crestfallen Knight - streets, far stairs", + "Large Soul of a Crestfallen Knight"), + DS3LocationData("RC: Ringed Knight Spear - streets, down far right hall", + "Ringed Knight Spear"), + DS3LocationData("RC: Black Witch Hat - streets garden", "Black Witch Hat", + hostile_npc=True), # Alva + DS3LocationData("RC: Black Witch Garb - streets garden", "Black Witch Garb", + hostile_npc=True), # Alva + DS3LocationData("RC: Black Witch Wrappings - streets garden", "Black Witch Wrappings", + hostile_npc=True), # Alva + DS3LocationData("RC: Black Witch Trousers - streets garden", "Black Witch Trousers", + hostile_npc=True), # Alva + DS3LocationData("RC: Dragonhead Shield - streets monument, across bridge", + "Dragonhead Shield", hidden=True), # "Show Your Humanity" puzzle + DS3LocationData("RC: Titanite Chunk - streets, near left drop", "Titanite Chunk", + hidden=True), # Hidden fall + DS3LocationData("RC: Mossfruit - streets, far left alcove", "Mossfruit x2"), + DS3LocationData("RC: Large Soul of a Crestfallen Knight - streets monument, across bridge", + "Large Soul of a Crestfallen Knight", + hidden=True), # "Show Your Humanity" puzzle + DS3LocationData("RC: Covetous Gold Serpent Ring+3 - streets, by Lapp", + "Covetous Gold Serpent Ring+3"), + DS3LocationData("RC: Titanite Chunk - streets high, building opposite", "Titanite Chunk x2"), + DS3LocationData("RC: Dark Gem - swamp near, by stairs", "Dark Gem"), + DS3LocationData("RC: Prism Stone - swamp near, railing by bonfire", "Prism Stone x4"), + DS3LocationData("RC: Ringed Knight Straight Sword - swamp near, tower on peninsula", + "Ringed Knight Straight Sword"), + DS3LocationData("RC: Havel's Ring+3 - streets high, drop from building opposite", + "Havel's Ring+3", hidden=True), # Hidden fall + DS3LocationData("RC: Titanite Chunk - swamp near left, by spire top", "Titanite Chunk"), + DS3LocationData("RC: Twinkling Titanite - swamp near left", "Twinkling Titanite"), + DS3LocationData("RC: Soul of a Weary Warrior - swamp center", "Soul of a Weary Warrior"), + DS3LocationData("RC: Preacher's Right Arm - swamp near right, by tower", + "Preacher's Right Arm"), + DS3LocationData("RC: Rubbish - swamp far, by crystal", "Rubbish"), + DS3LocationData("RC: Titanite Chunk - swamp near right, behind rock", + "Titanite Chunk"), + DS3LocationData("RC: Black Witch Veil - swamp near right, by sunken church", + "Black Witch Veil"), + DS3LocationData("RC: Twinkling Titanite - swamp near right, on sunken church", + "Twinkling Titanite"), + DS3LocationData("RC: Soul of a Crestfallen Knight - swamp near left, nook", + "Soul of a Crestfallen Knight"), + DS3LocationData("RC: White Preacher Head - swamp near, nook right of stairs", + "White Preacher Head"), + DS3LocationData("RC: Titanite Scale - swamp far, by miniboss", "Titanite Scale"), + DS3LocationData("RC: Dragonhead Greatshield - lower cliff, under bridge", + "Dragonhead Greatshield"), + DS3LocationData("RC: Titanite Scale - lower cliff, path under bridge", "Titanite Scale x2"), + DS3LocationData("RC: Rubbish - lower cliff, middle", "Rubbish"), + DS3LocationData("RC: Large Soul of a Weary Warrior - lower cliff, end", + "Large Soul of a Weary Warrior"), + DS3LocationData("RC: Titanite Scale - lower cliff, first alcove", "Titanite Scale x2"), + DS3LocationData("RC: Titanite Scale - lower cliff, lower path", "Titanite Scale"), + DS3LocationData("RC: Lightning Gem - grave, room after first drop", "Lightning Gem"), + DS3LocationData("RC: Blessed Gem - grave, down lowest stairs", "Blessed Gem"), + DS3LocationData("RC: Simple Gem - grave, up stairs after first drop", "Simple Gem"), + DS3LocationData("RC: Large Soul of a Weary Warrior - wall lower, past two illusory walls", + "Large Soul of a Weary Warrior", hidden=True), + DS3LocationData("RC: Lightning Arrow - wall lower, past three illusory walls", + "Lightning Arrow"), + DS3LocationData("RC: Chloranthy Ring+3 - wall hidden, drop onto statue", + "Chloranthy Ring+3", hidden=True), # Hidden fall + DS3LocationData("RC: Ember - wall hidden, statue room", "Ember"), + DS3LocationData("RC: Filianore's Spear Ornament - wall hidden, by ladder", + "Filianore's Spear Ornament"), + DS3LocationData("RC: Antiquated Plain Garb - wall hidden, before boss", + "Antiquated Plain Garb"), + DS3LocationData("RC: Violet Wrappings - wall hidden, before boss", "Violet Wrappings"), + DS3LocationData("RC: Soul of a Weary Warrior - lower cliff, by first alcove", + "Soul of a Weary Warrior"), + DS3LocationData("RC: Twinkling Titanite - church path, left of boss door", + "Twinkling Titanite x2"), + DS3LocationData("RC: Budding Green Blossom - church path", "Budding Green Blossom x3"), + DS3LocationData("RC: Titanite Chunk - swamp center, peninsula edge", "Titanite Chunk"), + DS3LocationData("RC: Large Soul of a Weary Warrior - swamp center, by peninsula", + "Large Soul of a Weary Warrior"), + DS3LocationData("RC: Soul of a Weary Warrior - swamp right, by sunken church", + "Soul of a Weary Warrior"), + DS3LocationData("RC: Titanite Scale - upper cliff, bridge", "Titanite Scale"), + DS3LocationData("RC: Soul of a Crestfallen Knight - swamp far, behind crystal", + "Soul of a Crestfallen Knight"), + DS3LocationData("RC: White Birch Bow - swamp far left, up hill", "White Birch Bow"), + DS3LocationData("RC: Titanite Chunk - swamp far left, up hill", "Titanite Chunk"), + DS3LocationData("RC: Young White Branch - swamp far left, by white tree #1", + "Young White Branch"), + DS3LocationData("RC: Young White Branch - swamp far left, by white tree #2", + "Young White Branch"), + DS3LocationData("RC: Young White Branch - swamp far left, by white tree #3", + "Young White Branch"), + DS3LocationData("RC: Ringed Knight Paired Greatswords - church path, mob drop", + "Ringed Knight Paired Greatswords", drop=True, + hidden=True), # Guaranteed drop from a normal-looking Ringed Knight + DS3LocationData("RC: Hidden Blessing - swamp center, mob drop", "Hidden Blessing", + drop=True, hidden=True), # Guaranteed drop from Judicator + DS3LocationData("RC: Divine Blessing - wall top, mob drop", "Divine Blessing", + drop=True, hidden=True), # Guaranteed drop from Judicator + DS3LocationData("RC: Divine Blessing - streets monument, mob drop", "Divine Blessing", + drop=True, + hidden=True), # Guaranteed drop from Judicator, "Show Your Humanity" puzzle + DS3LocationData("RC: Ring of the Evil Eye+3 - grave, mimic", "Ring of the Evil Eye+3", + mimic=True), + DS3LocationData("RC: Iron Dragonslayer Helm - swamp far, miniboss drop", + "Iron Dragonslayer Helm", miniboss=True), + DS3LocationData("RC: Iron Dragonslayer Armor - swamp far, miniboss drop", + "Iron Dragonslayer Armor", miniboss=True), + DS3LocationData("RC: Iron Dragonslayer Gauntlets - swamp far, miniboss drop", + "Iron Dragonslayer Gauntlets", miniboss=True), + DS3LocationData("RC: Iron Dragonslayer Leggings - swamp far, miniboss drop", + "Iron Dragonslayer Leggings", miniboss=True), + DS3LocationData("RC: Church Guardian Shiv - swamp far left, in building", + "Church Guardian Shiv"), + DS3LocationData("RC: Spears of the Church - hidden boss drop", "Spears of the Church", + boss=True), # Midir drop + DS3LocationData("RC: Ritual Spear Fragment - church path", "Ritual Spear Fragment"), + DS3LocationData("RC: Titanite Scale - swamp far, lagoon entrance", "Titanite Scale"), + DS3LocationData("RC: Twinkling Titanite - grave, lizard past first drop", + "Twinkling Titanite", lizard=True), + DS3LocationData("RC: Titanite Scale - grave, lizard past first drop", "Titanite Scale", + lizard=True), + DS3LocationData("RC: Twinkling Titanite - streets high, lizard", "Twinkling Titanite x2", + lizard=True), + DS3LocationData("RC: Titanite Scale - wall lower, lizard", "Titanite Scale", lizard=True), + DS3LocationData("RC: Twinkling Titanite - wall top, lizard on side path", + "Twinkling Titanite", lizard=True), + DS3LocationData("RC: Soul of Slave Knight Gael", "Soul of Slave Knight Gael", + prominent=True, boss=True), + DS3LocationData("RC: Blood of the Dark Soul - end boss drop", "Blood of the Dark Soul"), + DS3LocationData("RC: Titanite Slab - ashes, mob drop", "Titanite Slab", + drop=True, + hidden=True), # Guaranteed drop from normal-looking Ringed Knight + + # Lapp + DS3LocationData("RC: Siegbräu - Lapp", "Siegbräu", missable=True, + npc=True), # Lapp (quest) + # Quest or Shrine Handmaiden after death + DS3LocationData("RC: Lapp's Helm - Lapp", "Lapp's Helm", npc=True, shop=True), + DS3LocationData("RC: Lapp's Armor - Lapp", "Lapp's Armor", npc=True, shop=True), + DS3LocationData("RC: Lapp's Gauntlets - Lapp", "Lapp's Gauntlets", npc=True, shop=True), + DS3LocationData("RC: Lapp's Leggings - Lapp", "Lapp's Leggings", npc=True, shop=True), + ], + + # Unlockable shops. We only bother creating a "region" for these for shops that are locked + # behind keys and always have items available either through the shop or through the NPC's + # ashes. + "Greirat's Shop": [ + DS3LocationData("FS: Blue Tearstone Ring - Greirat", "Blue Tearstone Ring", + static='01,0:50006120::', npc=True), + DS3LocationData("FS: Ember - Greirat", "Ember", static="99,0:-1:110000,120000,70000110:", + shop=True, npc=True), + + # Undead Settlement rewards + DS3LocationData("FS: Divine Blessing - Greirat from US", "Divine Blessing", + static='99,0:-1:110000,120000,70000150,70000175:', missable=True, + shop=True, npc=True), + DS3LocationData("FS: Ember - Greirat from US", "Ember", + static='99,0:-1:110000,120000,70000150,70000175:', missable=True, + shop=True, npc=True), + + # Irityhll rewards + DS3LocationData("FS: Divine Blessing - Greirat from IBV", "Divine Blessing", + static='99,0:-1:110000,120000,70000151,70000176:', missable=True, + shop=True, npc=True), + DS3LocationData("FS: Hidden Blessing - Greirat from IBV", "Hidden Blessing", + static='99,0:-1:110000,120000,70000151,70000176:', missable=True, + shop=True, npc=True), + DS3LocationData("FS: Titanite Scale - Greirat from IBV", "Titanite Scale", + static='99,0:-1:110000,120000,70000151,70000176:', missable=True, + shop=True, npc=True), + DS3LocationData("FS: Twinkling Titanite - Greirat from IBV", "Twinkling Titanite", + static='99,0:-1:110000,120000,70000151,70000176:', missable=True, + shop=True, npc=True), + + # Lothric rewards (from Shrine Handmaid) + DS3LocationData("FS: Ember - shop for Greirat's Ashes", "Twinkling Titanite", + static='99,0:-1:110000,120000,70000152,70000177:', missable=True, + shop=True, npc=True), + ], + "Karla's Shop": [ + DS3LocationData("FS: Affinity - Karla", "Affinity", shop=True, npc=True), + DS3LocationData("FS: Dark Edge - Karla", "Dark Edge", shop=True, npc=True), + + # Quelana Pyromancy Tome + DS3LocationData("FS: Firestorm - Karla for Quelana Tome", "Firestorm", missable=True, + shop=True, npc=True), + DS3LocationData("FS: Rapport - Karla for Quelana Tome", "Rapport", missable=True, + shop=True, npc=True), + DS3LocationData("FS: Fire Whip - Karla for Quelana Tome", "Fire Whip", missable=True, + shop=True, npc=True), + + # Grave Warden Pyromancy Tome + DS3LocationData("FS: Black Flame - Karla for Grave Warden Tome", "Black Flame", + missable=True, shop=True, npc=True), + DS3LocationData("FS: Black Fire Orb - Karla for Grave Warden Tome", "Black Fire Orb", + missable=True, shop=True, npc=True), + + # Deep Braille Divine Tome. This can also be given to Irina, but it'll fail her quest + DS3LocationData("FS: Gnaw - Karla for Deep Braille Tome", "Gnaw", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Deep Protection - Karla for Deep Braille Tome", "Deep Protection", + missable=True, npc=True, shop=True), + + # Londor Braille Divine Tome. This can also be given to Irina, but it'll fail her quest + DS3LocationData("FS: Vow of Silence - Karla for Londor Tome", "Vow of Silence", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Dark Blade - Karla for Londor Tome", "Dark Blade", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Dead Again - Karla for Londor Tome", "Dead Again", missable=True, + npc=True, shop=True), + + # Drops on death. Missable because the player would have to decide between killing her or + # seeing everything she sells. + DS3LocationData("FS: Karla's Pointed Hat - kill Karla", "Karla's Pointed Hat", + static='07,0:50006150::', missable=True, drop=True, npc=True), + DS3LocationData("FS: Karla's Coat - kill Karla", "Karla's Coat", + static='07,0:50006150::', missable=True, drop=True, npc=True), + DS3LocationData("FS: Karla's Gloves - kill Karla", "Karla's Gloves", + static='07,0:50006150::', missable=True, drop=True, npc=True), + DS3LocationData("FS: Karla's Trousers - kill Karla", "Karla's Trousers", + static='07,0:50006150::', missable=True, drop=True, npc=True), ], +} + +for i, region in enumerate(region_order): + for location in location_tables[region]: location.region_value = i + +for region in [ + "Painted World of Ariandel (Before Contraption)", + "Painted World of Ariandel (After Contraption)", + "Dreg Heap", + "Ringed City", +]: + for location in location_tables[region]: + location.dlc = True - # Progressive - "Progressive Items 1": [] + - # Upgrade materials - [DS3LocationData(f"Titanite Shard #{i + 1}", "Titanite Shard", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(26)] + - [DS3LocationData(f"Large Titanite Shard #{i + 1}", "Large Titanite Shard", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(28)] + - [DS3LocationData(f"Titanite Slab #{i + 1}", "Titanite Slab", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Twinkling Titanite #{i + 1}", "Twinkling Titanite", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(15)], - - "Progressive Items 2": [] + - # Items - [DS3LocationData(f"Green Blossom #{i + 1}", "Green Blossom", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(6)] + - [DS3LocationData(f"Firebomb #{i + 1}", "Firebomb", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(4)] + - [DS3LocationData(f"Alluring Skull #{i + 1}", "Alluring Skull", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Undead Hunter Charm #{i + 1}", "Undead Hunter Charm", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Duel Charm #{i + 1}", "Duel Charm", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Throwing Knife #{i + 1}", "Throwing Knife", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Gold Pine Resin #{i + 1}", "Gold Pine Resin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Charcoal Pine Resin #{i + 1}", "Charcoal Pine Resin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Human Pine Resin #{i + 1}", "Human Pine Resin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Carthus Rouge #{i + 1}", "Carthus Rouge", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Pale Pine Resin #{i + 1}", "Pale Pine Resin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Charcoal Pine Bundle #{i + 1}", "Charcoal Pine Bundle", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Rotten Pine Resin #{i + 1}", "Rotten Pine Resin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Homeward Bone #{i + 1}", "Homeward Bone", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(16)] + - [DS3LocationData(f"Pale Tongue #{i + 1}", "Pale Tongue", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Rusted Coin #{i + 1}", "Rusted Coin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Rusted Gold Coin #{i + 1}", "Rusted Gold Coin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Ember #{i + 1}", "Ember", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(45)], - - "Progressive Items 3": [] + - # Souls & Bulk Upgrade Materials - [DS3LocationData(f"Fading Soul #{i + 1}", "Fading Soul", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Soul of a Deserted Corpse #{i + 1}", "Soul of a Deserted Corpse", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Large Soul of a Deserted Corpse #{i + 1}", "Large Soul of a Deserted Corpse", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Soul of an Unknown Traveler #{i + 1}", "Soul of an Unknown Traveler", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Large Soul of an Unknown Traveler #{i + 1}", "Large Soul of an Unknown Traveler", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Soul of a Nameless Soldier #{i + 1}", "Soul of a Nameless Soldier", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(4)] + - [DS3LocationData(f"Large Soul of a Nameless Soldier #{i + 1}", "Large Soul of a Nameless Soldier", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(4)] + - [DS3LocationData(f"Soul of a Weary Warrior #{i + 1}", "Soul of a Weary Warrior", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(6)] + - [DS3LocationData(f"Soul of a Crestfallen Knight #{i + 1}", "Soul of a Crestfallen Knight", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Titanite Chunk #{i + 1}", "Titanite Chunk", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(22)] + - [DS3LocationData(f"Titanite Scale #{i + 1}", "Titanite Scale", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(29)], - - "Progressive Items 4": [] + - # Gems & Random Consumables - [DS3LocationData(f"Ring of Sacrifice #{i + 1}", "Ring of Sacrifice", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(4)] + - [DS3LocationData(f"Divine Blessing #{i + 1}", "Divine Blessing", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Hidden Blessing #{i + 1}", "Hidden Blessing", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Budding Green Blossom #{i + 1}", "Budding Green Blossom", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Bloodred Moss Clump #{i + 1}", "Bloodred Moss Clump", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Purple Moss Clump #{i + 1}", "Purple Moss Clump", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Blooming Purple Moss Clump #{i + 1}", "Blooming Purple Moss Clump", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Purging Stone #{i + 1}", "Purging Stone", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Rime-blue Moss Clump #{i + 1}", "Rime-blue Moss Clump", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Repair Powder #{i + 1}", "Repair Powder", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Kukri #{i + 1}", "Kukri", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Lightning Urn #{i + 1}", "Lightning Urn", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Rubbish #{i + 1}", "Rubbish", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Blue Bug Pellet #{i + 1}", "Blue Bug Pellet", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Red Bug Pellet #{i + 1}", "Red Bug Pellet", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Yellow Bug Pellet #{i + 1}", "Yellow Bug Pellet", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Black Bug Pellet #{i + 1}", "Black Bug Pellet", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Heavy Gem #{i + 1}", "Heavy Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Sharp Gem #{i + 1}", "Sharp Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Refined Gem #{i + 1}", "Refined Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Crystal Gem #{i + 1}", "Crystal Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Simple Gem #{i + 1}", "Simple Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Fire Gem #{i + 1}", "Fire Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Chaos Gem #{i + 1}", "Chaos Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Lightning Gem #{i + 1}", "Lightning Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Deep Gem #{i + 1}", "Deep Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Dark Gem #{i + 1}", "Dark Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Poison Gem #{i + 1}", "Poison Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Blood Gem #{i + 1}", "Blood Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Raw Gem #{i + 1}", "Raw Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Blessed Gem #{i + 1}", "Blessed Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Hollow Gem #{i + 1}", "Hollow Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Shriving Stone #{i + 1}", "Shriving Stone", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)], - - "Progressive Items DLC": [] + - # Upgrade materials - [DS3LocationData(f"Large Titanite Shard ${i + 1}", "Large Titanite Shard", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Titanite Chunk ${i + 1}", "Titanite Chunk", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(15)] + - [DS3LocationData(f"Titanite Slab ${i + 1}", "Titanite Slab", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Twinkling Titanite ${i + 1}", "Twinkling Titanite", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Titanite Scale ${i + 1}", "Titanite Scale", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(11)] + - - - # Items - [DS3LocationData(f"Homeward Bone ${i + 1}", "Homeward Bone", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(6)] + - [DS3LocationData(f"Rusted Coin ${i + 1}", "Rusted Coin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Ember ${i + 1}", "Ember", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(10)] + - - # Souls - [DS3LocationData(f"Large Soul of an Unknown Traveler ${i + 1}", "Large Soul of an Unknown Traveler", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(9)] + - [DS3LocationData(f"Soul of a Weary Warrior ${i + 1}", "Soul of a Weary Warrior", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Large Soul of a Weary Warrior ${i + 1}", "Large Soul of a Weary Warrior", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(6)] + - [DS3LocationData(f"Soul of a Crestfallen Knight ${i + 1}", "Soul of a Crestfallen Knight", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(6)] + - [DS3LocationData(f"Large Soul of a Crestfallen Knight ${i + 1}", "Large Soul of a Crestfallen Knight", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - - # Gems - [DS3LocationData(f"Dark Gem ${i + 1}", "Dark Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Blood Gem ${i + 1}", "Blood Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Blessed Gem ${i + 1}", "Blessed Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Hollow Gem ${i + 1}", "Hollow Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)], - - "Progressive Items Health": [] + - # Healing - [DS3LocationData(f"Estus Shard #{i + 1}", "Estus Shard", DS3LocationCategory.HEALTH) for i in range(11)] + - [DS3LocationData(f"Undead Bone Shard #{i + 1}", "Undead Bone Shard", DS3LocationCategory.HEALTH) for i in range(10)], +for region in [ + "Firelink Shrine Bell Tower", + "Greirat's Shop", + "Karla's Shop" +]: + for location in location_tables[region]: + location.conditional = True + +location_name_groups: Dict[str, Set[str]] = { + # We could insert these locations automatically with setdefault(), but we set them up explicitly + # instead so we can choose the ordering. + "Prominent": set(), + "Progression": set(), + "Boss Rewards": set(), + "Miniboss Rewards": set(), + "Mimic Rewards": set(), + "Hostile NPC Rewards": set(), + "Friendly NPC Rewards": set(), + "Small Crystal Lizards": set(), + "Upgrade": set(), + "Small Souls": set(), + "Boss Souls": set(), + "Unique": set(), + "Healing": set(), + "Miscellaneous": set(), + "Hidden": set(), + "Weapons": set(), + "Shields": set(), + "Armor": set(), + "Rings": set(), + "Spells": set(), +} + +location_descriptions = { + "Prominent": "A small number of locations that are in very obvious locations. Mostly boss " + \ + "drops. Ideal for setting as priority locations.", + "Progression": "Locations that contain items in vanilla which unlock other locations.", + "Boss Rewards": "Boss drops. Does not include soul transfusions or shop items.", + "Miniboss Rewards": "Miniboss drops. Only includes enemies considered minibosses by the " + \ + "enemy randomizer.", + "Mimic Rewards": "Drops from enemies that are mimics in vanilla.", + "Hostile NPC Rewards": "Drops from NPCs that are hostile to you. This includes scripted " + \ + "invaders and initially-friendly NPCs that must be fought as part of their quest.", + "Friendly NPC Rewards": "Items given by friendly NPCs as part of their quests or from " + \ + "non-violent interaction.", + "Upgrade": "Locations that contain upgrade items in vanilla, including titanite, gems, and " + \ + "Shriving Stones.", + "Small Souls": "Locations that contain soul items in vanilla, not including boss souls.", + "Boss Souls": "Locations that contain boss souls in vanilla, as well as Soul of Rosaria.", + "Unique": "Locations that contain items in vanilla that are unique per NG cycle, such as " + \ + "scrolls, keys, ashes, and so on. Doesn't cover equipment, spells, or souls.", + "Healing": "Locations that contain Undead Bone Shards and Estus Shards in vanilla.", + "Miscellaneous": "Locations that contain generic stackable items in vanilla, such as arrows, " + + "firebombs, buffs, and so on.", + "Hidden": "Locations that are particularly difficult to find, such as behind illusory " + \ + "walls, down hidden drops, and so on. Does not include large locations like Untended " + \ + "Graves or Archdragon Peak.", + "Weapons": "Locations that contain weapons in vanilla.", + "Shields": "Locations that contain shields in vanilla.", + "Armor": "Locations that contain armor in vanilla.", + "Rings": "Locations that contain rings in vanilla.", + "Spells": "Locations that contain spells in vanilla.", } location_dictionary: Dict[str, DS3LocationData] = {} -for location_table in location_tables.values(): +for location_name, location_table in location_tables.items(): location_dictionary.update({location_data.name: location_data for location_data in location_table}) + + for location_data in location_table: + if not location_data.is_event: + for group_name in location_data.location_groups(): + location_name_groups[group_name].add(location_data.name) + + # Allow entire locations to be added to location sets. + if not location_name.endswith(" Shop"): + location_name_groups[location_name] = set([ + location_data.name for location_data in location_table + if not location_data.is_event + ]) + +location_name_groups["Painted World of Ariandel"] = ( + location_name_groups["Painted World of Ariandel (Before Contraption)"] + .union(location_name_groups["Painted World of Ariandel (After Contraption)"]) +) +del location_name_groups["Painted World of Ariandel (Before Contraption)"] +del location_name_groups["Painted World of Ariandel (After Contraption)"] + +location_name_groups["DLC"] = ( + location_name_groups["Painted World of Ariandel"] + .union(location_name_groups["Dreg Heap"]) + .union(location_name_groups["Ringed City"]) +) diff --git a/worlds/dark_souls_3/Options.py b/worlds/dark_souls_3/Options.py index df0bb953b8d9..ad81dd9f7b85 100644 --- a/worlds/dark_souls_3/Options.py +++ b/worlds/dark_souls_3/Options.py @@ -1,80 +1,78 @@ -import typing +from dataclasses import dataclass +import json +from typing import Any, Dict -from Options import Toggle, DefaultOnToggle, Option, Range, Choice, ItemDict, DeathLink +from Options import Choice, DeathLink, DefaultOnToggle, ExcludeLocations, NamedRange, OptionDict, \ + OptionGroup, PerGameCommonOptions, Range, Removed, Toggle +## Game Options -class RandomizeWeaponLocations(DefaultOnToggle): - """Randomizes weapons (+76 locations)""" - display_name = "Randomize Weapon Locations" - -class RandomizeShieldLocations(DefaultOnToggle): - """Randomizes shields (+24 locations)""" - display_name = "Randomize Shield Locations" - - -class RandomizeArmorLocations(DefaultOnToggle): - """Randomizes armor pieces (+97 locations)""" - display_name = "Randomize Armor Locations" - - -class RandomizeRingLocations(DefaultOnToggle): - """Randomizes rings (+49 locations)""" - display_name = "Randomize Ring Locations" - - -class RandomizeSpellLocations(DefaultOnToggle): - """Randomizes spells (+18 locations)""" - display_name = "Randomize Spell Locations" - - -class RandomizeKeyLocations(DefaultOnToggle): - """Randomizes items which unlock doors or bypass barriers""" - display_name = "Randomize Key Locations" +class EarlySmallLothricBanner(Choice): + """Force Small Lothric Banner into an early sphere in your world or across all worlds.""" + display_name = "Early Small Lothric Banner" + option_off = 0 + option_early_global = 1 + option_early_local = 2 + default = option_off -class RandomizeBossSoulLocations(DefaultOnToggle): - """Randomizes Boss Souls (+18 Locations)""" - display_name = "Randomize Boss Soul Locations" +class LateBasinOfVowsOption(Choice): + """Guarantee that you don't need to enter Lothric Castle until later in the run. + - **Off:** You may have to enter Lothric Castle and the areas beyond it immediately after High + Wall of Lothric. + - **After Small Lothric Banner:** You may have to enter Lothric Castle after Catacombs of + Carthus. + - **After Small Doll:** You won't have to enter Lothric Castle until after Irithyll of the + Boreal Valley. + """ + display_name = "Late Basin of Vows" + option_off = 0 + alias_false = 0 + option_after_small_lothric_banner = 1 + alias_true = 1 + option_after_small_doll = 2 -class RandomizeNPCLocations(Toggle): - """Randomizes friendly NPC drops (meaning you will probably have to kill them) (+14 locations)""" - display_name = "Randomize NPC Locations" +class LateDLCOption(Choice): + """Guarantee that you don't need to enter the DLC until later in the run. -class RandomizeMiscLocations(Toggle): - """Randomizes miscellaneous items (ashes, tomes, scrolls, etc.) to the pool. (+36 locations)""" - display_name = "Randomize Miscellaneous Locations" + - **Off:** You may have to enter the DLC after Catacombs of Carthus. + - **After Small Doll:** You may have to enter the DLC after Irithyll of the Boreal Valley. + - **After Basin:** You won't have to enter the DLC until after Lothric Castle. + """ + display_name = "Late DLC" + option_off = 0 + alias_false = 0 + option_after_small_doll = 1 + alias_true = 1 + option_after_basin = 2 -class RandomizeHealthLocations(Toggle): - """Randomizes health upgrade items. (+21 locations)""" - display_name = "Randomize Health Upgrade Locations" +class EnableDLCOption(Toggle): + """Include DLC locations, items, and enemies in the randomized pools. + To use this option, you must own both the "Ashes of Ariandel" and the "Ringed City" DLCs. + """ + display_name = "Enable DLC" -class RandomizeProgressiveLocationsOption(Toggle): - """Randomizes upgrade materials and consumables such as the titanite shards, firebombs, resin, etc... - Instead of specific locations, these are progressive, so Titanite Shard #1 is the first titanite shard - you pick up, regardless of whether it's from an enemy drop late in the game or an item on the ground in the - first 5 minutes.""" - display_name = "Randomize Progressive Locations" +class EnableNGPOption(Toggle): + """Include items and locations exclusive to NG+ cycles.""" + display_name = "Enable NG+" -class PoolTypeOption(Choice): - """Changes which non-progression items you add to the pool +## Equipment - Shuffle: Items are picked from the locations being randomized - Various: Items are picked from a list of all items in the game, but are the same type of item they replace""" - display_name = "Pool Type" - option_shuffle = 0 - option_various = 1 +class RandomizeStartingLoadout(DefaultOnToggle): + """Randomizes the equipment characters begin with.""" + display_name = "Randomize Starting Loadout" -class GuaranteedItemsOption(ItemDict): - """Guarantees that the specified items will be in the item pool""" - display_name = "Guaranteed Items" +class RequireOneHandedStartingWeapons(DefaultOnToggle): + """Require starting equipment to be usable one-handed.""" + display_name = "Require One-Handed Starting Weapons" class AutoEquipOption(Toggle): @@ -83,47 +81,56 @@ class AutoEquipOption(Toggle): class LockEquipOption(Toggle): - """Lock the equipment slots so you cannot change your armor or your left/right weapons. Works great with the - Auto-equip option.""" + """Lock the equipment slots so you cannot change your armor or your left/right weapons. + + Works great with the Auto-equip option. + """ display_name = "Lock Equipment Slots" +class NoEquipLoadOption(Toggle): + """Disable the equip load constraint from the game.""" + display_name = "No Equip Load" + + class NoWeaponRequirementsOption(Toggle): - """Disable the weapon requirements by removing any movement or damage penalties. - Permitting you to use any weapon early""" + """Disable the weapon requirements by removing any movement or damage penalties, permitting you + to use any weapon early. + """ display_name = "No Weapon Requirements" class NoSpellRequirementsOption(Toggle): - """Disable the spell requirements permitting you to use any spell""" + """Disable the spell requirements permitting you to use any spell.""" display_name = "No Spell Requirements" -class NoEquipLoadOption(Toggle): - """Disable the equip load constraint from the game""" - display_name = "No Equip Load" - +## Weapons class RandomizeInfusionOption(Toggle): """Enable this option to infuse a percentage of the pool of weapons and shields.""" display_name = "Randomize Infusion" -class RandomizeInfusionPercentageOption(Range): - """The percentage of weapons/shields in the pool to be infused if Randomize Infusion is toggled""" +class RandomizeInfusionPercentageOption(NamedRange): + """The percentage of weapons/shields in the pool to be infused if Randomize Infusion is toggled. + """ display_name = "Percentage of Infused Weapons" range_start = 0 range_end = 100 default = 33 + # 3/155 weapons are infused in the base game, or about 2% + special_range_names = {"similar to base game": 2} class RandomizeWeaponLevelOption(Choice): - """Enable this option to upgrade a percentage of the pool of weapons to a random value between the minimum and - maximum levels defined. + """Enable this option to upgrade a percentage of the pool of weapons to a random value between + the minimum and maximum levels defined. - All: All weapons are eligible, both basic and epic - Basic: Only weapons that can be upgraded to +10 - Epic: Only weapons that can be upgraded to +5""" + - **All:** All weapons are eligible, both basic and epic + - **Basic:** Only weapons that can be upgraded to +10 + - **Epic:** Only weapons that can be upgraded to +5 + """ display_name = "Randomize Weapon Level" option_none = 0 option_all = 1 @@ -132,7 +139,7 @@ class RandomizeWeaponLevelOption(Choice): class RandomizeWeaponLevelPercentageOption(Range): - """The percentage of weapons in the pool to be upgraded if randomize weapons level is toggled""" + """The percentage of weapons in the pool to be upgraded if randomize weapons level is toggled.""" display_name = "Percentage of Randomized Weapons" range_start = 0 range_end = 100 @@ -140,7 +147,7 @@ class RandomizeWeaponLevelPercentageOption(Range): class MinLevelsIn5WeaponPoolOption(Range): - """The minimum upgraded value of a weapon in the pool of weapons that can only reach +5""" + """The minimum upgraded value of a weapon in the pool of weapons that can only reach +5.""" display_name = "Minimum Level of +5 Weapons" range_start = 0 range_end = 5 @@ -148,7 +155,7 @@ class MinLevelsIn5WeaponPoolOption(Range): class MaxLevelsIn5WeaponPoolOption(Range): - """The maximum upgraded value of a weapon in the pool of weapons that can only reach +5""" + """The maximum upgraded value of a weapon in the pool of weapons that can only reach +5.""" display_name = "Maximum Level of +5 Weapons" range_start = 0 range_end = 5 @@ -156,7 +163,7 @@ class MaxLevelsIn5WeaponPoolOption(Range): class MinLevelsIn10WeaponPoolOption(Range): - """The minimum upgraded value of a weapon in the pool of weapons that can reach +10""" + """The minimum upgraded value of a weapon in the pool of weapons that can reach +10.""" display_name = "Minimum Level of +10 Weapons" range_start = 0 range_end = 10 @@ -164,72 +171,308 @@ class MinLevelsIn10WeaponPoolOption(Range): class MaxLevelsIn10WeaponPoolOption(Range): - """The maximum upgraded value of a weapon in the pool of weapons that can reach +10""" + """The maximum upgraded value of a weapon in the pool of weapons that can reach +10.""" display_name = "Maximum Level of +10 Weapons" range_start = 0 range_end = 10 default = 10 -class EarlySmallLothricBanner(Choice): - """This option makes it so the user can choose to force the Small Lothric Banner into an early sphere in their world or - into an early sphere across all worlds.""" - display_name = "Early Small Lothric Banner" - option_off = 0 - option_early_global = 1 - option_early_local = 2 - default = option_off +## Item Smoothing +class SmoothSoulItemsOption(DefaultOnToggle): + """Distribute soul items in a similar order as the base game. -class LateBasinOfVowsOption(Toggle): - """This option makes it so the Basin of Vows is still randomized, but guarantees you that you wont have to venture into - Lothric Castle to find your Small Lothric Banner to get out of High Wall of Lothric. So you may find Basin of Vows early, - but you wont have to fight Dancer to find your Small Lothric Banner.""" - display_name = "Late Basin of Vows" + By default, soul items will be distributed totally randomly. If this is set, less valuable soul + items will generally appear in earlier spheres and more valuable ones will generally appear + later. + """ + display_name = "Smooth Soul Items" -class LateDLCOption(Toggle): - """This option makes it so you are guaranteed to find your Small Doll without having to venture off into the DLC, - effectively putting anything in the DLC in logic after finding both Contraption Key and Small Doll, - and being able to get into Irithyll of the Boreal Valley.""" - display_name = "Late DLC" +class SmoothUpgradeItemsOption(DefaultOnToggle): + """Distribute upgrade items in a similar order as the base game. + By default, upgrade items will be distributed totally randomly. If this is set, lower-level + upgrade items will generally appear in earlier spheres and higher-level ones will generally + appear later. + """ + display_name = "Smooth Upgrade Items" -class EnableDLCOption(Toggle): - """To use this option, you must own both the ASHES OF ARIANDEL and the RINGED CITY DLC""" - display_name = "Enable DLC" +class SmoothUpgradedWeaponsOption(DefaultOnToggle): + """Distribute upgraded weapons in a similar order as the base game. + + By default, upgraded weapons will be distributed totally randomly. If this is set, lower-level + weapons will generally appear in earlier spheres and higher-level ones will generally appear + later. + """ + display_name = "Smooth Upgraded Weapons" + + +### Enemies + +class RandomizeEnemiesOption(DefaultOnToggle): + """Randomize enemy and boss placements.""" + display_name = "Randomize Enemies" + + +class SimpleEarlyBossesOption(DefaultOnToggle): + """Avoid replacing Iudex Gundyr and Vordt with late bosses. + + This excludes all bosses after Dancer of the Boreal Valley from these two boss fights. Disable + it for a chance at a much harder early game. + + This is ignored unless enemies are randomized. + """ + display_name = "Simple Early Bosses" + + +class ScaleEnemiesOption(DefaultOnToggle): + """Scale randomized enemy stats to match the areas in which they appear. + + Disabling this will tend to make the early game much more difficult and the late game much + easier. + + This is ignored unless enemies are randomized. + """ + display_name = "Scale Enemies" + + +class RandomizeMimicsWithEnemiesOption(Toggle): + """Mix Mimics into the main enemy pool. + + If this is enabled, Mimics will be replaced by normal enemies who drop the Mimic rewards on + death, and Mimics will be placed randomly in place of normal enemies. It's recommended to enable + Impatient Mimics as well if you enable this. + + This is ignored unless enemies are randomized. + """ + display_name = "Randomize Mimics With Enemies" + + +class RandomizeSmallCrystalLizardsWithEnemiesOption(Toggle): + """Mix small Crystal Lizards into the main enemy pool. + + If this is enabled, Crystal Lizards will be replaced by normal enemies who drop the Crystal + Lizard rewards on death, and Crystal Lizards will be placed randomly in place of normal enemies. + + This is ignored unless enemies are randomized. + """ + display_name = "Randomize Small Crystal Lizards With Enemies" + + +class ReduceHarmlessEnemiesOption(Toggle): + """Reduce the frequency that "harmless" enemies appear. + + Enable this to add a bit of extra challenge. This severely limits the number of enemies that are + slow to aggro, slow to attack, and do very little damage that appear in the enemy pool. + + This is ignored unless enemies are randomized. + """ + display_name = "Reduce Harmless Enemies" + + +class AllChestsAreMimicsOption(Toggle): + """Replace all chests with mimics that drop the same items. + + If "Randomize Mimics With Enemies" is set, these chests will instead be replaced with random + enemies that drop the same items. + + This is ignored unless enemies are randomized. + """ + display_name = "All Chests Are Mimics" + + +class ImpatientMimicsOption(Toggle): + """Mimics attack as soon as you get close instead of waiting for you to open them. + + This is ignored unless enemies are randomized. + """ + display_name = "Impatient Mimics" + + +class RandomEnemyPresetOption(OptionDict): + """The YAML preset for the static enemy randomizer. + + See the static randomizer documentation in `randomizer\\presets\\README.txt` for details. + Include this as nested YAML. For example: + + .. code-block:: YAML + + random_enemy_preset: + RemoveSource: Ancient Wyvern; Darkeater Midir + DontRandomize: Iudex Gundyr + """ + display_name = "Random Enemy Preset" + supports_weighting = False + default = {} + + valid_keys = ["Description", "RecommendFullRandomization", "RecommendNoEnemyProgression", + "OopsAll", "Boss", "Miniboss", "Basic", "BuffBasicEnemiesAsBosses", + "DontRandomize", "RemoveSource", "Enemies"] + + @classmethod + def get_option_name(cls, value: Dict[str, Any]) -> str: + return json.dumps(value) + + +## Item & Location + +class DS3ExcludeLocations(ExcludeLocations): + """Prevent these locations from having an important item.""" + default = frozenset({"Hidden", "Small Crystal Lizards", "Upgrade", "Small Souls", "Miscellaneous"}) -dark_souls_options: typing.Dict[str, Option] = { - "enable_weapon_locations": RandomizeWeaponLocations, - "enable_shield_locations": RandomizeShieldLocations, - "enable_armor_locations": RandomizeArmorLocations, - "enable_ring_locations": RandomizeRingLocations, - "enable_spell_locations": RandomizeSpellLocations, - "enable_key_locations": RandomizeKeyLocations, - "enable_boss_locations": RandomizeBossSoulLocations, - "enable_npc_locations": RandomizeNPCLocations, - "enable_misc_locations": RandomizeMiscLocations, - "enable_health_upgrade_locations": RandomizeHealthLocations, - "enable_progressive_locations": RandomizeProgressiveLocationsOption, - "pool_type": PoolTypeOption, - "guaranteed_items": GuaranteedItemsOption, - "auto_equip": AutoEquipOption, - "lock_equip": LockEquipOption, - "no_weapon_requirements": NoWeaponRequirementsOption, - "randomize_infusion": RandomizeInfusionOption, - "randomize_infusion_percentage": RandomizeInfusionPercentageOption, - "randomize_weapon_level": RandomizeWeaponLevelOption, - "randomize_weapon_level_percentage": RandomizeWeaponLevelPercentageOption, - "min_levels_in_5": MinLevelsIn5WeaponPoolOption, - "max_levels_in_5": MaxLevelsIn5WeaponPoolOption, - "min_levels_in_10": MinLevelsIn10WeaponPoolOption, - "max_levels_in_10": MaxLevelsIn10WeaponPoolOption, - "early_banner": EarlySmallLothricBanner, - "late_basin_of_vows": LateBasinOfVowsOption, - "late_dlc": LateDLCOption, - "no_spell_requirements": NoSpellRequirementsOption, - "no_equip_load": NoEquipLoadOption, - "death_link": DeathLink, - "enable_dlc": EnableDLCOption, -} + +class ExcludedLocationBehaviorOption(Choice): + """How to choose items for excluded locations in DS3. + + - **Allow Useful:** Excluded locations can't have progression items, but they can have useful + items. + - **Forbid Useful:** Neither progression items nor useful items can be placed in excluded + locations. + - **Do Not Randomize:** Excluded locations always contain the same item as in vanilla Dark Souls + III. + + A "progression item" is anything that's required to unlock another location in some game. A + "useful item" is something each game defines individually, usually items that are quite + desirable but not strictly necessary. + """ + display_name = "Excluded Locations Behavior" + option_allow_useful = 1 + option_forbid_useful = 2 + option_do_not_randomize = 3 + default = 2 + + +class MissableLocationBehaviorOption(Choice): + """Which items can be placed in locations that can be permanently missed. + + - **Allow Useful:** Missable locations can't have progression items, but they can have useful + items. + - **Forbid Useful:** Neither progression items nor useful items can be placed in missable + locations. + - **Do Not Randomize:** Missable locations always contain the same item as in vanilla Dark Souls + III. + + A "progression item" is anything that's required to unlock another location in some game. A + "useful item" is something each game defines individually, usually items that are quite + desirable but not strictly necessary. + """ + display_name = "Missable Locations Behavior" + option_allow_useful = 1 + option_forbid_useful = 2 + option_do_not_randomize = 3 + default = 2 + + +@dataclass +class DarkSouls3Options(PerGameCommonOptions): + # Game Options + early_banner: EarlySmallLothricBanner + late_basin_of_vows: LateBasinOfVowsOption + late_dlc: LateDLCOption + death_link: DeathLink + enable_dlc: EnableDLCOption + enable_ngp: EnableNGPOption + + # Equipment + random_starting_loadout: RandomizeStartingLoadout + require_one_handed_starting_weapons: RequireOneHandedStartingWeapons + auto_equip: AutoEquipOption + lock_equip: LockEquipOption + no_equip_load: NoEquipLoadOption + no_weapon_requirements: NoWeaponRequirementsOption + no_spell_requirements: NoSpellRequirementsOption + + # Weapons + randomize_infusion: RandomizeInfusionOption + randomize_infusion_percentage: RandomizeInfusionPercentageOption + randomize_weapon_level: RandomizeWeaponLevelOption + randomize_weapon_level_percentage: RandomizeWeaponLevelPercentageOption + min_levels_in_5: MinLevelsIn5WeaponPoolOption + max_levels_in_5: MaxLevelsIn5WeaponPoolOption + min_levels_in_10: MinLevelsIn10WeaponPoolOption + max_levels_in_10: MaxLevelsIn10WeaponPoolOption + + # Item Smoothing + smooth_soul_items: SmoothSoulItemsOption + smooth_upgrade_items: SmoothUpgradeItemsOption + smooth_upgraded_weapons: SmoothUpgradedWeaponsOption + + # Enemies + randomize_enemies: RandomizeEnemiesOption + simple_early_bosses: SimpleEarlyBossesOption + scale_enemies: ScaleEnemiesOption + randomize_mimics_with_enemies: RandomizeMimicsWithEnemiesOption + randomize_small_crystal_lizards_with_enemies: RandomizeSmallCrystalLizardsWithEnemiesOption + reduce_harmless_enemies: ReduceHarmlessEnemiesOption + all_chests_are_mimics: AllChestsAreMimicsOption + impatient_mimics: ImpatientMimicsOption + random_enemy_preset: RandomEnemyPresetOption + + # Item & Location + exclude_locations: DS3ExcludeLocations + excluded_location_behavior: ExcludedLocationBehaviorOption + missable_location_behavior: MissableLocationBehaviorOption + + # Removed + pool_type: Removed + enable_weapon_locations: Removed + enable_shield_locations: Removed + enable_armor_locations: Removed + enable_ring_locations: Removed + enable_spell_locations: Removed + enable_key_locations: Removed + enable_boss_locations: Removed + enable_npc_locations: Removed + enable_misc_locations: Removed + enable_health_upgrade_locations: Removed + enable_progressive_locations: Removed + guaranteed_items: Removed + excluded_locations: Removed + missable_locations: Removed + + +option_groups = [ + OptionGroup("Equipment", [ + RandomizeStartingLoadout, + RequireOneHandedStartingWeapons, + AutoEquipOption, + LockEquipOption, + NoEquipLoadOption, + NoWeaponRequirementsOption, + NoSpellRequirementsOption, + ]), + OptionGroup("Weapons", [ + RandomizeInfusionOption, + RandomizeInfusionPercentageOption, + RandomizeWeaponLevelOption, + RandomizeWeaponLevelPercentageOption, + MinLevelsIn5WeaponPoolOption, + MaxLevelsIn5WeaponPoolOption, + MinLevelsIn10WeaponPoolOption, + MaxLevelsIn10WeaponPoolOption, + ]), + OptionGroup("Item Smoothing", [ + SmoothSoulItemsOption, + SmoothUpgradeItemsOption, + SmoothUpgradedWeaponsOption, + ]), + OptionGroup("Enemies", [ + RandomizeEnemiesOption, + SimpleEarlyBossesOption, + ScaleEnemiesOption, + RandomizeMimicsWithEnemiesOption, + RandomizeSmallCrystalLizardsWithEnemiesOption, + ReduceHarmlessEnemiesOption, + AllChestsAreMimicsOption, + ImpatientMimicsOption, + RandomEnemyPresetOption, + ]), + OptionGroup("Item & Location Options", [ + DS3ExcludeLocations, + ExcludedLocationBehaviorOption, + MissableLocationBehaviorOption, + ]) +] diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py index 020010981160..159a870c7658 100644 --- a/worlds/dark_souls_3/__init__.py +++ b/worlds/dark_souls_3/__init__.py @@ -1,15 +1,19 @@ # world/dark_souls_3/__init__.py -from typing import Dict, Set, List +from collections.abc import Sequence +from collections import defaultdict +import json +from logging import warning +from typing import cast, Any, Callable, Dict, Set, List, Optional, TextIO, Union -from BaseClasses import MultiWorld, Region, Item, Entrance, Tutorial, ItemClassification -from Options import Toggle +from BaseClasses import CollectionState, MultiWorld, Region, Location, LocationProgressType, Entrance, Tutorial, ItemClassification from worlds.AutoWorld import World, WebWorld -from worlds.generic.Rules import set_rule, add_rule, add_item_rule +from worlds.generic.Rules import CollectionRule, ItemRule, add_rule, add_item_rule -from .Items import DarkSouls3Item, DS3ItemCategory, item_dictionary, key_item_names, item_descriptions -from .Locations import DarkSouls3Location, DS3LocationCategory, location_tables, location_dictionary -from .Options import RandomizeWeaponLevelOption, PoolTypeOption, EarlySmallLothricBanner, dark_souls_options +from .Bosses import DS3BossInfo, all_bosses, default_yhorm_location +from .Items import DarkSouls3Item, DS3ItemData, Infusion, UsefulIf, filler_item_names, item_descriptions, item_dictionary, item_name_groups +from .Locations import DarkSouls3Location, DS3LocationData, location_tables, location_descriptions, location_dictionary, location_name_groups, region_order +from .Options import DarkSouls3Options, option_groups class DarkSouls3Web(WebWorld): @@ -34,91 +38,117 @@ class DarkSouls3Web(WebWorld): ) tutorials = [setup_en, setup_fr] - + option_groups = option_groups item_descriptions = item_descriptions + rich_text_options_doc = True class DarkSouls3World(World): """ Dark souls III is an Action role-playing game and is part of the Souls series developed by FromSoftware. - Played in a third-person perspective, players have access to various weapons, armour, magic, and consumables that + Played from a third-person perspective, players have access to various weapons, armour, magic, and consumables that they can use to fight their enemies. """ - game: str = "Dark Souls III" - option_definitions = dark_souls_options - topology_present: bool = True + game = "Dark Souls III" + options: DarkSouls3Options + options_dataclass = DarkSouls3Options web = DarkSouls3Web() base_id = 100000 - enabled_location_categories: Set[DS3LocationCategory] required_client_version = (0, 4, 2) - item_name_to_id = DarkSouls3Item.get_name_to_id() - location_name_to_id = DarkSouls3Location.get_name_to_id() - item_name_groups = { - "Cinders": { - "Cinders of a Lord - Abyss Watcher", - "Cinders of a Lord - Aldrich", - "Cinders of a Lord - Yhorm the Giant", - "Cinders of a Lord - Lothric Prince" - } + item_name_to_id = {data.name: data.ap_code for data in item_dictionary.values() if data.ap_code is not None} + location_name_to_id = { + location.name: location.ap_code + for locations in location_tables.values() + for location in locations + if location.ap_code is not None } + location_name_groups = location_name_groups + item_name_groups = item_name_groups + location_descriptions = location_descriptions + item_descriptions = item_descriptions + + yhorm_location: DS3BossInfo = default_yhorm_location + """If enemy randomization is enabled, this is the boss who Yhorm the Giant should replace. + + This is used to determine where the Storm Ruler can be placed. + """ + + all_excluded_locations: Set[str] = set() + """This is the same value as `self.options.exclude_locations.value` initially, but if + `options.exclude_locations` gets cleared due to `excluded_locations: allow_useful` this still + holds the old locations so we can ensure they don't get necessary items. + """ + + local_itempool: List[DarkSouls3Item] = [] + """The pool of all items within this particular world. This is a subset of + `self.multiworld.itempool`.""" def __init__(self, multiworld: MultiWorld, player: int): super().__init__(multiworld, player) - self.locked_items = [] - self.locked_locations = [] - self.main_path_locations = [] - self.enabled_location_categories = set() - - - def generate_early(self): - if self.multiworld.enable_weapon_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.WEAPON) - if self.multiworld.enable_shield_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.SHIELD) - if self.multiworld.enable_armor_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.ARMOR) - if self.multiworld.enable_ring_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.RING) - if self.multiworld.enable_spell_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.SPELL) - if self.multiworld.enable_npc_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.NPC) - if self.multiworld.enable_key_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.KEY) - if self.multiworld.early_banner[self.player] == EarlySmallLothricBanner.option_early_global: - self.multiworld.early_items[self.player]['Small Lothric Banner'] = 1 - elif self.multiworld.early_banner[self.player] == EarlySmallLothricBanner.option_early_local: - self.multiworld.local_early_items[self.player]['Small Lothric Banner'] = 1 - if self.multiworld.enable_boss_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.BOSS) - if self.multiworld.enable_misc_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.MISC) - if self.multiworld.enable_health_upgrade_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.HEALTH) - if self.multiworld.enable_progressive_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.PROGRESSIVE_ITEM) - - - def create_regions(self): - progressive_location_table = [] - if self.multiworld.enable_progressive_locations[self.player]: - progressive_location_table = [] + \ - location_tables["Progressive Items 1"] + \ - location_tables["Progressive Items 2"] + \ - location_tables["Progressive Items 3"] + \ - location_tables["Progressive Items 4"] - - if self.multiworld.enable_dlc[self.player].value: - progressive_location_table += location_tables["Progressive Items DLC"] - - if self.multiworld.enable_health_upgrade_locations[self.player]: - progressive_location_table += location_tables["Progressive Items Health"] - + self.all_excluded_locations = set() + + def generate_early(self) -> None: + self.all_excluded_locations.update(self.options.exclude_locations.value) + + # Inform Universal Tracker where Yhorm is being randomized to. + if hasattr(self.multiworld, "re_gen_passthrough"): + if "Dark Souls III" in self.multiworld.re_gen_passthrough: + if self.multiworld.re_gen_passthrough["Dark Souls III"]["options"]["randomize_enemies"]: + yhorm_data = self.multiworld.re_gen_passthrough["Dark Souls III"]["yhorm"] + for boss in all_bosses: + if yhorm_data.startswith(boss.name): + self.yhorm_location = boss + + # Randomize Yhorm manually so that we know where to place the Storm Ruler. + elif self.options.randomize_enemies: + self.yhorm_location = self.random.choice( + [boss for boss in all_bosses if self._allow_boss_for_yhorm(boss)]) + + # If Yhorm is early, make sure the Storm Ruler is easily available to avoid BK + # Iudex Gundyr is handled separately in _fill_local_items + if ( + self.yhorm_location.name == "Vordt of the Boreal Valley" or ( + self.yhorm_location.name == "Dancer of the Boreal Valley" and + not self.options.late_basin_of_vows + ) + ): + self.multiworld.local_early_items[self.player]["Storm Ruler"] = 1 + + def _allow_boss_for_yhorm(self, boss: DS3BossInfo) -> bool: + """Returns whether boss is a valid location for Yhorm in this seed.""" + + if not self.options.enable_dlc and boss.dlc: return False + + if not self._is_location_available("PC: Storm Ruler - boss room"): + # If the Storm Ruler isn't randomized, make sure the player can get to the normal Storm + # Ruler location before they need to get through Yhorm. + if boss.before_storm_ruler: return False + + # If the Small Doll also wasn't randomized, make sure Yhorm isn't blocking access to it + # or it won't be possible to get into Profaned Capital before beating him. + if ( + not self._is_location_available("CD: Small Doll - boss drop") + and boss.name in {"Crystal Sage", "Deacons of the Deep"} + ): + return False + + if boss.name != "Iudex Gundyr": return True + + # Cemetery of Ash has very few locations and all of them are excluded by default, so only + # allow Yhorm as Iudex Gundyr if there's at least one available location. + return any( + self._is_location_available(location) + and location.name not in self.all_excluded_locations + and location.name != "CA: Coiled Sword - boss drop" + for location in location_tables["Cemetery of Ash"] + ) + + def create_regions(self) -> None: # Create Vanilla Regions - regions: Dict[str, Region] = {} - regions["Menu"] = self.create_region("Menu", progressive_location_table) + regions: Dict[str, Region] = {"Menu": self.create_region("Menu", {})} regions.update({region_name: self.create_region(region_name, location_tables[region_name]) for region_name in [ + "Cemetery of Ash", "Firelink Shrine", "Firelink Shrine Bell Tower", "High Wall of Lothric", @@ -138,18 +168,15 @@ def create_regions(self): "Untended Graves", "Archdragon Peak", "Kiln of the First Flame", + "Greirat's Shop", + "Karla's Shop", ]}) - # Adds Path of the Dragon as an event item for Archdragon Peak access - potd_location = DarkSouls3Location(self.player, "CKG: Path of the Dragon", DS3LocationCategory.EVENT, "Path of the Dragon", None, regions["Consumed King's Garden"]) - potd_location.place_locked_item(Item("Path of the Dragon", ItemClassification.progression, None, self.player)) - regions["Consumed King's Garden"].locations.append(potd_location) - # Create DLC Regions - if self.multiworld.enable_dlc[self.player]: + if self.options.enable_dlc: regions.update({region_name: self.create_region(region_name, location_tables[region_name]) for region_name in [ - "Painted World of Ariandel 1", - "Painted World of Ariandel 2", + "Painted World of Ariandel (Before Contraption)", + "Painted World of Ariandel (After Contraption)", "Dreg Heap", "Ringed City", ]}) @@ -161,7 +188,9 @@ def create_connection(from_region: str, to_region: str): connection.connect(regions[to_region]) regions["Menu"].exits.append(Entrance(self.player, "New Game", regions["Menu"])) - self.multiworld.get_entrance("New Game", self.player).connect(regions["Firelink Shrine"]) + self.multiworld.get_entrance("New Game", self.player).connect(regions["Cemetery of Ash"]) + + create_connection("Cemetery of Ash", "Firelink Shrine") create_connection("Firelink Shrine", "High Wall of Lothric") create_connection("Firelink Shrine", "Firelink Shrine Bell Tower") @@ -169,6 +198,7 @@ def create_connection(from_region: str, to_region: str): create_connection("High Wall of Lothric", "Undead Settlement") create_connection("High Wall of Lothric", "Lothric Castle") + create_connection("High Wall of Lothric", "Greirat's Shop") create_connection("Undead Settlement", "Road of Sacrifices") @@ -185,6 +215,7 @@ def create_connection(from_region: str, to_region: str): create_connection("Irithyll Dungeon", "Archdragon Peak") create_connection("Irithyll Dungeon", "Profaned Capital") + create_connection("Irithyll Dungeon", "Karla's Shop") create_connection("Lothric Castle", "Consumed King's Garden") create_connection("Lothric Castle", "Grand Archives") @@ -192,357 +223,1349 @@ def create_connection(from_region: str, to_region: str): create_connection("Consumed King's Garden", "Untended Graves") # Connect DLC Regions - if self.multiworld.enable_dlc[self.player]: - create_connection("Cathedral of the Deep", "Painted World of Ariandel 1") - create_connection("Painted World of Ariandel 1", "Painted World of Ariandel 2") - create_connection("Painted World of Ariandel 2", "Dreg Heap") + if self.options.enable_dlc: + create_connection("Cathedral of the Deep", "Painted World of Ariandel (Before Contraption)") + create_connection("Painted World of Ariandel (Before Contraption)", + "Painted World of Ariandel (After Contraption)") + create_connection("Painted World of Ariandel (After Contraption)", "Dreg Heap") create_connection("Dreg Heap", "Ringed City") - # For each region, add the associated locations retrieved from the corresponding location_table def create_region(self, region_name, location_table) -> Region: new_region = Region(region_name, self.player, self.multiworld) + # Use this to un-exclude event locations so the fill doesn't complain about items behind + # them being unreachable. + excluded = self.options.exclude_locations.value + for location in location_table: - if location.category in self.enabled_location_categories: - new_location = DarkSouls3Location( - self.player, - location.name, - location.category, - location.default_item, - self.location_name_to_id[location.name], - new_region - ) + if self._is_location_available(location): + new_location = DarkSouls3Location(self.player, location, new_region) + if ( + # Exclude missable locations that don't allow useful items + location.missable and self.options.missable_location_behavior == "forbid_useful" + and not ( + # Unless they are excluded to a higher degree already + location.name in self.all_excluded_locations + and self.options.missable_location_behavior < self.options.excluded_location_behavior + ) + ) or ( + # Lift Chamber Key is missable. Exclude Lift-Chamber-Key-Locked locations if it isn't randomized + not self._is_location_available("FS: Lift Chamber Key - Leonhard") + and location.name == "HWL: Red Eye Orb - wall tower, miniboss" + ) or ( + # Chameleon is missable. Exclude Chameleon-locked locations if it isn't randomized + not self._is_location_available("AL: Chameleon - tomb after marrying Anri") + and location.name in {"RC: Dragonhead Shield - streets monument, across bridge", + "RC: Large Soul of a Crestfallen Knight - streets monument, across bridge", + "RC: Divine Blessing - streets monument, mob drop", "RC: Lapp's Helm - Lapp", + "RC: Lapp's Armor - Lapp", + "RC: Lapp's Gauntlets - Lapp", + "RC: Lapp's Leggings - Lapp"} + ): + new_location.progress_type = LocationProgressType.EXCLUDED else: - # Replace non-randomized progression items with events - event_item = self.create_item(location.default_item) - if event_item.classification != ItemClassification.progression: + # Don't allow missable duplicates of progression items to be expected progression. + if location.name in {"PC: Storm Ruler - Siegward", + "US: Pyromancy Flame - Cornyx", + "US: Tower Key - kill Irina"}: continue + # Replace non-randomized items with events that give the default item + event_item = ( + self.create_item(location.default_item_name) if location.default_item_name + else DarkSouls3Item.event(location.name, self.player) + ) + new_location = DarkSouls3Location( self.player, - location.name, - location.category, - location.default_item, - None, - new_region + location, + parent = new_region, + event = True, ) event_item.code = None new_location.place_locked_item(event_item) - - if region_name == "Menu": - add_item_rule(new_location, lambda item: not item.advancement) + if location.name in excluded: + excluded.remove(location.name) + # Only remove from all_excluded if excluded does not have priority over missable + if not (self.options.missable_location_behavior < self.options.excluded_location_behavior): + self.all_excluded_locations.remove(location.name) new_region.locations.append(new_location) self.multiworld.regions.append(new_region) return new_region - - def create_items(self): - dlc_enabled = self.multiworld.enable_dlc[self.player] == Toggle.option_true - - itempool_by_category = {category: [] for category in self.enabled_location_categories} + def create_items(self) -> None: + # Just used to efficiently deduplicate items + item_set: Set[str] = set() # Gather all default items on randomized locations + self.local_itempool = [] num_required_extra_items = 0 - for location in self.multiworld.get_locations(self.player): - if location.category in itempool_by_category: - if item_dictionary[location.default_item_name].category == DS3ItemCategory.SKIP: + for location in cast(List[DarkSouls3Location], self.multiworld.get_unfilled_locations(self.player)): + if not self._is_location_available(location.name): + raise Exception("DS3 generation bug: Added an unavailable location.") + + default_item_name = cast(str, location.data.default_item_name) + item = item_dictionary[default_item_name] + if item.skip: + num_required_extra_items += 1 + elif not item.unique: + self.local_itempool.append(self.create_item(default_item_name)) + else: + # For unique items, make sure there aren't duplicates in the item set even if there + # are multiple in-game locations that provide them. + if default_item_name in item_set: num_required_extra_items += 1 else: - itempool_by_category[location.category].append(location.default_item_name) - - # Replace each item category with a random sample of items of those types - if self.multiworld.pool_type[self.player] == PoolTypeOption.option_various: - def create_random_replacement_list(item_categories: Set[DS3ItemCategory], num_items: int): - candidates = [ - item.name for item - in item_dictionary.values() - if (item.category in item_categories and (not item.is_dlc or dlc_enabled)) - ] - return self.multiworld.random.sample(candidates, num_items) - - if DS3LocationCategory.WEAPON in self.enabled_location_categories: - itempool_by_category[DS3LocationCategory.WEAPON] = create_random_replacement_list( - { - DS3ItemCategory.WEAPON_UPGRADE_5, - DS3ItemCategory.WEAPON_UPGRADE_10, - DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE - }, - len(itempool_by_category[DS3LocationCategory.WEAPON]) - ) - if DS3LocationCategory.SHIELD in self.enabled_location_categories: - itempool_by_category[DS3LocationCategory.SHIELD] = create_random_replacement_list( - {DS3ItemCategory.SHIELD, DS3ItemCategory.SHIELD_INFUSIBLE}, - len(itempool_by_category[DS3LocationCategory.SHIELD]) - ) - if DS3LocationCategory.ARMOR in self.enabled_location_categories: - itempool_by_category[DS3LocationCategory.ARMOR] = create_random_replacement_list( - {DS3ItemCategory.ARMOR}, - len(itempool_by_category[DS3LocationCategory.ARMOR]) - ) - if DS3LocationCategory.RING in self.enabled_location_categories: - itempool_by_category[DS3LocationCategory.RING] = create_random_replacement_list( - {DS3ItemCategory.RING}, - len(itempool_by_category[DS3LocationCategory.RING]) - ) - if DS3LocationCategory.SPELL in self.enabled_location_categories: - itempool_by_category[DS3LocationCategory.SPELL] = create_random_replacement_list( - {DS3ItemCategory.SPELL}, - len(itempool_by_category[DS3LocationCategory.SPELL]) - ) - - itempool: List[DarkSouls3Item] = [] - for category in self.enabled_location_categories: - itempool += [self.create_item(name) for name in itempool_by_category[category]] - - # A list of items we can replace - removable_items = [item for item in itempool if item.classification != ItemClassification.progression] - - guaranteed_items = self.multiworld.guaranteed_items[self.player].value - for item_name in guaranteed_items: - # Break early just in case nothing is removable (if user is trying to guarantee more - # items than the pool can hold, for example) - if len(removable_items) == 0: - break - - num_existing_copies = len([item for item in itempool if item.name == item_name]) - for _ in range(guaranteed_items[item_name]): - if num_existing_copies > 0: - num_existing_copies -= 1 - continue - - if num_required_extra_items > 0: - # We can just add them instead of using "Soul of an Intrepid Hero" later - num_required_extra_items -= 1 - else: - if len(removable_items) == 0: - break + item_set.add(default_item_name) + self.local_itempool.append(self.create_item(default_item_name)) - # Try to construct a list of items with the same category that can be removed - # If none exist, just remove something at random - removable_shortlist = [ - item for item - in removable_items - if item_dictionary[item.name].category == item_dictionary[item_name].category - ] - if len(removable_shortlist) == 0: - removable_shortlist = removable_items + injectables = self._create_injectable_items(num_required_extra_items) + num_required_extra_items -= len(injectables) + self.local_itempool.extend(injectables) - removed_item = self.multiworld.random.choice(removable_shortlist) - removable_items.remove(removed_item) # To avoid trying to replace the same item twice - itempool.remove(removed_item) + # Extra filler items for locations containing skip items + self.local_itempool.extend(self.create_item(self.get_filler_item_name()) for _ in range(num_required_extra_items)) - itempool.append(self.create_item(item_name)) - - # Extra filler items for locations containing SKIP items - itempool += [self.create_filler() for _ in range(num_required_extra_items)] + # Potentially fill some items locally and remove them from the itempool + self._fill_local_items() # Add items to itempool - self.multiworld.itempool += itempool - - - def create_item(self, name: str) -> Item: - useful_categories = { - DS3ItemCategory.WEAPON_UPGRADE_5, - DS3ItemCategory.WEAPON_UPGRADE_10, - DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE, - DS3ItemCategory.SPELL, - } - data = self.item_name_to_id[name] - - if name in key_item_names: - item_classification = ItemClassification.progression - elif item_dictionary[name].category in useful_categories or name in {"Estus Shard", "Undead Bone Shard"}: - item_classification = ItemClassification.useful - else: - item_classification = ItemClassification.filler - - return DarkSouls3Item(name, item_classification, data, self.player) + self.multiworld.itempool += self.local_itempool + + def _create_injectable_items(self, num_required_extra_items: int) -> List[DarkSouls3Item]: + """Returns a list of items to inject into the multiworld instead of skipped items. + + If there isn't enough room to inject all the necessary progression items + that are in missable locations by default, this adds them to the + player's starting inventory. + """ + + all_injectable_items = [ + item for item + in item_dictionary.values() + if item.inject and (not item.is_dlc or self.options.enable_dlc) + ] + injectable_mandatory = [ + item for item in all_injectable_items + if item.classification == ItemClassification.progression + ] + injectable_optional = [ + item for item in all_injectable_items + if item.classification != ItemClassification.progression + ] + + number_to_inject = min(num_required_extra_items, len(all_injectable_items)) + items = ( + self.random.sample( + injectable_mandatory, + k=min(len(injectable_mandatory), number_to_inject) + ) + + self.random.sample( + injectable_optional, + k=max(0, number_to_inject - len(injectable_mandatory)) + ) + ) + + if number_to_inject < len(injectable_mandatory): + # It's worth considering the possibility of _removing_ unimportant + # items from the pool to inject these instead rather than just + # making them part of the starting health back + for item in injectable_mandatory: + if item in items: continue + self.multiworld.push_precollected(self.create_item(item)) + warning( + f"Couldn't add \"{item.name}\" to the item pool for " + + f"{self.player_name}. Adding it to the starting " + + f"inventory instead." + ) + return [self.create_item(item) for item in items] + + def create_item(self, item: Union[str, DS3ItemData]) -> DarkSouls3Item: + data = item if isinstance(item, DS3ItemData) else item_dictionary[item] + classification = None + if self.multiworld and data.useful_if != UsefulIf.DEFAULT and ( + ( + data.useful_if == UsefulIf.BASE and + not self.options.enable_dlc and + not self.options.enable_ngp + ) + or (data.useful_if == UsefulIf.NO_DLC and not self.options.enable_dlc) + or (data.useful_if == UsefulIf.NO_NGP and not self.options.enable_ngp) + ): + classification = ItemClassification.useful + + if ( + self.options.randomize_weapon_level != "none" + and data.category.upgrade_level + # Because we require the Pyromancy Flame to be available early, don't upgrade it so it + # doesn't get shuffled around by weapon smoothing. + and data.name != "Pyromancy Flame" + ): + # if the user made an error and set a min higher than the max we default to the max + max_5 = self.options.max_levels_in_5.value + min_5 = min(self.options.min_levels_in_5.value, max_5) + max_10 = self.options.max_levels_in_10.value + min_10 = min(self.options.min_levels_in_10.value, max_10) + weapon_level_percentage = self.options.randomize_weapon_level_percentage + + if self.random.randint(0, 99) < weapon_level_percentage: + if data.category.upgrade_level == 5: + data = data.upgrade(self.random.randint(min_5, max_5)) + elif data.category.upgrade_level == 10: + data = data.upgrade(self.random.randint(min_10, max_10)) + + if self.options.randomize_infusion and data.category.is_infusible: + infusion_percentage = self.options.randomize_infusion_percentage + if self.random.randint(0, 99) < infusion_percentage: + data = data.infuse(self.random.choice(list(Infusion))) + + return DarkSouls3Item(self.player, data, classification=classification) + + def _fill_local_items(self) -> None: + """Removes certain items from the item pool and manually places them in the local world. + + We can't do this in pre_fill because the itempool may not be modified after create_items. + """ + # If Yhorm is at Iudex Gundyr, Storm Ruler must be randomized, so it can always be moved. + # Fill this manually so that, if very few slots are available in Cemetery of Ash, this + # doesn't get locked out by bad rolls on the next two fills. + if self.yhorm_location.name == "Iudex Gundyr": + self._fill_local_item("Storm Ruler", ["Cemetery of Ash"], + lambda location: location.name != "CA: Coiled Sword - boss drop") + + # If the Coiled Sword is vanilla, it is early enough and doesn't need to be placed. + # Don't place this in the multiworld because it's necessary almost immediately, and don't + # mark it as a blocker for HWL because having a miniscule Sphere 1 screws with progression balancing. + if self._is_location_available("CA: Coiled Sword - boss drop"): + self._fill_local_item("Coiled Sword", ["Cemetery of Ash", "Firelink Shrine"]) + + # If the HWL Raw Gem is vanilla, it is early enough and doesn't need to be removed. If + # upgrade smoothing is enabled, make sure one raw gem is available early for SL1 players + if ( + self._is_location_available("HWL: Raw Gem - fort roof, lizard") + and self.options.smooth_upgrade_items + ): + self._fill_local_item("Raw Gem", [ + "Cemetery of Ash", + "Firelink Shrine", + "High Wall of Lothric" + ]) + + def _fill_local_item( + self, name: str, + regions: List[str], + additional_condition: Optional[Callable[[DS3LocationData], bool]] = None, + ) -> None: + """Chooses a valid location for the item with the given name and places it there. + + This always chooses a local location among the given regions. If additional_condition is + passed, only locations meeting that condition will be considered. + + If the item could not be placed, it will be added to starting inventory. + """ + item = next((item for item in self.local_itempool if item.name == name), None) + if not item: return + + candidate_locations = [ + location for location in ( + self.multiworld.get_location(location.name, self.player) + for region in regions + for location in location_tables[region] + if self._is_location_available(location) + and not location.missable + and not location.conditional + and (not additional_condition or additional_condition(location)) + ) + # We can't use location.progress_type here because it's not set + # until after `set_rules()` runs. + if not location.item and location.name not in self.all_excluded_locations + and location.item_rule(item) + ] + + self.local_itempool.remove(item) + + if not candidate_locations: + warning(f"Couldn't place \"{name}\" in a valid location for {self.player_name}. Adding it to starting inventory instead.") + location = next( + (location for location in self._get_our_locations() if location.data.default_item_name == item.name), + None + ) + if location: self._replace_with_filler(location) + self.multiworld.push_precollected(self.create_item(name)) + return + + location = self.random.choice(candidate_locations) + location.place_locked_item(item) + + def _replace_with_filler(self, location: DarkSouls3Location) -> None: + """If possible, choose a filler item to replace location's current contents with.""" + if location.locked: return + + # Try 10 filler items. If none of them work, give up and leave it as-is. + for _ in range(0, 10): + candidate = self.create_filler() + if location.item_rule(candidate): + location.item = candidate + return def get_filler_item_name(self) -> str: - return "Soul of an Intrepid Hero" - + return self.random.choice(filler_item_names) def set_rules(self) -> None: - # Define the access rules to the entrances - set_rule(self.multiworld.get_entrance("Go To Undead Settlement", self.player), - lambda state: state.has("Small Lothric Banner", self.player)) - set_rule(self.multiworld.get_entrance("Go To Lothric Castle", self.player), - lambda state: state.has("Basin of Vows", self.player)) - set_rule(self.multiworld.get_entrance("Go To Irithyll of the Boreal Valley", self.player), - lambda state: state.has("Small Doll", self.player)) - set_rule(self.multiworld.get_entrance("Go To Archdragon Peak", self.player), - lambda state: state.has("Path of the Dragon", self.player)) - set_rule(self.multiworld.get_entrance("Go To Grand Archives", self.player), - lambda state: state.has("Grand Archives Key", self.player)) - set_rule(self.multiworld.get_entrance("Go To Kiln of the First Flame", self.player), - lambda state: state.has("Cinders of a Lord - Abyss Watcher", self.player) and - state.has("Cinders of a Lord - Yhorm the Giant", self.player) and - state.has("Cinders of a Lord - Aldrich", self.player) and - state.has("Cinders of a Lord - Lothric Prince", self.player)) - - if self.multiworld.late_basin_of_vows[self.player] == Toggle.option_true: - add_rule(self.multiworld.get_entrance("Go To Lothric Castle", self.player), - lambda state: state.has("Small Lothric Banner", self.player)) + randomized_items = {item.name for item in self.local_itempool} + + self._add_shop_rules() + self._add_npc_rules() + self._add_transposition_rules() + self._add_crow_rules() + self._add_allow_useful_location_rules() + self._add_early_item_rules(randomized_items) + + self._add_entrance_rule("Firelink Shrine Bell Tower", "Tower Key") + self._add_entrance_rule("Undead Settlement", lambda state: ( + state.has("Small Lothric Banner", self.player) + and self._can_get(state, "HWL: Soul of Boreal Valley Vordt") + )) + self._add_entrance_rule("Road of Sacrifices", "US -> RS") + self._add_entrance_rule( + "Cathedral of the Deep", + lambda state: self._can_get(state, "RS: Soul of a Crystal Sage") + ) + self._add_entrance_rule("Farron Keep", "RS -> FK") + self._add_entrance_rule( + "Catacombs of Carthus", + lambda state: self._can_get(state, "FK: Soul of the Blood of the Wolf") + ) + self._add_entrance_rule("Irithyll Dungeon", "IBV -> ID") + self._add_entrance_rule( + "Lothric Castle", + lambda state: self._can_get(state, "HWL: Soul of the Dancer") + ) + self._add_entrance_rule( + "Untended Graves", + lambda state: self._can_get(state, "CKG: Soul of Consumed Oceiros") + ) + self._add_entrance_rule("Irithyll of the Boreal Valley", lambda state: ( + state.has("Small Doll", self.player) + and self._can_get(state, "CC: Soul of High Lord Wolnir") + )) + self._add_entrance_rule( + "Anor Londo", + lambda state: self._can_get(state, "IBV: Soul of Pontiff Sulyvahn") + ) + self._add_entrance_rule("Archdragon Peak", "Path of the Dragon") + self._add_entrance_rule("Grand Archives", lambda state: ( + state.has("Grand Archives Key", self.player) + and self._can_get(state, "LC: Soul of Dragonslayer Armour") + )) + self._add_entrance_rule("Kiln of the First Flame", lambda state: ( + state.has("Cinders of a Lord - Abyss Watcher", self.player) + and state.has("Cinders of a Lord - Yhorm the Giant", self.player) + and state.has("Cinders of a Lord - Aldrich", self.player) + and state.has("Cinders of a Lord - Lothric Prince", self.player) + and state.has("Transposing Kiln", self.player) + )) + + if self.options.late_basin_of_vows: + self._add_entrance_rule("Lothric Castle", lambda state: ( + state.has("Small Lothric Banner", self.player) + # Make sure these are actually available early. + and ( + "Transposing Kiln" not in randomized_items + or state.has("Transposing Kiln", self.player) + ) and ( + "Pyromancy Flame" not in randomized_items + or state.has("Pyromancy Flame", self.player) + ) + # This isn't really necessary, but it ensures that the game logic knows players will + # want to do Lothric Castle after at least being _able_ to access Catacombs. This is + # useful for smooth item placement. + and self._has_any_scroll(state) + )) + + if self.options.late_basin_of_vows > 1: # After Small Doll + self._add_entrance_rule("Lothric Castle", "Small Doll") # DLC Access Rules Below - if self.multiworld.enable_dlc[self.player]: - set_rule(self.multiworld.get_entrance("Go To Ringed City", self.player), - lambda state: state.has("Small Envoy Banner", self.player)) + if self.options.enable_dlc: + self._add_entrance_rule("Painted World of Ariandel (Before Contraption)", "CD -> PW1") + self._add_entrance_rule("Painted World of Ariandel (After Contraption)", "Contraption Key") + self._add_entrance_rule( + "Dreg Heap", + lambda state: self._can_get(state, "PW2: Soul of Sister Friede") + ) + self._add_entrance_rule("Ringed City", lambda state: ( + state.has("Small Envoy Banner", self.player) + and self._can_get(state, "DH: Soul of the Demon Prince") + )) - # If key items are randomized, must have contraption key to enter second half of Ashes DLC - # If key items are not randomized, Contraption Key is guaranteed to be accessible before it is needed - if self.multiworld.enable_key_locations[self.player] == Toggle.option_true: - add_rule(self.multiworld.get_entrance("Go To Painted World of Ariandel 2", self.player), - lambda state: state.has("Contraption Key", self.player)) + if self.options.late_dlc: + self._add_entrance_rule( + "Painted World of Ariandel (Before Contraption)", + lambda state: state.has("Small Doll", self.player) and self._has_any_scroll(state)) - if self.multiworld.late_dlc[self.player] == Toggle.option_true: - add_rule(self.multiworld.get_entrance("Go To Painted World of Ariandel 1", self.player), - lambda state: state.has("Small Doll", self.player)) + if self.options.late_dlc > 1: # After Basin + self._add_entrance_rule("Painted World of Ariandel (Before Contraption)", "Basin of Vows") # Define the access rules to some specific locations - set_rule(self.multiworld.get_location("PC: Cinders of a Lord - Yhorm the Giant", self.player), - lambda state: state.has("Storm Ruler", self.player)) - - if self.multiworld.enable_ring_locations[self.player] == Toggle.option_true: - set_rule(self.multiworld.get_location("ID: Bellowing Dragoncrest Ring", self.player), - lambda state: state.has("Jailbreaker's Key", self.player)) - set_rule(self.multiworld.get_location("ID: Covetous Gold Serpent Ring", self.player), - lambda state: state.has("Old Cell Key", self.player)) - set_rule(self.multiworld.get_location("UG: Hornet Ring", self.player), - lambda state: state.has("Small Lothric Banner", self.player)) - - if self.multiworld.enable_npc_locations[self.player] == Toggle.option_true: - set_rule(self.multiworld.get_location("HWL: Greirat's Ashes", self.player), - lambda state: state.has("Cell Key", self.player)) - set_rule(self.multiworld.get_location("HWL: Blue Tearstone Ring", self.player), - lambda state: state.has("Cell Key", self.player)) - set_rule(self.multiworld.get_location("ID: Karla's Ashes", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - set_rule(self.multiworld.get_location("ID: Karla's Pointed Hat", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - set_rule(self.multiworld.get_location("ID: Karla's Coat", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - set_rule(self.multiworld.get_location("ID: Karla's Gloves", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - set_rule(self.multiworld.get_location("ID: Karla's Trousers", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - - if self.multiworld.enable_misc_locations[self.player] == Toggle.option_true: - set_rule(self.multiworld.get_location("ID: Prisoner Chief's Ashes", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - - if self.multiworld.enable_boss_locations[self.player] == Toggle.option_true: - set_rule(self.multiworld.get_location("PC: Soul of Yhorm the Giant", self.player), - lambda state: state.has("Storm Ruler", self.player)) - set_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player), - lambda state: state.has("Basin of Vows", self.player)) - - # Lump Soul of the Dancer in with LC for locations that should not be reachable - # before having access to US. (Prevents requiring getting Basin to fight Dancer to get SLB to go to US) - if self.multiworld.late_basin_of_vows[self.player] == Toggle.option_true: - add_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player), - lambda state: state.has("Small Lothric Banner", self.player)) - - gotthard_corpse_rule = lambda state: \ - (state.can_reach("AL: Cinders of a Lord - Aldrich", "Location", self.player) and - state.can_reach("PC: Cinders of a Lord - Yhorm the Giant", "Location", self.player)) - - set_rule(self.multiworld.get_location("LC: Grand Archives Key", self.player), gotthard_corpse_rule) - - if self.multiworld.enable_weapon_locations[self.player] == Toggle.option_true: - set_rule(self.multiworld.get_location("LC: Gotthard Twinswords", self.player), gotthard_corpse_rule) - - self.multiworld.completion_condition[self.player] = lambda state: \ - state.has("Cinders of a Lord - Abyss Watcher", self.player) and \ - state.has("Cinders of a Lord - Yhorm the Giant", self.player) and \ - state.has("Cinders of a Lord - Aldrich", self.player) and \ - state.has("Cinders of a Lord - Lothric Prince", self.player) + if self._is_location_available("FS: Lift Chamber Key - Leonhard"): + self._add_location_rule("HWL: Red Eye Orb - wall tower, miniboss", + "Lift Chamber Key") + self._add_location_rule("ID: Bellowing Dragoncrest Ring - drop from B1 towards pit", + "Jailbreaker's Key") + self._add_location_rule("ID: Covetous Gold Serpent Ring - Siegward's cell", "Old Cell Key") + self._add_location_rule([ + "UG: Hornet Ring - environs, right of main path after killing FK boss", + "UG: Wolf Knight Helm - shop after killing FK boss", + "UG: Wolf Knight Armor - shop after killing FK boss", + "UG: Wolf Knight Gauntlets - shop after killing FK boss", + "UG: Wolf Knight Leggings - shop after killing FK boss" + ], lambda state: self._can_get(state, "FK: Cinders of a Lord - Abyss Watcher")) + self._add_location_rule( + "ID: Prisoner Chief's Ashes - B2 near, locked cell by stairs", + "Jailer's Key Ring" + ) + self._add_entrance_rule("Karla's Shop", "Jailer's Key Ring") + + # The static randomizer edits events to guarantee that Greirat won't go to Lothric until + # Grand Archives is available, so his shop will always be available one way or another. + self._add_entrance_rule("Greirat's Shop", "Cell Key") + + self._add_location_rule("HWL: Soul of the Dancer", "Basin of Vows") + + # Lump Soul of the Dancer in with LC for locations that should not be reachable + # before having access to US. (Prevents requiring getting Basin to fight Dancer to get SLB to go to US) + if self.options.late_basin_of_vows: + self._add_location_rule("HWL: Soul of the Dancer", lambda state: ( + state.has("Small Lothric Banner", self.player) + # Make sure these are actually available early. + and ( + "Transposing Kiln" not in randomized_items + or state.has("Transposing Kiln", self.player) + ) and ( + "Pyromancy Flame" not in randomized_items + or state.has("Pyromancy Flame", self.player) + ) + # This isn't really necessary, but it ensures that the game logic knows players will + # want to do Lothric Castle after at least being _able_ to access Catacombs. This is + # useful for smooth item placement. + and self._has_any_scroll(state) + )) + + if self.options.late_basin_of_vows > 1: # After Small Doll + self._add_location_rule("HWL: Soul of the Dancer", "Small Doll") + + self._add_location_rule([ + "LC: Grand Archives Key - by Grand Archives door, after PC and AL bosses", + "LC: Gotthard Twinswords - by Grand Archives door, after PC and AL bosses" + ], lambda state: ( + self._can_get(state, "AL: Cinders of a Lord - Aldrich") and + self._can_get(state, "PC: Cinders of a Lord - Yhorm the Giant") + )) + + self._add_location_rule([ + "FS: Morne's Great Hammer - Eygon", + "FS: Moaning Shield - Eygon" + ], lambda state: ( + self._can_get(state, "LC: Soul of Dragonslayer Armour") and + self._can_get(state, "FK: Soul of the Blood of the Wolf") + )) + + self._add_location_rule([ + "CKG: Drakeblood Helm - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Armor - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Gauntlets - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Leggings - tomb, after killing AP mausoleum NPC", + ], lambda state: self._can_go_to(state, "Archdragon Peak")) + + self._add_location_rule([ + "FK: Havel's Helm - upper keep, after killing AP belfry roof NPC", + "FK: Havel's Armor - upper keep, after killing AP belfry roof NPC", + "FK: Havel's Gauntlets - upper keep, after killing AP belfry roof NPC", + "FK: Havel's Leggings - upper keep, after killing AP belfry roof NPC", + ], lambda state: self._can_go_to(state, "Archdragon Peak")) + + self._add_location_rule([ + "RC: Dragonhead Shield - streets monument, across bridge", + "RC: Large Soul of a Crestfallen Knight - streets monument, across bridge", + "RC: Divine Blessing - streets monument, mob drop", + "RC: Lapp's Helm - Lapp", + "RC: Lapp's Armor - Lapp", + "RC: Lapp's Gauntlets - Lapp", + "RC: Lapp's Leggings - Lapp", + ], "Chameleon") + + # Forbid shops from carrying items with multiple counts (the static randomizer has its own + # logic for choosing how many shop items to sell), and from carrying soul items. + for location in location_dictionary.values(): + if location.shop: + self._add_item_rule( + location.name, + lambda item: ( + item.player != self.player or + (item.data.count == 1 and not item.data.souls) + ) + ) + + # This particular location is bugged, and will drop two copies of whatever item is placed + # there. + if self._is_location_available("US: Young White Branch - by white tree #2"): + self._add_item_rule( + "US: Young White Branch - by white tree #2", + lambda item: item.player == self.player and not item.data.unique + ) + + # Make sure the Storm Ruler is available BEFORE Yhorm the Giant + if self.yhorm_location.name == "Ancient Wyvern": + # This is a white lie, you can get to a bunch of items in AP before you beat the Wyvern, + # but this saves us from having to split the entire region in two just to mark which + # specific items are before and after. + self._add_entrance_rule("Archdragon Peak", "Storm Ruler") + for location in self.yhorm_location.locations: + self._add_location_rule(location, "Storm Ruler") + + self.multiworld.completion_condition[self.player] = lambda state: self._can_get(state, "KFF: Soul of the Lords") + + def _add_shop_rules(self) -> None: + """Adds rules for items unlocked in shops.""" + + # Ashes + ashes = { + "Mortician's Ashes": ["Alluring Skull", "Ember", "Grave Key"], + "Dreamchaser's Ashes": ["Life Ring", "Hidden Blessing"], + "Paladin's Ashes": ["Lloyd's Shield Ring"], + "Grave Warden's Ashes": ["Ember"], + "Prisoner Chief's Ashes": [ + "Karla's Pointed Hat", "Karla's Coat", "Karla's Gloves", "Karla's Trousers" + ], + "Xanthous Ashes": ["Xanthous Overcoat", "Xanthous Gloves", "Xanthous Trousers"], + "Dragon Chaser's Ashes": ["Ember"], + "Easterner's Ashes": [ + "Washing Pole", "Eastern Helm", "Eastern Armor", "Eastern Gauntlets", + "Eastern Leggings", "Wood Grain Ring", + ], + "Captain's Ashes": [ + "Millwood Knight Helm", "Millwood Knight Armor", "Millwood Knight Gauntlets", + "Millwood Knight Leggings", "Refined Gem", + ] + } + for (ash, items) in ashes.items(): + self._add_location_rule([f"FS: {item} - {ash}" for item in items], ash) + + # Shop unlocks + shop_unlocks = { + "Cornyx": [ + ( + "Great Swamp Pyromancy Tome", "Great Swamp Tome", + ["Poison Mist", "Fire Orb", "Profuse Sweat", "Bursting Fireball"] + ), + ( + "Carthus Pyromancy Tome", "Carthus Tome", + ["Acid Surge", "Carthus Flame Arc", "Carthus Beacon"] + ), + ("Izalith Pyromancy Tome", "Izalith Tome", ["Great Chaos Fire Orb", "Chaos Storm"]), + ], + "Irina": [ + ( + "Braille Divine Tome of Carim", "Tome of Carim", + ["Med Heal", "Tears of Denial", "Force"] + ), + ( + "Braille Divine Tome of Lothric", "Tome of Lothric", + ["Bountiful Light", "Magic Barrier", "Blessed Weapon"] + ), + ], + "Orbeck": [ + ("Sage's Scroll", "Sage's Scroll", ["Great Farron Dart", "Farron Hail"]), + ( + "Golden Scroll", "Golden Scroll", + [ + "Cast Light", "Repair", "Hidden Weapon", "Hidden Body", + "Twisted Wall of Light" + ], + ), + ("Logan's Scroll", "Logan's Scroll", ["Homing Soulmass", "Soul Spear"]), + ( + "Crystal Scroll", "Crystal Scroll", + ["Homing Crystal Soulmass", "Crystal Soul Spear", "Crystal Magic Weapon"] + ), + ], + "Karla": [ + ("Quelana Pyromancy Tome", "Quelana Tome", ["Firestorm", "Rapport", "Fire Whip"]), + ( + "Grave Warden Pyromancy Tome", "Grave Warden Tome", + ["Black Flame", "Black Fire Orb"] + ), + ("Deep Braille Divine Tome", "Deep Braille Tome", ["Gnaw", "Deep Protection"]), + ( + "Londor Braille Divine Tome", "Londor Tome", + ["Vow of Silence", "Dark Blade", "Dead Again"] + ), + ], + } + for (shop, unlocks) in shop_unlocks.items(): + for (key, key_name, items) in unlocks: + self._add_location_rule( + [f"FS: {item} - {shop} for {key_name}" for item in items], key) + + def _add_npc_rules(self) -> None: + """Adds rules for items accessible via NPC quests. + + We list missable locations here even though they never contain progression items so that the + game knows what sphere they're in. This is especially useful for item smoothing. (We could + add rules for boss transposition items as well, but then we couldn't freely reorder boss + soul locations for smoothing.) + + Generally, for locations that can be accessed early by killing NPCs, we set up requirements + assuming the player _doesn't_ so they aren't forced to start killing allies to advance the + quest. + """ + + ## Greirat + + self._add_location_rule([ + "FS: Divine Blessing - Greirat from US", + "FS: Ember - Greirat from US", + ], lambda state: ( + self._can_go_to(state, "Undead Settlement") + and state.has("Loretta's Bone", self.player) + )) + self._add_location_rule([ + "FS: Divine Blessing - Greirat from IBV", + "FS: Hidden Blessing - Greirat from IBV", + "FS: Titanite Scale - Greirat from IBV", + "FS: Twinkling Titanite - Greirat from IBV", + "FS: Ember - shop for Greirat's Ashes" + ], lambda state: ( + self._can_go_to(state, "Irithyll of the Boreal Valley") + and self._can_get(state, "FS: Divine Blessing - Greirat from US") + # Either Patches or Siegward can save Greirat, but we assume the player will want to use + # Patches because it's harder to screw up + and self._can_get(state, "CD: Shotel - Patches") + )) + self._add_location_rule([ + "FS: Ember - shop for Greirat's Ashes", + ], lambda state: ( + self._can_go_to(state, "Grand Archives") + and self._can_get(state, "FS: Divine Blessing - Greirat from IBV") + )) + + ## Patches + + # Patches will only set up shop in Firelink once he's tricked you in the bell tower. He'll + # only do _that_ once you've spoken to Siegward after killing the Fire Demon and lit the + # Rosaria's Bed Chamber bonfire. He _won't_ set up shop in the Cathedral if you light the + # Rosaria's Bed Chamber bonfire before getting tricked by him, so we assume these locations + # require the bell tower. + self._add_location_rule([ + "CD: Shotel - Patches", + "CD: Ember - Patches", + "FS: Rusted Gold Coin - don't forgive Patches" + ], lambda state: ( + self._can_go_to(state, "Firelink Shrine Bell Tower") + and self._can_go_to(state, "Cathedral of the Deep") + )) + + # Patches sells this after you tell him to search for Greirat in Grand Archives + self._add_location_rule([ + "FS: Hidden Blessing - Patches after searching GA" + ], lambda state: ( + self._can_get(state, "CD: Shotel - Patches") + and self._can_get(state, "FS: Ember - shop for Greirat's Ashes") + )) + + # Only make the player kill Patches once all his other items are available + self._add_location_rule([ + "CD: Winged Spear - kill Patches", + # You don't _have_ to kill him for this, but he has to be in Firelink at the same time + # as Greirat to get it in the shop and that may not be feasible if the player progresses + # Greirat's quest much faster. + "CD: Horsehoof Ring - Patches", + ], lambda state: ( + self._can_get(state, "FS: Hidden Blessing - Patches after searching GA") + and self._can_get(state, "FS: Rusted Gold Coin - don't forgive Patches") + )) + + ## Leonhard + + self._add_location_rule([ + # Talk to Leonhard in Firelink with a Pale Tongue after lighting Cliff Underside or + # killing Greatwood. This doesn't consume the Pale Tongue, it just has to be in + # inventory + "FS: Lift Chamber Key - Leonhard", + # Progress Leonhard's quest and then return to Rosaria after lighting Profaned Capital + "CD: Black Eye Orb - Rosaria from Leonhard's quest", + ], "Pale Tongue") + + self._add_location_rule([ + "CD: Black Eye Orb - Rosaria from Leonhard's quest", + ], lambda state: ( + # The Black Eye Orb location won't spawn until you kill the HWL miniboss and resting at + # the Profaned Capital bonfire. + self._can_get(state, "HWL: Red Eye Orb - wall tower, miniboss") + and self._can_go_to(state, "Profaned Capital") + )) + + # Perhaps counterintuitively, you CAN fight Leonhard before you access the location that + # would normally give you the Black Eye Orb. + self._add_location_rule([ + "AL: Crescent Moon Sword - Leonhard drop", + "AL: Silver Mask - Leonhard drop", + "AL: Soul of Rosaria - Leonhard drop", + ] + [ + f"FS: {item} - shop after killing Leonhard" + for item in ["Leonhard's Garb", "Leonhard's Gauntlets", "Leonhard's Trousers"] + ], "Black Eye Orb") + + ## Hawkwood + + # After Hawkwood leaves and once you have the Torso Stone, you can fight him for dragon + # stones. Andre will give Swordgrass as a hint as well + self._add_location_rule([ + "FK: Twinkling Dragon Head Stone - Hawkwood drop", + "FS: Hawkwood's Swordgrass - Andre after gesture in AP summit" + ], lambda state: ( + self._can_get(state, "FS: Hawkwood's Shield - gravestone after Hawkwood leaves") + and state.has("Twinkling Dragon Torso Stone", self.player) + )) + + ## Siegward + + # Unlock Siegward's cell after progressing his quest + self._add_location_rule([ + "ID: Titanite Slab - Siegward", + ], lambda state: ( + state.has("Old Cell Key", self.player) + # Progressing Siegward's quest requires buying his armor from Patches. + and self._can_get(state, "CD: Shotel - Patches") + )) + + # These drop after completing Siegward's quest and talking to him in Yhorm's arena + self._add_location_rule([ + "PC: Siegbräu - Siegward after killing boss", + "PC: Storm Ruler - Siegward", + "PC: Pierce Shield - Siegward", + ], lambda state: ( + self._can_get(state, "ID: Titanite Slab - Siegward") + and self._can_get(state, "PC: Soul of Yhorm the Giant") + )) + + ## Sirris + + # Kill Greatwood and turn in Dreamchaser's Ashes to trigger this opportunity for invasion + self._add_location_rule([ + "FS: Mail Breaker - Sirris for killing Creighton", + "FS: Silvercat Ring - Sirris for killing Creighton", + "IBV: Creighton's Steel Mask - bridge after killing Creighton", + "IBV: Mirrah Chain Gloves - bridge after killing Creighton", + "IBV: Mirrah Chain Leggings - bridge after killing Creighton", + "IBV: Mirrah Chain Mail - bridge after killing Creighton", + "IBV: Dragonslayer's Axe - Creighton drop", + # Killing Pontiff without progressing Sirris's quest will break it. + "IBV: Soul of Pontiff Sulyvahn" + ], lambda state: ( + self._can_get(state, "US: Soul of the Rotted Greatwood") + and state.has("Dreamchaser's Ashes", self.player) + )) + # Add indirect condition since reaching AL requires defeating Pontiff which requires defeating Greatwood in US + self.multiworld.register_indirect_condition( + self.get_region("Undead Settlement"), + self.get_entrance("Go To Anor Londo") + ) + + # Kill Creighton and Aldrich to trigger this opportunity for invasion + self._add_location_rule([ + "FS: Budding Green Blossom - shop after killing Creighton and AL boss", + "FS: Sunset Shield - by grave after killing Hodrick w/Sirris", + "US: Sunset Helm - Pit of Hollows after killing Hodrick w/Sirris", + "US: Sunset Armor - pit of hollows after killing Hodrick w/Sirris", + "US: Sunset Gauntlets - pit of hollows after killing Hodrick w/Sirris", + "US: Sunset Leggings - pit of hollows after killing Hodrick w/Sirris", + ], lambda state: ( + self._can_get(state, "FS: Mail Breaker - Sirris for killing Creighton") + and self._can_get(state, "AL: Soul of Aldrich") + )) + + # Kill Hodrick and Twin Princes to trigger the end of the quest + self._add_location_rule([ + "FS: Sunless Talisman - Sirris, kill GA boss", + "FS: Sunless Veil - shop, Sirris quest, kill GA boss", + "FS: Sunless Armor - shop, Sirris quest, kill GA boss", + "FS: Sunless Gauntlets - shop, Sirris quest, kill GA boss", + "FS: Sunless Leggings - shop, Sirris quest, kill GA boss", + # Killing Yorshka will anger Sirris and stop her quest, so don't expect it until the + # quest is done + "AL: Yorshka's Chime - kill Yorshka", + ], lambda state: ( + self._can_get(state, "US: Soul of the Rotted Greatwood") + and state.has("Dreamchaser's Ashes", self.player) + )) + + ## Cornyx + + self._add_location_rule([ + "US: Old Sage's Blindfold - kill Cornyx", + "US: Cornyx's Garb - kill Cornyx", + "US: Cornyx's Wrap - kill Cornyx", + "US: Cornyx's Skirt - kill Cornyx", + ], lambda state: ( + state.has("Great Swamp Pyromancy Tome", self.player) + and state.has("Carthus Pyromancy Tome", self.player) + and state.has("Izalith Pyromancy Tome", self.player) + )) + + self._add_location_rule([ + "US: Old Sage's Blindfold - kill Cornyx", "US: Cornyx's Garb - kill Cornyx", + "US: Cornyx's Wrap - kill Cornyx", "US: Cornyx's Skirt - kill Cornyx" + ], lambda state: ( + state.has("Great Swamp Pyromancy Tome", self.player) + and state.has("Carthus Pyromancy Tome", self.player) + and state.has("Izalith Pyromancy Tome", self.player) + )) + + ## Irina + + self._add_location_rule([ + "US: Tower Key - kill Irina", + ], lambda state: ( + state.has("Braille Divine Tome of Carim", self.player) + and state.has("Braille Divine Tome of Lothric", self.player) + )) + + ## Karla + + self._add_location_rule([ + "FS: Karla's Pointed Hat - kill Karla", + "FS: Karla's Coat - kill Karla", + "FS: Karla's Gloves - kill Karla", + "FS: Karla's Trousers - kill Karla", + ], lambda state: ( + state.has("Quelana Pyromancy Tome", self.player) + and state.has("Grave Warden Pyromancy Tome", self.player) + and state.has("Deep Braille Divine Tome", self.player) + and state.has("Londor Braille Divine Tome", self.player) + )) + + ## Emma + + self._add_location_rule("HWL: Basin of Vows - Emma", "Small Doll") + + ## Orbeck + + self._add_location_rule([ + "FS: Morion Blade - Yuria for Orbeck's Ashes", + "FS: Clandestine Coat - shop with Orbeck's Ashes" + ], lambda state: ( + state.has("Golden Scroll", self.player) + and state.has("Logan's Scroll", self.player) + and state.has("Crystal Scroll", self.player) + and state.has("Sage's Scroll", self.player) + )) + + self._add_location_rule([ + "FS: Pestilent Mist - Orbeck for any scroll", + "FS: Young Dragon Ring - Orbeck for one scroll and buying three spells", + # Make sure that the player can keep Orbeck around by giving him at least one scroll + # before killing Abyss Watchers. + "FK: Soul of the Blood of the Wolf", + "FK: Cinders of a Lord - Abyss Watcher", + "FS: Undead Legion Helm - shop after killing FK boss", + "FS: Undead Legion Armor - shop after killing FK boss", + "FS: Undead Legion Gauntlet - shop after killing FK boss", + "FS: Undead Legion Leggings - shop after killing FK boss", + "FS: Farron Ring - Hawkwood", + "FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + "UG: Hornet Ring - environs, right of main path after killing FK boss", + "UG: Wolf Knight Helm - shop after killing FK boss", + "UG: Wolf Knight Armor - shop after killing FK boss", + "UG: Wolf Knight Gauntlets - shop after killing FK boss", + "UG: Wolf Knight Leggings - shop after killing FK boss", + ], self._has_any_scroll) + + # Not really necessary but ensures players can decide which way to go + if self.options.enable_dlc: + self._add_entrance_rule( + "Painted World of Ariandel (After Contraption)", + self._has_any_scroll + ) + + ## Anri + + # Anri only leaves Road of Sacrifices once Deacons is defeated + self._add_location_rule([ + "IBV: Ring of the Evil Eye - Anri", + "AL: Chameleon - tomb after marrying Anri", + ], lambda state: self._can_get(state, "CD: Soul of the Deacons of the Deep")) + + # If the player does Anri's non-marriage quest, they'll need to defeat the AL boss as well + # before it's complete. + self._add_location_rule([ + "AL: Anri's Straight Sword - Anri quest", + "FS: Elite Knight Helm - shop after Anri quest", + "FS: Elite Knight Armor - shop after Anri quest", + "FS: Elite Knight Gauntlets - shop after Anri quest", + "FS: Elite Knight Leggings - shop after Anri quest", + ], lambda state: ( + self._can_get(state, "IBV: Ring of the Evil Eye - Anri") and + self._can_get(state, "AL: Soul of Aldrich") + )) + + def _add_transposition_rules(self) -> None: + """Adds rules for items obtainable from Ludleth by soul transposition.""" + + transpositions = [ + ( + "Soul of Boreal Valley Vordt", "Vordt", + ["Vordt's Great Hammer", "Pontiff's Left Eye"] + ), + ("Soul of Rosaria", "Rosaria", ["Bountiful Sunlight"]), + ("Soul of Aldrich", "Aldrich", ["Darkmoon Longbow", "Lifehunt Scythe"]), + ( + "Soul of the Rotted Greatwood", "Greatwood", + ["Hollowslayer Greatsword", "Arstor's Spear"] + ), + ("Soul of a Crystal Sage", "Sage", ["Crystal Sage's Rapier", "Crystal Hail"]), + ("Soul of the Deacons of the Deep", "Deacons", ["Cleric's Candlestick", "Deep Soul"]), + ("Soul of a Stray Demon", "Stray Demon", ["Havel's Ring", "Boulder Heave"]), + ( + "Soul of the Blood of the Wolf", "Abyss Watchers", + ["Farron Greatsword", "Wolf Knight's Greatsword"] + ), + ("Soul of High Lord Wolnir", "Wolnir", ["Wolnir's Holy Sword", "Black Serpent"]), + ("Soul of a Demon", "Fire Demon", ["Demon's Greataxe", "Demon's Fist"]), + ( + "Soul of the Old Demon King", "Old Demon King", + ["Old King's Great Hammer", "Chaos Bed Vestiges"] + ), + ( + "Soul of Pontiff Sulyvahn", "Pontiff", + ["Greatsword of Judgment", "Profaned Greatsword"] + ), + ("Soul of Yhorm the Giant", "Yhorm", ["Yhorm's Great Machete", "Yhorm's Greatshield"]), + ("Soul of the Dancer", "Dancer", ["Dancer's Enchanted Swords", "Soothing Sunlight"]), + ( + "Soul of Dragonslayer Armour", "Dragonslayer", + ["Dragonslayer Greataxe", "Dragonslayer Greatshield"] + ), + ( + "Soul of Consumed Oceiros", "Oceiros", + ["Moonlight Greatsword", "White Dragon Breath"] + ), + ( + "Soul of the Twin Princes", "Princes", + ["Lorian's Greatsword", "Lothric's Holy Sword"] + ), + ("Soul of Champion Gundyr", "Champion", ["Gundyr's Halberd", "Prisoner's Chain"]), + ( + "Soul of the Nameless King", "Nameless", + ["Storm Curved Sword", "Dragonslayer Swordspear", "Lightning Storm"] + ), + ("Soul of the Lords", "Cinder", ["Firelink Greatsword", "Sunlight Spear"]), + ("Soul of Sister Friede", "Friede", ["Friede's Great Scythe", "Rose of Ariandel"]), + ("Soul of the Demon Prince", "Demon Prince", ["Demon's Scar", "Seething Chaos"]), + ("Soul of Darkeater Midir", "Midir", ["Frayed Blade", "Old Moonlight"]), + ("Soul of Slave Knight Gael", "Gael", ["Gael's Greatsword", "Repeating Crossbow"]), + ] + for (soul, soul_name, items) in transpositions: + self._add_location_rule([ + f"FS: {item} - Ludleth for {soul_name}" for item in items + ], lambda state, s=soul: ( + state.has(s, self.player) and state.has("Transposing Kiln", self.player) + )) + + def _add_crow_rules(self) -> None: + """Adds rules for items obtainable by trading items to the crow on Firelink roof.""" + + crow = { + "Loretta's Bone": "Ring of Sacrifice", + # "Avelyn": "Titanite Scale", # Missing from static randomizer + "Coiled Sword Fragment": "Titanite Slab", + "Seed of a Giant Tree": "Iron Leggings", + "Siegbräu": "Armor of the Sun", + # Static randomizer can't randomize Hodrick's drop yet + # "Vertebra Shackle": "Lucatiel's Mask", + "Xanthous Crown": "Lightning Gem", + "Mendicant's Staff": "Sunlight Shield", + "Blacksmith Hammer": "Titanite Scale", + "Large Leather Shield": "Twinkling Titanite", + "Moaning Shield": "Blessed Gem", + "Eleonora": "Hollow Gem", + } + for (given, received) in crow.items(): + name = f"FSBT: {received} - crow for {given}" + self._add_location_rule(name, given) + + # Don't let crow items have foreign items because they're picked up in a way that's + # missed by the hook we use to send location items + self._add_item_rule(name, lambda item: ( + item.player == self.player + # Because of the weird way they're delivered, crow items don't seem to support + # infused or upgraded weapons. + and not item.data.is_infused + and not item.data.is_upgraded + )) + + def _add_allow_useful_location_rules(self) -> None: + """Adds rules for locations that can contain useful but not necessary items. + + If we allow useful items in the excluded locations, we don't want Archipelago's fill + algorithm to consider them excluded because it never allows useful items there. Instead, we + manually add item rules to exclude important items. + """ + + all_locations = self._get_our_locations() + + allow_useful_locations = ( + ( + { + location.name + for location in all_locations + if location.name in self.all_excluded_locations + and not location.data.missable + } + if self.options.excluded_location_behavior < self.options.missable_location_behavior + else self.all_excluded_locations + ) + if self.options.excluded_location_behavior == "allow_useful" + else set() + ).union( + { + location.name + for location in all_locations + if location.data.missable + and not ( + location.name in self.all_excluded_locations + and self.options.missable_location_behavior < + self.options.excluded_location_behavior + ) + } + if self.options.missable_location_behavior == "allow_useful" + else set() + ) + for location in allow_useful_locations: + self._add_item_rule( + location, + lambda item: not item.advancement + ) + + if self.options.excluded_location_behavior == "allow_useful": + self.options.exclude_locations.value.clear() + + def _add_early_item_rules(self, randomized_items: Set[str]) -> None: + """Adds rules to make sure specific items are available early.""" + + if "Pyromancy Flame" in randomized_items: + # Make this available early because so many items are useless without it. + self._add_entrance_rule("Road of Sacrifices", "Pyromancy Flame") + self._add_entrance_rule("Consumed King's Garden", "Pyromancy Flame") + self._add_entrance_rule("Grand Archives", "Pyromancy Flame") + if "Transposing Kiln" in randomized_items: + # Make this available early so players can make use of their boss souls. + self._add_entrance_rule("Road of Sacrifices", "Transposing Kiln") + self._add_entrance_rule("Consumed King's Garden", "Transposing Kiln") + self._add_entrance_rule("Grand Archives", "Transposing Kiln") + # Make this available pretty early + if "Small Lothric Banner" in randomized_items: + if self.options.early_banner == "early_global": + self.multiworld.early_items[self.player]["Small Lothric Banner"] = 1 + elif self.options.early_banner == "early_local": + self.multiworld.local_early_items[self.player]["Small Lothric Banner"] = 1 + + def _has_any_scroll(self, state: CollectionState) -> bool: + """Returns whether the given state has any scroll item.""" + return ( + state.has("Sage's Scroll", self.player) + or state.has("Golden Scroll", self.player) + or state.has("Logan's Scroll", self.player) + or state.has("Crystal Scroll", self.player) + ) + + def _add_location_rule(self, location: Union[str, List[str]], rule: Union[CollectionRule, str]) -> None: + """Sets a rule for the given location if it that location is randomized. + + The rule can just be a single item/event name as well as an explicit rule lambda. + """ + locations = location if isinstance(location, list) else [location] + for location in locations: + data = location_dictionary[location] + if data.dlc and not self.options.enable_dlc: return + if data.ngp and not self.options.enable_ngp: return + + if not self._is_location_available(location): return + if isinstance(rule, str): + assert item_dictionary[rule].classification == ItemClassification.progression + rule = lambda state, item=rule: state.has(item, self.player) + add_rule(self.multiworld.get_location(location, self.player), rule) + + def _add_entrance_rule(self, region: str, rule: Union[CollectionRule, str]) -> None: + """Sets a rule for the entrance to the given region.""" + assert region in location_tables + if not any(region == reg for reg in self.multiworld.regions.region_cache[self.player]): return + if isinstance(rule, str): + if " -> " not in rule: + assert item_dictionary[rule].classification == ItemClassification.progression + rule = lambda state, item=rule: state.has(item, self.player) + add_rule(self.multiworld.get_entrance("Go To " + region, self.player), rule) + + def _add_item_rule(self, location: str, rule: ItemRule) -> None: + """Sets a rule for what items are allowed in a given location.""" + if not self._is_location_available(location): return + add_item_rule(self.multiworld.get_location(location, self.player), rule) + + def _can_go_to(self, state, region) -> bool: + """Returns whether state can access the given region name.""" + return state.can_reach_entrance(f"Go To {region}", self.player) + + def _can_get(self, state, location) -> bool: + """Returns whether state can access the given location name.""" + return state.can_reach_location(location, self.player) + + def _is_location_available( + self, + location: Union[str, DS3LocationData, DarkSouls3Location] + ) -> bool: + """Returns whether the given location is being randomized.""" + if isinstance(location, DS3LocationData): + data = location + elif isinstance(location, DarkSouls3Location): + data = location.data + else: + data = location_dictionary[location] + + return ( + not data.is_event + and (not data.dlc or bool(self.options.enable_dlc)) + and (not data.ngp or bool(self.options.enable_ngp)) + and not ( + self.options.excluded_location_behavior == "do_not_randomize" + and data.name in self.all_excluded_locations + ) + and not ( + self.options.missable_location_behavior == "do_not_randomize" + and data.missable + ) + ) + + def write_spoiler(self, spoiler_handle: TextIO) -> None: + text = "" + + if self.yhorm_location != default_yhorm_location: + text += f"\nYhorm takes the place of {self.yhorm_location.name} in {self.player_name}'s world\n" + + if self.options.excluded_location_behavior == "allow_useful": + text += f"\n{self.player_name}'s world excluded: {sorted(self.all_excluded_locations)}\n" + + if text: + text = "\n" + text + "\n" + spoiler_handle.write(text) + + def post_fill(self): + """If item smoothing is enabled, rearrange items so they scale up smoothly through the run. + + This determines the approximate order a given silo of items (say, soul items) show up in the + main game, then rearranges their shuffled placements to match that order. It determines what + should come "earlier" or "later" based on sphere order: earlier spheres get lower-level + items, later spheres get higher-level ones. Within a sphere, items in DS3 are distributed in + region order, and then the best items in a sphere go into the multiworld. + """ + + locations_by_sphere = [ + sorted(loc for loc in sphere if loc.item.player == self.player and not loc.locked) + for sphere in self.multiworld.get_spheres() + ] + + # All items in the base game in approximately the order they appear + all_item_order: List[DS3ItemData] = [ + item_dictionary[location.default_item_name] + for region in region_order + # Shuffle locations within each region. + for location in self._shuffle(location_tables[region]) + if self._is_location_available(location) + ] + + # All DarkSouls3Items for this world that have been assigned anywhere, grouped by name + full_items_by_name: Dict[str, List[DarkSouls3Item]] = defaultdict(list) + for location in self.multiworld.get_filled_locations(): + if location.item.player == self.player and ( + location.player != self.player or self._is_location_available(location) + ): + full_items_by_name[location.item.name].append(location.item) + + def smooth_items(item_order: List[Union[DS3ItemData, DarkSouls3Item]]) -> None: + """Rearrange all items in item_order to match that order. + + Note: this requires that item_order exactly matches the number of placed items from this + world matching the given names. + """ + + # Convert items to full DarkSouls3Items. + converted_item_order: List[DarkSouls3Item] = [ + item for item in ( + ( + # full_items_by_name won't contain DLC items if the DLC is disabled. + (full_items_by_name[item.name] or [None]).pop(0) + if isinstance(item, DS3ItemData) else item + ) + for item in item_order + ) + # Never re-order event items, because they weren't randomized in the first place. + if item and item.code is not None + ] + + names = {item.name for item in converted_item_order} + + all_matching_locations = [ + loc + for sphere in locations_by_sphere + for loc in sphere + if loc.item.name in names + ] + + # It's expected that there may be more total items than there are matching locations if + # the player has chosen a more limited accessibility option, since the matching + # locations *only* include items in the spheres of accessibility. + if len(converted_item_order) < len(all_matching_locations): + raise Exception( + f"DS3 bug: there are {len(all_matching_locations)} locations that can " + + f"contain smoothed items, but only {len(converted_item_order)} items to smooth." + ) + for sphere in locations_by_sphere: + locations = [loc for loc in sphere if loc.item.name in names] + + # Check the game, not the player, because we know how to sort within regions for DS3 + offworld = self._shuffle([loc for loc in locations if loc.game != "Dark Souls III"]) + onworld = sorted((loc for loc in locations if loc.game == "Dark Souls III"), + key=lambda loc: loc.data.region_value) + + # Give offworld regions the last (best) items within a given sphere + for location in onworld + offworld: + new_item = self._pop_item(location, converted_item_order) + location.item = new_item + new_item.location = location + + if self.options.smooth_upgrade_items: + base_names = { + "Titanite Shard", "Large Titanite Shard", "Titanite Chunk", "Titanite Slab", + "Titanite Scale", "Twinkling Titanite", "Farron Coal", "Sage's Coal", "Giant's Coal", + "Profaned Coal" + } + smooth_items([item for item in all_item_order if item.base_name in base_names]) + + if self.options.smooth_soul_items: + smooth_items([ + item for item in all_item_order + if item.souls and item.classification != ItemClassification.progression + ]) + + if self.options.smooth_upgraded_weapons: + upgraded_weapons = [ + location.item + for location in self.multiworld.get_filled_locations() + if location.item.player == self.player + and location.item.level and location.item.level > 0 + and location.item.classification != ItemClassification.progression + ] + upgraded_weapons.sort(key=lambda item: item.level) + smooth_items(upgraded_weapons) + + def _shuffle(self, seq: Sequence) -> List: + """Returns a shuffled copy of a sequence.""" + copy = list(seq) + self.random.shuffle(copy) + return copy + + def _pop_item( + self, + location: Location, + items: List[DarkSouls3Item] + ) -> DarkSouls3Item: + """Returns the next item in items that can be assigned to location.""" + for i, item in enumerate(items): + if location.can_fill(self.multiworld.state, item, False): + return items.pop(i) + + # If we can't find a suitable item, give up and assign an unsuitable one. + return items.pop(0) + + def _get_our_locations(self) -> List[DarkSouls3Location]: + return cast(List[DarkSouls3Location], self.multiworld.get_locations(self.player)) def fill_slot_data(self) -> Dict[str, object]: slot_data: Dict[str, object] = {} - # Depending on the specified option, modify items hexadecimal value to add an upgrade level or infusion - name_to_ds3_code = {item.name: item.ds3_code for item in item_dictionary.values()} - - # Randomize some weapon upgrades - if self.multiworld.randomize_weapon_level[self.player] != RandomizeWeaponLevelOption.option_none: - # if the user made an error and set a min higher than the max we default to the max - max_5 = self.multiworld.max_levels_in_5[self.player] - min_5 = min(self.multiworld.min_levels_in_5[self.player], max_5) - max_10 = self.multiworld.max_levels_in_10[self.player] - min_10 = min(self.multiworld.min_levels_in_10[self.player], max_10) - weapon_level_percentage = self.multiworld.randomize_weapon_level_percentage[self.player] - - for item in item_dictionary.values(): - if self.multiworld.per_slot_randoms[self.player].randint(0, 99) < weapon_level_percentage: - if item.category == DS3ItemCategory.WEAPON_UPGRADE_5: - name_to_ds3_code[item.name] += self.multiworld.per_slot_randoms[self.player].randint(min_5, max_5) - elif item.category in {DS3ItemCategory.WEAPON_UPGRADE_10, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE}: - name_to_ds3_code[item.name] += self.multiworld.per_slot_randoms[self.player].randint(min_10, max_10) - - # Randomize some weapon infusions - if self.multiworld.randomize_infusion[self.player] == Toggle.option_true: - infusion_percentage = self.multiworld.randomize_infusion_percentage[self.player] - for item in item_dictionary.values(): - if item.category in {DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE, DS3ItemCategory.SHIELD_INFUSIBLE}: - if self.multiworld.per_slot_randoms[self.player].randint(0, 99) < infusion_percentage: - name_to_ds3_code[item.name] += 100 * self.multiworld.per_slot_randoms[self.player].randint(0, 15) - - # Create the mandatory lists to generate the player's output file - items_id = [] - items_address = [] - locations_id = [] - locations_address = [] - locations_target = [] - for location in self.multiworld.get_filled_locations(): - # Skip events - if location.item.code is None: - continue - - if location.item.player == self.player: - items_id.append(location.item.code) - items_address.append(name_to_ds3_code[location.item.name]) - - if location.player == self.player: - locations_address.append(item_dictionary[location_dictionary[location.name].default_item].ds3_code) - locations_id.append(location.address) - if location.item.player == self.player: - locations_target.append(name_to_ds3_code[location.item.name]) - else: - locations_target.append(0) + # Once all clients support overlapping item IDs, adjust the DS3 AP item IDs to encode the + # in-game ID as well as the count so that we don't need to send this information at all. + # + # We include all the items the game knows about so that users can manually request items + # that aren't randomized, and then we _also_ include all the items that are placed in + # practice `item_dictionary.values()` doesn't include upgraded or infused weapons. + all_items = { + cast(DarkSouls3Item, location.item).data + for location in self.multiworld.get_filled_locations() + # item.code None is used for events, which we want to skip + if location.item.code is not None and location.item.player == self.player + }.union(item_dictionary.values()) + + ap_ids_to_ds3_ids: Dict[str, int] = {} + item_counts: Dict[str, int] = {} + for item in all_items: + if item.ap_code is None: continue + if item.ds3_code: ap_ids_to_ds3_ids[str(item.ap_code)] = item.ds3_code + if item.count != 1: item_counts[str(item.ap_code)] = item.count + + # A map from Archipelago's location IDs to the keys the static randomizer uses to identify + # locations. + location_ids_to_keys: Dict[int, str] = {} + for location in cast(List[DarkSouls3Location], self.multiworld.get_filled_locations(self.player)): + # Skip events and only look at this world's locations + if (location.address is not None and location.item.code is not None + and location.data.static): + location_ids_to_keys[location.address] = location.data.static slot_data = { "options": { - "enable_weapon_locations": self.multiworld.enable_weapon_locations[self.player].value, - "enable_shield_locations": self.multiworld.enable_shield_locations[self.player].value, - "enable_armor_locations": self.multiworld.enable_armor_locations[self.player].value, - "enable_ring_locations": self.multiworld.enable_ring_locations[self.player].value, - "enable_spell_locations": self.multiworld.enable_spell_locations[self.player].value, - "enable_key_locations": self.multiworld.enable_key_locations[self.player].value, - "enable_boss_locations": self.multiworld.enable_boss_locations[self.player].value, - "enable_npc_locations": self.multiworld.enable_npc_locations[self.player].value, - "enable_misc_locations": self.multiworld.enable_misc_locations[self.player].value, - "auto_equip": self.multiworld.auto_equip[self.player].value, - "lock_equip": self.multiworld.lock_equip[self.player].value, - "no_weapon_requirements": self.multiworld.no_weapon_requirements[self.player].value, - "death_link": self.multiworld.death_link[self.player].value, - "no_spell_requirements": self.multiworld.no_spell_requirements[self.player].value, - "no_equip_load": self.multiworld.no_equip_load[self.player].value, - "enable_dlc": self.multiworld.enable_dlc[self.player].value + "random_starting_loadout": self.options.random_starting_loadout.value, + "require_one_handed_starting_weapons": self.options.require_one_handed_starting_weapons.value, + "auto_equip": self.options.auto_equip.value, + "lock_equip": self.options.lock_equip.value, + "no_weapon_requirements": self.options.no_weapon_requirements.value, + "death_link": self.options.death_link.value, + "no_spell_requirements": self.options.no_spell_requirements.value, + "no_equip_load": self.options.no_equip_load.value, + "enable_dlc": self.options.enable_dlc.value, + "enable_ngp": self.options.enable_ngp.value, + "smooth_soul_locations": self.options.smooth_soul_items.value, + "smooth_upgrade_locations": self.options.smooth_upgrade_items.value, + "randomize_enemies": self.options.randomize_enemies.value, + "randomize_mimics_with_enemies": self.options.randomize_mimics_with_enemies.value, + "randomize_small_crystal_lizards_with_enemies": self.options.randomize_small_crystal_lizards_with_enemies.value, + "reduce_harmless_enemies": self.options.reduce_harmless_enemies.value, + "simple_early_bosses": self.options.simple_early_bosses.value, + "scale_enemies": self.options.scale_enemies.value, + "all_chests_are_mimics": self.options.all_chests_are_mimics.value, + "impatient_mimics": self.options.impatient_mimics.value, }, "seed": self.multiworld.seed_name, # to verify the server's multiworld "slot": self.multiworld.player_name[self.player], # to connect to server - "base_id": self.base_id, # to merge location and items lists - "locationsId": locations_id, - "locationsAddress": locations_address, - "locationsTarget": locations_target, - "itemsId": items_id, - "itemsAddress": items_address + # Reserializing here is silly, but it's easier for the static randomizer. + "random_enemy_preset": json.dumps(self.options.random_enemy_preset.value), + "yhorm": ( + f"{self.yhorm_location.name} {self.yhorm_location.id}" + if self.yhorm_location != default_yhorm_location + else None + ), + "apIdsToItemIds": ap_ids_to_ds3_ids, + "itemCounts": item_counts, + "locationIdsToKeys": location_ids_to_keys, } return slot_data + + @staticmethod + def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]: + return slot_data diff --git a/worlds/dark_souls_3/detailed_location_descriptions.py b/worlds/dark_souls_3/detailed_location_descriptions.py new file mode 100644 index 000000000000..e20c700ab1bc --- /dev/null +++ b/worlds/dark_souls_3/detailed_location_descriptions.py @@ -0,0 +1,97 @@ +# python -m worlds.dark_souls_3.detailed_location_descriptions \ +# worlds/dark_souls_3/detailed_location_descriptions.py +# +# This script downloads the static randomizer's descriptions for each location and adds them to +# the location documentation. + +from collections import defaultdict +import html +import os +import re +import requests +import yaml + +from .Locations import location_dictionary + + +location_re = re.compile(r'^([A-Z0-9]+): (.*?)(?:$| - )') + +if __name__ == '__main__': + # TODO: update this to the main branch of the main randomizer once Archipelago support is merged + url = 'https://raw.githubusercontent.com/nex3/SoulsRandomizers/archipelago-server/dist/Base/annotations.txt' + response = requests.get(url) + if response.status_code != 200: + raise Exception(f"Got {response.status_code} when downloading static randomizer locations") + annotations = yaml.load(response.text, Loader=yaml.Loader) + + static_to_archi_regions = { + area['Name']: area['Archipelago'] + for area in annotations['Areas'] + } + + descriptions_by_key = {slot['Key']: slot['Text'] for slot in annotations['Slots']} + + # A map from (region, item name) pairs to all the descriptions that match those pairs. + descriptions_by_location = defaultdict(list) + + # A map from item names to all the descriptions for those item names. + descriptions_by_item = defaultdict(list) + + for slot in annotations['Slots']: + region = static_to_archi_regions[slot['Area']] + for item in slot['DebugText']: + name = item.split(" - ")[0] + descriptions_by_location[(region, name)].append(slot['Text']) + descriptions_by_item[name].append(slot['Text']) + counts_by_location = { + location: len(descriptions) for (location, descriptions) in descriptions_by_location.items() + } + + location_names_to_descriptions = {} + for location in location_dictionary.values(): + if location.ap_code is None: continue + if location.static: + location_names_to_descriptions[location.name] = descriptions_by_key[location.static] + continue + + match = location_re.match(location.name) + if not match: + raise Exception(f"Location name \"{location.name}\" doesn't match expected format.") + + item_candidates = descriptions_by_item[match[2]] + if len(item_candidates) == 1: + location_names_to_descriptions[location.name] = item_candidates[0] + continue + + key = (match[1], match[2]) + if key not in descriptions_by_location: + raise Exception(f'No static randomizer location found matching "{match[1]}: {match[2]}".') + + candidates = descriptions_by_location[key] + if len(candidates) == 0: + raise Exception( + f'There are only {counts_by_location[key]} locations in the static randomizer ' + + f'matching "{match[1]}: {match[2]}", but there are more in Archipelago.' + ) + + location_names_to_descriptions[location.name] = candidates.pop(0) + + table = "\n" + for (name, description) in sorted( + location_names_to_descriptions.items(), + key = lambda pair: pair[0] + ): + table += f"\n" + table += "
Location nameDetailed description
{html.escape(name)}{html.escape(description)}
\n" + + with open(os.path.join(os.path.dirname(__file__), 'docs/locations_en.md'), 'r+') as f: + original = f.read() + start_flag = "\n" + start = original.index(start_flag) + len(start_flag) + end = original.index("") + + f.seek(0) + f.write(original[:start] + table + original[end:]) + f.truncate() + + print("Updated docs/locations_en.md!") diff --git a/worlds/dark_souls_3/docs/en_Dark Souls III.md b/worlds/dark_souls_3/docs/en_Dark Souls III.md index f31358bb9c2f..06227226aafe 100644 --- a/worlds/dark_souls_3/docs/en_Dark Souls III.md +++ b/worlds/dark_souls_3/docs/en_Dark Souls III.md @@ -1,28 +1,201 @@ # Dark Souls III +Game Page | [Items] | [Locations] + +[Items]: /tutorial/Dark%20Souls%20III/items/en +[Locations]: /tutorial/Dark%20Souls%20III/locations/en + +## What do I need to do to randomize DS3? + +See full instructions on [the setup page]. + +[the setup page]: /tutorial/Dark%20Souls%20III/setup/en + ## Where is the options page? -The [player options page for this game](../player-options) contains all the options you need to configure and export a -config file. +The [player options page for this game][options] contains all the options you +need to configure and export a config file. + +[options]: ../player-options ## What does randomization do to this game? -Items that can be picked up from static corpses, taken from chests, or earned from defeating enemies or NPCs can be -randomized. Common pickups like titanite shards or firebombs can be randomized as "progressive" items. That is, the -location "Titanite Shard #5" is the fifth titanite shard you pick up, no matter where it was from. This is also what -happens when you randomize Estus Shards and Undead Bone Shards. +1. All item locations are randomized, including those in the overworld, in + shops, and dropped by enemies. Most locations can contain games from other + worlds, and any items from your world can appear in other players' worlds. + +2. By default, all enemies and bosses are randomized. This can be disabled by + setting "Randomize Enemies" to false. + +3. By default, the starting equipment for each class is randomized. This can be + disabled by setting "Randomize Starting Loadout" to false. + +4. By setting the "Randomize Weapon Level" or "Randomize Infusion" options, you + can randomize whether the weapons you find will be upgraded or infused. + +There are also options that can make playing the game more convenient or +bring a new experience, like removing equip loads or auto-equipping weapons as +you pick them up. Check out [the options page][options] for more! + +## What's the goal? + +Your goal is to find the four "Cinders of a Lord" items randomized into the +multiworld and defeat the boss in the Kiln of the First Flame. + +## Do I have to check every item in every area? + +Dark Souls III has about 1500 item locations, which is a lot of checks for a +single run! But you don't necessarily need to check all of them. Locations that +you can potentially miss, such as rewards for failable quests or soul +transposition items, will _never_ have items required for any game to progress. +The following types of locations are also guaranteed not to contain progression +items by default: + +* **Hidden:** Locations that are particularly difficult to find, such as behind + illusory walls, down hidden drops, and so on. Does not include large locations + like Untended Graves or Archdragon Peak. + +* **Small Crystal Lizards:** Drops from small crystal lizards. + +* **Upgrade:** Locations that contain upgrade items in vanilla, including + titanite, gems, and Shriving Stones. + +* **Small Souls:** Locations that contain soul items in vanilla, not including + boss souls. + +* **Miscellaneous:** Locations that contain generic stackable items in vanilla, + such as arrows, firebombs, buffs, and so on. + +You can customize which locations are guaranteed not to contain progression +items by setting the `exclude_locations` field in your YAML to the [location +groups] you want to omit. For example, this is the default setting but without +"Hidden" so that hidden locations can contain progression items: + +[location groups]: /tutorial/Dark%20Souls%20III/locations/en#location-groups + +```yaml +Dark Souls III: + exclude_locations: + - Small Crystal Lizards + - Upgrade + - Small Souls + - Miscellaneous +``` + +This allows _all_ non-missable locations to have progression items, if you're in +for the long haul: + +```yaml +Dark Souls III: + exclude_locations: [] +``` + +## What if I don't want to do the whole game? + +If you want a shorter DS3 randomizer experience, you can exclude entire regions +from containing progression items. The items and enemies from those regions will +still be included in the randomization pool, but none of them will be mandatory. +For example, the following configuration just requires you to play the game +through Irithyll of the Boreal Valley: + +```yaml +Dark Souls III: + # Enable the DLC so it's included in the randomization pool + enable_dlc: true + + exclude_locations: + # Exclude late-game and DLC regions + - Anor Londo + - Lothric Castle + - Consumed King's Garden + - Untended Graves + - Grand Archives + - Archdragon Peak + - Painted World of Ariandel + - Dreg Heap + - Ringed City + + # Default exclusions + - Hidden + - Small Crystal Lizards + - Upgrade + - Small Souls + - Miscellaneous +``` + +## Where can I learn more about Dark Souls III locations? + +Location names have to pack a lot of information into very little space. To +better understand them, check out the [location guide], which explains all the +names used in locations and provides more detailed descriptions for each +individual location. + +[location guide]: /tutorial/Dark%20Souls%20III/locations/en + +## Where can I learn more about Dark Souls III items? + +Check out the [item guide], which explains the named groups available for items. + +[item guide]: /tutorial/Dark%20Souls%20III/items/en + +## What's new from 2.x.x? + +Version 3.0.0 of the Dark Souls III Archipelago client has a number of +substantial differences with the older 2.x.x versions. Improvements include: + +* Support for randomizing all item locations, not just unique items. + +* Support for randomizing items in shops, starting loadouts, Path of the Dragon, + and more. + +* Built-in integration with the enemy randomizer, including consistent seeding + for races. + +* Support for the latest patch for Dark Souls III, 1.15.2. Older patches are + *not* supported. + +* Optional smooth distribution for upgrade items, upgraded weapons, and soul + items so you're more likely to see weaker items earlier and more powerful + items later. + +* More detailed location names that indicate where a location is, not just what + it replaces. + +* Other players' item names are visible in DS3. + +* If you pick up items while static, they'll still send once you reconnect. + +However, 2.x.x YAMLs are not compatible with 3.0.0. You'll need to [generate a +new YAML configuration] for use with 3.x.x. + +[generating a new YAML configuration]: /games/Dark%20Souls%20III/player-options + +The following options have been removed: + +* `enable_boss_locations` is now controlled by the `soul_locations` option. + +* `enable_progressive_locations` was removed because all locations are now + individually randomized rather than replaced with a progressive list. + +* `pool_type` has been removed. Since there are no longer any non-randomized + items in randomized categories, there's not a meaningful distinction between + "shuffle" and "various" mode. -It's also possible to randomize the upgrade level of weapons and shields as well as their infusions (if they can have -one). Additionally, there are options that can make the randomized experience more convenient or more interesting, such as -removing weapon requirements or auto-equipping whatever equipment you most recently received. +* `enable_*_locations` options have all been removed. Instead, you can now add + [location group names] to the `exclude_locations` option to prevent them from + containing important items. -The goal is to find the four "Cinders of a Lord" items randomized into the multiworld and defeat the Soul of Cinder. + [location group names]: /tutorial/Dark%20Souls%20III/locations/en#location-groups -## What Dark Souls III items can appear in other players' worlds? + By default, the Hidden, Small Crystal Lizards, Upgrade, Small Souls, and + Miscellaneous groups are in `exclude_locations`. Once you've chosen your + excluded locations, you can set `excluded_locations: unrandomized` to preserve + the default vanilla item placements for all excluded locations. -Practically anything can be found in other worlds including pieces of armor, upgraded weapons, key items, consumables, -spells, upgrade materials, etc... +* `guaranteed_items`: In almost all cases, all items from the base game are now + included somewhere in the multiworld. -## What does another world's item look like in Dark Souls III? +In addition, the following options have changed: -In Dark Souls III, items which are sent to other worlds appear as Prism Stones. +* The location names used in options like `exclude_locations` have changed. See + the [location guide] for a full description. diff --git a/worlds/dark_souls_3/docs/items_en.md b/worlds/dark_souls_3/docs/items_en.md new file mode 100644 index 000000000000..b9de5e500a96 --- /dev/null +++ b/worlds/dark_souls_3/docs/items_en.md @@ -0,0 +1,24 @@ +# Dark Souls III Items + +[Game Page] | Items | [Locations] + +[Game Page]: /games/Dark%20Souls%20III/info/en +[Locations]: /tutorial/Dark%20Souls%20III/locations/en + +## Item Groups + +The Dark Souls III randomizer supports a number of item group names, which can +be used in YAML options like `local_items` to refer to many items at once: + +* **Progression:** Items which unlock locations. +* **Cinders:** All four Cinders of a Lord. Once you have these four, you can + fight Soul of Cinder and win the game. +* **Miscellaneous:** Generic stackable items, such as arrows, firebombs, buffs, + and so on. +* **Unique:** Items that are unique per NG cycle, such as scrolls, keys, ashes, + and so on. Doesn't include equipment, spells, or souls. +* **Boss Souls:** Souls that can be traded with Ludleth, including Soul of + Rosaria. +* **Small Souls:** Soul items, not including boss souls. +* **Upgrade:** Upgrade items, including titanite, gems, and Shriving Stones. +* **Healing:** Undead Bone Shards and Estus Shards. diff --git a/worlds/dark_souls_3/docs/locations_en.md b/worlds/dark_souls_3/docs/locations_en.md new file mode 100644 index 000000000000..ef07b84b2b34 --- /dev/null +++ b/worlds/dark_souls_3/docs/locations_en.md @@ -0,0 +1,2276 @@ +# Dark Souls III Locations + +[Game Page] | [Items] | Locations + +[Game Page]: /games/Dark%20Souls%20III/info/en +[Items]: /tutorial/Dark%20Souls%20III/items/en + +## Table of Contents + +* [Location Groups](#location-groups) +* [Understanding Location Names](#understanding-location-names) + * [HWL: High Wall of Lothric](#high-wall-of-lothric) + * [US: Undead Settlement](#undead-settlement) + * [RS: Road of Sacrifices](#road-of-sacrifices) + * [CD: Cathedral of the Deep](#cathedral-of-the-deep) + * [FK: Farron Keep](#farron-keep) + * [CC: Catacombs of Carthus](#catacombs-of-carthus) + * [SL: Smouldering Lake](#smouldering-lake) + * [IBV: Irithyll of the Boreal Valley](#irithyll-of-the-boreal-valley) + * [ID: Irithyll Dungeon](#irithyll-dungeon) + * [PC: Profaned Capital](#profaned-capital) + * [AL: Anor Londo](#anor-londo) + * [LC: Lothric Castle](#lothric-castle) + * [CKG: Consumed King's Garden](#consumed-kings-garden) + * [GA: Grand Archives](#grand-archives) + * [UG: Untended Graves](#untended-graves) + * [AP: Archdragon Peak](#archdragon-peak) + * [PW1: Painted World of Ariandel (Before Contraption)](#painted-world-of-ariandel-before-contraption) + * [PW2: Painted World of Ariandel (After Contraption)](#painted-world-of-ariandel-after-contraption) + * [DH: Dreg Heap](#dreg-heap) + * [RC: Ringed City](#ringed-city) +* [Detailed Location Descriptions](#detailed-location-descriptions) + +## Location Groups + +The Dark Souls III randomizer supports a number of location group names, which +can be used in YAML options like `exclude_locations` to refer to many locations +at once: + +* **Prominent:** A small number of locations that are in very obvious locations. + Mostly boss drops. Ideal for setting as priority locations. + +* **Progression:** Locations that contain items in vanilla which unlock other + locations. + +* **Boss Rewards:** Boss drops. Does not include soul transfusions or shop + items. + +* **Miniboss Rewards:** Miniboss drops. Minibosses are large enemies that don't + respawn after being killed and usually drop some sort of treasure, such as + Boreal Outrider Knights and Ravenous Crystal Lizards. Only includes enemies + considered minibosses by the enemy randomizer. + +* **Mimic Rewards:** Drops from enemies that are mimics in vanilla. + +* **Hostile NPC Rewards:** Drops from NPCs that are hostile to you. This + includes scripted invaders and initially-friendly NPCs that must be fought as + part of their quest. + +* **Friendly NPC Rewards:** Items given by friendly NPCs as part of their quests + or from non-violent interaction. + +* **Small Crystal Lizards:** Drops from small crystal lizards. + +* **Upgrade:** Locations that contain upgrade items in vanilla, including + titanite, gems, and Shriving Stones. + +* **Small Souls:** Locations that contain soul items in vanilla, not including + boss souls. + +* **Boss Souls:** Locations that contain boss souls in vanilla, as well as Soul + of Rosaria. + +* **Unique:** Locations that contain items in vanilla that are unique per NG + cycle, such as scrolls, keys, ashes, and so on. Doesn't cover equipment, + spells, or souls. + +* **Healing:** Locations that contain Undead Bone Shards and Estus Shards in + vanilla. + +* **Miscellaneous:** Locations that contain generic stackable items in vanilla, + such as arrows, firebombs, buffs, and so on. + +* **Hidden:** Locations that are particularly difficult to find, such as behind + illusory walls, down hidden drops, and so on. Does not include large locations + like Untended Graves or Archdragon Peak. + +* **Weapons:** Locations that contain weapons in vanilla. + +* **Shields:** Locations that contain shields in vanilla. + +* **Armor:** Locations that contain armor in vanilla. + +* **Rings:** Locations that contain rings in vanilla. + +* **Spells:** Locations that contain spells in vanilla. + +## Understanding Location Names + +All locations begin with an abbreviation indicating their general region. Most +locations have a set of landmarks that are used in location names to keep them +short. + +* **FS:** Firelink Shrine +* **FSBT:** Firelink Shrine belltower +* **HWL:** [High Wall of Lothric](#high-wall-of-lothric) +* **US:** [Undead Settlement](#undead-settlement) +* **RS:** [Road of Sacrifices](#road-of-sacrifices) +* **CD:** [Cathedral of the Deep](#cathedral-of-the-deep) +* **FK:** [Farron Keep](#farron-keep) +* **CC:** [Catacombs of Carthus](#catacombs-of-carthus) +* **SL:** [Smouldering Lake](#smouldering-lake) +* **IBV:** [Irithyll of the Boreal Valley](#irithyll-of-the-boreal-valley) +* **ID:** [Irithyll Dungeon](#irithyll-dungeon) +* **PC:** [Profaned Capital](#profaned-capital) +* **AL:** [Anor Londo](#anor-londo) +* **LC:** [Lothric Castle](#lothric-castle) +* **CKG:** [Consumed King's Garden](#consumed-kings-garden) +* **GA:** [Grand Archives](#grand-archives) +* **UG:** [Untended Graves](#untended-graves) +* **AP:** [Archdragon Peak](#archdragon-peak) +* **PW1:** [Painted World of Ariandel (Before Contraption)](#painted-world-of-ariandel-before-contraption) +* **PW2:** [Painted World of Ariandel (After Contraption)](#painted-world-of-ariandel-after-contraption) +* **DH:** [Dreg Heap](#dreg-heap) +* **RC:** [Ringed City](#ringed-city) + +General notes: + +* "Lizard" always refers to a small crystal lizard. + +* "Miniboss" are large enemies that don't respawn after being killed and usually + drop some sort of treasure, such as Boreal Outrider Knights and Ravenous + Crystal Lizards. + +* NPC quest items are always in the first location you can get them _without_ + killing the NPC or ending the quest early. + +### High Wall of Lothric + +* **Back tower:** The tower _behind_ the High Wall of Lothric bonfire, past the + path to the shortcut elevator. + +* **Corpse tower:** The first tower after the High Wall of Lothric bonfire, with + a dead Wyvern on top of it. + +* **Fire tower:** The second tower after the High Wall of Lothric bonfire, where + a living Wyvern lands and breathes fire at you. + +* **Flame plaza:** The open area with many items where the Wyvern breathes fire. + +* **Wall tower:** The third tower after the High Wall of Lothric bonfire, with + the Tower on the Wall bonfire. + +* **Fort:** The large building after the Tower on the Wall bonfire, with the + transforming hollow on top. + + * "Entry": The first room you enter after descending the ladder from the roof. + + * "Walkway": The top floor of the tall room, with a path around the edge + hidden by a large wheel. + + * "Mezzanine": The middle floor of the tall room, with a chest. + + * "Ground": The bottom floor of the tall room, with an anvil and many mobs. + +* **Fountain:** The large fountain with many dead knights around it, where the + Winged Knight patrols in vanilla. + +* **Shortcut:** The unlockable path between the promenade and the High Wall of + Lothric bonfire, including both the elevator and the area at its base. + +* **Promenade:** The long, wide path between the two boss arenas. + +### Undead Settlement + +* **Foot:** The area where you first appear, around the Foot of the High Wall + bonfire. + +* **Burning tree:** The tree near the beginning of the region, with the + Cathedral Evangelist in front of it in vanilla. + +* **Hanging corpse room:** The dark room to the left of the burning tree with + many hanging corpses inside, on the way to the Dilapidated Bridge bonfire. + +* **Back alley:** The path between buildings leading to the Dilapidated Bridge + bonfire. + +* **Stable:** The building complex across the bridge to the right of the burning + tree. + +* **White tree:** The birch tree by the Dilapidated Bridge bonfire, where the + giant shoots arrows. + +* **Sewer:** The underground passage between the chasm and the Dilapidated + Bridge bonfire. + +* **Chasm:** The chasm underneath the bridge on the way to the tower. It's + possible to get into the chasm without a key by dropping down next to Eygon of + Carim with a full health bar. + +* **Tower:** The tower at the end of the region with the giant archer at the + top. + +* **Tower village:** The village reachable from the tower, where the Fire Demon + patrols in vanilla. + +### Road of Sacrifices + +The area after the Crystal Sage is considered part of the Cathedral of the Deep +region. + +* **Road:** The path from the Road of Sacrifices bonfire to the Halfway Fortress + bonfire. + +* **Woods:** The wooded area on land, after the Halfway Fortress bonfire and + surrounding the Crucifixion Woods bonfire. + +* **Water:** The watery area, covered in crabs in vanilla. + +* **Deep water:** The area in the water near the ladder to Farron Keep, where + your walking is slowed. + +* **Stronghold:** The stone building complex on the way to Crystal Sage. + + * "Left room" is the room whose entrance is near the Crucifixion Woods + bonfire. + + * "Right room" is the room up the stairs closer to Farron Keep. + +* **Keep perimeter:** The building with the Black Knight and the locked door to + the Farron Keep Perimeter bonfire. + +### Cathedral of the Deep + +* **Path:** The path from Road of Sacrifices to the cathedral proper. + +* **Moat:** The circular path around the base of the front of the + cathedral, with the Ravenous Crystal Lizard and Corpse-Grubs in vanilla. + +* **Graveyard:** The area with respawning enemies up the hill from the Cleansing + Chapel bonfire. + +* **White tree:** The birch tree below the front doors of the chapel and across + the moat from the graveyard, where the giant shoots arrows if he's still + alive. + +* **Lower roofs:** The roofs, flying buttresses, and associated areas to the + right of the front door, which must be traversed before entering the + cathedral. + +* **Upper roofs:** The roofs, flying buttresses, and rafters leading to the + Rosaria's Bedchamber bonfire. + +* **Main hall:** The central and largest room in the cathedral, with the muck + that slows your movement. Divided into the south (with the sleeping giant in + vanilla) and east (with many items) wings, with north pointing towards the + door to the boss. + +* **Side chapel:** The room with rows of pews and the patrolling Cathedral + Knight in vanilla, to the side of the main hall. + +### Farron Keep + +* **Left island:** The large island with the ritual flame, to the left as you + leave the Farron Keep bonfire. + +* **Right island:** The large island with the ritual flame, to the right as you + leave the Farron Keep bonfire. + +* **Hidden cave:** A small cave in the far corner of the map, closest to the + right island. Near a bunch of basilisks in vanilla. + +* **Keep ruins:** The following two islands: + + * "Bonfire island": The island with the Keep Ruins bonfire. + * "Ritual island": The island with one of the three ritual fires. + +* **White tree**: The birch tree by the ramp down from the keep ruins bonfire + island, where the giant shoots arrows if he's still alive. + +* **Keep proper:** The building with the Old Wolf of Farron bonfire. + +* **Upper keep:** The area on top of the keep proper, reachable from the + elevator from the Old Wolf of Farron bonfire. + +* **Perimeter:** The area from near the Farron Keep Perimeter bonfire, including + the stone building and the path to the boss. + +### Catacombs of Carthus + +All the area up to the Small Doll wall into Irithyll is considered part of the +Catacombs of Carthus region. + +* **Atrium:** The large open area you first enter and the rooms attached to it. + + * "Upper" is the floor you begin on. + * "Lower" is the floor down the short stairs but at the top of the long + stairway that the skeleton ball rolls down. + +* **Crypt:** The enclosed area at the bottom of the long stairway that the + skeleton ball rolls down. + + * "Upper" is the floor the long stairway leads to that also contains the + Catacombs of Carthus bonfire. + * "Lower" is the floor with rats and bonewheels in vanilla. + * "Across" is the area reached by going up the set of stairs across from + the entrance downstairs. + +* **Cavern:** The even larger open area past the crypt with the rope bridge to + the boss arena. + +* **Tomb:** The area on the way to Smouldering Lake, reachable by cutting down + the rope bridge and climbing down it. + +* **Irithyll Bridge:** The outdoor bridge leading to Irithyll of the Boreal + Valley. + +### Smouldering Lake + +* **Lake:** The watery area you enter initially, where you get shot at by the + ballista. + +* **Side lake:** The small lake accessible via a passage from the larger one, in + which you face Horace the Hushed as part of his quest. + +* **Ruins main:** The area you first enter after the Demon Ruins bonfire. + + * "Upper" is the floor you begin on. + * "Lower" is the floor down the stairs. + +* **Antechamber:** The area up the flight of stairs near the +Old King's Antechamber bonfire. + +* **Ruins basement:** The area further down from ruins main lower, with many + basilisks and Knight Slayer Tsorig in vanilla. + +### Irithyll of the Boreal Valley + +This region starts _after_ the Small Doll wall and ends with Pontiff Sulyvahn. +Everything after that, including the contents of Sulyvahn's cathedral is +considered part of Anor Londo. + +* **Central:** The beginning of the region, from the Central Irithyll bonfire up + to the plaza. + +* **Dorhys:** The sobbing mob (a Cathedral Evangelist in vanilla) behind the + locked door opening onto central. Accessed through an illusory railing by the + crystal lizard just before the plaza. + +* **Plaza:** The area in front of and below the cathedral, with a locked door up + to the cathedral and a locked elevator to the Ascent. + +* **Descent:** The path from the Church of Yorshka bonfire down to the lake. + +* **Lake:** The open watery area outside the room with the Distant Manor + bonfire. + +* **Sewer:** The room between the lake and the beginning of the ascent, filled + with Sewer Centipedes in vanilla. + +* **Ascent:** The path up from the lake to the cathedral, through several + buildings and some open stairs. + +* **Great hall:** The building along the ascent with a large picture of + Gwynevere and several Silver Knights in vanilla. + +### Irithyll Dungeon + +In Irithyll Dungeon locations, "left" and "right" are always oriented as though +"near" is where you stand and "far" is where you're facing. (For example, you +enter the dungeon from the bonfire on the near left.) + +* **B1:** The floor on which the player enters the dungeon, with the Irithyll + Dungeon bonfire. + + * "Near" is the side of the dungeon with the bonfire. + * "Far" is the opposite side. + +* **B2:** The floor directly below B1, which can be reached by going down the + stairs or dropping. + + * "Near" is the same side of the dungeon as the bonfire. + * "Far" is the opposite side. + +* **Pit:** The large room with the Giant Slave and many Rats in vanilla. + +* **Pit lift:** The elevator from the pit up to B1 near, right to the Irithyll + Dungeon bonfire. + +* **B3:** The lowest floor, with Karla's cell, a lift back to B2, and the exit + onwards to the Profaned Capital. + + * "Near" is the side with Karla's cell and the path from the pit. + * "Far" is the opposite side with the mimic. + +* **B3 lift:** The elevator from B3 (near where you can use Path of the Dragon + to go to Archdragon Peak) up to B2. + +### Profaned Capital + +* **Tower:** The tower that contains the Profaned Capital bonfire. + +* **Swamp:** The pool of toxic liquid accessible by falling down out of the + lower floor of the tower, going into the corridor to the left, and falling + down a hole. + +* **Chapel:** The building in the swamp containing Monstrosities of Sin in + vanilla. + +* **Bridge:** The long bridge from the tower into the palace. + +* **Palace:** The large building carved into the wall of the cavern, full of + chalices and broken pillars. + +### Anor Londo + +This region includes everything after Sulyvahn's cathedral, including its upper +story. + +* **Light cathedral:** The cathedral in which you fight Pontiff Sulyvahn in + vanilla. + +* **Plaza:** The wide open area filled with Giant Slaves in vanilla. + +* **Walkway:** The path above the plaza leading to the second floor of the light + cathedral, with Deacons in vanilla. + +* **Buttresses:** The flying buttresses that you have to climb to get to the + spiral staircase. "Near" and "far" are relative to the light cathedral, so the + nearest buttress is the one that leads back to the walkway. + +* **Tomb:** The area past the illusory wall just before the spiral staircase, in + which you marry Anri during Yoel and Yuria's quest. + +* **Dark cathedral:** The darkened cathedral just before the Aldrich fight in + vanilla. + +### Lothric Castle + +This region covers everything up the ladder from the Dancer of the Boreal Valley +bonfire up to the door into Grand Archives, except the area to the left of the +ladder which is part of Consumed King's Garden. + +* **Lift:** The elevator from the room straight after the Dancer of the Boreal + Valley bonfire up to just before the boss fight. + +* **Ascent:** The set of stairways and turrets leading from the Lothric Castle + bonfire to the Dragon Barracks bonfire. + +* **Barracks:** The large building with two fire-breathing wyverns across from + the Dragon Barracks bonfire. + +* **Moat:** The ditch beneath the bridge leading to the barracks. + + * The "right path" leads to the right as you face the barracks, around and + above the stairs up to the Dragon Barracks bonfire. + +* **Plaza:** The open area in the center of the barracks, where the two wyverns + breathe fire. + + * "Left" is the enclosed area on the left as you're coming from the Dragon + Barracks bonfire, with the stairs down to the basement. + +* **Basement:** The room beneath plaza left, with the Boreal Outrider in + vanilla. + +* **Dark room:** The large darkened room on the right of the barracks as you're + coming from the Dragon Barracks bonfire, with firebomb-throwing Hollows in + vanilla. + + * "Lower" is the bottom floor that you enter onto from the plaza. + * "Upper" is the top floor with the door to the main hall. + * "Mid" is the middle floor accessible by climbing a ladder from lower or + going down stairs from upper. + +* **Main hall:** The central room of the barracks, behind the gate. + +* **Chapel:** The building to the right just before the stairs to the boss, with + a locked elevator to Grand Archives. + +* **Wyvern room:** The room where you can fight the Pus of Man infecting the + left wyvern, accessible by dropping down to the left of the stairs to the + boss. + +* **Altar:** The building containing the Altar of Sunlight, accessible by + climbing up a ladder onto a roof around the corner from the stairs to the + boss. + +### Consumed King's Garden + +This region covers everything to the left of the ladder up from the Dancer of +the Boreal Valley bonfire up to the illusory wall into Untended Graves. + +* **Balcony:** The walkway accessible by getting off the first elevator halfway + down. + +* **Rotunda:** The building in the center of the toxic pool, with a Cathedral + Knight on it in vanilla. + +* **Lone stairway:** A set of stairs leading nowhere in the far left of the main + area as you enter from the first elevator. + +* **Shortcut:** The path from the locked door into Lothric Castle, through the + room filled with thralls in vanilla, and down a lift. + +* **Tomb:** The area after the boss room. + +### Grand Archives + +* **1F:** The first floor of the Grand Archives, including the first wax pool. + +* **Dark room:** The unlit room on 1F to the right of the wax pool. + +* **2F:** The second floor of the grand archives. It's split into two sections + that are separated by retractable bookshelves. + + * "Early" is the first part you reach and has an outdoor balcony with a ladder + to 3F and a wax pool up a short set of stairs. + * "Late" is the part you can only reach by climbing down from F3, where you + encounter the teleporting miniboss for the final time. + +* **3F:** The third floor of the grand archives, where you encounter the + teleporting miniboss for the second time. Includes the area with a hidden room + with another miniboss. + +* **4F:** The topmost and most well-lit section of bookshelves, overlooking the + rest of the archives. + +* **Rooftops:** The outer rooftop area between 4F and 5F, with Gargoyles in + vanilla. + + * "Lower" is the balcony you can reach by dropping off the rooftops, as well + as the further rooftops leading down to the 2F early balcony. + +* **5F:** The topmost floor of the archives interior, accessible from the + rooftops, with a ladder down to 4F. + +* **Dome:** The domed roof of the Grand Archives, with Ascended Winged Knights + in vanilla. + +* **Rafters:** The narrow walkways above the Grand Archives, accessible by + dropping down from the dome. + +### Untended Graves + +* **Swamp:** The watery area immediately after the Untended graves bonfire, up + to the cemetery. + +* **Cemetery:** The area past where the Cemetery of Ash bonfire would be, up to + the boss arena. + +* **Environs:** The area after the boss and outside the abandoned Firelink + Shrine. + +* **Shrine:** The area inside the abandoned Firelink Shrine. + +### Archdragon Peak + +"Gesture" always means the Path of the Dragon gesture. + +* **Intro:** The first section, from where you warp in from Irithyll Dungeon up + to the first boss fight. + + * "Archway": The large stone archway in front of the boss door. + +* **Fort:** The arena where you fight Ancient Wyvern in vanilla. + + * "Overlook": The area down the stairs from where the Ancient Wyvern first + lands in vanilla, overlooking the fog. + + * "Rotunda": The top of the spiral staircase building, to the left before the + bridge with the chain-axe Man-Serpent in vanilla. + +* **Mausoleum:** The building with the Dragon-Kin Mausoleum bonfire, where + you're warped after the first boss fight. + +* **Walkway:** The path from the mausoleum to the belfry, looking out over + clouds. + + * "Building": The building along the walkway, just before the wyvern in + vanilla. + +* **Belfry:** The building with the Great Belfry bonfire, including the room + with the summoner. + +* **Plaza:** The arena that appears after you defeat Nameless King in vanilla. + +* **Summit:** The path up from the belfry to the final altar at the top of the + mountain. + +### Painted World of Ariandel (Before Contraption) + +This region covers the Ashes of Ariandel DLC up to the point where you must use +the Contraption Key to ascend to the second level of the building and first meet +the painter. + +* **Snowfield:** The area around the Snowfield bonfire, + + * "Upper": The area immediately after the Snowfield bonfire, before the + collapsing overhang, with the Followers in vanilla. + + * "Lower": The snowy tree-filled area after the collapsing overhang, with the + Wolves in vanilla. + + * "Village": The area with broken-down buildings and Millwood Knights in + vanilla. + + * "Tower": The tower by the village, with Millwood Knights in Vanilla. + +* **Bridge:** The rope bridge to the chapel. + + * "Near": The side of the bridge by the Rope Bridge Cave bonfire. + + * "Far": The side of the bridge by the Ariandel Chapel bonfire. + +* **Chapel:** The building with the Ariandel Chapel bonfire and Lady Friede. + +* **Depths:** The area reachable by cutting down the bridge and descending on + the far side, with the Depths of the Painting bonfire. + +* **Settlement:** The area reachable by cutting down the bridge and descending + on the near side, with the Corvian Settlement bonfire. Everything after the + slide down the hill is considered part of the settlement. + + * "Courtyard": The area in front of the settlement, immediately after the + slide. + + * "Main": The main road of the settlement leading up to the locked gate to the + library. Also includes the buildings that are immediately accessible from + this road. + + * "Loop": A side path that loops left from the main road and goes up and + behind the building with the bonfire. + + * "Back": The back alley of the settlement, accessible by dropping down to the + right of the locked gate to the library. Also includes the buildings that + are immediately accessible from this alley. + + * "Roofs": The village rooftops, first accessible by climbing a ladder from + the back alley. Also includes the buildings and items that are first + accessible from the roofs. + + * "Hall": The largest building in the settlement, with two Corvian Knights in + vanilla. + +* **Library:** The building where you use the contraption key, where Vilhelm + appears in vanilla. + +### Painted World of Ariandel (After Contraption) + +This region covers the Ashes of Ariandel DLC past the point where you must use +the Contraption Key to ascend to the second level of the building and first meet +the painter, including the basement beneath the chapel. + +* **Pass:** The mountainous area past the Snowy Mountain Pass bonfire. + +* **Pit:** The area with a large tree and numerous Millwood Knights in vanilla, + reached by a collapsing overhang in the pass. + +* **B1:** The floor immediately below the chapel, first accessible from the + pass. Filled with Giant Flies in vanilla. + +* **B2:** The floor below B1, with lots of fly eggs. Filled with even more Giant + Flies than B1 in vanilla. + +* **B3:** The floor below B2, accessible through an illusory wall. + +* **Rotunda:** The round arena out in the open, accessible by platforming down + tree roots from B3. + +### Dreg Heap + +* **Shop:** Items sold by the Stone-Humped Hag by The Dreg Heap bonfire. + +* **Castle:** The building with The Dreg Heap bonfire, up to the large fall into + the library. + +* **Library:** The building with the stained-glass window that you fall into + from the castle. + +* **Church:** The building below and to the right of the library, which the + pillar falls into to make a bridge. + +* **Pantry:** The set of rooms entered through a door near the fountain just + past the church, with boxes and barrels. + + * "Upstairs": The room with an open side, accessible through an illusory wall + in the furthest pantry room. + +* **Parapets:** The area with balconies and Overgrown Lothric Knights in + vanilla, accessible by taking the pillar bridge from the church, following + that path to the end, and dropping down to the right. + +* **Ruins:** The area around the Earthen Peak Ruins bonfire, up to the swamp. + +* **Swamp:** The area in and above the poisonous water, up to the point the + branches deposit you back on the ruins. + + * "Left": Left as you enter from the ruins, towards the cliff edge. + + * "Right": Right as you enter from the ruins, towards higher ground. + + * "Upper": The path up and over the swamp towards the Within Earthen Peak + Ruins bonfire. + +### Ringed City + +The "mid boss", "end boss", and "hidden boss" are the bosses who take the place +of Halflight, Gael, and Midir, respectively. + +* **Wall:** The large wall in which you spawn when you first enter the area, + with the Mausoleum Lookout bonfire. + + * "Top": The open-air top of the wall, where you first spawn in. + + * "Upper": The upper area of the wall, with the Ringed Inner Wall bonfire. + + * "Tower": The tiered tower leading down from the upper area to the stairs. + + * "Lower": The lower rooms of the wall, accessible from the lower cliff, with + an elevator back to upper. + + * "Hidden": The hidden floor accessible from the elevator from lower to upper, + from which you can reach Midir in vanilla. + +* **Streets:** The streets and skyways of the city proper. "Left" and "right" + are relative to the main staircase as you head down towards the swamp, "near" + and "far" are relative to Shira's chamber at the top of the stairs. + + * "Garden": The flower-filled back alley accessible from the left side of the + nearest bridge over the stairs. + + * "High": The higher areas in the far left where you can find the Locust + Preacher, accessible from a long ladder in the swamp. + + * "Monument": The area around the purging monument, which can only be accessed + by solving the "Show Your Humanity" puzzle. + +* **Swamp:** The wet area past the city streets. "Left" and "right" are relative + to heading out from the Ringed City Streets bonfire, and "near" and "far" are + relative to that bonfire as well. + +* **Lower cliff:** The cliffside path leading from the swamp into the shared + grave, where Midir breathes fire. + +* **Grave:** The cylindrical chamber with spiral stairs around the edges, + connecting the two cliffs, containing the Shared Grave bonfire. + +* **Upper cliff:** The cliffside path leading out of the grave to the lower + wall. + +* **Church path:** The sunlit path from the lower cliff up to the Church of + Filianore where you fight Halflight in vanilla. + +* **Ashes:** The final area, where you fight Gael in vanilla. + +## Detailed Location Descriptions + +These location descriptions were originally written by [Matt Gruen] for [the +static _Dark Souls III_ randomizer]. + +[Matt Gruen]: https://thefifthmatt.com/ +[the static _Dark Souls III_ randomizer]: https://www.nexusmods.com/darksouls3/mods/361 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Location nameDetailed description
AL: Aldrich Faithful - water reserves, talk to McDonnelGiven by Archdeacon McDonnel in Water Reserves.
AL: Aldrich's Ruby - dark cathedral, minibossDropped by the Deep Accursed who drops down when you open the Anor Londo Cathedral shortcut
AL: Anri's Straight Sword - Anri questDropped by Anri of Astora upon death or completing quest. In the Darkmoon Tomb with Lord of Hollows route, or given by Ludleth if summoned to defeat Aldrich.
AL: Blade of the Darkmoon - Yorshka with Darkmoon LoyaltyGiven by Yorshka after learning the Darkmoon Loyalty gesture from Sirris, or by killing her
AL: Brass Armor - tombBehind the illusory statue in the hallway leading to the Darkmoon Tomb
AL: Brass Gauntlets - tombBehind the illusory statue in the hallway leading to the Darkmoon Tomb
AL: Brass Helm - tombBehind the illusory statue in the hallway leading to the Darkmoon Tomb
AL: Brass Leggings - tombBehind the illusory statue in the hallway leading to the Darkmoon Tomb
AL: Chameleon - tomb after marrying AnriDropped by the Stone-humped Hag assassin after Anri reaches the Church of Yorshka, either in the church or after marrying Anri
AL: Cinders of a Lord - AldrichDropped by Aldrich
AL: Crescent Moon Sword - Leonhard dropDrop by Ringfinger Leonhard upon death. Includes Soul of Rosaria if invaded in Anor Londo.
AL: Dark Stoneplate Ring - by dark stairs up from plazaAfter the Pontiff fight, in the dark hallways to the left of the area with the Giant Slaves
AL: Deep Gem - water reservesIn the open in the Water Reserves
AL: Dragonslayer Greatarrow - drop from nearest buttressDropping down from about halfway down the flying buttress closest to the entrance to the Darkmoon Tomb
AL: Dragonslayer Greatbow - drop from nearest buttressDropping down from about halfway down the flying buttress closest to the entrance to the Darkmoon Tomb
AL: Drang Twinspears - plaza, NPC dropDropped by Drang Twinspears-wielding knight on the stairs leading up to the Anor Londo Silver Knights
AL: Easterner's Ashes - below top of furthest buttressDropping down from the rightmost flying buttress, or the rightmost set of stairs
AL: Ember - plaza, furtherAfter the Pontiff fight, in the middle of the area with the Giant Slaves
AL: Ember - plaza, right sideAfter the Pontiff fight, next to one of the Giant Slaves on the right side
AL: Ember - spiral staircase, bottomNext to the lever that summons the rotating Anor Londo stairs at the bottom
AL: Estus Shard - dark cathedral, by left stairsIn a chest on the floor of the Anor Londo cathedral
AL: Giant's Coal - by giant near dark cathedralOn the Giant Blacksmith's corpse in Anor Londo
AL: Golden Ritual Spear - light cathedral, mimic upstairsDrop from a mimic in the higher levels of Pontiff's cathedral, accessible from the Deacons after the Pontiff fight
AL: Havel's Ring+2 - prison tower, raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Human Dregs - water reservesIn the open in the Water Reserves
AL: Large Soul of a Weary Warrior - left of dark cathedral entranceIn front of the Anor Londo cathedral, slightly to the left
AL: Large Titanite Shard - balcony by dead giantsAfter the Pontiff fight, on the balcony to the right of the area with the Giant Slaves
AL: Large Titanite Shard - bottom of the furthest buttressAt the base of the rightmost flying buttress leading up to Anor Londo
AL: Large Titanite Shard - bottom of the nearest buttressOn the tower leading back from Anor Londo to the shortcut to Irithyll, down the flying buttress closest to the Darkmoon Tomb entrance.
AL: Large Titanite Shard - right after light cathedralAfter Pontiff's cathedral, hugging the wall to the right
AL: Large Titanite Shard - walkway, side path by cathedralAfter the Pontiff fight, going back from the Deacons area to the original cathedral, before a dropdown
AL: Moonlight Arrow - dark cathedral, up right stairsIn the Anor Londo cathedral, up the stairs on the right side
AL: Painting Guardian Gloves - prison tower, raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Painting Guardian Gown - prison tower, raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Painting Guardian Hood - prison tower, raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Painting Guardian Waistcloth - prison tower, raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Painting Guardian's Curved Sword - prison tower raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Proof of a Concord Kept - dark cathedral, up left stairsIn the Anor Londo cathedral, halfway down the stairs on the left side next to some Deacons
AL: Reversal Ring - tomb, chest in cornerIn a chest in Darkmoon Tomb
AL: Ring of Favor - water reserves, both minibossesDropped after killing both of Sulyvahn's Beasts in the Water Reserves
AL: Ring of Favor+1 - light cathedral, upstairsIn the higher levels of Pontiff's cathedral, accessible from the Deacons after the Pontiff fight
AL: Silver Mask - Leonhard dropDrop by Ringfinger Leonhard upon death. Includes Soul of Rosaria if invaded in Anor Londo.
AL: Simple Gem - light cathedral, lizard upstairsDropped by a Crystal Lizard in the higher levels of Pontiff's cathedral, accessible from the Deacons after the Pontiff fight
AL: Soul of AldrichDropped by Aldrich
AL: Soul of Rosaria - Leonhard dropDrop by Ringfinger Leonhard upon death. Includes Soul of Rosaria if invaded in Anor Londo.
AL: Soul of a Crestfallen Knight - right of dark cathedral entranceTo the right of the Anor Londo cathedral entrance, past the red-eyed Silver Knight
AL: Soul of a Weary Warrior - plaza, nearerAfter the Pontiff fight, in the middle of the area with the Giant Slaves
AL: Sun Princess Ring - dark cathedral, after bossIn the Anor Londo cathedral after defeating Aldrich, up the elevators in Gwynevere's Chamber
AL: Titanite Scale - top of ladder up to buttressesOn the platform after the stairs leading up to Anor Londo from the Water Reserves building
AL: Twinkling Titanite - lizard after light cathedral #1Dropped a Crystal Lizard straight after the Pontiff fight
AL: Twinkling Titanite - lizard after light cathedral #2Dropped a Crystal Lizard straight after the Pontiff fight
AL: Yorshka's Chime - kill YorshkaDropped by Yorshka upon death.
AP: Ancient Dragon Greatshield - intro, on archwayAfter the Archdragon Peak bonfire, on top of the arch in front of the Ancient Wyvern fight
AP: Calamity Ring - mausoleum, gesture at altarReceived using Path of the Dragon at the Altar by the Mausoleum bonfire
AP: Covetous Gold Serpent Ring+2 - plazaIn the Nameless King boss arena after he is defeated
AP: Dragon Chaser's Ashes - summit, side pathIn the run-up to the Dragon Altar after the Belfry bonfire, in a side path to the left side
AP: Dragon Head Stone - fort, boss dropDropped by Ancient Wyvern
AP: Dragon Tooth - belfry roof, NPC dropDropped from any of the Havel Knights
AP: Dragonslayer Armor - plazaIn the Nameless King boss arena after he is defeated
AP: Dragonslayer Gauntlets - plazaIn the Nameless King boss arena after he is defeated
AP: Dragonslayer Helm - plazaIn the Nameless King boss arena after he is defeated
AP: Dragonslayer Leggings - plazaIn the Nameless King boss arena after he is defeated
AP: Dragonslayer Spear - gate after mausoleumIn the gate connecting the Dragon-Kin Mausoleum area to the bridge where the Nameless King fight takes place
AP: Drakeblood Greatsword - mausoleum, NPC dropDropped by the Drakeblood Knight summoned by the Serpent-Man Summoner
AP: Dung Pie - fort, landing after second roomOn a landing going up the stairs from the Ancient Wyvern to the chainaxe Man-Serpent area
AP: Ember - belfry, below bellIn the area below the bell lever, either dropping down near the lever or going down the stairs from the open fountain area after the Belfry bonfire
AP: Ember - fort overlook #1From the right of where Ancient Wyvern first lands
AP: Ember - fort overlook #2From the right of where Ancient Wyvern first lands
AP: Ember - intro, by bonfireNext to the Archdragon Peak bonfire
AP: Great Magic Barrier - drop off belfry roofDropping down to the left from the area with the Havel Knight and the dead Wyvern
AP: Havel's Greatshield - belfry roof, NPC dropDropped from any of the Havel Knights
AP: Havel's Ring+1 - summit, after buildingJust past the building with all of the Man-Serpents on the way to the Dragon Altar, on the left side
AP: Homeward Bone - intro, path to bonfireFrom the start of the area, along the left path leading to the first bonfire
AP: Large Soul of a Crestfallen Knight - summit, by fountainIn the middle of the open fountain area after the Belfry bonfire
AP: Large Soul of a Nameless Soldier - fort, by stairs to first roomto the left of where the Ancient Wyvern lands
AP: Large Soul of a Weary Warrior - fort, centerWhere the Ancient Wyvern lands
AP: Lightning Bolt - rotundaOn top of the ruined dome found going up spiral stairs to the left before the bridge with the chainaxe Man-Serpent
AP: Lightning Clutch Ring - intro, left of boss doorTo the left of gate leading to Ancient Wyvern, past the Rock Lizard
AP: Lightning Gem - intro, side riseFrom the start of the area, up a ledge in between two forked paths toward the first bonfire
AP: Lightning Urn - fort, left of first room entranceOn the path to the left of where the Ancient Wyvern lands, left of the building entrance
AP: Ricard's Rapier - belfry, NPC dropDropped by the Richard Champion summoned by the Serpent-Man Summoner
AP: Ring of Steel Protection - fort overlook, beside stairsTo the right of the area where the Ancient Wyvern lands, dropping down onto the ledge
AP: Soul of a Crestfallen Knight - mausoleum, upstairsFrom the Mausoleum bonfire, up the second set of stairs to the right
AP: Soul of a Nameless Soldier - intro, right before archwayFrom the Archdragon Peak bonfire, going right before the arch before Ancient Wyvern
AP: Soul of a Weary Warrior - intro, first cliff edgeAt the very start of the area on the left cliff edge
AP: Soul of a Weary Warrior - walkway, building windowOn the way to the Belfry bonfire after the sagging wooden bridge, on a ledge visible in a room with a Crystal Lizard, accessible by a tricky jump or just going around the other side
AP: Soul of the Nameless KingDropped by Nameless King
AP: Stalk Dung Pie - fort overlookFrom the right of where Ancient Wyvern first lands
AP: Thunder Stoneplate Ring - walkway, up ladderAfter the long hallway after the Mausoleum bonfire, before the rope bridge, up the long ladder
AP: Titanite Chunk - fort, second room balconyAfter going left of where Ancient Wyvern lands and left again, rather than going up the stairs to the right, go to the open area to the left
AP: Titanite Chunk - intro, archway cornerFrom the Archdragon Peak bonfire, under the arch, immediately to the right
AP: Titanite Chunk - intro, behind rockAlmost at the Archdragon Peak bonfire, behind a rock in the area with many Man-Serpents
AP: Titanite Chunk - intro, left before archwayAfter the Archdragon Peak bonfire, going left before the arch before Ancient Wyvern
AP: Titanite Chunk - rotundaOn top of the ruined dome found going up spiral stairs to the left before the bridge with the chainaxe Man-Serpent
AP: Titanite Chunk - walkway, miniboss dropDropped by the second Ancient Wyvern patrolling the path up to the Belfry
AP: Titanite Scale - mausoleum, downstairs balcony #1From the Mausoleum bonfire, up the stairs to the left, past the Rock Lizard
AP: Titanite Scale - mausoleum, downstairs balcony #2From the Mausoleum bonfire, up the stairs to the left, past the Rock Lizard
AP: Titanite Scale - mausoleum, upstairs balconyFrom the Mausoleum bonfire, up the first stairs to the right, going around toward the Man-Serpent Summoner, on the balcony on the side
AP: Titanite Scale - walkway buildingIn a chest after the sagging wooden bridge on the way to the Belfry, in the building with the Crystal Lizard
AP: Titanite Scale - walkway, miniboss dropDropped by the second Ancient Wyvern patrolling the path up to the Belfry
AP: Titanite Slab - belfry roofNext to the Havel Knight by the dead Wyvern
AP: Titanite Slab - plazaIn the Nameless King boss arena after he is defeated
AP: Twinkling Dragon Torso Stone - summit, gesture at altarReceived using Path of the Dragon at the Altar after the Belfry bonfire. Hawkwood also uses the gesture there when summoned.
AP: Twinkling Titanite - belfry, by ladder to roofIn the chest before the ladder climbing up to the Havel Knight
AP: Twinkling Titanite - fort, down second room balcony ladderAfter going left of where Ancient Wyvern lands and left again, rather than going up the stairs to the right, go to the open area to the left and then down the ladder
AP: Twinkling Titanite - fort, end of raftersDropping down to the left of the Mausoleum bonfire, all the way down the wooden rafters
AP: Twinkling Titanite - walkway building, lizardDropped by Crystal Lizard in the building after the sagging wooden bridge toward the Belfry
AP: Twinkling Titanite - walkway, miniboss dropDropped by the second Ancient Wyvern patrolling the path up to the Belfry
CA: Coiled Sword - boss dropDropped by Iudex Gundyr
CA: Firebomb - down the cliff edgeAlong the cliff edge before the Iudex Gundyr fight, to the right
CA: Soul of a Deserted Corpse - right of spawnAt the very start of the game
CA: Soul of an Unknown Traveler - by minibossIn the area with the Ravenous Crystal Lizard
CA: Speckled Stoneplate Ring+1 - by minibossIn the area with the Ravenous Crystal Lizard, along the right wall
CA: Titanite Scale - miniboss dropDropped by Ravenous Crystal Lizard
CA: Titanite Shard - jump to coffinMaking a jump to a coffin after the Cemetery of Ash bonfire
CC: Black Blade - tomb, mimicDropped by the mimic before Smouldering Lake
CC: Black Bug Pellet - cavern, before bridgeIn the area where many many skeletons are before the bridge you can cut
CC: Bloodred Moss Clump - atrium lower, down more stairsTo the left before going down the main stairwell in the Catacombs, past the skeleton ambush and where Anri is standing, near the Crystal Lizard
CC: Carthus Bloodring - crypt lower, end of side hallAt the very end of the Bonewheel Skeleton area
CC: Carthus Milkring - crypt upper, among potsAfter the first Skeleton Ball, in the hallway alcove with the many dark-exploding pots
CC: Carthus Pyromancy Tome - atrium lower, jump from bridgeDown the hallway to the right before going down the main stairwell in the Catacombs and through an illusory wall on the left, or making a difficult dropdown from the top-level platform
CC: Carthus Rouge - atrium upper, left after entranceTo the right after first entering the Catacombs
CC: Carthus Rouge - crypt across, cornerMaking a difficult jump between the hallway after the first Skeleton Ball and the area at the same level on the opposite side, or going up the stairs from the main hall
CC: Dark Gem - crypt lower, skeleton ball dropDropped by second Skeleton Ball after killing its sorcerer skeleton
CC: Ember - atrium, on long stairwayOn the main stairwell in Catacombs
CC: Ember - crypt lower, shortcut to cavernIn the short hallway with the level shortcut where Knight Slayer Tsorig invades
CC: Ember - crypt upper, end of hall past holeGoing right from the Catacombs bonfire, down the hall to the left, then to the right. After a hole that drops down into the Bonewheel Skeleton area.
CC: Fire Gem - cavern, lizardDropped by a Crystal Lizard found between the Catacombs main halls and the ledge overlooking the bridge you can cut down
CC: Grave Warden Pyromancy Tome - boss arenaIn Wolnir's arena, or in the back left of the room containing his bonfire if not picked up in the arena
CC: Grave Warden's Ashes - crypt across, cornerFrom the Catacombs bonfire, down the stairs into the main hall and up the stairs to the other side, on the far left side. Stairwell past the illusory wall is most direct.
CC: Homeward Bone - Irithyll bridgeFound right before the wall blocking access to Irithyll
CC: Large Soul of a Nameless Soldier - cavern, before bridgeIn the area where many many skeletons are before the bridge you can cut
CC: Large Soul of a Nameless Soldier - tomb lowerDown the ramp from the Fire Demon, where all the skeletons are
CC: Large Soul of an Unknown Traveler - crypt upper, hall middleGoing right from the Catacombs bonfire, then down the long hallway after the hallway to the left
CC: Large Titanite Shard - crypt across, middle hallFrom the Catacombs bonfire, down the stairs into the main hall and up the stairs to the other side, in a middle hallway
CC: Large Titanite Shard - crypt upper, skeleton ball hallGoing right from the Catacombs bonfire, to the end of the hallway where second Skeleton Ball rolls
CC: Large Titanite Shard - tomb lowerDown the ramp from the Fire Demon, where all the skeletons are
CC: Old Sage's Blindfold - tomb, hall before bonfireDown the ramp from the Fire Demon, straight down the hallway past the room with the Abandoned Tomb bonfire
CC: Pontiff's Right Eye - Irithyll bridge, miniboss dropDropped by killing Sulyvahn's Beast on the bridge to Irithyll or in the lake below
CC: Ring of Steel Protection+2 - atrium upper, drop onto pillarFrom the first bridge in Catacombs where the first skeletons are encountered, parallel to the long stairwell, walk off onto a pillar on the left side.
CC: Sharp Gem - atrium lower, right before exitDown the hallway to the right before going down the main stairwell in the Catacombs
CC: Soul of High Lord WolnirDropped by High Lord Wolnir
CC: Soul of a Demon - tomb, miniboss dropDropped by the Fire Demon before Smouldering Lake
CC: Soul of a Nameless Soldier - atrium lower, down hallAll the way down the hallway to the right before going down the main stairwell in the Catacombs
CC: Soul of a Nameless Soldier - atrium upper, up more stairsFrom the room before the Catacombs main stairwell, up the two ramps and to the end of the long hallway crossing the room
CC: Thunder Stoneplate Ring+1 - crypt upper, among potsAfter the first Skeleton Ball, in the hallway alcove with the many dark-exploding pots, behind one of the pillars
CC: Titanite Shard - atrium lower, corner by stairsTo the left before going down the main stairwell in the Catacombs, behind the pensive Carthus Cursed Sword Skeleton
CC: Titanite Shard - crypt lower, left of entranceIn the main hall after the Catacombs bonfire, down the stairs and to the left
CC: Titanite Shard - crypt lower, start of side hallIn the Bonewheel Skeleton area, on the left side under a Writhing Flesh
CC: Twinkling Titanite - atrium lower, lizard down more stairsDropped by a Crystal Lizard found to the left before going down the main stairwell in the Catacombs, past the skeleton ambush and past where Anri is standing
CC: Undead Bone Shard - crypt upper, skeleton ball dropDropped by first Skeleton Ball after killing its sorcerer skeleton
CC: Witch's Ring - tomb, hall before bonfireDown the ramp from the Fire Demon, straight down the hallway past the room with the Abandoned Tomb bonfire
CC: Yellow Bug Pellet - cavern, on overlookTo the right of the Carthus Curved Sword Skeleton overlooking the pit Horace falls into
CD: Aldrich's Sapphire - side chapel, miniboss dropDropped by the Deep Accursed
CD: Arbalest - upper roofs, end of furthest buttressBefore the rafters on the way to Rosaria, up a flying buttress, past a halberd-wielding Large Hollow Soldier to the right, and down another flying buttress to the right
CD: Archdeacon Holy Garb - boss room after killing bossNear the Deacons of the Deep bonfire, found after resting at it
CD: Archdeacon Skirt - boss room after killing bossNear the Deacons of the Deep bonfire, found after resting at it
CD: Archdeacon White Crown - boss room after killing bossNear the Deacons of the Deep bonfire, found after resting at it
CD: Armor of Thorns - Rosaria's Bed Chamber after killing KirkFound in Rosaria's Bed Chamber after killing Longfinger Kirk
CD: Astora Greatsword - graveyard, left of entranceDown one of the side paths to the left in the Reanimated Corpse area
CD: Barbed Straight Sword - Kirk dropDropped by Longfinger Kirk when he invades in the cathedral central room
CD: Black Eye Orb - Rosaria from Leonhard's questOn Rosaria's corpse after joining Rosaria's Fingers, exhausting Leonhard's dialogue there and reaching the Profaned Capital bonfire.
CD: Blessed Gem - upper roofs, raftersIn the rafters leading to Rosaria, guarded by a Cathedral Knight to the right
CD: Crest Shield - path, drop down by Cathedral of the Deep bonfireOn a grave near the Cathedral of the Deep bonfire, accessed by dropping down to the right
CD: Curse Ward Greatshield - by ladder from white tree to moatTaking a right after the Infested Corpse graveyard, before the shortcut ladder down to the Ravenous Crystal Lizard area
CD: Deep Braille Divine Tome - mimic by side chapelDropped by the Mimic before the room with the patrolling Cathedral Knight and Deep Accursed
CD: Deep Gem - down stairs by first elevatorComing from the room where you first see deacons, go down instead of continuing to the main cathedral room. Guarded by a pensive Cathedral Evangelist.
CD: Deep Ring - upper roofs, passive mob drop in first towerDropped by the passive Deacon on the way to Rosaria
CD: Drang Armor - main hall, eastIn the Giant Slave muck pit leading up to Deacons
CD: Drang Gauntlets - main hall eastIn the Giant Slave muck pit leading up to Deacons
CD: Drang Hammers - main hall eastIn the Giant Slave muck pit leading up to Deacons, underneath the stairwell
CD: Drang Shoes - main hall eastIn the Giant Slave muck pit leading up to Deacons
CD: Duel Charm - by first elevatorAfter opening the cathedral's backdoor, where the Deacon enemies are first seen, under a fountain that spouts poison
CD: Duel Charm - next to Patches in onion armorTo the right of the bridge leading to Rosaria, from the Deacons side. Patches will lower the bridge if you try to cross from this side.
CD: Ember - PatchesSold by Patches in Firelink Shrine
CD: Ember - by back doorPast the pair of Grave Wardens and the Cathedral backdoor against a wall, guarded by a greataxe-wielding Large Hollow Soldier
CD: Ember - edge of platform before bossOn the edge of the chapel before Deacons overlooking the Giant Slaves
CD: Ember - side chapel upstairs, up ladderUp a ladder and past the Cathedral Evangelist from the top level of the room with the patrolling Cathedral Knight and Deep Accursed
CD: Ember - side chapel, miniboss roomIn the room with the Deep Accursed
CD: Estus Shard - monument outside Cleansing ChapelRight outside of the Cleansing Chapel. Requires killing praying hollows.
CD: Executioner's Greatsword - graveyard, far endIn an open area down one of the side paths to the left in the Reanimated Corpse area
CD: Exploding Bolt - ledge above main hall southOn the ledge where the Giant Slave slams his arms down
CD: Fading Soul - graveyard, far endIn an open area down one of the side paths to the left in the Reanimated Corpse area, next to the Executioner's Greatsword
CD: Gauntlets of Thorns - Rosaria's Bed Chamber after killing KirkFound in Rosaria's Bed Chamber after killing Longfinger Kirk
CD: Helm of Thorns - Rosaria's Bed Chamber after killing KirkFound in Rosaria's Bed Chamber after killing Longfinger Kirk
CD: Herald Armor - path, by fireGuarded by the Cathedral Evangelist after the Crystal Sage fight
CD: Herald Gloves - path, by fireGuarded by the Cathedral Evangelist after the Crystal Sage fight
CD: Herald Helm - path, by fireGuarded by the Cathedral Evangelist after the Crystal Sage fight
CD: Herald Trousers - path, by fireGuarded by the Cathedral Evangelist after the Crystal Sage fight
CD: Heysel Pick - Heysel Corpse-Grub in Rosaria's Bed ChamberDropped by the Heysel Corpse-grub in Rosaria's Bed Chamber
CD: Homeward Bone - outside main hall south doorPast the cathedral doors guarded by the Giant Slave opposite to the Deacons fight
CD: Horsehoof Ring - PatchesSold or dropped by Patches after he mentions Greirat
CD: Large Soul of an Unknown Traveler - by white tree #1In the graveyard with the White Birch and Infested Corpses
CD: Large Soul of an Unknown Traveler - by white tree #2In the graveyard with the White Birch and Infested Corpses
CD: Large Soul of an Unknown Traveler - lower roofs, semicircle balconyOn the cathedral roof after climbing up the flying buttresses, on the edge of the semicircle platform balcony
CD: Large Soul of an Unknown Traveler - main hall eastIn the Giant Slave muck pit leading up to Deacons
CD: Large Soul of an Unknown Traveler - main hall south, side pathDown a side path with poison-spouting fountains in the main cathedral room, accessible from the Cleansing Chapel shortcut, patrolled by a Cathedral Knight
CD: Large Soul of an Unknown Traveler - path, against outer wallFrom the Cathedral of the Deep bonfire after the Brigand, against the wall in the area with the dogs and crossbowmen
CD: Leggings of Thorns - Rosaria's Bed Chamber after killing KirkFound in Rosaria's Bed Chamber after killing Longfinger Kirk
CD: Lloyd's Sword Ring - ledge above main hall southOn the ledge where the Giant Slave slams his arms down
CD: Maiden Gloves - main hall southIn the muck pit with the Giant Slave that can attack with his arms
CD: Maiden Hood - main hall southIn the muck pit with the Giant Slave that can attack with his arms
CD: Maiden Robe - main hall southIn the muck pit with the Giant Slave that can attack with his arms
CD: Maiden Skirt - main hall southIn the muck pit with the Giant Slave that can attack with his arms
CD: Notched Whip - Cleansing ChapelIn a corner of the Cleansing Chapel
CD: Paladin's Ashes - path, guarded by lower NPCAt the very start of the area, guarded by the Fallen Knight
CD: Pale Tongue - main hall eastIn the Giant Slave muck pit leading up to Deacons
CD: Pale Tongue - upper roofs, outdoors far endBefore the rafters on the way to Rosaria, up a flying buttress and straight right, passing a halberd-wielding Large Hollow Soldier
CD: Poisonbite Ring - moat, hall past minibossIn the pit with the Infested Corpse, accessible from the Ravenous Crystal Lizard area or from dropping down near the second Cleansing Chapel shortcut
CD: Red Bug Pellet - lower roofs, up stairs between buttressesIn the area after the cathedral roof against the wall of the cathedral, down the path from the Cathedral Evangelist.
CD: Red Bug Pellet - right of cathedral front doorsUp the stairs past the Infested Corpse graveyard and the left, toward the roof path to the right of the cathedral doors
CD: Red Sign Soapstone - passive mob drop by Rosaria's Bed ChamberDropped by passive Corpse-grub against the wall near the entrance to Rosaria's Bed Chamber
CD: Repair Powder - by white treeIn the graveyard with the White Birch and Infested Corpses
CD: Ring of Favor+2 - upper roofs, on buttressBefore the rafters on the way to Rosaria, up a flying buttress, behind a greataxe-wielding Large Hollow Soldier to the left
CD: Ring of the Evil Eye+1 - by stairs to bossBefore the stairs leading down into the Deacons fight
CD: Rosaria's Fingers - RosariaGiven by Rosaria.
CD: Rusted Coin - don't forgive PatchesGiven by Patches after not forgiving him after he lowers the bridge in Cathedral of the Deep.
CD: Rusted Coin - left of cathedral front doors, behind cratesUp the stairs past the Infested Corpse graveyard and to the left, hidden behind some crates to the left of the cathedral door
CD: Saint Bident - outside main hall south doorPast the cathedral doors guarded by the Giant Slave opposite to the Deacons fight
CD: Saint-tree Bellvine - moat, by waterIn the Infested Corpse moat beneath the Cathedral
CD: Seek Guidance - side chapel upstairsAbove the room with the patrolling Cathedral Knight and Deep Accursed, below a writhing flesh on the ceiling.
CD: Shotel - PatchesSold by Patches
CD: Small Doll - boss dropDropped by Deacons of the Deep
CD: Soul of a Nameless Soldier - ledge above main hall southOn the ledge where the Giant Slave slams his arms down
CD: Soul of a Nameless Soldier - lower roofs, side roomComing from the cathedral roof, past the three crossbowmen to the path patrolled by the halberd-wielding Large Hollow Soldier, in a room to the left with many thralls.
CD: Soul of a Nameless Soldier - main hall southIn the muck pit with the Giant Slave that can attack with his arms
CD: Soul of the Deacons of the DeepDropped by Deacons of the Deep
CD: Spider Shield - NPC drop on pathDropped by the brigand at the start of Cathedral of the Deep
CD: Spiked Shield - Kirk dropDropped by Longfinger Kirk when he invades in the cathedral central room
CD: Titanite Scale - moat, miniboss dropDropped by the Ravenous Crystal Lizard outside of the Cathedral
CD: Titanite Shard - Cleansing Chapel windowsill, by minibossOn the ledge dropping back down into Cleansing Chapel from the area with the Ravenous Crystal Lizard
CD: Titanite Shard - moat, far endBehind the cathedral near the Infested Corpse moat, going from the Ravenous Crystal Lizard
CD: Titanite Shard - moat, up a slopeUp one of the slopes in the Ravenous Crystal Lizard area
CD: Titanite Shard - outside building by white treePast the Infested Corpse graveyard to the left, hidden along the left wall of the building with the shortcut ladder and Curse Ward Greatshield
CD: Titanite Shard - path, side path by Cathedral of the Deep bonfireUp a path to the left after the Cathedral of the Deep bonfire, after the Fallen Knight and before the Brigand
CD: Twinkling Titanite - moat, lizard #1Dropped by the Crystal Lizard behind the cathedral near the Infested Corpse moat, going from the Ravenous Crystal Lizard
CD: Twinkling Titanite - moat, lizard #2Dropped by the Crystal Lizard under the cathedral near the Infested Corpse moat, going from the Ravenous Crystal Lizard
CD: Twinkling Titanite - path, lizard #1Dropped by the first Crystal Lizard after the Crystal Sage fight
CD: Twinkling Titanite - path, lizard #2Dropped by the second Crystal Lizard after the Crystal Sage fight
CD: Undead Bone Shard - gravestone by white treeIn the graveyard with the Infested Corpses, on a coffin partly hanging off of the ledge
CD: Undead Hunter Charm - lower roofs, up stairs between buttressesIn the area after the cathedral roof guarded by a Cathedral Evangelist. Can be jumped to from a flying buttress or by going around and back
CD: Winged Spear - kill PatchesDropped by Patches when killed in his own armor.
CD: Xanthous Crown - Heysel Corpse-Grub in Rosaria's Bed ChamberDropped by the Heysel Corpse-grub in Rosaria's Bed Chamber
CD: Young White Branch - by white tree #1By the White Birch tree in the Infested Corpse graveyard
CD: Young White Branch - by white tree #2By the White Birch tree in the Infested Corpse graveyard
CKG: Black Firebomb - under rotundaUnder the platform in the middle of the garden, in the toxic pool
CKG: Claw - under rotundaUnder the platform in the middle of the garden, in the toxic pool
CKG: Dark Gem - under lone stairwayFollowing the left wall, behind the standalone set of stairs
CKG: Dragonscale Ring - shortcut, leave halfway down liftFrom the middle level of the second elevator, toward the Oceiros boss fight
CKG: Drakeblood Armor - tomb, after killing AP mausoleum NPCOn the Drakeblood Knight after Oceiros fight, after defeating the Drakeblood Knight from the Serpent-Man Summoner
CKG: Drakeblood Gauntlets - tomb, after killing AP mausoleum NPCOn the Drakeblood Knight after Oceiros fight, after defeating the Drakeblood Knight from the Serpent-Man Summoner
CKG: Drakeblood Helm - tomb, after killing AP mausoleum NPCOn the Drakeblood Knight after Oceiros fight, after defeating the Drakeblood Knight from the Serpent-Man Summoner
CKG: Drakeblood Leggings - tomb, after killing AP mausoleum NPCOn the Drakeblood Knight after Oceiros fight, after defeating the Drakeblood Knight from the Serpent-Man Summoner
CKG: Estus Shard - balconyFrom the middle level of the first Consumed King's Gardens elevator, out the balcony and to the right
CKG: Human Pine Resin - by lone stairway bottomOn the right side of the garden, following the wall past the entrance to the shortcut elevator building, in a toxic pool
CKG: Human Pine Resin - toxic pool, past rotundaIn between two platforms near the middle of the garden, by a tree in a toxic pool
CKG: Magic Stoneplate Ring - mob drop before bossDropped by the Cathedral Knight closest to the Oceiros fog gate
CKG: Ring of Sacrifice - under balconyAlong the right wall of the garden, next to the first elevator building
CKG: Sage Ring+2 - balcony, drop onto rubble, jump backFrom the middle platform of the first elevator in the target, going out and dropping off to the left, and then running off onto the ruined arch behind.
CKG: Shadow Garb - under rotundaUnder the platform in the middle of the garden, in the toxic pool
CKG: Shadow Gauntlets - under rotundaUnder the platform in the middle of the garden, in the toxic pool
CKG: Shadow Leggings - under rotundaUnder the platform in the middle of the garden, in the toxic pool
CKG: Shadow Mask - under center platformUnder the platform in the middle of the garden, in the toxic pool
CKG: Soul of Consumed OceirosDropped by Consumed King Oceiros
CKG: Soul of a Weary Warrior - before first liftOn the path leading to the first elevator from Lothric Castle
CKG: Titanite Chunk - balcony, drop onto rubbleFrom the middle platform of the first elevator, dropping down to the left
CKG: Titanite Chunk - right of shortcut lift bottomOn the right side of the garden, following the wall past the entrance to the shortcut elevator building, all the way to the end
CKG: Titanite Chunk - shortcutRight inside of the shortcut door leading to Oceiros from Lothric/Dancer bonfire
CKG: Titanite Chunk - up lone stairwayFollowing the left wall of the garden, in and up the standalone set of stairs
CKG: Titanite Scale - shortcutIn the room leading to the Oceiros shortcut elevator from Lothric/Dancer, in the first floor alcove.
CKG: Titanite Scale - tomb, chest #1Chest after Oceiros fight
CKG: Titanite Scale - tomb, chest #2Chest after Oceiros fight
CKG: Wood Grain Ring+1 - by first elevator bottomBehind the first elevator going down into the garden, in the toxic pool
DH: Aquamarine Dagger - castle, up stairsUp the second flight of stairs to the left of the starting area with the murkmen, before the long drop
DH: Black Firebomb - ruins, up windmill from bonfireTo the left of the Earthen Peak Ruins bonfire, past the ruined windmill, next to many Poisonhorn bugs.
DH: Covetous Silver Serpent Ring+3 - pantry upstairs, drop downAfter exiting the building with the Lothric Knights where the front crumbles, to the last room of the building to the right, up stairs past an illusory wall to the left, then dropping down after exiting the building from the last room.
DH: Desert Pyromancer Garb - ruins, by shack near cliffBehind a shack near the edge of the cliff of the area targeted by the second angel.
DH: Desert Pyromancer Gloves - swamp, far rightAfter dropping down in the poison swamp area, against the wall straight to the right.
DH: Desert Pyromancer Hood - swamp upper, tunnel endAt the end of the tunnel with Desert Pyromancy Zoey, to the right of the final branches.
DH: Desert Pyromancer Skirt - swamp right, by rootsIn the poison swamp, against a tree guarded by a few Poisonhorn bugs in the front right.
DH: Divine Blessing - library, after dropAfter the dropdown where an angel first targets you, behind you
DH: Divine Blessing - shopSold by Stone-humped Hag, or in her ashes
DH: Divine Blessing - swamp upper, building roofOn a rooftop of one of the buildings bordering the poison swamp. Can be reached by dropping down from the final tree branch and accessing the roof to the right.
DH: Ember - castle, behind spireAt the start of the area, behind a spire to the right of first drop down
DH: Ember - pantry, behind crates just before upstairsAfter exiting the building with the Lothric Knights where the front crumbles, to the last room end of the building to the right, up stairs past an illusory wall to the left, in the second-to-last room of the sequence, behind some crates to the left.
DH: Ember - ruins, alcove before swampIn an alcove providing cover from the second angel's projectiles, before dropping down in the poison swamp area.
DH: Ember - ruins, alcove on cliffIn the area with the pilgrim responsible for the second angel, below the Within Earthen Peak Ruins bonfire. Can be accessed by dropping down from a cliff edge, dropping down to the right of the bonfire.
DH: Ember - shopSold by Stone-humped Hag, or in her ashes
DH: Flame Fan - swamp upper, NPC dropDropped by Desert Pyromancer Zoey
DH: Giant Door Shield - ruins, path below far shackDescending down a path from the edge of the cliff of the area targeted by the second angel, to the very end of the cliff.
DH: Great Soul Dregs - pantry upstairsAfter exiting the building with the Lothric Knights where the front crumbles, to the last room of the building to the right, up stairs past an illusory wall to the left, then all the way to the end of the last room.
DH: Harald Curved Greatsword - swamp left, under rootIn the back leftmost area of the poison swamp, underneath the tree branch leading up and out, guarded by a stationary Harald Legion Knight.
DH: Hidden Blessing - shopSold by Stone-humped Hag, or in her ashes
DH: Homeward Bone - end of path from churchImmediately before dropping into the area with the Earthen Peak Ruins bonfire, next to Gael's flag.
DH: Homeward Bone - swamp left, on rootAll the way to the end of a short path in the back leftmost area of the poison swamp, where you can plunge attack the stationary Harald Legion Knight.
DH: Large Soul of a Weary Warrior - parapets, hallAfter crossing the spire bridge that crashes into the building with the Lothric Knights, past Lapp's initial location, dropping down behind the murkman and dropping down again, in a corner to the left.
DH: Large Soul of a Weary Warrior - swamp centerIn the middle of the poison swamp.
DH: Large Soul of a Weary Warrior - swamp, under overhangIn the cavern adjacent to the poison swamp, surrounded by a few Poisonhorn bugs.
DH: Lightning Urn - wall outside churchAfter the dropdown where an angel first targets you, against the wall on the left.
DH: Loincloth - swamp, left edgeIn the leftmost edge of the poison swamp after dropping down, guarded by 6 Poisonhorn bugs.
DH: Lothric War Banner - parapets, end of hallAfter crossing the spire bridge that crashes into the building with the Lothric Knights, past Lapp's initial location, dropping down behind the murkman and dropping down again, at the end of the hallway to the right.
DH: Murky Hand Scythe - library, behind bookshelvesAfter the first long drop into the building which looks like Grand Archives, to the left up the bookshelf stairs and behind the bookshelves
DH: Murky Longstaff - pantry, last roomAfter exiting the building with the Lothric Knights where the front crumbles, in the third-furthest room in the building to the right.
DH: Prism Stone - swamp upper, tunnel startNear the start of the tunnel with Desert Pyromancer Zoey.
DH: Projected Heal - parapets balconyAfter crossing the spire bridge that crashes into the building with the Lothric Knights, past Lapp's initial location, dropping down behind the murkman, against a wall in the area with the Lothric War Banner Knight and many murkmen.
DH: Purple Moss Clump - swamp shackIn the ruined shack with Poisonhorn bugs straight ahead of the dropdown into the poison swamp area.
DH: Ring of Favor+3 - swamp right, up rootUp the long branch close to the dropdown into the poison swamp area, in front of the cavern.
DH: Ring of Steel Protection+3 - ledge before churchAfter the dropdown where an angel first targets you, on an exposed edge to the left. Difficult to get without killing the angel.
DH: Rusted Coin - behind fountain after churchAfter exiting the building with the Lothric Knights where the front crumbles, behind the fountain on the right side.
DH: Rusted Gold Coin - shopSold by Stone-humped Hag, or in her ashes
DH: Siegbräu - LappGiven by Lapp after collecting the Titanite Slab in Earthen Peak Ruins, or left after Demon Princes fight, or dropped upon death if not given.
DH: Small Envoy Banner - boss dropFound in the small room after beating Demon Prince.
DH: Soul of a Crestfallen Knight - church, altarIn the building where the front crumbles, guarded by the two Lothric Knights at the front of the chapel.
DH: Soul of a Weary Warrior - castle overhangThe bait item at the start of the area which falls down with you into the ruined building below.
DH: Soul of the Demon PrinceDropped by Demon Prince
DH: Splitleaf Greatsword - shopSold by Stone-humped Hag, or in her ashes
DH: Titanite Chunk - castle, up stairsUp first flight of stairs to the left of the starting area with the murkmen, before the long drop
DH: Titanite Chunk - pantry, first roomAfter exiting the building with the Lothric Knights where the front crumbles, on a ledge in the first room of the building to the right.
DH: Titanite Chunk - path from church, by pillarBefore dropping into the area with the Earthen Peak Ruins bonfire, behind a pillar in front of a murkman pool.
DH: Titanite Chunk - ruins, by far shackIn front of a shack at the far edge of the cliff of the area targeted by the second angel. There is a shortcut dropdown to the left of the building.
DH: Titanite Chunk - ruins, path from bonfireAt the Earthen Peak Ruins bonfire, straight a bit then all the way left, near the edge of the cliff in the area targeted by the second angel.
DH: Titanite Chunk - swamp right, drop partway up rootPartway up the long branch close to the dropdown into the poison swamp area, in front of the cavern, dropping down to a branch to the left.
DH: Titanite Chunk - swamp, along buildingsAfter dropping down into the poison swamp, along the buildings on the left side.
DH: Titanite Chunk - swamp, path to upperPartway up the branch that leads out of the poison swamp, on a very exposed branch jutting out to the left.
DH: Titanite Scale - library, back of roomAfter the first long drop into the building which looks like Grand Archives, behind you at the back of the room
DH: Titanite Scale - swamp upper, drop and jump into towerAt the very end of the last tree branch before dropping down toward the Within Earthen Peak Ruins bonfire, drop down to the left instead. Make a jump into the interior of the overturned tower to the left.
DH: Titanite Slab - swamp, path under overhangDeep within the cavern adjacent to the poison swamp, to the back and then left. Alternatively, given by Lapp after exhausting dialogue near the bonfire and dying, or left after he moves on, or dropped upon death if not given.
DH: Twinkling Titanite - library, chandelierAfter the first long drop into the building which looks like Grand Archives, straight ahead hanging from a chandelier on the ground
DH: Twinkling Titanite - path after church, mob dropDropped the pilgrim responsible for the first angel encountered, below the spire bridge that forms by crashing into the building.
DH: Twinkling Titanite - ruins, alcove on cliff, mob dropDropped by the pilgrim responsible for the second angel, below the Within Earthen Peak Ruins bonfire. Can be accessed by dropping down from a cliff edge, or dropping down to the right of the bonfire.
DH: Twinkling Titanite - ruins, root near bonfireTreasure visible straight ahead of the Earthen Peak Ruins bonfire on a branch. Can be accessed by following the right wall from the bonfire until a point of access onto the branch is found.
DH: Twinkling Titanite - swamp upper, drop onto rootOn the final tree branches before dropping down toward the Within Earthen Peak Ruins bonfire, drop down on a smaller branch to the right. This loops back to the original branch.
DH: Twinkling Titanite - swamp upper, mob drop on roofDropped by the pilgrim responsible for the third angel in the swamp. Rather than heading left into the tunnel with Desert Pyromancy Zoey, go right onto a shack roof. Drop down onto a tree branch at the end, then drop down to another roof.
FK: Antiquated Dress - hidden caveIn a chest in the cave found along the keep wall in the basilisk area, with the Elizabeth corpse
FK: Antiquated Gloves - hidden caveIn a chest in the cave found along the keep wall in the basilisk area, with the Elizabeth corpse
FK: Antiquated Skirt - hidden caveIn a chest in the cave found along the keep wall in the basilisk area, with the Elizabeth corpse
FK: Atonement - perimeter, drop down into swampDropping down from the Farron Keep Perimeter building, to the right past the bonfire, before the stairs going up
FK: Black Bow of Pharis - miniboss drop, by keep ruins near wallDropped the Elder Ghru on the left side of the group of three to the left of the Keep Ruins bonfire, as approached from the ritual fire.
FK: Black Bug Pellet - perimeter, hill by boss doorOn the small hill to the right of the Abyss Watchers entrance, guarded by a spear-wielding Ghru Grunt
FK: Cinders of a Lord - Abyss WatcherDropped by Abyss Watchers
FK: Crown of Dusk - by white treeNear the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Dark Stoneplate Ring+2 - keep ruins ritual island, behind wallHidden behind the right wall of the ritual fire before Keep Ruins
FK: Dragon Crest Shield - upper keep, far side of the wallUp the elevator from Old Wolf of Farron bonfire, and dropping down to Crystal Lizard area, in the open.
FK: Dreamchaser's Ashes - keep proper, illusory wallNear the Old Wolf of Farron bonfire, behind an illusory wall near the Crystal Lizard
FK: Ember - by white treeNear the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Ember - perimeter, path to bossGuarded by a spear-wielding Ghru Grunt to the right of the main path leading up to Abyss Watchers
FK: Ember - upper keep, by miniboss #1Guarded by Stray Demon, up from the Old Wolf of Farron bonfire
FK: Ember - upper keep, by miniboss #2Guarded by Stray Demon, up from the Old Wolf of Farron bonfire
FK: Estus Shard - between Farron Keep bonfire and left islandStraight ahead from the Farron Keep bonfire to the ritual fire stairs, guarded by a slug
FK: Gold Pine Bundle - by white treeNear the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Golden Scroll - hidden caveIn a cave found along the keep wall in the basilisk area, with the Elizabeth corpse
FK: Great Magic Weapon - perimeter, by door to Road of SacrificesNext to the shortcut leading from Farron Keep Perimeter back into Crucifixion Woods, past the Ravenous Crystal Lizard
FK: Greataxe - upper keep, by minibossGuarded by Stray Demon, up from the Old Wolf of Farron bonfire
FK: Greatsword - ramp by keep ruins ritual islandIn the middle of the swamp, on the pair of long ramps furthest from the Farron Keep bonfire, going out forward and slightly right from the bonfire.
FK: Havel's Armor - upper keep, after killing AP belfry roof NPCAppears by Stray Demon, up from the Old Wolf of Farron bonfire, after the Havel Knight guarding the Titanite Slab in Archdragon Peak has been killed
FK: Havel's Gauntlets - upper keep, after killing AP belfry roof NPCAppears by Stray Demon, up from the Old Wolf of Farron bonfire, after the Havel Knight guarding the Titanite Slab in Archdragon Peak has been killed
FK: Havel's Helm - upper keep, after killing AP belfry roof NPCAppears by Stray Demon, up from the Old Wolf of Farron bonfire, after the Havel Knight guarding the Titanite Slab in Archdragon Peak has been killed
FK: Havel's Leggings - upper keep, after killing AP belfry roof NPCAppears by Stray Demon, up from the Old Wolf of Farron bonfire, after the Havel Knight guarding the Titanite Slab in Archdragon Peak has been killed
FK: Heavy Gem - upper keep, lizard on stairsDropped by the Crystal Lizard that scurries up the stairs in the area dropping down from near Stray Demon, up from Old Wolf of Farron bonfire
FK: Hollow Gem - perimeter, drop down into swampDropping down from the Farron Keep Perimeter building, to the right past the bonfire, before the stairs going up
FK: Homeward Bone - right island, behind fireBehind the ritual fire with stairs guarded by Elder Ghrus/basilisks
FK: Iron Flesh - Farron Keep bonfire, right after exitIn the open in the swamp, heading straight right from Farron Keep bonfire
FK: Large Soul of a Nameless Soldier - corner of keep and right islandHidden in a corner to the right of the stairs leading up to the ritual fire from the basilisk area
FK: Large Soul of a Nameless Soldier - near wall by right islandTo the left of the stairs leading up to the ritual fire from the Basilisk area, by the keep wall
FK: Large Soul of an Unknown Traveler - by white treeOn a tree close to the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Large Titanite Shard - upper keep, lizard by wyvernDropped by the farther Crystal Lizard in the area dropping down from near Stray Demon, up from Old Wolf of Farron bonfire
FK: Large Titanite Shard - upper keep, lizard in openDropped by the closer Crystal Lizard in the area dropping down from near Stray Demon, up from Old Wolf of Farron bonfire
FK: Lightning Spear - upper keep, far side of the wallUp the elevator from Old Wolf of Farron bonfire, and dropping down to Crystal Lizard area, in the open.
FK: Lingering Dragoncrest Ring - by white tree, miniboss dropDropped by the Greater Crab patrolling the birch tree where the Giant shoots arrows
FK: Magic Stoneplate Ring+1 - between right island and wallBehind a tree in the basilisk area, heading directly right from Farron Keep bonfire
FK: Manikin Claws - Londor Pale Shade dropDropped by Londor Pale Shade when he invades near the basilisks, if Yoel or Yuria have been betrayed
FK: Nameless Knight Armor - corner of keep and right islandFrom the Keep Ruins bonfire to the ritual fire stairs patrolled by Elder Ghrus, along the edge of the ritual fire hill
FK: Nameless Knight Gauntlets - corner of keep and right islandFrom the Keep Ruins bonfire to the ritual fire stairs patrolled by Elder Ghrus, along the edge of the ritual fire hill
FK: Nameless Knight Helm - corner of keep and right islandFrom the Keep Ruins bonfire to the ritual fire stairs patrolled by Elder Ghrus, along the edge of the ritual fire hill
FK: Nameless Knight Leggings - corner of keep and right islandFrom the Keep Ruins bonfire to the ritual fire stairs patrolled by Elder Ghrus, along the edge of the ritual fire hill
FK: Pharis's Hat - miniboss drop, by keep ruins near wallDropped the Elder Ghru in the back of the group of three to the left of the Keep Ruins bonfire, as approached from the ritual fire.
FK: Poison Gem - near wall by keep ruins bridgeFrom the left of the bridge leading from the ritual fire to the Keep Ruins bonfire, guarded by the three Elder Ghru
FK: Prism Stone - by left island stairsOn an island to the left of the stairs leading up to the ritual fire straight ahead of the Farron Keep bonfire
FK: Purple Moss Clump - Farron Keep bonfire, around right cornerAlong the inner wall of the keep, making an immediate right from Farron Keep bonfire
FK: Purple Moss Clump - keep ruins, ritual islandClose to the ritual fire before the Keep Ruins bonfire
FK: Purple Moss Clump - ramp directly in front of Farron Keep bonfireIn the middle of the swamp, on the pair of long ramps closest to the Farron Keep bonfire, going out forward and slightly right from the bonfire.
FK: Ragged Mask - Farron Keep bonfire, around left cornerAlong the inner wall of the keep, making an immediate left from Farron Keep bonfire, guarded by slugs
FK: Repair Powder - outside hidden caveAlong the keep wall in the basilisk area, outside of the cave with the Elizabeth corpse and Golden Scroll
FK: Rotten Pine Resin - left island, behind fireIn the area behind the ritual fire which is straight ahead of the Farron Keep bonfire
FK: Rotten Pine Resin - outside pavilion by left islandFrom the Farron Keep bonfire straight ahead to the pavilion guarded by the Darkwraith, just to the left of the ritual fire stairs
FK: Rusted Gold Coin - right island, behind wallHidden behind the right wall of the ritual fire with stairs guarded by Elder Ghrus/basilisks
FK: Sage's Coal - pavilion by left islandIn the pavilion guarded by a Darkwraith, straight ahead from the Farron Keep bonfire to the left of the ritual fire stairs
FK: Sage's Scroll - near wall by keep ruins bonfire islandAlong the keep inner wall, heading left from the stone doors past the crab area, surrounded by many Ghru enemies
FK: Shriving Stone - perimeter, just past stone doorsPast the stone doors, on the path leading up to Abyss Watchers by the Corvians
FK: Soul of a Nameless Soldier - by white treeNear the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Soul of a Stray Demon - upper keep, miniboss dropDropped by Stray Demon on the bridge above Farron Keep
FK: Soul of the Blood of the WolfDropped by Abyss Watchers
FK: Stone Parma - near wall by left islandAlong the inner wall of the keep, making a left from Farron Keep bonfire but before the area with the Darkwraith, guarded by a slug
FK: Sunlight Talisman - estus soup island, by ladder to keep properBy the pot of estus soup to the left of the stairs leading up to Old Wolf of Farron
FK: Titanite Scale - perimeter, miniboss dropDropped by Ravenous Crystal Lizard near the shortcut from Farron Keep back to Road of Sacrifices
FK: Titanite Shard - Farron Keep bonfire, left after exitAlong the inner wall of the keep, making a left from Farron Keep bonfire, by the second group of four slugs
FK: Titanite Shard - between left island and keep ruinsIn the swamp area with the Ghru Leaper between the Keep Ruins ritual fire and ritual fire straight ahead of Farron Keep bonfire, opposite from the keep wall
FK: Titanite Shard - by keep ruins ritual island stairsBy the stairs leading up to the Keep Ruins ritual fire from the middle of the swamp
FK: Titanite Shard - by ladder to keep properIn the swamp area close to the foot of the ladder leading to Old Wolf of Farron bonfire
FK: Titanite Shard - by left island stairsIn front of the stairs leading up to the ritual fire straight ahead of the Farron Keep bonfire
FK: Titanite Shard - keep ruins bonfire island, under rampUnder the ramp leading down from the Keep Ruins bonfire
FK: Titanite Shard - swamp by right islandBehind a tree patrolled by an Elder Ghru close to the ritual fire stairs
FK: Twinkling Dragon Head Stone - Hawkwood dropDropped by Hawkwood after killing him in the Abyss Watchers arena, after running up to the altar in Archdragon Peak. Twinkling Dragon Torso Stone needs to be acquired first.
FK: Twinkling Titanite - keep proper, lizardDropped by the Crystal Lizard on the balcony behind the Old Wolf of Farron bonfire
FK: Undead Bone Shard - pavilion by keep ruins bonfire islandIn a standalone pavilion down the ramp from Keep Ruins bonfire and to the right
FK: Watchdogs of Farron - Old WolfGiven by Old Wolf of Farron.
FK: Wolf Ring+1 - keep ruins bonfire island, outside buildingTo the right of the building with the Keep Ruins bonfire, when approached from the ritual fire
FK: Wolf's Blood Swordgrass - by ladder to keep properTo the left of the ladder leading up to the Old Wolf of Farron bonfire
FK: Young White Branch - by white tree #1Near the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Young White Branch - by white tree #2Near the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FS: Acid Surge - Cornyx for Carthus TomeSold by Cornyx after giving him the Carthus Pyromancy Tome
FS: Affinity - KarlaSold by Karla after recruiting her, or in her ashes
FS: Alluring Skull - Mortician's AshesSold by Handmaid after giving Mortician's Ashes
FS: Arstor's Spear - Ludleth for GreatwoodBoss weapon for Curse-Rotted Greatwood
FS: Aural Decoy - OrbeckSold by Orbeck
FS: Billed Mask - Yuria after killing KFF bossDropped by Yuria upon death or quest completion.
FS: Black Dress - Yuria after killing KFF bossDropped by Yuria upon death or quest completion.
FS: Black Fire Orb - Karla for Grave Warden TomeSold by Karla after giving her the Grave Warden Pyromancy Tome
FS: Black Flame - Karla for Grave Warden TomeSold by Karla after giving her the Grave Warden Pyromancy Tome
FS: Black Gauntlets - Yuria after killing KFF bossDropped by Yuria upon death or quest completion.
FS: Black Iron Armor - shop after killing TsorigSold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake
FS: Black Iron Gauntlets - shop after killing TsorigSold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake
FS: Black Iron Helm - shop after killing TsorigSold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake
FS: Black Iron Leggings - shop after killing TsorigSold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake
FS: Black Leggings - Yuria after killing KFF bossDropped by Yuria upon death or quest completion.
FS: Black Serpent - Ludleth for WolnirBoss weapon for High Lord Wolnir
FS: Blessed Weapon - Irina for Tome of LothricSold by Irina after giving her the Braille Divine Tome of Lothric
FS: Blue Tearstone Ring - GreiratGiven by Greirat upon rescuing him from the High Wall cell
FS: Boulder Heave - Ludleth for Stray DemonBoss weapon for Stray Demon
FS: Bountiful Light - Irina for Tome of LothricSold by Irina after giving her the Braille Divine Tome of Lothric
FS: Bountiful Sunlight - Ludleth for RosariaBoss weapon for Rosaria, available after Leonhard is killed
FS: Broken Straight Sword - gravestone after bossNear the grave after Iudex Gundyr fight
FS: Budding Green Blossom - shop after killing Creighton and AL bossSold by Handmaid after receiving Silvercat Ring item lot from Sirris and defeating Aldrich
FS: Bursting Fireball - Cornyx for Great Swamp TomeSold by Cornyx after giving him the Great Swamp Pyromancy Tome
FS: Caressing Tears - IrinaSold by Irina after recruiting her, or in her ashes
FS: Carthus Beacon - Cornyx for Carthus TomeSold by Cornyx after giving him the Carthus Pyromancy Tome
FS: Carthus Flame Arc - Cornyx for Carthus TomeSold by Cornyx after giving him the Carthus Pyromancy Tome
FS: Cast Light - Orbeck for Golden ScrollSold by Orbeck after giving him the Golden Scroll
FS: Chaos Bed Vestiges - Ludleth for Old Demon KingBoss weapon for Old Demon King
FS: Chaos Storm - Cornyx for Izalith TomeSold by Cornyx after giving him Izalith Pyromancy Tome
FS: Clandestine Coat - shop with Orbeck's AshesSold by Handmaid after giving Orbeck's Ashes and reloading
FS: Cleric's Candlestick - Ludleth for DeaconsBoss weapon for Deacons of the Deep
FS: Cracked Red Eye Orb - LeonhardGiven by Ringfinger Leonhard in Firelink Shrine after reaching Tower on the Wall bonfire
FS: Crystal Hail - Ludleth for SageBoss weapon for Crystal Sage
FS: Crystal Magic Weapon - Orbeck for Crystal ScrollSold by Orbeck after giving him the Crystal Scroll
FS: Crystal Sage's Rapier - Ludleth for SageBoss weapon for Crystal Sage
FS: Crystal Soul Spear - Orbeck for Crystal ScrollSold by Orbeck after giving him the Crystal Scroll
FS: Dancer's Armor - shop after killing LC entry bossSold by Handmaid after defeating Dancer of the Boreal Valley
FS: Dancer's Crown - shop after killing LC entry bossSold by Handmaid after defeating Dancer of the Boreal Valley
FS: Dancer's Enchanted Swords - Ludleth for DancerBoss weapon for Dancer of the Boreal Valley
FS: Dancer's Gauntlets - shop after killing LC entry bossSold by Handmaid after defeating Dancer of the Boreal Valley
FS: Dancer's Leggings - shop after killing LC entry bossSold by Handmaid after defeating Dancer of the Boreal Valley
FS: Dark Blade - Karla for Londor TomeSold by Irina or Karla after giving one the Londor Braille Divine Tome
FS: Dark Edge - KarlaSold by Karla after recruiting her, or in her ashes
FS: Dark Hand - Yoel/YuriaSold by Yuria
FS: Darkdrift - Yoel/YuriaDropped by Yuria upon death or quest completion.
FS: Darkmoon Longbow - Ludleth for AldrichBoss weapon for Aldrich
FS: Dead Again - Karla for Londor TomeSold by Irina or Karla after giving one the Londor Braille Divine Tome
FS: Deep Protection - Karla for Deep Braille TomeSold by Irina or Karla after giving one the Deep Braille Divine Tome
FS: Deep Soul - Ludleth for DeaconsBoss weapon for Deacons of the Deep
FS: Demon's Fist - Ludleth for Fire DemonBoss weapon for Fire Demon
FS: Demon's Greataxe - Ludleth for Fire DemonBoss weapon for Fire Demon
FS: Demon's Scar - Ludleth for Demon PrinceBoss weapon for Demon Prince
FS: Divine Blessing - Greirat from IBVSold by Greirat after pillaging Irithyll
FS: Divine Blessing - Greirat from USSold by Greirat after pillaging Undead Settlement
FS: Dragonscale Armor - shop after killing AP bossSold by Handmaid after defeating Nameless King
FS: Dragonscale Waistcloth - shop after killing AP bossSold by Handmaid after defeating Nameless King
FS: Dragonslayer Greataxe - Ludleth for DragonslayerBoss weapon for Dragonslayer Armour
FS: Dragonslayer Greatshield - Ludleth for DragonslayerBoss weapon for Dragonslayer Armour
FS: Dragonslayer Swordspear - Ludleth for NamelessBoss weapon for Nameless King
FS: Dried Finger - shopSold by both Shrine Handmaid and Untended Graves Handmaid
FS: East-West Shield - tree by shrine entranceIn a tree to the left of the Firelink Shrine entrance
FS: Eastern Armor - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: Eastern Gauntlets - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: Eastern Helm - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: Eastern Leggings - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: Elite Knight Armor - shop after Anri questSold by Handmaid after completing Anri's questline or killing Anri
FS: Elite Knight Gauntlets - shop after Anri questSold by Handmaid after completing Anri's questline or killing Anri
FS: Elite Knight Helm - shop after Anri questSold by Handmaid after completing Anri's questline or killing Anri
FS: Elite Knight Leggings - shop after Anri questSold by Handmaid after completing Anri's questline or killing Anri
FS: Ember - Dragon Chaser's AshesSold by Handmaid after giving Dragon Chaser's Ashes
FS: Ember - Grave Warden's AshesSold by Handmaid after giving Grave Warden's Ashes
FS: Ember - GreiratSold by Greirat after recruiting him, or in his ashes
FS: Ember - Greirat from USSold by Greirat after pillaging Undead Settlement
FS: Ember - Mortician's AshesSold by Handmaid after giving Mortician's Ashes
FS: Ember - above shrine entranceAbove the Firelink Shrine entrance, up the stairs/slope from either left or right of the entrance
FS: Ember - path right of Firelink entranceOn a cliffside to the right of the main path leading up to Firelink Shrine, guarded by a dog
FS: Ember - shopSold by Handmaid
FS: Ember - shop for Greirat's AshesSold by Handmaid after Greirat pillages Lothric Castle and handing in ashes
FS: Embraced Armor of Favor - shop after killing water reserve minibossesSold by Handmaid after killing Sulyvahn's Beasts in Water Reserve
FS: Executioner Armor - shop after killing HoraceSold by Handmaid after killing Horace the Hushed
FS: Executioner Gauntlets - shop after killing HoraceSold by Handmaid after killing Horace the Hushed
FS: Executioner Helm - shop after killing HoraceSold by Handmaid after killing Horace the Hushed
FS: Executioner Leggings - shop after killing HoraceSold by Handmaid after killing Horace the Hushed
FS: Exile Armor - shop after killing NPCs in RSSold by Handmaid after killing the exiles just before Farron Keep
FS: Exile Gauntlets - shop after killing NPCs in RSSold by Handmaid after killing the exiles just before Farron Keep
FS: Exile Leggings - shop after killing NPCs in RSSold by Handmaid after killing the exiles just before Farron Keep
FS: Exile Mask - shop after killing NPCs in RSSold by Handmaid after killing the exiles just before Farron Keep
FS: Faraam Helm - shop after killing GA NPCSold by Handmaid after killing Lion Knight Albert
FS: Farron Dart - OrbeckSold by Orbeck
FS: Farron Dart - shopSold by Handmaid
FS: Farron Flashsword - OrbeckSold by Orbeck
FS: Farron Greatsword - Ludleth for Abyss WatchersBoss weapon for Abyss Watchers
FS: Farron Hail - Orbeck for Sage's ScrollSold by Orbeck after giving him the Sage's Scroll
FS: Farron Ring - HawkwoodGiven by Hawkwood, or dropped upon death, after defeating Abyss Watchers.
FS: Fire Orb - Cornyx for Great Swamp TomeSold by Cornyx after giving him the Great Swamp Pyromancy Tome
FS: Fire Surge - CornyxSold by Cornyx after recruiting him, or in his ashes
FS: Fire Whip - Karla for Quelana TomeSold by Karla after giving her the Quelana Pyromancy Tome
FS: Fireball - CornyxSold by Cornyx after recruiting him, or in his ashes
FS: Firelink Armor - shop after placing all CindersSold by Handmaid after defeating Soul of Cinder
FS: Firelink Gauntlets - shop after placing all CindersSold by Handmaid after defeating Soul of Cinder
FS: Firelink Greatsword - Ludleth for CinderBoss weapon for Soul of Cinder
FS: Firelink Helm - shop after placing all CindersSold by Handmaid after defeating Soul of Cinder
FS: Firelink Leggings - shop after placing all CindersSold by Handmaid after defeating Soul of Cinder
FS: Firestorm - Karla for Quelana TomeSold by Karla after giving her the Quelana Pyromancy Tome
FS: Flash Sweat - CornyxSold by Cornyx after recruiting him, or in his ashes
FS: Force - Irina for Tome of CarimSold by Irina after giving her the Braille Divine Tome of Carim
FS: Frayed Blade - Ludleth for MidirBoss weapon for Darkeater Midir
FS: Friede's Great Scythe - Ludleth for FriedeBoss weapon for Sister Friede
FS: Gael's Greatsword - Ludleth for GaelBoss weapon for Slave Knight Gael
FS: Gauntlets of Favor - shop after killing water reserve minibossesSold by Handmaid after killing Sulyvahn's Beasts in Water Reserve
FS: Gnaw - Karla for Deep Braille TomeSold by Irina or Karla after giving one the Deep Braille Divine Tome
FS: Golden Bracelets - shop after killing AP bossSold by Handmaid after defeating Nameless King
FS: Golden Crown - shop after killing AP bossSold by Handmaid after defeating Nameless King
FS: Grave Key - Mortician's AshesSold by Handmaid after giving Mortician's Ashes
FS: Great Chaos Fire Orb - Cornyx for Izalith TomeSold by Cornyx after giving him Izalith Pyromancy Tome
FS: Great Combustion - CornyxSold by Cornyx after recruiting him, or in his ashes
FS: Great Farron Dart - Orbeck for Sage's ScrollSold by Orbeck after giving him the Sage's Scroll
FS: Great Heavy Soul Arrow - OrbeckSold by Orbeck
FS: Great Soul Arrow - OrbeckSold by Orbeck
FS: Greatsword of Judgment - Ludleth for PontiffBoss weapon for Pontiff Sulyvahn
FS: Gundyr's Armor - shop after killing UG bossSold by Handmaid after defeating Champion Gundyr
FS: Gundyr's Gauntlets - shop after killing UG bossSold by Handmaid after defeating Champion Gundyr
FS: Gundyr's Halberd - Ludleth for ChampionBoss weapon for Champion Gundyr
FS: Gundyr's Helm - shop after killing UG bossSold by Handmaid after defeating Champion Gundyr
FS: Gundyr's Leggings - shop after killing UG bossSold by Handmaid after defeating Champion Gundyr
FS: Havel's Ring - Ludleth for Stray DemonBoss weapon for Stray Demon
FS: Hawkwood's Shield - gravestone after Hawkwood leavesLeft by Hawkwood after defeating Abyss Watchers, Curse-Rotted Greatwood, Deacons of the Deep, and Crystal Sage
FS: Hawkwood's Swordgrass - Andre after gesture in AP summitGiven by Andre after praying at the Dragon Altar in Archdragon Peak, after acquiring Twinkling Dragon Torso Stone.
FS: Heal - IrinaSold by Irina after recruiting her, or in her ashes
FS: Heal Aid - shopSold by Handmaid
FS: Heavy Soul Arrow - OrbeckSold by Orbeck
FS: Heavy Soul Arrow - Yoel/YuriaSold by Yoel/Yuria
FS: Helm of Favor - shop after killing water reserve minibossesSold by Handmaid after killing Sulyvahn's Beasts in Water Reserve
FS: Hidden Blessing - Dreamchaser's AshesSold by Greirat after pillaging Irithyll
FS: Hidden Blessing - Greirat from IBVSold by Greirat after pillaging Irithyll
FS: Hidden Blessing - Patches after searching GASold by Handmaid after giving Dreamchaser's Ashes, saying where they were found
FS: Hidden Body - Orbeck for Golden ScrollSold by Orbeck after giving him the Golden Scroll
FS: Hidden Weapon - Orbeck for Golden ScrollSold by Orbeck after giving him the Golden Scroll
FS: Hollowslayer Greatsword - Ludleth for GreatwoodBoss weapon for Curse-Rotted Greatwood
FS: Homeward - IrinaSold by Irina after recruiting her, or in her ashes
FS: Homeward Bone - cliff edge after bossAlong the cliff edge straight ahead of the Iudex Gundyr fight
FS: Homeward Bone - path above shrine entranceTo the right of the Firelink Shrine entrance, up a slope and before the ledge on top of a coffin
FS: Homing Crystal Soulmass - Orbeck for Crystal ScrollSold by Orbeck after giving him the Crystal Scroll
FS: Homing Soulmass - Orbeck for Logan's ScrollSold by Orbeck after giving him Logan's Scroll
FS: Karla's Coat - Prisoner Chief's AshesSold by Handmaid after giving Prisoner Chief's Ashes
FS: Karla's Coat - kill KarlaDropped from Karla upon death
FS: Karla's Gloves - Prisoner Chief's AshesSold by Handmaid after giving Prisoner Chief's Ashes
FS: Karla's Gloves - kill KarlaDropped from Karla upon death
FS: Karla's Pointed Hat - Prisoner Chief's AshesSold by Handmaid after giving Prisoner Chief's Ashes
FS: Karla's Pointed Hat - kill KarlaDropped from Karla upon death
FS: Karla's Trousers - Prisoner Chief's AshesSold by Handmaid after giving Prisoner Chief's Ashes
FS: Karla's Trousers - kill KarlaDropped from Karla upon death
FS: Leggings of Favor - shop after killing water reserve minibossesSold by Handmaid after killing Sulyvahn's Beasts in Water Reserve
FS: Leonhard's Garb - shop after killing LeonhardSold by Handmaid after killing Leonhard
FS: Leonhard's Gauntlets - shop after killing LeonhardSold by Handmaid after killing Leonhard
FS: Leonhard's Trousers - shop after killing LeonhardSold by Handmaid after killing Leonhard
FS: Life Ring - Dreamchaser's AshesSold by Handmaid after giving Dreamchaser's Ashes
FS: Lifehunt Scythe - Ludleth for AldrichBoss weapon for Aldrich
FS: Lift Chamber Key - LeonhardGiven by Ringfinger Leonhard after acquiring a Pale Tongue.
FS: Lightning Storm - Ludleth for NamelessBoss weapon for Nameless King
FS: Lloyd's Shield Ring - Paladin's AshesSold by Handmaid after giving Paladin's Ashes
FS: Londor Braille Divine Tome - Yoel/YuriaSold by Yuria
FS: Lorian's Armor - shop after killing GA bossSold by Handmaid after defeating Lothric, Younger Prince
FS: Lorian's Gauntlets - shop after killing GA bossSold by Handmaid after defeating Lothric, Younger Prince
FS: Lorian's Greatsword - Ludleth for PrincesBoss weapon for Twin Princes
FS: Lorian's Helm - shop after killing GA bossSold by Handmaid after defeating Lothric, Younger Prince
FS: Lorian's Leggings - shop after killing GA bossSold by Handmaid after defeating Lothric, Younger Prince
FS: Lothric's Holy Sword - Ludleth for PrincesBoss weapon for Twin Princes
FS: Magic Barrier - Irina for Tome of LothricSold by Irina after giving her the Braille Divine Tome of Lothric
FS: Magic Shield - OrbeckSold by Orbeck
FS: Magic Shield - Yoel/YuriaSold by Yoel/Yuria
FS: Magic Weapon - OrbeckSold by Orbeck
FS: Magic Weapon - Yoel/YuriaSold by Yoel/Yuria
FS: Mail Breaker - Sirris for killing CreightonGiven by Sirris talking to her in Firelink Shrine after invading and vanquishing Creighton.
FS: Master's Attire - NPC dropDropped by Sword Master
FS: Master's Gloves - NPC dropDropped by Sword Master
FS: Med Heal - Irina for Tome of CarimSold by Irina after giving her the Braille Divine Tome of Carim
FS: Millwood Knight Armor - Captain's AshesSold by Handmaid after giving Captain's Ashes
FS: Millwood Knight Gauntlets - Captain's AshesSold by Handmaid after giving Captain's Ashes
FS: Millwood Knight Helm - Captain's AshesSold by Handmaid after giving Captain's Ashes
FS: Millwood Knight Leggings - Captain's AshesSold by Handmaid after giving Captain's Ashes
FS: Moaning Shield - EygonDropped by Eygon of Carim
FS: Moonlight Greatsword - Ludleth for OceirosBoss weapon for Oceiros, the Consumed King
FS: Morion Blade - Yuria for Orbeck's AshesGiven by Yuria after giving Orbeck's Ashes after she asks you to assassinate him, after he moves to Firelink Shrine. Can be done without killing Orbeck, by completing his questline.
FS: Morne's Armor - shop after killing Eygon or LC bossSold by Handmaid after killing Eygon of Carim or defeating Dragonslayer Armour
FS: Morne's Gauntlets - shop after killing Eygon or LC bossSold by Handmaid after killing Eygon of Carim or defeating Dragonslayer Armour
FS: Morne's Great Hammer - EygonDropped by Eygon of Carim
FS: Morne's Helm - shop after killing Eygon or LC bossSold by Handmaid after killing Eygon of Carim or defeating Dragonslayer Armour
FS: Morne's Leggings - shop after killing Eygon or LC bossSold by Handmaid after killing Eygon of Carim or defeating Dragonslayer Armour
FS: Old King's Great Hammer - Ludleth for Old Demon KingBoss weapon for Old Demon King
FS: Old Moonlight - Ludleth for MidirBoss weapon for Darkeater Midir
FS: Ordained Dress - shop after killing PW2 bossSold by Handmaid after defeating Sister Friede
FS: Ordained Hood - shop after killing PW2 bossSold by Handmaid after defeating Sister Friede
FS: Ordained Trousers - shop after killing PW2 bossSold by Handmaid after defeating Sister Friede
FS: Pale Shade Gloves - Yoel's room, kill Londor Pale Shade twiceIn Yoel/Yuria's area after defeating both Londor Pale Shade invasions
FS: Pale Shade Robe - Yoel's room, kill Londor Pale Shade twiceIn Yoel/Yuria's area after defeating both Londor Pale Shade invasions
FS: Pale Shade Trousers - Yoel's room, kill Londor Pale Shade twiceIn Yoel/Yuria's area after defeating both Londor Pale Shade invasions
FS: Pestilent Mist - Orbeck for any scrollSold by Orbeck after giving him any scroll
FS: Poison Mist - Cornyx for Great Swamp TomeSold by Cornyx after giving him the Great Swamp Pyromancy Tome
FS: Pontiff's Left Eye - Ludleth for VordtBoss weapon for Vordt of the Boreal Valley
FS: Prisoner's Chain - Ludleth for ChampionBoss weapon for Champion Gundyr
FS: Profaned Greatsword - Ludleth for PontiffBoss weapon for Pontiff Sulyvahn
FS: Profuse Sweat - Cornyx for Great Swamp TomeSold by Cornyx after giving him the Great Swamp Pyromancy Tome
FS: Rapport - Karla for Quelana TomeSold by Karla after giving her the Quelana Pyromancy Tome
FS: Refined Gem - Captain's AshesSold by Handmaid after giving Captain's Ashes
FS: Repair - Orbeck for Golden ScrollSold by Orbeck after giving him the Golden Scroll
FS: Repeating Crossbow - Ludleth for GaelBoss weapon for Slave Knight Gael
FS: Replenishment - IrinaSold by Irina after recruiting her, or in her ashes
FS: Ring of Sacrifice - Yuria shopSold by Yuria, or by Handmaid after giving Hollow's Ashes
FS: Rose of Ariandel - Ludleth for FriedeBoss weapon for Sister Friede
FS: Rusted Gold Coin - don't forgive PatchesGiven by Patches after not forgiving him after he locks you in the Bell Tower.
FS: Sage's Big Hat - shop after killing RS bossSold by Handmaid after defeating Crystal Sage
FS: Saint's Ring - IrinaSold by Irina after recruiting her, or in her ashes
FS: Seething Chaos - Ludleth for Demon PrinceBoss weapon for Demon Prince
FS: Silvercat Ring - Sirris for killing CreightonGiven by Sirris talking to her in Firelink Shrine after invading and vanquishing Creighton.
FS: Skull Ring - kill LudlethDropped by Ludleth upon death, including after placing all cinders. Note that if killed before giving Transposing Kiln, transposition is not possible.
FS: Slumbering Dragoncrest Ring - Orbeck for buying four specific spellsGiven by Orbeck after purchasing the shop items corresponding to Aural Decoy, Farron Flashsword, Spook (starting items), and Pestilent Mist (after giving one scroll).
FS: Smough's Armor - shop after killing AL bossSold by Handmaid after defeating Alrich, Devourer of Gods
FS: Smough's Gauntlets - shop after killing AL bossSold by Handmaid after defeating Alrich, Devourer of Gods
FS: Smough's Helm - shop after killing AL bossSold by Handmaid after defeating Alrich, Devourer of Gods
FS: Smough's Leggings - shop after killing AL bossSold by Handmaid after defeating Alrich, Devourer of Gods
FS: Sneering Mask - Yoel's room, kill Londor Pale Shade twiceIn Yoel/Yuria's area after defeating both Londor Pale Shade invasions
FS: Soothing Sunlight - Ludleth for DancerBoss weapon for Dancer of the Boreal Valley
FS: Soul Arrow - OrbeckSold by Orbeck
FS: Soul Arrow - Yoel/YuriaSold by Yoel/Yuria
FS: Soul Arrow - shopSold by Handmaid
FS: Soul Greatsword - OrbeckSold by Orbeck
FS: Soul Greatsword - Yoel/YuriaSold by Yoel/Yuria after using Draw Out True Strength
FS: Soul Spear - Orbeck for Logan's ScrollSold by Orbeck after giving him Logan's Scroll
FS: Soul of a Deserted Corpse - bell tower doorNext to the door requiring the Tower Key
FS: Spook - OrbeckSold by Orbeck
FS: Storm Curved Sword - Ludleth for NamelessBoss weapon for Nameless King
FS: Sunless Armor - shop, Sirris quest, kill GA bossSold by Handmaid after completing Sirris' questline
FS: Sunless Gauntlets - shop, Sirris quest, kill GA bossSold by Handmaid after completing Sirris' questline
FS: Sunless Leggings - shop, Sirris quest, kill GA bossSold by Handmaid after completing Sirris' questline
FS: Sunless Talisman - Sirris, kill GA bossDropped by Sirris on death or quest completion.
FS: Sunless Veil - shop, Sirris quest, kill GA bossSold by Handmaid after completing Sirris' questline
FS: Sunlight Spear - Ludleth for CinderBoss weapon for Soul of Cinder
FS: Sunset Shield - by grave after killing Hodrick w/SirrisLeft by Sirris upon quest completion.
FS: Tears of Denial - Irina for Tome of CarimSold by Irina after giving her the Braille Divine Tome of Carim
FS: Titanite Scale - Greirat from IBVSold by Greirat after pillaging Irithyll
FS: Titanite Slab - shop after placing all CindersSold by Handmaid after placing all Cinders of a Lord on their thrones
FS: Tower Key - shopSold by both Shrine Handmaid and Untended Graves Handmaid
FS: Twinkling Titanite - Greirat from IBVSold by Greirat after pillaging Irithyll
FS: Twisted Wall of Light - Orbeck for Golden ScrollSold by Orbeck after giving him the Golden Scroll
FS: Uchigatana - NPC dropDropped by Sword Master
FS: Undead Legion Armor - shop after killing FK bossSold by Handmaid after defeating Abyss Watchers
FS: Undead Legion Gauntlet - shop after killing FK bossSold by Handmaid after defeating Abyss Watchers
FS: Undead Legion Helm - shop after killing FK bossSold by Handmaid after defeating Abyss Watchers
FS: Undead Legion Leggings - shop after killing FK bossSold by Handmaid after defeating Abyss Watchers
FS: Untrue Dark Ring - Yoel/YuriaSold by Yuria
FS: Untrue White Ring - Yoel/YuriaSold by Yuria
FS: Vordt's Great Hammer - Ludleth for VordtBoss weapon for Vordt of the Boreal Valley
FS: Vow of Silence - Karla for Londor TomeSold by Irina or Karla after giving one the Londor Braille Divine Tome
FS: Washing Pole - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: White Dragon Breath - Ludleth for OceirosBoss weapon for Oceiros, the Consumed King
FS: White Sign Soapstone - shopSold by both Shrine Handmaid and Untended Graves Handmaid
FS: Wolf Knight's Greatsword - Ludleth for Abyss WatchersBoss weapon for Abyss Watchers
FS: Wolf Ring+2 - left of boss room exitAfter Iudex Gundyr on the left
FS: Wolnir's Crown - shop after killing CC bossSold by Handmaid after defeating High Lord Wolnir
FS: Wolnir's Holy Sword - Ludleth for WolnirBoss weapon for High Lord Wolnir
FS: Wood Grain Ring - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: Xanthous Gloves - Xanthous AshesSold by Handmaid after giving Xanthous Ashes
FS: Xanthous Overcoat - Xanthous AshesSold by Handmaid after giving Xanthous Ashes
FS: Xanthous Trousers - Xanthous AshesSold by Handmaid after giving Xanthous Ashes
FS: Yhorm's Great Machete - Ludleth for YhormBoss weapon for Yhorm the Giant
FS: Yhorm's Greatshield - Ludleth for YhormBoss weapon for Yhorm the Giant
FS: Young Dragon Ring - Orbeck for one scroll and buying three spellsGiven by Orbeck after purchasing four sorceries from him, and giving him one scroll, as a non-sorcerer.
FSBT: Armor of the Sun - crow for SiegbräuTrade Siegbräu with crow
FSBT: Blessed Gem - crow for Moaning ShieldTrade Moaning Shield with crow
FSBT: Covetous Silver Serpent Ring - illusory wall past raftersFrom the Firelink Shrine roof, past the rafters and an illusory wall
FSBT: Estus Ring - tower baseDropping down from the Bell Tower to where Irina eventually resides
FSBT: Estus Shard - raftersIn the Firelink Shrine rafters, accessible from the roof
FSBT: Fire Keeper Gloves - partway down towerDropping down to the left after entering the Bell Tower. Align with the center of the closest floor tile row and run off the edge at full speed, aiming slightly left.
FSBT: Fire Keeper Robe - partway down towerDropping down to the left after entering the Bell Tower. Align with the center of the closest floor tile row and run off the edge at full speed, aiming slightly left.
FSBT: Fire Keeper Skirt - partway down towerDropping down to the left after entering the Bell Tower. Align with the center of the closest floor tile row and run off the edge at full speed, aiming slightly left.
FSBT: Fire Keeper Soul - tower topAt the top of the Bell Tower
FSBT: Hello Carving - crow for Alluring SkullTrade Alluring Skull with crow
FSBT: Help me! Carving - crow for any sacred chimeTrade any Sacred Chime with crow
FSBT: Hollow Gem - crow for EleonoraTrade Eleonora with crow
FSBT: Homeward Bone - roofOn Firelink Shrine roof
FSBT: I'm sorry Carving - crow for Shriving StoneTrade Shriving Stone with crow
FSBT: Iron Bracelets - crow for Homeward BoneTrade Homeward Bone with crow
FSBT: Iron Helm - crow for Lightning UrnTrade Lightning Urn with crow
FSBT: Iron Leggings - crow for Seed of a Giant TreeTrade Seed of a Giant Tree with crow
FSBT: Large Titanite Shard - crow for FirebombTrade Firebomb or Rope Firebomb with crow
FSBT: Lightning Gem - crow for Xanthous CrownTrade Xanthous Crown with crow
FSBT: Lucatiel's Mask - crow for Vertebra ShackleTrade Vertebra Shackle with crow
FSBT: Porcine Shield - crow for Undead Bone ShardTrade Undead Bone Shard with crow
FSBT: Ring of Sacrifice - crow for Loretta's BoneTrade Loretta's Bone with crow
FSBT: Sunlight Shield - crow for Mendicant's StaffTrade Mendicant's Staff with crow
FSBT: Thank you Carving - crow for Hidden BlessingTrade Hidden Blessing with crow
FSBT: Titanite Chunk - crow for Black FirebombTrade Black Firebomb or Rope Black Firebomb with crow
FSBT: Titanite Scale - crow for Blacksmith HammerTrade Blacksmith Hammer with crow
FSBT: Titanite Slab - crow for Coiled Sword FragmentTrade Coiled Sword Fragment with crow
FSBT: Twinkling Titanite - crow for Large Leather ShieldTrade Large Leather Shield with crow
FSBT: Twinkling Titanite - crow for Prism StoneTrade Prism Stone with crow
FSBT: Twinkling Titanite - lizard behind FirelinkDropped by the Crystal Lizard behind Firelink Shrine. Can be accessed with tree jump by going all the way around the roof, left of the entrance to the rafters, or alternatively dropping down from the Bell Tower.
FSBT: Very good! Carving - crow for Divine BlessingTrade Divine Blessing with crow
GA: Avelyn - 1F, drop from 3F onto bookshelvesOn top of a bookshelf on the Archive first floor, accessible by going halfway up the stairs to the third floor, dropping down past the Grand Archives Scholar, and then dropping down again
GA: Black Hand Armor - shop after killing GA NPCSold by Handmaid after killing Black Hand Kumai
GA: Black Hand Hat - shop after killing GA NPCSold by Handmaid after killing Black Hand Kumai
GA: Blessed Gem - raftersOn the rafters high above the Archives, can be accessed by dropping down from the Winged Knight roof area
GA: Chaos Gem - dark room, lizardDropped by a Crystal Lizard on the Archives first floor in the dark room past the large wax pool
GA: Cinders of a Lord - Lothric PrinceDropped by Twin Princes
GA: Crystal Chime - 1F, path from wax poolOn the Archives first floor, in the room with the Lothric Knight, to the right
GA: Crystal Gem - 1F, lizard by dropDropped by the Crystal Lizard on the Archives first floor along the left wall
GA: Crystal Scroll - 2F late, miniboss dropDropped by the Grand Archives Crystal Sage
GA: Divine Blessing - rafters, down lower level ladderIn a chest reachable after dropping down from the Archives rafters and down a ladder near the Corpse-grub
GA: Divine Pillars of Light - cage above raftersIn a cage above the rafters high above the Archives, can be accessed by dropping down from the Winged Knight roof area
GA: Ember - 5F, by entranceOn a balcony high in the Archives overlooking the area with the Grand Archives Scholars with a shortcut ladder, on the opposite side from the wax pool
GA: Estus Shard - dome, far balconyOn the Archives roof near the three Winged Knights, in a side area overlooking the ocean.
GA: Faraam Armor - shop after killing GA NPCSold by Handmaid after killing Lion Knight Albert
GA: Faraam Boots - shop after killing GA NPCSold by Handmaid after killing Lion Knight Albert
GA: Faraam Gauntlets - shop after killing GA NPCSold by Handmaid after killing Lion Knight Albert
GA: Fleshbite Ring - up stairs from 4FFrom the first shortcut elevator with the movable bookshelf, past the Scholars right before going outside onto the roof, in an alcove to the right with many Clawed Curse bookshelves
GA: Golden Wing Crest Shield - outside 5F, NPC dropDropped by Lion Knight Albert before the stairs leading up to Twin Princes
GA: Heavy Gem - rooftops, lizardDropped by one of the pair of Crystal Lizards, on the right side, found going up a slope past the gargoyle on the Archives roof
GA: Hollow Gem - rooftops lower, in hallGoing onto the roof and down the first ladder, dropping down on either side from the ledge facing the ocean, in a tunnel underneath the ledge
GA: Homeward Bone - 2F early balconyOn the Archives second floor, on the balcony with the ladder going up to the Crystal Sage
GA: Hunter's Ring - dome, very topAt the top of the ladder in roof the area with the Winged Knights
GA: Large Soul of a Crestfallen Knight - 4F, backIn the back of a Clawed Curse-heavy corridor of bookshelves, in the area with the Grand Archives Scholars and dropdown ladder, after the first shortcut elevator with the movable bookshelf
GA: Large Soul of a Crestfallen Knight - outside 5FIn the middle of the area with the three human NPCs attacking you, before the Grand Archives bonfire shortcut elevator
GA: Lingering Dragoncrest Ring+2 - dome, room behind spireNear the tower with the Winged Knights, up the stairs on the opposite side from the ladder leading up to the Hunter's Ring
GA: Onikiri and Ubadachi - outside 5F, NPC dropDropped by Black Hand Kamui before the stairs leading up to Twin Princes
GA: Outrider Knight Armor - 3F, behind illusory wall, miniboss dropDropped by an Outrider Knight past the Crystal Sage's third floor location and an illusory wall
GA: Outrider Knight Gauntlets - 3F, behind illusory wall, miniboss dropDropped by an Outrider Knight past the Crystal Sage's third floor location and an illusory wall
GA: Outrider Knight Helm - 3F, behind illusory wall, miniboss dropDropped by an Outrider Knight past the Crystal Sage's third floor location and an illusory wall
GA: Outrider Knight Leggings - 3F, behind illusory wall, miniboss dropDropped by an Outrider Knight past the Crystal Sage's third floor location and an illusory wall
GA: Power Within - dark room, behind retractable bookshelfBehind a bookshelf in the dark room with the Crystal Lizards, moved by a lever in the same room
GA: Refined Gem - up stairs from 4F, lizardDropped by a Crystal Lizard found heading from the first elevator shortcut with the movable bookshelf, on the right side up the stairs before exiting to the roof
GA: Sage Ring+1 - rafters, second level downOn the rafters high above the Grand Archives, dropping down from the cage to the high rafters to the rafters below with the Corpse-grub
GA: Sage's Crystal Staff - outside 5F, NPC dropDropped by Daughter of Crystal Kriemhild before the stairs leading up to Twin Princes
GA: Scholar Ring - 2F, between late and earlyOn the corpse of a sitting Archives Scholar between two bookshelves, accessible by activating a lever before crossing the bridge that is the Crystal Sage's final location
GA: Sharp Gem - rooftops, lizardDropped by one of the pair of Crystal Lizards, on the left side, found going up a slope past the gargoyle on the Archives roof
GA: Shriving Stone - 2F late, by ladder from 3FGoing from the Crystal Sage's location on the third floor to its location on the bridge, after descending the ladder
GA: Soul Stream - 3F, behind illusory wallPast the Crystal Sage's third floor location, an illusory wall, and an Outrider Knight, on the corpse of a sitting Archives Scholar
GA: Soul of a Crestfallen Knight - 1F, loop left after dropOn the Archives first floor, hugging the left wall, on a ledge that loops back around to the left wall
GA: Soul of a Crestfallen Knight - path to domeOn balcony of the building with the second shortcut elevator down to the bonfire, accessible by going up the spiral stairs to the left
GA: Soul of a Nameless Soldier - dark roomOn the Archives first floor, after the wax pool, against a Clawed Curse bookshelf
GA: Soul of a Weary Warrior - rooftops, by lizardsOn the Archives roof, going up the first rooftop slope where a Gargoyle always attacks you
GA: Soul of the Twin PrincesDropped by Twin Princes
GA: Titanite Chunk - 1F, balconyOn the Archives first floor, on balcony overlooking the entrance opposite from the Grand Archives Scholars wax pool
GA: Titanite Chunk - 1F, path from wax poolOn the Archives first floor, toward the Lothric Knight, turning right to a ledge leading back to the entrance area
GA: Titanite Chunk - 1F, up right stairsGoing right after entering the Archives entrance and up the short flight of stairs
GA: Titanite Chunk - 2F, by wax poolUp the stairs from the Archives second floor on the right side from the entrance, in a corner near the small wax pool
GA: Titanite Chunk - 2F, right after dark roomExiting from the dark room with the Crystal Lizards on the first floor onto the second floor main room, then taking an immediate right
GA: Titanite Chunk - 5F, far balconyOn a balcony outside where Lothric Knight stands on the top floor of the Archives, accessing by going right from the final wax pool or by dropping down from the gargoyle area
GA: Titanite Chunk - rooftops, balconyGoing onto the roof and down the first ladder, all the way down the ledge facing the ocean to the right
GA: Titanite Chunk - rooftops lower, ledge by buttressGoing onto the roof and down the first ladder, dropping down on either side from the ledge facing the ocean, on a roof ledge to the right
GA: Titanite Chunk - rooftops, just before 5FOn the Archives roof, after a short dropdown, in the small area where the two Gargoyles attack you
GA: Titanite Scale - 1F, drop from 2F late onto bookshelves, lizardDropped by a Crystal Lizard on first floor bookshelves. Can be accessed by dropping down to the left at the end of the bridge which is the Crystal Sage's final location
GA: Titanite Scale - 1F, up stairs on bookshelfOn the Archives first floor, up a movable set of stairs near the large wax pool, on top of a bookshelf
GA: Titanite Scale - 2F, titanite scale atop bookshelfOn top of a bookshelf on the Archive second floor, accessible by going halfway up the stairs to the third floor and dropping down near a Grand Archives Scholar
GA: Titanite Scale - 3F, by ladder to 2F lateGoing from the Crystal Sage's location on the third floor to its location on the bridge, on the left side of the ladder you descend, behind a table
GA: Titanite Scale - 3F, corner up stairsFrom the Grand Archives third floor up past the thralls, in a corner with bookshelves to the left
GA: Titanite Scale - 5F, chest by exitIn a chest after the first elevator shortcut with the movable bookshelf, in the area with the Grand Archives Scholars, to the left of the stairwell leading up to the roof
GA: Titanite Scale - dark room, upstairsRight after going up the stairs to the Archives second floor, on the left guarded by a Grand Archives Scholar and a sequence of Clawed Curse bookshelves
GA: Titanite Scale - rooftops lower, path to 2FGoing onto the roof and down the first ladder, dropping down on either side from the ledge facing the ocean, then going past the corvians all the way to the left and making a jump
GA: Titanite Slab - 1F, after pulling 2F switchIn a chest on the Archives first floor, behind a bookshelf moved by pulling a lever in the middle of the second floor between two cursed bookshelves
GA: Titanite Slab - dome, kill all mobsDropped by killing all three Winged Knights on top of the Archives
GA: Titanite Slab - final elevator secretAt the bottom of the shortcut elevator right outside the Twin Princes fight. Requires sending the elevator up to the top from the middle, and then riding the lower elevator down.
GA: Twinkling Titanite - 1F, lizard by dropDropped by the Crystal Lizard on the Archives first floor along the left wall
GA: Twinkling Titanite - 2F, lizard by entranceDropped by the Crystal Lizard on the Archives second floor, going toward the stairs/balcony
GA: Twinkling Titanite - dark room, lizard #1Dropped by a Crystal Lizard on the Archives first floor in the dark room past the large wax pool
GA: Twinkling Titanite - dark room, lizard #2Dropped by a Crystal Lizard on the Archives first floor in the dark room past the large wax pool
GA: Twinkling Titanite - rafters, down lower level ladderIn a chest reachable after dropping down from the Archives rafters and down a ladder near the Corpse-grub
GA: Twinkling Titanite - rooftops, lizard #1Dropped by one of the pair of Crystal Lizards, on the right side, found going up a slope past the gargoyle on the Archives roof
GA: Twinkling Titanite - rooftops, lizard #2Dropped by one of the pair of Crystal Lizards, on the left side, found going up a slope past the gargoyle on the Archives roof
GA: Twinkling Titanite - up stairs from 4F, lizardDropped by a Crystal Lizard found heading from the first elevator shortcut with the movable bookshelf, on the right side up the stairs before exiting to the roof
GA: Undead Bone Shard - 5F, by entranceOn the corpse of a sitting Archives Scholar on a balcony high in the Archives overlooking the area with the Grand Archives Scholars with a shortcut ladder, near the final wax pool
GA: Witch's Locks - dark room, behind retractable bookshelfBehind a bookshelf in the dark room with the Crystal Lizards, moved by a lever in the same room
HWL: Astora Straight Sword - fort walkway, drop downIn the building with the Pus of Man on the roof, past the Lothric Knight down a hallway obscured by a wooden wheel, dropping down past the edge
HWL: Basin of Vows - EmmaDropped by Emma upon killing her. This is possible to do at any time
HWL: Battle Axe - flame tower, mimicDropped by mimic in the building guarded by the fire-breathing wyvern
HWL: Binoculars - corpse tower, upper platformIn the area with the dead wyvern, at the top of a set of stairs past a Hollow Soldier
HWL: Black Firebomb - small roof over fountainAfter roof with Pus of Man, on the edge of another rooftop to the left where you can drop down into Winged Knight area
HWL: Broadsword - fort, room off walkwayIn the building with the Pus of Man on the roof, past the Lothric Knight in an alcove to the left
HWL: Cell Key - fort ground, down stairsIn the basement of the building with Pus of Man on the roof, down the stairs guarded by a dog
HWL: Claymore - flame plazaIn the area where the wyvern breathes fire, farthest away from the door
HWL: Club - flame plazaIn the area where the wyvern breathes fire, in the open
HWL: Ember - back tower, transforming hollowDropped by the Pus of Man on the tower to the right of the High Wall bonfire after transformation
HWL: Ember - flame plazaIn the area where the wyvern breathes fire, in the open
HWL: Ember - fort roof, transforming hollowDropped by the Pus of Man on the roof after the Tower on the Wall bonfire after transformation
HWL: Ember - fountain #1In the area with the Winged Knight
HWL: Ember - fountain #2In the area with the Winged Knight
HWL: Estus Shard - fort ground, on anvilIn the basement of the building with the Pus of Man on the roof, on the blacksmith anvil
HWL: Firebomb - corpse tower, under tableIn the building near the dead wyvern, behind a table near the ladder you descend
HWL: Firebomb - fort roofNext to the Pus of Man on the roof
HWL: Firebomb - top of ladder to fountainBy the long ladder leading down to the area with the Winged Knight
HWL: Firebomb - wall tower, beamIn the building with the Tower on the Wall bonfire, on a wooden beam overhanging the lower levels
HWL: Fleshbite Ring+1 - fort roof, jump to other roofJumping from the roof with the Pus of Man to a nearby building with a fenced roof
HWL: Gold Pine Resin - corpse tower, dropDropping past the dead wyvern, down the left path from the High Wall bonfire
HWL: Green Blossom - fort walkway, hall behind wheelIn the building with the Pus of Man on the roof, past the Lothric Knight down a hallway obscured by a wooden wheel
HWL: Green Blossom - shortcut, lower courtyardIn the courtyard at the bottom of the shortcut elevator
HWL: Large Soul of a Deserted Corpse - flame plazaIn the area where the wyvern breathes fire, behind one of the praying statues
HWL: Large Soul of a Deserted Corpse - fort roofOn the edge of the roof with the Pus of Man
HWL: Large Soul of a Deserted Corpse - platform by fountainComing from the elevator shortcut, on a side path to the left (toward Winged Knight area)
HWL: Longbow - back towerDown the path from the right of the High Wall bonfire, where the Pus of Man and crossbowman are
HWL: Lucerne - promenade, side pathOn one of the side paths from the main path connecting Dancer and Vordt fights, patrolled by a Lothric Knight
HWL: Mail Breaker - wall tower, path to GreiratIn the basement of the building with the Tower on the Wall bonfire on the roof, before Greirat's cell
HWL: Rapier - fountain, cornerIn a corner in the area with the Winged Knight
HWL: Raw Gem - fort roof, lizardDropped by the Crystal Lizard on the rooftop after the Tower on the Wall bonfire
HWL: Red Eye Orb - wall tower, minibossDropped by the Darkwraith past the Lift Chamber Key
HWL: Refined Gem - promenade minibossDropped by the red-eyed Lothric Knight to the left of the Dancer's room entrance
HWL: Ring of Sacrifice - awning by fountainComing from the elevator shortcut, on a side path to the left (toward Winged Knight area), jumping onto a wooden support
HWL: Ring of the Evil Eye+2 - fort ground, far wallIn the basement of the building with the Pus of Man on the roof, on the far wall past the stairwell, behind some barrels
HWL: Silver Eagle Kite Shield - fort mezzanineIn the chest on the balcony overlooking the basement of the building with the Pus of Man on the roof
HWL: Small Lothric Banner - EmmaGiven by Emma, or dropped upon death
HWL: Soul of Boreal Valley VordtDropped by Vordt of the Boreal Valley
HWL: Soul of a Deserted Corpse - by wall tower doorRight before the entrance to the building with the Tower on the Wall bonfire
HWL: Soul of a Deserted Corpse - corpse tower, bottom floorDown the ladder of the building near the dead wyvern, on the way to the living wyvern
HWL: Soul of a Deserted Corpse - fort entry, cornerIn the corner of the room with a Lothric Knight, with the Pus of Man on the roof
HWL: Soul of a Deserted Corpse - fountain, path to promenadeIn between the Winged Knight area and the Dancer/Vordt corridor
HWL: Soul of a Deserted Corpse - path to back tower, by lift doorWhere the Greataxe Hollow Soldier patrols outside of the elevator shortcut entrance
HWL: Soul of a Deserted Corpse - path to corpse towerAt the very start, heading left from the High Wall bonfire
HWL: Soul of a Deserted Corpse - wall tower, right of exitExiting the building with the Tower on the Wall bonfire on the roof, immediately to the right
HWL: Soul of the DancerDropped by Dancer of the Boreal Valley
HWL: Standard Arrow - back towerDown the path from the right of the High Wall bonfire, where the Pus of Man and crossbowman are
HWL: Throwing Knife - shortcut, lift topAt the top of the elevator shortcut, opposite from the one-way door
HWL: Throwing Knife - wall tower, path to GreiratIn the basement of the building with the Tower on the Wall bonfire, in the room with the explosive barrels
HWL: Titanite Shard - back tower, transforming hollowDropped by the Pus of Man on the tower to the right of the High Wall bonfire after transformation
HWL: Titanite Shard - fort ground behind cratesBehind some wooden crates in the basement of the building with the Pus of Man on the roof
HWL: Titanite Shard - fort roof, transforming hollowDropped by the Pus of Man on the roof after the Tower on the Wall bonfire after transformation
HWL: Titanite Shard - fort, room off entryIn the building with the Pus of Man on the roof, in a room to the left and up the short stairs
HWL: Titanite Shard - wall tower, corner by bonfireOn the balcony with the Tower on the Wall bonfire
HWL: Undead Hunter Charm - fort, room off entry, in potIn the building with the Pus of Man on the roof, in a room to the left, in a pot you have to break
HWL: Way of Blue - EmmaGiven by Emma or dropped upon death.
IBV: Blood Gem - descent, platform before lakeIn front of the tree in the courtyard before going down the stairs to the lake leading to the Distant Manor bonfire
IBV: Blue Bug Pellet - ascent, in last buildingIn the final building before Pontiff's cathedral, coming from the sewer, on the first floor
IBV: Blue Bug Pellet - descent, dark roomIn the dark area with the Irithyllian slaves, to the left of the staircase
IBV: Budding Green Blossom - central, by second fountainNext to the fountain up the stairs from the Central Irithyll bonfire
IBV: Chloranthy Ring+1 - plaza, behind altarIn the area before and below Pontiff's cathedral, behind the central structure
IBV: Covetous Gold Serpent Ring+1 - descent, drop after dark roomAfter the dark area with the Irithyllian slaves, drop down to the right
IBV: Creighton's Steel Mask - bridge after killing CreightonFollowing Sirris' questline, found on the bridge to Irithyll after being invaded by Creighton the Wanderer in the graveyard after the Church of Yorshka.
IBV: Divine Blessing - great hall, chestIn a chest up the stairs in the room with the Silver Knight staring at the painting
IBV: Divine Blessing - great hall, mob dropOne-time drop from the Silver Knight staring at the painting in Irithyll
IBV: Dorhys' Gnawing - Dorhys dropDropped by Cathedral Evangelist Dorhys, past an illusory railing past the Central Irithyll Fire Witches and to the left
IBV: Dragonslayer's Axe - Creighton dropFollowing Sirris' questline, dropped by Creighton the Wanderer when he invades in the graveyard after the Church of Yorshka.
IBV: Dung Pie - sewer #1In the area with the sewer centipedes
IBV: Dung Pie - sewer #2In the area with the sewer centipedes
IBV: Ember - shortcut from church to cathedralAfter the gate shortcut from Church of Yorshka to Pontiff's cathedral
IBV: Emit Force - SiegwardGiven by Siegward meeting him in the Irithyll kitchen after the Sewer Centipedes.
IBV: Excrement-covered Ashes - sewer, by stairsIn the area with the sewer centipedes, before going up the stairs to the kitchen
IBV: Fading Soul - descent, cliff edge #1In the graveyard down the stairs from the Church of Yorshka, at the cliff edge
IBV: Fading Soul - descent, cliff edge #2In the graveyard down the stairs from the Church of Yorshka, at the cliff edge
IBV: Great Heal - lake, dead Corpse-GrubOn the Corpse-grub at the edge of the lake leading to the Distant Manor bonfire
IBV: Green Blossom - lake wallOn the wall of the lake leading to the Distant Manor bonfire
IBV: Green Blossom - lake, by Distant ManorIn the lake close to the Distant Manor bonfire
IBV: Green Blossom - lake, by stairs from descentGoing down the stairs into the lake leading to the Distant Manor bonfire
IBV: Homeward Bone - descent, before gravestoneIn the graveyard down the stairs from the Church of Yorshka, in front of the grave with the Corvian
IBV: Kukri - descent, side pathDown the stairs from the graveyard after Church of Yorshka, before the group of dogs in the left path
IBV: Large Soul of a Nameless Soldier - ascent, after great hallBy the tree near the stairs from the sewer leading up to Pontiff's cathedral, where the first dogs attack you
IBV: Large Soul of a Nameless Soldier - central, by bonfireBy the Central Irithyll bonfire
IBV: Large Soul of a Nameless Soldier - central, by second fountainNext to the fountain up the stairs from the Central Irithyll bonfire
IBV: Large Soul of a Nameless Soldier - lake islandOn an island in the lake leading to the Distant Manor bonfire
IBV: Large Soul of a Nameless Soldier - stairs to plazaOn the path from Central Irithyll bonfire, before making the left toward Church of Yorshka
IBV: Large Titanite Shard - Distant Manor, under overhangUnder overhang next to second set of stairs leading from Distant Manor bonfire
IBV: Large Titanite Shard - ascent, by elevator doorOn the path from the sewer leading up to Pontiff's cathedral, to the right of the statue surrounded by dogs
IBV: Large Titanite Shard - ascent, down ladder in last buildingOutside the final building before Pontiff's cathedral, coming from the sewer, dropping down to the left before the entrance
IBV: Large Titanite Shard - central, balcony just before plazaFrom the Central Irithyll bonfire, on the balcony with the second Fire Witch.
IBV: Large Titanite Shard - central, side path after first fountainUp the stairs from the Central Irithyll bonfire, on a railing to the right
IBV: Large Titanite Shard - great hall, main floor mob dropOne-time drop from the Silver Knight staring at the painting in Irithyll
IBV: Large Titanite Shard - great hall, upstairs mob drop #1One-time drop from the Silver Knight on the balcony of the room with the painting
IBV: Large Titanite Shard - great hall, upstairs mob drop #2One-time drop from the Silver Knight on the balcony of the room with the painting
IBV: Large Titanite Shard - path to DorhysBefore the area with Cathedral Evangelist Dorhys, past an illusory railing past the Central Irithyll Fire Witches
IBV: Large Titanite Shard - plaza, balcony overlooking ascentOn the path from Central Irithyll bonfire, instead of going left toward the Church of Yorshka, going right, on the balcony
IBV: Large Titanite Shard - plaza, by stairs to churchTo the left of the stairs leading up to the Church of Yorshka from Central Irithyll
IBV: Leo Ring - great hall, chestIn a chest up the stairs in the room with the Silver Knight staring at the painting
IBV: Lightning Gem - plaza centerIn the area before and below Pontiff's cathedral, in the center guarded by the enemies
IBV: Magic Clutch Ring - plaza, illusory wallIn the area before and below Pontiff's cathedral, behind an illusory wall to the right
IBV: Mirrah Chain Gloves - bridge after killing CreightonFollowing Sirris' questline, found on the bridge to Irithyll after being invaded by Creighton the Wanderer in the graveyard after the Church of Yorshka.
IBV: Mirrah Chain Leggings - bridge after killing CreightonFollowing Sirris' questline, found on the bridge to Irithyll after being invaded by Creighton the Wanderer in the graveyard after the Church of Yorshka.
IBV: Mirrah Chain Mail - bridge after killing CreightonFollowing Sirris' questline, found on the bridge to Irithyll after being invaded by Creighton the Wanderer in the graveyard after the Church of Yorshka.
IBV: Proof of a Concord Kept - Church of Yorshka altarAt the altar in the Church of Yorshka
IBV: Rime-blue Moss Clump - central, by bonfireBy the Central Irithyll bonfire
IBV: Rime-blue Moss Clump - central, past second fountainFrom the Central Irithyll bonfire, to the left before the first Fire Witch.
IBV: Ring of Sacrifice - lake, right of stairs from descentNear the sewer centipede at the start of the lake leading to the Distant Manor bonfire
IBV: Ring of the Evil Eye - AnriGiven by Anri of Astora in the Church of Yorshka, or if told of Horace's whereabouts in the Catacombs
IBV: Ring of the Sun's First Born - fall from in front of cathedralDropping down from in front of Pontiff Sulyvahn's church toward the Church of Yorshka
IBV: Roster of Knights - descent, first landingOn the landing going down the stairs from Church of Yorshka to the graveyard
IBV: Rusted Gold Coin - Distant Manor, drop after stairsDropping down after the first set of stairs leading from Distant Manor bonfire
IBV: Rusted Gold Coin - descent, side pathDown the stairs from the graveyard after Church of Yorshka, guarded by the group of dogs in the left path
IBV: Shriving Stone - descent, dark room raftersOn the rafters in the dark area with the Irithyllian slaves
IBV: Siegbräu - SiegwardGiven by Siegward meeting him in the Irithyll kitchen after the Sewer Centipedes.
IBV: Smough's Great Hammer - great hall, chestIn a chest up the stairs in the room with the Silver Knight staring at the painting
IBV: Soul of Pontiff SulyvahnDropped by Pontiff Sulyvahn
IBV: Soul of a Weary Warrior - ascent, by final staircaseToward the end of the path from the sewer leading up to Pontiff's cathedral, to the left of the final staircase
IBV: Soul of a Weary Warrior - central, by first fountainBy the Central Irithyll bonfire
IBV: Soul of a Weary Warrior - central, railing by first fountainOn the railing overlooking the Central Irithyll bonfire, at the very start
IBV: Soul of a Weary Warrior - plaza, side room lowerDropping down from the path from Church of Yorshka to Pontiff, guarded by the pensive Fire Witch
IBV: Soul of a Weary Warrior - plaza, side room upperIn the path from Church of Yorshka to Pontiff's cathedral, at the broken ledge you can drop down onto the Fire Witch
IBV: Twinkling Titanite - central, lizard before plazaDropped by a Crystal Lizard past the Central Irithyll Fire Witches and to the left
IBV: Twinkling Titanite - descent, lizard behind illusory wallDropped by a Crystal Lizard behind an illusory wall before going down the stairs to the lake leading to the Distant Manor bonfire
IBV: Undead Bone Shard - descent, behind gravestoneIn the graveyard down the stairs from the Church of Yorshka, behind the grave with the Corvian
IBV: Witchtree Branch - by DorhysIn the area with Cathedral Evangelist Dorhys, past an illusory railing past the Central Irithyll Fire Witches
IBV: Wood Grain Ring+2 - ascent, right after great hallLeaving the building with the Silver Knight staring at the painting, instead of going left up the stairs, go right
IBV: Yorshka's Spear - descent, dark room rafters chestIn a chest in the rafters of the dark area with the Irithyllian slaves
ID: Alva Armor - B3 near, by Karla's cell, after killing AlvaIn the main Jailer cell block on the floor close to Karla's cell, if the invading Alva is killed
ID: Alva Gauntlets - B3 near, by Karla's cell, after killing AlvaIn the main Jailer cell block on the floor close to Karla's cell, if the invading Alva is killed
ID: Alva Helm - B3 near, by Karla's cell, after killing AlvaIn the main Jailer cell block on the floor close to Karla's cell, if the invading Alva is killed
ID: Alva Leggings - B3 near, by Karla's cell, after killing AlvaIn the main Jailer cell block on the floor close to Karla's cell, if the invading Alva is killed
ID: Bellowing Dragoncrest Ring - drop from B1 towards pitDropping down from the Jailbreaker's Key shortcut at the end of the top corridor on the bonfire side in Irithyll Dungeon
ID: Covetous Gold Serpent Ring - Siegward's cellIn the Old Cell where Siegward is rescued
ID: Covetous Silver Serpent Ring+1 - pit lift, middle platformOn one of the platforms in elevator shaft of the shortcut elevator from the Giant Slave area to the Irithyll Dungeon bonfire
ID: Dark Clutch Ring - stairs between pit and B3, mimicDropped by the mimic found going past the Giant Slave to the sewer with the rats and the basilisks, up the first flight of stairs, on the left side
ID: Dragon Torso Stone - B3, outside liftOn the balcony corpse in the Path of the Dragon pose
ID: Dragonslayer Lightning Arrow - pit, mimic in hallDropped by the mimic in the side corridor from where the Giant Slave is standing, before the long ladder
ID: Dung Pie - B3, by path from pitIn the room with the Giant Hound Rats
ID: Dung Pie - pit, miniboss dropDrop from the Giant Slave
ID: Dusk Crown Ring - B3 far, right cellIn the cell in the main Jailer cell block to the left of the Profaned Capital exit
ID: Ember - B3 centerAt the center pillar in the main Jailer cell block
ID: Ember - B3 far rightIn the main Jailer cell block, on the left side coming from the Profaned Capital
ID: Estus Shard - mimic on path from B2 to pitDropped by the mimic in the room after the outside area of Irithyll Dungeon overlooking Profaned Capital
ID: Fading Soul - B1 near, main hallOn the top corridor on the bonfire side in Irithyll Dungeon, close to the first Jailer
ID: Great Magic Shield - B2 near, mob drop in far left cellOne-time drop from the Infested Corpse in the bottom corridor on the bonfire side of Irithyll Dungeon, in the closest cell
ID: Homeward Bone - path from B2 to pitIn the part of Irithyll Dungeon overlooking the Profaned Capital, after exiting the last jail cell corridor
ID: Jailbreaker's Key - B1 far, cell after gateIn the cell of the top corridor opposite to the bonfire in Irithyll Dungeon
ID: Large Soul of a Nameless Soldier - B2 far, by liftTaking the elevator up from the area you can use Path of the Dragon, before the one-way door
ID: Large Soul of a Nameless Soldier - B2, hall by stairsAt the end of the bottom corridor on the bonfire side in Irithyll Dungeon
ID: Large Soul of a Weary Warrior - just before Profaned CapitalIn the open area before the bridge leading into Profaned Capital from Irithyll Dungeon
ID: Large Titanite Shard - B1 far, rightmost cellIn a cell on the far end of the top corridor opposite to the bonfire in Irithyll Dungeon, nearby the Jailer
ID: Large Titanite Shard - B1 near, by doorAt the end of the top corridor on the bonfire side in Irithyll Dungeon, before the Jailbreaker's Key door
ID: Large Titanite Shard - B3 near, right cornerIn the main Jailer cell block, to the left of the hallway leading to the Path of the Dragon area
ID: Large Titanite Shard - after bonfire, second cell on rightIn the second cell on the right after Irithyll Dungeon bonfire
ID: Large Titanite Shard - pit #1On the floor where the Giant Slave is standing
ID: Large Titanite Shard - pit #2On the floor where the Giant Slave is standing
ID: Lightning Blade - B3 lift, middle platformOn the middle platform riding the elevator up from the Path of the Dragon area
ID: Lightning Bolt - awning over pitOn the wooden overhangs above the Giant Slave. Can be reached by dropping down after climbing the long ladder around the area where the Giant stands.
ID: Murakumo - Alva dropDropped by Alva, Seeker of the Spurned when he invades in the cliffside path to Irithyll Dungeon
ID: Old Cell Key - stairs between pit and B3In a chest found going past the Giant Slave to the sewer with the rats and the basilisks, up the stairs to the end, on the right side
ID: Old Sorcerer Boots - B2 near, middle cellIn one of the cells on the bottom corridor on the bonfire side in Irithyll Dungeon, close to the bonfire, with many Infested Corpses
ID: Old Sorcerer Coat - B2 near, middle cellIn one of the cells on the bottom corridor on the bonfire side in Irithyll Dungeon, close to the bonfire, with many Infested Corpses
ID: Old Sorcerer Gauntlets - B2 near, middle cellIn one of the cells on the bottom corridor on the bonfire side in Irithyll Dungeon, close to the bonfire, with many Infested Corpses
ID: Old Sorcerer Hat - B2 near, middle cellIn one of the cells on the bottom corridor on the bonfire side in Irithyll Dungeon, close to the bonfire, with many Infested Corpses
ID: Pale Pine Resin - B1 far, cell with broken wallIn the jail cell with the broken wall in the top corridor opposite to the bonfire in Irithyll Dungeon, near the passive Wretch on the wall
ID: Pickaxe - path from pit to B3Passing by the Giant Slave, before the tunnel with the rats and basilisks
ID: Prisoner Chief's Ashes - B2 near, locked cell by stairsIn the cell at the far end of the bottom corridor on the bonfire side in Irithyll Dungeon
ID: Profaned Coal - B3 far, left cellIn the room with the Wretches next to the main Jailer cell block, guarded by a Wretch
ID: Profaned Flame - pitOn the floor where the Giant Slave is standing
ID: Rusted Coin - after bonfire, first cell on leftIn the first cell on the left from the Irithyll dungeon bonfire
ID: Rusted Gold Coin - after bonfire, last cell on rightIn the third cell on the right from the Irithyll Dungeon bonfire
ID: Simple Gem - B2 far, cell by stairsIn the cell near the bottom corridor opposite to the bonfire in Irithyll Dungeon, adjacent to the room with three Jailers and Cage Spiders
ID: Soul of a Crestfallen Knight - balcony above pitUnder whether the Giant Slave is resting his head
ID: Soul of a Weary Warrior - by drop to pitAt the end of the room with many peasant hollows after the Estus Shard mimic
ID: Soul of a Weary Warrior - stairs between pit and B3Going past the Giant Slave to the sewer with the rats and the basilisks, up the first flight of stairs
ID: Titanite Chunk - balcony above pit, lizardDropped by the Crystal Lizard where the Giant Slave is resting his head
ID: Titanite Chunk - pit, miniboss dropDrop from the Giant Slave
ID: Titanite Scale - B2 far, lizardDropped by the Crystal Lizard on the bottom corridor opposite from the bonfire in Irithyll Dungeon where a Wretch attacks you
ID: Titanite Scale - B3 far, mimic in hallDropped by the mimic in the main Jailer cell block
ID: Titanite Slab - SiegwardGiven by Siegward after unlocking Old Cell or on quest completion
ID: Xanthous Ashes - B3 far, right cellIn the cell in the main Jailer cell block to the left of the Profaned Capital exit
KFF: Soul of the LordsDropped by Soul of Cinder
LC: Black Firebomb - dark room lowerIn the room with the firebomb-throwing hollows, against the wall on the lowest level
LC: Braille Divine Tome of Lothric - wyvern roomIn the room next to the second Pus of Man wyvern
LC: Caitha's Chime - chapel, drop onto roofDropping down from the chapel balcony where the Red Tearstone Ring is found, and then dropping down again towards the Lothric knights
LC: Dark Stoneplate Ring+1 - wyvern room, balconyThrough the room next to the second Pus of Man wyvern, on the balcony outside
LC: Ember - by Dragon Barracks bonfireNear the Dragon Barracks bonfire
LC: Ember - dark room mid, pus of man mob dropDropped by the first Pus of Man wyvern
LC: Ember - main hall, left of stairsTo the left of the stairs past the Dragon Barracks grate
LC: Ember - plaza centerIn the area where the Pus of Man wyverns breathe fire
LC: Ember - plaza, by gateOn the railing near the area where the Pus of Man wyverns breathe fire, before the gate
LC: Ember - wyvern room, wyvern foot mob dropDropped by the second Pus of Man wyvern
LC: Gotthard Twinswords - by Grand Archives door, after PC and AL bossesBefore the door to the Grand Archives after Aldrich and Yhorm are killed
LC: Grand Archives Key - by Grand Archives door, after PC and AL bossesBefore the door to the Grand Archives after Aldrich and Yhorm are killed
LC: Greatlance - overlooking Dragon Barracks bonfireGuarded by a pensive Lothric Knight after the Dragon Barracks bonfire and continuing up the stairs
LC: Hood of PrayerIn a chest right after the Lothric Castle bonfire
LC: Irithyll Rapier - basement, miniboss dropDropped by the Boreal Outrider Knight in the basement
LC: Knight's Ring - altarClimbing the ladder to the rooftop outside the Dragonslayer Armour fight, past the Large Hollow Soldier, down into the room with the tables
LC: Large Soul of a Nameless Soldier - dark room midIn the room with the firebomb-throwing hollows, up the ladder
LC: Large Soul of a Nameless Soldier - moat, right pathFound on the ledge after dropping into the area with the Pus of Man transforming hollows and making the entire loop
LC: Large Soul of a Nameless Soldier - plaza left, by pillarIn the building to the left of the area where the Pus of Man wyverns breathe fire, against a pillar
LC: Large Soul of a Weary Warrior - ascent, last turretRather than going up the stairs to the Dragon Barracks bonfire, continue straight down the stairs and forwards
LC: Large Soul of a Weary Warrior - main hall, by leverOn a ledge to the right of the lever opening the grate
LC: Life Ring+2 - dark room mid, out door opposite wyvern, drop downPast the room with the firebomb-throwing hollows and Pus of Man wyvern, around to the front, dropping down past where the Titanite Chunk is
LC: Lightning Urn - moat, right path, first roomStarting the loop from where the Pus of Man hollows transform, behind some crates in the first room
LC: Lightning Urn - plazaIn the area where the Pus of Man wyverns breathe fire
LC: Pale Pine Resin - dark room upper, by mimicIn the room with the firebomb-throwing hollows, next to the mimic in the far back left
LC: Raw Gem - plaza leftOn a balcony to the left of the area where the Pus of Man wyverns breathe fire, where the Hollow Soldier throws Undead Hunter Charms
LC: Red Tearstone Ring - chapel, drop onto roofFrom the chapel to the right of the Dragonslayer Armour fight, on the balcony to the left
LC: Refined Gem - plazaIn the area where the Pus of Man wyverns breathe fire
LC: Robe of Prayer - ascent, chest at beginningIn a chest right after the Lothric Castle bonfire
LC: Rusted Coin - chapelIn the chapel to the right of the Dragonslayer Armour fight
LC: Sacred Bloom Shield - ascent, behind illusory wallUp the ladder where the Winged Knight is waiting, past an illusory wall
LC: Skirt of Prayer - ascent, chest at beginningIn a chest right after the Lothric Castle bonfire
LC: Sniper Bolt - moat, right path endHanging from the arch passed under on the way to the Dragon Barracks bonfire. Can be accessed by dropping into the area with the Pus of Man transforming hollows and making the entire loop, but going left at the end
LC: Sniper Crossbow - moat, right path endHanging from the arch passed under on the way to the Dragon Barracks bonfire. Can be accessed by dropping into the area with the Pus of Man transforming hollows and making the entire loop, but going left at the end
LC: Soul of Dragonslayer ArmourDropped by Dragonslayer Armour
LC: Soul of a Crestfallen Knight - by lift bottomGuarded by a buffed Lothric Knight straight from the Dancer bonfire
LC: Soul of a Crestfallen Knight - wyvern room, balconyOn a ledge accessible after the second Pus of Man wyvern is defeated
LC: Spirit Tree Crest Shield - basement, chestIn a chest in the basement with the Outrider Knight
LC: Sunlight Medal - by lift topNext to the shortcut elevator outside of the Dragonslayer Armour fight that goes down to the start of the area
LC: Sunlight Straight Sword - wyvern room, mimicDropped by the mimic in the room next to the second Pus of Man wyvern
LC: Thunder Stoneplate Ring+2 - chapel, drop onto roofDropping down from the chapel balcony where the Red Tearstone Ring is found, out on the edge
LC: Titanite Chunk - altar roofClimbing the ladder to the rooftop outside the Dragonslayer Armour fight, overlooking the tree
LC: Titanite Chunk - ascent, final turretRather than going up the stairs to the Dragon Barracks bonfire, continue straight down the stairs, then right
LC: Titanite Chunk - ascent, first balconyRight after the Lothric Castle bonfire, out on the balcony
LC: Titanite Chunk - ascent, turret before barricadesFrom the Lothric Castle bonfire, up the stairs, straight, and then down the stairs behind the barricade
LC: Titanite Chunk - dark room mid, out door opposite wyvernFrom the room with the firebomb-throwing hollows, past the Pus of Man Wyvern and back around the front, before the Crystal Lizard
LC: Titanite Chunk - dark room mid, pus of man mob dropDropped by the first Pus of Man wyvern
LC: Titanite Chunk - down stairs after bossDown the stairs to the right after Dragonslayer Armour
LC: Titanite Chunk - moat #1In the center of the area where the Pus of Man hollows transform
LC: Titanite Chunk - moat #2In the center of the area where the Pus of Man hollows transform
LC: Titanite Chunk - moat, near ledgeDropping down from the bridge where the Pus of Man wyverns breathe fire on the near side to the bonfire
LC: Titanite Chunk - wyvern room, wyvern foot mob dropDropped by the second Pus of Man wyvern
LC: Titanite Scale - altarIn a chest climbing the ladder to the rooftop outside the Dragonslayer Armour fight, continuing the loop past the Red-Eyed Lothric Knight
LC: Titanite Scale - basement, chestIn a chest in the basement with the Outrider Knight
LC: Titanite Scale - chapel, chestIn a chest in the chapel to the right of the Dragonslayer Armour fight
LC: Titanite Scale - dark room mid, out door opposite wyvernPassing through the room with the firebomb-throwing hollows and the Pus of Man wyvern around to the front, overlooking the area where the wyverns breathe fire
LC: Titanite Scale - dark room, upper balconyIn the room with the firebomb-throwing hollows, at the very top on a balcony to the right
LC: Titanite Scale - dark room, upper, mimicDropped by the crawling mimic at the top of the room with the firebomb-throwing hollows
LC: Twinkling Titanite - ascent, side roomIn the room where the Winged Knight drops down
LC: Twinkling Titanite - basement, chest #1In a chest in the basement with the Outrider Knight
LC: Twinkling Titanite - basement, chest #2In a chest in the basement with the Outrider Knight
LC: Twinkling Titanite - dark room mid, out door opposite wyvern, lizardDropped by the Crystal Lizard after the room with the firebomb-throwing hollows around the front
LC: Twinkling Titanite - moat, left sideBehind one of the Pus of Man transforming hollows, to the left of the bridge to the wyvern fire-breathing area
LC: Twinkling Titanite - moat, right path, lizardDropped by the Crystal Lizard near the thieves after dropping down to the area with the Pus of Man transforming hollows
LC: Undead Bone Shard - moat, far ledgeDropping down from the bridge where the Pus of Man wyverns breathe fire on the far side from the bonfire
LC: Winged Knight Armor - ascent, behind illusory wallIn the area where the Winged Knight drops down, up the ladder and past the illusory wall
LC: Winged Knight Gauntlets - ascent, behind illusory wallIn the area where the Winged Knight drops down, up the ladder and past the illusory wall
LC: Winged Knight Helm - ascent, behind illusory wallIn the area where the Winged Knight drops down, up the ladder and past the illusory wall
LC: Winged Knight Leggings - ascent, behind illusory wallIn the area where the Winged Knight drops down, up the ladder and past the illusory wall
PC: Blooming Purple Moss Clump - walkway above swampAt the right end of the plank before dropping down into the Profaned Capital toxic pool
PC: Cinders of a Lord - Yhorm the GiantDropped by Yhorm the Giant
PC: Court Sorcerer Gloves - chapel, second floorOn the second floor of the Monstrosity of Sin building in front of the Monstrosity of Sin
PC: Court Sorcerer Hood - chapel, second floorOn the second floor of the Monstrosity of Sin building in front of the Monstrosity of Sin
PC: Court Sorcerer Robe - chapel, second floorOn the second floor of the Monstrosity of Sin building in front of the Monstrosity of Sin
PC: Court Sorcerer Trousers - chapel, second floorOn the second floor of the Monstrosity of Sin building in front of the Monstrosity of Sin
PC: Court Sorcerer's Staff - chapel, mimic on second floorDropped by the mimic on the second floor of the Monstrosity of Sin building
PC: Cursebite Ring - swamp, below hallsIn the inner cave of the Profaned Capital toxic pool
PC: Eleonora - chapel ground floor, kill mobDropped by the Monstrosity of Sin on the first floor, furthest away from the door
PC: Ember - palace, far roomTo the right of the Profaned Flame, in the room with the many Jailers looking at the mimics
PC: Flame Stoneplate Ring+1 - chapel, drop from roof towards entranceDropping down from the roof connected to the second floor of the Monstrosity of Sin building, above the main entrance to the building
PC: Greatshield of Glory - palace, mimic in far roomDropped by the left mimic surrounded by the Jailers to the right of the Profaned Flame
PC: Jailer's Key Ring - hall past chapelPast the Profaned Capital Court Sorcerer, in the corridor overlooking the Irithyll Dungeon Giant Slave area
PC: Large Soul of a Weary Warrior - bridge, far endOn the way from the Profaned Capital bonfire toward the Profaned Flame, crossing the bridge without dropping down
PC: Logan's Scroll - chapel roof, NPC dropDropped by the court sorcerer above the toxic pool
PC: Magic Stoneplate Ring+2 - tower baseAt the base of the Profaned Capital structure, going all the way around the outside wall clockwise
PC: Onislayer Greatarrow - bridgeItem on the bridge descending from the Profaned Capital bonfire into the Profaned Flame building
PC: Onislayer Greatbow - drop from bridgeFrom the bridge leading from the Profaned Capital bonfire to Yhorm, onto the ruined pillars shortcut to the right, behind you after the first dropdown.
PC: Pierce Shield - SiegwardDropped by Siegward upon death or quest completion, and sold by Patches while Siegward is in the well.
PC: Poison Arrow - chapel roofAt the far end of the roof with the Court Sorcerer
PC: Poison Gem - swamp, below hallsIn the inner cave of the Profaned Capital toxic pool
PC: Purging Stone - chapel ground floorAt the back of the room with the three Monstrosities of Sin on the first floor
PC: Purging Stone - swamp, by chapel ladderIn the middle of the Profaned Capital toxic pool, near the ladder to the Court Sorcerer
PC: Rubbish - chapel, down stairs from second floorHanging corpse visible from Profaned Capital accessible from the second floor of the building with the Monstrosities of Sin, in the back right
PC: Rusted Coin - below bridge #1Among the rubble before the steps leading up to the Profaned Flame
PC: Rusted Coin - below bridge #2Among the rubble before the steps leading up to the Profaned Flame
PC: Rusted Coin - tower exteriorTreasure visible on a ledge in the Profaned Capital bonfire. Can be accessed by climbing a ladder outside the main structure.
PC: Rusted Gold Coin - halls above swampIn the corridors leading to the Profaned Capital toxic pool
PC: Rusted Gold Coin - palace, mimic in far roomDropped by the right mimic surrounded by the Jailers to the right of the Profaned Flame
PC: Shriving Stone - swamp, by chapel doorAt the far end of the Profaned Capital toxic pool, to the left of the door leading to the Monstrosities of Sin
PC: Siegbräu - Siegward after killing bossGiven by Siegward after helping him defeat Yhorm the Giant. You must talk to him before Emma teleports you.
PC: Soul of Yhorm the GiantDropped by Yhorm the Giant
PC: Storm Ruler - SiegwardDropped by Siegward upon death or quest completion.
PC: Storm Ruler - boss roomTo the right of Yhorm's throne
PC: Twinkling Titanite - halls above swamp, lizard #1Dropped by the second Crystal Lizard in the corridors before the Profaned Capital toxic pool
PC: Twinkling Titanite - halls above swamp, lizard #2Dropped by the first Crystal Lizard in the corridors before the Profaned Capital toxic pool
PC: Undead Bone Shard - by bonfireOn the corpse of Laddersmith Gilligan next to the Profaned Capital bonfire
PC: Wrath of the Gods - chapel, drop from roofDropping down from the roof of the Monstrosity of Sin building where the Court Sorcerer is
PW1: Black Firebomb - snowfield lower, path to bonfireDropping down after the first snow overhang and following the wall on the left, past the rotting bed descending toward the second bonfire
PW1: Blessed Gem - snowfield, behind towerBehind the Millwood Knight tower in the first area, approach from the right side
PW1: Budding Green Blossom - settlement courtyard, ledgeAfter sliding down the slope on the way to Corvian Settlement, dropping down hugging the left wall
PW1: Captain's Ashes - snowfield tower, 6FAt the very top of the Millwood Knight tower after climbing up the second ladder
PW1: Chillbite Ring - FriedeGiven by Sister Friede while she is sitting in the Ariandel Chapel, or on the stool after she moves.
PW1: Contraption Key - library, NPC dropDropped by Sir Vilhelm
PW1: Crow Quills - settlement loop, jump into courtyardCrossing the bridge after Corvian Settlement bonfire, follow the left edge past another bridge until a dropdown point looping back to the bonfire. Go right and jump past some barrels onto the central platform.
PW1: Crow Talons - settlement roofs, near bonfireAfter climbing the ladder onto Corvian Settlement rooftops, dropping down on a bridge to the left, into the building, then looping around onto its roof.
PW1: Dark Gem - settlement back, egg buildingDropping down to the right of the gate guarded by a Corvian Knight in Corvian Settlement, inside of the last building on the right
PW1: Ember - roots above depthsIn the tree branch area after climbing down the rope bridge, hugging a right wall past a Follower Javelin wielder
PW1: Ember - settlement main, left building after bridgeCrossing the bridge after Corvian Settlement bonfire, in the building to the left.
PW1: Ember - settlement, building near bonfireIn the first building in Corvian Settlement next to the bonfire building
PW1: Ethereal Oak Shield - snowfield tower, 3FIn the Millwood Knight tower on a Millwood Knight corpse, after climbing the first ladder, then going down the staircase
PW1: Follower Javelin - snowfield lower, path back upDropping down after the first snow overhang, follow the right wall around and up a slope, past the Followers
PW1: Follower Sabre - roots above depthsOn a tree branch after climbing down the rope bridge. Rather than hugging a right wall toward a Follower Javelin wielder, drop off to the left.
PW1: Frozen Weapon - snowfield lower, egg zoneDropping down after the first snow overhang, in the rotting bed along the left side
PW1: Heavy Gem - snowfield villageBefore the Millwood Knight tower, on the far side of one of the ruined walls targeted by the archer
PW1: Hollow Gem - beside chapelTo the right of the entrance to the Ariandel
PW1: Homeward Bone - depths, up hillIn the Depths of the Painting, up a hill next to the giant crabs.
PW1: Homeward Bone - snowfield village, outcroppingDropping down after the first snow overhang and following the cliff on the right, making a sharp right after a ruined wall segment before approaching the Millwood Knight tower
PW1: Large Soul of a Weary Warrior - settlement hall roofOn top of the chapel with the Corvian Knight to the left of Vilhelm's building
PW1: Large Soul of a Weary Warrior - snowfield tower, 6FAt the very top of the Millwood Knight tower after climbing up the second ladder, on a Millwood Knight corpse
PW1: Large Soul of an Unknown Traveler - below snowfield village overhangUp the slope to the left of the Millwood Knight tower, dropping down after a snow overhang, then several more ledges.
PW1: Large Soul of an Unknown Traveler - settlement backIn Corvian Settlement, on the ground before the ladder climbing onto the rooftops
PW1: Large Soul of an Unknown Traveler - settlement courtyard, cliffAfter sliding down the slope on the way to Corvian Settlement, on a cliff to the right and behind
PW1: Large Soul of an Unknown Traveler - settlement loop, by bonfireCrossing the bridge after Corvian Settlement bonfire, follow the left edge past another bridge until a dropdown point looping back to the bonfire. On the corpse in a hole in the wall leading back to the bonfire.
PW1: Large Soul of an Unknown Traveler - settlement roofs, balconyAfter climbing the ladder onto Corvian Settlement rooftops, dropping down on a bridge to the left, on the other side of the bridge.
PW1: Large Soul of an Unknown Traveler - settlement, by ladder to bonfireTo the right of the ladder leading up to Corvian Settlement bonfire.
PW1: Large Soul of an Unknown Traveler - snowfield lower, by cliffDropping down after the first snow overhang, between the forest and the cliff edge, before where the large wolf drops down
PW1: Large Soul of an Unknown Traveler - snowfield lower, path back upDropping down after the first snow overhang, follow the right wall around and up a slope, past the Followers
PW1: Large Soul of an Unknown Traveler - snowfield lower, path to villageDropping down after the first snow overhang and following the cliff on the right, on a tree past where the large wolf jumps down
PW1: Large Soul of an Unknown Traveler - snowfield upperGoing straight after the first bonfire, to the left of the caving snow overhand
PW1: Large Titanite Shard - lizard under bridge nearDropped by a Crystal Lizard after the Rope Bridge Cave on the way to Corvian Settlement
PW1: Large Titanite Shard - settlement loop, lizardCrossing the bridge after Corvian Settlement bonfire, follow the left edge past another bridge until a dropdown point looping back to the bonfire. Hug the bonfire building's outer wall along the right side.
PW1: Large Titanite Shard - snowfield lower, left from fallDropping down after the first snow overhang, guarded by a Tree Woman overlooking the rotting bed along the left wall
PW1: Millwood Battle Axe - snowfield tower, 5FIn the Milkwood Knight tower, either dropping down from rafters after climbing the second ladder or making a risky jump
PW1: Millwood Greatarrow - snowfield village, loop back to lowerDropping down after the first snow overhang and following the cliff on the right, making the full loop around, up the slope leading towards where the large wolf drops down
PW1: Millwood Greatbow - snowfield village, loop back to lowerDropping down after the first snow overhang and following the cliff on the right, making the full loop around, up the slope leading towards where the large wolf drops down
PW1: Onyx Blade - library, NPC dropDropped by Sir Vilhelm
PW1: Poison Gem - snowfield upper, forward from bonfireFollowing the left wall from the start, guarded by a Giant Fly
PW1: Rime-blue Moss Clump - below bridge farIn a small alcove to the right after climbing down the rope bridge
PW1: Rime-blue Moss Clump - snowfield upper, overhangOn the first snow overhang at the start. It drops down at the same time you do.
PW1: Rime-blue Moss Clump - snowfield upper, starting caveIn the starting cave
PW1: Rusted Coin - right of libraryTo the right of Vilhelm's building
PW1: Rusted Coin - snowfield lower, straight from fallDropping down after the first snow overhang, shortly straight ahead
PW1: Rusted Gold Coin - settlement roofs, roof near second ladderAfter climbing the second ladder on the Corvian Settlement rooftops, immediately dropping off the bridge to the right, on a rooftop
PW1: Shriving Stone - below bridge nearAfter the Rope Bridge Cave bonfire, dropping down before the bridge, following the ledge all the way to the right
PW1: Simple Gem - settlement, lowest level, behind gateCrossing the bridge after Corvian Settlement bonfire, follow the left edge until a bridge, then drop down on the right side. Guarded by a Sewer Centipede.
PW1: Slave Knight Armor - settlement roofs, drop by ladderIn Corvian Settlement, rather than climbing up a ladder leading to a bridge to the roof of the chapel with the Corvian Knight, dropping down a hole to the left of the ladder into the building below.
PW1: Slave Knight Gauntlets - settlement roofs, drop by ladderIn Corvian Settlement, rather than climbing up a ladder leading to a bridge to the roof of the chapel with the Corvian Knight, dropping down a hole to the left of the ladder into the building below.
PW1: Slave Knight Hood - settlement roofs, drop by ladderIn Corvian Settlement, rather than climbing up a ladder leading to a bridge to the roof of the chapel with the Corvian Knight, dropping down a hole to the left of the ladder into the building below.
PW1: Slave Knight Leggings - settlement roofs, drop by ladderIn Corvian Settlement, rather than climbing up a ladder leading to a bridge to the roof of the chapel with the Corvian Knight, dropping down a hole to the left of the ladder into the building below.
PW1: Snap Freeze - depths, far end, mob dropIn the Depths of the Painting, past the giant crabs, guarded by a special Tree Woman. Killing her drops down a very long nearby ladder.
PW1: Soul of a Crestfallen Knight - settlement hall, raftersIn the rafters of the chapel with the Corvian Knight to the left of Vilhelm's building. Can drop down from the windows exposed to the roof.
PW1: Soul of a Weary Warrior - snowfield tower, 1FAt the bottom of the Millwood Knight tower on a Millwood Knight corpse
PW1: Titanite Slab - CorvianGiven by the Corvian NPC in the building next to Corvian Settlement bonfire.
PW1: Titanite Slab - depths, up secret ladderIn the Depths of the Painting, past the giant crabs, killing a special Tree Woman drops down a very long nearby ladder. Climb the ladder and also the ladder after that one.
PW1: Twinkling Titanite - roots, lizardDropped by a Crystal Lizard in the tree branch area after climbing down the rope bridge, before the ledge with the Follower Javelin wielder
PW1: Twinkling Titanite - settlement roofs, lizard before hallDropped by a Crystal Lizard on a bridge in Corvian Settlement before the rooftop of the chapel with the Corvian Knight inside.
PW1: Twinkling Titanite - snowfield tower, 3F lizardDropped by a Crystal Lizard in the Millwood Knight tower, climbing up the first ladder and descending the stairs down
PW1: Valorheart - boss dropDropped by Champion's Gravetender
PW1: Way of White Corona - settlement hall, by altarIn the chapel with the Corvian Knight to the left of Vilhelm's building, in front of the altar.
PW1: Young White Branch - right of libraryTo the right of Vilhelm's building
PW2: Blood Gem - B2, centerOn the lower level of the Ariandel Chapel basement, in the middle
PW2: Dung Pie - B1On the higher level of the Ariandel Chapel basement, on a wooden beam overlooking the lower level
PW2: Earth Seeker - pit caveIn the area after Snowy Mountain Pass with the giant tree and Earth Seeker Millwood Knight, in the cave
PW2: Ember - pass, central alcoveAfter the Snowy Mountain Pass bonfire, going left of the bell stuck in the ground, in a small alcove along the left wall
PW2: Floating Chaos - NPC dropDropped by Livid Pyromancer Dunnel when he invades while embered, whether boss is defeated or not. On the second level of Priscilla's building above the Gravetender fight, accessed from the lowest level of the Ariandel Chapel basement, past an illusory wall nearly straight left of the mechanism that moves the statue, then carefully dropping down tree branches.
PW2: Follower Shield - pass, far cliffsideAfter the Snowy Mountain Pass bonfire, going left of the bell stuck in the ground, on the cliff ledge past the open area, to the left
PW2: Follower Torch - pass, far side pathOn the way to the Ariandel Chapel basement, where the first wolf enemies reappear, going all the way down the slope on the edge of the map. Guarded by a Follower
PW2: Homeward Bone - rotundaOn the second level of Priscilla's building above the Gravetender fight. Can be accessed from the lowest level of the Ariandel Chapel basement, past an illusory wall nearly straight left of the mechanism that moves the statue, then carefully dropping down tree branches.
PW2: Large Soul of a Crestfallen Knight - pit, by treeIn the area after Snowy Mountain Pass with the giant tree and Earth Seeker Millwood Knight, by the tree
PW2: Large Titanite Shard - pass, far side pathOn the way to the Ariandel Chapel basement, where the first wolf enemies reappear, going partway down the slope on the edge of the map
PW2: Large Titanite Shard - pass, just before B1On the way to Ariandel Chapel basement, past the Millwood Knights and before the first rotten tree that can be knocked down
PW2: Prism Stone - pass, tree by beginningUp the slope and to the left after the Snowy Mountain Pass, straight ahead by a tree
PW2: Pyromancer's Parting Flame - rotundaOn the second level of Priscilla's building above the Gravetender fight. Can be accessed from the lowest level of the Ariandel Chapel basement, past an illusory wall nearly straight left of the mechanism that moves the statue, then carefully dropping down tree branches.
PW2: Quakestone Hammer - pass, side path near B1On the way to Ariandel Chapel basement, rather than going right past the two Millwood Knights, go left, guarded by a very strong Millwood Knight
PW2: Soul of Sister FriedeDropped by Sister Friede
PW2: Soul of a Crestfallen Knight - pit edge #1In the area after Snowy Mountain Pass with the giant tree and Earth Seeker Millwood Knight, along the edge
PW2: Soul of a Crestfallen Knight - pit edge #2In the area after Snowy Mountain Pass with the giant tree and Earth Seeker Millwood Knight, along the edge
PW2: Titanite Chunk - pass, by kickable treeAfter the Snowy Mountain Pass bonfire, on a ledge to the right of the slope with the bell stuck in the ground, behind a tree
PW2: Titanite Chunk - pass, cliff overlooking bonfireOn a cliff overlooking the Snowy Mountain Pass bonfire. Requires following the left wall
PW2: Titanite Slab - boss dropOne-time drop after killing Father Ariandel and Friede (phase 2) for the first time.
PW2: Twinkling Titanite - B3, lizard #1Dropped by a Crystal Lizard past an illusory wall nearly straight left of the mechanism that moves the statue in the lowest level of the Ariandel Chapel basement
PW2: Twinkling Titanite - B3, lizard #2Dropped by a Crystal Lizard past an illusory wall nearly straight left of the mechanism that moves the statue in the lowest level of the Ariandel Chapel basement
PW2: Vilhelm's Armor - B2, along wallOn the lower level of the Ariandel Chapel basement, along a wall to the left of the contraption that turns the statue
PW2: Vilhelm's Gauntlets - B2, along wallOn the lower level of the Ariandel Chapel basement, along a wall to the left of the contraption that turns the statue
PW2: Vilhelm's HelmOn the lower level of the Ariandel Chapel basement, along a wall to the left of the contraption that turns the statue
PW2: Vilhelm's Leggings - B2, along wallOn the lower level of the Ariandel Chapel basement, along a wall to the left of the contraption that turns the statue
RC: Antiquated Plain Garb - wall hidden, before bossIn the chapel before the Midir fight in the Ringed Inner Wall building.
RC: Black Witch Garb - streets gardenGuarded by Alva (invades whether embered or not), partway down the stairs from Shira, across the bridge, and past the Ringed Knight.
RC: Black Witch Hat - streets gardenGuarded by Alva (invades whether embered or not), partway down the stairs from Shira, across the bridge, and past the Ringed Knight.
RC: Black Witch Trousers - streets gardenGuarded by Alva (invades whether embered or not), partway down the stairs from Shira, across the bridge, and past the Ringed Knight.
RC: Black Witch Veil - swamp near right, by sunken churchTo the left of the submerged building with 4 Ringed Knights, near a spear-wielding knight.
RC: Black Witch Wrappings - streets gardenGuarded by Alva (invades whether embered or not), partway down the stairs from Shira, across the bridge, and past the Ringed Knight.
RC: Blessed Gem - grave, down lowest stairsIn Shared Grave, after dropping down near Gael's flag and dropping down again, behind you. Or from the bonfire, go back through the side tunnel with the skeletons and down the stairs after that.
RC: Blindfold Mask - grave, NPC dropDropped by Moaning Knight (invades whether embered or not, or boss defeated or not) in Shared Grave.
RC: Blood of the Dark Soul - end boss dropDropped by Slave Knight Gael
RC: Budding Green Blossom - church pathOn the way to the Halflight building.
RC: Budding Green Blossom - wall top, flowers by stairsIn a patch of flowers to the right of the stairs leading up to the first Judicator along the left wall of the courtyard are Mausoleum Lookout.
RC: Budding Green Blossom - wall top, in flower clusterAlong the left wall of the courtyard after Mausoleum Lookout, in a patch of flowers.
RC: Chloranthy Ring+3 - wall hidden, drop onto statueFrom the mid level of the Ringed Inner Wall elevator that leads to the Midir fight, dropping back down toward the way to Filianore, onto a platform with a Gwyn statue. Try to land on the platform rather than the statue.
RC: Church Guardian Shiv - swamp far left, in buildingInside of the building at the remote end of the muck pit surrounded by praying Hollow Clerics.
RC: Covetous Gold Serpent Ring+3 - streets, by LappGoing up the very long ladder from the muck pit, then up some stairs, to the left, and across the bridge, in a building past the Ringed Knights. Also where Lapp can be found to tell him of the Purging Monument.
RC: Crucifix of the Mad King - ashes, NPC dropDropped by Shira, who invades you (ember not required) in the far-future version of her room
RC: Dark Gem - swamp near, by stairsIn the middle of the muck pit, close to the long stairs.
RC: Divine Blessing - streets monument, mob dropDropped by the Judicator near the Purging Monument area. Requires solving "Show Your Humanity" puzzle.
RC: Divine Blessing - wall top, mob dropDropped by the Judicator after the Mausoleum Lookup bonfire.
RC: Dragonhead Greatshield - lower cliff, under bridgeDown a slope to the right of the bridge where Midir first assaults you, past a sword-wielding Ringed Knight, under the bridge.
RC: Dragonhead Shield - streets monument, across bridgeFound in Purging Monument area, across the bridge from the monument. Requires solving "Show Your Humanity" puzzle.
RC: Ember - wall hidden, statue roomFrom the mid level of the Ringed Inner Wall elevator that leads to the Midir fight, in the room with the illusory statue.
RC: Ember - wall top, by statueAlong the left wall of the courtyard after Mausoleum Lookout, in front of a tall monument.
RC: Ember - wall upper, balconyOn the balcony attached to the room with the Ringed Inner Wall bonfire.
RC: Filianore's Spear Ornament - mid boss dropDropped by Halflight, Spear of the Church
RC: Filianore's Spear Ornament - wall hidden, by ladderNext the ladder leading down to the chapel before the Midir fight in the Ringed Inner Wall building.
RC: Havel's Ring+3 - streets high, drop from building oppositeDropping down from the building where Silver Knight Ledo invades. The building is up the very long ladder from the muck pit, down the path all the way to the right.
RC: Hidden Blessing - swamp center, mob dropDropped by Judicator patrolling the muck pit.
RC: Hidden Blessing - wall top, tomb under platformIn a tomb underneath the platform with the first Judicator, accessed by approaching from Mausoleum Lookout bonfire.
RC: Hollow Gem - wall upper, path to towerHeading down the cursed stairs after Ringed Inner Wall bonfire and another short flight of stairs, hanging on a balcony.
RC: Iron Dragonslayer Armor - swamp far, miniboss dropDropped by Dragonslayer Armour at the far end of the muck pit.
RC: Iron Dragonslayer Gauntlets - swamp far, miniboss dropDropped by Dragonslayer Armour at the far end of the muck pit.
RC: Iron Dragonslayer Helm - swamp far, miniboss dropDropped by Dragonslayer Armour at the far end of the muck pit.
RC: Iron Dragonslayer Leggings - swamp far, miniboss dropDropped by Dragonslayer Armour at the far end of the muck pit.
RC: Lapp's Armor - LappLeft at Lapp's final location in Shared Grave after his quest is complete, or sold by Shrine Handmaid upon killing Lapp.
RC: Lapp's Gauntlets - LappLeft at Lapp's final location in Shared Grave after his quest is complete, or sold by Shrine Handmaid upon killing Lapp.
RC: Lapp's Helm - LappLeft at Lapp's final location in Shared Grave after his quest is complete, or sold by Shrine Handmaid upon killing Lapp.
RC: Lapp's Leggings - LappLeft at Lapp's final location in Shared Grave after his quest is complete, or sold by Shrine Handmaid upon killing Lapp.
RC: Large Soul of a Crestfallen Knight - streets monument, across bridgeFound in Purging Monument area, on the other side of the bridge leading to the monument. Requires solving "Show Your Humanity" puzzle.
RC: Large Soul of a Crestfallen Knight - streets, far stairsToward the bottom of the stairs leading down to the muck pit.
RC: Large Soul of a Weary Warrior - lower cliff, endToward the end of the upper path attacked Midir's fire-breathing.
RC: Large Soul of a Weary Warrior - swamp center, by peninsulaIn the muck pit approaching where the Judicator patrols from the stairs.
RC: Large Soul of a Weary Warrior - wall lower, past two illusory wallsIn the Ringed Inner Wall building coming from Shared Grave, past two illusory walls on the right side of the ascending stairs.
RC: Large Soul of a Weary Warrior - wall top, right of small tombIn the open toward the end of the courtyard after the Mausoleum Lookout bonfire, on the right side of the small tomb.
RC: Ledo's Great Hammer - streets high, opposite building, NPC dropDropped by Silver Knight Ledo (invades whether embered or not, or boss defeated or not) in the building down the path to the right after climbing the very long ladder from the muck area.
RC: Lightning Arrow - wall lower, past three illusory wallsIn the Ringed Inner Wall building coming from Shared Grave, past three illusory walls on the right side of the ascending stairs.
RC: Lightning Gem - grave, room after first dropIn Shared Grave, in the first room encountered after falling down from the crumbling stairs and continuing upward.
RC: Mossfruit - streets near left, path to gardenPartway down the stairs from Shira, across the bridge.
RC: Mossfruit - streets, far left alcoveNear the bottom of the stairs before the muck pit, in an alcove to the left.
RC: Preacher's Right Arm - swamp near right, by towerIn the muck pit behind a crystal-covered structure, close to the Ringed City Streets shortcut entrance.
RC: Prism Stone - swamp near, railing by bonfireOn the balcony of the path leading up to Ringed City Streets bonfire from the muck pit.
RC: Purging Stone - wall top, by door to upperAt the end of the path from Mausoleum Lookup to Ringed Inner Wall, just outside the door.
RC: Ring of the Evil Eye+3 - grave, mimicDropped by mimic in Shared Grave. In one of the rooms after dropping down near Gael's flag and then dropping down again.
RC: Ringed Knight Paired Greatswords - church path, mob dropDropped by Ringed Knight with paired greatswords before Filianore building.
RC: Ringed Knight Spear - streets, down far right hallIn a courtyard guarded by a spear-wielding Ringed Knight. Can be accessed from a hallway filled with cursed clerics on the right side going down the long stairs, or by climbing up the long ladder from the muck pit and dropping down past the Locust Preacher.
RC: Ringed Knight Straight Sword - swamp near, tower on peninsulaOn a monument next to the Ringed City Streets building. Can be easily accessed after unlocking the shortcut by following the left wall inside and then outside the building.
RC: Ritual Spear Fragment - church pathTo the right of the Paired Greatswords Ringed Knight on the way to Halflight.
RC: Rubbish - lower cliff, middleIn the middle of the upper path attacked Midir's fire-breathing, after the first alcove.
RC: Rubbish - swamp far, by crystalIn the remote end of the muck pit, next to a massive crystal structure between a giant tree and the building with praying Hollow Clerics, guarded by several Locust Preachers.
RC: Ruin Armor - wall top, under stairs to bonfireUnderneath the stairs leading down from Mausoleum Lookout.
RC: Ruin Gauntlets - wall top, under stairs to bonfireUnderneath the stairs leading down from Mausoleum Lookout.
RC: Ruin Helm - wall top, under stairs to bonfireUnderneath the stairs leading down from Mausoleum Lookout.
RC: Ruin Leggings - wall top, under stairs to bonfireUnderneath the stairs leading down from Mausoleum Lookout.
RC: Sacred Chime of Filianore - ashes, NPC dropGiven by Shira after accepting her request to kill Midir, or dropped by her in post-Filianore Ringed City.
RC: Shira's Armor - Shira's room after killing ashes NPCFound in Shira's room in Ringed City after killing her in post-Filianore Ringed City.
RC: Shira's Crown - Shira's room after killing ashes NPCFound in Shira's room in Ringed City after killing her in post-Filianore Ringed City.
RC: Shira's Gloves - Shira's room after killing ashes NPCFound in Shira's room in Ringed City after killing her in post-Filianore Ringed City.
RC: Shira's Trousers - Shira's room after killing ashes NPCFound in Shira's room in Ringed City after killing her in post-Filianore Ringed City.
RC: Shriving Stone - wall tower, bottom floor centerIn the cylindrical building before the long stairs with many Harald Legion Knights, in the center structure on the first floor.
RC: Siegbräu - LappGiven by Lapp within the Ringed Inner Wall.
RC: Simple Gem - grave, up stairs after first dropIn Shared Grave, following the path after falling down from the crumbling stairs and continuing upward.
RC: Soul of Darkeater MidirDropped by Darkeater Midir
RC: Soul of Slave Knight GaelDropped by Slave Knight Gael
RC: Soul of a Crestfallen Knight - swamp far, behind crystalBehind a crystal structure at the far end of the muck pit, close to the building with the praying Hollow Clerics before Dragonslayer Armour.
RC: Soul of a Crestfallen Knight - swamp near left, nookIn the muck pit behind all of the Hollow Clerics near the very long ladder.
RC: Soul of a Crestfallen Knight - wall top, under dropAfter dropping down onto the side path on the right side of the Mausoleum Lookout courtyard to where the Crystal Lizard is, behind you.
RC: Soul of a Weary Warrior - lower cliff, by first alcoveIn front of the first alcove providing shelter from Midir's fire-breathing on the way to Shared Grave.
RC: Soul of a Weary Warrior - swamp centerIn the middle of the muck pit where the Judicator is patrolling.
RC: Soul of a Weary Warrior - swamp right, by sunken churchIn between where the Judicator patrols in the muck pit and the submerged building with the 4 Ringed Knights. Provides some shelter from his arrows.
RC: Spears of the Church - hidden boss dropDropped by Darkeater Midir
RC: Titanite Chunk - streets high, building oppositeDown a path past the room where Silver Knight Ledo invades. The building is up the very long ladder from the muck pit, down the path all the way to the right.
RC: Titanite Chunk - streets, near left dropNear the top of the stairs by Shira, dropping down in an alcove to the left.
RC: Titanite Chunk - swamp center, peninsula edgeAlong the edge of the muck pit close to where the Judicator patrols.
RC: Titanite Chunk - swamp far left, up hillUp a hill at the edge of the muck pit with the Hollow Clerics.
RC: Titanite Chunk - swamp near left, by spire topAt the edge of the muck pit, on the opposite side of the wall from the very long ladder.
RC: Titanite Chunk - swamp near right, behind rockAt the very edge of the muck pit, to the left of the submerged building with 4 Ringed Knights.
RC: Titanite Chunk - wall top, among gravesAlong the right edge of the courtyard after Mausoleum Lookout in a cluster of graves.
RC: Titanite Chunk - wall upper, courtyard alcoveIn the courtyard where the first Ringed Knight is seen, along the right wall into an alcove.
RC: Titanite Scale - grave, lizard past first dropDropped by the Crystal Lizard right after the crumbling stairs in Shared Grave.
RC: Titanite Scale - lower cliff, first alcoveIn the first alcove providing shelter from Midir's fire-breathing on the way to Shared Grave.
RC: Titanite Scale - lower cliff, lower pathAfter dropping down from the upper path attacked by Midir's fire-breathing to the lower path.
RC: Titanite Scale - lower cliff, path under bridgePartway down a slope to the right of the bridge where Midir first assaults you.
RC: Titanite Scale - swamp far, by minibossIn the area at the far end of the muck pit with the Dragonslayer Armour.
RC: Titanite Scale - swamp far, lagoon entranceIn the area at the far end of the muck pit with the Dragonslayer Armour.
RC: Titanite Scale - upper cliff, bridgeOn the final bridge where Midir attacks before you knock him off.
RC: Titanite Scale - wall lower, lizardDropped by the Crystal Lizard on the stairs going up from Shared Grave to Ringed Inner Wall elevator.
RC: Titanite Scale - wall top, behind spawnBehind you at the very start of the level.
RC: Titanite Slab - ashes, NPC dropGiven by Shira after defeating Midir, or dropped by her in post-Filianore Ringed City.
RC: Titanite Slab - ashes, mob dropDropped by the Ringed Knight wandering around near Gael's arena
RC: Titanite Slab - mid boss dropDropped by Halflight, Spear of the Church
RC: Twinkling Titanite - church path, left of boss doorDropping down to the left of the door leading to Halflight.
RC: Twinkling Titanite - grave, lizard past first dropDropped by the Crystal Lizard right after the crumbling stairs in Shared Grave.
RC: Twinkling Titanite - streets high, lizardDropped by the Crystal Lizard which runs across the bridge after climbing the very long ladder up from the muck pit.
RC: Twinkling Titanite - swamp near leftAt the left edge of the muck pit coming from the stairs, guarded by a Preacher Locust.
RC: Twinkling Titanite - swamp near right, on sunken churchFollowing the sloped roof of the submerged building with the 4 Ringed Knights, along the back wall
RC: Twinkling Titanite - wall top, lizard on side pathDropped by the first Crystal Lizard on the side path on the right side of the Mausoleum Lookout courtyard
RC: Twinkling Titanite - wall tower, jump from chandelierIn the cylindrical building before the long stairs with many Harald Legion Knights. Carefully drop down to the chandelier in the center, then jump to the second floor. The item is on a ledge.
RC: Violet Wrappings - wall hidden, before bossIn the chapel before the Midir fight in the Ringed Inner Wall building.
RC: White Birch Bow - swamp far left, up hillUp a hill at the edge of the muck pit with the Hollow Clerics.
RC: White Preacher Head - swamp near, nook right of stairsPast the balcony to the right of the Ringed City Streets bonfire room entrance. Can be accessed by dropping down straight after from the bonfire, then around to the left.
RC: Wolf Ring+3 - street gardens, NPC dropDropped by Alva (invades whether embered or not, or boss defeated or not), partway down the stairs from Shira, across the bridge, and past the Ringed Knight.
RC: Young White Branch - swamp far left, by white tree #1Next to a small birch tree at the edge of the muck pit, between the hill with the aggressive Hollow Clerics and the building with the praying Hollow Clerics outside.
RC: Young White Branch - swamp far left, by white tree #2Next to a small birch tree at the edge of the muck pit, between the hill with the aggressive Hollow Clerics and the building with the praying Hollow Clerics outside.
RC: Young White Branch - swamp far left, by white tree #3Next to a small birch tree at the edge of the muck pit, between the hill with the aggressive Hollow Clerics and the building with the praying Hollow Clerics outside.
RS: Blue Bug Pellet - broken stairs by OrbeckOn the broken stairs leading down from Orbeck's area, on the opposite side from Orbeck
RS: Blue Sentinels - HoraceGiven by Horace the Hushed by first "talking" to him, or upon death.
RS: Braille Divine Tome of Carim - drop from bridge to Halfway FortressDropping down before the bridge leading up to Halfway Fortress from Road of Sacrifices, guarded by the maggot belly dog
RS: Brigand Armor - beneath roadIn the middle of the path where the Madwoman waits in Road of Sacrifices
RS: Brigand Axe - beneath roadAt the start of the path leading down to the Madwoman in Road of Sacrifices
RS: Brigand Gauntlets - beneath roadIn the middle of the path where the Madwoman waits in Road of Sacrifices
RS: Brigand Hood - beneath roadIn the middle of the path where the Madwoman waits in Road of Sacrifices
RS: Brigand Trousers - beneath roadIn the middle of the path where the Madwoman waits in Road of Sacrifices
RS: Brigand Twindaggers - beneath roadAt the end of the path guarded by the Madwoman in Road of Sacrifices
RS: Butcher Knife - NPC drop beneath roadDropped by the Butcher Knife-wielding madwoman near the start of Road of Sacrifices
RS: Chloranthy Ring+2 - road, drop across from carriageFound dropping down from the first Storyteller Corvian on the left side rather than the right side. You can then further drop down to where the madwoman is, after healing.
RS: Conjurator Boots - deep waterIn the deep water part of the Crucifixion Woods crab area, between a large tree and the keep wall
RS: Conjurator Hood - deep waterIn the deep water part of the Crucifixion Woods crab area, between a large tree and the keep wall
RS: Conjurator Manchettes - deep waterIn the deep water part of the Crucifixion Woods crab area, between a large tree and the keep wall
RS: Conjurator Robe - deep waterIn the deep water part of the Crucifixion Woods crab area, between a large tree and the keep wall
RS: Crystal Gem - stronghold, lizardDropped by the Crystal Lizard in the building before Crystal Sage
RS: Ember - right of Halfway Fortress entranceOn the ledge with the Corvian with the Storyteller Staff, to the right of the Halfway Fortress entrance
RS: Ember - right of fire behind stronghold left roomBehind the building before Crystal Sage, approached from Crucifixion Woods bonfire. Can drop down on left side or go under bridge on right side
RS: Estus Shard - left of fire behind stronghold left roomBehind the building leading to Crystal Sage, approached from Crucifixion Woods bonfire. Can drop down on left side of go under bridge on right side
RS: Exile Greatsword - NPC drop by Farron KeepDropped by the greatsword-wielding Exile Knight before the ladder down to Farron Keep
RS: Fading Soul - woods by Crucifixion Woods bonfireDropping down from the Crucifixion Woods bonfire toward the Halfway Fortress, guarded by dogs
RS: Fallen Knight Armor - water's edge by Farron KeepOn the edge of the water surrounding the building where you descend into Farron Keep
RS: Fallen Knight Gauntlets - water's edge by Farron KeepOn the edge of the water surrounding the building where you descend into Farron Keep
RS: Fallen Knight Helm - water's edge by Farron KeepOn the edge of the water surrounding the building where you descend into Farron Keep
RS: Fallen Knight Trousers - water's edge by Farron KeepOn the edge of the water surrounding the building where you descend into Farron Keep
RS: Farron Coal - keep perimeterAt the end of the Farron Keep Perimeter building on Crucifixion Woods side, behind the Black Knight
RS: Golden Falcon Shield - path from stronghold right room to Farron KeepHalfway up the stairs to the sorcerer in the building before Crystal Sage, entering from the stairs leading up from the crab area, go straight and follow the path down
RS: Grass Crest Shield - water by Crucifixion Woods bonfireDropping down into the crab area from Crucifixion Woods, on the other side of a tree from the greater crab
RS: Great Club - NPC drop by Farron KeepDropped by the club-wielding Exile Knight before the ladder down to Farron Keep
RS: Great Swamp Pyromancy Tome - deep waterIn the deep water part of the Crucifixion Woods crab area, between a large tree and the keep wall
RS: Great Swamp Ring - miniboss drop, by Farron KeepDropped by Greater Crab in Crucifixion Woods close to the Farron Keep outer wall
RS: Green Blossom - by deep waterIn the Crucifixion Woods crab area out in the open, close to the edge of the deep water area
RS: Green Blossom - water beneath strongholdIn the Crucifixion Woods crab area close to the Crucifixion Woods bonfire, along the left wall of the water area, to the right of the entrance to the building before Crystal Sage
RS: Heretic's Staff - stronghold left roomIn the building before Crystal Sage, entering from near Crucifixion Woods, in a corner under the first stairwell and balcony
RS: Heysel Pick - Heysel dropDropped by Heysel when she invades in Road of Sacrifices
RS: Homeward Bone - balcony by Farron KeepAt the far end of the building where you descend into Farron Keep, by the balcony
RS: Large Soul of an Unknown Traveler - left of stairs to Farron KeepIn the area before you descend into Farron Keep, before the stairs to the far left
RS: Lingering Dragoncrest Ring+1 - waterOn a tree by the greater crab near the Crucifixion Woods bonfire, after the Grass Crest Shield tree
RS: Morne's Ring - drop from bridge to Halfway FortressDropping down before the bridge leading up to Halfway Fortress from Road of Sacrifices, guarded by the maggot belly dog
RS: Ring of Sacrifice - stronghold, drop from right room balconyDrop down from the platform behind the sorcerer in the building before Crystal Sage, entering from the stairs leading up from the crab area
RS: Sage Ring - water beneath strongholdIn an alcove under the building before Crystal Sage, guarded by a Lycanthrope, accessible from the swamp or from dropping down
RS: Sellsword Armor - keep perimeter balconyIn the Farron Keep Perimeter building on Crucifixion Woods side, on the balcony on the right side overlooking the Black Knight
RS: Sellsword Gauntlet - keep perimeter balconyIn the Farron Keep Perimeter building on Crucifixion Woods side, on the balcony on the right side overlooking the Black Knight
RS: Sellsword Helm - keep perimeter balconyIn the Farron Keep Perimeter building on Crucifixion Woods side, on the balcony on the right side overlooking the Black Knight
RS: Sellsword Trousers - keep perimeter balconyIn the Farron Keep Perimeter building on Crucifixion Woods side, on the balcony on the right side overlooking the Black Knight
RS: Sellsword Twinblades - keep perimeterIn the Farron Keep Perimeter building on Crucifixion Woods side, behind and to the right of the Black Knight
RS: Shriving Stone - road, by startDropping down to the left of the first Corvian enemy in Road of Sacrifices
RS: Sorcerer Gloves - water beneath strongholdIn an alcove under the building before Crystal Sage, guarded by a Lycanthrope, accessible from the swamp or from dropping down
RS: Sorcerer Hood - water beneath strongholdIn an alcove under the building before Crystal Sage, guarded by a Lycanthrope, accessible from the swamp or from dropping down
RS: Sorcerer Robe - water beneath strongholdIn an alcove under the building before Crystal Sage, guarded by a Lycanthrope, accessible from the swamp or from dropping down
RS: Sorcerer Trousers - water beneath strongholdIn an alcove under the building before Crystal Sage, guarded by a Lycanthrope, accessible from the swamp or from dropping down
RS: Soul of a Crystal SageDropped by Crystal Sage
RS: Soul of an Unknown Traveler - drop along wall from Halfway FortressFrom Halfway Fortress, hug the right wall and drop down twice on the way to the crab area
RS: Soul of an Unknown Traveler - right of door to stronghold leftOut in the open to the right of the building before Crystal Sage, as entered from Crucifixion Woods bonfire
RS: Soul of an Unknown Traveler - road, by wagonTo the right of the overturned wagon descending from the Road of Sacrifices bonfire
RS: Titanite Shard - road, on bridge after you go underCrossing the bridge you go under after the first Road of Sacrifices bonfire, after a sleeping Corvian and another Corvian guarding the pickup
RS: Titanite Shard - water by Halfway FortressDropping down into the Crucifixion Woods crab area right after Halfway Fortress, on the left wall heading toward the Black Knight building, guarded by dog
RS: Titanite Shard - woods, left of path from Halfway FortressHugging the left wall from Halfway Fortress to Crystal Sage, behind you after the first dropdown
RS: Titanite Shard - woods, surrounded by enemiesHugging the left wall from Halfway Fortress to the Crystal Sage bonfire, after a dropdown surrounded by seven Poisonhorn bugs
RS: Twin Dragon Greatshield - woods by Crucifixion Woods bonfireIn the middle of the area with the Poisonhorn bugs and Lycanthrope Hunters, following the wall where the bugs guard a Titanite Shard
RS: Xanthous Crown - Heysel dropDropped by Heysel when she invades in Road of Sacrifices
SL: Black Iron Greatshield - ruins basement, NPC dropDropped by Knight Slayer Tsorig in Smouldering Lake
SL: Black Knight Sword - ruins main lower, illusory wall in far hallOn the far exit of the Demon Ruins main hall, past an illusory wall, guarded by a Black Knight
SL: Bloodbite Ring+1 - behind ballistaBehind the ballista, overlooking Smouldering Lake
SL: Chaos Gem - antechamber, lizard at end of long hallDropped by the Crystal Lizard found from the Antechamber bonfire, toward the Demon Cleric and to the right, then all the way down
SL: Chaos Gem - lake, far end by mobIn Smouldering Lake along the wall underneath the ballista, all the way to the left past two crabs
SL: Dragonrider Bow - by ladder from ruins basement to ballistaAfter climbing up the ladder after the Black Knight in Demon Ruins, falling back down to a ledge
SL: Ember - ruins basement, in lavaIn the lava pit under the Black Knight, by Knight Slayer Tsorig
SL: Ember - ruins main lower, path to antechamberGoing down the stairs from the Antechamber bonfire, to the right, at the end of the short hallway to the next right
SL: Ember - ruins main upper, hall end by holeIn the Demon Ruins, hugging the right wall from the Demon Ruins bonfire, or making a jump from the illusory hall corridor from Antechamber bonfire
SL: Ember - ruins main upper, just after entranceBehind the first Demon Cleric from the Demon Ruins bonfire
SL: Estus Shard - antechamber, illusory wallBehind an illusory wall and Smouldering Writhing Flesh-filled corridor from Antechamber bonfire
SL: Flame Stoneplate Ring+2 - ruins main lower, illusory wall in far hallOn the far exit of the Demon Ruins main hall, past an illusory wall, past the Black Knight, hidden in a corner
SL: Fume Ultra Greatsword - ruins basement, NPC dropDropped by Knight Slayer Tsorig in Smouldering Lake
SL: Homeward Bone - path to ballistaIn the area targeted by the ballista after the long ladder guarded by the Black Knight, before the Bonewheel Skeletons
SL: Izalith Pyromancy Tome - antechamber, room near bonfireIn the room straight down from the Antechamber bonfire, past a Demon Cleric, surrounded by many Ghrus.
SL: Izalith Staff - ruins basement, second illusory wall behind chestPast an illusory wall to the left of the Large Hound Rat in Demon Ruins, and then past another illusory wall, before the basilisk area
SL: Knight Slayer's Ring - ruins basement, NPC dropDropped by Knight Slayer Tsorig after invading in the Catacombs
SL: Large Titanite Shard - lake, by entranceIn the middle of Smouldering Lake, close to the Abandoned Tomb
SL: Large Titanite Shard - lake, by minibossIn the middle of Smouldering Lake, under the Carthus Sandworm
SL: Large Titanite Shard - lake, by tree #1In the middle of Smouldering Lake, by a tree before the hallway to the pit
SL: Large Titanite Shard - lake, by tree #2In the middle of Smouldering Lake, by a tree before the hallway to the pit
SL: Large Titanite Shard - lake, straight from entranceIn the middle of Smouldering Lake, in between Abandoned Tomb and Demon Ruins
SL: Large Titanite Shard - ledge by Demon Ruins bonfireOn a corpse hanging off the ledge outside the Demon Ruins bonfire
SL: Large Titanite Shard - ruins basement, illusory wall in upper hallIn a chest past an illusory wall to the left of the Large Hound Rat in Demon Ruins, before the basilisk area
SL: Large Titanite Shard - side lake #1In the Smouldering Lake pit where Horace can be found, following the right wall from Abandoned Tomb
SL: Large Titanite Shard - side lake #2In the Smouldering Lake pit where Horace can be found, following the right wall from Abandoned Tomb
SL: Lightning Stake - lake, miniboss dropDropped by the giant Carthus Sandworm
SL: Llewellyn Shield - Horace dropDropped by Horace the Hushed upon death or quest completion.
SL: Quelana Pyromancy Tome - ruins main lower, illusory wall in grey roomAt the far end of the Demon Ruins main hall to the right, where the rats are, then another right and past the illusory wall
SL: Sacred Flame - ruins basement, in lavaIn the lava pit under the Black Knight, by Knight Slayer Tsorig
SL: Shield of Want - lake, by minibossIn the middle of Smouldering Lake, under the Carthus Sandworm
SL: Soul of a Crestfallen Knight - ruins basement, above lavaNext to the Black Knight in Demon Ruins
SL: Soul of the Old Demon KingDropped by Old Demon King in Smouldering Lake
SL: Speckled Stoneplate Ring - lake, ballista breaks bricksBehind a destructible wall in Smouldering Lake which the ballista has to destroy
SL: Titanite Chunk - path to side lake, lizardDropped by the second Crystal Lizard in the cave leading to the pit where Horace can be found in Smouldering Lake
SL: Titanite Scale - ruins basement, path to lavaIn the area with Basilisks on the way to the ballista
SL: Toxic Mist - ruins main lower, in lavaAt the far end of the Demon Ruins main hall to the right, where the rats are, then another right and past the illusory wall, in the middle of the lava pit.
SL: Twinkling Titanite - path to side lake, lizardDropped by the first Crystal Lizard in the cave leading to the pit where Horace can be found in Smouldering Lake
SL: Undead Bone Shard - lake, miniboss dropDropped by the giant Carthus Sandworm
SL: Undead Bone Shard - ruins main lower, left after stairsIn the close end of the Demon Ruins main hall, right below a Smouldering Writhing Flesh
SL: White Hair Talisman - ruins main lower, in lavaAt the far end of the Demon Ruins main hall to the right, where the rats are, then another right and past the illusory wall, at the far end of the lava pit.
SL: Yellow Bug Pellet - side lakeIn the Smouldering Lake pit where Horace can be found, following the right wall from Abandoned Tomb
UG: Ashen Estus Ring - swamp, path opposite bonfireIn the coffin similar to your initial spawn location, guarded by Corvians
UG: Black Knight Glaive - boss arenaIn the Champion Gundyr boss area
UG: Blacksmith Hammer - shrine, Andre's roomWhere Andre sits in Firelink Shrine
UG: Chaos Blade - environs, left of shrineWhere Sword Master is in Firelink Shrine
UG: Coiled Sword Fragment - shrine, dead bonfireIn the dead Firelink Shrine bonfire
UG: Ember - shopSold by Untended Graves Handmaid
UG: Eyes of a Fire Keeper - shrine, Irina's roomBehind an illusory wall, in the same location Irina sits in Firelink Shrine
UG: Hidden Blessing - cemetery, behind coffinBehind the coffin that had a Titanite Shard in Cemetery of Ash
UG: Hornet Ring - environs, right of main path after killing FK bossOn a cliffside to the right of the main path leading up to dark Firelink Shrine, after Abyss Watchers is defeated.
UG: Life Ring+3 - shrine, behind big throneBehind Prince Lothric's throne
UG: Priestess Ring - shopSold or dropped by Untended Graves Handmaid. Killing her is not recommended
UG: Ring of Steel Protection+1 - environs, behind bell towerBehind Bell Tower to the right
UG: Shriving Stone - swamp, by bonfireAt the very start of the area
UG: Soul of Champion GundyrDropped by Champion Gundyr
UG: Soul of a Crestfallen Knight - environs, above shrine entranceAbove the Firelink Shrine entrance, up the stairs/slope from either left or right of the entrance
UG: Soul of a Crestfallen Knight - swamp, centerClose to where Ashen Estus Flask was in Cemetery of Ash
UG: Titanite Chunk - swamp, left path by fountainIn a path to the left of where Ashen Estus Flask was in Cemetery of Ash
UG: Titanite Chunk - swamp, right path by fountainIn a path to the right of where Ashen Estus Flask was in Cemetery of Ash
UG: Wolf Knight Armor - shop after killing FK bossSold by Untended Graves Handmaid after defeating Abyss Watchers
UG: Wolf Knight Gauntlets - shop after killing FK bossSold by Untended Graves Handmaid after defeating Abyss Watchers
UG: Wolf Knight Helm - shop after killing FK bossSold by Untended Graves Handmaid after defeating Abyss Watchers
UG: Wolf Knight Leggings - shop after killing FK bossSold by Untended Graves Handmaid after defeating Abyss Watchers
US: Alluring Skull - foot, behind carriageGuarded by two dogs after the Foot of the High Wall bonfire
US: Alluring Skull - on the way to tower, behind buildingAfter the ravine bridge leading to Eygon and the Giant's tower, wrapping around the building to the right.
US: Alluring Skull - tower village building, upstairsUp the stairs of the building with Cage Spiders after the Fire Demon, before the dogs
US: Bloodbite Ring - miniboss in sewerDropped by the large rat in the sewers with grave access
US: Blue Wooden Shield - graveyard by white treeAfter Dilapidated Bridge bonfire, in the back of the Giant's arrow area. Guarded by a flamberge-wielding thrall.
US: Caduceus Round Shield - right after stable exitAfter exiting the building across the bridge to the right of the first Undead Settlement building, to the left
US: Caestus - sewerIn the tunnel with the Giant Hound Rat and Grave Key door, from the ravine bridge toward Dilapidated Bridge bonfire
US: Charcoal Pine Bundle - first building, bottom floorDown the stairs in the first building
US: Charcoal Pine Bundle - first building, middle floorOn the bottom floor of the first building
US: Charcoal Pine Resin - hanging corpse roomIn the building after the burning tree and Cathedral Evangelist, in the room with the many hanging corpses
US: Chloranthy Ring - tower village, jump from roofAt the end of the Fire Demon loop, in the tower where you have to drop down after the roof
US: Cleric Blue Robe - graveyard by white treeAfter Dilapidated Bridge bonfire, in the back of the Giant's arrow area. Guarded by a flamberge-wielding thrall.
US: Cleric Gloves - graveyard by white treeAfter Dilapidated Bridge bonfire, in the back of the Giant's arrow area. Guarded by a flamberge-wielding thrall.
US: Cleric Hat - graveyard by white treeAfter Dilapidated Bridge bonfire, in the back of the Giant's arrow area. Guarded by a flamberge-wielding thrall.
US: Cleric Trousers - graveyard by white treeAfter Dilapidated Bridge bonfire, in the back of the Giant's arrow area. Guarded by a flamberge-wielding thrall.
US: Cornyx's Garb - by Cornyx's cage after Cuculus questAppears next to Cornyx's cage after defeating Old Demon King with Cuculus surviving
US: Cornyx's Garb - kill CornyxDropped by Cornyx
US: Cornyx's Skirt - by Cornyx's cage after Cuculus questAppears next to Cornyx's cage after defeating Old Demon King with Cuculus surviving
US: Cornyx's Skirt - kill CornyxDropped by Cornyx
US: Cornyx's Wrap - by Cornyx's cage after Cuculus questAppears next to Cornyx's cage after defeating Old Demon King with Cuculus surviving
US: Cornyx's Wrap - kill CornyxDropped by Cornyx
US: Covetous Silver Serpent Ring+2 - tower village, drop down from roofAt the back of a roof near the end of the Fire Demon loop, dropping down past where Flynn's Ring is
US: Ember - behind burning treeBehind the burning tree with the Cathedral Evangelist
US: Ember - bridge on the way to towerOn the ravine bridge leading toward Eygon and the Giant's tower
US: Ember - by stairs to bossNext to the stairs leading up to Curse-Rotted Greatwood fight, near a tree guarded by a dog
US: Ember - by white treeNear the Birch Tree where giant shoots arrows
US: Ember - tower basement, minibossIn the room with the Outrider Knight
US: Estus Shard - under burning treeIn front of the burning tree guarded by the Cathedral Evangelist
US: Fading Soul - by white treeNear the Birch Tree where giant shoots arrows
US: Fading Soul - outside stableIn the thrall area to the right of the bridge to the right of the burning tree with the Cathedral Evangelist
US: Fire Clutch Ring - wooden walkway past stableFrom the area bombarded by firebombs above the Cliff Underside bonfire
US: Fire Gem - tower village, miniboss dropDropped by the Fire Demon you fight with Siegward
US: Firebomb - stable roofIn the thrall area across the bridge from the first Undead Settlement building, on a rooftop overlooking the Cliff Underside area.
US: Flame Stoneplate Ring - hanging corpse by Mound-Maker transportOn a hanging corpse in the area with the Pit of Hollows cage manservant, after the thrall area, overlooking the entrance to the Giant's tower.
US: Flynn's Ring - tower village, rooftopOn the roof toward the end of the Fire Demon loop, past the Cathedral Evangelists
US: Great Scythe - building by white tree, balconyOn the balcony of the building before Curse-Rotted Greatwood, coming from Dilapidated Bridge bonfire
US: Hand Axe - by CornyxNext to Cornyx's cell
US: Hawk Ring - Giant ArcherDropped by Giant, either by killing him or collecting all of the birch tree items locations in the base game.
US: Heavy Gem - HawkwoodGiven or dropped by Hawkwood after defeating Curse-Rotted Greatwood or Crystal Sage
US: Heavy Gem - chasm, lizardDrop by Crystal Lizard in ravine accessible by Grave Key or dropping down near Eygon.
US: Homeward Bone - foot, drop overlookUnder Foot of the High Wall bonfire, around where Yoel can be first met
US: Homeward Bone - stable roofIn the thrall area across the bridge from the first Undead Settlement building, on a roof overlooking the ravine bridge.
US: Homeward Bone - tower village, jump from roofAt the end of the loop from the Siegward Demon fight, after dropping down from the roof onto the tower with Chloranthy Ring, to the right of the tower entrance
US: Homeward Bone - tower village, right at startUnder Foot of the High Wall bonfire, around where Yoel can be first met
US: Human Pine Resin - tower village building, chest upstairsIn a chest after Fire Demon. Cage Spiders activate open opening it.
US: Irithyll Straight Sword - miniboss drop, by Road of SacrificesDropped by the Boreal Outright Knight before Road of Sacrifices
US: Kukri - hanging corpse above burning treeHanging corpse high above the burning tree with the Cathedral Evangelist. Must be shot down with an arrow or projective.
US: Large Club - tower village, by minibossIn the Fire Demon area
US: Large Soul of a Deserted Corpse - across from Foot of the High WallOn the opposite tower from the Foot of the High Wall bonfire
US: Large Soul of a Deserted Corpse - around corner by Cliff UndersideAfter going up the stairs from Curse-Rotted Greatwood to Cliff Underside area, on a cliff edge to the right
US: Large Soul of a Deserted Corpse - by white treeNear the Birch Tree where giant shoots arrows
US: Large Soul of a Deserted Corpse - hanging corpse room, over stairsOn a hanging corpse in the building after the burning tree. Can be knocked down by dropping onto the stairs through the broken railing.
US: Large Soul of a Deserted Corpse - on the way to tower, by wellAfter the ravine bridge leading toward Eygon and the Giant's tower, next to the well to the right
US: Large Soul of a Deserted Corpse - stableIn the building with stables across the bridge and to the right from the first Undead Settlement building
US: Life Ring+1 - tower on the way to villageOn the wooden rafters near where Siegward is waiting for Fire Demon
US: Loincloth - by Velka statueNext to the Velka statue. Requires Grave Key or dropping down near Eygon and backtracking through the skeleton area.
US: Loretta's Bone - first building, hanging corpse on balconyOn a hanging corpse after the first building, can be knocked down by rolling into it
US: Mirrah Gloves - tower village, jump from roofAt the end of the Fire Demon loop, in the tower where you have to drop down after the roof
US: Mirrah Trousers - tower village, jump from roofAt the end of the Fire Demon loop, in the tower where you have to drop down after the roof
US: Mirrah Vest - tower village, jump from roofAt the end of the Fire Demon loop, in the tower where you have to drop down after the roof
US: Mortician's Ashes - graveyard by white treeIn the area past the Dilapidated Bridge bonfire, where the Giant is shooting arrows, at the close end of the graveyard
US: Mound-makers - HodrickGiven by Hodrick if accessing the Pit of Hollows before fighting Curse-Rotted Greatwood, or dropped after invading him with Sirris.
US: Northern Armor - tower village, hanging corpseHanging corpse in the Fire Demon fight area, can be knocked down by rolling into it
US: Northern Gloves - tower village, hanging corpseHanging corpse in the Fire Demon fight area, can be knocked down by rolling into it
US: Northern Helm - tower village, hanging corpseHanging corpse in the Fire Demon fight area, can be knocked down by rolling into it
US: Northern Trousers - tower village, hanging corpseHanging corpse in the Fire Demon fight area, can be knocked down by rolling into it
US: Old Sage's Blindfold - kill CornyxDropped by Cornyx
US: Pale Tongue - tower village, hanging corpseHanging corpse in the Fire Demon fight area, can be knocked down by rolling into it
US: Partizan - hanging corpse above Cliff UndersideOn a hanging corpse on the path from Cliff Underside to Cornyx's cage. Must be shot down with an arrow or projective.
US: Plank Shield - outside stable, by NPCIn the thrall area across the bridge from the first Undead Settlement building, on a cliff edge overlooking the ravine bridge.
US: Poisonbite Ring+1 - graveyard by white tree, near wellBehind the well in the back of area where the Giant shoots arrows, nearby where the flamberge-wielding thrall drops down.
US: Pyromancy Flame - CornyxGiven by Cornyx in Firelink Shrine or dropped.
US: Red Bug Pellet - tower village building, basementOn the floor of the building after the Fire Demon encounter
US: Red Hilted Halberd - chasm cryptIn the skeleton area accessible from Grave Key or dropping down from near Eygon
US: Red and White Shield - chasm, hanging corpseOn a hanging corpse in the ravine accessible with the Grave Key or dropping down near Eygon, to the entrance of Irina's prison. Must be shot down with an arrow or projective.
US: Reinforced Club - by white treeNear the Birch Tree where giant shoots arrows
US: Repair Powder - first building, balconyOn the balcony of the first Undead Settlement building
US: Rusted Coin - awning above Dilapidated BridgeOn a wooden ledge near the Dilapidated Bridge bonfire. Must be jumped to from near Cathedral Evangelist enemy
US: Saint's Talisman - chasm, by ladderFrom the ravine accessible via Grave Key or dropping near Eygon, before ladder leading up to Irina of Carim
US: Sharp Gem - lizard by Dilapidated BridgeDrop by Crystal Lizard near Dilapidated Bridge bonfire.
US: Siegbräu - SiegwardGiven by Siegward after helping him defeat the Fire Demon.
US: Small Leather Shield - first building, hanging corpse by entranceHanging corpse in the first building, to the right of the entrance
US: Soul of a Nameless Soldier - top of towerAt the top of the tower where Giant shoots arrows
US: Soul of an Unknown Traveler - back alley, past cratesAfter exiting the building after the burning tree on the way to the Dilapidated Bridge bonfire. Hidden behind some crates between two buildings on the right.
US: Soul of an Unknown Traveler - chasm cryptIn the skeleton area accessible Grave Key or dropping down from near Eygon
US: Soul of an Unknown Traveler - pillory past stableIn the area bombarded by firebombs above the Cliff Underside bonfire
US: Soul of an Unknown Traveler - portcullis by burning treeBehind a grate to the left of the burning tree and Cathedral Evangelist
US: Soul of the Rotted GreatwoodDropped by Curse Rotted Greatwood
US: Spotted Whip - by Cornyx's cage after Cuculus questAppears next to Cornyx's cage after defeating Old Demon King with Cuculus surviving
US: Sunset Armor - pit of hollows after killing Hodrick w/SirrisFound in Pit of Hollows after completing Sirris' questline.
US: Sunset Gauntlets - pit of hollows after killing Hodrick w/SirrisFound in Pit of Hollows after completing Sirris' questline.
US: Sunset Helm - Pit of Hollows after killing Hodrick w/SirrisFound in Pit of Hollows after completing Sirris' questline.
US: Sunset Leggings - pit of hollows after killing Hodrick w/SirrisFound in Pit of Hollows after completing Sirris' questline.
US: Titanite Shard - back alley, side pathOn a side path to the right of the Cathedral Evangelist before the Dilapidated Bridge bonfire
US: Titanite Shard - back alley, up ladderNext to the Cathedral Evangelist close to the Dilapidated Bridge bonfire
US: Titanite Shard - chasm #1In the ravine accessible from Grave Key or dropping down from near Eygon
US: Titanite Shard - chasm #2In the ravine accessible from Grave Key or dropping down from near Eygon
US: Titanite Shard - lower path to Cliff UndersideAt the end of the cliffside path next to Cliff Underside bonfire, guarded by a Hollow Peasant wielding a four-pronged plow.
US: Titanite Shard - porch after burning treeIn front of the building after the burning tree and Cathedral Evangelist
US: Tower Key - kill IrinaDropped by Irina of Carim
US: Transposing Kiln - boss dropDropped by Curse Rotted Greatwood
US: Undead Bone Shard - by white treeIn the area past the Dilapidated Bridge bonfire, where the Giant is shooting arrows, jumping to the floating platform on the right
US: Wargod Wooden Shield - Pit of HollowsIn the Pit of Hollows
US: Warrior of Sunlight - hanging corpse room, drop through holeDropping through a hole in the floor in the first building after the burning tree.
US: Whip - back alley, behind wooden wallIn one of the houses between building after the burning tree and the Dilapidated Bridge bonfire
US: Young White Branch - by white tree #1Near the Birch Tree where giant shoots arrows
US: Young White Branch - by white tree #2Near the Birch Tree where giant shoots arrows
+ diff --git a/worlds/dark_souls_3/docs/setup_en.md b/worlds/dark_souls_3/docs/setup_en.md index 61215dbc6043..ed90289a8baf 100644 --- a/worlds/dark_souls_3/docs/setup_en.md +++ b/worlds/dark_souls_3/docs/setup_en.md @@ -7,48 +7,49 @@ ## Optional Software -- [Dark Souls III Maptracker Pack](https://github.com/Br00ty/DS3_AP_Maptracker/releases/latest), for use with [Poptracker](https://github.com/black-sliver/PopTracker/releases) +- Map tracker not yet updated for 3.0.0 -## General Concept +## Setting Up - -**This mod can ban you permanently from the FromSoftware servers if used online.** - -The Dark Souls III AP Client is a dinput8.dll triggered when launching Dark Souls III. This .dll file will launch a command -prompt where you can read information about your run and write any command to interact with the Archipelago server. +First, download the client from the link above. It doesn't need to go into any particular directory; +it'll automatically locate _Dark Souls III_ in your Steam installation folder. -This client has only been tested with the Official Steam version of the game at version 1.15. It does not matter which DLCs are installed. However, you will have to downpatch your Dark Souls III installation from current patch. +Version 3.0.0 of the randomizer _only_ supports the latest version of _Dark Souls III_, 1.15.2. This +is the latest version, so you don't need to do any downpatching! However, if you've already +downpatched your game to use an older version of the randomizer, you'll need to reinstall the latest +version before using this version. -## Downpatching Dark Souls III +### One-Time Setup -To downpatch DS3 for use with Archipelago, use the following instructions from the speedsouls wiki database. +Before you first connect to a multiworld, you need to generate the local data files for your world's +randomized item and (optionally) enemy locations. You only need to do this once per multiworld. -1. Launch Steam (in online mode). -2. Press the Windows Key + R. This will open the Run window. -3. Open the Steam console by typing the following string: `steam://open/console`. Steam should now open in Console Mode. -4. Insert the string of the depot you wish to download. For the AP-supported v1.15, you will want to use: `download_depot 374320 374321 4471176929659548333`. -5. Steam will now download the depot. Note: There is no progress bar for the download in Steam, but it is still downloading in the background. -6. Back up your existing game executable (`DarkSoulsIII.exe`) found in `\Steam\steamapps\common\DARK SOULS III\Game`. Easiest way to do this is to move it to another directory. If you have file extensions enabled, you can instead rename the executable to `DarkSoulsIII.exe.bak`. -7. Return to the Steam console. Once the download is complete, it should say so along with the temporary local directory in which the depot has been stored. This is usually something like `\Steam\steamapps\content\app_XXXXXX\depot_XXXXXX`. -8. Take the `DarkSoulsIII.exe` from that folder and place it in `\Steam\steamapps\common\DARK SOULS III\Game`. -9. Back up and delete your save file (`DS30000.sl2`) in AppData. AppData is hidden by default. To locate it, press Windows Key + R, type `%appdata%` and hit enter. Alternatively: open File Explorer > View > Hidden Items and follow `C:\Users\\AppData\Roaming\DarkSoulsIII\`. -10. If you did all these steps correctly, you should be able to confirm your game version in the upper-left corner after launching Dark Souls III. +1. Before you first connect to a multiworld, run `randomizer\DS3Randomizer.exe`. +2. Put in your Archipelago room address (usually something like `archipelago.gg:12345`), your player + name (also known as your "slot name"), and your password if you have one. -## Installing the Archipelago mod +3. Click "Load" and wait a minute or two. -Get the `dinput8.dll` from the [Dark Souls III AP Client](https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/releases) and -add it at the root folder of your game (e.g. `SteamLibrary\steamapps\common\DARK SOULS III\Game`) +### Running and Connecting the Game -## Joining a MultiWorld Game +To run _Dark Souls III_ in Archipelago mode: -1. Run Steam in offline mode to avoid being banned. -2. Launch Dark Souls III. -3. Type in `/connect {SERVER_IP}:{SERVER_PORT} {SLOT_NAME} password:{PASSWORD}` in the "Windows Command Prompt" that opened. For example: `/connect archipelago.gg:38281 "Example Name" password:"Example Password"`. The password parameter is only necessary if your game requires one. -4. Once connected, create a new game, choose a class and wait for the others before starting. -5. You can quit and launch at anytime during a game. +1. Start Steam. **Do not run in offline mode.** The mod will make sure you don't connect to the + DS3 servers, and running Steam in offline mode will make certain scripted invaders fail to spawn. -## Where do I get a config file? +2. Run `launchmod_darksouls3.bat`. This will start _Dark Souls III_ as well as a command prompt that + you can use to interact with the Archipelago server. + +3. Type `/connect {SERVER_IP}:{SERVER_PORT} {SLOT_NAME}` into the command prompt, with the + appropriate values filled in. For example: `/connect archipelago.gg:24242 PlayerName`. + +4. Start playing as normal. An "Archipelago connected" message will appear onscreen once you have + control of your character and the connection is established. + +## Frequently Asked Questions + +### Where do I get a config file? The [Player Options](/games/Dark%20Souls%20III/player-options) page on the website allows you to configure your personal options and export them into a config file. diff --git a/worlds/dark_souls_3/test/TestDarkSouls3.py b/worlds/dark_souls_3/test/TestDarkSouls3.py new file mode 100644 index 000000000000..e590cd732b41 --- /dev/null +++ b/worlds/dark_souls_3/test/TestDarkSouls3.py @@ -0,0 +1,27 @@ +from test.TestBase import WorldTestBase + +from worlds.dark_souls_3.Items import item_dictionary +from worlds.dark_souls_3.Locations import location_tables +from worlds.dark_souls_3.Bosses import all_bosses + +class DarkSouls3Test(WorldTestBase): + game = "Dark Souls III" + + def testLocationDefaultItems(self): + for locations in location_tables.values(): + for location in locations: + if location.default_item_name: + self.assertIn(location.default_item_name, item_dictionary) + + def testLocationsUnique(self): + names = set() + for locations in location_tables.values(): + for location in locations: + self.assertNotIn(location.name, names) + names.add(location.name) + + def testBossLocations(self): + all_locations = {location.name for locations in location_tables.values() for location in locations} + for boss in all_bosses: + for location in boss.locations: + self.assertIn(location, all_locations) diff --git a/worlds/dark_souls_3/test/__init__.py b/worlds/dark_souls_3/test/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 From 6e41c6067208d0286f56694f4281d03a0aaf6dad Mon Sep 17 00:00:00 2001 From: Mysteryem Date: Fri, 9 Aug 2024 13:13:01 +0100 Subject: [PATCH 21/98] Core: Check parent_region.can_reach first in Location.can_reach (#3724) * Core: Check parent_region.can_reach first in Location.can_reach The comment about self.access_rule computing faster on average appears to no longer be correct with the current caching system for region accessibility, resulting in self.parent_region.can_reach computing faster on average. Generation of template yamls for each game that does not require a rom to generate, generated with `python -O .\Generate.py --seed 1` (all durations averaged over at 4 or 5 generations): Full generation with `spoiler: 1` and no progression balancing: 89.9s -> 72.6s Only output from above case: 2.6s -> 2.2s Full generation with `spoiler: 3` and no progression balancing: 769.9s -> 627.1s Only playthrough calculation + paths from above case: 680.5s -> 555.3s Full generation with `spoiler: 1` with default progression balancing: 123.5s -> 98.3s Only progression balancing from above case: 11.3s -> 9.6s * Update BaseClasses.py * Update BaseClasses.py * Update BaseClasses.py --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- BaseClasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 81601506d084..34e7248415f6 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1128,9 +1128,9 @@ def can_fill(self, state: CollectionState, item: Item, check_access=True) -> boo and (not check_access or self.can_reach(state)))) def can_reach(self, state: CollectionState) -> bool: - # self.access_rule computes faster on average, so placing it first for faster abort + # Region.can_reach is just a cache lookup, so placing it first for faster abort on average assert self.parent_region, "Can't reach location without region" - return self.access_rule(state) and self.parent_region.can_reach(state) + return self.parent_region.can_reach(state) and self.access_rule(state) def place_locked_item(self, item: Item): if self.item: From 30f97dd7dead5cad3a9d7a1eecf3737a210098c7 Mon Sep 17 00:00:00 2001 From: Mysteryem Date: Fri, 9 Aug 2024 13:25:39 +0100 Subject: [PATCH 22/98] Core: Speed up CollectionState.copy() using built-in copy methods (#3678) All the types being copied are built-in types with their own `copy()` methods, so using the `copy` module was a bit overkill and also slower. This patch replaces the use of the `copy` module in `CollectionState.copy()` with using the built-in `.copy()` methods. The copying of `reachable_regions` and `blocked_connections` was also iterating the keys of each dictionary and then looking up the value in the dictionary for that key. It is faster, and I think more readable, to iterate the dictionary's `.items()` instead. For me, when generating a multiworld including the template yaml of every world with `python -O .\Generate.py --skip_output`, this patch saves about 2.1s. The overall generation duration for these yamls varies quite a lot, but averages around 160s for me, so on average this patch reduced overall generation duration (excluding output duration) by around 1.3%. Timing comparisons were made by calling time.perf_counter() at the start and end of `CollectionState.copy()`'s body, and summing the differences between the starts and ends of the method body into a global variable that was printed at the end of generation. Additional timing comparisons were made, using the `timeit` module, of the individual function calls or dictionary comprehensions used to perform the copying. The main performance cost was `copy.deepcopy()`, which gets slow as the number of keys multiplied by the number of values within the sets/Counters gets large, e.g., to deepcopy a `dict[int, Counter[str]]` with 100 keys and where each Counter contains 100 keys was 30x slower than most other tested copying methods. Increasing the number of dict keys or Counter keys only makes it slower. --- BaseClasses.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 34e7248415f6..092f330bcbb4 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1,7 +1,6 @@ from __future__ import annotations import collections -import copy import itertools import functools import logging @@ -719,14 +718,14 @@ def update_reachable_regions(self, player: int): def copy(self) -> CollectionState: ret = CollectionState(self.multiworld) - ret.prog_items = copy.deepcopy(self.prog_items) - ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in - self.reachable_regions} - ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in - self.blocked_connections} - ret.events = copy.copy(self.events) - ret.path = copy.copy(self.path) - ret.locations_checked = copy.copy(self.locations_checked) + ret.prog_items = {player: counter.copy() for player, counter in self.prog_items.items()} + ret.reachable_regions = {player: region_set.copy() for player, region_set in + self.reachable_regions.items()} + ret.blocked_connections = {player: entrance_set.copy() for player, entrance_set in + self.blocked_connections.items()} + ret.events = self.events.copy() + ret.path = self.path.copy() + ret.locations_checked = self.locations_checked.copy() for function in self.additional_copy_functions: ret = function(self, ret) return ret From ac7590e621be1662e9522022670c5949e0195cfa Mon Sep 17 00:00:00 2001 From: Mysteryem Date: Fri, 9 Aug 2024 16:02:41 +0100 Subject: [PATCH 23/98] HK: fix iterating all worlds instead of only HK worlds in stage_pre_fill (#3750) Would cause generation to fail when generating with HK and another game. Mistake in 6803c373e5ff. --- worlds/hk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index 99277378a162..cbb909606127 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -467,7 +467,7 @@ def set_goal(player, grub_rule: typing.Callable[[CollectionState], bool]): worlds = [world for world in multiworld.get_game_worlds(cls.game) if world.options.Goal in ["any", "grub_hunt"]] if worlds: grubs = [item for item in multiworld.get_items() if item.name == "Grub"] - all_grub_players = [world.player for world in multiworld.worlds.values() if world.options.GrubHuntGoal == GrubHuntGoal.special_range_names["all"]] + all_grub_players = [world.player for world in worlds if world.options.GrubHuntGoal == GrubHuntGoal.special_range_names["all"]] if all_grub_players: group_lookup = defaultdict(set) From c66a8605da1ba1aaaaac57bc2722ee1b585d4c5d Mon Sep 17 00:00:00 2001 From: Kaito Sinclaire Date: Fri, 9 Aug 2024 08:04:59 -0700 Subject: [PATCH 24/98] DOOM, DOOM II: Update steam URLs (#3746) --- worlds/doom_1993/docs/setup_en.md | 2 +- worlds/doom_ii/docs/setup_en.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/doom_1993/docs/setup_en.md b/worlds/doom_1993/docs/setup_en.md index 8906efac9cea..5d96e6a8056e 100644 --- a/worlds/doom_1993/docs/setup_en.md +++ b/worlds/doom_1993/docs/setup_en.md @@ -2,7 +2,7 @@ ## Required Software -- [DOOM 1993 (e.g. Steam version)](https://store.steampowered.com/app/2280/DOOM_1993/) +- [DOOM 1993 (e.g. Steam version)](https://store.steampowered.com/app/2280/DOOM__DOOM_II/) - [Archipelago Crispy DOOM](https://github.com/Daivuk/apdoom/releases) ## Optional Software diff --git a/worlds/doom_ii/docs/setup_en.md b/worlds/doom_ii/docs/setup_en.md index 87054ab30783..ec6697c76da2 100644 --- a/worlds/doom_ii/docs/setup_en.md +++ b/worlds/doom_ii/docs/setup_en.md @@ -2,7 +2,7 @@ ## Required Software -- [DOOM II (e.g. Steam version)](https://store.steampowered.com/app/2300/DOOM_II/) +- [DOOM II (e.g. Steam version)](https://store.steampowered.com/app/2280/DOOM__DOOM_II/) - [Archipelago Crispy DOOM](https://github.com/Daivuk/apdoom/releases) ## Optional Software From a6f376b02e48e98f253b42ca66fb99eaa927a1ab Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:38:42 -0400 Subject: [PATCH 25/98] TLOZ: world: multiworld (#3752) --- worlds/tloz/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/tloz/__init__.py b/worlds/tloz/__init__.py index 8ea5f3e18ca1..c8c76bd85a8a 100644 --- a/worlds/tloz/__init__.py +++ b/worlds/tloz/__init__.py @@ -110,8 +110,8 @@ class TLoZWorld(World): if v is not None: location_name_to_id[k] = v + base_id - def __init__(self, world: MultiWorld, player: int): - super().__init__(world, player) + def __init__(self, multiworld: MultiWorld, player: int): + super().__init__(multiworld, player) self.generator_in_use = threading.Event() self.rom_name_available_event = threading.Event() self.levels = None From 9dba39b6064b162124885f556b7b72476774907e Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sat, 10 Aug 2024 13:08:24 +0200 Subject: [PATCH 26/98] SoE: fix determinism (#3745) Fixes randomly placed ingredients not being deterministic (depending on settings) and in turn also fixes logic not being deterministic if they get replaced by fragments. --- worlds/soe/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py index 3baed165d821..161c749fd6bd 100644 --- a/worlds/soe/__init__.py +++ b/worlds/soe/__init__.py @@ -188,6 +188,7 @@ class SoEWorld(World): connect_name: str _halls_ne_chest_names: typing.List[str] = [loc.name for loc in _locations if 'Halls NE' in loc.name] + _fillers = sorted(item_name_groups["Ingredients"]) def __init__(self, multiworld: "MultiWorld", player: int): self.connect_name_available_event = threading.Event() @@ -469,7 +470,7 @@ def modify_multidata(self, multidata: typing.Dict[str, typing.Any]) -> None: multidata["connect_names"][self.connect_name] = payload def get_filler_item_name(self) -> str: - return self.random.choice(list(self.item_name_groups["Ingredients"])) + return self.random.choice(self._fillers) class SoEItem(Item): From 8e06ab4f688c5e32350bea51ae47f8fcb3dd3e71 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Sat, 10 Aug 2024 06:49:32 -0500 Subject: [PATCH 27/98] Core: fix invalid __package__ of zipped worlds (#3686) * fix invalid package fix * add comment describing fix --- worlds/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/worlds/__init__.py b/worlds/__init__.py index bb2fe866d02d..c277ac9ca1de 100644 --- a/worlds/__init__.py +++ b/worlds/__init__.py @@ -73,7 +73,12 @@ def load(self) -> bool: else: # TODO: remove with 3.8 support mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0]) - mod.__package__ = f"worlds.{mod.__package__}" + if mod.__package__ is not None: + mod.__package__ = f"worlds.{mod.__package__}" + else: + # load_module does not populate package, we'll have to assume mod.__name__ is correct here + # probably safe to remove with 3.8 support + mod.__package__ = f"worlds.{mod.__name__}" mod.__name__ = f"worlds.{mod.__name__}" sys.modules[mod.__name__] = mod with warnings.catch_warnings(): From 68a92b0c6fe5b011da20ab5338c3f23757f1876d Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Sun, 11 Aug 2024 08:47:17 -0400 Subject: [PATCH 28/98] Clique: Update to new options API (#3759) --- worlds/clique/Items.py | 13 ++++++++----- worlds/clique/Locations.py | 11 +++++++---- worlds/clique/Options.py | 16 ++++++++-------- worlds/clique/Rules.py | 13 ++++++++----- worlds/clique/__init__.py | 32 ++++++++++++++++---------------- 5 files changed, 47 insertions(+), 38 deletions(-) diff --git a/worlds/clique/Items.py b/worlds/clique/Items.py index 5474f58b82d5..81e2540bacc0 100644 --- a/worlds/clique/Items.py +++ b/worlds/clique/Items.py @@ -1,6 +1,9 @@ -from typing import Callable, Dict, NamedTuple, Optional +from typing import Callable, Dict, NamedTuple, Optional, TYPE_CHECKING -from BaseClasses import Item, ItemClassification, MultiWorld +from BaseClasses import Item, ItemClassification + +if TYPE_CHECKING: + from . import CliqueWorld class CliqueItem(Item): @@ -10,7 +13,7 @@ class CliqueItem(Item): class CliqueItemData(NamedTuple): code: Optional[int] = None type: ItemClassification = ItemClassification.filler - can_create: Callable[[MultiWorld, int], bool] = lambda multiworld, player: True + can_create: Callable[["CliqueWorld"], bool] = lambda world: True item_data_table: Dict[str, CliqueItemData] = { @@ -21,11 +24,11 @@ class CliqueItemData(NamedTuple): "Button Activation": CliqueItemData( code=69696968, type=ItemClassification.progression, - can_create=lambda multiworld, player: bool(getattr(multiworld, "hard_mode")[player]), + can_create=lambda world: world.options.hard_mode, ), "A Cool Filler Item (No Satisfaction Guaranteed)": CliqueItemData( code=69696967, - can_create=lambda multiworld, player: False # Only created from `get_filler_item_name`. + can_create=lambda world: False # Only created from `get_filler_item_name`. ), "The Urge to Push": CliqueItemData( type=ItemClassification.progression, diff --git a/worlds/clique/Locations.py b/worlds/clique/Locations.py index 144becae5368..900b497eb4eb 100644 --- a/worlds/clique/Locations.py +++ b/worlds/clique/Locations.py @@ -1,6 +1,9 @@ -from typing import Callable, Dict, NamedTuple, Optional +from typing import Callable, Dict, NamedTuple, Optional, TYPE_CHECKING -from BaseClasses import Location, MultiWorld +from BaseClasses import Location + +if TYPE_CHECKING: + from . import CliqueWorld class CliqueLocation(Location): @@ -10,7 +13,7 @@ class CliqueLocation(Location): class CliqueLocationData(NamedTuple): region: str address: Optional[int] = None - can_create: Callable[[MultiWorld, int], bool] = lambda multiworld, player: True + can_create: Callable[["CliqueWorld"], bool] = lambda world: True locked_item: Optional[str] = None @@ -22,7 +25,7 @@ class CliqueLocationData(NamedTuple): "The Item on the Desk": CliqueLocationData( region="The Button Realm", address=69696968, - can_create=lambda multiworld, player: bool(getattr(multiworld, "hard_mode")[player]), + can_create=lambda world: world.options.hard_mode, ), "In the Player's Mind": CliqueLocationData( region="The Button Realm", diff --git a/worlds/clique/Options.py b/worlds/clique/Options.py index 7976dcb62130..d88a1289903c 100644 --- a/worlds/clique/Options.py +++ b/worlds/clique/Options.py @@ -1,6 +1,5 @@ -from typing import Dict - -from Options import Choice, Option, Toggle +from dataclasses import dataclass +from Options import Choice, Toggle, PerGameCommonOptions, StartInventoryPool class HardMode(Toggle): @@ -25,10 +24,11 @@ class ButtonColor(Choice): option_black = 11 -clique_options: Dict[str, type(Option)] = { - "color": ButtonColor, - "hard_mode": HardMode, +@dataclass +class CliqueOptions(PerGameCommonOptions): + color: ButtonColor + hard_mode: HardMode + start_inventory_from_pool: StartInventoryPool # DeathLink is always on. Always. - # "death_link": DeathLink, -} + # death_link: DeathLink diff --git a/worlds/clique/Rules.py b/worlds/clique/Rules.py index 5ae1d2c68e39..63ecd4e9e17c 100644 --- a/worlds/clique/Rules.py +++ b/worlds/clique/Rules.py @@ -1,10 +1,13 @@ -from typing import Callable +from typing import Callable, TYPE_CHECKING -from BaseClasses import CollectionState, MultiWorld +from BaseClasses import CollectionState +if TYPE_CHECKING: + from . import CliqueWorld -def get_button_rule(multiworld: MultiWorld, player: int) -> Callable[[CollectionState], bool]: - if getattr(multiworld, "hard_mode")[player]: - return lambda state: state.has("Button Activation", player) + +def get_button_rule(world: "CliqueWorld") -> Callable[[CollectionState], bool]: + if world.options.hard_mode: + return lambda state: state.has("Button Activation", world.player) return lambda state: True diff --git a/worlds/clique/__init__.py b/worlds/clique/__init__.py index b5cc74d94ac0..3d06e477eba7 100644 --- a/worlds/clique/__init__.py +++ b/worlds/clique/__init__.py @@ -1,10 +1,10 @@ -from typing import List +from typing import List, Dict, Any from BaseClasses import Region, Tutorial from worlds.AutoWorld import WebWorld, World from .Items import CliqueItem, item_data_table, item_table from .Locations import CliqueLocation, location_data_table, location_table, locked_locations -from .Options import clique_options +from .Options import CliqueOptions from .Regions import region_data_table from .Rules import get_button_rule @@ -38,7 +38,8 @@ class CliqueWorld(World): game = "Clique" web = CliqueWebWorld() - option_definitions = clique_options + options: CliqueOptions + options_dataclass = CliqueOptions location_name_to_id = location_table item_name_to_id = item_table @@ -48,7 +49,7 @@ def create_item(self, name: str) -> CliqueItem: def create_items(self) -> None: item_pool: List[CliqueItem] = [] for name, item in item_data_table.items(): - if item.code and item.can_create(self.multiworld, self.player): + if item.code and item.can_create(self): item_pool.append(self.create_item(name)) self.multiworld.itempool += item_pool @@ -61,41 +62,40 @@ def create_regions(self) -> None: # Create locations. for region_name, region_data in region_data_table.items(): - region = self.multiworld.get_region(region_name, self.player) + region = self.get_region(region_name) region.add_locations({ location_name: location_data.address for location_name, location_data in location_data_table.items() - if location_data.region == region_name and location_data.can_create(self.multiworld, self.player) + if location_data.region == region_name and location_data.can_create(self) }, CliqueLocation) region.add_exits(region_data_table[region_name].connecting_regions) # Place locked locations. for location_name, location_data in locked_locations.items(): # Ignore locations we never created. - if not location_data.can_create(self.multiworld, self.player): + if not location_data.can_create(self): continue locked_item = self.create_item(location_data_table[location_name].locked_item) - self.multiworld.get_location(location_name, self.player).place_locked_item(locked_item) + self.get_location(location_name).place_locked_item(locked_item) # Set priority location for the Big Red Button! - self.multiworld.priority_locations[self.player].value.add("The Big Red Button") + self.options.priority_locations.value.add("The Big Red Button") def get_filler_item_name(self) -> str: return "A Cool Filler Item (No Satisfaction Guaranteed)" def set_rules(self) -> None: - button_rule = get_button_rule(self.multiworld, self.player) - self.multiworld.get_location("The Big Red Button", self.player).access_rule = button_rule - self.multiworld.get_location("In the Player's Mind", self.player).access_rule = button_rule + button_rule = get_button_rule(self) + self.get_location("The Big Red Button").access_rule = button_rule + self.get_location("In the Player's Mind").access_rule = button_rule # Do not allow button activations on buttons. - self.multiworld.get_location("The Big Red Button", self.player).item_rule =\ - lambda item: item.name != "Button Activation" + self.get_location("The Big Red Button").item_rule = lambda item: item.name != "Button Activation" # Completion condition. self.multiworld.completion_condition[self.player] = lambda state: state.has("The Urge to Push", self.player) - def fill_slot_data(self): + def fill_slot_data(self) -> Dict[str, Any]: return { - "color": getattr(self.multiworld, "color")[self.player].current_key + "color": self.options.color.current_key } From 09e052c750fcd1fa2e56afb8e9ec39e95775e382 Mon Sep 17 00:00:00 2001 From: Jarno Date: Mon, 12 Aug 2024 00:24:09 +0200 Subject: [PATCH 29/98] Timespinner: Fix eels check logic #3777 --- worlds/timespinner/Locations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/timespinner/Locations.py b/worlds/timespinner/Locations.py index 86839f0f2167..f99dd7615571 100644 --- a/worlds/timespinner/Locations.py +++ b/worlds/timespinner/Locations.py @@ -135,11 +135,11 @@ def get_location_datas(player: Optional[int], options: Optional[TimespinnerOptio LocationData('Upper Lake Serene', 'Lake Serene: Pyramid keys room', 1337104), LocationData('Upper Lake Serene', 'Lake Serene (Upper): Chicken ledge', 1337174), LocationData('Lower Lake Serene', 'Lake Serene (Lower): Deep dive', 1337105), - LocationData('Lower Lake Serene', 'Lake Serene (Lower): Under the eels', 1337106), + LocationData('Left Side forest Caves', 'Lake Serene (Lower): Under the eels', 1337106, lambda state: state.has('Water Mask', player)), LocationData('Lower Lake Serene', 'Lake Serene (Lower): Water spikes room', 1337107), LocationData('Lower Lake Serene', 'Lake Serene (Lower): Underwater secret', 1337108, logic.can_break_walls), LocationData('Lower Lake Serene', 'Lake Serene (Lower): T chest', 1337109, lambda state: flooded.flood_lake_serene or logic.has_doublejump_of_npc(state)), - LocationData('Lower Lake Serene', 'Lake Serene (Lower): Past the eels', 1337110), + LocationData('Left Side forest Caves', 'Lake Serene (Lower): Past the eels', 1337110, lambda state: state.has('Water Mask', player)), LocationData('Lower Lake Serene', 'Lake Serene (Lower): Underwater pedestal', 1337111, lambda state: flooded.flood_lake_serene or logic.has_doublejump(state)), LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Shroom jump room', 1337112, lambda state: flooded.flood_maw or logic.has_doublejump(state)), LocationData('Caves of Banishment (upper)', 'Caves of Banishment (Maw): Secret room', 1337113, lambda state: logic.can_break_walls(state) and (not flooded.flood_maw or state.has('Water Mask', player))), From 21bbf5fb95bcf6c1cc842197ca44ec253c5daeb4 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Sun, 11 Aug 2024 18:24:30 -0400 Subject: [PATCH 30/98] TUNIC: Add note to Universal Tracker stuff #3772 --- worlds/tunic/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index 5253e9951437..6657b464ed2d 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -410,7 +410,9 @@ def fill_slot_data(self) -> Dict[str, Any]: return slot_data # for the universal tracker, doesn't get called in standard gen + # docs: https://github.com/FarisTheAncient/Archipelago/blob/tracker/worlds/tracker/docs/re-gen-passthrough.md @staticmethod def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]: # returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough + # we are using re_gen_passthrough over modifying the world here due to complexities with ER return slot_data From ae0abd38217d03bba2f2ab78ee6a2311c5fdb995 Mon Sep 17 00:00:00 2001 From: qwint Date: Sun, 11 Aug 2024 17:57:59 -0500 Subject: [PATCH 31/98] Core: change start inventory from pool to warn when nothing to remove (#3158) * makes start inventory from pool warn and fixes the itempool to match when it can not find a matching item to remove * calc the difference correctly * save new filler and non-removed items differently so we don't remove existing items at random --- Main.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Main.py b/Main.py index ce054dcd393f..6dc03aaa55e0 100644 --- a/Main.py +++ b/Main.py @@ -151,6 +151,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No # Because some worlds don't actually create items during create_items this has to be as late as possible. if any(getattr(multiworld.worlds[player].options, "start_inventory_from_pool", None) for player in multiworld.player_ids): new_items: List[Item] = [] + old_items: List[Item] = [] depletion_pool: Dict[int, Dict[str, int]] = { player: getattr(multiworld.worlds[player].options, "start_inventory_from_pool", @@ -169,20 +170,24 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No depletion_pool[item.player][item.name] -= 1 # quick abort if we have found all items if not target: - new_items.extend(multiworld.itempool[i+1:]) + old_items.extend(multiworld.itempool[i+1:]) break else: - new_items.append(item) + old_items.append(item) # leftovers? if target: for player, remaining_items in depletion_pool.items(): remaining_items = {name: count for name, count in remaining_items.items() if count} if remaining_items: - raise Exception(f"{multiworld.get_player_name(player)}" + logger.warning(f"{multiworld.get_player_name(player)}" f" is trying to remove items from their pool that don't exist: {remaining_items}") - assert len(multiworld.itempool) == len(new_items), "Item Pool amounts should not change." - multiworld.itempool[:] = new_items + # find all filler we generated for the current player and remove until it matches + removables = [item for item in new_items if item.player == player] + for _ in range(sum(remaining_items.values())): + new_items.remove(removables.pop()) + assert len(multiworld.itempool) == len(new_items + old_items), "Item Pool amounts should not change." + multiworld.itempool[:] = new_items + old_items multiworld.link_items() From a3e54a951fb2ea322fe5a13ba09024e57425a4a2 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Sun, 11 Aug 2024 19:53:40 -0400 Subject: [PATCH 32/98] Undertale: Fix slot_data and options.as_dict() (#3774) * Undertale: Fixing slot_data * Booleans were difficult --- worlds/undertale/__init__.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/worlds/undertale/__init__.py b/worlds/undertale/__init__.py index 9084c77b0065..9f09bb34526b 100644 --- a/worlds/undertale/__init__.py +++ b/worlds/undertale/__init__.py @@ -67,12 +67,15 @@ def _get_undertale_data(self): "only_flakes": bool(self.options.only_flakes.value), "no_equips": bool(self.options.no_equips.value), "key_hunt": bool(self.options.key_hunt.value), - "key_pieces": self.options.key_pieces.value, - "rando_love": bool(self.options.rando_love.value), - "rando_stats": bool(self.options.rando_stats.value), + "key_pieces": int(self.options.key_pieces.value), + "rando_love": bool(self.options.rando_love and (self.options.route_required == "genocide" or self.options.route_required == "all_routes")), + "rando_stats": bool(self.options.rando_stats and (self.options.route_required == "genocide" or self.options.route_required == "all_routes")), "prog_armor": bool(self.options.prog_armor.value), "prog_weapons": bool(self.options.prog_weapons.value), - "rando_item_button": bool(self.options.rando_item_button.value) + "rando_item_button": bool(self.options.rando_item_button.value), + "route_required": int(self.options.route_required.value), + "temy_include": int(self.options.temy_include.value) + } def get_filler_item_name(self): @@ -220,16 +223,7 @@ def UndertaleRegion(region_name: str, exits=[]): link_undertale_areas(self.multiworld, self.player) def fill_slot_data(self): - slot_data = self._get_undertale_data() - for option_name in self.options.as_dict(): - option = getattr(self.multiworld, option_name)[self.player] - if (option_name == "rando_love" or option_name == "rando_stats") and \ - self.options.route_required != "genocide" and \ - self.options.route_required != "all_routes": - option.value = False - if slot_data.get(option_name, None) is None and type(option.value) in {str, int}: - slot_data[option_name] = int(option.value) - return slot_data + return self._get_undertale_data() def create_item(self, name: str) -> Item: item_data = item_table[name] From 67520adceae7d7a1222afd319ab274868f3bd3b9 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Sun, 11 Aug 2024 20:13:45 -0400 Subject: [PATCH 33/98] Core: Error on empty options.as_dict (#3773) * Error on empty options.as_dict * ValueError instead * Apply suggestions from code review Co-authored-by: Aaron Wagener --------- Co-authored-by: Aaron Wagener Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- Options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Options.py b/Options.py index d040828509d1..e22ee1ee63a0 100644 --- a/Options.py +++ b/Options.py @@ -1236,6 +1236,7 @@ def as_dict(self, *option_names: str, casing: str = "snake") -> typing.Dict[str, :param option_names: names of the options to return :param casing: case of the keys to return. Supports `snake`, `camel`, `pascal`, `kebab` """ + assert option_names, "options.as_dict() was used without any option names." option_results = {} for option_name in option_names: if option_name in type(self).type_hints: From 50330cf32f05e5e1afb65b51f4fd5c01391c3534 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:32:14 +0200 Subject: [PATCH 34/98] Core: Remove broken unused code from Options.py (#3781) "Unused" is a baseless assertion, but this code path has been crashing on the first statement for 6 months and noone's complained --- Options.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/Options.py b/Options.py index e22ee1ee63a0..ecde6275f1ea 100644 --- a/Options.py +++ b/Options.py @@ -1518,31 +1518,3 @@ def yaml_dump_scalar(scalar) -> str: with open(os.path.join(target_folder, game_name + ".yaml"), "w", encoding="utf-8-sig") as f: f.write(res) - - -if __name__ == "__main__": - - from worlds.alttp.Options import Logic - import argparse - - map_shuffle = Toggle - compass_shuffle = Toggle - key_shuffle = Toggle - big_key_shuffle = Toggle - hints = Toggle - test = argparse.Namespace() - test.logic = Logic.from_text("no_logic") - test.map_shuffle = map_shuffle.from_text("ON") - test.hints = hints.from_text('OFF') - try: - test.logic = Logic.from_text("overworld_glitches_typo") - except KeyError as e: - print(e) - try: - test.logic_owg = Logic.from_text("owg") - except KeyError as e: - print(e) - if test.map_shuffle: - print("map_shuffle is on") - print(f"Hints are {bool(test.hints)}") - print(test) From dcaa2f7b971d9b41de7a84331008a095223ac997 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:02:09 -0400 Subject: [PATCH 35/98] Core: Two Small Fixes (#3782) --- BaseClasses.py | 2 +- Launcher.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 092f330bcbb4..97e792cc5cc2 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1427,7 +1427,7 @@ def get_path(state: CollectionState, region: Region) -> List[Union[Tuple[str, st # Maybe move the big bomb over to the Event system instead? if any(exit_path == 'Pyramid Fairy' for path in self.paths.values() for (_, exit_path) in path): - if multiworld.mode[player] != 'inverted': + if multiworld.worlds[player].options.mode != 'inverted': self.paths[str(multiworld.get_region('Big Bomb Shop', player))] = \ get_path(state, multiworld.get_region('Big Bomb Shop', player)) else: diff --git a/Launcher.py b/Launcher.py index e4b65be93a68..6b66b2a3a671 100644 --- a/Launcher.py +++ b/Launcher.py @@ -266,7 +266,7 @@ def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None if file and component: run_component(component, file) else: - logging.warning(f"unable to identify component for {filename}") + logging.warning(f"unable to identify component for {file}") def _stop(self, *largs): # ran into what appears to be https://groups.google.com/g/kivy-users/c/saWDLoYCSZ4 with PyCharm. From 96d48a923a34a1cd4c5f64a5f2e12acc657dd041 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Tue, 13 Aug 2024 15:28:05 -0500 Subject: [PATCH 36/98] Core: recontextualize `CollectionState.collect` (#3723) * Core: renamed `CollectionState.collect` arg from `event` to `prevent_sweep` and remove forced collection * Update TestDungeon.py --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- BaseClasses.py | 8 +-- test/bases.py | 2 +- worlds/alttp/test/dungeons/TestDungeon.py | 4 +- worlds/oot/EntranceShuffle.py | 2 +- worlds/oot/__init__.py | 2 +- worlds/stardew_valley/test/TestCrops.py | 8 +-- .../stardew_valley/test/TestDynamicGoals.py | 34 ++++++------ worlds/stardew_valley/test/TestLogic.py | 2 +- worlds/stardew_valley/test/__init__.py | 8 +-- .../test/assertion/world_assert.py | 4 +- .../stardew_valley/test/rules/TestArcades.py | 52 +++++++++---------- .../test/rules/TestBuildings.py | 14 ++--- .../test/rules/TestCookingRecipes.py | 32 ++++++------ .../test/rules/TestCraftingRecipes.py | 26 +++++----- .../test/rules/TestDonations.py | 6 +-- .../test/rules/TestFriendship.py | 34 ++++++------ .../stardew_valley/test/rules/TestShipping.py | 2 +- worlds/stardew_valley/test/rules/TestTools.py | 40 +++++++------- .../stardew_valley/test/rules/TestWeapons.py | 16 +++--- 19 files changed, 146 insertions(+), 150 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 97e792cc5cc2..95f24af26548 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -863,19 +863,15 @@ def count_group_unique(self, item_name_group: str, player: int) -> int: ) # Item related - def collect(self, item: Item, event: bool = False, location: Optional[Location] = None) -> bool: + def collect(self, item: Item, prevent_sweep: bool = False, location: Optional[Location] = None) -> bool: if location: self.locations_checked.add(location) changed = self.multiworld.worlds[item.player].collect(self, item) - if not changed and event: - self.prog_items[item.player][item.name] += 1 - changed = True - self.stale[item.player] = True - if changed and not event: + if changed and not prevent_sweep: self.sweep_for_events() return changed diff --git a/test/bases.py b/test/bases.py index 5c2d241cbbfe..9fb223af2ac1 100644 --- a/test/bases.py +++ b/test/bases.py @@ -23,7 +23,7 @@ def get_state(self, items): state = CollectionState(self.multiworld) for item in items: item.classification = ItemClassification.progression - state.collect(item, event=True) + state.collect(item, prevent_sweep=True) state.sweep_for_events() state.update_reachable_regions(1) self._state_cache[self.multiworld, tuple(items)] = state diff --git a/worlds/alttp/test/dungeons/TestDungeon.py b/worlds/alttp/test/dungeons/TestDungeon.py index 91fc462c4ecc..796bfeec3f0e 100644 --- a/worlds/alttp/test/dungeons/TestDungeon.py +++ b/worlds/alttp/test/dungeons/TestDungeon.py @@ -54,7 +54,7 @@ def run_tests(self, access_pool): for item in items: item.classification = ItemClassification.progression - state.collect(item, event=True) # event=True prevents running sweep_for_events() and picking up + state.collect(item, prevent_sweep=True) # prevent_sweep=True prevents running sweep_for_events() and picking up state.sweep_for_events() # key drop keys repeatedly - self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access, f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}") \ No newline at end of file + self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access, f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}") diff --git a/worlds/oot/EntranceShuffle.py b/worlds/oot/EntranceShuffle.py index bbdc30490c18..058fdbed0011 100644 --- a/worlds/oot/EntranceShuffle.py +++ b/worlds/oot/EntranceShuffle.py @@ -796,7 +796,7 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all if ootworld.shuffle_interior_entrances or ootworld.shuffle_overworld_entrances or ootworld.spawn_positions: time_travel_state = none_state.copy() - time_travel_state.collect(ootworld.create_item('Time Travel'), event=True) + time_travel_state.collect(ootworld.create_item('Time Travel'), prevent_sweep=True) time_travel_state._oot_update_age_reachable_regions(player) # Unless entrances are decoupled, we don't want the player to end up through certain entrances as the wrong age diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index 89f10a5a2da0..24be303f822b 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -1388,7 +1388,7 @@ def get_state_with_complete_itempool(self): self.multiworld.worlds[item.player].collect(all_state, item) # If free_scarecrow give Scarecrow Song if self.free_scarecrow: - all_state.collect(self.create_item("Scarecrow Song"), event=True) + all_state.collect(self.create_item("Scarecrow Song"), prevent_sweep=True) all_state.stale[self.player] = True return all_state diff --git a/worlds/stardew_valley/test/TestCrops.py b/worlds/stardew_valley/test/TestCrops.py index 38b736367b80..362e6bf27e7c 100644 --- a/worlds/stardew_valley/test/TestCrops.py +++ b/worlds/stardew_valley/test/TestCrops.py @@ -11,10 +11,10 @@ def test_need_greenhouse_for_cactus(self): harvest_cactus = self.world.logic.region.can_reach_location("Harvest Cactus Fruit") self.assert_rule_false(harvest_cactus, self.multiworld.state) - self.multiworld.state.collect(self.world.create_item("Cactus Seeds"), event=False) - self.multiworld.state.collect(self.world.create_item("Shipping Bin"), event=False) - self.multiworld.state.collect(self.world.create_item("Desert Obelisk"), event=False) + self.multiworld.state.collect(self.world.create_item("Cactus Seeds"), prevent_sweep=False) + self.multiworld.state.collect(self.world.create_item("Shipping Bin"), prevent_sweep=False) + self.multiworld.state.collect(self.world.create_item("Desert Obelisk"), prevent_sweep=False) self.assert_rule_false(harvest_cactus, self.multiworld.state) - self.multiworld.state.collect(self.world.create_item("Greenhouse"), event=False) + self.multiworld.state.collect(self.world.create_item("Greenhouse"), prevent_sweep=False) self.assert_rule_true(harvest_cactus, self.multiworld.state) diff --git a/worlds/stardew_valley/test/TestDynamicGoals.py b/worlds/stardew_valley/test/TestDynamicGoals.py index fe1bfb5f3044..bfa58dd34063 100644 --- a/worlds/stardew_valley/test/TestDynamicGoals.py +++ b/worlds/stardew_valley/test/TestDynamicGoals.py @@ -12,29 +12,29 @@ def collect_fishing_abilities(tester: SVTestBase): for i in range(4): - tester.multiworld.state.collect(tester.world.create_item(APTool.fishing_rod), event=False) - tester.multiworld.state.collect(tester.world.create_item(APTool.pickaxe), event=False) - tester.multiworld.state.collect(tester.world.create_item(APTool.axe), event=False) - tester.multiworld.state.collect(tester.world.create_item(APWeapon.weapon), event=False) + tester.multiworld.state.collect(tester.world.create_item(APTool.fishing_rod), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item(APTool.pickaxe), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item(APTool.axe), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item(APWeapon.weapon), prevent_sweep=False) for i in range(10): - tester.multiworld.state.collect(tester.world.create_item("Fishing Level"), event=False) - tester.multiworld.state.collect(tester.world.create_item("Combat Level"), event=False) - tester.multiworld.state.collect(tester.world.create_item("Mining Level"), event=False) + tester.multiworld.state.collect(tester.world.create_item("Fishing Level"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Combat Level"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Mining Level"), prevent_sweep=False) for i in range(17): - tester.multiworld.state.collect(tester.world.create_item("Progressive Mine Elevator"), event=False) - tester.multiworld.state.collect(tester.world.create_item("Spring"), event=False) - tester.multiworld.state.collect(tester.world.create_item("Summer"), event=False) - tester.multiworld.state.collect(tester.world.create_item("Fall"), event=False) - tester.multiworld.state.collect(tester.world.create_item("Winter"), event=False) - tester.multiworld.state.collect(tester.world.create_item(Transportation.desert_obelisk), event=False) - tester.multiworld.state.collect(tester.world.create_item("Railroad Boulder Removed"), event=False) - tester.multiworld.state.collect(tester.world.create_item("Island North Turtle"), event=False) - tester.multiworld.state.collect(tester.world.create_item("Island West Turtle"), event=False) + tester.multiworld.state.collect(tester.world.create_item("Progressive Mine Elevator"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Spring"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Summer"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Fall"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Winter"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item(Transportation.desert_obelisk), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Railroad Boulder Removed"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Island North Turtle"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Island West Turtle"), prevent_sweep=False) def create_and_collect(tester: SVTestBase, item_name: str) -> StardewItem: item = tester.world.create_item(item_name) - tester.multiworld.state.collect(item, event=False) + tester.multiworld.state.collect(item, prevent_sweep=False) return item diff --git a/worlds/stardew_valley/test/TestLogic.py b/worlds/stardew_valley/test/TestLogic.py index 65f7352a5e36..da00a0f43e43 100644 --- a/worlds/stardew_valley/test/TestLogic.py +++ b/worlds/stardew_valley/test/TestLogic.py @@ -12,7 +12,7 @@ def collect_all(mw): for item in mw.get_items(): - mw.state.collect(item, event=True) + mw.state.collect(item, prevent_sweep=True) class LogicTestBase(RuleAssertMixin, TestCase): diff --git a/worlds/stardew_valley/test/__init__.py b/worlds/stardew_valley/test/__init__.py index 7e82ea91e434..c2c2a6a20baf 100644 --- a/worlds/stardew_valley/test/__init__.py +++ b/worlds/stardew_valley/test/__init__.py @@ -257,16 +257,16 @@ def run_default_tests(self) -> bool: return super().run_default_tests def collect_lots_of_money(self): - self.multiworld.state.collect(self.world.create_item("Shipping Bin"), event=False) + self.multiworld.state.collect(self.world.create_item("Shipping Bin"), prevent_sweep=False) required_prog_items = int(round(self.multiworld.worlds[self.player].total_progression_items * 0.25)) for i in range(required_prog_items): - self.multiworld.state.collect(self.world.create_item("Stardrop"), event=False) + self.multiworld.state.collect(self.world.create_item("Stardrop"), prevent_sweep=False) def collect_all_the_money(self): - self.multiworld.state.collect(self.world.create_item("Shipping Bin"), event=False) + self.multiworld.state.collect(self.world.create_item("Shipping Bin"), prevent_sweep=False) required_prog_items = int(round(self.multiworld.worlds[self.player].total_progression_items * 0.95)) for i in range(required_prog_items): - self.multiworld.state.collect(self.world.create_item("Stardrop"), event=False) + self.multiworld.state.collect(self.world.create_item("Stardrop"), prevent_sweep=False) def collect_everything(self): non_event_items = [item for item in self.multiworld.get_items() if item.code] diff --git a/worlds/stardew_valley/test/assertion/world_assert.py b/worlds/stardew_valley/test/assertion/world_assert.py index c1c24bdf75b4..97172834543c 100644 --- a/worlds/stardew_valley/test/assertion/world_assert.py +++ b/worlds/stardew_valley/test/assertion/world_assert.py @@ -33,14 +33,14 @@ def assert_item_was_necessary_for_victory(self, item: StardewItem, multiworld: M self.assert_can_reach_victory(multiworld) multiworld.state.remove(item) self.assert_cannot_reach_victory(multiworld) - multiworld.state.collect(item, event=False) + multiworld.state.collect(item, prevent_sweep=False) self.assert_can_reach_victory(multiworld) def assert_item_was_not_necessary_for_victory(self, item: StardewItem, multiworld: MultiWorld): self.assert_can_reach_victory(multiworld) multiworld.state.remove(item) self.assert_can_reach_victory(multiworld) - multiworld.state.collect(item, event=False) + multiworld.state.collect(item, prevent_sweep=False) self.assert_can_reach_victory(multiworld) def assert_can_win(self, multiworld: MultiWorld): diff --git a/worlds/stardew_valley/test/rules/TestArcades.py b/worlds/stardew_valley/test/rules/TestArcades.py index fb62a456378a..2922ecfb5d9e 100644 --- a/worlds/stardew_valley/test/rules/TestArcades.py +++ b/worlds/stardew_valley/test/rules/TestArcades.py @@ -19,8 +19,8 @@ def test_prairie_king(self): life = self.create_item("JotPK: Extra Life") drop = self.create_item("JotPK: Increased Drop Rate") - self.multiworld.state.collect(boots, event=True) - self.multiworld.state.collect(gun, event=True) + self.multiworld.state.collect(boots, prevent_sweep=True) + self.multiworld.state.collect(gun, prevent_sweep=True) self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) @@ -28,8 +28,8 @@ def test_prairie_king(self): self.remove(boots) self.remove(gun) - self.multiworld.state.collect(boots, event=True) - self.multiworld.state.collect(boots, event=True) + self.multiworld.state.collect(boots, prevent_sweep=True) + self.multiworld.state.collect(boots, prevent_sweep=True) self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) @@ -37,10 +37,10 @@ def test_prairie_king(self): self.remove(boots) self.remove(boots) - self.multiworld.state.collect(boots, event=True) - self.multiworld.state.collect(gun, event=True) - self.multiworld.state.collect(ammo, event=True) - self.multiworld.state.collect(life, event=True) + self.multiworld.state.collect(boots, prevent_sweep=True) + self.multiworld.state.collect(gun, prevent_sweep=True) + self.multiworld.state.collect(ammo, prevent_sweep=True) + self.multiworld.state.collect(life, prevent_sweep=True) self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertTrue(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) @@ -50,13 +50,13 @@ def test_prairie_king(self): self.remove(ammo) self.remove(life) - self.multiworld.state.collect(boots, event=True) - self.multiworld.state.collect(gun, event=True) - self.multiworld.state.collect(gun, event=True) - self.multiworld.state.collect(ammo, event=True) - self.multiworld.state.collect(ammo, event=True) - self.multiworld.state.collect(life, event=True) - self.multiworld.state.collect(drop, event=True) + self.multiworld.state.collect(boots, prevent_sweep=True) + self.multiworld.state.collect(gun, prevent_sweep=True) + self.multiworld.state.collect(gun, prevent_sweep=True) + self.multiworld.state.collect(ammo, prevent_sweep=True) + self.multiworld.state.collect(ammo, prevent_sweep=True) + self.multiworld.state.collect(life, prevent_sweep=True) + self.multiworld.state.collect(drop, prevent_sweep=True) self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertTrue(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertTrue(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) @@ -69,17 +69,17 @@ def test_prairie_king(self): self.remove(life) self.remove(drop) - self.multiworld.state.collect(boots, event=True) - self.multiworld.state.collect(boots, event=True) - self.multiworld.state.collect(gun, event=True) - self.multiworld.state.collect(gun, event=True) - self.multiworld.state.collect(gun, event=True) - self.multiworld.state.collect(gun, event=True) - self.multiworld.state.collect(ammo, event=True) - self.multiworld.state.collect(ammo, event=True) - self.multiworld.state.collect(ammo, event=True) - self.multiworld.state.collect(life, event=True) - self.multiworld.state.collect(drop, event=True) + self.multiworld.state.collect(boots, prevent_sweep=True) + self.multiworld.state.collect(boots, prevent_sweep=True) + self.multiworld.state.collect(gun, prevent_sweep=True) + self.multiworld.state.collect(gun, prevent_sweep=True) + self.multiworld.state.collect(gun, prevent_sweep=True) + self.multiworld.state.collect(gun, prevent_sweep=True) + self.multiworld.state.collect(ammo, prevent_sweep=True) + self.multiworld.state.collect(ammo, prevent_sweep=True) + self.multiworld.state.collect(ammo, prevent_sweep=True) + self.multiworld.state.collect(life, prevent_sweep=True) + self.multiworld.state.collect(drop, prevent_sweep=True) self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertTrue(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertTrue(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) diff --git a/worlds/stardew_valley/test/rules/TestBuildings.py b/worlds/stardew_valley/test/rules/TestBuildings.py index b00e4138a195..2c276d8b5cbe 100644 --- a/worlds/stardew_valley/test/rules/TestBuildings.py +++ b/worlds/stardew_valley/test/rules/TestBuildings.py @@ -23,11 +23,11 @@ def test_big_coop_blueprint(self): self.assertFalse(big_coop_blueprint_rule(self.multiworld.state), f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") - self.multiworld.state.collect(self.create_item("Can Construct Buildings"), event=True) + self.multiworld.state.collect(self.create_item("Can Construct Buildings"), prevent_sweep=True) self.assertFalse(big_coop_blueprint_rule(self.multiworld.state), f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") - self.multiworld.state.collect(self.create_item("Progressive Coop"), event=False) + self.multiworld.state.collect(self.create_item("Progressive Coop"), prevent_sweep=False) self.assertTrue(big_coop_blueprint_rule(self.multiworld.state), f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") @@ -35,13 +35,13 @@ def test_deluxe_coop_blueprint(self): self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) self.collect_lots_of_money() - self.multiworld.state.collect(self.create_item("Can Construct Buildings"), event=True) + self.multiworld.state.collect(self.create_item("Can Construct Buildings"), prevent_sweep=True) self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) - self.multiworld.state.collect(self.create_item("Progressive Coop"), event=True) + self.multiworld.state.collect(self.create_item("Progressive Coop"), prevent_sweep=True) self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) - self.multiworld.state.collect(self.create_item("Progressive Coop"), event=True) + self.multiworld.state.collect(self.create_item("Progressive Coop"), prevent_sweep=True) self.assertTrue(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) def test_big_shed_blueprint(self): @@ -53,10 +53,10 @@ def test_big_shed_blueprint(self): self.assertFalse(big_shed_rule(self.multiworld.state), f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") - self.multiworld.state.collect(self.create_item("Can Construct Buildings"), event=True) + self.multiworld.state.collect(self.create_item("Can Construct Buildings"), prevent_sweep=True) self.assertFalse(big_shed_rule(self.multiworld.state), f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") - self.multiworld.state.collect(self.create_item("Progressive Shed"), event=True) + self.multiworld.state.collect(self.create_item("Progressive Shed"), prevent_sweep=True) self.assertTrue(big_shed_rule(self.multiworld.state), f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") diff --git a/worlds/stardew_valley/test/rules/TestCookingRecipes.py b/worlds/stardew_valley/test/rules/TestCookingRecipes.py index 81a91d1e7482..7ab9d61cb942 100644 --- a/worlds/stardew_valley/test/rules/TestCookingRecipes.py +++ b/worlds/stardew_valley/test/rules/TestCookingRecipes.py @@ -17,14 +17,14 @@ def test_can_learn_qos_recipe(self): rule = self.world.logic.region.can_reach_location(location) self.assert_rule_false(rule, self.multiworld.state) - self.multiworld.state.collect(self.create_item("Progressive House"), event=False) - self.multiworld.state.collect(self.create_item("Radish Seeds"), event=False) - self.multiworld.state.collect(self.create_item("Spring"), event=False) - self.multiworld.state.collect(self.create_item("Summer"), event=False) + self.multiworld.state.collect(self.create_item("Progressive House"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Radish Seeds"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Spring"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Summer"), prevent_sweep=False) self.collect_lots_of_money() self.assert_rule_false(rule, self.multiworld.state) - self.multiworld.state.collect(self.create_item("The Queen of Sauce"), event=False) + self.multiworld.state.collect(self.create_item("The Queen of Sauce"), prevent_sweep=False) self.assert_rule_true(rule, self.multiworld.state) @@ -42,21 +42,21 @@ def test_can_learn_qos_recipe(self): rule = self.world.logic.region.can_reach_location(location) self.assert_rule_false(rule, self.multiworld.state) - self.multiworld.state.collect(self.create_item("Progressive House"), event=False) - self.multiworld.state.collect(self.create_item("Radish Seeds"), event=False) - self.multiworld.state.collect(self.create_item("Summer"), event=False) + self.multiworld.state.collect(self.create_item("Progressive House"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Radish Seeds"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Summer"), prevent_sweep=False) self.collect_lots_of_money() self.assert_rule_false(rule, self.multiworld.state) spring = self.create_item("Spring") qos = self.create_item("The Queen of Sauce") - self.multiworld.state.collect(spring, event=False) - self.multiworld.state.collect(qos, event=False) + self.multiworld.state.collect(spring, prevent_sweep=False) + self.multiworld.state.collect(qos, prevent_sweep=False) self.assert_rule_false(rule, self.multiworld.state) self.multiworld.state.remove(spring) self.multiworld.state.remove(qos) - self.multiworld.state.collect(self.create_item("Radish Salad Recipe"), event=False) + self.multiworld.state.collect(self.create_item("Radish Salad Recipe"), prevent_sweep=False) self.assert_rule_true(rule, self.multiworld.state) def test_get_chefsanity_check_recipe(self): @@ -64,20 +64,20 @@ def test_get_chefsanity_check_recipe(self): rule = self.world.logic.region.can_reach_location(location) self.assert_rule_false(rule, self.multiworld.state) - self.multiworld.state.collect(self.create_item("Spring"), event=False) + self.multiworld.state.collect(self.create_item("Spring"), prevent_sweep=False) self.collect_lots_of_money() self.assert_rule_false(rule, self.multiworld.state) seeds = self.create_item("Radish Seeds") summer = self.create_item("Summer") house = self.create_item("Progressive House") - self.multiworld.state.collect(seeds, event=False) - self.multiworld.state.collect(summer, event=False) - self.multiworld.state.collect(house, event=False) + self.multiworld.state.collect(seeds, prevent_sweep=False) + self.multiworld.state.collect(summer, prevent_sweep=False) + self.multiworld.state.collect(house, prevent_sweep=False) self.assert_rule_false(rule, self.multiworld.state) self.multiworld.state.remove(seeds) self.multiworld.state.remove(summer) self.multiworld.state.remove(house) - self.multiworld.state.collect(self.create_item("The Queen of Sauce"), event=False) + self.multiworld.state.collect(self.create_item("The Queen of Sauce"), prevent_sweep=False) self.assert_rule_true(rule, self.multiworld.state) diff --git a/worlds/stardew_valley/test/rules/TestCraftingRecipes.py b/worlds/stardew_valley/test/rules/TestCraftingRecipes.py index 59d41f6a63d6..93c325ae5c5c 100644 --- a/worlds/stardew_valley/test/rules/TestCraftingRecipes.py +++ b/worlds/stardew_valley/test/rules/TestCraftingRecipes.py @@ -25,7 +25,7 @@ def test_can_craft_recipe(self): self.collect_all_the_money() self.assert_rule_false(rule, self.multiworld.state) - self.multiworld.state.collect(self.create_item("Marble Brazier Recipe"), event=False) + self.multiworld.state.collect(self.create_item("Marble Brazier Recipe"), prevent_sweep=False) self.assert_rule_true(rule, self.multiworld.state) def test_can_learn_crafting_recipe(self): @@ -38,16 +38,16 @@ def test_can_learn_crafting_recipe(self): def test_can_craft_festival_recipe(self): recipe = all_crafting_recipes_by_name["Jack-O-Lantern"] - self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), event=False) - self.multiworld.state.collect(self.create_item("Torch Recipe"), event=False) + self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Torch Recipe"), prevent_sweep=False) self.collect_lots_of_money() rule = self.world.logic.crafting.can_craft(recipe) self.assert_rule_false(rule, self.multiworld.state) - self.multiworld.state.collect(self.create_item("Fall"), event=False) + self.multiworld.state.collect(self.create_item("Fall"), prevent_sweep=False) self.assert_rule_false(rule, self.multiworld.state) - self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"), event=False) + self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"), prevent_sweep=False) self.assert_rule_true(rule, self.multiworld.state) @@ -62,16 +62,16 @@ class TestCraftsanityWithFestivalsLogic(SVTestBase): def test_can_craft_festival_recipe(self): recipe = all_crafting_recipes_by_name["Jack-O-Lantern"] - self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), event=False) - self.multiworld.state.collect(self.create_item("Fall"), event=False) + self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Fall"), prevent_sweep=False) self.collect_lots_of_money() rule = self.world.logic.crafting.can_craft(recipe) self.assert_rule_false(rule, self.multiworld.state) - self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"), event=False) + self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"), prevent_sweep=False) self.assert_rule_false(rule, self.multiworld.state) - self.multiworld.state.collect(self.create_item("Torch Recipe"), event=False) + self.multiworld.state.collect(self.create_item("Torch Recipe"), prevent_sweep=False) self.assert_rule_true(rule, self.multiworld.state) @@ -92,7 +92,7 @@ def test_can_craft_recipe(self): def test_can_craft_festival_recipe(self): recipe = all_crafting_recipes_by_name["Jack-O-Lantern"] - self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), event=False) + self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), prevent_sweep=False) self.collect_lots_of_money() rule = self.world.logic.crafting.can_craft(recipe) result = rule(self.multiworld.state) @@ -113,11 +113,11 @@ class TestNoCraftsanityWithFestivalsLogic(SVTestBase): def test_can_craft_festival_recipe(self): recipe = all_crafting_recipes_by_name["Jack-O-Lantern"] - self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), event=False) - self.multiworld.state.collect(self.create_item("Fall"), event=False) + self.multiworld.state.collect(self.create_item("Pumpkin Seeds"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Fall"), prevent_sweep=False) self.collect_lots_of_money() rule = self.world.logic.crafting.can_craft(recipe) self.assert_rule_false(rule, self.multiworld.state) - self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"), event=False) + self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"), prevent_sweep=False) self.assert_rule_true(rule, self.multiworld.state) diff --git a/worlds/stardew_valley/test/rules/TestDonations.py b/worlds/stardew_valley/test/rules/TestDonations.py index 84ceac50ff5a..984a3ebc38b4 100644 --- a/worlds/stardew_valley/test/rules/TestDonations.py +++ b/worlds/stardew_valley/test/rules/TestDonations.py @@ -18,7 +18,7 @@ def test_cannot_make_any_donation_without_museum_access(self): for donation in locations_by_tag[LocationTags.MUSEUM_DONATIONS]: self.assertFalse(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) - self.multiworld.state.collect(self.create_item(railroad_item), event=False) + self.multiworld.state.collect(self.create_item(railroad_item), prevent_sweep=False) for donation in locations_by_tag[LocationTags.MUSEUM_DONATIONS]: self.assertTrue(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) @@ -39,7 +39,7 @@ def test_cannot_make_any_donation_without_museum_access(self): for donation in donation_locations: self.assertFalse(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) - self.multiworld.state.collect(self.create_item(railroad_item), event=False) + self.multiworld.state.collect(self.create_item(railroad_item), prevent_sweep=False) for donation in donation_locations: self.assertTrue(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) @@ -58,7 +58,7 @@ def test_cannot_make_any_donation_without_museum_access(self): for donation in locations_by_tag[LocationTags.MUSEUM_MILESTONES]: self.assertFalse(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) - self.multiworld.state.collect(self.create_item(railroad_item), event=False) + self.multiworld.state.collect(self.create_item(railroad_item), prevent_sweep=False) for donation in locations_by_tag[LocationTags.MUSEUM_MILESTONES]: self.assertTrue(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) diff --git a/worlds/stardew_valley/test/rules/TestFriendship.py b/worlds/stardew_valley/test/rules/TestFriendship.py index 43c5e55c7fca..fb186ca99480 100644 --- a/worlds/stardew_valley/test/rules/TestFriendship.py +++ b/worlds/stardew_valley/test/rules/TestFriendship.py @@ -11,34 +11,34 @@ class TestFriendsanityDatingRules(SVTestBase): def test_earning_dating_heart_requires_dating(self): self.collect_all_the_money() - self.multiworld.state.collect(self.create_item("Fall"), event=False) - self.multiworld.state.collect(self.create_item("Beach Bridge"), event=False) - self.multiworld.state.collect(self.create_item("Progressive House"), event=False) + self.multiworld.state.collect(self.create_item("Fall"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Beach Bridge"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Progressive House"), prevent_sweep=False) for i in range(3): - self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), event=False) - self.multiworld.state.collect(self.create_item("Progressive Weapon"), event=False) - self.multiworld.state.collect(self.create_item("Progressive Axe"), event=False) - self.multiworld.state.collect(self.create_item("Progressive Barn"), event=False) + self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Progressive Weapon"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Progressive Axe"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Progressive Barn"), prevent_sweep=False) for i in range(10): - self.multiworld.state.collect(self.create_item("Foraging Level"), event=False) - self.multiworld.state.collect(self.create_item("Farming Level"), event=False) - self.multiworld.state.collect(self.create_item("Mining Level"), event=False) - self.multiworld.state.collect(self.create_item("Combat Level"), event=False) - self.multiworld.state.collect(self.create_item("Progressive Mine Elevator"), event=False) - self.multiworld.state.collect(self.create_item("Progressive Mine Elevator"), event=False) + self.multiworld.state.collect(self.create_item("Foraging Level"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Farming Level"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Mining Level"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Combat Level"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Progressive Mine Elevator"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Progressive Mine Elevator"), prevent_sweep=False) npc = "Abigail" heart_name = f"{npc} <3" step = 3 self.assert_can_reach_heart_up_to(npc, 3, step) - self.multiworld.state.collect(self.create_item(heart_name), event=False) + self.multiworld.state.collect(self.create_item(heart_name), prevent_sweep=False) self.assert_can_reach_heart_up_to(npc, 6, step) - self.multiworld.state.collect(self.create_item(heart_name), event=False) + self.multiworld.state.collect(self.create_item(heart_name), prevent_sweep=False) self.assert_can_reach_heart_up_to(npc, 8, step) - self.multiworld.state.collect(self.create_item(heart_name), event=False) + self.multiworld.state.collect(self.create_item(heart_name), prevent_sweep=False) self.assert_can_reach_heart_up_to(npc, 10, step) - self.multiworld.state.collect(self.create_item(heart_name), event=False) + self.multiworld.state.collect(self.create_item(heart_name), prevent_sweep=False) self.assert_can_reach_heart_up_to(npc, 14, step) def assert_can_reach_heart_up_to(self, npc: str, max_reachable: int, step: int): diff --git a/worlds/stardew_valley/test/rules/TestShipping.py b/worlds/stardew_valley/test/rules/TestShipping.py index 378933b7d75d..973d8d3ada7d 100644 --- a/worlds/stardew_valley/test/rules/TestShipping.py +++ b/worlds/stardew_valley/test/rules/TestShipping.py @@ -76,7 +76,7 @@ def test_all_shipsanity_locations_require_shipping_bin(self): with self.subTest(location.name): self.remove(bin_item) self.assertFalse(self.world.logic.region.can_reach_location(location.name)(self.multiworld.state)) - self.multiworld.state.collect(bin_item, event=False) + self.multiworld.state.collect(bin_item, prevent_sweep=False) shipsanity_rule = self.world.logic.region.can_reach_location(location.name) self.assert_rule_true(shipsanity_rule, self.multiworld.state) self.remove(bin_item) diff --git a/worlds/stardew_valley/test/rules/TestTools.py b/worlds/stardew_valley/test/rules/TestTools.py index a1fb152812c8..5f0fe8ef3ffb 100644 --- a/worlds/stardew_valley/test/rules/TestTools.py +++ b/worlds/stardew_valley/test/rules/TestTools.py @@ -21,30 +21,30 @@ def test_sturgeon(self): self.assert_rule_false(sturgeon_rule, self.multiworld.state) summer = self.create_item("Summer") - self.multiworld.state.collect(summer, event=False) + self.multiworld.state.collect(summer, prevent_sweep=False) self.assert_rule_false(sturgeon_rule, self.multiworld.state) fishing_rod = self.create_item("Progressive Fishing Rod") - self.multiworld.state.collect(fishing_rod, event=False) - self.multiworld.state.collect(fishing_rod, event=False) + self.multiworld.state.collect(fishing_rod, prevent_sweep=False) + self.multiworld.state.collect(fishing_rod, prevent_sweep=False) self.assert_rule_false(sturgeon_rule, self.multiworld.state) fishing_level = self.create_item("Fishing Level") - self.multiworld.state.collect(fishing_level, event=False) + self.multiworld.state.collect(fishing_level, prevent_sweep=False) self.assert_rule_false(sturgeon_rule, self.multiworld.state) - self.multiworld.state.collect(fishing_level, event=False) - self.multiworld.state.collect(fishing_level, event=False) - self.multiworld.state.collect(fishing_level, event=False) - self.multiworld.state.collect(fishing_level, event=False) - self.multiworld.state.collect(fishing_level, event=False) + self.multiworld.state.collect(fishing_level, prevent_sweep=False) + self.multiworld.state.collect(fishing_level, prevent_sweep=False) + self.multiworld.state.collect(fishing_level, prevent_sweep=False) + self.multiworld.state.collect(fishing_level, prevent_sweep=False) + self.multiworld.state.collect(fishing_level, prevent_sweep=False) self.assert_rule_true(sturgeon_rule, self.multiworld.state) self.remove(summer) self.assert_rule_false(sturgeon_rule, self.multiworld.state) winter = self.create_item("Winter") - self.multiworld.state.collect(winter, event=False) + self.multiworld.state.collect(winter, prevent_sweep=False) self.assert_rule_true(sturgeon_rule, self.multiworld.state) self.remove(fishing_rod) @@ -53,24 +53,24 @@ def test_sturgeon(self): def test_old_master_cannoli(self): self.multiworld.state.prog_items = {1: Counter()} - self.multiworld.state.collect(self.create_item("Progressive Axe"), event=False) - self.multiworld.state.collect(self.create_item("Progressive Axe"), event=False) - self.multiworld.state.collect(self.create_item("Summer"), event=False) + self.multiworld.state.collect(self.create_item("Progressive Axe"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Progressive Axe"), prevent_sweep=False) + self.multiworld.state.collect(self.create_item("Summer"), prevent_sweep=False) self.collect_lots_of_money() rule = self.world.logic.region.can_reach_location("Old Master Cannoli") self.assert_rule_false(rule, self.multiworld.state) fall = self.create_item("Fall") - self.multiworld.state.collect(fall, event=False) + self.multiworld.state.collect(fall, prevent_sweep=False) self.assert_rule_false(rule, self.multiworld.state) tuesday = self.create_item("Traveling Merchant: Tuesday") - self.multiworld.state.collect(tuesday, event=False) + self.multiworld.state.collect(tuesday, prevent_sweep=False) self.assert_rule_false(rule, self.multiworld.state) rare_seed = self.create_item("Rare Seed") - self.multiworld.state.collect(rare_seed, event=False) + self.multiworld.state.collect(rare_seed, prevent_sweep=False) self.assert_rule_true(rule, self.multiworld.state) self.remove(fall) @@ -80,11 +80,11 @@ def test_old_master_cannoli(self): green_house = self.create_item("Greenhouse") self.collect(self.create_item(Event.fall_farming)) - self.multiworld.state.collect(green_house, event=False) + self.multiworld.state.collect(green_house, prevent_sweep=False) self.assert_rule_false(rule, self.multiworld.state) friday = self.create_item("Traveling Merchant: Friday") - self.multiworld.state.collect(friday, event=False) + self.multiworld.state.collect(friday, prevent_sweep=False) self.assertTrue(self.multiworld.get_location("Old Master Cannoli", 1).access_rule(self.multiworld.state)) self.remove(green_house) @@ -111,7 +111,7 @@ def test_cannot_get_any_tool_without_blacksmith_access(self): for material in [ToolMaterial.copper, ToolMaterial.iron, ToolMaterial.gold, ToolMaterial.iridium]: self.assert_rule_false(self.world.logic.tool.has_tool(tool, material), self.multiworld.state) - self.multiworld.state.collect(self.create_item(railroad_item), event=False) + self.multiworld.state.collect(self.create_item(railroad_item), prevent_sweep=False) for tool in [Tool.pickaxe, Tool.axe, Tool.hoe, Tool.trash_can, Tool.watering_can]: for material in [ToolMaterial.copper, ToolMaterial.iron, ToolMaterial.gold, ToolMaterial.iridium]: @@ -125,7 +125,7 @@ def test_cannot_get_fishing_rod_without_willy_access(self): for fishing_rod_level in [3, 4]: self.assert_rule_false(self.world.logic.tool.has_fishing_rod(fishing_rod_level), self.multiworld.state) - self.multiworld.state.collect(self.create_item(railroad_item), event=False) + self.multiworld.state.collect(self.create_item(railroad_item), prevent_sweep=False) for fishing_rod_level in [3, 4]: self.assert_rule_true(self.world.logic.tool.has_fishing_rod(fishing_rod_level), self.multiworld.state) diff --git a/worlds/stardew_valley/test/rules/TestWeapons.py b/worlds/stardew_valley/test/rules/TestWeapons.py index 77887f8eca0c..972170b93c75 100644 --- a/worlds/stardew_valley/test/rules/TestWeapons.py +++ b/worlds/stardew_valley/test/rules/TestWeapons.py @@ -10,16 +10,16 @@ class TestWeaponsLogic(SVTestBase): } def test_mine(self): - self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), event=True) - self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), event=True) - self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), event=True) - self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), event=True) - self.multiworld.state.collect(self.create_item("Progressive House"), event=True) + self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), prevent_sweep=True) + self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), prevent_sweep=True) + self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), prevent_sweep=True) + self.multiworld.state.collect(self.create_item("Progressive Pickaxe"), prevent_sweep=True) + self.multiworld.state.collect(self.create_item("Progressive House"), prevent_sweep=True) self.collect([self.create_item("Combat Level")] * 10) self.collect([self.create_item("Mining Level")] * 10) self.collect([self.create_item("Progressive Mine Elevator")] * 24) - self.multiworld.state.collect(self.create_item("Bus Repair"), event=True) - self.multiworld.state.collect(self.create_item("Skull Key"), event=True) + self.multiworld.state.collect(self.create_item("Bus Repair"), prevent_sweep=True) + self.multiworld.state.collect(self.create_item("Skull Key"), prevent_sweep=True) self.GiveItemAndCheckReachableMine("Progressive Sword", 1) self.GiveItemAndCheckReachableMine("Progressive Dagger", 1) @@ -43,7 +43,7 @@ def test_mine(self): def GiveItemAndCheckReachableMine(self, item_name: str, reachable_level: int): item = self.multiworld.create_item(item_name, self.player) - self.multiworld.state.collect(item, event=True) + self.multiworld.state.collect(item, prevent_sweep=True) rule = self.world.logic.mine.can_mine_in_the_mines_floor_1_40() if reachable_level > 0: self.assert_rule_true(rule, self.multiworld.state) From 8e7ea06f39248b93f02f9640eae9a3d21c805fdb Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Tue, 13 Aug 2024 17:17:42 -0500 Subject: [PATCH 37/98] Core: dump all item placements for generation failures. (#3237) * Core: dump all item placements for generation failures * pass the multiworld from remaining fill * change how the args get handled to fix formatting --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- Fill.py | 19 +++++++++++++------ Main.py | 5 +++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Fill.py b/Fill.py index 5185bbb60ee4..15d5842e2904 100644 --- a/Fill.py +++ b/Fill.py @@ -12,7 +12,12 @@ class FillError(RuntimeError): - pass + def __init__(self, *args: typing.Union[str, typing.Any], **kwargs) -> None: + if "multiworld" in kwargs and isinstance(args[0], str): + placements = (args[0] + f"\nAll Placements:\n" + + f"{[(loc, loc.item) for loc in kwargs['multiworld'].get_filled_locations()]}") + args = (placements, *args[1:]) + super().__init__(*args) def _log_fill_progress(name: str, placed: int, total_items: int) -> None: @@ -212,7 +217,7 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati f"Unfilled locations:\n" f"{', '.join(str(location) for location in locations)}\n" f"Already placed {len(placements)}:\n" - f"{', '.join(str(place) for place in placements)}") + f"{', '.join(str(place) for place in placements)}", multiworld=multiworld) item_pool.extend(unplaced_items) @@ -299,7 +304,7 @@ def remaining_fill(multiworld: MultiWorld, f"Unfilled locations:\n" f"{', '.join(str(location) for location in locations)}\n" f"Already placed {len(placements)}:\n" - f"{', '.join(str(place) for place in placements)}") + f"{', '.join(str(place) for place in placements)}", multiworld=multiworld) itempool.extend(unplaced_items) @@ -506,7 +511,8 @@ def mark_for_locking(location: Location): 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.", + multiworld=multiworld, ) accessibility_corrections(multiworld, multiworld.state, defaultlocations) @@ -523,7 +529,8 @@ def mark_for_locking(location: Location): if excludedlocations: raise FillError( f"Not enough filler items for excluded locations. " - f"There are {len(excludedlocations)} more excluded locations than filler or trap items." + f"There are {len(excludedlocations)} more excluded locations than filler or trap items.", + multiworld=multiworld, ) restitempool = filleritempool + usefulitempool @@ -589,7 +596,7 @@ def flood_items(multiworld: MultiWorld) -> None: if candidate_item_to_place is not None: item_to_place = candidate_item_to_place else: - raise FillError('No more progress items left to place.') + raise FillError('No more progress items left to place.', multiworld=multiworld) # find item to replace with progress item location_list = multiworld.get_reachable_locations() diff --git a/Main.py b/Main.py index 6dc03aaa55e0..edae5d7b19b1 100644 --- a/Main.py +++ b/Main.py @@ -11,7 +11,8 @@ import worlds from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region -from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items +from Fill import FillError, balance_multiworld_progression, distribute_items_restrictive, distribute_planned, \ + flood_items from Options import StartInventoryPool from Utils import __version__, output_path, version_tuple, get_settings from settings import get_settings @@ -346,7 +347,7 @@ def precollect_hint(location): output_file_futures.append(pool.submit(write_multidata)) if not check_accessibility_task.result(): if not multiworld.can_beat_game(): - raise Exception("Game appears as unbeatable. Aborting.") + raise FillError("Game appears as unbeatable. Aborting.", multiworld=multiworld) else: logger.warning("Location Accessibility requirements not fulfilled.") From 169da1b1e021bda141f7049cae591bb5c67d37df Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Tue, 13 Aug 2024 17:31:26 -0500 Subject: [PATCH 38/98] Tests: fix the all games multiworld test (#3788) --- test/multiworld/test_multiworlds.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/multiworld/test_multiworlds.py b/test/multiworld/test_multiworlds.py index 5289cac6c357..8415ac4c8429 100644 --- a/test/multiworld/test_multiworlds.py +++ b/test/multiworld/test_multiworlds.py @@ -55,7 +55,7 @@ def test_fills(self) -> None: all_worlds = list(AutoWorldRegister.world_types.values()) self.multiworld = setup_multiworld(all_worlds, ()) for world in self.multiworld.worlds.values(): - world.options.accessibility.value = Accessibility.option_locations + world.options.accessibility.value = Accessibility.option_full self.assertSteps(gen_steps) with self.subTest("filling multiworld", seed=self.multiworld.seed): distribute_items_restrictive(self.multiworld) @@ -66,8 +66,8 @@ def test_fills(self) -> None: class TestTwoPlayerMulti(MultiworldTestBase): def test_two_player_single_game_fills(self) -> None: """Tests that a multiworld of two players for each registered game world can generate.""" - for world in AutoWorldRegister.world_types.values(): - self.multiworld = setup_multiworld([world, world], ()) + for world_type in AutoWorldRegister.world_types.values(): + self.multiworld = setup_multiworld([world_type, world_type], ()) for world in self.multiworld.worlds.values(): world.options.accessibility.value = Accessibility.option_full self.assertSteps(gen_steps) From 0af31c71e0a8e3930cf24aec717fdea644054314 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Tue, 13 Aug 2024 20:35:08 -0400 Subject: [PATCH 39/98] TUNIC: Swap from multiworld.get to world.get for applicable things (#3789) * Swap from multiworld.get to world.get for applicable things * Why was this even here in the first place? --- worlds/tunic/__init__.py | 23 ++--- worlds/tunic/er_rules.py | 181 ++++++++++++++++++------------------- worlds/tunic/er_scripts.py | 2 +- worlds/tunic/rules.py | 180 ++++++++++++++++++------------------ 4 files changed, 190 insertions(+), 196 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index 6657b464ed2d..47c66591f912 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -155,8 +155,7 @@ def stage_generate_early(cls, multiworld: MultiWorld) -> None: if is_mismatched: raise Exception(f"TUNIC: Conflict between seed group {group}'s plando " f"connection {group_cxn.entrance} <-> {group_cxn.exit} and " - f"{tunic.multiworld.get_player_name(tunic.player)}'s plando " - f"connection {cxn.entrance} <-> {cxn.exit}") + f"{tunic.player_name}'s plando connection {cxn.entrance} <-> {cxn.exit}") if new_cxn: cls.seed_groups[group]["plando"].value.append(cxn) @@ -187,17 +186,17 @@ def create_items(self) -> None: if self.options.laurels_location: laurels = self.create_item("Hero's Laurels") if self.options.laurels_location == "6_coins": - self.multiworld.get_location("Coins in the Well - 6 Coins", self.player).place_locked_item(laurels) + self.get_location("Coins in the Well - 6 Coins").place_locked_item(laurels) elif self.options.laurels_location == "10_coins": - self.multiworld.get_location("Coins in the Well - 10 Coins", self.player).place_locked_item(laurels) + self.get_location("Coins in the Well - 10 Coins").place_locked_item(laurels) elif self.options.laurels_location == "10_fairies": - self.multiworld.get_location("Secret Gathering Place - 10 Fairy Reward", self.player).place_locked_item(laurels) + self.get_location("Secret Gathering Place - 10 Fairy Reward").place_locked_item(laurels) items_to_create["Hero's Laurels"] = 0 if self.options.keys_behind_bosses: for rgb_hexagon, location in hexagon_locations.items(): hex_item = self.create_item(gold_hexagon if self.options.hexagon_quest else rgb_hexagon) - self.multiworld.get_location(location, self.player).place_locked_item(hex_item) + self.get_location(location).place_locked_item(hex_item) items_to_create[rgb_hexagon] = 0 items_to_create[gold_hexagon] -= 3 @@ -297,15 +296,15 @@ def create_regions(self) -> None: self.multiworld.regions.append(region) for region_name, exits in tunic_regions.items(): - region = self.multiworld.get_region(region_name, self.player) + region = self.get_region(region_name) region.add_exits(exits) for location_name, location_id in self.location_name_to_id.items(): - region = self.multiworld.get_region(location_table[location_name].region, self.player) + region = self.get_region(location_table[location_name].region) location = TunicLocation(self.player, location_name, location_id, region) region.locations.append(location) - victory_region = self.multiworld.get_region("Spirit Arena", self.player) + victory_region = self.get_region("Spirit Arena") victory_location = TunicLocation(self.player, "The Heir", None, victory_region) victory_location.place_locked_item(TunicItem("Victory", ItemClassification.progression, None, self.player)) self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) @@ -339,10 +338,8 @@ def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]) -> None: name, connection = paths[location.parent_region] except KeyError: # logic bug, proceed with warning since it takes a long time to update AP - warning(f"{location.name} is not logically accessible for " - f"{self.multiworld.get_file_safe_player_name(self.player)}. " - "Creating entrance hint Inaccessible. " - "Please report this to the TUNIC rando devs.") + warning(f"{location.name} is not logically accessible for {self.player_name}. " + "Creating entrance hint Inaccessible. Please report this to the TUNIC rando devs.") hint_text = "Inaccessible" else: while connection != ("Menu", None): diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index 81e9d48b4afc..a54ea23bcc0a 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -1318,222 +1318,221 @@ def get_portal_info(portal_sd: str) -> Tuple[str, str]: def set_er_location_rules(world: "TunicWorld") -> None: player = world.player - multiworld = world.multiworld options = world.options - forbid_item(multiworld.get_location("Secret Gathering Place - 20 Fairy Reward", player), fairies, player) + forbid_item(world.get_location("Secret Gathering Place - 20 Fairy Reward"), fairies, player) # Ability Shuffle Exclusive Rules - set_rule(multiworld.get_location("East Forest - Dancing Fox Spirit Holy Cross", player), + set_rule(world.get_location("East Forest - Dancing Fox Spirit Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Forest Grave Path - Holy Cross Code by Grave", player), + set_rule(world.get_location("Forest Grave Path - Holy Cross Code by Grave"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("East Forest - Golden Obelisk Holy Cross", player), + set_rule(world.get_location("East Forest - Golden Obelisk Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Beneath the Well - [Powered Secret Room] Chest", player), + set_rule(world.get_location("Beneath the Well - [Powered Secret Room] Chest"), lambda state: state.has("Activate Furnace Fuse", player)) - set_rule(multiworld.get_location("West Garden - [North] Behind Holy Cross Door", player), + set_rule(world.get_location("West Garden - [North] Behind Holy Cross Door"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Library Hall - Holy Cross Chest", player), + set_rule(world.get_location("Library Hall - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Eastern Vault Fortress - [West Wing] Candles Holy Cross", player), + set_rule(world.get_location("Eastern Vault Fortress - [West Wing] Candles Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("West Garden - [Central Highlands] Holy Cross (Blue Lines)", player), + set_rule(world.get_location("West Garden - [Central Highlands] Holy Cross (Blue Lines)"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Quarry - [Back Entrance] Bushes Holy Cross", player), + set_rule(world.get_location("Quarry - [Back Entrance] Bushes Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Cathedral - Secret Legend Trophy Chest", player), + set_rule(world.get_location("Cathedral - Secret Legend Trophy Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Overworld - [Southwest] Flowers Holy Cross", player), + set_rule(world.get_location("Overworld - [Southwest] Flowers Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Overworld - [East] Weathervane Holy Cross", player), + set_rule(world.get_location("Overworld - [East] Weathervane Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Overworld - [Northeast] Flowers Holy Cross", player), + set_rule(world.get_location("Overworld - [Northeast] Flowers Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Overworld - [Southwest] Haiku Holy Cross", player), + set_rule(world.get_location("Overworld - [Southwest] Haiku Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Overworld - [Northwest] Golden Obelisk Page", player), + set_rule(world.get_location("Overworld - [Northwest] Golden Obelisk Page"), lambda state: has_ability(holy_cross, state, world)) # Overworld - set_rule(multiworld.get_location("Overworld - [Southwest] Grapple Chest Over Walkway", player), + set_rule(world.get_location("Overworld - [Southwest] Grapple Chest Over Walkway"), lambda state: state.has_any({grapple, laurels}, player)) - set_rule(multiworld.get_location("Overworld - [Southwest] West Beach Guarded By Turret 2", player), + set_rule(world.get_location("Overworld - [Southwest] West Beach Guarded By Turret 2"), lambda state: state.has_any({grapple, laurels}, player)) - set_rule(multiworld.get_location("Overworld - [Southwest] From West Garden", player), + set_rule(world.get_location("Overworld - [Southwest] From West Garden"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Overworld - [Southeast] Page on Pillar by Swamp", player), + set_rule(world.get_location("Overworld - [Southeast] Page on Pillar by Swamp"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Overworld - [Southwest] Fountain Page", player), + set_rule(world.get_location("Overworld - [Southwest] Fountain Page"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Overworld - [Northwest] Page on Pillar by Dark Tomb", player), + set_rule(world.get_location("Overworld - [Northwest] Page on Pillar by Dark Tomb"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Old House - Holy Cross Chest", player), + set_rule(world.get_location("Old House - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Overworld - [East] Grapple Chest", player), + set_rule(world.get_location("Overworld - [East] Grapple Chest"), lambda state: state.has(grapple, player)) - set_rule(multiworld.get_location("Sealed Temple - Holy Cross Chest", player), + set_rule(world.get_location("Sealed Temple - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Caustic Light Cave - Holy Cross Chest", player), + set_rule(world.get_location("Caustic Light Cave - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Cube Cave - Holy Cross Chest", player), + set_rule(world.get_location("Cube Cave - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Old House - Holy Cross Door Page", player), + set_rule(world.get_location("Old House - Holy Cross Door Page"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Maze Cave - Maze Room Holy Cross", player), + set_rule(world.get_location("Maze Cave - Maze Room Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Old House - Holy Cross Chest", player), + set_rule(world.get_location("Old House - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Patrol Cave - Holy Cross Chest", player), + set_rule(world.get_location("Patrol Cave - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Ruined Passage - Holy Cross Chest", player), + set_rule(world.get_location("Ruined Passage - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Hourglass Cave - Holy Cross Chest", player), + set_rule(world.get_location("Hourglass Cave - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Secret Gathering Place - Holy Cross Chest", player), + set_rule(world.get_location("Secret Gathering Place - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Secret Gathering Place - 10 Fairy Reward", player), + set_rule(world.get_location("Secret Gathering Place - 10 Fairy Reward"), lambda state: state.has(fairies, player, 10)) - set_rule(multiworld.get_location("Secret Gathering Place - 20 Fairy Reward", player), + set_rule(world.get_location("Secret Gathering Place - 20 Fairy Reward"), lambda state: state.has(fairies, player, 20)) - set_rule(multiworld.get_location("Coins in the Well - 3 Coins", player), + set_rule(world.get_location("Coins in the Well - 3 Coins"), lambda state: state.has(coins, player, 3)) - set_rule(multiworld.get_location("Coins in the Well - 6 Coins", player), + set_rule(world.get_location("Coins in the Well - 6 Coins"), lambda state: state.has(coins, player, 6)) - set_rule(multiworld.get_location("Coins in the Well - 10 Coins", player), + set_rule(world.get_location("Coins in the Well - 10 Coins"), lambda state: state.has(coins, player, 10)) - set_rule(multiworld.get_location("Coins in the Well - 15 Coins", player), + set_rule(world.get_location("Coins in the Well - 15 Coins"), lambda state: state.has(coins, player, 15)) # East Forest - set_rule(multiworld.get_location("East Forest - Lower Grapple Chest", player), + set_rule(world.get_location("East Forest - Lower Grapple Chest"), lambda state: state.has(grapple, player)) - set_rule(multiworld.get_location("East Forest - Lower Dash Chest", player), + set_rule(world.get_location("East Forest - Lower Dash Chest"), lambda state: state.has_all({grapple, laurels}, player)) - set_rule(multiworld.get_location("East Forest - Ice Rod Grapple Chest", player), lambda state: ( + set_rule(world.get_location("East Forest - Ice Rod Grapple Chest"), lambda state: ( state.has_all({grapple, ice_dagger, fire_wand}, player) and has_ability(icebolt, state, world))) # West Garden - set_rule(multiworld.get_location("West Garden - [North] Across From Page Pickup", player), + set_rule(world.get_location("West Garden - [North] Across From Page Pickup"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("West Garden - [West] In Flooded Walkway", player), + set_rule(world.get_location("West Garden - [West] In Flooded Walkway"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("West Garden - [West Lowlands] Tree Holy Cross Chest", player), + set_rule(world.get_location("West Garden - [West Lowlands] Tree Holy Cross Chest"), lambda state: state.has(laurels, player) and has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("West Garden - [East Lowlands] Page Behind Ice Dagger House", player), + set_rule(world.get_location("West Garden - [East Lowlands] Page Behind Ice Dagger House"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("West Garden - [Central Lowlands] Below Left Walkway", player), + set_rule(world.get_location("West Garden - [Central Lowlands] Below Left Walkway"), lambda state: state.has(laurels, player)) # Ruined Atoll - set_rule(multiworld.get_location("Ruined Atoll - [West] Near Kevin Block", player), + set_rule(world.get_location("Ruined Atoll - [West] Near Kevin Block"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Ruined Atoll - [East] Locked Room Lower Chest", player), + set_rule(world.get_location("Ruined Atoll - [East] Locked Room Lower Chest"), lambda state: state.has(laurels, player) or state.has(key, player, 2)) - set_rule(multiworld.get_location("Ruined Atoll - [East] Locked Room Upper Chest", player), + set_rule(world.get_location("Ruined Atoll - [East] Locked Room Upper Chest"), lambda state: state.has(laurels, player) or state.has(key, player, 2)) # Frog's Domain - set_rule(multiworld.get_location("Frog's Domain - Side Room Grapple Secret", player), + set_rule(world.get_location("Frog's Domain - Side Room Grapple Secret"), lambda state: state.has_any({grapple, laurels}, player)) - set_rule(multiworld.get_location("Frog's Domain - Grapple Above Hot Tub", player), + set_rule(world.get_location("Frog's Domain - Grapple Above Hot Tub"), lambda state: state.has_any({grapple, laurels}, player)) - set_rule(multiworld.get_location("Frog's Domain - Escape Chest", player), + set_rule(world.get_location("Frog's Domain - Escape Chest"), lambda state: state.has_any({grapple, laurels}, player)) # Eastern Vault Fortress - set_rule(multiworld.get_location("Fortress Arena - Hexagon Red", player), + set_rule(world.get_location("Fortress Arena - Hexagon Red"), lambda state: state.has(vault_key, player)) # Beneath the Vault - set_rule(multiworld.get_location("Beneath the Fortress - Bridge", player), + set_rule(world.get_location("Beneath the Fortress - Bridge"), lambda state: state.has_group("Melee Weapons", player, 1) or state.has_any({laurels, fire_wand}, player)) # Quarry - set_rule(multiworld.get_location("Quarry - [Central] Above Ladder Dash Chest", player), + set_rule(world.get_location("Quarry - [Central] Above Ladder Dash Chest"), lambda state: state.has(laurels, player)) # Ziggurat # if ER is off, you still need to get past the Admin or you'll get stuck in lower zig - set_rule(multiworld.get_location("Rooted Ziggurat Upper - Near Bridge Switch", player), + set_rule(world.get_location("Rooted Ziggurat Upper - Near Bridge Switch"), lambda state: has_sword(state, player) or (state.has(fire_wand, player) and (state.has(laurels, player) or options.entrance_rando))) - set_rule(multiworld.get_location("Rooted Ziggurat Lower - After Guarded Fuse", player), + set_rule(world.get_location("Rooted Ziggurat Lower - After Guarded Fuse"), lambda state: has_sword(state, player) and has_ability(prayer, state, world)) # Bosses - set_rule(multiworld.get_location("Fortress Arena - Siege Engine/Vault Key Pickup", player), + set_rule(world.get_location("Fortress Arena - Siege Engine/Vault Key Pickup"), lambda state: has_sword(state, player)) # nmg - kill Librarian with a lure, or gun I guess - set_rule(multiworld.get_location("Librarian - Hexagon Green", player), + set_rule(world.get_location("Librarian - Hexagon Green"), lambda state: (has_sword(state, player) or options.logic_rules) and has_ladder("Ladders in Library", state, world)) # nmg - kill boss scav with orb + firecracker, or similar - set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player), + set_rule(world.get_location("Rooted Ziggurat Lower - Hexagon Blue"), lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules)) # Swamp - set_rule(multiworld.get_location("Cathedral Gauntlet - Gauntlet Reward", player), + set_rule(world.get_location("Cathedral Gauntlet - Gauntlet Reward"), lambda state: state.has(fire_wand, player) and has_sword(state, player)) - set_rule(multiworld.get_location("Swamp - [Entrance] Above Entryway", player), + set_rule(world.get_location("Swamp - [Entrance] Above Entryway"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Swamp - [South Graveyard] Upper Walkway Dash Chest", player), + set_rule(world.get_location("Swamp - [South Graveyard] Upper Walkway Dash Chest"), lambda state: state.has(laurels, player)) # these two swamp checks really want you to kill the big skeleton first - set_rule(multiworld.get_location("Swamp - [South Graveyard] 4 Orange Skulls", player), + set_rule(world.get_location("Swamp - [South Graveyard] 4 Orange Skulls"), lambda state: has_sword(state, player)) # Hero's Grave and Far Shore - set_rule(multiworld.get_location("Hero's Grave - Tooth Relic", player), + set_rule(world.get_location("Hero's Grave - Tooth Relic"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Hero's Grave - Mushroom Relic", player), + set_rule(world.get_location("Hero's Grave - Mushroom Relic"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Hero's Grave - Ash Relic", player), + set_rule(world.get_location("Hero's Grave - Ash Relic"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Hero's Grave - Flowers Relic", player), + set_rule(world.get_location("Hero's Grave - Flowers Relic"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Hero's Grave - Effigy Relic", player), + set_rule(world.get_location("Hero's Grave - Effigy Relic"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Hero's Grave - Feathers Relic", player), + set_rule(world.get_location("Hero's Grave - Feathers Relic"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Far Shore - Secret Chest", player), + set_rule(world.get_location("Far Shore - Secret Chest"), lambda state: state.has(laurels, player)) # Events - set_rule(multiworld.get_location("Eastern Bell", player), + set_rule(world.get_location("Eastern Bell"), lambda state: (has_stick(state, player) or state.has(fire_wand, player))) - set_rule(multiworld.get_location("Western Bell", player), + set_rule(world.get_location("Western Bell"), lambda state: (has_stick(state, player) or state.has(fire_wand, player))) - set_rule(multiworld.get_location("Furnace Fuse", player), + set_rule(world.get_location("Furnace Fuse"), lambda state: has_ability(prayer, state, world)) - set_rule(multiworld.get_location("South and West Fortress Exterior Fuses", player), + set_rule(world.get_location("South and West Fortress Exterior Fuses"), lambda state: has_ability(prayer, state, world)) - set_rule(multiworld.get_location("Upper and Central Fortress Exterior Fuses", player), + set_rule(world.get_location("Upper and Central Fortress Exterior Fuses"), lambda state: has_ability(prayer, state, world)) - set_rule(multiworld.get_location("Beneath the Vault Fuse", player), + set_rule(world.get_location("Beneath the Vault Fuse"), lambda state: state.has("Activate South and West Fortress Exterior Fuses", player)) - set_rule(multiworld.get_location("Eastern Vault West Fuses", player), + set_rule(world.get_location("Eastern Vault West Fuses"), lambda state: state.has("Activate Beneath the Vault Fuse", player)) - set_rule(multiworld.get_location("Eastern Vault East Fuse", player), + set_rule(world.get_location("Eastern Vault East Fuse"), lambda state: state.has_all({"Activate Upper and Central Fortress Exterior Fuses", "Activate South and West Fortress Exterior Fuses"}, player)) - set_rule(multiworld.get_location("Quarry Connector Fuse", player), + set_rule(world.get_location("Quarry Connector Fuse"), lambda state: has_ability(prayer, state, world) and state.has(grapple, player)) - set_rule(multiworld.get_location("Quarry Fuse", player), + set_rule(world.get_location("Quarry Fuse"), lambda state: state.has("Activate Quarry Connector Fuse", player)) - set_rule(multiworld.get_location("Ziggurat Fuse", player), + set_rule(world.get_location("Ziggurat Fuse"), lambda state: has_ability(prayer, state, world)) - set_rule(multiworld.get_location("West Garden Fuse", player), + set_rule(world.get_location("West Garden Fuse"), lambda state: has_ability(prayer, state, world)) - set_rule(multiworld.get_location("Library Fuse", player), + set_rule(world.get_location("Library Fuse"), lambda state: has_ability(prayer, state, world)) # Shop - set_rule(multiworld.get_location("Shop - Potion 1", player), + set_rule(world.get_location("Shop - Potion 1"), lambda state: has_sword(state, player)) - set_rule(multiworld.get_location("Shop - Potion 2", player), + set_rule(world.get_location("Shop - Potion 2"), lambda state: has_sword(state, player)) - set_rule(multiworld.get_location("Shop - Coin 1", player), + set_rule(world.get_location("Shop - Coin 1"), lambda state: has_sword(state, player)) - set_rule(multiworld.get_location("Shop - Coin 2", player), + set_rule(world.get_location("Shop - Coin 2"), lambda state: has_sword(state, player)) diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index a4295cf9f2a4..e7c8fd58d0c6 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -130,7 +130,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: portal_pairs: Dict[Portal, Portal] = {} dead_ends: List[Portal] = [] two_plus: List[Portal] = [] - player_name = world.multiworld.get_player_name(world.player) + player_name = world.player_name portal_map = portal_mapping.copy() logic_rules = world.options.logic_rules.value fixed_shop = world.options.fixed_shop diff --git a/worlds/tunic/rules.py b/worlds/tunic/rules.py index 73eb8118901b..2ff588da904d 100644 --- a/worlds/tunic/rules.py +++ b/worlds/tunic/rules.py @@ -87,41 +87,40 @@ def has_lantern(state: CollectionState, world: "TunicWorld") -> bool: def set_region_rules(world: "TunicWorld") -> None: - multiworld = world.multiworld player = world.player options = world.options - multiworld.get_entrance("Overworld -> Overworld Holy Cross", player).access_rule = \ + world.get_entrance("Overworld -> Overworld Holy Cross").access_rule = \ lambda state: has_ability(holy_cross, state, world) - multiworld.get_entrance("Overworld -> Beneath the Well", player).access_rule = \ + world.get_entrance("Overworld -> Beneath the Well").access_rule = \ lambda state: has_stick(state, player) or state.has(fire_wand, player) - multiworld.get_entrance("Overworld -> Dark Tomb", player).access_rule = \ + world.get_entrance("Overworld -> Dark Tomb").access_rule = \ lambda state: has_lantern(state, world) - multiworld.get_entrance("Overworld -> West Garden", player).access_rule = \ + world.get_entrance("Overworld -> West Garden").access_rule = \ lambda state: state.has(laurels, player) \ or can_ladder_storage(state, world) - multiworld.get_entrance("Overworld -> Eastern Vault Fortress", player).access_rule = \ + world.get_entrance("Overworld -> Eastern Vault Fortress").access_rule = \ lambda state: state.has(laurels, player) \ or has_ice_grapple_logic(True, state, world) \ or can_ladder_storage(state, world) # using laurels or ls to get in is covered by the -> Eastern Vault Fortress rules - multiworld.get_entrance("Overworld -> Beneath the Vault", player).access_rule = \ + world.get_entrance("Overworld -> Beneath the Vault").access_rule = \ lambda state: has_lantern(state, world) and has_ability(prayer, state, world) - multiworld.get_entrance("Ruined Atoll -> Library", player).access_rule = \ + world.get_entrance("Ruined Atoll -> Library").access_rule = \ lambda state: state.has_any({grapple, laurels}, player) and has_ability(prayer, state, world) - multiworld.get_entrance("Overworld -> Quarry", player).access_rule = \ + world.get_entrance("Overworld -> Quarry").access_rule = \ lambda state: (has_sword(state, player) or state.has(fire_wand, player)) \ and (state.has_any({grapple, laurels}, player) or can_ladder_storage(state, world)) - multiworld.get_entrance("Quarry Back -> Quarry", player).access_rule = \ + world.get_entrance("Quarry Back -> Quarry").access_rule = \ lambda state: has_sword(state, player) or state.has(fire_wand, player) - multiworld.get_entrance("Quarry -> Lower Quarry", player).access_rule = \ + world.get_entrance("Quarry -> Lower Quarry").access_rule = \ lambda state: has_mask(state, world) - multiworld.get_entrance("Lower Quarry -> Rooted Ziggurat", player).access_rule = \ + world.get_entrance("Lower Quarry -> Rooted Ziggurat").access_rule = \ lambda state: state.has(grapple, player) and has_ability(prayer, state, world) - multiworld.get_entrance("Swamp -> Cathedral", player).access_rule = \ + world.get_entrance("Swamp -> Cathedral").access_rule = \ lambda state: state.has(laurels, player) and has_ability(prayer, state, world) \ or has_ice_grapple_logic(False, state, world) - multiworld.get_entrance("Overworld -> Spirit Arena", player).access_rule = \ + world.get_entrance("Overworld -> Spirit Arena").access_rule = \ lambda state: ((state.has(gold_hexagon, player, options.hexagon_goal.value) if options.hexagon_quest.value else state.has_all({red_hexagon, green_hexagon, blue_hexagon}, player) and state.has_group_unique("Hero Relics", player, 6)) @@ -130,210 +129,209 @@ def set_region_rules(world: "TunicWorld") -> None: def set_location_rules(world: "TunicWorld") -> None: - multiworld = world.multiworld player = world.player options = world.options - forbid_item(multiworld.get_location("Secret Gathering Place - 20 Fairy Reward", player), fairies, player) + forbid_item(world.get_location("Secret Gathering Place - 20 Fairy Reward"), fairies, player) # Ability Shuffle Exclusive Rules - set_rule(multiworld.get_location("Far Shore - Page Pickup", player), + set_rule(world.get_location("Far Shore - Page Pickup"), lambda state: has_ability(prayer, state, world)) - set_rule(multiworld.get_location("Fortress Courtyard - Chest Near Cave", player), + set_rule(world.get_location("Fortress Courtyard - Chest Near Cave"), lambda state: has_ability(prayer, state, world) or state.has(laurels, player) or can_ladder_storage(state, world) or (has_ice_grapple_logic(True, state, world) and has_lantern(state, world))) - set_rule(multiworld.get_location("Fortress Courtyard - Page Near Cave", player), + set_rule(world.get_location("Fortress Courtyard - Page Near Cave"), lambda state: has_ability(prayer, state, world) or state.has(laurels, player) or can_ladder_storage(state, world) or (has_ice_grapple_logic(True, state, world) and has_lantern(state, world))) - set_rule(multiworld.get_location("East Forest - Dancing Fox Spirit Holy Cross", player), + set_rule(world.get_location("East Forest - Dancing Fox Spirit Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Forest Grave Path - Holy Cross Code by Grave", player), + set_rule(world.get_location("Forest Grave Path - Holy Cross Code by Grave"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("East Forest - Golden Obelisk Holy Cross", player), + set_rule(world.get_location("East Forest - Golden Obelisk Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Beneath the Well - [Powered Secret Room] Chest", player), + set_rule(world.get_location("Beneath the Well - [Powered Secret Room] Chest"), lambda state: has_ability(prayer, state, world)) - set_rule(multiworld.get_location("West Garden - [North] Behind Holy Cross Door", player), + set_rule(world.get_location("West Garden - [North] Behind Holy Cross Door"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Library Hall - Holy Cross Chest", player), + set_rule(world.get_location("Library Hall - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Eastern Vault Fortress - [West Wing] Candles Holy Cross", player), + set_rule(world.get_location("Eastern Vault Fortress - [West Wing] Candles Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("West Garden - [Central Highlands] Holy Cross (Blue Lines)", player), + set_rule(world.get_location("West Garden - [Central Highlands] Holy Cross (Blue Lines)"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Quarry - [Back Entrance] Bushes Holy Cross", player), + set_rule(world.get_location("Quarry - [Back Entrance] Bushes Holy Cross"), lambda state: has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("Cathedral - Secret Legend Trophy Chest", player), + set_rule(world.get_location("Cathedral - Secret Legend Trophy Chest"), lambda state: has_ability(holy_cross, state, world)) # Overworld - set_rule(multiworld.get_location("Overworld - [Southwest] Fountain Page", player), + set_rule(world.get_location("Overworld - [Southwest] Fountain Page"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Overworld - [Southwest] Grapple Chest Over Walkway", player), + set_rule(world.get_location("Overworld - [Southwest] Grapple Chest Over Walkway"), lambda state: state.has_any({grapple, laurels}, player)) - set_rule(multiworld.get_location("Overworld - [Southwest] West Beach Guarded By Turret 2", player), + set_rule(world.get_location("Overworld - [Southwest] West Beach Guarded By Turret 2"), lambda state: state.has_any({grapple, laurels}, player)) - set_rule(multiworld.get_location("Far Shore - Secret Chest", player), + set_rule(world.get_location("Far Shore - Secret Chest"), lambda state: state.has(laurels, player) and has_ability(prayer, state, world)) - set_rule(multiworld.get_location("Overworld - [Southeast] Page on Pillar by Swamp", player), + set_rule(world.get_location("Overworld - [Southeast] Page on Pillar by Swamp"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Old House - Normal Chest", player), + set_rule(world.get_location("Old House - Normal Chest"), lambda state: state.has(house_key, player) or has_ice_grapple_logic(False, state, world) or (state.has(laurels, player) and options.logic_rules)) - set_rule(multiworld.get_location("Old House - Holy Cross Chest", player), + set_rule(world.get_location("Old House - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world) and ( state.has(house_key, player) or has_ice_grapple_logic(False, state, world) or (state.has(laurels, player) and options.logic_rules))) - set_rule(multiworld.get_location("Old House - Shield Pickup", player), + set_rule(world.get_location("Old House - Shield Pickup"), lambda state: state.has(house_key, player) or has_ice_grapple_logic(False, state, world) or (state.has(laurels, player) and options.logic_rules)) - set_rule(multiworld.get_location("Overworld - [Northwest] Page on Pillar by Dark Tomb", player), + set_rule(world.get_location("Overworld - [Northwest] Page on Pillar by Dark Tomb"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Overworld - [Southwest] From West Garden", player), + set_rule(world.get_location("Overworld - [Southwest] From West Garden"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Overworld - [West] Chest After Bell", player), + set_rule(world.get_location("Overworld - [West] Chest After Bell"), lambda state: state.has(laurels, player) or (has_lantern(state, world) and has_sword(state, player)) or can_ladder_storage(state, world)) - set_rule(multiworld.get_location("Overworld - [Northwest] Chest Beneath Quarry Gate", player), + set_rule(world.get_location("Overworld - [Northwest] Chest Beneath Quarry Gate"), lambda state: state.has_any({grapple, laurels}, player) or options.logic_rules) - set_rule(multiworld.get_location("Overworld - [East] Grapple Chest", player), + set_rule(world.get_location("Overworld - [East] Grapple Chest"), lambda state: state.has(grapple, player)) - set_rule(multiworld.get_location("Special Shop - Secret Page Pickup", player), + set_rule(world.get_location("Special Shop - Secret Page Pickup"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Sealed Temple - Holy Cross Chest", player), + set_rule(world.get_location("Sealed Temple - Holy Cross Chest"), lambda state: has_ability(holy_cross, state, world) and (state.has(laurels, player) or (has_lantern(state, world) and (has_sword(state, player) or state.has(fire_wand, player))) or has_ice_grapple_logic(False, state, world))) - set_rule(multiworld.get_location("Sealed Temple - Page Pickup", player), + set_rule(world.get_location("Sealed Temple - Page Pickup"), lambda state: state.has(laurels, player) or (has_lantern(state, world) and (has_sword(state, player) or state.has(fire_wand, player))) or has_ice_grapple_logic(False, state, world)) - set_rule(multiworld.get_location("West Furnace - Lantern Pickup", player), + set_rule(world.get_location("West Furnace - Lantern Pickup"), lambda state: has_stick(state, player) or state.has_any({fire_wand, laurels}, player)) - set_rule(multiworld.get_location("Secret Gathering Place - 10 Fairy Reward", player), + set_rule(world.get_location("Secret Gathering Place - 10 Fairy Reward"), lambda state: state.has(fairies, player, 10)) - set_rule(multiworld.get_location("Secret Gathering Place - 20 Fairy Reward", player), + set_rule(world.get_location("Secret Gathering Place - 20 Fairy Reward"), lambda state: state.has(fairies, player, 20)) - set_rule(multiworld.get_location("Coins in the Well - 3 Coins", player), + set_rule(world.get_location("Coins in the Well - 3 Coins"), lambda state: state.has(coins, player, 3)) - set_rule(multiworld.get_location("Coins in the Well - 6 Coins", player), + set_rule(world.get_location("Coins in the Well - 6 Coins"), lambda state: state.has(coins, player, 6)) - set_rule(multiworld.get_location("Coins in the Well - 10 Coins", player), + set_rule(world.get_location("Coins in the Well - 10 Coins"), lambda state: state.has(coins, player, 10)) - set_rule(multiworld.get_location("Coins in the Well - 15 Coins", player), + set_rule(world.get_location("Coins in the Well - 15 Coins"), lambda state: state.has(coins, player, 15)) # East Forest - set_rule(multiworld.get_location("East Forest - Lower Grapple Chest", player), + set_rule(world.get_location("East Forest - Lower Grapple Chest"), lambda state: state.has(grapple, player)) - set_rule(multiworld.get_location("East Forest - Lower Dash Chest", player), + set_rule(world.get_location("East Forest - Lower Dash Chest"), lambda state: state.has_all({grapple, laurels}, player)) - set_rule(multiworld.get_location("East Forest - Ice Rod Grapple Chest", player), + set_rule(world.get_location("East Forest - Ice Rod Grapple Chest"), lambda state: state.has_all({grapple, ice_dagger, fire_wand}, player) and has_ability(icebolt, state, world)) # West Garden - set_rule(multiworld.get_location("West Garden - [North] Across From Page Pickup", player), + set_rule(world.get_location("West Garden - [North] Across From Page Pickup"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("West Garden - [West] In Flooded Walkway", player), + set_rule(world.get_location("West Garden - [West] In Flooded Walkway"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("West Garden - [West Lowlands] Tree Holy Cross Chest", player), + set_rule(world.get_location("West Garden - [West Lowlands] Tree Holy Cross Chest"), lambda state: state.has(laurels, player) and has_ability(holy_cross, state, world)) - set_rule(multiworld.get_location("West Garden - [East Lowlands] Page Behind Ice Dagger House", player), + set_rule(world.get_location("West Garden - [East Lowlands] Page Behind Ice Dagger House"), lambda state: (state.has(laurels, player) and has_ability(prayer, state, world)) or has_ice_grapple_logic(True, state, world)) - set_rule(multiworld.get_location("West Garden - [Central Lowlands] Below Left Walkway", player), + set_rule(world.get_location("West Garden - [Central Lowlands] Below Left Walkway"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("West Garden - [Central Highlands] After Garden Knight", player), + set_rule(world.get_location("West Garden - [Central Highlands] After Garden Knight"), lambda state: state.has(laurels, player) or (has_lantern(state, world) and has_sword(state, player)) or can_ladder_storage(state, world)) # Ruined Atoll - set_rule(multiworld.get_location("Ruined Atoll - [West] Near Kevin Block", player), + set_rule(world.get_location("Ruined Atoll - [West] Near Kevin Block"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Ruined Atoll - [East] Locked Room Lower Chest", player), + set_rule(world.get_location("Ruined Atoll - [East] Locked Room Lower Chest"), lambda state: state.has(laurels, player) or state.has(key, player, 2)) - set_rule(multiworld.get_location("Ruined Atoll - [East] Locked Room Upper Chest", player), + set_rule(world.get_location("Ruined Atoll - [East] Locked Room Upper Chest"), lambda state: state.has(laurels, player) or state.has(key, player, 2)) - set_rule(multiworld.get_location("Librarian - Hexagon Green", player), + set_rule(world.get_location("Librarian - Hexagon Green"), lambda state: has_sword(state, player) or options.logic_rules) # Frog's Domain - set_rule(multiworld.get_location("Frog's Domain - Side Room Grapple Secret", player), + set_rule(world.get_location("Frog's Domain - Side Room Grapple Secret"), lambda state: state.has_any({grapple, laurels}, player)) - set_rule(multiworld.get_location("Frog's Domain - Grapple Above Hot Tub", player), + set_rule(world.get_location("Frog's Domain - Grapple Above Hot Tub"), lambda state: state.has_any({grapple, laurels}, player)) - set_rule(multiworld.get_location("Frog's Domain - Escape Chest", player), + set_rule(world.get_location("Frog's Domain - Escape Chest"), lambda state: state.has_any({grapple, laurels}, player)) # Eastern Vault Fortress - set_rule(multiworld.get_location("Fortress Leaf Piles - Secret Chest", player), + set_rule(world.get_location("Fortress Leaf Piles - Secret Chest"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Fortress Arena - Siege Engine/Vault Key Pickup", player), + set_rule(world.get_location("Fortress Arena - Siege Engine/Vault Key Pickup"), lambda state: has_sword(state, player) and (has_ability(prayer, state, world) or has_ice_grapple_logic(False, state, world))) - set_rule(multiworld.get_location("Fortress Arena - Hexagon Red", player), + set_rule(world.get_location("Fortress Arena - Hexagon Red"), lambda state: state.has(vault_key, player) and (has_ability(prayer, state, world) or has_ice_grapple_logic(False, state, world))) # Beneath the Vault - set_rule(multiworld.get_location("Beneath the Fortress - Bridge", player), + set_rule(world.get_location("Beneath the Fortress - Bridge"), lambda state: has_stick(state, player) or state.has_any({laurels, fire_wand}, player)) - set_rule(multiworld.get_location("Beneath the Fortress - Obscured Behind Waterfall", player), + set_rule(world.get_location("Beneath the Fortress - Obscured Behind Waterfall"), lambda state: has_stick(state, player) and has_lantern(state, world)) # Quarry - set_rule(multiworld.get_location("Quarry - [Central] Above Ladder Dash Chest", player), + set_rule(world.get_location("Quarry - [Central] Above Ladder Dash Chest"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Rooted Ziggurat Upper - Near Bridge Switch", player), + set_rule(world.get_location("Rooted Ziggurat Upper - Near Bridge Switch"), lambda state: has_sword(state, player) or state.has_all({fire_wand, laurels}, player)) # nmg - kill boss scav with orb + firecracker, or similar - set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player), + set_rule(world.get_location("Rooted Ziggurat Lower - Hexagon Blue"), lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules)) # Swamp - set_rule(multiworld.get_location("Cathedral Gauntlet - Gauntlet Reward", player), + set_rule(world.get_location("Cathedral Gauntlet - Gauntlet Reward"), lambda state: (state.has(fire_wand, player) and has_sword(state, player)) and (state.has(laurels, player) or has_ice_grapple_logic(False, state, world))) - set_rule(multiworld.get_location("Swamp - [Entrance] Above Entryway", player), + set_rule(world.get_location("Swamp - [Entrance] Above Entryway"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Swamp - [South Graveyard] Upper Walkway Dash Chest", player), + set_rule(world.get_location("Swamp - [South Graveyard] Upper Walkway Dash Chest"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Swamp - [Outside Cathedral] Obscured Behind Memorial", player), + set_rule(world.get_location("Swamp - [Outside Cathedral] Obscured Behind Memorial"), lambda state: state.has(laurels, player)) - set_rule(multiworld.get_location("Swamp - [South Graveyard] 4 Orange Skulls", player), + set_rule(world.get_location("Swamp - [South Graveyard] 4 Orange Skulls"), lambda state: has_sword(state, player)) # Hero's Grave - set_rule(multiworld.get_location("Hero's Grave - Tooth Relic", player), + set_rule(world.get_location("Hero's Grave - Tooth Relic"), lambda state: state.has(laurels, player) and has_ability(prayer, state, world)) - set_rule(multiworld.get_location("Hero's Grave - Mushroom Relic", player), + set_rule(world.get_location("Hero's Grave - Mushroom Relic"), lambda state: state.has(laurels, player) and has_ability(prayer, state, world)) - set_rule(multiworld.get_location("Hero's Grave - Ash Relic", player), + set_rule(world.get_location("Hero's Grave - Ash Relic"), lambda state: state.has(laurels, player) and has_ability(prayer, state, world)) - set_rule(multiworld.get_location("Hero's Grave - Flowers Relic", player), + set_rule(world.get_location("Hero's Grave - Flowers Relic"), lambda state: state.has(laurels, player) and has_ability(prayer, state, world)) - set_rule(multiworld.get_location("Hero's Grave - Effigy Relic", player), + set_rule(world.get_location("Hero's Grave - Effigy Relic"), lambda state: state.has(laurels, player) and has_ability(prayer, state, world)) - set_rule(multiworld.get_location("Hero's Grave - Feathers Relic", player), + set_rule(world.get_location("Hero's Grave - Feathers Relic"), lambda state: state.has(laurels, player) and has_ability(prayer, state, world)) # Shop - set_rule(multiworld.get_location("Shop - Potion 1", player), + set_rule(world.get_location("Shop - Potion 1"), lambda state: has_sword(state, player)) - set_rule(multiworld.get_location("Shop - Potion 2", player), + set_rule(world.get_location("Shop - Potion 2"), lambda state: has_sword(state, player)) - set_rule(multiworld.get_location("Shop - Coin 1", player), + set_rule(world.get_location("Shop - Coin 1"), lambda state: has_sword(state, player)) - set_rule(multiworld.get_location("Shop - Coin 2", player), + set_rule(world.get_location("Shop - Coin 2"), lambda state: has_sword(state, player)) From 9fbaa6050f40aded0546ea143743e1b3db559aa9 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Wed, 14 Aug 2024 00:21:42 -0400 Subject: [PATCH 40/98] I have no idea (#3791) --- WebHostLib/templates/weightedOptions/macros.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebHostLib/templates/weightedOptions/macros.html b/WebHostLib/templates/weightedOptions/macros.html index a1d319697154..68d3968a178a 100644 --- a/WebHostLib/templates/weightedOptions/macros.html +++ b/WebHostLib/templates/weightedOptions/macros.html @@ -138,7 +138,7 @@ id="{{ option_name }}-{{ key }}" name="{{ option_name }}||{{ key }}" value="1" - checked="{{ "checked" if key in option.default else "" }}" + {{ "checked" if key in option.default }} />