134 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			134 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Python
		
	
	
	
| import struct
 | |
| from typing import Optional, Dict, TYPE_CHECKING, List, Union
 | |
| from BaseClasses import Region, ItemClassification, MultiWorld
 | |
| from worlds.Files import APTokenTypes
 | |
| from .client_addrs import consumable_addrs, star_addrs
 | |
| 
 | |
| if TYPE_CHECKING:
 | |
|     from .rom import KDL3ProcedurePatch
 | |
| 
 | |
| animal_map = {
 | |
|     "Rick Spawn": 0,
 | |
|     "Kine Spawn": 1,
 | |
|     "Coo Spawn": 2,
 | |
|     "Nago Spawn": 3,
 | |
|     "ChuChu Spawn": 4,
 | |
|     "Pitch Spawn": 5
 | |
| }
 | |
| 
 | |
| 
 | |
| class KDL3Room(Region):
 | |
|     pointer: int = 0
 | |
|     level: int = 0
 | |
|     stage: int = 0
 | |
|     room: int = 0
 | |
|     music: int = 0
 | |
|     default_exits: List[Dict[str, Union[int, List[str]]]]
 | |
|     animal_pointers: List[int]
 | |
|     enemies: List[str]
 | |
|     entity_load: List[List[int]]
 | |
|     consumables: List[Dict[str, Union[int, str]]]
 | |
| 
 | |
|     def __init__(self, name: str, player: int, multiworld: MultiWorld, hint: Optional[str], level: int,
 | |
|                  stage: int, room: int, pointer: int, music: int,
 | |
|                  default_exits: List[Dict[str, List[str]]],
 | |
|                  animal_pointers: List[int], enemies: List[str],
 | |
|                  entity_load: List[List[int]],
 | |
|                  consumables: List[Dict[str, Union[int, str]]], consumable_pointer: int) -> None:
 | |
|         super().__init__(name, player, multiworld, hint)
 | |
|         self.level = level
 | |
|         self.stage = stage
 | |
|         self.room = room
 | |
|         self.pointer = pointer
 | |
|         self.music = music
 | |
|         self.default_exits = default_exits
 | |
|         self.animal_pointers = animal_pointers
 | |
|         self.enemies = enemies
 | |
|         self.entity_load = entity_load
 | |
|         self.consumables = consumables
 | |
|         self.consumable_pointer = consumable_pointer
 | |
| 
 | |
|     def patch(self, patch: "KDL3ProcedurePatch", consumables: bool, local_items: bool) -> None:
 | |
|         patch.write_token(APTokenTypes.WRITE, self.pointer + 2, self.music.to_bytes(1, "little"))
 | |
|         animals = [x.item.name for x in self.locations if "Animal" in x.name and x.item]
 | |
|         if len(animals) > 0:
 | |
|             for current_animal, address in zip(animals, self.animal_pointers):
 | |
|                 patch.write_token(APTokenTypes.WRITE, self.pointer + address + 7,
 | |
|                                   animal_map[current_animal].to_bytes(1, "little"))
 | |
|         if local_items:
 | |
|             for location in self.get_locations():
 | |
|                 if location.item is None or location.item.player != self.player:
 | |
|                     continue
 | |
|                 item = location.item.code
 | |
|                 if item is None:
 | |
|                     continue
 | |
|                 item_idx = item & 0x00000F
 | |
|                 location_idx = location.address & 0xFFFF
 | |
|                 if location_idx & 0xF00 in (0x300, 0x400, 0x500, 0x600):
 | |
|                     # consumable or star, need remapped
 | |
|                     location_base = location_idx & 0xF00
 | |
|                     if location_base == 0x300:
 | |
|                         # consumable
 | |
|                         location_idx = consumable_addrs[location_idx & 0xFF] | 0x1000
 | |
|                     else:
 | |
|                         # star
 | |
|                         location_idx = star_addrs[location.address] | 0x2000
 | |
|                 if item & 0x000070 == 0:
 | |
|                     patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x10]))
 | |
|                 elif item & 0x000010 > 0:
 | |
|                     patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x20]))
 | |
|                 elif item & 0x000020 > 0:
 | |
|                     patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x40]))
 | |
|                 elif item & 0x000040 > 0:
 | |
|                     patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x80]))
 | |
| 
 | |
|         if consumables:
 | |
|             load_len = len(self.entity_load)
 | |
|             for consumable in self.consumables:
 | |
|                 location = next(x for x in self.locations if x.name == consumable["name"])
 | |
|                 assert location.item is not None
 | |
|                 is_progression = location.item.classification & ItemClassification.progression
 | |
|                 if load_len == 8:
 | |
|                     # edge case, there is exactly 1 room with 8 entities and only 1 consumable among them
 | |
|                     if not (any(x in self.entity_load for x in [[0, 22], [1, 22]])
 | |
|                             and any(x in self.entity_load for x in [[2, 22], [3, 22]])):
 | |
|                         replacement_target = self.entity_load.index(
 | |
|                             next(x for x in self.entity_load if x in [[0, 22], [1, 22], [2, 22], [3, 22]]))
 | |
|                         if is_progression:
 | |
|                             vtype = 0
 | |
|                         else:
 | |
|                             vtype = 2
 | |
|                         patch.write_token(APTokenTypes.WRITE, self.pointer + 88 + (replacement_target * 2),
 | |
|                                           vtype.to_bytes(1, "little"))
 | |
|                         self.entity_load[replacement_target] = [vtype, 22]
 | |
|                 else:
 | |
|                     if is_progression:
 | |
|                         # we need to see if 1-ups are in our load list
 | |
|                         if any(x not in self.entity_load for x in [[0, 22], [1, 22]]):
 | |
|                             self.entity_load.append([0, 22])
 | |
|                     else:
 | |
|                         if any(x not in self.entity_load for x in [[2, 22], [3, 22]]):
 | |
|                             # edge case: if (1, 22) is in, we need to load (3, 22) instead
 | |
|                             if [1, 22] in self.entity_load:
 | |
|                                 self.entity_load.append([3, 22])
 | |
|                             else:
 | |
|                                 self.entity_load.append([2, 22])
 | |
|                 if load_len < len(self.entity_load):
 | |
|                     patch.write_token(APTokenTypes.WRITE, self.pointer + 88 + (load_len * 2),
 | |
|                                       bytes(self.entity_load[load_len]))
 | |
|                     patch.write_token(APTokenTypes.WRITE, self.pointer + 104 + (load_len * 2),
 | |
|                                       bytes(struct.pack("H", self.consumable_pointer)))
 | |
|                 if is_progression:
 | |
|                     if [1, 22] in self.entity_load:
 | |
|                         vtype = 1
 | |
|                     else:
 | |
|                         vtype = 0
 | |
|                 else:
 | |
|                     if [3, 22] in self.entity_load:
 | |
|                         vtype = 3
 | |
|                     else:
 | |
|                         vtype = 2
 | |
|                 assert isinstance(consumable["pointer"], int)
 | |
|                 patch.write_token(APTokenTypes.WRITE, self.pointer + consumable["pointer"] + 7,
 | |
|                                   vtype.to_bytes(1, "little"))
 |