266 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			266 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | from BaseClasses import Location | ||
|  | from .data import lname, iname | ||
|  | from .options import CVCotMOptions, CompletionGoal, IronMaidenBehavior, RequiredSkirmishes | ||
|  | 
 | ||
|  | from typing import Dict, List, Union, Tuple, Optional, Set, NamedTuple | ||
|  | 
 | ||
|  | BASE_ID = 0xD55C0000 | ||
|  | 
 | ||
|  | 
 | ||
|  | class CVCotMLocation(Location): | ||
|  |     game: str = "Castlevania - Circle of the Moon" | ||
|  | 
 | ||
|  | 
 | ||
|  | class CVCotMLocationData(NamedTuple): | ||
|  |     code: Union[int, str] | ||
|  |     offset: Optional[int] | ||
|  |     countdown: Optional[int] | ||
|  |     type: Optional[str] = None | ||
|  | # code = The unique part of the Location's AP code attribute, as well as the in-game bitflag index starting from | ||
|  | #        0x02025374 that indicates the Location has been checked. Add this + base_id to get the actual AP code. | ||
|  | #        If we put an Item name string here instead of an int, then it is an event Location and that Item should be | ||
|  | #        forced on it while calling the actual code None. | ||
|  | # offset = The offset in the ROM to overwrite to change the Item on that Location. | ||
|  | # countdown = The index of the Countdown number region it contributes to. | ||
|  | # rule = What rule should be applied to the Location during set_rules, as defined in self.rules in the CVCotMRules class | ||
|  | #        definition in rules.py. | ||
|  | # event = What event Item to place on that Location, for Locations that are events specifically. | ||
|  | # type = Anything special about this Location that should be considered, whether it be a boss Location, etc. | ||
|  | 
 | ||
|  | 
 | ||
