Skip to content

Commit c845303

Browse files
authored
LttP: extract Dungeon and Boss from core (#1787)
1 parent a2ddd5c commit c845303

File tree

13 files changed

+343
-306
lines changed

13 files changed

+343
-306
lines changed

BaseClasses.py

-65
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ def __init__(self, players: int):
9696
self.player_types = {player: NetUtils.SlotType.player for player in self.player_ids}
9797
self.glitch_triforce = False
9898
self.algorithm = 'balanced'
99-
self.dungeons: Dict[Tuple[str, int], Dungeon] = {}
10099
self.groups = {}
101100
self.regions = []
102101
self.shops = []
@@ -386,12 +385,6 @@ def get_location(self, location: str, player: int) -> Location:
386385
self._recache()
387386
return self._location_cache[location, player]
388387

389-
def get_dungeon(self, dungeonname: str, player: int) -> Dungeon:
390-
try:
391-
return self.dungeons[dungeonname, player]
392-
except KeyError as e:
393-
raise KeyError('No such dungeon %s for player %d' % (dungeonname, player)) from e
394-
395388
def get_all_state(self, use_cache: bool) -> CollectionState:
396389
cached = getattr(self, "_all_state", None)
397390
if use_cache and cached:
@@ -801,7 +794,6 @@ class Region:
801794
entrances: List[Entrance]
802795
exits: List[Entrance]
803796
locations: List[Location]
804-
dungeon: Optional[Dungeon] = None
805797

806798
def __init__(self, name: str, player: int, multiworld: MultiWorld, hint: Optional[str] = None):
807799
self.name = name
@@ -904,63 +896,6 @@ def __str__(self):
904896
return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})'
905897

906898

907-
class Dungeon(object):
908-
def __init__(self, name: str, regions: List[Region], big_key: Item, small_keys: List[Item],
909-
dungeon_items: List[Item], player: int):
910-
self.name = name
911-
self.regions = regions
912-
self.big_key = big_key
913-
self.small_keys = small_keys
914-
self.dungeon_items = dungeon_items
915-
self.bosses = dict()
916-
self.player = player
917-
self.multiworld = None
918-
919-
@property
920-
def boss(self) -> Optional[Boss]:
921-
return self.bosses.get(None, None)
922-
923-
@boss.setter
924-
def boss(self, value: Optional[Boss]):
925-
self.bosses[None] = value
926-
927-
@property
928-
def keys(self) -> List[Item]:
929-
return self.small_keys + ([self.big_key] if self.big_key else [])
930-
931-
@property
932-
def all_items(self) -> List[Item]:
933-
return self.dungeon_items + self.keys
934-
935-
def is_dungeon_item(self, item: Item) -> bool:
936-
return item.player == self.player and item.name in (dungeon_item.name for dungeon_item in self.all_items)
937-
938-
def __eq__(self, other: Dungeon) -> bool:
939-
if not other:
940-
return False
941-
return self.name == other.name and self.player == other.player
942-
943-
def __repr__(self):
944-
return self.__str__()
945-
946-
def __str__(self):
947-
return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})'
948-
949-
950-
class Boss():
951-
def __init__(self, name: str, enemizer_name: str, defeat_rule: Callable, player: int):
952-
self.name = name
953-
self.enemizer_name = enemizer_name
954-
self.defeat_rule = defeat_rule
955-
self.player = player
956-
957-
def can_defeat(self, state) -> bool:
958-
return self.defeat_rule(state, self.player)
959-
960-
def __repr__(self):
961-
return f"Boss({self.name})"
962-
963-
964899
class LocationProgressType(IntEnum):
965900
DEFAULT = 1
966901
PRIORITY = 2

worlds/alttp/Bosses.py

+49-27
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
1+
from __future__ import annotations
2+
13
import logging
2-
from typing import Optional, Union, List, Tuple, Callable, Dict
4+
from typing import Optional, Union, List, Tuple, Callable, Dict, TYPE_CHECKING
35

