Ejemplo n.º 1
0
    def _GetPatchForOverworldCaveData(self) -> Patch:
        patch = Patch()
        for cave_type in Range.VALID_CAVE_TYPES:
            cave_num = int(cave_type) - self.CAVE_TYPE_CAVE_NUM_OFFSET
            if cave_type == CaveType.ARMOS_ITEM_VIRTUAL_CAVE:
                patch.AddData(
                    self.ARMOS_ITEM_ADDRESS,
                    [self.overworld_caves[cave_num].GetItemAtPosition(2)])
                continue
            if cave_type == CaveType.COAST_ITEM_VIRTUAL_CAVE:
                patch.AddData(
                    self.COAST_ITEM_ADDRESS,
                    [self.overworld_caves[cave_num].GetItemAtPosition(2)])
                continue

            # Note that the Cave class is responsible for protecting bits 6 and 7 in its item data
            patch.AddData(
                self.OVERWORLD_DATA_START_ADDRESS +
                self._GetOverworldCaveDataIndex(
                    cave_type, 0, is_second_byte=False),
                self.overworld_caves[cave_num].GetItemData())
            patch.AddData(
                self.OVERWORLD_DATA_START_ADDRESS +
                self._GetOverworldCaveDataIndex(
                    cave_type, 0, is_second_byte=True),
                self.overworld_caves[cave_num].GetPriceData())
        return patch
Ejemplo n.º 2
0
    def _GetPatchForLevelGrid(self, start_address: int,
                              rooms: List[Room]) -> Patch:
        patch = Patch()
        for room_num in Range.VALID_ROOM_NUMBERS:
            room_data = rooms[room_num].GetRomData()
            assert len(room_data) == self.NUM_BYTES_OF_DATA_PER_ROOM

            for table_num in range(0, self.NUM_BYTES_OF_DATA_PER_ROOM):
                patch.AddData(
                    start_address + table_num * self.LEVEL_TABLE_SIZE +
                    room_num, [room_data[table_num]])
        return patch
Ejemplo n.º 3
0
  def _AddExtras(self, patch: Patch) -> None:
    if self.settings.progressive_items:
      patch.AddData(0x6B49, [0x11, 0x12, 0x13])  # Swords
      patch.AddData(0x6B4E, [0x11, 0x12])  # Candles
      patch.AddData(0x6B50, [0x11, 0x12])  # Arrows
      patch.AddData(0x6B5A, [0x11, 0x12])  # Rings

    # Change "no item" code from 0x03 (Mags) to 0x0E (Triforce of Power)
    patch.AddData(0x1785F, [0x0E])

    # Include everything above in the hash code.
    hash_code = patch.GetHashCode()
    patch.AddData(0xAFD0, hash_code)
    patch.AddData(0xA4CD, [0x4C, 0x90, 0xAF])
    patch.AddData(0xAFA0, [
        0xA2, 0x0A, 0xA9, 0xFF, 0x95, 0xAC, 0xCA, 0xD0, 0xFB, 0xA2, 0x04, 0xA0, 0x60, 0xBD, 0xBF,
        0xAF, 0x9D, 0x44, 0x04, 0x98, 0x69, 0x1B, 0xA8, 0x95, 0x70, 0xA9, 0x20, 0x95, 0x84, 0xA9,
        0x00, 0x95, 0xAC, 0xCA, 0xD0, 0xE9, 0x20, 0x9D, 0x97, 0xA9, 0x14, 0x85, 0x14, 0xE6, 0x13,
        0x60, 0xFF, 0xFF, 0x1E, 0x0A, 0x06, 0x01
    ])

    # What does this do?
    patch.AddData(
        0x1A129,
        [0x0C, 0x18, 0x0D, 0x0E, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24])

    if self.settings.select_swap:
      patch.AddData(0x1EC4C, [0x4C, 0xC0, 0xFF])
      patch.AddData(0x1FFD0, [
          0xA9, 0x05, 0x20, 0xAC, 0xFF, 0xAD, 0x56, 0x06, 0xC9, 0x0F, 0xD0, 0x02, 0xA9, 0x07, 0xA8,
          0xA9, 0x01, 0x20, 0xC8, 0xB7, 0x4C, 0x58, 0xEC
      ])

    if self.settings.randomize_level_text or self.settings.speed_up_text:
      random_level_text = random.choice(
          ['palace', 'house-', 'block-', 'random', 'cage_-', 'home_-', 'castle'])
      text_data_table = TextDataTable(
          "very_fast" if self.settings.speed_up_text else "normal",
          random_level_text if self.settings.randomize_level_text else "level-")
      patch += text_data_table.GetPatch()