|  | cvcotm_location_info: Dict[str, CVCotMLocationData] = { | ||
|  |     # Sealed Room | ||
|  |     lname.sr3:   CVCotMLocationData(0x35, 0xD0310,  0), | ||
|  |     # Catacombs | ||
|  |     lname.cc1:   CVCotMLocationData(0x37, 0xD0658,  1), | ||
|  |     lname.cc3:   CVCotMLocationData(0x43, 0xD0370,  1), | ||
|  |     lname.cc3b:  CVCotMLocationData(0x36, 0xD0364,  1), | ||
|  |     lname.cc4:   CVCotMLocationData(0xA8, 0xD0934,  1, type="magic item"), | ||
|  |     lname.cc5:   CVCotMLocationData(0x38, 0xD0DE4,  1), | ||
|  |     lname.cc8:   CVCotMLocationData(0x3A, 0xD1078,  1), | ||
|  |     lname.cc8b:  CVCotMLocationData(0x3B, 0xD1084,  1), | ||
|  |     lname.cc9:   CVCotMLocationData(0x40, 0xD0F94,  1), | ||
|  |     lname.cc10:  CVCotMLocationData(0x39, 0xD12C4,  1), | ||
|  |     lname.cc13:  CVCotMLocationData(0x41, 0xD0DA8,  1), | ||
|  |     lname.cc14:  CVCotMLocationData(0x3C, 0xD1168,  1), | ||
|  |     lname.cc14b: CVCotMLocationData(0x3D, 0xD1174,  1), | ||
|  |     lname.cc16:  CVCotMLocationData(0x3E, 0xD0C40,  1), | ||
|  |     lname.cc20:  CVCotMLocationData(0x42, 0xD103C,  1), | ||
|  |     lname.cc22:  CVCotMLocationData(0x3F, 0xD07C0,  1), | ||
|  |     lname.cc24:  CVCotMLocationData(0xA9, 0xD1288,  1, type="boss"), | ||
|  |     lname.cc25:  CVCotMLocationData(0x44, 0xD12A0,  1), | ||
|  |     # Abyss Staircase | ||
|  |     lname.as2:   CVCotMLocationData(0x47, 0xD181C,  2), | ||
|  |     lname.as3:   CVCotMLocationData(0x45, 0xD1774,  2), | ||
|  |     lname.as4:   CVCotMLocationData(0x46, 0xD1678,  2), | ||
|  |     lname.as9:   CVCotMLocationData(0x48, 0xD17EC,  2), | ||
|  |     # Audience Room | ||
|  |     lname.ar4:   CVCotMLocationData(0x53, 0xD2344,  3), | ||
|  |     lname.ar7:   CVCotMLocationData(0x54, 0xD2368,  3), | ||
|  |     lname.ar8:   CVCotMLocationData(0x51, 0xD1BF4,  3), | ||
|  |     lname.ar9:   CVCotMLocationData(0x4B, 0xD1E1C,  3), | ||
|  |     lname.ar10:  CVCotMLocationData(0x4A, 0xD1DE0,  3), | ||
|  |     lname.ar11:  CVCotMLocationData(0x49, 0xD1E58,  3), | ||
|  |     lname.ar14:  CVCotMLocationData(0x4D, 0xD2158,  3), | ||
|  |     lname.ar14b: CVCotMLocationData(0x4C, 0xD214C,  3), | ||
|  |     lname.ar16:  CVCotMLocationData(0x52, 0xD20BC,  3), | ||
|  |     lname.ar17:  CVCotMLocationData(0x50, 0xD2290,  3), | ||
|  |     lname.ar17b: CVCotMLocationData(0x4F, 0xD2284,  3), | ||
|  |     lname.ar18:  CVCotMLocationData(0x4E, 0xD1FA8,  3), | ||
|  |     lname.ar19:  CVCotMLocationData(0x6A, 0xD44A4,  7), | ||
|  |     lname.ar21:  CVCotMLocationData(0x55, 0xD238C,  3), | ||
|  |     lname.ar25:  CVCotMLocationData(0xAA, 0xD1E04,  3, type="boss"), | ||
|  |     lname.ar26:  CVCotMLocationData(0x59, 0xD3370,  5), | ||
|  |     lname.ar27:  CVCotMLocationData(0x58, 0xD34E4,  5), | ||
|  |     lname.ar30:  CVCotMLocationData(0x99, 0xD6A24, 11), | ||
|  |     lname.ar30b: CVCotMLocationData(0x9A, 0xD6A30, 11), | ||
|  |     # Outer Wall | ||
|  |     lname.ow0:   CVCotMLocationData(0x97, 0xD6BEC, 11), | ||
|  |     lname.ow1:   CVCotMLocationData(0x98, 0xD6CE8, 11), | ||
|  |     lname.ow2:   CVCotMLocationData(0x9E, 0xD6DE4, 11), | ||
|  |     # Triumph Hallway | ||
|  |     lname.th1:   CVCotMLocationData(0x57, 0xD26D4,  4), | ||
|  |     lname.th3:   CVCotMLocationData(0x56, 0xD23C8,  4), | ||
|  |     # Machine Tower | ||
|  |     lname.mt0:   CVCotMLocationData(0x61, 0xD307C,  5), | ||
|  |     lname.mt2:   CVCotMLocationData(0x62, 0xD32A4,  5), | ||
|  |     lname.mt3:   CVCotMLocationData(0x5B, 0xD3244,  5), | ||
|  |     lname.mt4:   CVCotMLocationData(0x5A, 0xD31FC,  5), | ||
|  |     lname.mt6:   CVCotMLocationData(0x5F, 0xD2F38,  5), | ||
|  |     lname.mt8:   CVCotMLocationData(0x5E, 0xD2EC0,  5), | ||
|  |     lname.mt10:  CVCotMLocationData(0x63, 0xD3550,  5), | ||
|  |     lname.mt11:  CVCotMLocationData(0x5D, 0xD2D88,  5), | ||
|  |     lname.mt13:  CVCotMLocationData(0x5C, 0xD3580,  5), | ||
|  |     lname.mt14:  CVCotMLocationData(0x60, 0xD2A64,  5), | ||
|  |     lname.mt17:  CVCotMLocationData(0x64, 0xD3520,  5), | ||
|  |     lname.mt19:  CVCotMLocationData(0xAB, 0xD283C,  5, type="boss"), | ||
|  |     # Eternal Corridor | ||
|  |     lname.ec5:   CVCotMLocationData(0x66, 0xD3B50,  6), | ||
|  |     lname.ec7:   CVCotMLocationData(0x65, 0xD3A90,  6), | ||
|  |     lname.ec9:   CVCotMLocationData(0x67, 0xD3B98,  6), | ||
|  |     # Chapel Tower | ||
|  |     lname.ct1:   CVCotMLocationData(0x68, 0xD40F0,  7), | ||
|  |     lname.ct4:   CVCotMLocationData(0x69, 0xD4630,  7), | ||
|  |     lname.ct5:   CVCotMLocationData(0x72, 0xD481C,  7), | ||
|  |     lname.ct6:   CVCotMLocationData(0x6B, 0xD4294,  7), | ||
|  |     lname.ct6b:  CVCotMLocationData(0x6C, 0xD42A0,  7), | ||
|  |     lname.ct8:   CVCotMLocationData(0x6D, 0xD4330,  7), | ||
|  |     lname.ct10:  CVCotMLocationData(0x6E, 0xD415C,  7), | ||
|  |     lname.ct13:  CVCotMLocationData(0x6F, 0xD4060,  7), | ||
|  |     lname.ct15:  CVCotMLocationData(0x73, 0xD47F8,  7), | ||
|  |     lname.ct16:  CVCotMLocationData(0x70, 0xD3DA8,  7), | ||
|  |     lname.ct18:  CVCotMLocationData(0x74, 0xD47C8,  7), | ||
|  |     lname.ct21:  CVCotMLocationData(0xF0, 0xD47B0,  7, type="maiden switch"), | ||
|  |     lname.ct22:  CVCotMLocationData(0x71, 0xD3CF4,  7, type="max up boss"), | ||
|  |     lname.ct26:  CVCotMLocationData(0x9C, 0xD6ACC, 11), | ||
|  |     lname.ct26b: CVCotMLocationData(0x9B, 0xD6AC0, 11), | ||
|  |     # Underground Gallery | ||
|  |     lname.ug0:   CVCotMLocationData(0x82, 0xD5944,  9), | ||
|  |     lname.ug1:   CVCotMLocationData(0x83, 0xD5890,  9), | ||
|  |     lname.ug2:   CVCotMLocationData(0x81, 0xD5A1C,  9), | ||
|  |     lname.ug3:   CVCotMLocationData(0x85, 0xD56A4,  9), | ||
|  |     lname.ug3b:  CVCotMLocationData(0x84, 0xD5698,  9), | ||
|  |     lname.ug8:   CVCotMLocationData(0x86, 0xD5E30,  9), | ||
|  |     lname.ug10:  CVCotMLocationData(0x87, 0xD5F68,  9), | ||
|  |     lname.ug13:  CVCotMLocationData(0x88, 0xD5AB8,  9), | ||
|  |     lname.ug15:  CVCotMLocationData(0x89, 0xD5BD8,  9), | ||
|  |     lname.ug20:  CVCotMLocationData(0xAC, 0xD5470,  9, type="boss"), | ||
|  |     # Underground Warehouse | ||
|  |     lname.uw1:   CVCotMLocationData(0x75, 0xD48DC,  8), | ||
|  |     lname.uw6:   CVCotMLocationData(0x76, 0xD4D20,  8), | ||
|  |     lname.uw8:   CVCotMLocationData(0x77, 0xD4BA0,  8), | ||
|  |     lname.uw9:   CVCotMLocationData(0x7E, 0xD53EC,  8), | ||
|  |     lname.uw10:  CVCotMLocationData(0x78, 0xD4C84,  8), | ||
|  |     lname.uw11:  CVCotMLocationData(0x79, 0xD4EC4,  8), | ||
|  |     lname.uw14:  CVCotMLocationData(0x7F, 0xD5410,  8), | ||
|  |     lname.uw16:  CVCotMLocationData(0x7A, 0xD5050,  8), | ||
|  |     lname.uw16b: CVCotMLocationData(0x7B, 0xD505C,  8), | ||
|  |     lname.uw19:  CVCotMLocationData(0x7C, 0xD5344,  8), | ||
|  |     lname.uw23:  CVCotMLocationData(0xAE, 0xD53B0,  8, type="boss"), | ||
|  |     lname.uw24:  CVCotMLocationData(0x80, 0xD5434,  8), | ||
|  |     lname.uw25:  CVCotMLocationData(0x7D, 0xD4FC0,  8), | ||
|  |     # Underground Waterway | ||
|  |     lname.uy1:   CVCotMLocationData(0x93, 0xD5F98, 10), | ||
|  |     lname.uy3:   CVCotMLocationData(0x8B, 0xD5FEC, 10), | ||
|  |     lname.uy3b:  CVCotMLocationData(0x8A, 0xD5FE0, 10), | ||
|  |     lname.uy4:   CVCotMLocationData(0x94, 0xD697C, 10), | ||
|  |     lname.uy5:   CVCotMLocationData(0x8C, 0xD6214, 10), | ||
|  |     lname.uy7:   CVCotMLocationData(0x8D, 0xD65A4, 10), | ||
|  |     lname.uy8:   CVCotMLocationData(0x95, 0xD69A0, 10), | ||
|  |     lname.uy9:   CVCotMLocationData(0x8E, 0xD640C, 10), | ||
|  |     lname.uy9b:  CVCotMLocationData(0x8F, 0xD6418, 10), | ||
|  |     lname.uy12:  CVCotMLocationData(0x90, 0xD6730, 10), | ||
|  |     lname.uy12b: CVCotMLocationData(0x91, 0xD673C, 10), | ||
|  |     lname.uy13:  CVCotMLocationData(0x92, 0xD685C, 10), | ||
|  |     lname.uy17:  CVCotMLocationData(0xAF, 0xD6940, 10, type="boss"), | ||
|  |     lname.uy18:  CVCotMLocationData(0x96, 0xD69C4, 10), | ||
|  |     # Observation Tower | ||
|  |     lname.ot1:   CVCotMLocationData(0x9D, 0xD6B38, 11), | ||
|  |     lname.ot2:   CVCotMLocationData(0xA4, 0xD760C, 12), | ||
|  |     lname.ot3:   CVCotMLocationData(0x9F, 0xD72E8, 12), | ||
|  |     lname.ot5:   CVCotMLocationData(0xA5, 0xD75E8, 12), | ||
|  |     lname.ot8:   CVCotMLocationData(0xA0, 0xD71EC, 12), | ||
|  |     lname.ot9:   CVCotMLocationData(0xA2, 0xD6FE8, 12), | ||
|  |     lname.ot12:  CVCotMLocationData(0xA6, 0xD75C4, 12), | ||
|  |     lname.ot13:  CVCotMLocationData(0xA3, 0xD6F64, 12), | ||
|  |     lname.ot16:  CVCotMLocationData(0xA1, 0xD751C, 12), | ||
|  |     lname.ot20:  CVCotMLocationData(0xB0, 0xD6E20, 12, type="boss"), | ||
|  |     # Ceremonial Room | ||
|  |     lname.cr1:   CVCotMLocationData(0xA7, 0xD7690, 13), | ||
|  |     lname.dracula: CVCotMLocationData(iname.dracula, None, None), | ||
|  |     # Battle Arena | ||
|  |     lname.ba24:  CVCotMLocationData(0xB2, 0xD7D20, 14, type="arena"), | ||
|  |     lname.arena_victory:  CVCotMLocationData(iname.shinning_armor, None, None), | ||
|  |  } | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_location_names_to_ids() -> Dict[str, int]: | ||
|  |     return {name: cvcotm_location_info[name].code+BASE_ID for name in cvcotm_location_info | ||
|  |             if isinstance(cvcotm_location_info[name].code, int)} | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_location_name_groups() -> Dict[str, Set[str]]: | ||
|  |     loc_name_groups: Dict[str, Set[str]] = {"Breakable Secrets": set(), | ||
|  |                                             "Bosses": set()} | ||
|  | 
 | ||