4-
from BaseClasses import Boss
56
from Fill import FillError
67
from .Options import LTTPBosses as Bosses
7-
from .StateHelpers import can_shoot_arrows, can_extend_magic, can_get_good_bee, has_sword, has_beam_sword, has_melee_weapon, has_fire_source
8+
from .StateHelpers import can_shoot_arrows, can_extend_magic, can_get_good_bee, has_sword, has_beam_sword, \
9+
has_melee_weapon, has_fire_source
10+
11+
if TYPE_CHECKING:
12+
from . import ALTTPWorld
13+
14+
15+
class Boss:
16+
def __init__(self, name: str, enemizer_name: str, defeat_rule: Callable, player: int):
17+
self.name = name
18+
self.enemizer_name = enemizer_name
19+
self.defeat_rule = defeat_rule
20+
self.player = player
21+
22+
def can_defeat(self, state) -> bool:
23+
return self.defeat_rule(state, self.player)
24+
25+
def __repr__(self):
26+
return f"Boss({self.name})"
827

928

1029
def BossFactory(boss: str, player: int) -> Optional[Boss]:
@@ -166,10 +185,10 @@ def GanonDefeatRule(state, player: int) -> bool:
166185
]
167186

168187

169-
def place_plando_bosses(bosses: List[str], world, player: int) -> Tuple[List[str], List[Tuple[str, str]]]:
188+
def place_plando_bosses(world: "ALTTPWorld", bosses: List[str]) -> Tuple[List[str], List[Tuple[str, str]]]:
170189
# Most to least restrictive order
171190
boss_locations = boss_location_table.copy()
172-
world.random.shuffle(boss_locations)
191+
world.multiworld.random.shuffle(boss_locations)
173192
boss_locations.sort(key=lambda location: -int(restrictive_boss_locations[location]))
174193
already_placed_bosses: List[str] = []
175194

@@ -184,12 +203,12 @@ def place_plando_bosses(bosses: List[str], world, player: int) -> Tuple[List[str
184203
level = loc[-1]
185204
loc = " ".join(loc[:-1])
186205
loc = loc.title().replace("Of", "of")
187-
place_boss(world, player, boss, loc, level)
206+
place_boss(world, boss, loc, level)
188207
already_placed_bosses.append(boss)
189208
boss_locations.remove((loc, level))
190209
else: # boss chosen with no specified locations
191210
boss = boss.title()
192-
boss_locations, already_placed_bosses = place_where_possible(world, player, boss, boss_locations)
211+
boss_locations, already_placed_bosses = place_where_possible(world, boss, boss_locations)
193212

194213
return already_placed_bosses, boss_locations
195214

@@ -224,20 +243,23 @@ def can_place_boss(boss: str, dungeon_name: str, level: Optional[str] = None) ->
224243
for boss in boss_table if not boss.startswith("Agahnim"))
225244

226245

227-
def place_boss(world, player: int, boss: str, location: str, level: Optional[str]) -> None:
228-
if location == 'Ganons Tower' and world.mode[player] == 'inverted':
246+
def place_boss(world: "ALTTPWorld", boss: str, location: str, level: Optional[str]) -> None:
247+
player = world.player
248+
if location == 'Ganons Tower' and world.multiworld.mode[player] == 'inverted':
229249
location = 'Inverted Ganons Tower'
230250
logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else ''))
231-
world.get_dungeon(location, player).bosses[level] = BossFactory(boss, player)
251+
world.dungeons[location].bosses[level] = BossFactory(boss, player)
232252

233253

234-
def format_boss_location(location: str, level: str) -> str:
235-
return location + (' (' + level + ')' if level else '')
254+
def format_boss_location(location_name: str, level: str) -> str:
255+
return location_name + (' (' + level + ')' if level else '')
236256

237257

238-
def place_bosses(world, player: int) -> None:
258+
def place_bosses(world: "ALTTPWorld") -> None:
259+
multiworld = world.multiworld
260+
player = world.player
239261
# will either be an int or a lower case string with ';' between options
240-
boss_shuffle: Union[str, int] = world.boss_shuffle[player].value
262+
boss_shuffle: Union[str, int] = multiworld.boss_shuffle[player].value
241263
already_placed_bosses: List[str] = []
242264
remaining_locations: List[Tuple[str, str]] = []
243265
# handle plando
@@ -246,14 +268,14 @@ def place_bosses(world, player: int) -> None:
246268
options = boss_shuffle.split(";")
247269
boss_shuffle = Bosses.options[options.pop()]
248270
# place our plando bosses
249-
already_placed_bosses, remaining_locations = place_plando_bosses(options, world, player)
271+
already_placed_bosses, remaining_locations = place_plando_bosses(world, options)
250272
if boss_shuffle == Bosses.option_none: # vanilla boss locations
251273
return
252274

