| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | import orjson | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | from pkgutil import get_data | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  | from copy import deepcopy | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  | from typing import TYPE_CHECKING, List, Dict, Optional, Union, Callable | 
					
						
							|  |  |  | from BaseClasses import Region, CollectionState | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | from worlds.generic.Rules import add_item_rule | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  | from .locations import KDL3Location | 
					
						
							|  |  |  | from .names import location_name | 
					
						
							|  |  |  | from .options import BossShuffle | 
					
						
							|  |  |  | from .room import KDL3Room | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 11:52:23 -05:00
										 |  |  | if TYPE_CHECKING: | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |     from . import KDL3World | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | default_levels = { | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |     1: [0x770000, 0x770001, 0x770002, 0x770003, 0x770004, 0x770005, 0x770200], | 
					
						
							|  |  |  |     2: [0x770006, 0x770007, 0x770008, 0x770009, 0x77000A, 0x77000B, 0x770201], | 
					
						
							|  |  |  |     3: [0x77000C, 0x77000D, 0x77000E, 0x77000F, 0x770010, 0x770011, 0x770202], | 
					
						
							|  |  |  |     4: [0x770012, 0x770013, 0x770014, 0x770015, 0x770016, 0x770017, 0x770203], | 
					
						
							|  |  |  |     5: [0x770018, 0x770019, 0x77001A, 0x77001B, 0x77001C, 0x77001D, 0x770204], | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | first_stage_blacklist = { | 
					
						
							|  |  |  |     # We want to confirm that the first stage can be completed without any items | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |     0x77000A,  # 2-5 needs Kine | 
					
						
							|  |  |  |     0x770010,  # 3-5 needs Cutter | 
					
						
							|  |  |  |     0x77001B,  # 5-4 needs Burning | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  | first_world_limit = { | 
					
						
							|  |  |  |     # We need to limit the number of very restrictive stages in level 1 on solo gens | 
					
						
							|  |  |  |     *first_stage_blacklist,  # all three of the blacklist stages need 2+ items for both checks | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |     0x770006, | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  |     0x770007, | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |     0x770012, | 
					
						
							|  |  |  |     0x77001D, | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 11:52:23 -05:00
										 |  |  | def generate_valid_level(world: "KDL3World", level: int, stage: int, | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |                          possible_stages: List[int], placed_stages: List[Optional[int]]) -> int: | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  |     new_stage = world.random.choice(possible_stages) | 
					
						
							|  |  |  |     if level == 1: | 
					
						
							|  |  |  |         if stage == 0 and new_stage in first_stage_blacklist: | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |             possible_stages.remove(new_stage) | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  |             return generate_valid_level(world, level, stage, possible_stages, placed_stages) | 
					
						
							| 
									
										
										
										
											2024-04-18 11:52:23 -05:00
										 |  |  |         elif (not (world.multiworld.players > 1 or world.options.consumables or world.options.starsanity) and | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |               new_stage in first_world_limit and | 
					
						
							|  |  |  |               sum(p_stage in first_world_limit for p_stage in placed_stages) | 
					
						
							| 
									
										
										
										
											2024-04-18 11:52:23 -05:00
										 |  |  |               >= (2 if world.options.open_world else 1)): | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  |             return generate_valid_level(world, level, stage, possible_stages, placed_stages) | 
					
						
							|  |  |  |     return new_stage | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  | def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]) -> None: | 
					
						
							|  |  |  |     level_names = {location_name.level_names[level]: level for level in location_name.level_names} | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |     room_data = orjson.loads(get_data(__name__, os.path.join("data", "Rooms.json"))) | 
					
						
							| 
									
										
										
										
											2024-04-18 11:52:23 -05:00
										 |  |  |     rooms: Dict[str, KDL3Room] = dict() | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |     for room_entry in room_data: | 
					
						
							|  |  |  |         room = KDL3Room(room_entry["name"], world.player, world.multiworld, None, room_entry["level"], | 
					
						
							|  |  |  |                         room_entry["stage"], room_entry["room"], room_entry["pointer"], room_entry["music"], | 
					
						
							|  |  |  |                         room_entry["default_exits"], room_entry["animal_pointers"], room_entry["enemies"], | 
					
						
							|  |  |  |                         room_entry["entity_load"], room_entry["consumables"], room_entry["consumables_pointer"]) | 
					
						
							|  |  |  |         room.add_locations({location: world.location_name_to_id[location] if location in world.location_name_to_id else | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |         None for location in room_entry["locations"] | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |                             if (not any(x in location for x in ["1-Up", "Maxim"]) or | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  |                                 world.options.consumables.value) and ("Star" not in location | 
					
						
							|  |  |  |                                                                       or world.options.starsanity.value)}, | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |                            KDL3Location) | 
					
						
							|  |  |  |         rooms[room.name] = room | 
					
						
							|  |  |  |         for location in room.locations: | 
					
						
							|  |  |  |             if "Animal" in location.name: | 
					
						
							|  |  |  |                 add_item_rule(location, lambda item: item.name in { | 
					
						
							|  |  |  |                     "Rick Spawn", "Kine Spawn", "Coo Spawn", "Nago Spawn", "ChuChu Spawn", "Pitch Spawn" | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |     world.rooms = list(rooms.values()) | 
					
						
							|  |  |  |     world.multiworld.regions.extend(world.rooms) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 11:52:23 -05:00
										 |  |  |     first_rooms: Dict[int, KDL3Room] = dict() | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  |     for name, room in rooms.items(): | 
					
						
							|  |  |  |         if room.room == 0: | 
					
						
							|  |  |  |             if room.stage == 7: | 
					
						
							|  |  |  |                 first_rooms[0x770200 + room.level - 1] = room | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |                 first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage - 1] = room | 
					
						
							|  |  |  |         exits: Dict[str, Callable[[CollectionState], bool]] = dict() | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  |         for def_exit in room.default_exits: | 
					
						
							|  |  |  |             target = f"{level_names[room.level]} {room.stage} - {def_exit['room']}" | 
					
						
							|  |  |  |             access_rule = tuple(def_exit["access_rule"]) | 
					
						
							|  |  |  |             exits[target] = lambda state, rule=access_rule: state.has_all(rule, world.player) | 
					
						
							|  |  |  |         room.add_exits( | 
					
						
							|  |  |  |             exits.keys(), | 
					
						
							|  |  |  |             exits | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         if world.options.open_world: | 
					
						
							|  |  |  |             if any("Complete" in location.name for location in room.locations): | 
					
						
							|  |  |  |                 room.add_locations({f"{level_names[room.level]} {room.stage} - Stage Completion": None}, | 
					
						
							|  |  |  |                                    KDL3Location) | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for level in world.player_levels: | 
					
						
							|  |  |  |         for stage in range(6): | 
					
						
							|  |  |  |             proper_stage = world.player_levels[level][stage] | 
					
						
							|  |  |  |             stage_name = world.multiworld.get_location(world.location_id_to_name[proper_stage], | 
					
						
							|  |  |  |                                                        world.player).name.replace(" - Complete", "") | 
					
						
							|  |  |  |             stage_regions = [rooms[room] for room in rooms if stage_name in rooms[room].name] | 
					
						
							|  |  |  |             for region in stage_regions: | 
					
						
							|  |  |  |                 region.level = level | 
					
						
							|  |  |  |                 region.stage = stage | 
					
						
							|  |  |  |             if world.options.open_world or stage == 0: | 
					
						
							|  |  |  |                 level_regions[level].add_exits([first_rooms[proper_stage].name]) | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  |                 world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][stage - 1]], | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |                                               world.player).parent_region.add_exits([first_rooms[proper_stage].name]) | 
					
						
							| 
									
										
										
										
											2024-04-11 17:29:40 -05:00
										 |  |  |         if world.options.open_world: | 
					
						
							|  |  |  |             level_regions[level].add_exits([first_rooms[0x770200 + level - 1].name]) | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |             world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][5]], world.player) \ | 
					
						
							| 
									
										
										
										
											2024-04-11 17:29:40 -05:00
										 |  |  |                 .parent_region.add_exits([first_rooms[0x770200 + level - 1].name]) | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  | def generate_valid_levels(world: "KDL3World", shuffle_mode: int) -> Dict[int, List[int]]: | 
					
						
							|  |  |  |     if shuffle_mode: | 
					
						
							|  |  |  |         levels: Dict[int, List[Optional[int]]] = { | 
					
						
							|  |  |  |             1: [None] * 7, | 
					
						
							|  |  |  |             2: [None] * 7, | 
					
						
							|  |  |  |             3: [None] * 7, | 
					
						
							|  |  |  |             4: [None] * 7, | 
					
						
							|  |  |  |             5: [None] * 7, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         possible_stages = [default_levels[level][stage] for level in default_levels for stage in range(6)] | 
					
						
							|  |  |  |         if world.options.plando_connections: | 
					
						
							|  |  |  |             for connection in world.options.plando_connections: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     entrance_world, entrance_stage = connection.entrance.rsplit(" ", 1) | 
					
						
							|  |  |  |                     stage_world, stage_stage = connection.exit.rsplit(" ", 1) | 
					
						
							|  |  |  |                     new_stage = default_levels[location_name.level_names[stage_world.strip()]][int(stage_stage) - 1] | 
					
						
							|  |  |  |                     levels[location_name.level_names[entrance_world.strip()]][int(entrance_stage) - 1] = new_stage | 
					
						
							|  |  |  |                     possible_stages.remove(new_stage) | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |                 except Exception: | 
					
						
							|  |  |  |                     raise Exception( | 
					
						
							|  |  |  |                         f"Invalid connection: {connection.entrance} =>" | 
					
						
							|  |  |  |                         f" {connection.exit} for player {world.player} ({world.player_name})") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for level in range(1, 6): | 
					
						
							|  |  |  |             for stage in range(6): | 
					
						
							|  |  |  |                 # Randomize bosses separately | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |                 if levels[level][stage] is None: | 
					
						
							|  |  |  |                     stage_candidates = [candidate for candidate in possible_stages | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |                                         if (shuffle_mode == 1 and candidate in default_levels[level]) | 
					
						
							|  |  |  |                                         or (shuffle_mode == 2 and (candidate & 0x00FFFF) % 6 == stage) | 
					
						
							|  |  |  |                                         or (shuffle_mode == 3) | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |                                         ] | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |                     if not stage_candidates: | 
					
						
							|  |  |  |                         raise Exception( | 
					
						
							|  |  |  |                             f"Failed to find valid stage for {level}-{stage}. Remaining Stages:{possible_stages}") | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  |                     new_stage = generate_valid_level(world, level, stage, stage_candidates, levels[level]) | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |                     possible_stages.remove(new_stage) | 
					
						
							|  |  |  |                     levels[level][stage] = new_stage | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |     else: | 
					
						
							|  |  |  |         levels = deepcopy(default_levels) | 
					
						
							|  |  |  |         for level in levels: | 
					
						
							|  |  |  |             levels[level][6] = None | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |     # now handle bosses | 
					
						
							| 
									
										
										
										
											2024-04-18 11:52:23 -05:00
										 |  |  |     boss_shuffle: Union[int, str] = world.options.boss_shuffle.value | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |     plando_bosses = [] | 
					
						
							|  |  |  |     if isinstance(boss_shuffle, str): | 
					
						
							|  |  |  |         # boss plando | 
					
						
							|  |  |  |         options = boss_shuffle.split(";") | 
					
						
							|  |  |  |         boss_shuffle = BossShuffle.options[options.pop()] | 
					
						
							|  |  |  |         for option in options: | 
					
						
							|  |  |  |             if "-" in option: | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |                 loc, plando_boss = option.split("-") | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |                 loc = loc.title() | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |                 plando_boss = plando_boss.title() | 
					
						
							|  |  |  |                 levels[location_name.level_names[loc]][6] = location_name.boss_names[plando_boss] | 
					
						
							|  |  |  |                 plando_bosses.append(location_name.boss_names[plando_boss]) | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 option = option.title() | 
					
						
							|  |  |  |                 for level in levels: | 
					
						
							|  |  |  |                     if levels[level][6] is None: | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |                         levels[level][6] = location_name.boss_names[option] | 
					
						
							|  |  |  |                         plando_bosses.append(location_name.boss_names[option]) | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if boss_shuffle > 0: | 
					
						
							|  |  |  |         if boss_shuffle == BossShuffle.option_full: | 
					
						
							|  |  |  |             possible_bosses = [default_levels[world.random.randint(1, 5)][6] | 
					
						
							|  |  |  |                                for _ in range(5 - len(plando_bosses))] | 
					
						
							|  |  |  |         elif boss_shuffle == BossShuffle.option_singularity: | 
					
						
							|  |  |  |             boss = world.random.randint(1, 5) | 
					
						
							|  |  |  |             possible_bosses = [default_levels[boss][6] for _ in range(5 - len(plando_bosses))] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             possible_bosses = [default_levels[level][6] for level in default_levels | 
					
						
							|  |  |  |                                if default_levels[level][6] not in plando_bosses] | 
					
						
							|  |  |  |         for level in levels: | 
					
						
							|  |  |  |             if levels[level][6] is None: | 
					
						
							|  |  |  |                 boss = world.random.choice(possible_bosses) | 
					
						
							|  |  |  |                 levels[level][6] = boss | 
					
						
							|  |  |  |                 possible_bosses.remove(boss) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         for level in levels: | 
					
						
							|  |  |  |             if levels[level][6] is None: | 
					
						
							|  |  |  |                 levels[level][6] = default_levels[level][6] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for level in levels: | 
					
						
							|  |  |  |         for stage in range(7): | 
					
						
							|  |  |  |             assert levels[level][stage] is not None, "Level tried to be sent with a None stage, incorrect plando?" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return levels | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def create_levels(world: "KDL3World") -> None: | 
					
						
							|  |  |  |     menu = Region("Menu", world.player, world.multiworld) | 
					
						
							|  |  |  |     level1 = Region("Grass Land", world.player, world.multiworld) | 
					
						
							|  |  |  |     level2 = Region("Ripple Field", world.player, world.multiworld) | 
					
						
							|  |  |  |     level3 = Region("Sand Canyon", world.player, world.multiworld) | 
					
						
							|  |  |  |     level4 = Region("Cloudy Park", world.player, world.multiworld) | 
					
						
							|  |  |  |     level5 = Region("Iceberg", world.player, world.multiworld) | 
					
						
							|  |  |  |     level6 = Region("Hyper Zone", world.player, world.multiworld) | 
					
						
							|  |  |  |     levels = { | 
					
						
							|  |  |  |         1: level1, | 
					
						
							|  |  |  |         2: level2, | 
					
						
							|  |  |  |         3: level3, | 
					
						
							|  |  |  |         4: level4, | 
					
						
							|  |  |  |         5: level5, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     level_shuffle = world.options.stage_shuffle.value | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |     if hasattr(world.multiworld, "re_gen_passthrough"): | 
					
						
							|  |  |  |         world.player_levels = getattr(world.multiworld, "re_gen_passthrough")["Kirby's Dream Land 3"]["player_levels"] | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         world.player_levels = generate_valid_levels(world, level_shuffle) | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-28 09:41:32 -05:00
										 |  |  |     generate_rooms(world, levels) | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-31 06:15:00 -05:00
										 |  |  |     level6.add_locations({location_name.goals[world.options.goal.value]: None}, KDL3Location) | 
					
						
							| 
									
										
										
										
											2024-03-05 09:59:55 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     menu.connect(level1, "Start Game") | 
					
						
							|  |  |  |     level1.connect(level2, "To Level 2") | 
					
						
							|  |  |  |     level2.connect(level3, "To Level 3") | 
					
						
							|  |  |  |     level3.connect(level4, "To Level 4") | 
					
						
							|  |  |  |     level4.connect(level5, "To Level 5") | 
					
						
							|  |  |  |     menu.connect(level6, "To Level 6")  # put the connection on menu, since you can reach it before level 5 on fast goal | 
					
						
							|  |  |  |     world.multiworld.regions += [menu, level1, level2, level3, level4, level5, level6] |