-
Notifications
You must be signed in to change notification settings - Fork 1.1k
LttP: extract Dungeon and Boss from core #1787
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
334ae97
39ef1c1
6d519f5
59617a5
6184e52
31ce6a1
96bcd08
2ccd041
6bb8feb
c29f5d7
1f2e89b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,26 @@ | ||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import Optional, Union, List, Tuple, Callable, Dict | ||
|
||
from BaseClasses import Boss | ||
from Fill import FillError | ||
from .Options import LTTPBosses as Bosses | ||
from .StateHelpers import can_shoot_arrows, can_extend_magic, can_get_good_bee, has_sword, has_beam_sword, has_melee_weapon, has_fire_source | ||
from .StateHelpers import can_shoot_arrows, can_extend_magic, can_get_good_bee, has_sword, has_beam_sword, \ | ||
has_melee_weapon, has_fire_source | ||
|
||
|
||
class Boss: | ||
def __init__(self, name: str, enemizer_name: str, defeat_rule: Callable, player: int): | ||
self.name = name | ||
self.enemizer_name = enemizer_name | ||
self.defeat_rule = defeat_rule | ||
self.player = player | ||
|
||
def can_defeat(self, state) -> bool: | ||
return self.defeat_rule(state, self.player) | ||
|
||
def __repr__(self): | ||
return f"Boss({self.name})" | ||
|
||
|
||
def BossFactory(boss: str, player: int) -> Optional[Boss]: | ||
|
@@ -166,10 +182,10 @@ def GanonDefeatRule(state, player: int) -> bool: | |
] | ||
|
||
|
||
def place_plando_bosses(bosses: List[str], world, player: int) -> Tuple[List[str], List[Tuple[str, str]]]: | ||
def place_plando_bosses(bosses: List[str], multiworld, world, player: int) -> Tuple[List[str], List[Tuple[str, str]]]: | ||
# Most to least restrictive order | ||
boss_locations = boss_location_table.copy() | ||
world.random.shuffle(boss_locations) | ||
multiworld.random.shuffle(boss_locations) | ||
boss_locations.sort(key=lambda location: -int(restrictive_boss_locations[location])) | ||
already_placed_bosses: List[str] = [] | ||
|
||
|
@@ -184,12 +200,12 @@ def place_plando_bosses(bosses: List[str], world, player: int) -> Tuple[List[str | |
level = loc[-1] | ||
loc = " ".join(loc[:-1]) | ||
loc = loc.title().replace("Of", "of") | ||
place_boss(world, player, boss, loc, level) | ||
place_boss(multiworld, world, player, boss, loc, level) | ||
already_placed_bosses.append(boss) | ||
boss_locations.remove((loc, level)) | ||
else: # boss chosen with no specified locations | ||
boss = boss.title() | ||
boss_locations, already_placed_bosses = place_where_possible(world, player, boss, boss_locations) | ||
boss_locations, already_placed_bosses = place_where_possible(multiworld, world, player, boss, boss_locations) | ||
|
||
return already_placed_bosses, boss_locations | ||
|
||
|
@@ -224,20 +240,20 @@ def can_place_boss(boss: str, dungeon_name: str, level: Optional[str] = None) -> | |
for boss in boss_table if not boss.startswith("Agahnim")) | ||
|
||
|
||
def place_boss(world, player: int, boss: str, location: str, level: Optional[str]) -> None: | ||
if location == 'Ganons Tower' and world.mode[player] == 'inverted': | ||
def place_boss(multiworld, world, player: int, boss: str, location: str, level: Optional[str]) -> None: | ||
if location == 'Ganons Tower' and multiworld.mode[player] == 'inverted': | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (I agree, with el-u's comment. It'd be better to either pass multiworld+player or world, not both.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be fixed now |
||
location = 'Inverted Ganons Tower' | ||
logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else '')) | ||
world.get_dungeon(location, player).bosses[level] = BossFactory(boss, player) | ||
world.dungeons[location].bosses[level] = BossFactory(boss, player) | ||
|
||
|
||
def format_boss_location(location: str, level: str) -> str: | ||
return location + (' (' + level + ')' if level else '') | ||
|
||
|
||
def place_bosses(world, player: int) -> None: | ||
def place_bosses(multiworld, world, player: int) -> None: | ||
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why pass around (Yes, I realize that in the old code the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be fixed now |
||
# will either be an int or a lower case string with ';' between options | ||
boss_shuffle: Union[str, int] = world.boss_shuffle[player].value | ||
boss_shuffle: Union[str, int] = multiworld.boss_shuffle[player].value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above and also the random might go away when/if we fade out non-slot-random? multiworld is only required for random and the one option, so you could capture both from world.multiworld at the top. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be fixed now |
||
already_placed_bosses: List[str] = [] | ||
remaining_locations: List[Tuple[str, str]] = [] | ||
# handle plando | ||
|
@@ -246,14 +262,14 @@ def place_bosses(world, player: int) -> None: | |
options = boss_shuffle.split(";") | ||
boss_shuffle = Bosses.options[options.pop()] | ||
# place our plando bosses | ||
already_placed_bosses, remaining_locations = place_plando_bosses(options, world, player) | ||
already_placed_bosses, remaining_locations = place_plando_bosses(options, multiworld, world, player) | ||
if boss_shuffle == Bosses.option_none: # vanilla boss locations | ||
return | ||
|
||
# Most to least restrictive order | ||
if not remaining_locations and not already_placed_bosses: | ||
remaining_locations = boss_location_table.copy() | ||
world.random.shuffle(remaining_locations) | ||
multiworld.random.shuffle(remaining_locations) | ||
remaining_locations.sort(key=lambda location: -int(restrictive_boss_locations[location])) | ||
|
||
all_bosses = sorted(boss_table.keys()) # sorted to be deterministic on older pythons | ||
|
@@ -263,7 +279,7 @@ def place_bosses(world, player: int) -> None: | |
if boss_shuffle == Bosses.option_basic: # vanilla bosses shuffled | ||
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm'] | ||
else: # all bosses present, the three duplicates chosen at random | ||
bosses = placeable_bosses + world.random.sample(placeable_bosses, 3) | ||
bosses = placeable_bosses + multiworld.random.sample(placeable_bosses, 3) | ||
|
||
# there is probably a better way to do this | ||
while already_placed_bosses: | ||
|
@@ -275,7 +291,7 @@ def place_bosses(world, player: int) -> None: | |
|
||
logging.debug('Bosses chosen %s', bosses) | ||
|
||
world.random.shuffle(bosses) | ||
multiworld.random.shuffle(bosses) | ||
for loc, level in remaining_locations: | ||
for _ in range(len(bosses)): | ||
boss = bosses.pop() | ||
|
@@ -288,39 +304,40 @@ def place_bosses(world, player: int) -> None: | |
else: | ||
raise FillError(f'Could not place boss for location {format_boss_location(loc, level)}') | ||
|
||
place_boss(world, player, boss, loc, level) | ||
place_boss(multiworld, world, player, boss, loc, level) | ||
|
||
elif boss_shuffle == Bosses.option_chaos: # all bosses chosen at random | ||
for loc, level in remaining_locations: | ||
try: | ||
boss = world.random.choice( | ||
boss = multiworld.random.choice( | ||
[b for b in placeable_bosses if can_place_boss(b, loc, level)]) | ||
except IndexError: | ||
raise FillError(f'Could not place boss for location {format_boss_location(loc, level)}') | ||
else: | ||
place_boss(world, player, boss, loc, level) | ||
place_boss(multiworld, world, player, boss, loc, level) | ||
|
||
elif boss_shuffle == Bosses.option_singularity: | ||
primary_boss = world.random.choice(placeable_bosses) | ||
remaining_boss_locations, _ = place_where_possible(world, player, primary_boss, remaining_locations) | ||
primary_boss = multiworld.random.choice(placeable_bosses) | ||
remaining_boss_locations, _ = place_where_possible(multiworld, world, player, primary_boss, remaining_locations) | ||
if remaining_boss_locations: | ||
# pick a boss to go into the remaining locations | ||
remaining_boss = world.random.choice([boss for boss in placeable_bosses if all( | ||
remaining_boss = multiworld.random.choice([boss for boss in placeable_bosses if all( | ||
can_place_boss(boss, loc, level) for loc, level in remaining_boss_locations)]) | ||
remaining_boss_locations, _ = place_where_possible(world, player, remaining_boss, remaining_boss_locations) | ||
remaining_boss_locations, _ = place_where_possible(multiworld, world, player, remaining_boss, | ||
remaining_boss_locations) | ||
if remaining_boss_locations: | ||
raise Exception("Unfilled boss locations!") | ||
else: | ||
raise FillError(f"Could not find boss shuffle mode {boss_shuffle}") | ||
|
||
|
||
def place_where_possible(world, player: int, boss: str, boss_locations) -> Tuple[List[Tuple[str, str]], List[str]]: | ||
def place_where_possible(multiworld, world, player: int, boss: str, boss_locations) -> Tuple[List[Tuple[str, str]], List[str]]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here we only pass it down to the next stage There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be fixed now |
||
remainder: List[Tuple[str, str]] = [] | ||
placed_bosses: List[str] = [] | ||
for loc, level in boss_locations: | ||
# place that boss where it can go | ||
if can_place_boss(boss, loc, level): | ||
place_boss(world, player, boss, loc, level) | ||
place_boss(multiworld, world, player, boss, loc, level) | ||
placed_bosses.append(boss) | ||
else: | ||
remainder.append((loc, level)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we only use multiworld here for random. see the other comment about random
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am keeping this intentionally like this for now.