253275
# Most to least restrictive order
254276
if not remaining_locations and not already_placed_bosses:
255277
remaining_locations = boss_location_table.copy()
256-
world.random.shuffle(remaining_locations)
278+
multiworld.random.shuffle(remaining_locations)
257279
remaining_locations.sort(key=lambda location: -int(restrictive_boss_locations[location]))
258280

259281
all_bosses = sorted(boss_table.keys()) # sorted to be deterministic on older pythons
@@ -263,7 +285,7 @@ def place_bosses(world, player: int) -> None:
263285
if boss_shuffle == Bosses.option_basic: # vanilla bosses shuffled
264286
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
265287
else: # all bosses present, the three duplicates chosen at random
266-
bosses = placeable_bosses + world.random.sample(placeable_bosses, 3)
288+
bosses = placeable_bosses + multiworld.random.sample(placeable_bosses, 3)
267289

268290
# there is probably a better way to do this
269291
while already_placed_bosses:
@@ -275,7 +297,7 @@ def place_bosses(world, player: int) -> None:
275297

276298
logging.debug('Bosses chosen %s', bosses)
277299

278-
world.random.shuffle(bosses)
300+
multiworld.random.shuffle(bosses)
279301
for loc, level in remaining_locations:
280302
for _ in range(len(bosses)):
281303
boss = bosses.pop()
@@ -288,39 +310,39 @@ def place_bosses(world, player: int) -> None:
288310
else:
289311
raise FillError(f'Could not place boss for location {format_boss_location(loc, level)}')
290312

291-
place_boss(world, player, boss, loc, level)
313+
place_boss(world, boss, loc, level)
292314

293315
elif boss_shuffle == Bosses.option_chaos: # all bosses chosen at random
294316
for loc, level in remaining_locations:
295317
try:
296-
boss = world.random.choice(
318+
boss = multiworld.random.choice(
297319
[b for b in placeable_bosses if can_place_boss(b, loc, level)])
298320
except IndexError:
299321
raise FillError(f'Could not place boss for location {format_boss_location(loc, level)}')
300322
else:
301-
place_boss(world, player, boss, loc, level)
323+
place_boss(world, boss, loc, level)
302324

303325
elif boss_shuffle == Bosses.option_singularity:
304-
primary_boss = world.random.choice(placeable_bosses)
305-
remaining_boss_locations, _ = place_where_possible(world, player, primary_boss, remaining_locations)
326+
primary_boss = multiworld.random.choice(placeable_bosses)
327+
remaining_boss_locations, _ = place_where_possible(world, primary_boss, remaining_locations)
306328
if remaining_boss_locations:
307329
# pick a boss to go into the remaining locations
308-
remaining_boss = world.random.choice([boss for boss in placeable_bosses if all(
330+
remaining_boss = multiworld.random.choice([boss for boss in placeable_bosses if all(
309331
can_place_boss(boss, loc, level) for loc, level in remaining_boss_locations)])
310-
remaining_boss_locations, _ = place_where_possible(world, player, remaining_boss, remaining_boss_locations)
332+
remaining_boss_locations, _ = place_where_possible(world, remaining_boss, remaining_boss_locations)
311333
if remaining_boss_locations:
312334
raise Exception("Unfilled boss locations!")
313335
else:
314336
raise FillError(f"Could not find boss shuffle mode {boss_shuffle}")
315337

316338

317-
def place_where_possible(world, player: int, boss: str, boss_locations) -> Tuple[List[Tuple[str, str]], List[str]]:
339+
def place_where_possible(world: "ALTTPWorld", boss: str, boss_locations) -> Tuple[List[Tuple[str, str]], List[str]]:
318340
remainder: List[Tuple[str, str]] = []
319341
placed_bosses: List[str] = []
320342
for loc, level in boss_locations:
321343
# place that boss where it can go
322344
if can_place_boss(boss, loc, level):
323-
place_boss(world, player, boss, loc, level)
345+
place_boss(world, boss, loc, level)
324346
placed_bosses.append(boss)
325347
else:
326348
remainder.append((loc, level))

0 commit comments

Comments
 (0)