Пример #1
0
    def __init__(self, graphic_set_number):
        if not self.GRAPHIC_SET_BG_PAGE_1:
            self.GRAPHIC_SET_BG_PAGE_1 = ROM().bulk_read(
                BG_PAGE_COUNT, Level_BG_Pages1)
            self.GRAPHIC_SET_BG_PAGE_2 = ROM().bulk_read(
                BG_PAGE_COUNT, Level_BG_Pages2)

        self.data = bytearray()
        self.number = graphic_set_number

        segments = []

        if graphic_set_number == WORLD_MAP:
            segments = [0x14, 0x16, 0x20, 0x21, 0x22, 0x23]
        if graphic_set_number not in range(BG_PAGE_COUNT):
            self._read_in([graphic_set_number, graphic_set_number + 2])
        else:
            gfx_index = self.GRAPHIC_SET_BG_PAGE_1[graphic_set_number]
            common_index = self.GRAPHIC_SET_BG_PAGE_2[graphic_set_number]

            segments.append(gfx_index)
            segments.append(common_index)

            if graphic_set_number == SPADE_ROULETTE:
                segments.extend([0x20, 0x21, 0x22, 0x23])
            elif graphic_set_number == N_SPADE:
                segments.extend([0x28, 0x29, 0x5A, 0x31])
            elif graphic_set_number == VS_2P:
                segments.extend([0x04, 0x05, 0x06, 0x07])
            else:
                segments.extend([0x00, 0x00, 0x00, 0x00])

        self._read_in(segments)
Пример #2
0
    def on_open_rom(self, path_to_rom="") -> bool:
        if not self.safe_to_change():
            return False

        if not path_to_rom:
            # otherwise ask the user what new file to open
            path_to_rom, _ = QFileDialog.getOpenFileName(
                self, caption="Open ROM", filter=ROM_FILE_FILTER)

            if not path_to_rom:
                self._enable_disable_gui_elements()
                return False

        # Proceed loading the file chosen by the user
        try:
            ROM.load_from_file(path_to_rom)

            return self.open_level_selector(None)

        except IOError as exp:
            QMessageBox.warning(self,
                                type(exp).__name__,
                                f"Cannot open file '{path_to_rom}'.")
            return False
        finally:
            self._enable_disable_gui_elements()
Пример #3
0
    def __init__(self, level_name: str, layout_address: int,
                 enemy_data_offset: int, object_set_number: int):
        super(Level, self).__init__(object_set_number, layout_address)

        self._signal_emitter = LevelSignaller()

        self.attached_to_rom = True

        self.object_set = ObjectSet(object_set_number)

        self.undo_stack = UndoStack()

        self.name = level_name

        self.header_offset = layout_address
        self.enemy_offset = enemy_data_offset

        self.objects: List[LevelObject] = []
        self.jumps: List[Jump] = []
        self.enemies: List[EnemyObject] = []

        rom = ROM()

        self.header_bytes = rom.bulk_read(Level.HEADER_LENGTH,
                                          self.header_offset)
        self._parse_header()

        self.object_offset = self.header_offset + Level.HEADER_LENGTH

        object_data = ROM.rom_data[self.object_offset:]
        enemy_data = ROM.rom_data[self.enemy_offset:]

        self._load_level_data(object_data, enemy_data)

        self.changed = False
Пример #4
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
Пример #5
0
    def _save_current_changes_to_file(self, pathname: str, set_new_path):
        for offset, data in self.level_ref.to_bytes():
            ROM().bulk_write(data, offset)

        try:
            ROM().save_to_file(pathname, set_new_path)
        except IOError as exp:
            QMessageBox.warning(self, f"{type(exp).__name__}", f"Cannot save ROM data to file '{pathname}'.")
