| 
									
										
											  
											
												Castlevania: Circle of the Moon - Implement New Game (#3299)
* 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>
											
										 
											2024-12-12 06:47:47 -07:00
										 |  |  | from BaseClasses import ItemClassification, Location | 
					
						
							|  |  |  | from .options import ItemDropRandomization, Countdown, RequiredSkirmishes, IronMaidenBehavior | 
					
						
							|  |  |  | from .locations import cvcotm_location_info | 
					
						
							|  |  |  | from .items import cvcotm_item_info, MAJORS_CLASSIFICATIONS | 
					
						
							|  |  |  | from .data import iname | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from typing import TYPE_CHECKING, Dict, List, Iterable, Tuple, NamedTuple, Optional, TypedDict | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from . import CVCotMWorld | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class StatInfo(TypedDict): | 
					
						
							|  |  |  |     # Amount this stat increases per Max Up the player starts with. | 
					
						
							|  |  |  |     amount_per: int | 
					
						
							|  |  |  |     # The most amount of this stat the player is allowed to start with. Problems arise if the stat  exceeds 9999, so we | 
					
						
							|  |  |  |     # must ensure it can't if the player raises any class to level 99 as well as collects 255 of that max up. The game | 
					
						
							|  |  |  |     # caps hearts at 999 automatically, so it doesn't matter so much for that one. | 
					
						
							|  |  |  |     max_allowed: int | 
					
						
							|  |  |  |     # The key variable in extra_stats that the stat max up affects. | 
					
						
							|  |  |  |     variable: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | extra_starting_stat_info: Dict[str, StatInfo] = { | 
					
						
							|  |  |  |     iname.hp_max: {"amount_per": 10, | 
					
						
							|  |  |  |                    "max_allowed": 5289, | 
					
						
							|  |  |  |                    "variable": "extra health"}, | 
					
						
							|  |  |  |     iname.mp_max: {"amount_per": 10, | 
					
						
							|  |  |  |                    "max_allowed": 3129, | 
					
						
							|  |  |  |                    "variable": "extra magic"}, | 
					
						
							|  |  |  |     iname.heart_max: {"amount_per": 6, | 
					
						
							|  |  |  |                       "max_allowed": 999, | 
					
						
							|  |  |  |                       "variable": "extra hearts"}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | other_player_subtype_bytes = { | 
					
						
							|  |  |  |     0xE4: 0x03, | 
					
						
							|  |  |  |     0xE6: 0x14, | 
					
						
							|  |  |  |     0xE8: 0x0A | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class OtherGameAppearancesInfo(TypedDict): | 
					
						
							|  |  |  |     # What type of item to place for the other player. | 
					
						
							|  |  |  |     type: int | 
					
						
							|  |  |  |     # What item to display it as for the other player. | 
					
						
							|  |  |  |     appearance: int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | other_game_item_appearances: Dict[str, Dict[str, OtherGameAppearancesInfo]] = { | 
					
						
							| 
									
										
										
										
											2025-05-28 08:47:24 -06:00
										 |  |  |     # NOTE: Symphony of the Night and Harmony of Dissonance are custom worlds that are not core verified. | 
					
						
							| 
									
										
											  
											
												Castlevania: Circle of the Moon - Implement New Game (#3299)
* 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>
											
										 
											2024-12-12 06:47:47 -07:00
										 |  |  |     "Symphony of the Night": {"Life Vessel": {"type": 0xE4, | 
					
						
							|  |  |  |                                               "appearance": 0x01}, | 
					
						
							|  |  |  |                               "Heart Vessel": {"type": 0xE4, | 
					
						
							|  |  |  |                                                "appearance": 0x00}}, | 
					
						
							| 
									
										
										
										
											2025-05-28 08:47:24 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     "Castlevania - Harmony of Dissonance": {"Life Max Up": {"type": 0xE4, | 
					
						
							|  |  |  |                                                             "appearance": 0x01}, | 
					
						
							|  |  |  |                                             "Heart Max Up": {"type": 0xE4, | 
					
						
							|  |  |  |                                                              "appearance": 0x00}}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												Castlevania: Circle of the Moon - Implement New Game (#3299)
* 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>
											
										 
											2024-12-12 06:47:47 -07:00
										 |  |  |     "Timespinner": {"Max HP": {"type": 0xE4, | 
					
						
							|  |  |  |                                "appearance": 0x01}, | 
					
						
							|  |  |  |                     "Max Aura": {"type": 0xE4, | 
					
						
							|  |  |  |                                  "appearance": 0x02}, | 
					
						
							|  |  |  |                     "Max Sand": {"type": 0xE8, | 
					
						
							|  |  |  |                                  "appearance": 0x0F}} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # 0 = Holy water  22 | 
					
						
							|  |  |  | # 1 = Axe         24 | 
					
						
							|  |  |  | # 2 = Knife       32 | 
					
						
							|  |  |  | # 3 = Cross        6 | 
					
						
							|  |  |  | # 4 = Stopwatch   12 | 
					
						
							|  |  |  | # 5 = Small heart | 
					
						
							|  |  |  | # 6 = Big heart | 
					
						
							|  |  |  | rom_sub_weapon_offsets = { | 
					
						
							|  |  |  |     0xD034E: b"\x01", | 
					
						
							|  |  |  |     0xD0462: b"\x02", | 
					
						
							|  |  |  |     0xD064E: b"\x00", | 
					
						
							|  |  |  |     0xD06F6: b"\x02", | 
					
						
							|  |  |  |     0xD0882: b"\x00", | 
					
						
							|  |  |  |     0xD0912: b"\x02", | 
					
						
							|  |  |  |     0xD0C2A: b"\x02", | 
					
						
							|  |  |  |     0xD0C96: b"\x01", | 
					
						
							|  |  |  |     0xD0D92: b"\x02", | 
					
						
							|  |  |  |     0xD0DCE: b"\x01", | 
					
						
							|  |  |  |     0xD1332: b"\x00", | 
					
						
							|  |  |  |     0xD13AA: b"\x01", | 
					
						
							|  |  |  |     0xD1722: b"\x02", | 
					
						
							|  |  |  |     0xD17A6: b"\x01", | 
					
						
							|  |  |  |     0xD1926: b"\x01", | 
					
						
							|  |  |  |     0xD19AA: b"\x02", | 
					
						
							|  |  |  |     0xD1A9A: b"\x02", | 
					
						
							|  |  |  |     0xD1AA6: b"\x00", | 
					
						
							|  |  |  |     0xD1EBA: b"\x00", | 
					
						
							|  |  |  |     0xD1ED2: b"\x01", | 
					
						
							|  |  |  |     0xD2262: b"\x02", | 
					
						
							|  |  |  |     0xD23B2: b"\x03", | 
					
						
							|  |  |  |     0xD256E: b"\x02", | 
					
						
							|  |  |  |     0xD2742: b"\x02", | 
					
						
							|  |  |  |     0xD2832: b"\x04", | 
					
						
							|  |  |  |     0xD2862: b"\x01", | 
					
						
							|  |  |  |     0xD2A2A: b"\x01", | 
					
						
							|  |  |  |     0xD2DBA: b"\x04", | 
					
						
							|  |  |  |     0xD2DC6: b"\x00", | 
					
						
							|  |  |  |     0xD2E02: b"\x02", | 
					
						
							|  |  |  |     0xD2EFE: b"\x04", | 
					
						
							|  |  |  |     0xD2F0A: b"\x02", | 
					
						
							|  |  |  |     0xD302A: b"\x00", | 
					
						
							|  |  |  |     0xD3042: b"\x01", | 
					
						
							|  |  |  |     0xD304E: b"\x04", | 
					
						
							|  |  |  |     0xD3066: b"\x02", | 
					
						
							|  |  |  |     0xD322E: b"\x04", | 
					
						
							|  |  |  |     0xD334E: b"\x04", | 
					
						
							|  |  |  |     0xD3516: b"\x03", | 
					
						
							|  |  |  |     0xD35CA: b"\x02", | 
					
						
							|  |  |  |     0xD371A: b"\x01", | 
					
						
							|  |  |  |     0xD38EE: b"\x00", | 
					
						
							|  |  |  |     0xD3BE2: b"\x02", | 
					
						
							|  |  |  |     0xD3D1A: b"\x01", | 
					
						
							|  |  |  |     0xD3D56: b"\x02", | 
					
						
							|  |  |  |     0xD3ECA: b"\x00", | 
					
						
							|  |  |  |     0xD3EE2: b"\x02", | 
					
						
							|  |  |  |     0xD4056: b"\x01", | 
					
						
							|  |  |  |     0xD40E6: b"\x04", | 
					
						
							|  |  |  |     0xD413A: b"\x04", | 
					
						
							|  |  |  |     0xD4326: b"\x00", | 
					
						
							|  |  |  |     0xD460E: b"\x00", | 
					
						
							|  |  |  |     0xD48D2: b"\x00", | 
					
						
							|  |  |  |     0xD49E6: b"\x01", | 
					
						
							|  |  |  |     0xD4ABE: b"\x02", | 
					
						
							|  |  |  |     0xD4B8A: b"\x01", | 
					
						
							|  |  |  |     0xD4D0A: b"\x04", | 
					
						
							|  |  |  |     0xD4EAE: b"\x02", | 
					
						
							|  |  |  |     0xD4F0E: b"\x00", | 
					
						
							|  |  |  |     0xD4F92: b"\x02", | 
					
						
							|  |  |  |     0xD4FB6: b"\x01", | 
					
						
							|  |  |  |     0xD503A: b"\x03", | 
					
						
							|  |  |  |     0xD5646: b"\x01", | 
					
						
							|  |  |  |     0xD5682: b"\x02", | 
					
						
							|  |  |  |     0xD57C6: b"\x02", | 
					
						
							|  |  |  |     0xD57D2: b"\x02", | 
					
						
							|  |  |  |     0xD58F2: b"\x00", | 
					
						
							|  |  |  |     0xD5922: b"\x01", | 
					
						
							|  |  |  |     0xD5B9E: b"\x02", | 
					
						
							|  |  |  |     0xD5E26: b"\x01", | 
					
						
							|  |  |  |     0xD5E56: b"\x02", | 
					
						
							|  |  |  |     0xD5E7A: b"\x02", | 
					
						
							|  |  |  |     0xD5F5E: b"\x00", | 
					
						
							|  |  |  |     0xD69EA: b"\x02", | 
					
						
							|  |  |  |     0xD69F6: b"\x01", | 
					
						
							|  |  |  |     0xD6A02: b"\x00", | 
					
						
							|  |  |  |     0xD6A0E: b"\x04", | 
					
						
							|  |  |  |     0xD6A1A: b"\x03", | 
					
						
							|  |  |  |     0xD6BE2: b"\x00", | 
					
						
							|  |  |  |     0xD6CBA: b"\x01", | 
					
						
							|  |  |  |     0xD6CDE: b"\x02", | 
					
						
							|  |  |  |     0xD6EEE: b"\x00", | 
					
						
							|  |  |  |     0xD6F1E: b"\x02", | 
					
						
							|  |  |  |     0xD6F42: b"\x01", | 
					
						
							|  |  |  |     0xD6FC6: b"\x04", | 
					
						
							|  |  |  |     0xD706E: b"\x00", | 
					
						
							|  |  |  |     0xD716A: b"\x02", | 
					
						
							|  |  |  |     0xD72AE: b"\x01", | 
					
						
							|  |  |  |     0xD75BA: b"\x03", | 
					
						
							|  |  |  |     0xD76AA: b"\x04", | 
					
						
							|  |  |  |     0xD76B6: b"\x00", | 
					
						
							|  |  |  |     0xD76C2: b"\x01", | 
					
						
							|  |  |  |     0xD76CE: b"\x02", | 
					
						
							|  |  |  |     0xD76DA: b"\x03", | 
					
						
							|  |  |  |     0xD7D46: b"\x00", | 
					
						
							|  |  |  |     0xD7D52: b"\x00", | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | LOW_ITEMS = [ | 
					
						
							|  |  |  |     41,  # Potion | 
					
						
							|  |  |  |     42,  # Meat | 
					
						
							|  |  |  |     48,  # Mind Restore | 
					
						
							|  |  |  |     51,  # Heart | 
					
						
							|  |  |  |     46,  # Antidote | 
					
						
							|  |  |  |     47,  # Cure Curse | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     17,  # Cotton Clothes | 
					
						
							|  |  |  |     18,  # Prison Garb | 
					
						
							|  |  |  |     12,  # Cotton Robe | 
					
						
							|  |  |  |     1,  # Leather Armor | 
					
						
							|  |  |  |     2,  # Bronze Armor | 
					
						
							|  |  |  |     3,  # Gold Armor | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     39,  # Toy Ring | 
					
						
							|  |  |  |     40,  # Bear Ring | 
					
						
							|  |  |  |     34,  # Wristband | 
					
						
							|  |  |  |     36,  # Arm Guard | 
					
						
							|  |  |  |     37,  # Magic Gauntlet | 
					
						
							|  |  |  |     38,  # Miracle Armband | 
					
						
							|  |  |  |     35,  # Gauntlet | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MID_ITEMS = [ | 
					
						
							|  |  |  |     43,  # Spiced Meat | 
					
						
							|  |  |  |     49,  # Mind High | 
					
						
							|  |  |  |     52,  # Heart High | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     19,  # Stylish Suit | 
					
						
							|  |  |  |     20,  # Night Suit | 
					
						
							|  |  |  |     13,  # Silk Robe | 
					
						
							|  |  |  |     14,  # Rainbow Robe | 
					
						
							|  |  |  |     4,  # Chainmail | 
					
						
							|  |  |  |     5,  # Steel Armor | 
					
						
							|  |  |  |     6,  # Platinum Armor | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     24,  # Star Bracelet | 
					
						
							|  |  |  |     29,  # Cursed Ring | 
					
						
							|  |  |  |     25,  # Strength Ring | 
					
						
							|  |  |  |     26,  # Hard Ring | 
					
						
							|  |  |  |     27,  # Intelligence Ring | 
					
						
							|  |  |  |     28,  # Luck Ring | 
					
						
							|  |  |  |     23,  # Double Grips | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | HIGH_ITEMS = [ | 
					
						
							|  |  |  |     44,  # Potion High | 
					
						
							|  |  |  |     45,  # Potion Ex | 
					
						
							|  |  |  |     50,  # Mind Ex | 
					
						
							|  |  |  |     53,  # Heart Ex | 
					
						
							|  |  |  |     54,  # Heart Mega | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     21,  # Ninja Garb | 
					
						
							|  |  |  |     22,  # Soldier Fatigues | 
					
						
							|  |  |  |     15,  # Magic Robe | 
					
						
							|  |  |  |     16,  # Sage Robe | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     7,  # Diamond Armor | 
					
						
							|  |  |  |     8,  # Mirror Armor | 
					
						
							|  |  |  |     9,  # Needle Armor | 
					
						
							|  |  |  |     10,  # Dark Armor | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     30,  # Strength Armband | 
					
						
							|  |  |  |     31,  # Defense Armband | 
					
						
							|  |  |  |     32,  # Sage Armband | 
					
						
							|  |  |  |     33,  # Gambler Armband | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | COMMON_ITEMS = LOW_ITEMS + MID_ITEMS | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | RARE_ITEMS = LOW_ITEMS + MID_ITEMS + HIGH_ITEMS | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class CVCotMEnemyData(NamedTuple): | 
					
						
							|  |  |  |     name: str | 
					
						
							|  |  |  |     hp: int | 
					
						
							|  |  |  |     attack: int | 
					
						
							|  |  |  |     defense: int | 
					
						
							|  |  |  |     exp: int | 
					
						
							|  |  |  |     type: Optional[str] = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | cvcotm_enemy_info: List[CVCotMEnemyData] = [ | 
					
						
							|  |  |  |     #                Name                  HP   ATK   DEF     EXP | 
					
						
							|  |  |  |     CVCotMEnemyData("Medusa Head",          6,  120,   60,      2), | 
					
						
							|  |  |  |     CVCotMEnemyData("Zombie",              48,   70,   20,      2), | 
					
						
							|  |  |  |     CVCotMEnemyData("Ghoul",              100,  190,   79,      3), | 
					
						
							|  |  |  |     CVCotMEnemyData("Wight",              110,  235,   87,      4), | 
					
						
							|  |  |  |     CVCotMEnemyData("Clinking Man",        80,  135,   25,     21), | 
					
						
							|  |  |  |     CVCotMEnemyData("Zombie Thief",       120,  185,   30,     58), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton",            25,   65,   45,      4), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton Bomber",     20,   50,   40,      4), | 
					
						
							|  |  |  |     CVCotMEnemyData("Electric Skeleton",   42,   80,   50,     30), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton Spear",      30,   65,   46,      6), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton Boomerang",  60,  170,   90,    112), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton Soldier",    35,   90,   60,     16), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton Knight",     50,  140,   80,     39), | 
					
						
							|  |  |  |     CVCotMEnemyData("Bone Tower",          84,  201,  280,    160), | 
					
						
							|  |  |  |     CVCotMEnemyData("Fleaman",             60,  142,   45,     29), | 
					
						
							|  |  |  |     CVCotMEnemyData("Poltergeist",        105,  360,  380,    510), | 
					
						
							|  |  |  |     CVCotMEnemyData("Bat",                  5,   50,   15,      4), | 
					
						
							|  |  |  |     CVCotMEnemyData("Spirit",               9,   55,   17,      1), | 
					
						
							|  |  |  |     CVCotMEnemyData("Ectoplasm",           12,  165,   51,      2), | 
					
						
							|  |  |  |     CVCotMEnemyData("Specter",             15,  295,   95,      3), | 
					
						
							|  |  |  |     CVCotMEnemyData("Axe Armor",           55,  120,  130,     31), | 
					
						
							|  |  |  |     CVCotMEnemyData("Flame Armor",        160,  320,  300,    280), | 
					
						
							|  |  |  |     CVCotMEnemyData("Flame Demon",        300,  315,  270,    600), | 
					
						
							|  |  |  |     CVCotMEnemyData("Ice Armor",          240,  470,  520,   1500), | 
					
						
							|  |  |  |     CVCotMEnemyData("Thunder Armor",      204,  340,  320,    800), | 
					
						
							|  |  |  |     CVCotMEnemyData("Wind Armor",         320,  500,  460,   1800), | 
					
						
							|  |  |  |     CVCotMEnemyData("Earth Armor",        130,  230,  280,    240), | 
					
						
							|  |  |  |     CVCotMEnemyData("Poison Armor",       260,  382,  310,    822), | 
					
						
							|  |  |  |     CVCotMEnemyData("Forest Armor",       370,  390,  390,   1280), | 
					
						
							|  |  |  |     CVCotMEnemyData("Stone Armor",         90,  220,  320,    222), | 
					
						
							|  |  |  |     CVCotMEnemyData("Ice Demon",          350,  492,  510,   4200), | 
					
						
							|  |  |  |     CVCotMEnemyData("Holy Armor",         350,  420,  450,   1700), | 
					
						
							|  |  |  |     CVCotMEnemyData("Thunder Demon",      180,  270,  230,    450), | 
					
						
							|  |  |  |     CVCotMEnemyData("Dark Armor",         400,  680,  560,   3300), | 
					
						
							|  |  |  |     CVCotMEnemyData("Wind Demon",         400,  540,  490,   3600), | 
					
						
							|  |  |  |     CVCotMEnemyData("Bloody Sword",        30,  220,  500,    200), | 
					
						
							|  |  |  |     CVCotMEnemyData("Golem",              650,  520,  700,   1400), | 
					
						
							|  |  |  |     CVCotMEnemyData("Earth Demon",        150,   90,   85,     25), | 
					
						
							|  |  |  |     CVCotMEnemyData("Were-wolf",          160,  265,  110,    140), | 
					
						
							|  |  |  |     CVCotMEnemyData("Man Eater",          400,  330,  233,    700), | 
					
						
							|  |  |  |     CVCotMEnemyData("Devil Tower",         10,  140,  200,     17), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton Athlete",   100,  100,   50,     25), | 
					
						
							|  |  |  |     CVCotMEnemyData("Harpy",              120,  275,  200,    271), | 
					
						
							|  |  |  |     CVCotMEnemyData("Siren",              160,  443,  300,    880), | 
					
						
							|  |  |  |     CVCotMEnemyData("Imp",                 90,  220,   99,    103), | 
					
						
							|  |  |  |     CVCotMEnemyData("Mudman",              25,   79,   30,      2), | 
					
						
							|  |  |  |     CVCotMEnemyData("Gargoyle",            60,  160,   66,      3), | 
					
						
							|  |  |  |     CVCotMEnemyData("Slime",               40,  102,   18,     11), | 
					
						
							|  |  |  |     CVCotMEnemyData("Frozen Shade",       112,  490,  560,   1212), | 
					
						
							|  |  |  |     CVCotMEnemyData("Heat Shade",          80,  240,  200,    136), | 
					
						
							|  |  |  |     CVCotMEnemyData("Poison Worm",        120,   30,   20,     12), | 
					
						
							|  |  |  |     CVCotMEnemyData("Myconid",             50,  250,  114,     25), | 
					
						
							|  |  |  |     CVCotMEnemyData("Will O'Wisp",         11,  110,   16,      9), | 
					
						
							|  |  |  |     CVCotMEnemyData("Spearfish",           40,  360,  450,    280), | 
					
						
							|  |  |  |     CVCotMEnemyData("Merman",              60,  303,  301,     10), | 
					
						
							|  |  |  |     CVCotMEnemyData("Minotaur",           410,  520,  640,   2000), | 
					
						
							|  |  |  |     CVCotMEnemyData("Were-horse",         400,  540,  360,   1970), | 
					
						
							|  |  |  |     CVCotMEnemyData("Marionette",          80,  160,  150,    127), | 
					
						
							|  |  |  |     CVCotMEnemyData("Gremlin",             30,   80,   33,      2), | 
					
						
							|  |  |  |     CVCotMEnemyData("Hopper",              40,   87,   35,      8), | 
					
						
							|  |  |  |     CVCotMEnemyData("Evil Pillar",         20,  460,  800,    480), | 
					
						
							|  |  |  |     CVCotMEnemyData("Were-panther",       200,  300,  130,    270), | 
					
						
							|  |  |  |     CVCotMEnemyData("Were-jaguar",        270,  416,  170,    760), | 
					
						
							|  |  |  |     CVCotMEnemyData("Bone Head",           24,   60,   80,      7), | 
					
						
							|  |  |  |     CVCotMEnemyData("Fox Archer",          75,  130,   59,     53), | 
					
						
							|  |  |  |     CVCotMEnemyData("Fox Hunter",         100,  290,  140,    272), | 
					
						
							|  |  |  |     CVCotMEnemyData("Were-bear",          265,  250,  140,    227), | 
					
						
							|  |  |  |     CVCotMEnemyData("Grizzly",            600,  380,  200,    960), | 
					
						
							|  |  |  |     CVCotMEnemyData("Cerberus",           600,  150,  100,    500, "boss"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Beast Demon",        150,  330,  250,    260), | 
					
						
							|  |  |  |     CVCotMEnemyData("Arch Demon",         320,  505,  400,   1000), | 
					
						
							|  |  |  |     CVCotMEnemyData("Demon Lord",         460,  660,  500,   1950), | 
					
						
							|  |  |  |     CVCotMEnemyData("Gorgon",             230,  215,  165,    219), | 
					
						
							|  |  |  |     CVCotMEnemyData("Catoblepas",         550,  500,  430,   1800), | 
					
						
							|  |  |  |     CVCotMEnemyData("Succubus",           150,  400,  350,    710), | 
					
						
							|  |  |  |     CVCotMEnemyData("Fallen Angel",       370,  770,  770,   6000), | 
					
						
							|  |  |  |     CVCotMEnemyData("Necromancer",        500,  200,  250,   2500, "boss"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Hyena",               93,  140,   70,    105), | 
					
						
							|  |  |  |     CVCotMEnemyData("Fishhead",            80,  320,  504,    486), | 
					
						
							|  |  |  |     CVCotMEnemyData("Dryad",              120,  300,  360,    300), | 
					
						
							|  |  |  |     CVCotMEnemyData("Mimic Candle",       990,  600,  600,   6600, "candle"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Brain Float",         20,   50,   25,     10), | 
					
						
							|  |  |  |     CVCotMEnemyData("Evil Hand",           52,  150,  120,     63), | 
					
						
							|  |  |  |     CVCotMEnemyData("Abiondarg",           88,  388,  188,    388), | 
					
						
							|  |  |  |     CVCotMEnemyData("Iron Golem",         640,  290,  450,   8000, "boss"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Devil",             1080,  800,  900,  10000), | 
					
						
							|  |  |  |     CVCotMEnemyData("Witch",              144,  330,  290,    600), | 
					
						
							|  |  |  |     CVCotMEnemyData("Mummy",              100,  100,   35,      3), | 
					
						
							|  |  |  |     CVCotMEnemyData("Hipogriff",          300,  500,  210,    740), | 
					
						
							|  |  |  |     CVCotMEnemyData("Adramelech",        1800,  380,  360,  16000, "boss"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Arachne",            330,  420,  288,   1300), | 
					
						
							|  |  |  |     CVCotMEnemyData("Death Mantis",       200,  318,  240,    400), | 
					
						
							|  |  |  |     CVCotMEnemyData("Alraune",            774,  490,  303,   2500), | 
					
						
							|  |  |  |     CVCotMEnemyData("King Moth",          140,  290,  160,    150), | 
					
						
							|  |  |  |     CVCotMEnemyData("Killer Bee",           8,  308,  108,     88), | 
					
						
							|  |  |  |     CVCotMEnemyData("Dragon Zombie",     1400,  390,  440,  15000, "boss"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Lizardman",          100,  345,  400,    800), | 
					
						
							|  |  |  |     CVCotMEnemyData("Franken",           1200,  700,  350,   2100), | 
					
						
							|  |  |  |     CVCotMEnemyData("Legion",             420,  610,  375,   1590), | 
					
						
							|  |  |  |     CVCotMEnemyData("Dullahan",           240,  550,  440,   2200), | 
					
						
							|  |  |  |     CVCotMEnemyData("Death",              880,  600,  800,  60000, "boss"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Camilla",           1500,  650,  700,  80000, "boss"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Hugh",              1400,  570,  750, 120000, "boss"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Dracula",           1100,  805,  850, 150000, "boss"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Dracula",           3000, 1000, 1000,      0, "final boss"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton Medalist",  250,  100,  100,   1500), | 
					
						
							|  |  |  |     CVCotMEnemyData("Were-jaguar",        320,  518,  260,   1200, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Were-wolf",          340,  525,  180,   1100, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Catoblepas",         560,  510,  435,   2000, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Hipogriff",          500,  620,  280,   1900, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Wind Demon",         490,  600,  540,   4000, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Witch",              210,  480,  340,   1000, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Stone Armor",        260,  585,  750,   3000, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Devil Tower",         50,  560,  700,    600, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton",           150,  400,  200,    500, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton Bomber",    150,  400,  200,    550, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Electric Skeleton",  150,  400,  200,    700, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Skeleton Spear",     150,  400,  200,    580, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Flame Demon",        680,  650,  600,   4500, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Bone Tower",         120,  500,  650,    800, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Fox Hunter",         160,  510,  220,    600, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Poison Armor",       380,  680,  634,   3600, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Bloody Sword",        55,  600, 1200,   2000, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Abiondarg",          188,  588,  288,    588, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Legion",             540,  760,  480,   2900, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Marionette",         200,  420,  400,   1200, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Minotaur",           580,  700,  715,   4100, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Arachne",            430,  590,  348,   2400, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Succubus",           300,  670,  630,   3100, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Demon Lord",         590,  800,  656,   4200, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Alraune",           1003,  640,  450,   5000, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Hyena",              210,  408,  170,   1000, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Devil Armor",        500,  804,  714,   6600), | 
					
						
							|  |  |  |     CVCotMEnemyData("Evil Pillar",         55,  655,  900,   1500, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("White Armor",        640,  770,  807,   7000), | 
					
						
							|  |  |  |     CVCotMEnemyData("Devil",             1530,  980, 1060,  30000, "battle arena"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Scary Candle",       150,  300,  300,    900, "candle"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Trick Candle",       200,  400,  400,   1400, "candle"), | 
					
						
							|  |  |  |     CVCotMEnemyData("Nightmare",          250,  550,  550,   2000), | 
					
						
							|  |  |  |     CVCotMEnemyData("Lilim",              400,  800,  800,   8000), | 
					
						
							|  |  |  |     CVCotMEnemyData("Lilith",             660,  960,  960,  20000), | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | # NOTE: Coffin is omitted from the end of this, as its presence doesn't | 
					
						
							|  |  |  | # actually impact the randomizer (all stats and drops inherited from Mummy). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | BOSS_IDS = [enemy_id for enemy_id in range(len(cvcotm_enemy_info)) if cvcotm_enemy_info[enemy_id].type == "boss"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ENEMY_TABLE_START = 0xCB2C4 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | NUMBER_ITEMS = 55 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | COUNTDOWN_TABLE_ADDR = 0x673400 | 
					
						
							|  |  |  | ITEM_ID_SHINNING_ARMOR = 11 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def shuffle_sub_weapons(world: "CVCotMWorld") -> Dict[int, bytes]: | 
					
						
							|  |  |  |     """Shuffles the sub-weapons amongst themselves.""" | 
					
						
							|  |  |  |     sub_bytes = list(rom_sub_weapon_offsets.values()) | 
					
						
							|  |  |  |     world.random.shuffle(sub_bytes) | 
					
						
							|  |  |  |     return dict(zip(rom_sub_weapon_offsets, sub_bytes)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_countdown_flags(world: "CVCotMWorld", active_locations: Iterable[Location]) -> Dict[int, bytes]: | 
					
						
							|  |  |  |     """Figures out which Countdown numbers to increase for each Location after verifying the Item on the Location should
 | 
					
						
							|  |  |  |     count towards a number. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Which number to increase is determined by the Location's "countdown" attr in its CVCotMLocationData.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     next_pos = COUNTDOWN_TABLE_ADDR + 0x40 | 
					
						
							|  |  |  |     countdown_flags: List[List[int]] = [[] for _ in range(16)] | 
					
						
							|  |  |  |     countdown_dict = {} | 
					
						
							|  |  |  |     ptr_offset = COUNTDOWN_TABLE_ADDR | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Loop over every Location. | 
					
						
							|  |  |  |     for loc in active_locations: | 
					
						
							|  |  |  |         # If the Location's Item is not Progression/Useful-classified with the "Majors" Countdown being used, or if the | 
					
						
							|  |  |  |         # Location is the Iron Maiden switch with the vanilla Iron Maiden behavior, skip adding its flag to the arrays. | 
					
						
							|  |  |  |         if (not loc.item.classification & MAJORS_CLASSIFICATIONS and world.options.countdown == | 
					
						
							|  |  |  |                 Countdown.option_majors): | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         countdown_index = cvcotm_location_info[loc.name].countdown | 
					
						
							|  |  |  |         # Take the Location's address if the above condition is satisfied, and get the flag value out of it. | 
					
						
							|  |  |  |         countdown_flags[countdown_index] += [loc.address & 0xFF, 0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Write the Countdown flag arrays and array pointers correctly. Each flag list should end with a 0xFFFF to indicate | 
					
						
							|  |  |  |     # the end of an area's list. | 
					
						
							|  |  |  |     for area_flags in countdown_flags: | 
					
						
							|  |  |  |         countdown_dict[ptr_offset] = int.to_bytes(next_pos | 0x08000000, 4, "little") | 
					
						
							|  |  |  |         countdown_dict[next_pos] = bytes(area_flags + [0xFF, 0xFF]) | 
					
						
							|  |  |  |         ptr_offset += 4 | 
					
						
							|  |  |  |         next_pos += len(area_flags) + 2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return countdown_dict | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_location_data(world: "CVCotMWorld", active_locations: Iterable[Location]) -> Dict[int, bytes]: | 
					
						
							|  |  |  |     """Gets ALL the Item data to go into the ROM. Items consist of four bytes; the first two represent the object ID
 | 
					
						
							|  |  |  |     for the "category" of item that it belongs to, the third is the sub-value for which item within that "category" it | 
					
						
							|  |  |  |     is, and the fourth controls the appearance it takes."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     location_bytes = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for loc in active_locations: | 
					
						
							|  |  |  |         # Figure out the item ID bytes to put in each Location's offset here. | 
					
						
							|  |  |  |         # If it's a CotM Item, always write the Item's primary type byte. | 
					
						
							|  |  |  |         if loc.item.game == "Castlevania - Circle of the Moon": | 
					
						
							|  |  |  |             type_byte = cvcotm_item_info[loc.item.name].code >> 8 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # If the Item is for this player, set the subtype to actually be that Item. | 
					
						
							|  |  |  |             # Otherwise, set a dummy subtype value that is different for every item type. | 
					
						
							|  |  |  |             if loc.item.player == world.player: | 
					
						
							|  |  |  |                 subtype_byte = cvcotm_item_info[loc.item.name].code & 0xFF | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 subtype_byte = other_player_subtype_bytes[type_byte] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # If it's a DSS Card, set the appearance based on whether it's progression or not; freeze combo cards should | 
					
						
							|  |  |  |             # all appear blue in color while the others are standard purple/yellow. Otherwise, set the appearance the | 
					
						
							|  |  |  |             # same way as the subtype for local items regardless of whether it's actually local or not. | 
					
						
							|  |  |  |             if type_byte == 0xE6: | 
					
						
							|  |  |  |                 if loc.item.advancement: | 
					
						
							|  |  |  |                     appearance_byte = 1 | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     appearance_byte = 0 | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 appearance_byte = cvcotm_item_info[loc.item.name].code & 0xFF | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # If it's not a CotM Item at all, always set the primary type to that of a Magic Item and the subtype to that of | 
					
						
							|  |  |  |         # a dummy item. The AP Items are all under Magic Items. | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             type_byte = 0xE8 | 
					
						
							|  |  |  |             subtype_byte = 0x0A | 
					
						
							|  |  |  |             # Decide which AP Item to use to represent the other game item. | 
					
						
							|  |  |  |             if loc.item.classification & ItemClassification.progression and \ | 
					
						
							|  |  |  |                     loc.item.classification & ItemClassification.useful: | 
					
						
							|  |  |  |                 appearance_byte = 0x0E  # Progression + Useful | 
					
						
							|  |  |  |             elif loc.item.classification & ItemClassification.progression: | 
					
						
							|  |  |  |                 appearance_byte = 0x0C  # Progression | 
					
						
							|  |  |  |             elif loc.item.classification & ItemClassification.useful: | 
					
						
							|  |  |  |                 appearance_byte = 0x0B  # Useful | 
					
						
							|  |  |  |             elif loc.item.classification & ItemClassification.trap: | 
					
						
							|  |  |  |                 appearance_byte = 0x0D  # Trap | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 appearance_byte = 0x0A  # Filler | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Check if the Item's game is in the other game item appearances' dict, and if so, if the Item is under that | 
					
						
							|  |  |  |             # game's name. If it is, change the appearance accordingly. | 
					
						
							|  |  |  |             # Right now, only SotN and Timespinner stat ups are supported. | 
					
						
							|  |  |  |             other_game_name = world.multiworld.worlds[loc.item.player].game | 
					
						
							|  |  |  |             if other_game_name in other_game_item_appearances: | 
					
						
							|  |  |  |                 if loc.item.name in other_game_item_appearances[other_game_name]: | 
					
						
							|  |  |  |                     type_byte = other_game_item_appearances[other_game_name][loc.item.name]["type"] | 
					
						
							|  |  |  |                     subtype_byte = other_player_subtype_bytes[type_byte] | 
					
						
							|  |  |  |                     appearance_byte = other_game_item_appearances[other_game_name][loc.item.name]["appearance"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create the correct bytes object for the Item on that Location. | 
					
						
							|  |  |  |         location_bytes[cvcotm_location_info[loc.name].offset] = bytes([type_byte, 1, subtype_byte, appearance_byte]) | 
					
						
							|  |  |  |     return location_bytes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def populate_enemy_drops(world: "CVCotMWorld") -> Dict[int, bytes]: | 
					
						
							|  |  |  |     """Randomizes the enemy-dropped items throughout the game within each other. There are three tiers of item drops:
 | 
					
						
							|  |  |  |     Low, Mid, and High. Each enemy has two item slots that can both drop its own item; a Common slot and a Rare one. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     On Normal item randomization, easy enemies (below 61 HP) will only have Low-tier drops in both of their stats, | 
					
						
							|  |  |  |     bosses and candle enemies will be guaranteed to have High drops in one or both of their slots respectively (bosses | 
					
						
							|  |  |  |     are made to only drop one slot 100% of the time), and everything else can have a Low or Mid-tier item in its Common | 
					
						
							|  |  |  |     drop slot and a Low, Mid, OR High-tier item in its Rare drop slot. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     If Item Drop Randomization is set to Tiered, the HP threshold for enemies being considered "easily" will raise to | 
					
						
							|  |  |  |     below 144, enemies in the 144-369 HP range (inclusive) will have a Low-tier item in its Common slot and a Mid-tier | 
					
						
							|  |  |  |     item in its rare slot, and enemies with more than 369 HP will have a Mid-tier in its Common slot and a High-tier in | 
					
						
							|  |  |  |     its Rare slot. Candles and bosses still have Rares in all their slots, but now the guaranteed drops that land on | 
					
						
							|  |  |  |     bosses will be exclusive to them; no other enemy in the game will have their item. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     This and select_drop are the most directly adapted code from upstream CotMR in this package by far. Credit where | 
					
						
							|  |  |  |     it's due to Spooky for writing the original, and Malaert64 for further refinements and updating what used to be | 
					
						
							|  |  |  |     Random Item Hardmode to instead be Tiered Item Mode. The original C code this was adapted from can be found here: | 
					
						
							|  |  |  |     https://github.com/calm-palm/cotm-randomizer/blob/master/Program/randomizer.c#L1028""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     placed_low_items = [0] * len(LOW_ITEMS) | 
					
						
							|  |  |  |     placed_mid_items = [0] * len(MID_ITEMS) | 
					
						
							|  |  |  |     placed_high_items = [0] * len(HIGH_ITEMS) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     placed_common_items = [0] * len(COMMON_ITEMS) | 
					
						
							|  |  |  |     placed_rare_items = [0] * len(RARE_ITEMS) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     regular_drops = [0] * len(cvcotm_enemy_info) | 
					
						
							|  |  |  |     regular_drop_chances = [0] * len(cvcotm_enemy_info) | 
					
						
							|  |  |  |     rare_drops = [0] * len(cvcotm_enemy_info) | 
					
						
							|  |  |  |     rare_drop_chances = [0] * len(cvcotm_enemy_info) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Set boss items first to prevent boss drop duplicates. | 
					
						
							|  |  |  |     # If Tiered mode is enabled, make these items exclusive to these enemies by adding an arbitrary integer larger | 
					
						
							|  |  |  |     # than could be reached normally (e.g.the total number of enemies) and use the placed high items array instead of | 
					
						
							|  |  |  |     # the placed rare items one. | 
					
						
							|  |  |  |     if world.options.item_drop_randomization == ItemDropRandomization.option_tiered: | 
					
						
							|  |  |  |         for boss_id in BOSS_IDS: | 
					
						
							|  |  |  |             regular_drops[boss_id] = select_drop(world, HIGH_ITEMS, placed_high_items, True) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         for boss_id in BOSS_IDS: | 
					
						
							|  |  |  |             regular_drops[boss_id] = select_drop(world, RARE_ITEMS, placed_rare_items, start_index=len(COMMON_ITEMS)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Setting drop logic for all enemies. | 
					
						
							|  |  |  |     for i in range(len(cvcotm_enemy_info)): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Give Dracula II Shining Armor occasionally as a joke. | 
					
						
							|  |  |  |         if cvcotm_enemy_info[i].type == "final boss": | 
					
						
							|  |  |  |             regular_drops[i] = rare_drops[i] = ITEM_ID_SHINNING_ARMOR | 
					
						
							|  |  |  |             regular_drop_chances[i] = rare_drop_chances[i] = 5000 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Set bosses' secondary item to none since we already set the primary item earlier. | 
					
						
							|  |  |  |         elif cvcotm_enemy_info[i].type == "boss": | 
					
						
							|  |  |  |             # Set rare drop to none. | 
					
						
							|  |  |  |             rare_drops[i] = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Max out rare boss drops (normally, drops are capped to 50% and 25% for common and rare respectively, but | 
					
						
							|  |  |  |             # Fuse's patch AllowAlwaysDrop.ips allows setting the regular item drop chance to 10000 to force a drop | 
					
						
							|  |  |  |             # always) | 
					
						
							|  |  |  |             regular_drop_chances[i] = 10000 | 
					
						
							|  |  |  |             rare_drop_chances[i] = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Candle enemies use a similar placement logic to the bosses, except items that land on them are NOT exclusive | 
					
						
							|  |  |  |         # to them on Tiered mode. | 
					
						
							|  |  |  |         elif cvcotm_enemy_info[i].type == "candle": | 
					
						
							|  |  |  |             if world.options.item_drop_randomization == ItemDropRandomization.option_tiered: | 
					
						
							|  |  |  |                 regular_drops[i] = select_drop(world, HIGH_ITEMS, placed_high_items) | 
					
						
							|  |  |  |                 rare_drops[i] = select_drop(world, HIGH_ITEMS, placed_high_items) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 regular_drops[i] = select_drop(world, RARE_ITEMS, placed_rare_items, start_index=len(COMMON_ITEMS)) | 
					
						
							|  |  |  |                 rare_drops[i] = select_drop(world, RARE_ITEMS, placed_rare_items, start_index=len(COMMON_ITEMS)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Set base drop chances at 20-30% for common and 15-20% for rare. | 
					
						
							|  |  |  |             regular_drop_chances[i] = 2000 + world.random.randint(0, 1000) | 
					
						
							|  |  |  |             rare_drop_chances[i] = 1500 + world.random.randint(0, 500) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # On All Bosses and Battle Arena Required, the Shinning Armor at the end of Battle Arena is removed. | 
					
						
							|  |  |  |         # We compensate for this by giving the Battle Arena Devil a 100% chance to drop Shinning Armor. | 
					
						
							|  |  |  |         elif cvcotm_enemy_info[i].name == "Devil" and cvcotm_enemy_info[i].type == "battle arena" and \ | 
					
						
							|  |  |  |                 world.options.required_skirmishes == RequiredSkirmishes.option_all_bosses_and_arena: | 
					
						
							|  |  |  |             regular_drops[i] = ITEM_ID_SHINNING_ARMOR | 
					
						
							|  |  |  |             rare_drops[i] = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             regular_drop_chances[i] = 10000 | 
					
						
							|  |  |  |             rare_drop_chances[i] = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Low-tier items drop from enemies that are trivial to farm (60 HP or less) | 
					
						
							|  |  |  |         # on Normal drop logic, or enemies under 144 HP on Tiered logic. | 
					
						
							|  |  |  |         elif (world.options.item_drop_randomization == ItemDropRandomization.option_normal and | 
					
						
							|  |  |  |               cvcotm_enemy_info[i].hp <= 60) or \ | 
					
						
							|  |  |  |                 (world.options.item_drop_randomization == ItemDropRandomization.option_tiered and | 
					
						
							|  |  |  |                  cvcotm_enemy_info[i].hp <= 143): | 
					
						
							|  |  |  |             # Low-tier enemy drops. | 
					
						
							|  |  |  |             regular_drops[i] = select_drop(world, LOW_ITEMS, placed_low_items) | 
					
						
							|  |  |  |             rare_drops[i] = select_drop(world, LOW_ITEMS, placed_low_items) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Set base drop chances at 6-10% for common and 3-6% for rare. | 
					
						
							|  |  |  |             regular_drop_chances[i] = 600 + world.random.randint(0, 400) | 
					
						
							|  |  |  |             rare_drop_chances[i] = 300 + world.random.randint(0, 300) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Rest of Tiered logic, by Malaert64. | 
					
						
							|  |  |  |         elif world.options.item_drop_randomization == ItemDropRandomization.option_tiered: | 
					
						
							|  |  |  |             # If under 370 HP, mid-tier enemy. | 
					
						
							|  |  |  |             if cvcotm_enemy_info[i].hp <= 369: | 
					
						
							|  |  |  |                 regular_drops[i] = select_drop(world, LOW_ITEMS, placed_low_items) | 
					
						
							|  |  |  |                 rare_drops[i] = select_drop(world, MID_ITEMS, placed_mid_items) | 
					
						
							|  |  |  |             # Otherwise, enemy HP is 370+, thus high-tier enemy. | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 regular_drops[i] = select_drop(world, MID_ITEMS, placed_mid_items) | 
					
						
							|  |  |  |                 rare_drops[i] = select_drop(world, HIGH_ITEMS, placed_high_items) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Set base drop chances at 6-10% for common and 3-6% for rare. | 
					
						
							|  |  |  |             regular_drop_chances[i] = 600 + world.random.randint(0, 400) | 
					
						
							|  |  |  |             rare_drop_chances[i] = 300 + world.random.randint(0, 300) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Regular enemies outside Tiered logic. | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # Select a random regular and rare drop for every enemy from their respective lists. | 
					
						
							|  |  |  |             regular_drops[i] = select_drop(world, COMMON_ITEMS, placed_common_items) | 
					
						
							|  |  |  |             rare_drops[i] = select_drop(world, RARE_ITEMS, placed_rare_items) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Set base drop chances at 6-10% for common and 3-6% for rare. | 
					
						
							|  |  |  |             regular_drop_chances[i] = 600 + world.random.randint(0, 400) | 
					
						
							|  |  |  |             rare_drop_chances[i] = 300 + world.random.randint(0, 300) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Return the randomized drop data as bytes with their respective offsets. | 
					
						
							|  |  |  |     enemy_address = ENEMY_TABLE_START | 
					
						
							|  |  |  |     drop_data = {} | 
					
						
							|  |  |  |     for i, enemy_info in enumerate(cvcotm_enemy_info): | 
					
						
							|  |  |  |         drop_data[enemy_address] = bytes([regular_drops[i], 0, regular_drop_chances[i] & 0xFF, | 
					
						
							|  |  |  |                                           regular_drop_chances[i] >> 8, rare_drops[i], 0, rare_drop_chances[i] & 0xFF, | 
					
						
							|  |  |  |                                           rare_drop_chances[i] >> 8]) | 
					
						
							|  |  |  |         enemy_address += 20 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return drop_data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def select_drop(world: "CVCotMWorld", drop_list: List[int], drops_placed: List[int], exclusive_drop: bool = False, | 
					
						
							|  |  |  |                 start_index: int = 0) -> int: | 
					
						
							|  |  |  |     """Chooses a drop from a given list of drops based on another given list of how many drops from that list were
 | 
					
						
							|  |  |  |     selected before. In order to ensure an even number of drops are distributed, drops that were selected the least are | 
					
						
							|  |  |  |     the ones that will be picked from. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Calling this with exclusive_drop param being True will force the number of the chosen item really high to ensure it | 
					
						
							|  |  |  |     will never be picked again."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Take the list of placed item drops beginning from the starting index. | 
					
						
							|  |  |  |     drops_from_start_index = drops_placed[start_index:] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Determine the lowest drop counts and the indices with that drop count. | 
					
						
							|  |  |  |     lowest_number = min(drops_from_start_index) | 
					
						
							|  |  |  |     indices_with_lowest_number = [index for index, placed in enumerate(drops_from_start_index) if | 
					
						
							|  |  |  |                                   placed == lowest_number] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     random_index = world.random.choice(indices_with_lowest_number) | 
					
						
							|  |  |  |     random_index += start_index  # Add start_index back on | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Increment the number of this item placed, unless it should be exclusive to the boss / candle, in which case | 
					
						
							|  |  |  |     # set it to an arbitrarily large number to make it exclusive. | 
					
						
							|  |  |  |     if exclusive_drop: | 
					
						
							|  |  |  |         drops_placed[random_index] += 999 | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         drops_placed[random_index] += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Return the in-game item ID of the chosen item. | 
					
						
							|  |  |  |     return drop_list[random_index] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_start_inventory_data(world: "CVCotMWorld") -> Tuple[Dict[int, bytes], bool]: | 
					
						
							|  |  |  |     """Calculate and return the starting inventory arrays. Different items go into different arrays, so they all have
 | 
					
						
							|  |  |  |     to be handled accordingly."""
 | 
					
						
							|  |  |  |     start_inventory_data = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     magic_items_array = [0 for _ in range(8)] | 
					
						
							|  |  |  |     cards_array = [0 for _ in range(20)] | 
					
						
							|  |  |  |     extra_stats = {"extra health": 0, | 
					
						
							|  |  |  |                    "extra magic": 0, | 
					
						
							|  |  |  |                    "extra hearts": 0} | 
					
						
							|  |  |  |     start_with_detonator = False | 
					
						
							|  |  |  |     # If the Iron Maiden Behavior option is set to Start Broken, consider ourselves starting with the Maiden Detonator. | 
					
						
							|  |  |  |     if world.options.iron_maiden_behavior == IronMaidenBehavior.option_start_broken: | 
					
						
							|  |  |  |         start_with_detonator = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Always start with the Dash Boots. | 
					
						
							|  |  |  |     magic_items_array[0] = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for item in world.multiworld.precollected_items[world.player]: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         array_offset = item.code & 0xFF | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # If it's a Maiden Detonator we're starting with, set the boolean for it to True. | 
					
						
							|  |  |  |         if item.name == iname.ironmaidens: | 
					
						
							|  |  |  |             start_with_detonator = True | 
					
						
							|  |  |  |         # If it's a Max Up we're starting with, check if increasing the extra amount of that stat will put us over the | 
					
						
							|  |  |  |         # max amount of the stat allowed. If it will, set the current extra amount to the max. Otherwise, increase it by | 
					
						
							|  |  |  |         # the amount that it should. | 
					
						
							|  |  |  |         elif "Max Up" in item.name: | 
					
						
							|  |  |  |             info = extra_starting_stat_info[item.name] | 
					
						
							|  |  |  |             if extra_stats[info["variable"]] + info["amount_per"] > info["max_allowed"]: | 
					
						
							|  |  |  |                 extra_stats[info["variable"]] = info["max_allowed"] | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 extra_stats[info["variable"]] += info["amount_per"] | 
					
						
							|  |  |  |         # If it's a DSS card we're starting with, set that card's value in the cards array. | 
					
						
							|  |  |  |         elif "Card" in item.name: | 
					
						
							|  |  |  |             cards_array[array_offset] = 1 | 
					
						
							|  |  |  |         # If it's none of the above, it has to be a regular Magic Item. | 
					
						
							|  |  |  |         # Increase that Magic Item's value in the Magic Items array if it's not greater than 240. Last Keys are the only | 
					
						
							|  |  |  |         # Magic Item wherein having more than one is relevant. | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # Decrease the Magic Item array offset by 1 if it's higher than the unused Map's item value. | 
					
						
							|  |  |  |             if array_offset > 5: | 
					
						
							|  |  |  |                 array_offset -= 1 | 
					
						
							|  |  |  |             if magic_items_array[array_offset] < 240: | 
					
						
							|  |  |  |                 magic_items_array[array_offset] += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Add the start inventory arrays to the offset data in bytes form. | 
					
						
							| 
									
										
										
										
											2025-06-27 16:25:45 -06:00
										 |  |  |     start_inventory_data[0x690080] = bytes(magic_items_array) | 
					
						
							|  |  |  |     start_inventory_data[0x6900A0] = bytes(cards_array) | 
					
						
							| 
									
										
											  
											
												Castlevania: Circle of the Moon - Implement New Game (#3299)
* 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>
											
										 
											2024-12-12 06:47:47 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Add the extra max HP/MP/Hearts to all classes' base stats. Doing it this way makes us less likely to hit the max | 
					
						
							|  |  |  |     # possible Max Ups. | 
					
						
							|  |  |  |     # Vampire Killer | 
					
						
							|  |  |  |     start_inventory_data[0xE08C6] = int.to_bytes(100 + extra_stats["extra health"], 2, "little") | 
					
						
							|  |  |  |     start_inventory_data[0xE08CE] = int.to_bytes(100 + extra_stats["extra magic"], 2, "little") | 
					
						
							|  |  |  |     start_inventory_data[0xE08D4] = int.to_bytes(50 + extra_stats["extra hearts"], 2, "little") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Magician | 
					
						
							|  |  |  |     start_inventory_data[0xE090E] = int.to_bytes(50 + extra_stats["extra health"], 2, "little") | 
					
						
							|  |  |  |     start_inventory_data[0xE0916] = int.to_bytes(400 + extra_stats["extra magic"], 2, "little") | 
					
						
							|  |  |  |     start_inventory_data[0xE091C] = int.to_bytes(50 + extra_stats["extra hearts"], 2, "little") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Fighter | 
					
						
							|  |  |  |     start_inventory_data[0xE0932] = int.to_bytes(200 + extra_stats["extra health"], 2, "little") | 
					
						
							|  |  |  |     start_inventory_data[0xE093A] = int.to_bytes(50 + extra_stats["extra magic"], 2, "little") | 
					
						
							|  |  |  |     start_inventory_data[0xE0940] = int.to_bytes(50 + extra_stats["extra hearts"], 2, "little") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Shooter | 
					
						
							|  |  |  |     start_inventory_data[0xE0832] = int.to_bytes(50 + extra_stats["extra health"], 2, "little") | 
					
						
							|  |  |  |     start_inventory_data[0xE08F2] = int.to_bytes(100 + extra_stats["extra magic"], 2, "little") | 
					
						
							|  |  |  |     start_inventory_data[0xE08F8] = int.to_bytes(250 + extra_stats["extra hearts"], 2, "little") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Thief | 
					
						
							|  |  |  |     start_inventory_data[0xE0956] = int.to_bytes(50 + extra_stats["extra health"], 2, "little") | 
					
						
							|  |  |  |     start_inventory_data[0xE095E] = int.to_bytes(50 + extra_stats["extra magic"], 2, "little") | 
					
						
							|  |  |  |     start_inventory_data[0xE0964] = int.to_bytes(50 + extra_stats["extra hearts"], 2, "little") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return start_inventory_data, start_with_detonator |