Ejemplo n.º 4
0
 def _GetPatchForOverworldData(self) -> Patch:
     patch = Patch()
     for screen_num in Range.VALID_ROOM_NUMBERS:
         addr = 0x80 + int(screen_num)
         patch.AddData(0x18410 + addr, [self.overworld_raw_data[addr]])
     return patch
Ejemplo n.º 5
0
 def _GetPatchForLevelMetadata(self) -> Patch:
     patch = Patch()
     patch.AddData(self.LEVEL_METADATA_ADDRESS, self.level_metadata)
     return patch
Ejemplo n.º 6
0
class DataTable():
    NES_FILE_OFFSET = 0x10
    OVERWORLD_DATA_START_ADDRESS = 0x18400 + NES_FILE_OFFSET
    LEVEL_1_TO_6_DATA_START_ADDRESS = 0x18700 + NES_FILE_OFFSET
    LEVEL_7_TO_9_DATA_START_ADDRESS = 0x18A00 + NES_FILE_OFFSET
    LEVEL_TABLE_SIZE = 0x80
    NUM_BYTES_OF_DATA_PER_ROOM = 6
    ARMOS_ITEM_ADDRESS = 0x10CF5 + NES_FILE_OFFSET
    COAST_ITEM_ADDRESS = 0x1788A + NES_FILE_OFFSET
    CAVE_TYPE_CAVE_NUM_OFFSET = 0x10
    BOMB_UPGRADE_PRICE_ADDRESS = 0x4B72 + NES_FILE_OFFSET
    BOMB_UPGRADE_QUANTITY_ADDRESS = 0x4B8A + NES_FILE_OFFSET

    LEVEL_METADATA_ADDRESS = 0x19300 + NES_FILE_OFFSET
    LEVEL_METADATA_OFFSET = 0xFC
    GATEWAY_OFFSET = 0x23
    ENEMY_QUANTITIES_OFFSET = 0x24
    ITEM_POSITIONS_OFFSET = 0x29
    OFFSET_OFFSET = 0x2D
    START_ROOM_OFFSET = 0x2F
    TRIFORCE_LOCATION_OFFSET = 0x30
    STAIRCASE_LIST_OFFSET = 0x34
    ENTRANCE_DIRECTION_OFFSET = 0x3D
    MAP_BYTES_OFFSET = 0x3F
    MAP_THINGIES_OFFSET = 0x4F

    DARK_PALETTE_COLOR_OFFSETS = [
        0x0C, 0x20, 0x7D, 0x85, 0x86, 0x8A, 0x8E, 0x8F, 0x92, 0x93, 0xBD, 0xC5,
        0xC6, 0xCA, 0xCE, 0xCF, 0xD2, 0xD3, 0xD7
    ]
    MEDIUM_PALETTE_COLOR_OFFSETS = [
        0x0D, 0x11, 0x21, 0x7E, 0x82, 0x87, 0x8B, 0xBE, 0xC2, 0xC7, 0xCB
    ]
    LIGHT_PALETTE_COLOR_OFFSETS = [0x0E, 0x12, 0x22, 0x7F, 0x83, 0xBF, 0xC3]
    WATER_PALETTE_COLOR_OFFSETS = [0x10, 0x81, 0x89, 0xC1, 0xC9]

    SPRITE_SET_ADDRESS = 0xC010
    BOSS_SPRITE_SET_ADDRESS = 0xC024

    SPRITE_SET_VALUE_LOOKUP: Dict[SpriteSet, List[int]] = {
        SpriteSet.GORIYA_SPRITE_SET: [0xBB, 0x9D],
        SpriteSet.DARKNUT_SPRITE_SET: [0x7B, 0x98],
        SpriteSet.WIZZROBE_SPRITE_SET: [0x9B, 0x9A],
        SpriteSet.DODONGO_SPRITE_SET: [0xDB, 0x9F],
        SpriteSet.GLEEOK_SPRITE_SET: [0xDB, 0xA3],
        SpriteSet.PATRA_SPRITE_SET: [0xDB, 0xA7]
    }

    def __init__(self) -> None:
        self.overworld_raw_data = list(
            open("data/overworld-data.bin", 'rb').read(0x300))
        self.level_1_to_6_raw_data = list(
            open("data/level-1-6-data.bin", 'rb').read(0x300))
        self.level_7_to_9_raw_data = list(
            open("data/level-7-9-data.bin", 'rb').read(0x300))
        self.level_metadata = list(
            open("data/level-metadata.bin", 'rb').read(0x9D8))
        self.overworld_caves: List[Cave] = []
        self.level_1_to_6_rooms: List[Room] = []
        self.level_7_to_9_rooms: List[Room] = []
        self.sprite_set_patch = Patch()
        self.misc_data_patch = Patch()

    def GetCaveDestination(self, screen_num: int) -> int:
        print("Reading byte %x " % (screen_num))
        print("Which we think is 0x%x " % (0x80 + screen_num + 0x18410))
        foo = self.overworld_raw_data[0x80 + screen_num]
        print("Data is 0x%x" % foo)
        #    foo = foo  4
        #    print ("without bottom 2 bits is %x" % foo )
        bar = foo >> 2
        print("Bar is 0x%x" % bar)
        bar -= 0x10
        print("Cave type 0x%x?" % bar)
        return bar

    def SetCaveDestination(self, screen_num: int,
                           level_num_or_cave_type: int) -> None:
        foo = self.overworld_raw_data[0x80 + screen_num]
        bits_to_keep = foo & 0x03

        foo = level_num_or_cave_type + 0x10

        bits_to_write = foo << 2
        self.overworld_raw_data[0x80 +
                                screen_num] = bits_to_keep + bits_to_write

    def SetLevelGrid(self, grid_id: GridId, level_grid: List[Room]) -> None:
        if grid_id == GridId.GRID_A:
            self.level_1_to_6_rooms = level_grid
        else:
            self.level_7_to_9_rooms = level_grid

    def ResetToVanilla(self) -> None:
        self._ReadOverworldData()
        self.level_1_to_6_rooms = self._ReadDataForLevelGrid(
            self.level_1_to_6_raw_data)
        self.level_7_to_9_rooms = self._ReadDataForLevelGrid(
            self.level_7_to_9_raw_data)
        self.level_metadata = list(
            open("data/level-metadata.bin", 'rb').read(0x9D8))
        self.sprite_set_patch = Patch()

    def _ReadDataForLevelGrid(self, level_data: List[int]) -> List[Room]:
        rooms: List[Room] = []
        for room_num in Range.VALID_ROOM_NUMBERS:
            room_data: List[int] = []
            for byte_num in range(0, self.NUM_BYTES_OF_DATA_PER_ROOM):
                room_data.append(level_data[byte_num * self.LEVEL_TABLE_SIZE +
                                            room_num])
            rooms.append(Room(room_data))
        return rooms

    def GetLevelNumberOrCaveType(self, screen_num: int) -> int:
        return (self.overworld_raw_data[0x80 + screen_num] & 0xFC) >> 2

    def _GetOverworldCaveDataIndex(self, cave_type: CaveType,
                                   position_num: int,
                                   is_second_byte: bool) -> int:
        cave_index = int(cave_type - 0x10)
        assert cave_index in range(0, 0x16)
        second_byte_index = 0x3C if is_second_byte else 0x00
        return 0x200 + second_byte_index + 3 * cave_index + position_num

    def _ReadOverworldData(self) -> None:
        self.overworld_caves = []
        for cave_type in Range.VALID_CAVE_TYPES:
            cave_num = cave_type - 0x10
            if cave_type == CaveType.ARMOS_ITEM_VIRTUAL_CAVE:
                self.overworld_caves.append(
                    Cave([0x3F, Item.POWER_BRACELET, 0x7F, 0x00, 0x00, 0x00]))
            elif cave_type == CaveType.COAST_ITEM_VIRTUAL_CAVE:
                self.overworld_caves.append(
                    Cave([0x3F, Item.HEART_CONTAINER, 0x7F, 0x00, 0x00, 0x00]))
            else:
                assert cave_type in Range.VALID_CAVE_TYPES  # Not needed?
                cave_data: List[int] = []
                for position_num in range(0, 3):
                    cave_data.append(self.overworld_raw_data[
                        self._GetOverworldCaveDataIndex(cave_type,
                                                        position_num,
                                                        is_second_byte=False)])
                for position_num in range(0, 3):
                    cave_data.append(self.overworld_raw_data[
                        self._GetOverworldCaveDataIndex(cave_type,
                                                        position_num,
                                                        is_second_byte=True)])
                self.overworld_caves.append(Cave(cave_data))
        assert len(
            self.overworld_caves
        ) == 22  # 0-19 are actual caves, 20-21 are for the armos/coast

    def GetRoom(self, level_num: LevelNum, room_num: RoomNum) -> Room:
        assert level_num in Range.VALID_LEVEL_NUMBERS
        assert room_num in Range.VALID_ROOM_NUMBERS

        if level_num in [7, 8, 9]:
            return self.level_7_to_9_rooms[room_num]
        return self.level_1_to_6_rooms[room_num]

    def GetRoomItem(self, location: Location) -> Item:
        assert location.IsLevelRoom()
        if location.GetLevelNum() in [7, 8, 9]:
            return self.level_7_to_9_rooms[location.GetRoomNum()].GetItem()
        return self.level_1_to_6_rooms[location.GetRoomNum()].GetItem()

    def SetRoomItem(self, item: Item, location: Location) -> None:
        assert location.IsLevelRoom()
        if location.GetLevelNum() in [7, 8, 9]:
            self.level_7_to_9_rooms[location.GetRoomNum()].SetItem(item)
        else:
            self.level_1_to_6_rooms[location.GetRoomNum()].SetItem(item)

    def GetCaveItem(self, location: Location) -> Item:
        assert location.IsCavePosition()
        return self.overworld_caves[location.GetCaveNum()].GetItemAtPosition(
            location.GetPositionNum())

    def SetCaveItem(self, item: Item, location: Location) -> None:
        assert location.IsCavePosition()
        self.overworld_caves[location.GetCaveNum()].SetItemAtPosition(
            item, location.GetPositionNum())

    def SetCavePrice(self, price: int, location: Location) -> None:
        assert location.IsCavePosition()
        self.overworld_caves[location.GetCaveNum()].SetPriceAtPosition(
            price, location.GetPositionNum())

    def RandomizeBombUpgrades(self) -> None:
        done = False
        while not done:
            price = random.randrange(75, 125)
            quantity = random.randrange(2, 6)
            if price not in range(110, 126) or quantity not in [2, 3]:
                done = True
        self.misc_data_patch.AddData(self.BOMB_UPGRADE_PRICE_ADDRESS, [price])
        self.misc_data_patch.AddData(self.BOMB_UPGRADE_QUANTITY_ADDRESS,
                                     [quantity])

    def ClearAllVisitMarkers(self) -> None:
        logging.debug("Clearing Visit markers")
        for room in self.level_1_to_6_rooms:
            room.ClearVisitMark()
        for room in self.level_7_to_9_rooms:
            room.ClearVisitMark()

    def ClearStaircaseRoomNumbersForLevel(self, level_num: LevelNum) -> None:
        assert level_num in range(1, 10)
        offset = level_num * self.LEVEL_METADATA_OFFSET + self.STAIRCASE_LIST_OFFSET
        for counter in range(0, 9):
            self.level_metadata[offset + counter] = 0xFF

    def AddStaircaseRoomNumberForLevel(self, level_num: LevelNum,
                                       room_num: RoomNum) -> None:
        offset = level_num * self.LEVEL_METADATA_OFFSET + self.STAIRCASE_LIST_OFFSET
        assert room_num in range(0, 0x80)
        for counter in range(0, 9):
            if self.level_metadata[offset + counter] == 0xFF:
                self.level_metadata[offset + counter] = room_num
                return
        print("This should never happen! (AddStaircaseRoomNumberForLevel)")
        sys.exit()

    def UpdateCompassPointer(self, location: Location) -> None:
        assert location.IsLevelRoom()
        (level_num, room_num) = (location.GetLevelNum(), location.GetRoomNum())
        #room = self.GetRoom(location.GetLevelNum(), room_num)
        assert room_num in range(0, 0x100)
        self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET +
                            self.TRIFORCE_LOCATION_OFFSET] = room_num

    def WriteDungeonPalette(self, level_num: LevelNum,
                            palette: Tuple[int, int, int, int]) -> None:
        (dark, medium, light, water) = palette
        assert dark in range(0, 0x100)
        assert medium in range(0, 0x100)
        assert light in range(0, 0x100)
        assert water in range(0, 0x100)

        for offset in self.DARK_PALETTE_COLOR_OFFSETS:
            self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET +
                                offset] = dark
        for offset in self.MEDIUM_PALETTE_COLOR_OFFSETS:
            self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET +
                                offset] = medium
        for offset in self.LIGHT_PALETTE_COLOR_OFFSETS:
            self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET +
                                offset] = light
        for offset in self.WATER_PALETTE_COLOR_OFFSETS:
            self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET +
                                offset] = water

    # Gets a list of staircase rooms for a level.
    #
    # Note that this will include not just passage staircases between two
    # dungeon rooms but also item rooms with only one passage two and
    # from a dungeon room.
    def GetLevelStaircaseRoomNumberList(self,
                                        level_num: LevelNum) -> List[RoomNum]:
        assert level_num in Range.VALID_LEVEL_NUMBERS
        offset = level_num * self.LEVEL_METADATA_OFFSET + self.STAIRCASE_LIST_OFFSET
        tbr: List[RoomNum] = []
        for offset in range(0, 9):
            tbr.append(RoomNum(self.level_metadata[offset]))
        return tbr

    def SetMapData(self, level_num: LevelNum, map_bytes: List[int],
                   thingies: List[int], offset: int) -> None:
        assert level_num in Range.VALID_LEVEL_NUMBERS
        level_offset = level_num * self.LEVEL_METADATA_OFFSET
        for num in range(0, 0x10):
            self.level_metadata[level_offset + self.MAP_BYTES_OFFSET +
                                num] = map_bytes[num]
        for num in range(0, 44):
            self.level_metadata[level_offset + self.MAP_THINGIES_OFFSET +
                                num] = thingies[num]
        self.level_metadata[level_offset +
                            self.OFFSET_OFFSET] = (4 - offset) % 16
        self.level_metadata[level_offset + self.OFFSET_OFFSET +
                            1] = ((32 - offset) % 32) * 8

    def SetStartRoomDataForLevel(self, level_num: LevelNum,
                                 start_room: RoomNum,
                                 entrance_direction: Direction) -> None:
        level_offset = level_num * self.LEVEL_METADATA_OFFSET
        if start_room:
            self.level_metadata[level_offset +
                                self.START_ROOM_OFFSET] = start_room
        if entrance_direction:
            self.level_metadata[
                level_offset + self.
                ENTRANCE_DIRECTION_OFFSET] = entrance_direction.GetRomValue()
        if start_room and entrance_direction:
            formatted_gateway = (start_room + int(entrance_direction) +
                                 0x80) % 0x100
            self.level_metadata[level_offset +
                                self.GATEWAY_OFFSET] = formatted_gateway

    # Gets the Room number of the start screen for a level.
    def GetLevelStartRoomNumber(self, level_num: LevelNum) -> RoomNum:
        assert level_num in Range.VALID_LEVEL_NUMBERS
        return RoomNum(
            self.level_metadata[level_num * self.LEVEL_METADATA_OFFSET +
                                self.START_ROOM_OFFSET])

    def GetLevelEntranceDirection(self, level_num: LevelNum) -> Direction:
        assert level_num in Range.VALID_LEVEL_NUMBERS
        offset = level_num * self.LEVEL_METADATA_OFFSET + self.ENTRANCE_DIRECTION_OFFSET
        return Direction.FromRomValue(self.level_metadata[offset])

    def SetSpriteSetsForLevel(self, level_num: LevelNum, sprite_set: SpriteSet,
                              boss_sprite_set: SpriteSet) -> None:
        self.sprite_set_patch.AddData(self.SPRITE_SET_ADDRESS + 2 * level_num,
                                      self.SPRITE_SET_VALUE_LOOKUP[sprite_set])
        self.sprite_set_patch.AddData(
            self.BOSS_SPRITE_SET_ADDRESS + 2 * level_num,
            self.SPRITE_SET_VALUE_LOOKUP[boss_sprite_set])

    def SetItemPositionsForLevel(self, level_num: LevelNum,
                                 item_positions: List[int]) -> None:
        enemy_quantities = []
        enemy_quantities.append(random.randrange(1, 5))
        enemy_quantities.append(random.randrange(2, 6))
        enemy_quantities.append(random.randrange(3, 7))
        enemy_quantities.append(random.randrange(4, 8))
        enemy_quantities.sort()

        assert item_positions[0] == 0x89

        for counter in range(0, 4):
            offset = level_num * self.LEVEL_METADATA_OFFSET + counter
            assert item_positions[counter] in range(0, 0x100)
            assert enemy_quantities[counter] in range(0, 0x100)
            self.level_metadata[
                offset + self.ITEM_POSITIONS_OFFSET] = item_positions[counter]
            self.level_metadata[
                offset +
                self.ENEMY_QUANTITIES_OFFSET] = enemy_quantities[counter]

    def GetPatch(self) -> Patch:
        patch = Patch()
        patch += self._GetPatchForLevelGrid(
            self.LEVEL_1_TO_6_DATA_START_ADDRESS, self.level_1_to_6_rooms)
        patch += self._GetPatchForLevelGrid(
            self.LEVEL_7_TO_9_DATA_START_ADDRESS, self.level_7_to_9_rooms)
        patch += self._GetPatchForLevelMetadata()
        patch += self._GetPatchForOverworldCaveData()
        patch += self._GetPatchForOverworldData()
        patch += self.sprite_set_patch
        return patch

    def _GetPatchForLevelGrid(self, start_address: int,
                              rooms: List[Room]) -> Patch:
        patch = Patch()
        for room_num in Range.VALID_ROOM_NUMBERS:
            room_data = rooms[room_num].GetRomData()
            assert len(room_data) == self.NUM_BYTES_OF_DATA_PER_ROOM

            for table_num in range(0, self.NUM_BYTES_OF_DATA_PER_ROOM):
                patch.AddData(
                    start_address + table_num * self.LEVEL_TABLE_SIZE +
                    room_num, [room_data[table_num]])
        return patch

    def _GetPatchForLevelMetadata(self) -> Patch:
        patch = Patch()
        patch.AddData(self.LEVEL_METADATA_ADDRESS, self.level_metadata)
        return patch

    def _GetPatchForOverworldData(self) -> Patch:
        patch = Patch()
        for screen_num in Range.VALID_ROOM_NUMBERS:
            addr = 0x80 + int(screen_num)
            patch.AddData(0x18410 + addr, [self.overworld_raw_data[addr]])
        return patch

    def _GetPatchForOverworldCaveData(self) -> Patch:
        patch = Patch()
        for cave_type in Range.VALID_CAVE_TYPES:
            cave_num = int(cave_type) - self.CAVE_TYPE_CAVE_NUM_OFFSET
            if cave_type == CaveType.ARMOS_ITEM_VIRTUAL_CAVE:
                patch.AddData(
                    self.ARMOS_ITEM_ADDRESS,
                    [self.overworld_caves[cave_num].GetItemAtPosition(2)])
                continue
            if cave_type == CaveType.COAST_ITEM_VIRTUAL_CAVE:
                patch.AddData(
                    self.COAST_ITEM_ADDRESS,
                    [self.overworld_caves[cave_num].GetItemAtPosition(2)])
                continue

            # Note that the Cave class is responsible for protecting bits 6 and 7 in its item data
            patch.AddData(
                self.OVERWORLD_DATA_START_ADDRESS +
                self._GetOverworldCaveDataIndex(
                    cave_type, 0, is_second_byte=False),
                self.overworld_caves[cave_num].GetItemData())
            patch.AddData(
                self.OVERWORLD_DATA_START_ADDRESS +
                self._GetOverworldCaveDataIndex(
                    cave_type, 0, is_second_byte=True),
                self.overworld_caves[cave_num].GetPriceData())
        return patch