|  |     for loc_name in cvcotm_location_info: | ||
|  |         # If we are looking at an event Location, don't include it. | ||
|  |         if isinstance(cvcotm_location_info[loc_name].code, str): | ||
|  |             continue | ||
|  | 
 | ||
|  |         # The part of the Location name's string before the colon is its area name. | ||
|  |         area_name = loc_name.split(":")[0] | ||
|  | 
 | ||
|  |         # Add each Location to its corresponding area name group. | ||
|  |         if area_name not in loc_name_groups: | ||
|  |             loc_name_groups[area_name] = {loc_name} | ||
|  |         else: | ||
|  |             loc_name_groups[area_name].add(loc_name) | ||
|  | 
 | ||
|  |         # If the word "fake" is in the Location's name, add it to the "Breakable Walls" Location group. | ||
|  |         if "fake" in loc_name.casefold(): | ||
|  |             loc_name_groups["Breakable Secrets"].add(loc_name) | ||
|  | 
 | ||
|  |         # If it's a behind boss Location, add it to the "Bosses" Location group. | ||
|  |         if cvcotm_location_info[loc_name].type in ["boss", "max up boss"]: | ||
|  |             loc_name_groups["Bosses"].add(loc_name) | ||
|  | 
 | ||
|  |     return loc_name_groups | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_named_locations_data(locations: List[str], options: CVCotMOptions) -> \ | ||
|  |         Tuple[Dict[str, Optional[int]], Dict[str, str]]: | ||
|  |     locations_with_ids = {} | ||
|  |     locked_pairs = {} | ||
|  |     locked_key_types = [] | ||
|  | 
 | ||