Пример #6
0
    def paintEvent(self, event: QPaintEvent):
        painter = QPainter(self)

        bg_color = bg_color_for_object_set(self.object_set, 0)
        painter.setBrush(QBrush(bg_color))

        painter.drawRect(QRect(QPoint(0, 0), self.size()))

        pattern_table = PatternTable(self.object_set)
        palette = load_palette(self.object_set, 0)
        tsa_data = ROM.get_tsa_data(self.object_set)

        horizontal = self.sprites_horiz

        block_length = Block.WIDTH * self.zoom

        for i in range(self.sprites):
            block = Block(i, palette, pattern_table, tsa_data)

            x = (i % horizontal) * block_length
            y = (i // horizontal) * block_length

            block.draw(painter, x, y, block_length)

        return
Пример #7
0
def gen_object_factories():
    ROM(root_dir.joinpath("SMB3.nes"))

    for object_set in range(MAX_OBJECT_SET + 1):
        if object_set in [WORLD_MAP_OBJECT_SET, MUSHROOM_OBJECT_SET, SPADE_BONUS_OBJECT_SET]:
            continue

        yield LevelObjectFactory(object_set, object_set, 0, [], False)
Пример #8
0
def get_block(block_index, palette_group, graphics_set, tsa_data):
    if block_index > 0xFF:
        rom_block_index = ROM().get_byte(block_index)  # block_index is an offset into the graphic memory
        block = Block(rom_block_index, palette_group, graphics_set, tsa_data)
    else:
        block = Block(block_index, palette_group, graphics_set, tsa_data)

    return block
Пример #9
0
    def __init__(self, world: int, level: int, layout_address: int,
                 enemy_data_offset: int, object_set_number: int):
        super(Level, self).__init__(object_set_number, layout_address)

        self._signal_emitter = LevelSignaller()

        self.attached_to_rom = True

        self.object_set = ObjectSet(object_set_number)

        self.undo_stack = UndoStack()

        level_index = Level.world_indexes[world] + level

        level_data: Mario3Level = Level.offsets[level_index]

        if world == 0:
            self.name = level_data.name
        else:
            self.name = f"Level {world}-{level}, '{level_data.name}'"

        # TODO get rid of this; only used for naming and M3L header
        self.world = world
        self.level_number = level

        self.header_offset = layout_address
        self.enemy_offset = enemy_data_offset

        self.objects: List[LevelObject] = []
        self.jumps: List[Jump] = []
        self.enemies: List[EnemyObject] = []

        rom = ROM()

        self.header_bytes = rom.bulk_read(Level.HEADER_LENGTH,
                                          self.header_offset)
        self._parse_header()

        self.object_offset = self.header_offset + Level.HEADER_LENGTH

        object_data = ROM.rom_data[self.object_offset:]
        enemy_data = ROM.rom_data[self.enemy_offset:]

        self._load_level_data(object_data, enemy_data)

        self.changed = False
Пример #10
0
    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()
Пример #11
0
    def __init__(self, world_index):
        self._internal_world_map = _WorldMap.from_world_number(ROM(), world_index)

        super(WorldMap, self).__init__(0, self._internal_world_map.layout_address)

        self.name = f"World {world_index} - Overworld"

        self.pattern_table = PatternTable(OVERWORLD_GRAPHIC_SET)
        self.palette_group = load_palette(WORLD_MAP_OBJECT_SET, 0)

        self.object_set = WORLD_MAP_OBJECT_SET
        self.tsa_data = ROM.get_tsa_data(self.object_set)

        self.world = 0
        self.level_number = 0

        self.objects = []

        self._load_objects()

        self._calc_size()
Пример #12
0
    def _draw_floor(self, painter: QPainter, level: Level):
        floor_level = (GROUND - 1) * self.block_length
        floor_block_index = 86

        palette_group = load_palette(level.object_set_number, level.header.object_palette_index)
        pattern_table = PatternTable(level.header.graphic_set_index)
        tsa_data = ROM().get_tsa_data(level.object_set_number)

        floor_block = Block(floor_block_index, palette_group, pattern_table, tsa_data)

        for x in range(level.width):
            floor_block.draw(painter, x * self.block_length, floor_level, self.block_length)
Пример #13
0
    def _enable_disable_gui_elements(self):
        # actions and widgets, that depend on whether the ROM is loaded
        rom_elements = [
            # entries in file menu
            self.open_m3l_action,
            self.save_rom_action,
            self.save_rom_as_action,
            # entry in level menu
            self.select_level_action,
        ]

        # actions and widgets, that depend on whether a level is loaded or not
        level_elements = [
            # entry in file menu
            self.save_m3l_action,
            # top toolbar
            self.menu_toolbar,
            # other gui elements
            self.level_view,
            self.spinner_panel,
            self.object_toolbar,
            self.level_size_bar,
            self.enemy_size_bar,
            self.object_list,
            self.jump_list,
            self.object_toolbar,
        ]

        level_elements.extend(self.level_menu.actions())
        level_elements.remove(self.select_level_action)

        level_elements.extend(self.object_menu.actions())
        level_elements.extend(self.view_menu.actions())

        for gui_element in rom_elements:
            gui_element.setEnabled(ROM.is_loaded())

        for gui_element in level_elements:
            gui_element.setEnabled(ROM.is_loaded()
                                   and self.level_ref.fully_loaded)
Пример #14
0
def _block_from_index(block_index: int, level: Level) -> Block:
    """
    Returns the block at the given index, from the TSA table for the given level.

    :param block_index:
    :param level:
    :return:
    """

    palette_group = load_palette_group(level.object_set_number, level.header.object_palette_index)
    graphics_set = GraphicsSet(level.header.graphic_set_index)
    tsa_data = ROM().get_tsa_data(level.object_set_number)

    return Block(block_index, palette_group, graphics_set, tsa_data)
Пример #15
0
    def __init__(
        self,
        data: bytearray,
        object_set: int,
        palette_group: PaletteGroup,
        graphics_set: GraphicsSet,
        objects_ref: List["LevelObject"],
        is_vertical: bool,
        index: int,
        size_minimal: bool = False,
    ):
        self.object_set = ObjectSet(object_set)

        self.graphics_set = graphics_set
        self.tsa_data = ROM.get_tsa_data(object_set)

        self.x_position = 0
        self.y_position = 0

        self.rendered_base_x = 0
        self.rendered_base_y = 0

        self.palette_group = palette_group

        self.index_in_level = index
        self.objects_ref = objects_ref
        self.vertical_level = is_vertical

        self.data = data

        self.selected = False

        self.size_minimal = size_minimal

        if self.size_minimal:
            self.ground_level = 0
        else:
            self.ground_level = GROUND

        self._length = 0
        self.secondary_length = 0

        self._setup()
Пример #16
0
    def on_play(self):
        """
        Copies the ROM, including the current level, to a temporary directory, saves the current level as level 1-1 and
        opens the rom in an emulator.
        """
        temp_dir = pathlib.Path(tempfile.gettempdir()) / "smb3foundry"
        temp_dir.mkdir(parents=True, exist_ok=True)

        path_to_temp_rom = temp_dir / "instaplay.rom"

        ROM().save_to(path_to_temp_rom)

        if not self._put_current_level_to_level_1_1(path_to_temp_rom):
            return

        if not self._set_default_powerup(path_to_temp_rom):
            return

        arguments = SETTINGS["instaplay_arguments"].replace(
            "%f", str(path_to_temp_rom))
        arguments = shlex.split(arguments, posix=False)

        emu_path = pathlib.Path(SETTINGS["instaplay_emulator"])

        if emu_path.is_absolute():
            if emu_path.exists():
                emulator = str(emu_path)
            else:
                QMessageBox.critical(
                    self, "Emulator not found",
                    f"Check it under File > Settings.\nFile {emu_path} not found."
                )
                return
        else:
            emulator = SETTINGS["instaplay_emulator"]

        try:
            subprocess.run([emulator, *arguments])
        except Exception as e:
            QMessageBox.critical(self, "Emulator command failed.",
                                 f"Check it under File > Settings.\n{str(e)}")
Пример #17
0
    def _draw_block(self, painter: QPainter, block_index, x, y, block_length,
                    transparent):
        if block_index not in self.block_cache:
            if block_index > 0xFF:
                rom_block_index = ROM().get_byte(
                    block_index
                )  # block_index is an offset into the graphic memory
                block = Block(rom_block_index, self.palette_group,
                              self.pattern_table, self.tsa_data)
            else:
                block = Block(block_index, self.palette_group,
                              self.pattern_table, self.tsa_data)

            self.block_cache[block_index] = block

        self.block_cache[block_index].draw(
            painter,
            x * block_length,
            y * block_length,
            block_length=block_length,
            selected=self.selected,
            transparent=transparent,
        )
Пример #18
0
def rom():
    rom = ROM(test_rom_path)

    yield rom
Пример #19
0
def rom():
    rom_path = root_dir.joinpath("SMB3.nes")

    rom = ROM(rom_path)

    yield rom
Пример #20
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
Пример #21
0
    def save_rom(self, is_save_as):
        safe_to_save, reason, additional_info = self.level_view.level_safe_to_save(
        )

        if not safe_to_save:
            answer = QMessageBox.warning(
                self,
                reason,
                f"{additional_info}\n\nDo you want to proceed?",
                QMessageBox.No | QMessageBox.Yes,
                QMessageBox.No,
            )

            if answer == QMessageBox.No:
                return

        if not self.level_ref.attached_to_rom:
            QMessageBox.information(
                self,
                "Importing M3L into ROM",
                "Please select the positions in the ROM you want the level objects and enemies/items to be stored.",
                QMessageBox.Ok,
            )

            level_selector = LevelSelector(self)

            answer = level_selector.exec_()

            if answer == QMessageBox.Accepted:
                self.level_view.level_ref.attach_to_rom(
                    level_selector.object_data_offset,
                    level_selector.enemy_data_offset)

                if is_save_as:
                    # if we save to another rom, don't consider the level
                    # attached (to the current rom)
                    self.level_view.level_ref.attached_to_rom = False
            else:
                return

        if is_save_as:
            pathname, _ = QFileDialog.getSaveFileName(self,
                                                      caption="Save ROM as",
                                                      filter=ROM_FILE_FILTER)
            if not pathname:
                return  # the user changed their mind
        else:
            pathname = ROM.path

        level = self.level_ref.level

        for offset, data in level.to_bytes():
            ROM().bulk_write(data, offset)

        try:
            ROM().save_to_file(pathname)
        except IOError as exp:
            QMessageBox.warning(self, f"{type(exp).__name__}",
                                f"Cannot save ROM data to file '{pathname}'.")

        self.update_title()

        if not is_save_as:
            level.changed = False
Пример #22
0
    def _render(self):
        self.rendered_base_x = base_x = self.x_position
        self.rendered_base_y = base_y = self.y_position

        self.rendered_width = new_width = self.width
        self.rendered_height = new_height = self.height

        try:
            self.index_in_level = self.objects_ref.index(self)
        except ValueError:
            # the object has not been added yet, so stick with the one given in the constructor
            pass

        blocks_to_draw = []

        if self.orientation == TO_THE_SKY:
            base_x = self.x_position
            base_y = SKY

            for _ in range(self.y_position):
                blocks_to_draw.extend(self.blocks[0:self.width])

            blocks_to_draw.extend(self.blocks[-self.width:])

        elif self.orientation == DESERT_PIPE_BOX:
            # segments are the horizontal sections, which are 8 blocks long
            # two of those are drawn per length bit
            # rows are the 4 block high rows Mario can walk in

            is_pipe_box_type_b = self.obj_index // 0x10 == 4

            rows_per_box = self.height
            lines_per_row = 4

            segment_width = self.width
            segments = (self.length + 1) * 2

            box_height = lines_per_row * rows_per_box

            new_width = segments * segment_width
            new_height = box_height

            for row_number in range(rows_per_box):
                for line in range(lines_per_row):
                    if is_pipe_box_type_b and row_number > 0 and line == 0:
                        # in pipebox type b we do not repeat the horizontal beams
                        line += 1

                    start = line * segment_width
                    stop = start + segment_width

                    for segment_number in range(segments):
                        blocks_to_draw.extend(self.blocks[start:stop])

            if is_pipe_box_type_b:
                # draw another open row
                start = segment_width
            else:
                # draw the first row again to close the box
                start = 0

            stop = start + segment_width

            for segment_number in range(segments):
                blocks_to_draw.extend(self.blocks[start:stop])

        elif self.orientation in [
                DIAG_DOWN_LEFT, DIAG_DOWN_RIGHT, DIAG_UP_RIGHT, DIAG_WEIRD
        ]:
            if self.ending == UNIFORM:
                new_height = (self.length + 1) * self.height
                new_width = (self.length + 1) * self.width

                left = [BLANK]
                right = [BLANK]
                slopes = self.blocks

            elif self.ending == END_ON_TOP_OR_LEFT:
                new_height = (self.length + 1) * self.height
                new_width = (self.length + 1) * (self.width - 1
                                                 )  # without fill block

                if self.orientation in [DIAG_DOWN_RIGHT, DIAG_UP_RIGHT]:
                    fill_block = self.blocks[0:1]
                    slopes = self.blocks[1:]

                    left = fill_block
                    right = [BLANK]
                elif self.orientation == DIAG_DOWN_LEFT:
                    fill_block = self.blocks[-1:]
                    slopes = self.blocks[0:-1]

                    right = fill_block
                    left = [BLANK]

                else:
                    fill_block = self.blocks[0:1]
                    slopes = self.blocks[1:]

                    right = [BLANK]
                    left = fill_block

            elif self.ending == END_ON_BOTTOM_OR_RIGHT:
                new_height = (self.length + 1) * self.height
                new_width = (self.length + 1) * (self.width - 1
                                                 )  # without fill block

                fill_block = self.blocks[-1:]
                slopes = self.blocks[0:-1]

                left = [BLANK]
                right = fill_block
            else:
                # todo other two ends not used with diagonals?
                print(self.description)
                self.rendered_blocks = []
                return

            rows = []

            if self.height > self.width:
                slope_width = self.width
            else:
                slope_width = len(slopes)

            for y in range(new_height):
                amount_right = (y // self.height) * slope_width
                amount_left = new_width - slope_width - amount_right

                offset = y % self.height

                rows.append(amount_left * left +
                            slopes[offset:offset + slope_width] +
                            amount_right * right)

            if self.orientation in [DIAG_UP_RIGHT]:
                for row in rows:
                    row.reverse()

            if self.orientation in [DIAG_DOWN_RIGHT, DIAG_UP_RIGHT]:
                if not self.height > self.width:  # special case for 60 degree platform wire down right
                    rows.reverse()

            if self.orientation in [DIAG_UP_RIGHT]:
                base_y -= new_height - 1

            if self.orientation in [DIAG_DOWN_LEFT]:
                base_x -= new_width - slope_width

            for row in rows:
                blocks_to_draw.extend(row)

        elif self.orientation in [PYRAMID_TO_GROUND, PYRAMID_2]:
            # since pyramids grow horizontally in both directions when extending
            # we need to check for new ground every time it grows

            base_x += 1  # set the new base_x to the tip of the pyramid

            for y in range(base_y, self.ground_level):
                new_height = y - base_y
                new_width = 2 * new_height

                bottom_row = QRect(base_x, y, new_width, 1)

                if any([
                        bottom_row.intersects(obj.get_rect())
                        and y == obj.get_rect().top()
                        for obj in self.objects_ref[0:self.index_in_level]
                ]):
                    break

            base_x = base_x - (new_width // 2)

            blank = self.blocks[0]
            left_slope = self.blocks[1]
            left_fill = self.blocks[2]
            right_fill = self.blocks[3]
            right_slope = self.blocks[4]

            for y in range(new_height):
                blank_blocks = (new_width // 2) - (y + 1)
                middle_blocks = y  # times two

                blocks_to_draw.extend(blank_blocks * [blank])

                blocks_to_draw.append(left_slope)
                blocks_to_draw.extend(middle_blocks * [left_fill] +
                                      middle_blocks * [right_fill])
                blocks_to_draw.append(right_slope)

                blocks_to_draw.extend(blank_blocks * [blank])

        elif self.orientation == ENDING:
            page_width = 16
            page_limit = page_width - self.x_position % page_width

            new_width = page_width + page_limit + 1
            new_height = (GROUND - 1) - SKY

            for y in range(SKY, GROUND - 1):
                blocks_to_draw.append(self.blocks[0])
                blocks_to_draw.extend([self.blocks[1]] * (new_width - 1))

            # todo magic number
            # ending graphics
            rom_offset = ENDING_OBJECT_OFFSET + self.object_set.get_ending_offset(
            ) * 0x60

            rom = ROM()

            ending_graphic_height = 6
            floor_height = 1

            y_offset = GROUND - floor_height - ending_graphic_height

            for y in range(ending_graphic_height):
                for x in range(page_width):
                    block_index = rom.get_byte(rom_offset + y * page_width +
                                               x - 1)

                    block_position = (y_offset +
                                      y) * new_width + x + page_limit + 1
                    blocks_to_draw[block_position] = block_index

            # Mushroom/Fire flower/Star is categorized as an enemy

        elif self.orientation == VERTICAL:
            new_height = self.length + 1
            new_width = self.width

            if self.ending == UNIFORM:
                if self.is_4byte:
                    # there is one VERTICAL 4-byte object: Vertically oriented X-blocks
                    # the width is the primary expansion
                    new_width = (self.obj_index & 0x0F) + 1

                for _ in range(new_height):
                    for x in range(new_width):
                        for y in range(self.height):
                            blocks_to_draw.append(self.blocks[x % self.width])

            elif self.ending == END_ON_TOP_OR_LEFT:
                # in case the drawn object is smaller than its actual size
                for y in range(min(self.height, new_height)):
                    offset = y * self.width
                    blocks_to_draw.extend(self.blocks[offset:offset +
                                                      self.width])

                additional_rows = new_height - self.height

                # assume only the last row needs to repeat
                # todo true for giant blocks?
                if additional_rows > 0:
                    last_row = self.blocks[-self.width:]

                    for _ in range(additional_rows):
                        blocks_to_draw.extend(last_row)

            elif self.ending == END_ON_BOTTOM_OR_RIGHT:
                additional_rows = new_height - self.height

                # assume only the first row needs to repeat
                # todo true for giant blocks?
                if additional_rows > 0:
                    last_row = self.blocks[0:self.width]

                    for _ in range(additional_rows):
                        blocks_to_draw.extend(last_row)

                # in case the drawn object is smaller than its actual size
                for y in range(min(self.height, new_height)):
                    offset = y * self.width
                    blocks_to_draw.extend(self.blocks[offset:offset +
                                                      self.width])

            elif self.ending == TWO_ENDS:
                # object exists on ships
                top_row = self.blocks[0:self.width]
                bottom_row = self.blocks[-self.width:]

                blocks_to_draw.extend(top_row)

                additional_rows = new_height - 2

                # repeat second to last row
                if additional_rows > 0:
                    for _ in range(additional_rows):
                        blocks_to_draw.extend(
                            self.blocks[-2 * self.width:-self.width])

                if new_height > 1:
                    blocks_to_draw.extend(bottom_row)

        elif self.orientation in [HORIZONTAL, HORIZ_TO_GROUND, HORIZONTAL_2]:
            new_width = self.length + 1

            if self.orientation == HORIZ_TO_GROUND:
                # to the ground only, until it hits something
                for y in range(base_y, self.ground_level):
                    bottom_row = QRect(base_x, y, new_width, 1)

                    if any([
                            bottom_row.intersects(obj.get_rect())
                            and y == obj.get_rect().top()
                            for obj in self.objects_ref[0:self.index_in_level]
                    ]):
                        new_height = y - base_y
                        break
                else:
                    # nothing underneath this object, extend to the ground
                    new_height = self.ground_level - base_y

                if self.is_single_block:
                    new_width = self.length

            elif self.orientation == HORIZONTAL_2 and self.ending == TWO_ENDS:
                # floating platforms seem to just be one shorter for some reason
                new_width -= 1
            else:
                new_height = self.height + self.secondary_length

            if self.ending == UNIFORM and not self.is_4byte:
                for y in range(new_height):
                    offset = (y % self.height) * self.width

                    for _ in range(0, new_width):
                        blocks_to_draw.extend(self.blocks[offset:offset +
                                                          self.width])

                # in case of giant blocks
                new_width *= self.width

            elif self.ending == UNIFORM and self.is_4byte:
                # 4 byte objects
                top = self.blocks[0:1]
                bottom = self.blocks[-1:]

                new_height = self.height + self.secondary_length

                # ceilings are one shorter than normal
                if self.height > self.width:
                    new_height -= 1

                blocks_to_draw.extend(new_width * top)

                for _ in range(1, new_height):
                    blocks_to_draw.extend(new_width * bottom)

            elif self.ending == END_ON_TOP_OR_LEFT:
                for y in range(new_height):
                    offset = y * self.width

                    blocks_to_draw.append(self.blocks[offset])

                    for x in range(1, new_width):
                        blocks_to_draw.append(self.blocks[offset + 1])

            elif self.ending == END_ON_BOTTOM_OR_RIGHT:
                for y in range(new_height):
                    offset = y * self.width

                    for x in range(new_width - 1):
                        blocks_to_draw.append(self.blocks[offset])

                    blocks_to_draw.append(self.blocks[offset + self.width - 1])

            elif self.ending == TWO_ENDS:
                top_and_bottom_line = 2

                for y in range(self.height):
                    offset = y * self.width
                    left, *middle, right = self.blocks[offset:offset +
                                                       self.width]

                    blocks_to_draw.append(left)
                    blocks_to_draw.extend(middle *
                                          (new_width - top_and_bottom_line))
                    blocks_to_draw.append(right)

                if not len(blocks_to_draw) % self.height == 0:
                    print(
                        f"Blocks to draw are not divisible by height. {self}")

                new_width = int(len(blocks_to_draw) / self.height)

                top_row = blocks_to_draw[0:new_width]
                bottom_row = blocks_to_draw[-new_width:]

                middle_blocks = blocks_to_draw[new_width:-new_width]

                new_rows = new_height - top_and_bottom_line

                if new_rows >= 0:
                    blocks_to_draw = top_row + middle_blocks * new_rows + bottom_row
        else:
            if not self.orientation == SINGLE_BLOCK_OBJECT:
                print(f"Didn't render {self.description}")
                # breakpoint()

        # for not yet implemented objects and single block objects
        if blocks_to_draw:
            self.rendered_blocks = blocks_to_draw
        else:
            self.rendered_blocks = self.blocks

        self.rendered_width = new_width
        self.rendered_height = new_height
        self.rendered_base_x = base_x
        self.rendered_base_y = base_y

        if new_width and not self.rendered_height == len(
                self.rendered_blocks) / new_width:
            print(
                f"Not enough Blocks for calculated height: {self.description}. "
                f"Blocks for height: {len(self.rendered_blocks) / new_width}. Rendered height: {self.rendered_height}"
            )

            self.rendered_height = len(self.rendered_blocks) / new_width
        elif new_width == 0:
            print(
                f"Calculated Width is 0, setting to 1: {self.description}. "
                f"Blocks to draw: {len(self.rendered_blocks)}. Rendered height: {self.rendered_height}"
            )

            self.rendered_width = 1

        self.rect = QRect(self.rendered_base_x, self.rendered_base_y,
                          self.rendered_width, self.rendered_height)
Пример #23
0
    def _read_in_chr_rom_segment(self, index):
        offset = CHR_ROM_OFFSET + index * CHR_ROM_SEGMENT_SIZE
        chr_rom_data = ROM().bulk_read(2 * CHR_ROM_SEGMENT_SIZE, offset)

        self.data.extend(chr_rom_data)
Пример #24
0
 def _save_auto_rom():
     ROM().save_to_file(auto_save_rom_path, set_new_path=False)