215 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			215 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from BaseClasses import Item
							 | 
						||
| 
								 | 
							
								from .data import iname
							 | 
						||
| 
								 | 
							
								from .locations import base_id, get_location_info
							 | 
						||
| 
								 | 
							
								from .options import DraculasCondition, SpareKeys
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from typing import TYPE_CHECKING, Dict, Union
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if TYPE_CHECKING:
							 | 
						||
| 
								 | 
							
								    from . import CV64World
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import math
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class CV64Item(Item):
							 | 
						||
| 
								 | 
							
								    game: str = "Castlevania 64"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# # #    KEY    # # #
							 | 
						||
| 
								 | 
							
								# "code" = The unique part of the Item's AP code attribute, as well as the value to call the in-game "prepare item
							 | 
						||
| 
								 | 
							
								#          textbox" function with to give the Item in-game. Add this + base_id to get the actual AP code.
							 | 
						||
| 
								 | 
							
								# "default classification" = The AP Item Classification that gets assigned to instances of that Item in create_item
							 | 
						||
| 
								 | 
							
								#                            by default, unless I deliberately override it (as is the case for some Special1s).
							 | 
						||
| 
								 | 
							
								# "inventory offset" = What offset from the start of the in-game inventory array (beginning at 0x80389C4B) stores the
							 | 
						||
| 
								 | 
							
								#                      current count for that Item. Used for start inventory purposes.
							 | 
						||
| 
								 | 
							
								# "pickup actor id" = The ID for the Item's in-game Item pickup actor. If it's not in the Item's data dict, it's the
							 | 
						||
| 
								 | 
							
								#                     same as the Item's code. This is what gets written in the ROM to replace non-NPC/shop items.
							 | 
						||
| 
								 | 
							
								# "sub equip id" = For sub-weapons specifically, this is the number to put in the game's "current sub-weapon" value to
							 | 
						||
| 
								 | 
							
								#                  indicate the player currently having that weapon. Used for start inventory purposes.
							 | 
						||
