1
+ from __future__ import annotations
2
+
1
3
import logging
2
- from typing import Optional , Union , List , Tuple , Callable , Dict
4
+ from typing import Optional , Union , List , Tuple , Callable , Dict , TYPE_CHECKING
3
5
4
- from BaseClasses import Boss
5
6
from Fill import FillError
6
7
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 } )"
8
27
9
28
10
29
def BossFactory (boss : str , player : int ) -> Optional [Boss ]:
@@ -166,10 +185,10 @@ def GanonDefeatRule(state, player: int) -> bool:
166
185
]
167
186
168
187
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 ]]]:
170
189
# Most to least restrictive order
171
190
boss_locations = boss_location_table .copy ()
172
- world .random .shuffle (boss_locations )
191
+ world .multiworld . random .shuffle (boss_locations )
173
192
boss_locations .sort (key = lambda location : - int (restrictive_boss_locations [location ]))
174
193
already_placed_bosses : List [str ] = []
175
194
@@ -184,12 +203,12 @@ def place_plando_bosses(bosses: List[str], world, player: int) -> Tuple[List[str
184
203
level = loc [- 1 ]
185
204
loc = " " .join (loc [:- 1 ])
186
205
loc = loc .title ().replace ("Of" , "of" )
187
- place_boss (world , player , boss , loc , level )
206
+ place_boss (world , boss , loc , level )
188
207
already_placed_bosses .append (boss )
189
208
boss_locations .remove ((loc , level ))
190
209
else : # boss chosen with no specified locations
191
210
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 )
193
212
194
213
return already_placed_bosses , boss_locations
195
214
@@ -224,20 +243,23 @@ def can_place_boss(boss: str, dungeon_name: str, level: Optional[str] = None) ->
224
243
for boss in boss_table if not boss .startswith ("Agahnim" ))
225
244
226
245
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' :
229
249
location = 'Inverted Ganons Tower'
230
250
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 )
232
252
233
253
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 '' )
236
256
237
257
238
- def place_bosses (world , player : int ) -> None :
258
+ def place_bosses (world : "ALTTPWorld" ) -> None :
259
+ multiworld = world .multiworld
260
+ player = world .player
239
261
# 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
241
263
already_placed_bosses : List [str ] = []
242
264
remaining_locations : List [Tuple [str , str ]] = []
243
265
# handle plando
@@ -246,14 +268,14 @@ def place_bosses(world, player: int) -> None:
246
268
options = boss_shuffle .split (";" )
247
269
boss_shuffle = Bosses .options [options .pop ()]
248
270
# 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 )
250
272
if boss_shuffle == Bosses .option_none : # vanilla boss locations
251
273
return
252
274
253
275
# Most to least restrictive order
254
276
if not remaining_locations and not already_placed_bosses :
255
277
remaining_locations = boss_location_table .copy ()
256
- world .random .shuffle (remaining_locations )
278
+ multiworld .random .shuffle (remaining_locations )
257
279
remaining_locations .sort (key = lambda location : - int (restrictive_boss_locations [location ]))
258
280
259
281
all_bosses = sorted (boss_table .keys ()) # sorted to be deterministic on older pythons
@@ -263,7 +285,7 @@ def place_bosses(world, player: int) -> None:
263
285
if boss_shuffle == Bosses .option_basic : # vanilla bosses shuffled
264
286
bosses = placeable_bosses + ['Armos Knights' , 'Lanmolas' , 'Moldorm' ]
265
287
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 )
267
289
268
290
# there is probably a better way to do this
269
291
while already_placed_bosses :
@@ -275,7 +297,7 @@ def place_bosses(world, player: int) -> None:
275
297
276
298
logging .debug ('Bosses chosen %s' , bosses )
277
299
278
- world .random .shuffle (bosses )
300
+ multiworld .random .shuffle (bosses )
279
301
for loc , level in remaining_locations :
280
302
for _ in range (len (bosses )):
281
303
boss = bosses .pop ()
@@ -288,39 +310,39 @@ def place_bosses(world, player: int) -> None:
288
310
else :
289
311
raise FillError (f'Could not place boss for location { format_boss_location (loc , level )} ' )
290
312
291
- place_boss (world , player , boss , loc , level )
313
+ place_boss (world , boss , loc , level )
292
314
293
315
elif boss_shuffle == Bosses .option_chaos : # all bosses chosen at random
294
316
for loc , level in remaining_locations :
295
317
try :
296
- boss = world .random .choice (
318
+ boss = multiworld .random .choice (
297
319
[b for b in placeable_bosses if can_place_boss (b , loc , level )])
298
320
except IndexError :
299
321
raise FillError (f'Could not place boss for location { format_boss_location (loc , level )} ' )
300
322
else :
301
- place_boss (world , player , boss , loc , level )
323
+ place_boss (world , boss , loc , level )
302
324
303
325
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 )
306
328
if remaining_boss_locations :
307
329
# 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 (
309
331
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 )
311
333
if remaining_boss_locations :
312
334
raise Exception ("Unfilled boss locations!" )
313
335
else :
314
336
raise FillError (f"Could not find boss shuffle mode { boss_shuffle } " )
315
337
316
338
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 ]]:
318
340
remainder : List [Tuple [str , str ]] = []
319
341
placed_bosses : List [str ] = []
320
342
for loc , level in boss_locations :
321
343
# place that boss where it can go
322
344
if can_place_boss (boss , loc , level ):
323
- place_boss (world , player , boss , loc , level )
345
+ place_boss (world , boss , loc , level )
324
346
placed_bosses .append (boss )
325
347
else :
326
348
remainder .append ((loc , level ))
0 commit comments