|  |     # Decide which Location types should have locked Last Keys placed on them, if skirmishes are required. | ||
|  |     # If the Maiden Detonator is in the pool, Adramelech's key should be on the switch instead of behind the maiden. | ||
|  |     if options.required_skirmishes: | ||
|  |         locked_key_types += ["boss"] | ||
|  |         if options.iron_maiden_behavior == IronMaidenBehavior.option_detonator_in_pool: | ||
|  |             locked_key_types += ["maiden switch"] | ||
|  |         else: | ||
|  |             locked_key_types += ["max up boss"] | ||
|  |         # If all bosses and the Arena is required, the Arena end reward should have a Last Key as well. | ||
|  |         if options.required_skirmishes == RequiredSkirmishes.option_all_bosses_and_arena: | ||
|  |             locked_key_types += ["arena"] | ||
|  | 
 | ||
|  |     for loc in locations: | ||
|  |         if loc == lname.ct21: | ||
|  |             # If the maidens are pre-broken, don't create the iron maiden switch Location at all. | ||
|  |             if options.iron_maiden_behavior == IronMaidenBehavior.option_start_broken: | ||
|  |                 continue | ||
|  |             # If the maiden behavior is vanilla, lock the Maiden Detonator on this Location. | ||
|  |             if options.iron_maiden_behavior == IronMaidenBehavior.option_vanilla: | ||
|  |                 locked_pairs[loc] = iname.ironmaidens | ||
|  | 
 | ||
