Skip to content

Commit

Permalink
More typing and small fixes (ArchipelagoMW#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
Exempt-Medic authored Aug 8, 2024
1 parent 27d4c41 commit 443a285
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 32 deletions.
4 changes: 2 additions & 2 deletions worlds/dark_souls_3/Bosses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# available before his fight.

from dataclasses import dataclass, field
from typing import Optional, Set
from typing import Set


@dataclass
Expand All @@ -26,7 +26,7 @@ class DS3BossInfo:
aren't randomized.
"""

locations: Optional[Set[str]] = field(default_factory=set)
locations: Set[str] = field(default_factory=set)
"""Additional individual locations that can't be accessed until the boss is dead."""


Expand Down
4 changes: 2 additions & 2 deletions worlds/dark_souls_3/Items.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from dataclasses import dataclass
import dataclasses
from enum import IntEnum
from typing import cast, ClassVar, Dict, Generator, List, Optional, Set
from typing import Any, cast, ClassVar, Dict, Generator, List, Optional, Set

from BaseClasses import Item, ItemClassification

Expand Down Expand Up @@ -242,7 +242,7 @@ def upgrade(self, level: int) -> "DS3ItemData":
def __hash__(self) -> int:
return (self.name, self.ds3_code).__hash__()

def __eq__(self, other: any) -> bool:
def __eq__(self, other: Any) -> bool:
if isinstance(other, self.__class__):
return self.name == other.name and self.ds3_code == other.ds3_code
else:
Expand Down
4 changes: 0 additions & 4 deletions worlds/dark_souls_3/Options.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from copy import deepcopy
from dataclasses import dataclass
import json
from typing import Any, Dict
Expand Down Expand Up @@ -313,9 +312,6 @@ class RandomEnemyPresetOption(OptionDict):
"OopsAll", "Boss", "Miniboss", "Basic", "BuffBasicEnemiesAsBosses",
"DontRandomize", "RemoveSource", "Enemies"]

def __init__(self, value: Dict[str, Any]):
self.value = deepcopy(value)

@classmethod
def get_option_name(cls, value: Dict[str, Any]) -> str:
return json.dumps(value)
Expand Down
53 changes: 29 additions & 24 deletions worlds/dark_souls_3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ class DarkSouls3World(World):
web = DarkSouls3Web()
base_id = 100000
required_client_version = (0, 4, 2)
item_name_to_id = {data.name: data.ap_code for data in item_dictionary.values()}
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
Expand Down Expand Up @@ -302,30 +303,31 @@ def create_items(self) -> None:
# Gather all default items on randomized locations
self.local_itempool = []
num_required_extra_items = 0
for location in self.multiworld.get_unfilled_locations(self.player):
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.")

item = item_dictionary[location.data.default_item_name]
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(location.data.default_item_name))
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 location.data.default_item_name in item_set:
if default_item_name in item_set:
num_required_extra_items += 1
else:
item_set.add(location.data.default_item_name)
self.local_itempool.append(self.create_item(location.data.default_item_name))
item_set.add(default_item_name)
self.local_itempool.append(self.create_item(default_item_name))

injectables = self._create_injectable_items(num_required_extra_items)
num_required_extra_items -= len(injectables)
self.local_itempool.extend(injectables)

# Extra filler items for locations containing skip items
self.local_itempool.extend(self.create_filler() for _ in range(num_required_extra_items))
self.local_itempool.extend(self.create_item(self.get_filler_item_name()) for _ in range(num_required_extra_items))

# Potentially fill some items locally and remove them from the itempool
self._fill_local_items()
Expand Down Expand Up @@ -456,7 +458,7 @@ def _fill_local_items(self) -> None:
def _fill_local_item(
self, name: str,
regions: List[str],
additional_condition: Optional[Callable[[DarkSouls3Location], bool]] = None,
additional_condition: Optional[Callable[[DS3LocationData], bool]] = None,
) -> None:
"""Chooses a valid location for the item with the given name and places it there.
Expand Down Expand Up @@ -489,7 +491,7 @@ def _fill_local_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.multiworld.get_locations(self.player) if location.data.default_item_name == item.name),
(location for location in self._get_our_locations() if location.data.default_item_name == item.name),
None
)
if location: self._replace_with_filler(location)
Expand Down Expand Up @@ -969,7 +971,7 @@ def _add_npc_rules(self) -> None:
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 deafeating Pontiff which requires defeating Greatwood in US
# 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")
Expand Down Expand Up @@ -1215,7 +1217,7 @@ def _add_allow_useful_location_rules(self) -> None:
manually add item rules to exclude important items.
"""

all_locations = self.multiworld.get_locations(self.player)
all_locations = self._get_our_locations()

allow_useful_locations = (
(
Expand Down Expand Up @@ -1336,8 +1338,8 @@ def _is_location_available(

return (
not data.is_event
and (not data.dlc or self.options.enable_dlc)
and (not data.ngp or self.options.enable_ngp)
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
Expand Down Expand Up @@ -1377,7 +1379,7 @@ def post_fill(self):
]

# All items in the base game in approximately the order they appear
all_item_order = [
all_item_order: List[DS3ItemData] = [
item_dictionary[location.default_item_name]
for region in region_order
# Shuffle locations within each region.
Expand All @@ -1386,7 +1388,7 @@ def post_fill(self):
]

# All DarkSouls3Items for this world that have been assigned anywhere, grouped by name
full_items_by_name = defaultdict(list)
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)
Expand All @@ -1401,7 +1403,7 @@ def smooth_items(item_order: List[Union[DS3ItemData, DarkSouls3Item]]) -> None:
"""

# Convert items to full DarkSouls3Items.
item_order = [
converted_item_order: List[DarkSouls3Item] = [
item for item in (
(
# full_items_by_name won't contain DLC items if the DLC is disabled.
Expand All @@ -1414,7 +1416,7 @@ def smooth_items(item_order: List[Union[DS3ItemData, DarkSouls3Item]]) -> None:
if item and item.code is not None
]

names = {item.name for item in item_order}
names = {item.name for item in converted_item_order}

all_matching_locations = [
loc
Expand All @@ -1426,10 +1428,10 @@ def smooth_items(item_order: List[Union[DS3ItemData, DarkSouls3Item]]) -> None:
# 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(item_order) < len(all_matching_locations):
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(item_order)} items to smooth."
f"contain smoothed items, but only {len(converted_item_order)} items to smooth."
)

for sphere in locations_by_sphere:
Expand All @@ -1442,7 +1444,7 @@ def smooth_items(item_order: List[Union[DS3ItemData, DarkSouls3Item]]) -> None:

# Give offworld regions the last (best) items within a given sphere
for location in onworld + offworld:
new_item = self._pop_item(location, item_order)
new_item = self._pop_item(location, converted_item_order)
location.item = new_item
new_item.location = location

Expand Down Expand Up @@ -1480,8 +1482,8 @@ def _shuffle(self, seq: Sequence) -> List:
def _pop_item(
self,
location: Location,
items: List[Union[DS3ItemData, DarkSouls3Item]]
) -> Union[DS3ItemData, DarkSouls3Item]:
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):
Expand All @@ -1490,6 +1492,9 @@ def _pop_item(
# 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] = {}

Expand All @@ -1516,7 +1521,7 @@ def fill_slot_data(self) -> Dict[str, object]:
# 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 self.multiworld.get_filled_locations(self.player):
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):
Expand Down

0 comments on commit 443a285

Please sign in to comment.