762 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			762 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								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]] = {
							 | 
						||
| 
								 | 
							
								    # NOTE: Symphony of the Night is currently an unsupported world not in main.
							 | 
						||
| 
								 | 
							
								    "Symphony of the Night": {"Life Vessel": {"type": 0xE4,
							 | 
						||
| 
								 | 
							
								                                              "appearance": 0x01},
							 | 
						||
| 
								 | 
							
								                              "Heart Vessel": {"type": 0xE4,
							 | 
						||
| 
								 | 
							
								                                               "appearance": 0x00}},
							 | 
						||
| 
								 | 
							
								    "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.
							 | 
						||
| 
								 | 
							
								    start_inventory_data[0x680080] = bytes(magic_items_array)
							 | 
						||
| 
								 | 
							
								    start_inventory_data[0x6800A0] = bytes(cards_array)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # 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
							 |