|  |         # Don't place the Dracula Location if our Completion Goal is the Battle Arena only. | ||
|  |         if loc == lname.dracula and options.completion_goal == CompletionGoal.option_battle_arena: | ||
|  |             continue | ||
|  | 
 | ||
|  |         # Don't place the Battle Arena normal Location if the Arena is not required by the Skirmishes option. | ||
|  |         if loc == lname.ba24 and options.required_skirmishes != RequiredSkirmishes.option_all_bosses_and_arena: | ||
|  |             continue | ||
|  | 
 | ||
|  |         # Don't place the Battle Arena event Location if our Completion Goal is Dracula only. | ||
|  |         if loc == lname.arena_victory and options.completion_goal == CompletionGoal.option_dracula: | ||
|  |             continue | ||
|  | 
 | ||
|  |         loc_code = cvcotm_location_info[loc].code | ||
|  | 
 | ||
|  |         # If we are looking at an event Location, add its associated event Item to the events' dict. | ||
|  |         # Otherwise, add the base_id to the Location's code. | ||
|  |         if isinstance(loc_code, str): | ||
|  |             locked_pairs[loc] = loc_code | ||
|  |             locations_with_ids.update({loc: None}) | ||
|  |         else: | ||
|  |             loc_code += BASE_ID | ||
|  |             locations_with_ids.update({loc: loc_code}) | ||
|  | 
 | ||
|  |         # Place a locked Last Key on this Location if its of a type that should have one. | ||
|  |         if cvcotm_location_info[loc].type in locked_key_types: | ||
|  |             locked_pairs[loc] = iname.last_key | ||
|  | 
 | ||
|  |     return locations_with_ids, locked_pairs |