| 
								 | 
							
								item_info = {
							 | 
						||
| 
								 | 
							
								    # White jewel
							 | 
						||
| 
								 | 
							
								    iname.red_jewel_s:        {"code": 0x02,  "default classification": "filler"},
							 | 
						||
| 
								 | 
							
								    iname.red_jewel_l:        {"code": 0x03,  "default classification": "filler"},
							 | 
						||
| 
								 | 
							
								    iname.special_one:        {"code": 0x04,  "default classification": "progression_skip_balancing",
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 0},
							 | 
						||
| 
								 | 
							
								    iname.special_two:        {"code": 0x05,  "default classification": "progression_skip_balancing",
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 1},
							 | 
						||
| 
								 | 
							
								    iname.roast_chicken:      {"code": 0x06,  "default classification": "filler", "inventory offset": 2},
							 | 
						||
| 
								 | 
							
								    iname.roast_beef:         {"code": 0x07,  "default classification": "filler", "inventory offset": 3},
							 | 
						||
| 
								 | 
							
								    iname.healing_kit:        {"code": 0x08,  "default classification": "useful", "inventory offset": 4},
							 | 
						||
| 
								 | 
							
								    iname.purifying:          {"code": 0x09,  "default classification": "filler", "inventory offset": 5},
							 | 
						||
| 
								 | 
							
								    iname.cure_ampoule:       {"code": 0x0A,  "default classification": "filler", "inventory offset": 6},
							 | 
						||
| 
								 | 
							
								    # pot-pourri
							 | 
						||
| 
								 | 
							
								    iname.powerup:            {"code": 0x0C,  "default classification": "filler"},
							 | 
						||
| 
								 | 
							
								    iname.permaup:            {"code": 0x10C, "default classification": "useful", "pickup actor id": 0x0C,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 8},
							 | 
						||
| 
								 | 
							
								    iname.knife:              {"code": 0x0D,  "default classification": "filler", "pickup actor id": 0x10,
							 | 
						||
| 
								 | 
							
								                               "sub equip id": 1},
							 | 
						||
| 
								 | 
							
								    iname.holy_water:         {"code": 0x0E,  "default classification": "filler", "pickup actor id": 0x0D,
							 | 
						||
| 
								 | 
							
								                               "sub equip id": 2},
							 | 
						||
| 
								 | 
							
								    iname.cross:              {"code": 0x0F,  "default classification": "filler", "pickup actor id": 0x0E,
							 | 
						||
| 
								 | 
							
								                               "sub equip id": 3},
							 | 
						||
| 
								 | 
							
								    iname.axe:                {"code": 0x10,  "default classification": "filler", "pickup actor id": 0x0F,
							 | 
						||
| 
								 | 
							
								                               "sub equip id": 4},
							 | 
						||
| 
								 | 
							
								    # Wooden stake (AP item)
							 | 
						||
| 
								 | 
							
								    iname.ice_trap:           {"code": 0x12,  "default classification": "trap"},
							 | 
						||
| 
								 | 
							
								    # The contract
							 | 
						||
| 
								 | 
							
								    # engagement ring
							 | 
						||
| 
								 | 
							
								    iname.magical_nitro:      {"code": 0x15,  "default classification": "progression", "inventory offset": 17},
							 | 
						||
| 
								 | 
							
								    iname.mandragora:         {"code": 0x16,  "default classification": "progression", "inventory offset": 18},
							 | 
						||
| 
								 | 
							
								    iname.sun_card:           {"code": 0x17,  "default classification": "filler", "inventory offset": 19},
							 | 
						||
| 
								 | 
							
								    iname.moon_card:          {"code": 0x18,  "default classification": "filler", "inventory offset": 20},
							 | 
						||
| 
								 | 
							
								    # Incandescent gaze
							 | 
						||
| 
								 | 
							
								    iname.archives_key:       {"code": 0x1A,  "default classification": "progression", "pickup actor id": 0x1D,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 22},
							 | 
						||
| 
								 | 
							
								    iname.left_tower_key:     {"code": 0x1B,  "default classification": "progression", "pickup actor id": 0x1E,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 23},
							 | 
						||
| 
								 | 
							
								    iname.storeroom_key:      {"code": 0x1C,  "default classification": "progression", "pickup actor id": 0x1F,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 24},
							 | 
						||
| 
								 | 
							
								    iname.garden_key:         {"code": 0x1D,  "default classification": "progression", "pickup actor id": 0x20,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 25},
							 | 
						||
| 
								 | 
							
								    iname.copper_key:         {"code": 0x1E,  "default classification": "progression", "pickup actor id": 0x21,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 26},
							 | 
						||
| 
								 | 
							
								    iname.chamber_key:        {"code": 0x1F,  "default classification": "progression", "pickup actor id": 0x22,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 27},
							 | 
						||
| 
								 | 
							
								    iname.execution_key:      {"code": 0x20,  "default classification": "progression", "pickup actor id": 0x23,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 28},
							 | 
						||
| 
								 | 
							
								    iname.science_key1:       {"code": 0x21,  "default classification": "progression", "pickup actor id": 0x24,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 29},
							 | 
						||
| 
								 | 
							
								    iname.science_key2:       {"code": 0x22,  "default classification": "progression", "pickup actor id": 0x25,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 30},
							 | 
						||
| 
								 | 
							
								    iname.science_key3:       {"code": 0x23,  "default classification": "progression", "pickup actor id": 0x26,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 31},
							 | 
						||
| 
								 | 
							
								    iname.clocktower_key1:    {"code": 0x24,  "default classification": "progression", "pickup actor id": 0x27,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 32},
							 | 
						||
| 
								 | 
							
								    iname.clocktower_key2:    {"code": 0x25,  "default classification": "progression", "pickup actor id": 0x28,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 33},
							 | 
						||
| 
								 | 
							
								    iname.clocktower_key3:    {"code": 0x26,  "default classification": "progression", "pickup actor id": 0x29,
							 | 
						||
| 
								 | 
							
								                               "inventory offset": 34},
							 | 
						||
| 
								 | 
							
								    iname.five_hundred_gold:  {"code": 0x27,  "default classification": "filler", "pickup actor id": 0x1A},
							 | 
						||
| 
								 | 
							
								    iname.three_hundred_gold: {"code": 0x28,  "default classification": "filler", "pickup actor id": 0x1B},
							 | 
						||
| 
								 | 
							
								    iname.one_hundred_gold:   {"code": 0x29,  "default classification": "filler", "pickup actor id": 0x1C},
							 | 
						||
| 
								 | 
							
								    iname.crystal:            {"default classification": "progression"},
							 | 
						||
| 
								 | 
							
								    iname.trophy:             {"default classification": "progression"},
							 | 
						||
| 
								 | 
							
								    iname.victory:            {"default classification": "progression"}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								filler_item_names = [iname.red_jewel_s, iname.red_jewel_l, iname.five_hundred_gold, iname.three_hundred_gold,
							 | 
						||
| 
								 | 
							
								                     iname.one_hundred_gold]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_item_info(item: str, info: str) -> Union[str, int, None]:
							 | 
						||
| 
								 | 
							
								    return item_info[item].get(info, None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_item_names_to_ids() -> Dict[str, int]:
							 | 
						||
| 
								 | 
							
								    return {name: get_item_info(name, "code")+base_id for name in item_info if get_item_info(name, "code") is not None}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_item_counts(world: "CV64World") -> Dict[str, Dict[str, int]]:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    active_locations = world.multiworld.get_unfilled_locations(world.player)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    item_counts = {
							 | 
						||
| 
								 | 
							
								        "progression": {},
							 | 
						||
| 
								 | 
							
								        "progression_skip_balancing": {},
							 | 
						||
| 
								 | 
							
								        "useful": {},
							 | 
						||
| 
								 | 
							
								        "filler": {},
							 | 
						||
| 
								 | 
							
								        "trap": {}
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    total_items = 0
							 | 
						||
| 
								 | 
							
								    extras_count = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Get from each location its vanilla item and add it to the default item counts.
							 | 
						||
| 
								 | 
							
								    for loc in active_locations:
							 | 
						||
| 
								 | 
							
								        if loc.address is None:
							 | 
						||
| 
								 | 
							
								            continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if world.options.hard_item_pool and get_location_info(loc.name, "hard item") is not None:
							 | 
						||
| 
								 | 
							
								            item_to_add = get_location_info(loc.name, "hard item")
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            item_to_add = get_location_info(loc.name, "normal item")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        classification = get_item_info(item_to_add, "default classification")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if item_to_add not in item_counts[classification]:
							 | 
						||
| 
								 | 
							
								            item_counts[classification][item_to_add] = 1
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            item_counts[classification][item_to_add] += 1
							 | 
						||
| 
								 | 
							
								        total_items += 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Replace all but 2 PowerUps with junk if Permanent PowerUps is on and mark those two PowerUps as Useful.
							 | 
						||
| 
								 | 
							
								    if world.options.permanent_powerups:
							 | 
						||
| 
								 | 
							
								        for i in range(item_counts["filler"][iname.powerup] - 2):
							 | 
						||
| 
								 | 
							
								            item_counts["filler"][world.get_filler_item_name()] += 1
							 | 
						||
| 
								 | 
							
								        del(item_counts["filler"][iname.powerup])
							 | 
						||
| 
								 | 
							
								        item_counts["useful"][iname.permaup] = 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Add the total Special1s.
							 | 
						||
| 
								 | 
							
								    item_counts["progression_skip_balancing"][iname.special_one] = world.options.total_special1s.value
							 | 
						||
| 
								 | 
							
								    extras_count += world.options.total_special1s.value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Add the total Special2s if Dracula's Condition is Special2s.
							 | 
						||
| 
								 | 
							
								    if world.options.draculas_condition == DraculasCondition.option_specials:
							 | 
						||
| 
								 | 
							
								        item_counts["progression_skip_balancing"][iname.special_two] = world.options.total_special2s.value
							 | 
						||
| 
								 | 
							
								        extras_count += world.options.total_special2s.value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Determine the extra key counts if applicable. Doing this before moving Special1s will ensure only the keys and
							 | 
						||
| 
								 | 
							
								    # bomb components are affected by this.
							 | 
						||
| 
								 | 
							
								    for key in item_counts["progression"]:
							 | 
						||
| 
								 | 
							
								        spare_keys = 0
							 | 
						||
| 
								 | 
							
								        if world.options.spare_keys == SpareKeys.option_on:
							 | 
						||
| 
								 | 
							
								            spare_keys = item_counts["progression"][key]
							 | 
						||
| 
								 | 
							
								        elif world.options.spare_keys == SpareKeys.option_chance:
							 | 
						||
| 
								 | 
							
								            if item_counts["progression"][key] > 0:
							 | 
						||
| 
								 | 
							
								                for i in range(item_counts["progression"][key]):
							 | 
						||
| 
								 | 
							
								                    spare_keys += world.random.randint(0, 1)
							 | 
						||
| 
								 | 
							
								        item_counts["progression"][key] += spare_keys
							 | 
						||
| 
								 | 
							
								        extras_count += spare_keys
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Move the total number of Special1s needed to warp everywhere to normal progression balancing if S1s per warp is
							 | 
						||
| 
								 | 
							
								    # 3 or lower.
							 | 
						||
| 
								 | 
							
								    if world.s1s_per_warp <= 3:
							 | 
						||
| 
								 | 
							
								        item_counts["progression_skip_balancing"][iname.special_one] -= world.s1s_per_warp * 7
							 | 
						||
| 
								 | 
							
								        item_counts["progression"][iname.special_one] = world.s1s_per_warp * 7
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Determine the total amounts of replaceable filler and non-filler junk.
							 | 
						||
| 
								 | 
							
								    total_filler_junk = 0
							 | 
						||
| 
								 | 
							
								    total_non_filler_junk = 0
							 | 
						||
| 
								 | 
							
								    for junk in item_counts["filler"]:
							 | 
						||
| 
								 | 
							
								        if junk in filler_item_names:
							 | 
						||
| 
								 | 
							
								            total_filler_junk += item_counts["filler"][junk]
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            total_non_filler_junk += item_counts["filler"][junk]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Subtract from the filler counts total number of "extra" items we've added. get_filler_item_name() filler will be
							 | 
						||
| 
								 | 
							
								    # subtracted from first until we run out of that, at which point we'll start subtracting from the rest. At this
							 | 
						||
| 
								 | 
							
								    # moment, non-filler item name filler cannot run out no matter the settings, so I haven't bothered adding handling
							 | 
						||
| 
								 | 
							
								    # for when it does yet.
							 | 
						||
| 
								 | 
							
								    available_filler_junk = filler_item_names.copy()
							 | 
						||
| 
								 | 
							
								    for i in range(extras_count):
							 | 
						||
| 
								 | 
							
								        if total_filler_junk > 0:
							 | 
						||
| 
								 | 
							
								            total_filler_junk -= 1
							 | 
						||
| 
								 | 
							
								            item_to_subtract = world.random.choice(available_filler_junk)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            total_non_filler_junk -= 1
							 | 
						||
| 
								 | 
							
								            item_to_subtract = world.random.choice(list(item_counts["filler"].keys()))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        item_counts["filler"][item_to_subtract] -= 1
							 | 
						||
| 
								 | 
							
								        if item_counts["filler"][item_to_subtract] == 0:
							 | 
						||
| 
								 | 
							
								            del(item_counts["filler"][item_to_subtract])
							 | 
						||
| 
								 | 
							
								            if item_to_subtract in available_filler_junk:
							 | 
						||
| 
								 | 
							
								                available_filler_junk.remove(item_to_subtract)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Determine the Ice Trap count by taking a certain % of the total filler remaining at this point.
							 | 
						||
| 
								 | 
							
								    item_counts["trap"][iname.ice_trap] = math.floor((total_filler_junk + total_non_filler_junk) *
							 | 
						||
| 
								 | 
							
								                                                     (world.options.ice_trap_percentage.value / 100.0))
							 | 
						||
| 
								 | 
							
								    for i in range(item_counts["trap"][iname.ice_trap]):
							 | 
						||
| 
								 | 
							
								        # Subtract the remaining filler after determining the ice trap count.
							 | 
						||
| 
								 | 
							
								        item_to_subtract = world.random.choice(list(item_counts["filler"].keys()))
							 | 
						||
| 
								 | 
							
								        item_counts["filler"][item_to_subtract] -= 1
							 | 
						||
| 
								 | 
							
								        if item_counts["filler"][item_to_subtract] == 0:
							 | 
						||
| 
								 | 
							
								            del (item_counts["filler"][item_to_subtract])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return item_counts
							 |