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"))
							 |