 3acbe9ece1
			
		
	
	3acbe9ece1
	
	
	
		
			
			* Add the cotm package with working seed playthrough generation. * Add the proper event flag IDs for the Item codes. * Oooops. Put the world completion condition in! * Adjust the game name and abbreviations. * Implement more settings. * Account for too many start_inventory_from_pool cards with Halve DSS Cards Placed. * Working (albeit very sloooooooooooow) ROM patching. * Screw you, bsdiff! AP Procedure Patch for life! * Nuke stage_assert_generate as the ROM is no longer needed for that. * Working item writing and position adjusting. * Fix the magic item graphics in Locations wherein they can be fixed. * Enable sub-weapon shuffle * Get the seed display working. * Get the enemy item drop randomization working. Phew! * Enemy drop rando and seed display fixes. * Functional Countdown + Early Double setting * Working multiworld (yay!) * Fix item links and demo shenanigans. * Add Wii U VC hash and a docs section explaining the rereleases. * Change all client read/writes to EWRAM instead of Combined WRAM. * Custom text insertion foundations. * Working text converter and word wrap detector. * More refinements to the text wrap system. * Well and truly working sent/received messages. * Add DeathLink and Battle Arena goal options. * Add tracker stuff, unittests, all locations countdown, presets. * Add to README, CODEOWNERS, and inno_setup * Add to README, CODEOWNERS, and inno_setup * Address some suggestions/problems. * Switch the Items and Locations to using dataclasses. * Add note about the alternate classes to the Game Page. * Oooops, typo! * Touch up the Options descriptions. * Fix Battle Arena flag being detected incorrectly on connection and name the locked location/item pairs better. * Implement option groups * Swap the Lizard-man Locations into their correct Regions. * Local start inventory, better DeathLink message handling, handle receiving over 255 of an item. * Update the PopTracker pack links to no longer point to the Releases page. * Add Skip Dialogues option. * Update the presets for the accessibility rework. * Swap the choices in the accessibility preset options. * Uhhhhhhh...just see the apworld v4 changelog for this one. * Ooops, typo! * . * Bunch of small stuff * Correctly change "Fake" to "Breakable" in this comment. Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Make can_touch_water one line. Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Make broke_iron_maidens one line. Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Fix majors countdown and make can_open_ceremonial_door one line. * Make the Trap AP Item less obvious. * Add Progression + Useful stuff, patcher handling for incompatible versions, and fix some mypy stuff. * Better option groups. * Change Early Double to Early Escape Item. * Update DeathLink description and ditch the Menu region. * Fix the Start Broken choice for Iron Maiden Behavior * Remove the forced option change with Arena goal + required All Bosses and Arena. * Update the Game Page with the removal of the forced option combination change. * Fix client potential to send packets nonstop. * More review addressing. * Fix the new select_drop code. * Fix the new select_drop code for REAL this time. * Send another LocationScout if we send Location checks without having the Location info. --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: Exempt-Medic <ExemptMedic@Gmail.com> Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
		
			
				
	
	
		
			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
 |