Пример #1
0
def load_palette_group(object_set: int,
                       palette_group_index: int) -> PaletteGroup:
    """
    Basically does, what the Setup_PalData routine does.

    :param object_set: Level_Tileset in the disassembly.
    :param palette_group_index: Palette_By_Tileset. Defined in the level header.

    :return: A list of 4 groups of 4 colors.
    """
    rom = ROM()

    palette_offset_position = PALETTE_OFFSET_LIST + (object_set *
                                                     PALETTE_OFFSET_SIZE)
    palette_offset = rom.little_endian(palette_offset_position)

    palette_address = PALETTE_BASE_ADDRESS + palette_offset
    palette_address += palette_group_index * PALETTES_PER_PALETTES_GROUP * COLORS_PER_PALETTE

    palettes = []

    for _ in range(PALETTES_PER_PALETTES_GROUP):
        palettes.append(rom.read(palette_address, COLORS_PER_PALETTE))

        palette_address += COLORS_PER_PALETTE

    return palettes
Пример #2
0
class AutoScrollDrawer:
    def __init__(self, auto_scroll_row: int, level: Level):
        self.auto_scroll_row = auto_scroll_row
        self.level = level

        self.current_pos = QPointF()
        self.horizontal_speed = 0
        self.vertical_speed = 0

        self.rom = ROM()

        self.pixel_length = 1

        self.acceleration_pen = Qt.NoPen
        self.acceleration_brush = Qt.NoBrush
        self.scroll_pen = Qt.NoPen
        self.scroll_brush = Qt.NoBrush

        self.screen_polygon = QPolygonF()

    def draw(self, painter: QPainter, block_length: int):
        self.pixel_length = block_length / Block.WIDTH

        self.scroll_brush = QBrush(Qt.blue)
        self.scroll_pen = QPen(self.scroll_brush, 2 * self.pixel_length)

        self.acceleration_brush = QBrush(Qt.red)
        self.acceleration_pen = QPen(self.acceleration_brush,
                                     2 * self.pixel_length)

        painter.setPen(self.scroll_pen)
        painter.setBrush(self.scroll_brush)

        auto_scroll_type_index = self.auto_scroll_row >> 4
        auto_scroll_routine_index = self.auto_scroll_row & 0b0001_1111

        if auto_scroll_type_index in [
                SPIKE_CEILING_SCROLL,
                UP_TIL_DOOR_SCROLL,
                WATER_LEVEL_SCROLL,
                UP_RIGHT_DIAG_SCROLL,
        ]:
            # not visualized
            return
        elif auto_scroll_type_index not in [
                HORIZONTAL_SCROLL_0, HORIZONTAL_SCROLL_1
        ]:
            # illegal value, those appear in the vanilla ROM, though; so error out
            return

        first_movement_command_index = (self.rom.int(
            AScroll_HorizontalInitMove + auto_scroll_routine_index) + 1) % 256
        last_movement_command_index = (self.rom.int(
            AScroll_HorizontalInitMove + auto_scroll_routine_index + 1)) % 256

        self.horizontal_speed = 0
        self.vertical_speed = 0

        self.current_pos = self._determine_auto_scroll_start(block_length)

        for movement_command_index in range(first_movement_command_index,
                                            last_movement_command_index + 1):

            movement_command = self.rom.int(AScroll_Movement +
                                            movement_command_index)
            movement_repeat = self.rom.int(AScroll_MovementRepeat +
                                           movement_command_index)

            self._execute_movement_command(painter, movement_command,
                                           movement_repeat)

        stop_marker = QRectF(QPoint(0, 0), QSizeF(10, 10) * self.pixel_length)
        stop_marker.moveCenter(self.current_pos)

        painter.setPen(Qt.NoPen)
        painter.drawRect(stop_marker)

        painter.setPen(self.scroll_pen)
        painter.setBrush(self.scroll_brush)

        painter.setOpacity(0.2)
        painter.drawPolygon(self.screen_polygon)

    def _execute_movement_command(self, painter: QPainter, command: int,
                                  repeat: int):
        h_updates_per_tick = 4  # got those by reading the auto scroll routine
        v_updates_per_tick = 2

        is_acceleration_command = (command >> 4) == 0

        if is_acceleration_command:
            # set speed
            h_acceleration_index = (command & 0b00001100) >> 2
            v_acceleration_index = command & 0b00000011

            assert h_acceleration_index != 3
            assert v_acceleration_index != 3

            h_acceleration = self.rom.int(AScroll_VelAccel +
                                          h_acceleration_index)
            v_acceleration = self.rom.int(AScroll_VelAccel +
                                          v_acceleration_index)

            if h_acceleration == 0xFF:
                h_acceleration = -0x01

            if v_acceleration == 0xFF:
                v_acceleration = -0x01

            h_acceleration <<= 4
            v_acceleration <<= 4

            movement_ticks = repeat
            repeat = 1
        else:
            auto_scroll_loop_selector = command >> 4

            loop_start_offset = AScroll_MovementLoopStart - 2 + auto_scroll_loop_selector

            if auto_scroll_loop_selector in [0, 1]:
                # normal movement command
                movement_ticks = self.rom.int(loop_start_offset)

                h_acceleration = 0
                v_acceleration = 0
            else:
                # loop command
                movement_loop_start_index = self.rom.int(loop_start_offset)
                movement_loop_end_index = self.rom.int(loop_start_offset + 1)

                number_of_commands = movement_loop_end_index - movement_loop_start_index

                movement_loop_commands = self.rom.read(
                    AScroll_MovementLoop + movement_loop_start_index,
                    number_of_commands)
                movement_loop_repeats = self.rom.read(
                    AScroll_MovementLoopTicks + movement_loop_start_index,
                    number_of_commands)

                for _ in range(repeat):
                    for sub_command, sub_repeat in zip(movement_loop_commands,
                                                       movement_loop_repeats):
                        self._execute_movement_command(painter, sub_command,
                                                       sub_repeat)

                return

        if is_acceleration_command and (h_acceleration or v_acceleration):
            painter.setPen(self.acceleration_pen)
            painter.setBrush(self.acceleration_brush)
        else:
            painter.setPen(self.scroll_pen)
            painter.setBrush(self.scroll_brush)

        # circle at start of new command
        painter.drawEllipse(self.current_pos, 4 * self.pixel_length,
                            4 * self.pixel_length)

        self._add_points_for_position(self.current_pos)

        if is_acceleration_command and (h_acceleration or v_acceleration):
            for _ in range(movement_ticks):
                self.horizontal_speed += h_acceleration
                self.vertical_speed += v_acceleration

                old_pos = self.current_pos

                self.current_pos += (
                    QPointF(h_updates_per_tick * self.horizontal_speed / 256,
                            v_updates_per_tick * self.vertical_speed / 256) *
                    self.pixel_length)

                painter.drawLine(old_pos, self.current_pos)
                self._add_points_for_position(self.current_pos)
        else:
            old_pos = QPointF(self.current_pos)

            h_movement = h_updates_per_tick * self.horizontal_speed / 256 * movement_ticks * repeat
            v_movement = v_updates_per_tick * self.vertical_speed / 256 * movement_ticks * repeat

            self.current_pos += QPointF(h_movement,
                                        v_movement) * self.pixel_length

            painter.drawLine(old_pos, self.current_pos)

            self._add_points_for_line(old_pos, self.current_pos)

    def _add_points_for_position(self, pos: QPointF):
        self.screen_polygon = self.screen_polygon.united(
            QPolygonF.fromList(self._rect_for_point(pos)))

    def _add_points_for_line(self, start: QPointF, stop: QPointF):
        start_points = self._rect_for_point(start)
        stop_points = self._rect_for_point(stop)

        point_list = []

        if start.y() == stop.y():
            point_list.extend([
                start_points[0], stop_points[1], stop_points[2],
                start_points[3]
            ])
        elif start.y() < stop.y():
            point_list.extend(start_points[0:2])
            point_list.extend(stop_points[1:4])
            point_list.append(start_points[3])
        else:
            point_list.append(start_points[0])
            point_list.extend(stop_points[0:3])
            point_list.extend(start_points[2:4])

        self.screen_polygon = self.screen_polygon.united(
            QPolygonF.fromList(point_list))

    def _rect_for_point(self, pos: QPointF):
        top_right = pos + QPointF(SCREEN_WIDTH // 2, -_ASCROLL_SCREEN_HEIGHT //
                                  2) * self.pixel_length * Block.WIDTH
        bottom_right = pos + QPoint(SCREEN_WIDTH // 2, _ASCROLL_SCREEN_HEIGHT
                                    // 2) * self.pixel_length * Block.WIDTH

        top_left = top_right - QPointF(SCREEN_WIDTH,
                                       0) * self.pixel_length * Block.WIDTH
        bottom_left = bottom_right - QPointF(
            SCREEN_WIDTH, 0) * self.pixel_length * Block.WIDTH

        return top_left, top_right, bottom_right, bottom_left

    def _determine_auto_scroll_start(self, block_length: int) -> QPointF:
        # only support horizontal levels for now
        _, mario_y = self.level.header.mario_position()

        scroll_x, scroll_y = SCREEN_WIDTH // 2, min(
            mario_y + 2, GROUND - _ASCROLL_SCREEN_HEIGHT // 2)

        return QPointF(scroll_x, scroll_y) * block_length