Ejemplo n.º 7
0
class TextDataTable():
    TEXT_SPEED_ADDRESS = 0x482D
    TEXT_LEVEL_ADDRESS = 0x19D17

    def __init__(self, text_speed: str, phrase: str) -> None:
        self.patch = Patch()
        self.text_speed = text_speed
        self.phrase = phrase

    def GetPatch(self) -> Patch:
        self._AddTextSpeedToPatchIfNeeded()
        self._AddLevelNameToPatchIfNeeded()
        return self.patch

    def _AddTextSpeedToPatchIfNeeded(self) -> None:
        logging.debug("Updating text speed.")

        converted_text_speed = TextSpeed.NORMAL
        if self.text_speed == 'normal':
            return
        if self.text_speed == 'random':
            converted_text_speed = random.choice(list(TextSpeed))
        else:
            converted_text_speed = TextSpeed[self.text_speed.upper()]

        self.patch.AddData(self.TEXT_SPEED_ADDRESS,
                           [int(converted_text_speed)])

    def _AddLevelNameToPatchIfNeeded(self) -> None:
        assert len(
            self.phrase) == 6, "The level prefix must be six characters long."
        if self.phrase.lower() == 'level-':
            return  # No need to replace the existing text with the same text.

        self.patch.AddData(self.TEXT_LEVEL_ADDRESS,
                           self.__ascii_string_to_bytes(self.phrase))

    def __ascii_string_to_bytes(self, phrase: str) -> List[int]:
        """Convert the string to a form the game can understand."""
        return list(map(self._ascii_char_to_bytes, phrase))

    @staticmethod
    def _ascii_char_to_bytes(char: str) -> int:
        if ord(char) >= 48 and ord(char) <= 57:  # 0-9
            return ord(char) - 48
        if ord(char) >= 65 and ord(char) <= 90:  # A-Z
            return ord(char) - 55
        if ord(char) >= 97 and ord(char) <= 122:  # a-z
            return ord(char) - 87

        misc_char_map = {
            '_': 0x24,  # Meant to represent a space.
            '~': 0x25,  # Meant to represent leading space.
            ',': 0x28,
            '!': 0x29,
            "'": 0x2A,
            '&': 0x2B,
            '.': 0x2C,
            '"': 0x2D,
            '?': 0x2E,
            '-': 0x2F
        }

        return misc_char_map[char] or misc_char_map['_']