Ejemplo n.º 1
0
def tweakMap(rom):
    # 5 holes at the castle, reduces to 3
    re = RoomEditor(rom, 0x078)
    re.objects[-1].count = 3
    re.overlay[7 + 6 * 10] = re.overlay[9 + 6 * 10]
    re.overlay[8 + 6 * 10] = re.overlay[9 + 6 * 10]
    re.store(rom)
Ejemplo n.º 2
0
def readEntrances(rom):
    result = []
    entrance_rooms = [
        0x0D3, 0x024, 0x0B5, (0x09, 0x109A), 0x0D9, (0x1A, 0x034E),
        (0x09, 0x07EC), 0x010, 0x077
    ]

    for idx, room in enumerate(entrance_rooms):
        if isinstance(room, tuple):
            re = RoomEditor(rom, bank_nr=room[0], address=room[1])
        else:
            re = RoomEditor(rom, room)
        warp = None
        for obj in re.objects:
            if isinstance(obj, ObjectWarp) and (obj.map_nr < 9
                                                or obj.map_nr == 0xff):
                warp = obj
        if warp.map_nr == 0xFF:
            result.append(8)
        else:
            result.append(warp.map_nr)
    return result
Ejemplo n.º 3
0
def readBossMapping(rom):
    mapping = []
    for dungeon_nr in range(9):
        r = RoomEditor(rom, BOSS_ROOMS[dungeon_nr][0])
        if r.entities:
            mapping.append(BOSS_ENTITIES.index(r.entities[0]))
        elif isinstance(r.objects[-1],
                        ObjectWarp) and r.objects[-1].room == 0x1ef:
            mapping.append(3)
        elif isinstance(r.objects[-1],
                        ObjectWarp) and r.objects[-1].room == 0x2f8:
            mapping.append(6)
        else:
            mapping.append(dungeon_nr)
    return mapping
Ejemplo n.º 4
0
def readEntrances(rom):
    result = []
    entrance_rooms = [0x0D3, 0x024, 0x0B5, "Alt2B", 0x0D9, "Alt8C", "Alt0E", 0x010, 0x077]

    for idx, room in enumerate(entrance_rooms):
        re = RoomEditor(rom, room)
        warp = None
        for obj in re.objects:
            if isinstance(obj, ObjectWarp) and (obj.map_nr < 9 or obj.map_nr == 0xff):
                warp = obj
        if not warp:
            # TODO: This indicates a different map setup...
            result.append(idx)
        elif warp.map_nr == 0xFF:
            result.append(8)
        else:
            result.append(warp.map_nr)
    return result
Ejemplo n.º 5
0
def removeOwlEvents(rom):
    # Remove all the owl events from the entity tables.
    for room in range(0x100):
        re = RoomEditor(rom, room)
        if re.hasEntity(0x41):
            re.removeEntities(0x41)
            re.store(rom)
    # Clear texts used by the owl. Potentially reused somewhere else.
    rom.texts[0x0D9] = b'\xff'  # used by boomerang
    # 1 Used by empty chest (master stalfos message)
    # 9 used by keysanity items
    # 1 used by bowwow in chest
    # 1 used by item for other player message
    # 2 used by arrow chest messages
    # 2 used by tunics
    for idx in range(0x0BE, 0x0CE):
        rom.texts[idx] = b'\xff'
Ejemplo n.º 6
0
    def patch(self, rom, option, *, multiworld=None):
        if option != SWORD or multiworld is not None:
            # Set the heart piece data
            super().patch(rom, option, multiworld=multiworld)

            # Patch the room to contain a heart piece instead of the sword on the beach
            re = RoomEditor(rom, 0x0F2)
            re.removeEntities(0x31)  # remove sword
            re.addEntity(5, 5, 0x35)  # add heart piece
            re.store(rom)

            # Prevent shield drops from the like-like from turning into swords.
            rom.patch(0x03,
                      0x1B9C,
                      ASM("ld a, [$DB4E]"),
                      ASM("ld a, $01"),
                      fill_nop=True)
            rom.patch(0x03,
                      0x244D,
                      ASM("ld a, [$DB4E]"),
                      ASM("ld a, $01"),
                      fill_nop=True)
Ejemplo n.º 7
0
def removeBirdKeyHoleDrop(rom):
    # Prevent the cave with the bird key from dropping you in the water
    # (if you do not have flippers this would softlock you)
    rom.patch(
        0x02, 0x1176,
        ASM("""
        ldh a, [$F7]
        cp $0A
        jr nz, $30
    """),
        ASM("""
        nop
        nop
        nop
        nop
        jr $30
    """))
    # Remove the hole that drops you all the way from dungeon7 entrance to the water in the cave
    re = RoomEditor(rom, 0x01E)
    re.removeObject(5, 4)
    re.store(rom)
Ejemplo n.º 8
0
def doubleTrouble(rom):
    for n in range(0x316):
        if n == 0x2FF:
            continue
        re = RoomEditor(rom, n)
        # Bosses
        if re.hasEntity(0x59):  # Moldorm (TODO; double heart container drop)
            re.removeEntities(0x59)
            re.entities += [(3, 2, 0x59), (4, 2, 0x59)]
            re.store(rom)
        if re.hasEntity(0x5C):  # Ghini
            re.removeEntities(0x5C)
            re.entities += [(3, 2, 0x5C), (4, 2, 0x5C)]
            re.store(rom)
        if re.hasEntity(0x5B):  # slime eye
            re.removeEntities(0x5B)
            re.entities += [(3, 2, 0x5B), (6, 2, 0x5B)]
            re.store(rom)
        if re.hasEntity(0x65):  # angler fish
            re.removeEntities(0x65)
            re.entities += [(6, 2, 0x65), (6, 5, 0x65)]
            re.store(rom)
        # Slime eel bugs out on death if duplicated.
        # if re.hasEntity(0x5D):  # slime eel
        #     re.removeEntities(0x5D)
        #     re.entities += [(6, 2, 0x5D), (6, 5, 0x5D)]
        #     re.store(rom)
        if re.hasEntity(
                0x5A):  # facade (TODO: Drops two hearts, shared health?)
            re.removeEntities(0x5A)
            re.entities += [(2, 3, 0x5A), (6, 3, 0x5A)]
            re.store(rom)
        # Evil eagle causes a crash, and messes up the intro sequence and generally is just a mess if I spawn multiple
        # if re.hasEntity(0x63):  # evil eagle
        #     re.removeEntities(0x63)
        #     re.entities += [(3, 4, 0x63), (2, 4, 0x63)]
        #     re.store(rom)
        #     # Remove that links movement is blocked
        #     rom.patch(0x05, 0x2258, ASM("ldh [$A1], a"), "0000")
        #     rom.patch(0x05, 0x1AE3, ASM("ldh [$A1], a"), "0000")
        #     rom.patch(0x05, 0x1C5D, ASM("ldh [$A1], a"), "0000")
        #     rom.patch(0x05, 0x1C8D, ASM("ldh [$A1], a"), "0000")
        #     rom.patch(0x05, 0x1CAF, ASM("ldh [$A1], a"), "0000")
        if re.hasEntity(0x62):  # hot head (TODO: Drops thwo hearts)
            re.removeEntities(0x62)
            re.entities += [(2, 2, 0x62), (4, 4, 0x62)]
            re.store(rom)
        if re.hasEntity(0xF9):  # hardhit beetle
            re.removeEntities(0xF9)
            re.entities += [(2, 2, 0xF9), (5, 4, 0xF9)]
            re.store(rom)
        # Minibosses
        if re.hasEntity(0x89):
            re.removeEntities(0x89)
            re.entities += [(2, 3, 0x89), (6, 3, 0x89)]
            re.store(rom)
        if re.hasEntity(0x81):
            re.removeEntities(0x81)
            re.entities += [(2, 3, 0x81), (6, 3, 0x81)]
            re.store(rom)
        if re.hasEntity(0x60):
            dodongo = [e for e in re.entities if e[2] == 0x60]
            x = (dodongo[0][0] + dodongo[1][0]) // 2
            y = (dodongo[0][1] + dodongo[1][1]) // 2
            re.entities += [(x, y, 0x60)]
            re.store(rom)
        if re.hasEntity(0x8e):
            re.removeEntities(0x8e)
            re.entities += [(1, 1, 0x8e), (7, 1, 0x8e)]
            re.store(rom)
        if re.hasEntity(0x92):
            re.removeEntities(0x92)
            re.entities += [(2, 3, 0x92), (4, 3, 0x92)]
            re.store(rom)
        if re.hasEntity(0xf4):
            re.removeEntities(0xf4)
            re.entities += [(2, 1, 0xf4), (6, 1, 0xf4)]
            re.store(rom)
        if re.hasEntity(0xf8):
            re.removeEntities(0xf8)
            re.entities += [(2, 2, 0xf8), (6, 2, 0xf8)]
            re.store(rom)
        if re.hasEntity(0xe4):
            re.removeEntities(0xe4)
            re.entities += [(5, 2, 0xe4), (5, 5, 0xe4)]
            re.store(rom)

        if re.hasEntity(0x88):  # Armos knight (TODO: double item drop)
            re.removeEntities(0x88)
            re.entities += [(3, 3, 0x88), (6, 3, 0x88)]
            re.store(rom)
        if re.hasEntity(
                0x87
        ):  # Lanmola (TODO: killing one drops the item, and marks as done)
            re.removeEntities(0x87)
            re.entities += [(2, 2, 0x87), (1, 1, 0x87)]
            re.store(rom)
Ejemplo n.º 9
0
def changeMiniBosses(rom, mapping):
    # Fix avalaunch not working when entering a room from the left or right
    rom.patch(0x03,
              0x0BE0,
              ASM("""
        ld  [hl], $50
        ld  hl, $C2D0
        add hl, bc
        ld  [hl], $00
        jp  $4B56
    """),
              ASM("""
        ld  a, [hl]
        sub $08
        ld  [hl], a    
        ld  hl, $C2D0
        add hl, bc
        ld  [hl], b ; b is always zero here
        ret
    """),
              fill_nop=True)
    # Remove the powder fairy from giant buzz blob
    rom.patch(0x36, 0x14F7, ASM("jr nz, $05"), ASM("jr $05"))

    for target, name in mapping.items():
        re = RoomEditor(rom, MINIBOSS_ROOMS[target])
        re.entities = [e for e in re.entities
                       if e[2] == 0x61]  # Only keep warp, if available
        re.entities += MINIBOSS_ENTITIES[name]

        if re.room == 0x228 and name != "GRIM_CREEPER":
            for x in range(3, 7):
                for y in range(0, 3):
                    re.removeObject(x, y)

        if name == "CUE_BALL":
            re.objects += [
                Object(3, 3, 0x2c),
                ObjectHorizontal(4, 3, 0x22, 2),
                Object(6, 3, 0x2b),
                Object(3, 4, 0x2a),
                ObjectHorizontal(4, 4, 0x21, 2),
                Object(6, 4, 0x29),
            ]
        if name == "BLAINO":
            # BLAINO needs a warp object to hit you to the entrance of the dungeon.
            if len(re.getWarps()) < 1:
                # Default to start house.
                target = (0x10, 0x2A3, 0x50, 0x7c, 0x2A3)
                if 0x100 <= re.room < 0x11D:  #D1
                    target = (0, 0x117, 80, 80)
                elif 0x11D <= re.room < 0x140:  #D2
                    target = (1, 0x136, 80, 80)
                elif 0x140 <= re.room < 0x15D:  #D3
                    target = (2, 0x152, 80, 80)
                elif 0x15D <= re.room < 0x180:  #D4
                    target = (2, 0x174, 80, 80)
                elif 0x180 <= re.room < 0x1AC:  #D5
                    target = (2, 0x1A1, 80, 80)
                elif 0x1B0 <= re.room < 0x1DE:  #D6
                    target = (2, 0x1D4, 80, 80)
                elif 0x200 <= re.room < 0x22D:  #D7
                    target = (6, 0x20E, 80, 80)
                elif 0x22D <= re.room < 0x26C:  #D8
                    target = (7, 0x25D, 80, 80)
                elif re.room >= 0x300:  #D0
                    target = (0xFF, 0x312, 80, 80)
                elif re.room == 0x2E1:  #Moblin cave
                    target = (0x15, 0x2F0, 0x50, 0x7C)
                re.objects.append(ObjectWarp(1, *target))
        if name == "DODONGO":
            # Remove breaking floor tiles from the room.
            re.objects = [obj for obj in re.objects if obj.type_id != 0xDF]
        if name == "ROLLING_BONES" and target == 2:
            # Make rolling bones pass trough walls so it does not get stuck here.
            rom.patch(0x03, 0x02F1 + 0x81, "84", "95")
        re.store(rom)
Ejemplo n.º 10
0
def changeBosses(rom, mapping):
    for dungeon_nr in range(9):
        target = mapping[dungeon_nr]
        if target == dungeon_nr:
            continue

        if target == 3:  # D4 fish boss
            # If dungeon_nr == 6: use normal eagle door towards fish.
            if dungeon_nr == 6:
                # Add the staircase to the boss, and fix the warp back.
                re = RoomEditor(rom, 0x22E)
                for obj in re.objects:
                    if isinstance(obj, ObjectWarp):
                        obj.type_id = 2
                        obj.map_nr = 3
                        obj.room = 0x1EF
                        obj.target_x = 24
                        obj.target_y = 16
                re.store(rom)
                re = RoomEditor(rom, 0x1EF)
                re.objects[-1] = ObjectWarp(
                    1, dungeon_nr if dungeon_nr < 8 else 0xff, 0x22E, 72, 80)
                re.store(rom)
            else:
                # Set the proper room event flags
                rom.banks[0x14][BOSS_ROOMS[dungeon_nr][0] - 0x100] = 0x2A

                # Patch the fish heart container to open up the right room.
                rom.patch(
                    0x03, 0x1A0F, ASM("ld hl, $D966"),
                    ASM("ld hl, $%04x" % (0xD800 + BOSS_ROOMS[dungeon_nr][0])))

                # Add the staircase to the boss, and fix the warp back.
                re = getCleanBossRoom(rom, dungeon_nr)
                re.objects += [
                    Object(4, 4, 0xBE),
                    ObjectWarp(2, 3, 0x1EF, 24, 16)
                ]
                re.store(rom)
                re = RoomEditor(rom, 0x1EF)
                re.objects[-1] = ObjectWarp(
                    1, dungeon_nr if dungeon_nr < 8 else 0xff,
                    BOSS_ROOMS[dungeon_nr][0], 72, 80)
                re.store(rom)
            # Patch the proper item towards the D4 boss
            rom.banks[0x3E][0x3800 + 0x01ff] = rom.banks[0x3E][
                0x3800 + BOSS_ROOMS[dungeon_nr][0]]
            rom.banks[0x3E][0x3300 + 0x01ff] = rom.banks[0x3E][
                0x3300 + BOSS_ROOMS[dungeon_nr][0]]
        elif target == 6:  # Evil eagle
            rom.banks[0x14][BOSS_ROOMS[dungeon_nr][0] - 0x100] = 0x2A

            # Patch the eagle heart container to open up the right room.
            rom.patch(
                0x03, 0x1A04, ASM("ld hl, $DA2E"),
                ASM("ld hl, $%04x" % (0xD800 + BOSS_ROOMS[dungeon_nr][0])))
            rom.patch(
                0x02, 0x1FC8, ASM("cp $06"),
                ASM("cp $%02x" % (dungeon_nr if dungeon_nr < 8 else 0xff)))

            # Add the staircase to the boss, and fix the warp back.
            re = getCleanBossRoom(rom, dungeon_nr)
            re.objects += [Object(4, 4, 0xBE), ObjectWarp(2, 6, 0x2F8, 72, 80)]
            re.store(rom)
            re = RoomEditor(rom, 0x2F8)
            re.objects[-1] = ObjectWarp(1,
                                        dungeon_nr if dungeon_nr < 8 else 0xff,
                                        BOSS_ROOMS[dungeon_nr][0], 72, 80)
            re.store(rom)

            # Patch the proper item towards the D7 boss
            rom.banks[0x3E][0x3800 + 0x0223] = rom.banks[0x3E][
                0x3800 + BOSS_ROOMS[dungeon_nr][0]]
            rom.banks[0x3E][0x3300 + 0x0223] = rom.banks[0x3E][
                0x3300 + BOSS_ROOMS[dungeon_nr][0]]
        else:
            rom.banks[0x14][BOSS_ROOMS[dungeon_nr][0] - 0x100] = 0x21
            rom.room_sprite_data_indoor[BOSS_ROOMS[dungeon_nr][0] -
                                        0x100] = SPRITE_DATA[target]
            for room in BOSS_ROOMS[dungeon_nr][1:]:
                rom.room_sprite_data_indoor[room - 0x100] = b'\xff\xff\xff\xff'
            re = getCleanBossRoom(rom, dungeon_nr)
            re.entities = [BOSS_ENTITIES[target]]

            if target == 4:
                # For slime eel, we need to setup the right wall tiles.
                rom.banks[0x20][0x2EB3 + BOSS_ROOMS[dungeon_nr][0] -
                                0x100] = 0x06
            if target == 5:
                # Patch facade so he doesn't use the spinning tiles, which is a problem for the sprites.
                rom.patch(0x04, 0x121D, ASM("cp $14"), ASM("cp $00"))
                rom.patch(0x04, 0x1226, ASM("cp $04"), ASM("cp $00"))
                rom.patch(0x04, 0x127F, ASM("cp $14"), ASM("cp $00"))
            if target == 7:
                pass
                # For hot head, add some lava (causes graphical glitches)
                # re.animation_id = 0x06
                # re.objects += [
                #     ObjectHorizontal(3, 2, 0x06, 4),
                #     ObjectHorizontal(2, 3, 0x06, 6),
                #     ObjectHorizontal(2, 4, 0x06, 6),
                #     ObjectHorizontal(3, 5, 0x06, 4),
                # ]

            re.store(rom)
Ejemplo n.º 11
0
def changeBosses(rom, mapping):
    # Fix the color dungeon not properly warping to room 0 with the boss.
    rom.patch(
        0x14, 0x04E0, "0000000000000000" + "0000000000000000" +
        "0000000000000000" + "0000010000020300" + "0004050607080900" +
        "00000A0B0C0D0000" + "00000E0F10110000" + "0000121314150000",
        "FFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFF" +
        "FF0001FFFF0203FF" + "FF040506070809FF" + "FFFF0A0B0C0DFFFF" +
        "FFFF0E0F1011FFFF" + "FFFF12131415FFFF")
    # Fix the genie death not really liking pits/water.
    rom.patch(0x04, 0x0521, ASM("ld [hl], $81"), ASM("ld [hl], $91"))

    for dungeon_nr in range(9):
        target = mapping[dungeon_nr]
        if target == dungeon_nr:
            continue

        if target == 3:  # D4 fish boss
            # If dungeon_nr == 6: use normal eagle door towards fish.
            if dungeon_nr == 6:
                # Add the staircase to the boss, and fix the warp back.
                re = RoomEditor(rom, 0x22E)
                for obj in re.objects:
                    if isinstance(obj, ObjectWarp):
                        obj.type_id = 2
                        obj.map_nr = 3
                        obj.room = 0x1EF
                        obj.target_x = 24
                        obj.target_y = 16
                re.store(rom)
                re = RoomEditor(rom, 0x1EF)
                re.objects[-1] = ObjectWarp(
                    1, dungeon_nr if dungeon_nr < 8 else 0xff, 0x22E, 24, 16)
                re.store(rom)
            else:
                # Set the proper room event flags
                rom.banks[0x14][BOSS_ROOMS[dungeon_nr][0] - 0x100] = 0x2A

                # Add the staircase to the boss, and fix the warp back.
                re = getCleanBossRoom(rom, dungeon_nr)
                re.objects += [
                    Object(4, 4, 0xBE),
                    ObjectWarp(2, 3, 0x1EF, 24, 16)
                ]
                re.store(rom)
                re = RoomEditor(rom, 0x1EF)
                re.objects[-1] = ObjectWarp(
                    1, dungeon_nr if dungeon_nr < 8 else 0xff,
                    BOSS_ROOMS[dungeon_nr][0], 72, 80)
                re.store(rom)

            # Patch the fish heart container to open up the right room.
            rom.patch(
                0x03, 0x1A0F, ASM("ld hl, $D966"),
                ASM("ld hl, $%04x" %
                    (getBossRoomStatusFlagLocation(dungeon_nr))))

            # Patch the proper item towards the D4 boss
            rom.banks[0x3E][0x3800 + 0x01ff] = rom.banks[0x3E][
                0x3800 + BOSS_ROOMS[dungeon_nr][0]]
            rom.banks[0x3E][0x3300 + 0x01ff] = rom.banks[0x3E][
                0x3300 + BOSS_ROOMS[dungeon_nr][0]]
        elif target == 6:  # Evil eagle
            rom.banks[0x14][BOSS_ROOMS[dungeon_nr][0] - 0x100] = 0x2A

            # Patch the eagle heart container to open up the right room.
            rom.patch(
                0x03, 0x1A04, ASM("ld hl, $DA2E"),
                ASM("ld hl, $%04x" %
                    (getBossRoomStatusFlagLocation(dungeon_nr))))
            rom.patch(
                0x02, 0x1FC8, ASM("cp $06"),
                ASM("cp $%02x" % (dungeon_nr if dungeon_nr < 8 else 0xff)))

            # Add the staircase to the boss, and fix the warp back.
            re = getCleanBossRoom(rom, dungeon_nr)
            re.objects += [Object(4, 4, 0xBE), ObjectWarp(2, 6, 0x2F8, 72, 80)]
            re.store(rom)
            re = RoomEditor(rom, 0x2F8)
            re.objects[-1] = ObjectWarp(1,
                                        dungeon_nr if dungeon_nr < 8 else 0xff,
                                        BOSS_ROOMS[dungeon_nr][0], 72, 80)
            re.store(rom)

            # Patch the proper item towards the D7 boss
            rom.banks[0x3E][0x3800 + 0x02E8] = rom.banks[0x3E][
                0x3800 + BOSS_ROOMS[dungeon_nr][0]]
            rom.banks[0x3E][0x3300 + 0x02E8] = rom.banks[0x3E][
                0x3300 + BOSS_ROOMS[dungeon_nr][0]]
        else:
            rom.banks[0x14][BOSS_ROOMS[dungeon_nr][0] - 0x100] = 0x21
            re = getCleanBossRoom(rom, dungeon_nr)
            re.entities = [BOSS_ENTITIES[target]]

            if target == 4:
                # For slime eel, we need to setup the right wall tiles.
                rom.banks[0x20][0x2EB3 + BOSS_ROOMS[dungeon_nr][0] -
                                0x100] = 0x06
            if target == 5:
                # Patch facade so he doesn't use the spinning tiles, which is a problem for the sprites.
                rom.patch(0x04, 0x121D, ASM("cp $14"), ASM("cp $00"))
                rom.patch(0x04, 0x1226, ASM("cp $04"), ASM("cp $00"))
                rom.patch(0x04, 0x127F, ASM("cp $14"), ASM("cp $00"))
            if target == 7:
                pass
                # For hot head, add some lava (causes graphical glitches)
                # re.animation_id = 0x06
                # re.objects += [
                #     ObjectHorizontal(3, 2, 0x06, 4),
                #     ObjectHorizontal(2, 3, 0x06, 6),
                #     ObjectHorizontal(2, 4, 0x06, 6),
                #     ObjectHorizontal(3, 5, 0x06, 4),
                # ]

            re.store(rom)
Ejemplo n.º 12
0
def changeEntrances(rom, mapping):
    warp_to_indoor = {}
    warp_to_outdoor = {}
    for key in mapping.keys():
        info = ENTRANCE_INFO[key]
        re = RoomEditor(
            rom, info.alt_room if info.alt_room is not None else info.room)
        warp = re.getWarps()[info.index if info.index not in (None,
                                                              "all") else 0]
        warp_to_indoor[key] = warp
        assert info.target == warp.room, "%s != %03x" % (key, warp.room)

        re = RoomEditor(rom, warp.room)
        for warp in re.getWarps():
            if warp.room == info.room:
                warp_to_outdoor[key] = warp
        assert key in warp_to_outdoor, "Missing warp to outdoor on %s" % (key)

    for key, target in mapping.items():
        if key == target:
            continue
        info = ENTRANCE_INFO[key]
        # Change the entrance to point to the new indoor room
        re = RoomEditor(rom, info.room)
        re.changeWarp(warp_to_indoor[key].room, warp_to_indoor[target])
        re.store(rom)
        if info.alt_room:
            re = RoomEditor(rom, info.alt_room)
            re.changeWarp(warp_to_indoor[key].room, warp_to_indoor[target])
            re.store(rom)

        # Change the exit to point to the right outside
        re = RoomEditor(rom, warp_to_indoor[target].room)
        re.changeWarp(ENTRANCE_INFO[target].room, warp_to_outdoor[key])
        re.store(rom)
        if ENTRANCE_INFO[target].instrument_room is not None:
            re = RoomEditor(rom, ENTRANCE_INFO[target].instrument_room)
            re.changeWarp(ENTRANCE_INFO[target].room, warp_to_outdoor[key])
            re.store(rom)
Ejemplo n.º 13
0
def addBetaRoom(rom):
    re = RoomEditor(rom, 0x1FC)
    re.objects[-1].target_y -= 0x10
    re.store(rom)
    re = RoomEditor(rom, 0x038)
    re.changeObject(5, 1, 0xE1)
    re.removeObject(0, 0)
    re.removeObject(0, 1)
    re.removeObject(0, 2)
    re.removeObject(6, 1)
    re.objects.append(ObjectVertical(0, 0, 0x38, 3))
    re.objects.append(ObjectWarp(1, 0x1F, 0x1FC, 0x50, 0x7C))
    re.store(rom)

    rom.room_sprite_data_indoor[0x0FC] = rom.room_sprite_data_indoor[0x1A1]
Ejemplo n.º 14
0
    def patch(self, rom, option, *, multiworld=None):
        super().patch(rom, option, multiworld=multiworld)

        re = RoomEditor(rom, self.room)

        # Make the bird key accessible without the rooster
        re.removeObject(1, 6)
        re.removeObject(2, 6)
        re.removeObject(3, 5)
        re.removeObject(3, 6)
        re.moveObject(1, 5, 2, 6)
        re.moveObject(2, 5, 3, 6)
        re.addEntity(3, 5, 0x9D)
        re.store(rom)

        # Do not give the rooster
        rom.patch(0x19, 0x0E9D, ASM("ld [$DB7B], a"), "", fill_nop=True)
Ejemplo n.º 15
0
def exportOverworld(rom):
    import PIL.Image

    path = os.path.dirname(__file__)
    for room_index in list(range(0x100)) + [
            "Alt06", "Alt0E", "Alt1B", "Alt2B", "Alt79", "Alt8C"
    ]:
        room = RoomEditor(rom, room_index)
        if isinstance(room_index, int):
            room_nr = room_index
        else:
            room_nr = int(room_index[3:], 16)
        tileset_index = rom.banks[0x3F][0x2f00 + room_nr]
        attributedata_bank = rom.banks[0x1A][0x2476 + room_nr]
        attributedata_addr = rom.banks[0x1A][0x1E76 + room_nr * 2]
        attributedata_addr |= rom.banks[0x1A][0x1E76 + room_nr * 2 + 1] << 8
        attributedata_addr -= 0x4000

        metatile_info = rom.banks[0x1A][0x2B1D:0x2B1D + 0x400]
        attrtile_info = rom.banks[attributedata_bank][
            attributedata_addr:attributedata_addr + 0x400]

        palette_index = rom.banks[0x21][0x02EF + room_nr]
        palette_addr = rom.banks[0x21][0x02B1 + palette_index * 2]
        palette_addr |= rom.banks[0x21][0x02B1 + palette_index * 2 + 1] << 8
        palette_addr -= 0x4000

        hidden_warp_tiles = []
        for obj in room.objects:
            if obj.type_id in WARP_TYPE_IDS and room.overlay[
                    obj.x + obj.y * 10] != obj.type_id:
                if obj.type_id != 0xE1 or room.overlay[
                        obj.x +
                        obj.y * 10] != 0x53:  # Ignore the waterfall 'caves'
                    hidden_warp_tiles.append(obj)
            if obj.type_id == 0xC5 and room_nr < 0x100 and room.overlay[
                    obj.x + obj.y * 10] == 0xC4:
                # Pushable gravestones have the wrong overlay by default
                room.overlay[obj.x + obj.y * 10] = 0xC5
            if obj.type_id == 0xDC and room_nr < 0x100:
                # Flowers above the rooster windmill need a different tile
                hidden_warp_tiles.append(obj)

        image_filename = "tiles_%02x_%02x_%02x_%02x_%04x.png" % (
            tileset_index, room.animation_id, palette_index,
            attributedata_bank, attributedata_addr)
        data = {
            "width":
            10,
            "height":
            8,
            "type":
            "map",
            "renderorder":
            "right-down",
            "tiledversion":
            "1.4.3",
            "version":
            1.4,
            "tilewidth":
            16,
            "tileheight":
            16,
            "orientation":
            "orthogonal",
            "tilesets": [{
                "columns": 16,
                "firstgid": 1,
                "image": image_filename,
                "imageheight": 256,
                "imagewidth": 256,
                "margin": 0,
                "name": "main",
                "spacing": 0,
                "tilecount": 256,
                "tileheight": 16,
                "tilewidth": 16
            }],
            "layers": [{
                "data": [n + 1 for n in room.overlay],
                "width": 10,
                "height": 8,
                "id": 1,
                "name": "Tiles",
                "type": "tilelayer",
                "visible": True,
                "opacity": 1,
                "x": 0,
                "y": 0,
            }, {
                "id":
                2,
                "name":
                "EntityLayer",
                "type":
                "objectgroup",
                "visible":
                True,
                "opacity":
                1,
                "x":
                0,
                "y":
                0,
                "objects": [{
                    "width": 16,
                    "height": 16,
                    "x": entity[0] * 16,
                    "y": entity[1] * 16,
                    "name": entityData.NAME[entity[2]],
                    "type": "entity"
                } for entity in room.entities] + [{
                    "width":
                    8,
                    "height":
                    8,
                    "x":
                    0,
                    "y":
                    idx * 8,
                    "name":
                    "%x:%02x:%03x:%02x:%02x" %
                    (obj.warp_type, obj.map_nr, obj.room, obj.target_x,
                     obj.target_y),
                    "type":
                    "warp"
                } for idx, obj in enumerate(room.getWarps()) if isinstance(
                    obj, ObjectWarp)] + [{
                        "width": 16,
                        "height": 16,
                        "x": obj.x * 16,
                        "y": obj.y * 16,
                        "name": "%02X" % (obj.type_id),
                        "type": "hidden_tile"
                    } for obj in hidden_warp_tiles],
            }],
            "properties": [
                {
                    "name": "tileset",
                    "type": "string",
                    "value": "%02X" % (tileset_index)
                },
                {
                    "name": "animationset",
                    "type": "string",
                    "value": "%02X" % (room.animation_id)
                },
                {
                    "name": "attribset",
                    "type": "string",
                    "value":
                    "%02X:%04X" % (attributedata_bank, attributedata_addr)
                },
                {
                    "name": "palette",
                    "type": "string",
                    "value": "%02X" % (palette_index)
                },
            ]
        }
        if isinstance(room_index, str):
            json.dump(
                data,
                open("%s/overworld/export/%s.json" % (path, room_index), "wt"))
        else:
            json.dump(
                data,
                open("%s/overworld/export/%02X.json" % (path, room_index),
                     "wt"))

        if not os.path.exists("%s/overworld/export/%s" %
                              (path, image_filename)):
            tilemap = rom.banks[0x2F][tileset_index *
                                      0x100:tileset_index * 0x100 + 0x200]
            tilemap += rom.banks[0x2C][0x1200:0x1800]
            tilemap += rom.banks[0x2C][0x0800:0x1000]
            anim_addr = {
                2: 0x2B00,
                3: 0x2C00,
                4: 0x2D00,
                5: 0x2E00,
                6: 0x2F00,
                7: 0x2D00,
                8: 0x3000,
                9: 0x3100,
                10: 0x3200,
                11: 0x2A00,
                12: 0x3300,
                13: 0x3500,
                14: 0x3600,
                15: 0x3400,
                16: 0x3700
            }.get(room.animation_id, 0x0000)
            tilemap[0x6C0:0x700] = rom.banks[0x2C][anim_addr:anim_addr + 0x40]

            palette = []
            for n in range(8 * 4):
                p0 = rom.banks[0x21][palette_addr]
                p1 = rom.banks[0x21][palette_addr + 1]
                pal = p0 | p1 << 8
                palette_addr += 2
                r = (pal & 0x1F) << 3
                g = ((pal >> 5) & 0x1F) << 3
                b = ((pal >> 10) & 0x1F) << 3
                palette += [r, g, b]

            img = PIL.Image.new("P", (16 * 16, 16 * 16))
            img.putpalette(palette)

            def drawTile(x, y, index, attr):
                for py in range(8):
                    a = tilemap[index * 16 + py * 2]
                    b = tilemap[index * 16 + py * 2 + 1]
                    if attr & 0x40:
                        a = tilemap[index * 16 + 14 - py * 2]
                        b = tilemap[index * 16 + 15 - py * 2]
                    for px in range(8):
                        bit = 0x80 >> px
                        if attr & 0x20:
                            bit = 0x01 << px
                        c = (attr & 7) << 2
                        if a & bit:
                            c |= 1
                        if b & bit:
                            c |= 2
                        img.putpixel((x + px, y + py), c)

            for x in range(16):
                for y in range(16):
                    idx = x + y * 16
                    metatiles = metatile_info[idx * 4:idx * 4 + 4]
                    attrtiles = attrtile_info[idx * 4:idx * 4 + 4]
                    drawTile(x * 16 + 0, y * 16 + 0, metatiles[0],
                             attrtiles[0])
                    drawTile(x * 16 + 8, y * 16 + 0, metatiles[1],
                             attrtiles[1])
                    drawTile(x * 16 + 0, y * 16 + 8, metatiles[2],
                             attrtiles[2])
                    drawTile(x * 16 + 8, y * 16 + 8, metatiles[3],
                             attrtiles[3])
            img.save("%s/overworld/export/%s" % (path, image_filename))

    world = {
        "maps": [{
            "fileName": "%02X.json" % (n),
            "height": 128,
            "width": 160,
            "x": (n & 0x0F) * 160,
            "y": (n >> 4) * 128
        } for n in range(0x100)],
        "onlyShowAdjacentMaps":
        False,
        "type":
        "world"
    }
    json.dump(world, open("%s/overworld/export/world.world" % (path), "wt"))
Ejemplo n.º 16
0
def createDungeonOnlyOverworld(rom):
    # Skip the whole egg maze.
    rom.patch(0x14, 0x0453, "75", "73")

    instrument_rooms = [
        0x102, 0x12A, 0x159, 0x162, 0x182, 0x1B5, 0x22C, 0x230, 0x301
    ]
    path = os.path.dirname(__file__)

    # Start with clearing all the maps, because this just generates a bunch of room in the rom.
    for n in range(0x100):
        re = RoomEditor(rom, n)
        re.entities = []
        re.objects = []
        if os.path.exists("%s/overworld/dive/%02X.json" % (path, n)):
            re.loadFromJson("%s/overworld/dive/%02X.json" % (path, n))
        entrances = list(
            filter(lambda obj: obj.type_id in WARP_TYPE_IDS, re.objects))
        for obj in re.objects:
            if isinstance(obj, ObjectWarp) and entrances:
                e = entrances.pop(0)

                other = RoomEditor(rom, obj.room)
                for o in other.objects:
                    if isinstance(o, ObjectWarp) and o.warp_type == 0:
                        o.room = n
                        o.target_x = e.x * 16 + 8
                        o.target_y = e.y * 16 + 16
                other.store(rom)

                if obj.room == 0x1F5:
                    # Patch the boomang guy exit
                    other = RoomEditor(rom, "Alt1F5")
                    other.getWarps()[0].room = n
                    other.getWarps()[0].target_x = e.x * 16 + 8
                    other.getWarps()[0].target_y = e.y * 16 + 16
                    other.store(rom)

                if obj.warp_type == 1 and (obj.map_nr < 8 or obj.map_nr == 0xFF
                                           ) and obj.room not in (0x1B0, 0x23A,
                                                                  0x23D):
                    other = RoomEditor(rom,
                                       instrument_rooms[min(8, obj.map_nr)])
                    for o in other.objects:
                        if isinstance(o, ObjectWarp) and o.warp_type == 0:
                            o.room = n
                            o.target_x = e.x * 16 + 8
                            o.target_y = e.y * 16 + 16
                    other.store(rom)
        re.store(rom)
Ejemplo n.º 17
0
def isNormalOverworld(rom):
    return len(RoomEditor(rom, 0x010).getWarps()) > 0
Ejemplo n.º 18
0
def changeMiniBosses(rom, mapping):
    # Fix avalaunch not working when entering a room from the left or right
    rom.patch(0x03,
              0x0BE0,
              ASM("""
        ld  [hl], $50
        ld  hl, $C2D0
        add hl, bc
        ld  [hl], $00
        jp  $4B56
    """),
              ASM("""
        ld  a, [hl]
        sub $08
        ld  [hl], a    
        ld  hl, $C2D0
        add hl, bc
        ld  [hl], b ; b is always zero here
        ret
    """),
              fill_nop=True)

    for target, name in mapping.items():
        re = RoomEditor(rom, MINIBOSS_ROOMS[target])
        re.entities = [e for e in re.entities
                       if e[2] == 0x61]  # Only keep warp, if available
        re.entities += MINIBOSS_ENTITIES[name]

        if re.room == 0x228 and name != "GRIM_CREEPER":
            for x in range(3, 7):
                for y in range(0, 3):
                    re.removeObject(x, y)

        if name == "CUE_BALL":
            re.objects += [
                Object(3, 3, 0x2c),
                ObjectHorizontal(4, 3, 0x22, 2),
                Object(6, 3, 0x2b),
                Object(3, 4, 0x2a),
                ObjectHorizontal(4, 4, 0x21, 2),
                Object(6, 4, 0x29),
            ]
        if name == "BLAINO":
            # BLAINO needs a warp object to hit you to the entrance of the dungeon.
            if len(re.getWarps()) < 1:
                # Default to start house.
                target = (0x10, 0x2A3, 0x50, 0x7c, 0x2A3)
                if 0x100 <= re.room < 0x11D:  #D1
                    target = (0, 0x117, 80, 80)
                elif 0x11D <= re.room < 0x140:  #D2
                    target = (1, 0x136, 80, 80)
                elif 0x140 <= re.room < 0x15D:  #D3
                    target = (2, 0x152, 80, 80)
                elif 0x15D <= re.room < 0x180:  #D4
                    target = (2, 0x174, 80, 80)
                elif 0x180 <= re.room < 0x1AC:  #D5
                    target = (2, 0x1A1, 80, 80)
                elif 0x1B0 <= re.room < 0x1DE:  #D6
                    target = (2, 0x1D4, 80, 80)
                elif 0x200 <= re.room < 0x22D:  #D7
                    target = (6, 0x20E, 80, 80)
                elif 0x22D <= re.room < 0x26C:  #D8
                    target = (7, 0x25D, 80, 80)
                elif re.room >= 0x300:  #D0
                    target = (0xFF, 0x312, 80, 80)
                elif re.room == 0x2E1:  #Moblin cave
                    target = (0x15, 0x2F0, 0x50, 0x7C)
                re.objects.append(ObjectWarp(1, *target))
        re.store(rom)

        sprite_data = entityData.SPRITE_DATA[MINIBOSS_ENTITIES[name][0][2]]
        for n in range(0, len(sprite_data), 2):
            rom.room_sprite_data_indoor[re.room -
                                        0x100][sprite_data[n]] = sprite_data[n
                                                                             +
                                                                             1]
Ejemplo n.º 19
0
def changeEntrances(rom, mapping):
    dungeon_entrance_rooms = [0x117, 0x136, 0x152, 0x17a, 0x1a1, 0x1d4, 0x20e, 0x25d, 0x312]
    instrument_rooms = [0x102, 0x12A, 0x159, 0x162, 0x182, 0x1B5, 0x22C, 0x230, 0x301]

    alt_rooms = {0x02B: "Alt2B", 0x08C: "Alt8C", 0x00E: "Alt0E"}

    world_entrance_rooms = []
    enter_warps = []
    exit_warps = []

    for room in dungeon_entrance_rooms:
        world_entrance_rooms.append(RoomEditor(rom, room).getWarps()[0].room)

    for idx, room in enumerate(world_entrance_rooms):
        if alt_rooms.get(room, None) is not None:
            re = RoomEditor(rom, alt_rooms[room])
        else:
            re = RoomEditor(rom, room)
        warp = None
        for obj in re.objects:
            if isinstance(obj, ObjectWarp) and (obj.map_nr < 9 or obj.map_nr == 0xff):
                warp = obj
        enter_warps.append(warp)
        if warp:
            re = RoomEditor(rom, warp.room)
            warp = None
            for obj in re.objects:
                if isinstance(obj, ObjectWarp) and obj.room == room:
                    warp = obj
        exit_warps.append(warp)

        re = RoomEditor(rom, instrument_rooms[idx])
        for obj in re.objects:
            if isinstance(obj, ObjectWarp):
                assert obj.room == world_entrance_rooms[idx], "D%d instrument room not warping to entrance %03x != %03x" % (idx + 1, obj.room, world_entrance_rooms[idx])

    for a, b in enumerate(mapping):
        re = RoomEditor(rom, world_entrance_rooms[a])
        re.changeWarpTarget(enter_warps[a].room, enter_warps[b].room, enter_warps[b].map_nr, enter_warps[b].target_x, enter_warps[b].target_y)
        re.store(rom)
        if alt_rooms.get(world_entrance_rooms[a], None) is not None:
            re = RoomEditor(rom, alt_rooms[world_entrance_rooms[a]])
            re.changeWarpTarget(enter_warps[a].room, enter_warps[b].room, enter_warps[b].map_nr, enter_warps[b].target_x, enter_warps[b].target_y)
            re.store(rom)
        re = RoomEditor(rom, enter_warps[b].room)
        re.changeWarpTarget(world_entrance_rooms[b], exit_warps[a].room, exit_warps[a].map_nr, exit_warps[a].target_x, exit_warps[a].target_y)
        re.store(rom)
        re = RoomEditor(rom, instrument_rooms[b])
        re.changeWarpTarget(world_entrance_rooms[b], exit_warps[a].room, exit_warps[a].map_nr, exit_warps[a].target_x, exit_warps[a].target_y)
        re.store(rom)
Ejemplo n.º 20
0
def getCleanBossRoom(rom, dungeon_nr):
    re = RoomEditor(rom, BOSS_ROOMS[dungeon_nr][0])
    new_objects = []
    for obj in re.objects:
        if isinstance(obj, ObjectWarp):
            continue
        if obj.type_id == 0xBE:  # Remove staircases
            continue
        if obj.type_id == 0x06:  # Remove lava
            continue
        if obj.type_id == 0x1c:  # Change D1 pits into normal pits
            obj.type_id = 0x01
        if obj.type_id == 0x1e:  # Change D1 pits into normal pits
            obj.type_id = 0xaf
        if obj.type_id == 0x1f:  # Change D1 pits into normal pits
            obj.type_id = 0xb0
        if obj.type_id == 0xF5:  # Change open doors into closing doors.
            obj.type_id = 0xF1
        new_objects.append(obj)

    # Make D4 room a valid fighting room by removing most content.
    if dungeon_nr == 3:
        new_objects = new_objects[:2] + [
            Object(1, 1, 0xAC),
            Object(8, 1, 0xAC),
            Object(1, 6, 0xAC),
            Object(8, 6, 0xAC)
        ]

    # D7 has an empty room we use for most bosses, but it needs some adjustments.
    if dungeon_nr == 6:
        # Move around the unused and instrument room.
        rom.banks[0x14][0x03a0 + 6 + 1 * 8] = 0x00
        rom.banks[0x14][0x03a0 + 7 + 2 * 8] = 0x2C
        rom.banks[0x14][0x03a0 + 7 + 3 * 8] = 0x23
        rom.banks[0x14][0x03a0 + 6 + 5 * 8] = 0x00

        rom.banks[0x14][0x0520 + 7 + 2 * 8] = 0x2C
        rom.banks[0x14][0x0520 + 7 + 3 * 8] = 0x23
        rom.banks[0x14][0x0520 + 6 + 5 * 8] = 0x00

        re.floor_object &= 0x0F
        new_objects += [
            Object(4, 0, 0xF0),
            Object(1, 6, 0xBE),
            ObjectWarp(1, dungeon_nr, 0x22E, 24, 16)
        ]

        # Set the stairs towards the eagle tower top to our new room.
        r = RoomEditor(rom, 0x22E)
        r.objects[-1] = ObjectWarp(1, dungeon_nr, re.room, 24, 112)
        r.store(rom)

        # Remove the normal door to the instrument room
        r = RoomEditor(rom, 0x22e)
        r.removeObject(4, 0)
        r.store(rom)
        rom.banks[0x14][0x22e - 0x100] = 0x00

        r = RoomEditor(rom, 0x22c)
        r.changeObject(0, 7, 0x03)
        r.changeObject(2, 7, 0x03)
        r.store(rom)

    re.objects = new_objects
    re.entities = []
    return re
Ejemplo n.º 21
0
def changeEntrances(rom, mapping):
    entrance_rooms = [
        0x0D3, 0x024, 0x0B5, 0x02B, 0x0D9, 0x08C, 0x00E, 0x010, 0x077
    ]
    instrument_rooms = [
        0x102, 0x12A, 0x159, 0x162, 0x182, 0x1B5, 0x22C, 0x230, 0x301
    ]
    alt_rooms = [
        None, None, None, (0x09, 0x109A), None, (0x1A, 0x034E), (0x09, 0x07EC),
        None, None
    ]
    enter_warps = []
    exit_warps = []

    for idx, room in enumerate(entrance_rooms):
        if alt_rooms[idx] is not None:
            re = RoomEditor(rom,
                            bank_nr=alt_rooms[idx][0],
                            address=alt_rooms[idx][1])
        else:
            re = RoomEditor(rom, room)
        warp = None
        for obj in re.objects:
            if isinstance(obj, ObjectWarp) and (obj.map_nr < 9
                                                or obj.map_nr == 0xff):
                warp = obj
        enter_warps.append(warp)
        if warp:
            re = RoomEditor(rom, warp.room)
            warp = None
            for obj in re.objects:
                if isinstance(obj, ObjectWarp) and obj.room == room:
                    warp = obj
        exit_warps.append(warp)

        re = RoomEditor(rom, instrument_rooms[idx])
        for obj in re.objects:
            if isinstance(obj, ObjectWarp):
                assert obj.room == entrance_rooms[idx]

    for a, b in enumerate(mapping):
        re = RoomEditor(rom, entrance_rooms[a])
        re.changeWarpTarget(enter_warps[a].room, enter_warps[b].room,
                            enter_warps[b].map_nr, enter_warps[b].target_x,
                            enter_warps[b].target_y)
        re.store(rom)
        if alt_rooms[a] is not None:
            re = RoomEditor(rom,
                            bank_nr=alt_rooms[a][0],
                            address=alt_rooms[a][1])
            re.changeWarpTarget(enter_warps[a].room, enter_warps[b].room,
                                enter_warps[b].map_nr, enter_warps[b].target_x,
                                enter_warps[b].target_y)
            re.store(rom)
        re = RoomEditor(rom, enter_warps[b].room)
        re.changeWarpTarget(entrance_rooms[b], exit_warps[a].room,
                            exit_warps[a].map_nr, exit_warps[a].target_x,
                            exit_warps[a].target_y)
        re.store(rom)
        re = RoomEditor(rom, instrument_rooms[b])
        re.changeWarpTarget(entrance_rooms[b], exit_warps[a].room,
                            exit_warps[a].map_nr, exit_warps[a].target_x,
                            exit_warps[a].target_y)
        re.store(rom)
Ejemplo n.º 22
0
def setRaftGoal(rom):
    rom.texts[0x1A3] = formatText(b"Just sail away.")

    # Remove the egg and egg event handler.
    re = RoomEditor(rom, 0x006)
    for x in range(4, 7):
        for y in range(0, 4):
            re.removeObject(x, y)
    re.objects.append(ObjectHorizontal(4, 1, 0x4d, 3))
    re.objects.append(ObjectHorizontal(4, 2, 0x03, 3))
    re.objects.append(ObjectHorizontal(4, 3, 0x03, 3))
    re.entities = []
    re.updateOverlay()
    re.store(rom)

    re = RoomEditor(rom, 0x08D)
    re.objects[6].count = 4
    re.objects[7].x += 2
    re.objects[7].type_id = 0x2B
    re.objects[8].x += 2
    re.objects[8].count = 2
    re.objects[9].x += 1
    re.objects[11] = ObjectVertical(7, 5, 0x37, 2)
    re.objects[12].x -= 1
    re.objects[13].x -= 1
    re.objects[14].x -= 1
    re.objects[14].type_id = 0x34
    re.objects[17].x += 3
    re.objects[17].count -= 3
    re.updateOverlay()
    re.overlay[7 + 60] = 0x33
    re.store(rom)

    re = RoomEditor(rom, 0x0E9)
    re.objects[30].count = 1
    re.objects[30].x += 2
    re.overlay[7 + 70] = 0x0E
    re.overlay[8 + 70] = 0x0E
    re.store(rom)
    re = RoomEditor(rom, 0x0F9)
    re.objects = [
        ObjectHorizontal(4, 0, 0x0E, 6),
        ObjectVertical(9, 0, 0xCA, 8),
        ObjectVertical(8, 0, 0x0E, 8),
        Object(3, 0, 0x38),
        Object(3, 1, 0x32),
        ObjectHorizontal(4, 1, 0x2C, 3),
        Object(7, 1, 0x2D),
        ObjectVertical(7, 2, 0x38, 5),
        Object(7, 7, 0x34),
        ObjectHorizontal(0, 7, 0x2F, 7),
        ObjectVertical(2, 3, 0xE8, 4),
        ObjectVertical(3, 2, 0xE8, 5),
        ObjectVertical(4, 2, 0xE8, 2),
        ObjectVertical(4, 4, 0x5C, 3),
        ObjectVertical(5, 2, 0x5C, 5),
        ObjectVertical(6, 2, 0x5C, 5),
        Object(6, 4, 0xC6),
        ObjectWarp(1, 0x1F, 0xF6, 136, 112)
    ]
    re.updateOverlay(True)
    re.entities.append((0, 0, 0x41))
    re.store(rom)
    re = RoomEditor(rom, 0x1F6)
    re.objects[-1].target_x -= 16
    re.store(rom)

    # Fix the raft graphics (this overrides some unused graphic tiles)
    rom.banks[0x31][0x21C0:0x2200] = rom.banks[0x2E][0x07C0:0x0800]

    # Patch the owl entity to run our custom end handling.
    rom.patch(0x06,
              0x27F5,
              0x2A77,
              ASM("""
        ld  a, [$DB95]
        cp  $0B
        ret nz
        ; If map is not fully loaded, return
        ld  a, [$C124]
        and a
        ret nz
        ; Check if we are moving off the bottom of the map
        ldh a, [$99]
        cp  $7D
        ret c
        ; Move link back so it does not move off the map
        ld  a, $7D
        ldh [$99], a
        
        xor a
        ld  e, a
        ld  d, a

raftSearchLoop:
        ld  hl, $C280
        add hl, de
        ld  a, [hl]
        and a
        jr  z, .skipEntity
        
        ld  hl, $C3A0
        add hl, de
        ld  a, [hl]
        cp  $6A
        jr  nz, .skipEntity

        ; Raft found, check if near the bottom of the screen.
        ld  hl, $C210
        add hl, de
        ld  a, [hl]
        cp  $70
        jr  nc, raftOffWorld

.skipEntity:
        inc e
        ld  a, e
        cp  $10
        jr  nz, raftSearchLoop
        ret

raftOffWorld:
        ; Switch to the end credits
        ld  a, $01
        ld  [$DB95], a
        ld  a, $00
        ld  [$DB96], a
        ret
    """),
              fill_nop=True)

    # We need to run quickly trough part of the credits, or else it bugs out
    # Skip the whole windfish part.
    rom.patch(0x17, 0x0D39, None, ASM("ld a, $18\nld [$D00E], a\nret"))
    # And skip the zoomed out laying on the log
    rom.patch(0x17, 0x20ED, None, ASM("ld a, $00"))
    # Finally skip some waking up on the log.
    rom.patch(0x17, 0x23BC, None, ASM("jp $4CD9"))
    rom.patch(0x17, 0x2476, None, ASM("jp $4CD9"))
Ejemplo n.º 23
0
def updateWitch(rom):
    # Add a heartpiece at the toadstool, the item patches turn this into a 1 time toadstool item
    # Or depending on flags, in something else.
    re = RoomEditor(rom, 0x050)
    re.addEntity(2, 3, 0x35)
    re.store(rom)

    # Change what happens when you trade the toadstool with the witch
    #  Note that the 2nd byte of this code gets patched with the item to give from the witch.
    rom.patch(0x05,
              0x08D4,
              0x08F0,
              ASM("""
        ld  e, $09 ; load the item to give the first time

        ; Get the room flags and mark the witch as done.
        ld  hl, $DAA2
        ld  a, [hl]
        and $30
        set 4, [hl]
        set 5, [hl]
        jr  z, skip
        ld  e, $09 ; give powder every time after the first time.
skip:
        ld  a, e
        ldh [$F1], a
        ld  a, $02
        rst 8
        ld  a, $03
        rst 8
    """),
              fill_nop=True)

    # Patch the toadstool to unload when you haven't delivered something to the witch yet.
    rom.patch(0x03,
              0x1D4B,
              ASM("""
        ld   hl, $DB4B
        ld   a, [$DB4C]
        or   [hl]
        jp   nz, $3F8D
    """),
              ASM("""
        ld   a, [$DAA2]
        and  $20
        jp   z, $3F8D
    """),
              fill_nop=True)

    # Patch what happens when we pickup the toadstool, call our chest code to give a toadstool.
    rom.patch(0x03,
              0x1D6F,
              0x1D7D,
              ASM("""
        ld   a, $50
        ldh  [$F1], a
        ld  a, $02 ; give item
        rst 8

        ld   hl, $DAA2
        res  5, [hl]
    """),
              fill_nop=True)
Ejemplo n.º 24
0
def createDungeonOnlyOverworld(rom):
    # Skip the whole egg maze.
    rom.patch(0x14, 0x0453, "75", "73")

    # Some sprite patches (should generalize this)
    rom.room_sprite_data_overworld[0x72] = b'\xff\xff\xff\xff'
    rom.room_sprite_data_overworld[0x73] = rom.room_sprite_data_overworld[0x8C]
    rom.room_sprite_data_overworld[0xB1] = rom.room_sprite_data_overworld[0x92]

    instrument_rooms = [
        0x102, 0x12A, 0x159, 0x162, 0x182, 0x1B5, 0x22C, 0x230, 0x301
    ]

    # Start with clearing all the maps, because this just generates a bunch of room in the rom.
    for n in range(0x100):
        re = RoomEditor(rom, n)
        re.entities = []
        re.objects = []
        if os.path.exists("patches/overworld/%02X.json" % (n)):
            re.loadFromJson("patches/overworld/%02X.json" % (n))
        re.updateOverlay()
        entrances = list(
            filter(
                lambda obj: obj.type_id in
                (0xE1, 0xE2, 0xE3, 0xBA, 0xA8, 0xBE, 0xCB), re.objects))
        for obj in re.objects:
            if isinstance(obj, ObjectWarp) and entrances:
                e = entrances.pop(0)

                other = RoomEditor(rom, obj.room)
                for o in other.objects:
                    if isinstance(o, ObjectWarp) and o.warp_type == 0:
                        o.room = n
                        o.target_x = e.x * 16 + 8
                        o.target_y = e.y * 16 + 16
                other.store(rom)

                if obj.room == 0x1F5:
                    # Patch the boomang guy exit
                    rom.patch(
                        0x0a, 0x3891, "E000F41820",
                        "E000%02x%02x%02x" % (n, e.x * 16 + 8, e.y * 16 + 16))

                if obj.warp_type == 1 and obj.map_nr < 8 or obj.map_nr == 0xFF:
                    other = RoomEditor(rom,
                                       instrument_rooms[min(8, obj.map_nr)])
                    for o in other.objects:
                        if isinstance(o, ObjectWarp) and o.warp_type == 0:
                            o.room = n
                            o.target_x = e.x * 16 + 8
                            o.target_y = e.y * 16 + 16
                    other.store(rom)
        if n == 0x06:
            re.objects.insert(0, Object(5, 3, 0xE1))
        re.store(rom)
Ejemplo n.º 25
0
def addMultiworldShop(rom):
    # Make a copy of the shop into GrandpaUlrira house
    shop_room = RoomEditor(rom, 0x2A1)
    re = RoomEditor(rom, 0x2A9)
    re.objects = [
        obj for obj in shop_room.objects
        if obj.x is not None and obj.type_id != 0xCE
    ] + re.getWarps()
    re.entities = [(1, 6, 0x77), (2, 6, 0x77)]
    re.animation_id = shop_room.animation_id
    re.floor_object = shop_room.floor_object
    re.store(rom)
    # Fix the tileset
    rom.banks[0x20][0x2EB3 + 0x2A9 - 0x100] = rom.banks[0x20][0x2EB3 + 0x2A1 -
                                                              0x100]

    # Load the shopkeeper sprites instead of Grandpa sprites
    entityData.SPRITE_DATA[0x77] = entityData.SPRITE_DATA[0x4D]

    labels = {}
    rom.patch(0x06,
              0x2860,
              "00" * 0x215,
              ASM(
                  """
shopItemsHandler:
; Render the shop items
    ld   h, $00
loop:
    ; First load links position to render the item at
    ldh  a, [$98] ; LinkX
    ldh  [$EE], a ; X
    ldh  a, [$99] ; LinkY
    sub  $0E
    ldh  [$EC], a ; Y
    ; Check if this is the item we have picked up
    ld   a, [$C509] ; picked up item in shop
    dec  a
    cp   h
    jr   z, .renderCarry

    ld   a, h
    swap a
    add  a, $20
    ldh  [$EE], a ; X
    ld   a, $30
    ldh  [$EC], a ; Y
.renderCarry:
    ld   a, h
    push hl
    ldh  [$F1], a ; variant
    cp   $03
    jr   nc, .singleSprite
    ld   de, ItemsDualSpriteData
    call $3BC0 ; render sprite pair
    jr   .renderDone
.singleSprite:
    ld   de, ItemsSingleSpriteData
    call $3C77 ; render sprite
.renderDone:

    pop  hl
.skipItem:
    inc  h
    ld   a, $07
    cp   h
    jr   nz, loop

;   check if we want to pickup or drop an item
    ldh  a, [$CC]
    and  $30 ; A or B button
    call nz, checkForPickup

;   check if we have an item
    ld   a, [$C509] ; carry item
    and  a
    ret  z

    ; Set that link has picked something up
    ld   a, $01
    ld   [$C15C], a
    call $0CAF ; reset spin attack...

    ; Check if we are trying to exit the shop and so drop our item.
    ldh  a, [$99]
    cp   $78
    ret  c
    xor  a
    ld   [$C509], a

    ret

checkForPickup:
    ldh  a, [$9E] ; direction
    cp   $02
    ret  nz
    ldh  a, [$99] ; LinkY
    cp   $48
    ret  nc

    ld   a, $13
    ldh  [$F2], a ; play SFX

    ld   a, [$C509] ; picked up shop item
    and  a
    jr   nz, .drop

    ldh  a, [$98] ; LinkX
    sub  $08
    swap a
    and  $07
    ld   [$C509], a ; picked up shop item
    ret
.drop:
    xor  a
    ld   [$C509], a
    ret

ItemsDualSpriteData:
    db   $60, $08, $60, $28 ; zol
    db   $68, $09 ; chicken (left)
ItemsSingleSpriteData: ; (first 3 entries are still dual sprites)
    db   $6A, $09 ; chicken (right)
    db   $14, $02, $14, $22 ; piece of power
;Real single sprite data starts here
    db   $00, $0F ; bomb
    db   $38, $0A ; rupees
    db   $20, $0C ; medicine
    db   $28, $0C ; heart

;------------------------------------trying to buy something starts here
talkHandler:
    ld   a, [$C509] ; carry item
    add  a, a
    ret  z ; check if we have something to buy
    sub  $02

    ld   hl, itemNames
    ld   e, a
    ld   d, b ; b=0
    add  hl, de
    ld   e, [hl]
    inc  hl
    ld   d, [hl]

    ld   hl, wCustomMessage
    call appendString
    dec  hl
    call padString
    ld   de, postMessage
    call appendString
    dec  hl
    ld   a, $fe
    ld   [hl], a
    ld   de, $FFEF
    add  hl, de
    ldh  a, [$EE]
    swap a
    and  $0F
    add  a, $30
    ld   [hl], a
    ld   a, $C9
    call $2385 ; open dialog
    call $3B12 ; increase entity state
    ret

appendString:
    ld   a, [de]
    inc  de
    and  a
    ret  z
    ldi  [hl], a
    jr   appendString

padString:
    ld   a, l
    and  $0F
    ret  z
    ld   a, $20
    ldi  [hl], a
    jr   padString

itemNames:
    dw itemZol
    dw itemChicken
    dw itemPieceOfPower
    dw itemBombs
    dw itemRupees
    dw itemMedicine
    dw itemHealth

postMessage:
    db  "For player X?       Yes  No  ", $00

itemZol:
    db  m"Slime storm|100 {RUPEES}", $00
itemChicken:
    db  m"Coccu party|50 {RUPEES}", $00
itemPieceOfPower:
    db  m"Piece of Power|50 {RUPEES}", $00
itemBombs:
    db  m"20 Bombs|50 {RUPEES}", $00
itemRupees:
    db  m"100 {RUPEES}|200 {RUPEES}", $00
itemMedicine:
    db  m"Medicine|100 {RUPEES}", $00
itemHealth:
    db  m"Health refill|10 {RUPEES}", $00

TalkResultHandler:
    ld  hl, ItemPriceTableBCD
    ld  a, [$C509]
    dec a
    add a, a
    ld  c, a ; b=0
    add hl, bc
    ldi a, [hl]
    ld  d, [hl]
    ld  e, a
    ld  a, [$DB5D]
    cp  d
    ret c
    jr  nz, .highEnough
    ld  a, [$DB5E]
    cp  e
    ret c
.highEnough:
    ; Got enough money, take it.
    ld  hl, ItemPriceTableDEC
    ld  a, [$C509]
    dec a
    ld  c, a ; b=0
    add hl, bc
    ld  a, [hl]
    ld  [$DB92], a

    ; No longer picked up item
    xor a
    ld  [$C509], a
    ret

ItemPriceTableBCD:
    dw $0100, $0050, $0050, $0050, $0200, $0100, $0010
ItemPriceTableDEC:
    db $64, $32, $32, $32, $C8, $64, $0A

""", 0x6860, labels),
              fill_nop=True)

    # Patch GrandpaUlrira to work as a multiworld shop
    rom.patch(0x06,
              0x1C0E,
              0x1C89,
              ASM(
                  """
    ld   a, $01
    ld   [$C50A], a ; this stops link from using items

;Draw shopkeeper
    ld   de, OwnerSpriteData
    call $3BC0 ; render sprite pair
    ldh  a, [$E7] ; frame counter
    swap a
    and  $01
    call $3B0C ; set sprite variant

    ldh  a, [$F0]
    and  a
    jr   nz, checkTalkingResult

    call $641A ; prevent link from moving into the sprite
    call $645D ; check if talking to NPC
    call c, ${TALKHANDLER:04x} ; talk handling

    ldh  a, [$EE] ; X
    cp   $18
    ret  nz

    ; Jump to other code which is placed on the old owl code. As we do not have enough space here.
    jp   ${SHOPITEMSHANDLER:04x}

checkTalkingResult:
    ld   a, [$C19F]
    and  a
    ret  nz ; still taking
    call $3B12 ; increase entity state
    ld   [hl], $00
    ld   a, [$C177] ; dialog selection
    and  a
    ret  nz
    jp ${TALKRESULTHANDLER:04x}

OwnerSpriteData:
    ;db   $60, $03, $62, $03, $62, $23, $60, $23 ; down
    db   $64, $03, $66, $03, $66, $23, $64, $23 ; up
    ;db   $68, $03, $6A, $03, $6C, $03, $6E, $03 ; left
    ;db   $6A, $23, $68, $23, $6E, $23, $6C, $23 ; right
    """.format(**labels), 0x5C0E),
              fill_nop=True)
Ejemplo n.º 26
0
 def read(self, rom):
     re = RoomEditor(rom, 0x0F2)
     if re.hasEntity(0x31):
         return SWORD
     return super().read(rom)
Ejemplo n.º 27
0
def setSeashellGoal(rom, count):
    rom.texts[0x1A3] = formatText(b"You need %d seashells" % (count))

    # Remove the seashell mansion handler (as it will take your seashells) but put a heartpiece instead
    re = RoomEditor(rom, 0x2E9)
    re.entities = [(4, 4, 0x35)]
    re.store(rom)

    rom.patch(0x19,
              0x0ACB,
              0x0C21,
              ASM(
                  """
        ldh  a, [$F8] ; room status
        and  $10
        ret  nz
        ldh  a, [$F0] ; active entity state
        rst  0
        dw   state0, state1, state2, state3, state4

state0:
        ld   a, [$C124] ; room transition state
        and  a
        ret  nz
        ldh  a, [$99]  ; link position Y
        cp   $70
        ret  nc
        jp   $3B12  ; increase entity state

state1:
        call $0C05 ; get entity transition countdown
        jr   nz, renderShells
        ld   [hl], $10
        call renderShells

        ld   hl, $C2B0 ; private state 1 table
        add  hl, bc
        ld   a, [wSeashellsCount]
        cp   [hl]
        jp   z, $3B12  ; increase entity state
        ld   a, [hl]   ; increase the amount of compared shells
        inc  a
        daa
        ld   [hl], a
        ld   hl, $C2C0 ; private state 2 table
        add  hl, bc
        inc  [hl] ; increase amount of displayed shells
        ld   a, $2B
        ldh  [$F4], a ; SFX
        ret

state2:
        ld   a, [wSeashellsCount]
        cp   $%02d
        jr   c, renderShells
        ; got enough shells
        call $3B12 ; increase entity state
        call $0C05 ; get entity transition countdown
        ld   [hl], $40
        jp   renderShells

state3:
        ld   a, $23
        ldh  [$F2], a ; SFX: Dungeon opened
        ld   hl, $D806 ; egg room status
        set  4, [hl]
        ld   a, [hl]
        ldh  [$F8], a ; current room status
        call $3B12 ; increase entity state

        ld   a, $00
        jp   $4C2E

state4:
        ret

renderShells:
        ld   hl, $C2C0 ; private state 2 table
        add  hl, bc
        ld   a, [hl]
        cp   $14
        jr   c, .noMax
        ld   a, $14
.noMax:
        and  a
        ret  z
        ld   c, a
        ld   hl, spriteRect
        call $3CE6 ; RenderActiveEntitySpritesRect
        ret

spriteRect:
        db $10, $1E, $1E, $0C
        db $10, $2A, $1E, $0C
        db $10, $36, $1E, $0C
        db $10, $42, $1E, $0C
        db $10, $4E, $1E, $0C

        db $10, $5A, $1E, $0C
        db $10, $66, $1E, $0C
        db $10, $72, $1E, $0C
        db $10, $7E, $1E, $0C
        db $10, $8A, $1E, $0C

        db $24, $1E, $1E, $0C
        db $24, $2A, $1E, $0C
        db $24, $36, $1E, $0C
        db $24, $42, $1E, $0C
        db $24, $4E, $1E, $0C

        db $24, $5A, $1E, $0C
        db $24, $66, $1E, $0C
        db $24, $72, $1E, $0C
        db $24, $7E, $1E, $0C
        db $24, $8A, $1E, $0C
    """ % (count), 0x4ACB),
              fill_nop=True)
Ejemplo n.º 28
0
    def exportRoom(self, room_nr):
        re = RoomEditor(self.__rom, room_nr)

        if room_nr < 0x100:
            tile_info_offset = self.__rom.banks[0x1A].find(
                b'\x7C\x7C\x7C\x7C\x7D\x7D\x7D\x7D')
            tile_info = self.__rom.banks[0x1A][
                tile_info_offset:tile_info_offset + 0x100 * 4]
        else:
            tile_info_offset = self.__rom.banks[0x08].find(
                b'\x7F\x7F\x7F\x7F\x7E\x7E\x7E\x7E')
            tile_info = self.__rom.banks[0x08][
                tile_info_offset:tile_info_offset + 0x100 * 4]

        if room_nr >= 0x100:
            rendered_map = RenderedMap(re.floor_object & 0x0F)
        else:
            rendered_map = RenderedMap(re.floor_object, True)

        def objHSize(type_id):
            if type_id == 0xF5:
                return 2
            return 1

        def objVSize(type_id):
            if type_id == 0xF5:
                return 2
            return 1

        if room_nr >= 0x100:
            if re.floor_object & 0xF0 == 0x00:
                rendered_map.addWalls(RenderedMap.WALL_LEFT
                                      | RenderedMap.WALL_RIGHT
                                      | RenderedMap.WALL_UP
                                      | RenderedMap.WALL_DOWN)
            if re.floor_object & 0xF0 == 0x10:
                rendered_map.addWalls(RenderedMap.WALL_LEFT
                                      | RenderedMap.WALL_RIGHT
                                      | RenderedMap.WALL_DOWN)
            if re.floor_object & 0xF0 == 0x20:
                rendered_map.addWalls(RenderedMap.WALL_LEFT
                                      | RenderedMap.WALL_UP
                                      | RenderedMap.WALL_DOWN)
            if re.floor_object & 0xF0 == 0x30:
                rendered_map.addWalls(RenderedMap.WALL_LEFT
                                      | RenderedMap.WALL_RIGHT
                                      | RenderedMap.WALL_UP)
            if re.floor_object & 0xF0 == 0x40:
                rendered_map.addWalls(RenderedMap.WALL_RIGHT
                                      | RenderedMap.WALL_UP
                                      | RenderedMap.WALL_DOWN)
            if re.floor_object & 0xF0 == 0x50:
                rendered_map.addWalls(RenderedMap.WALL_LEFT
                                      | RenderedMap.WALL_DOWN)
            if re.floor_object & 0xF0 == 0x60:
                rendered_map.addWalls(RenderedMap.WALL_RIGHT
                                      | RenderedMap.WALL_DOWN)
            if re.floor_object & 0xF0 == 0x70:
                rendered_map.addWalls(RenderedMap.WALL_RIGHT
                                      | RenderedMap.WALL_UP)
            if re.floor_object & 0xF0 == 0x80:
                rendered_map.addWalls(RenderedMap.WALL_LEFT
                                      | RenderedMap.WALL_UP)
        for obj in re.objects:
            if isinstance(obj, ObjectWarp):
                pass
            elif isinstance(obj, ObjectHorizontal):
                for n in range(0, obj.count):
                    rendered_map.placeObject(obj.x + n * objHSize(obj.type_id),
                                             obj.y, obj.type_id)
            elif isinstance(obj, ObjectVertical):
                for n in range(0, obj.count):
                    rendered_map.placeObject(obj.x,
                                             obj.y + n * objVSize(obj.type_id),
                                             obj.type_id)
            else:
                rendered_map.placeObject(obj.x, obj.y, obj.type_id)

        tiles = [0] * 20 * 16
        for y in range(8):
            for x in range(10):
                obj = rendered_map.objects[(x, y)]
                tiles[x * 2 + y * 2 * 20] = tile_info[obj * 4]
                tiles[x * 2 + 1 + y * 2 * 20] = tile_info[obj * 4 + 1]
                tiles[x * 2 + (y * 2 + 1) * 20] = tile_info[obj * 4 + 2]
                tiles[x * 2 + 1 + (y * 2 + 1) * 20] = tile_info[obj * 4 + 3]

        if room_nr < 0x100:
            sub_tileset_offset = self.__rom.banks[0x20][0x2E73 +
                                                        (room_nr & 0x0F) // 2 +
                                                        ((room_nr >> 5) *
                                                         8)] << 4
            tilemap = self.__tiles[0x0f][
                sub_tileset_offset:sub_tileset_offset + 0x20]
            tilemap += self.__tiles[0x0c][0x120:0x180]
            tilemap += self.__tiles[0x0c][0x080:0x100]
        else:
            # TODO: The whole indoor tileset loading seems complex...
            tileset_nr = self.__rom.banks[0x20][0x2eB3 + room_nr - 0x100]
            tilemap = [None] * 0x100
            tilemap[0x20:0x80] = self.__tiles[0x0D][0x000:0x060]
            if tileset_nr != 0xFF:
                tilemap[0x00:0x10] = self.__tiles[0x0D][0x100 + tileset_nr *
                                                        0x10:0x110 +
                                                        tileset_nr * 0x10]
            tilemap[0x10:0x20] = self.__tiles[0x0D][0x210:0x220]
            tilemap[0xF0:0x100] = self.__tiles[0x12][0x380:0x390]

        if re.animation_id == 2:
            addr = 0x2B0
        elif re.animation_id == 3:
            addr = 0x2C0
        elif re.animation_id == 4:
            addr = 0x2D0
        elif re.animation_id == 5:
            addr = 0x2E0
        elif re.animation_id == 6:
            addr = 0x2F0
        elif re.animation_id == 7:
            addr = 0x2D0
        elif re.animation_id == 8:
            addr = 0x300
        elif re.animation_id == 9:
            addr = 0x310
        elif re.animation_id == 10:
            addr = 0x320
        elif re.animation_id == 11:
            addr = 0x2A0
        elif re.animation_id == 12:
            addr = 0x330
        elif re.animation_id == 13:
            addr = 0x350
        elif re.animation_id == 14:
            addr = 0x360
        elif re.animation_id == 15:
            addr = 0x340
        elif re.animation_id == 16:
            addr = 0x370
        else:
            print(hex(room_nr), re.animation_id)
            addr = 0x000
        tilemap[0x6C:0x70] = self.__tiles[0x0c][addr:addr + 4]

        assert len(tilemap) == 0x100

        result = PIL.Image.new('L', (8 * 20, 8 * 16))
        draw = PIL.ImageDraw.Draw(result)
        for y in range(16):
            for x in range(20):
                tile = tilemap[tiles[x + y * 20]]
                if tile is not None:
                    result.paste(tile, (x * 8, y * 8))
        warp_pos = []
        for y in range(8):
            for x in range(10):
                if rendered_map.objects[(x, y)] in (0xE1, 0xE3, 0xBA, 0xD5,
                                                    0xA8, 0xBE, 0xCB):
                    warp_pos.append((x, y))
        for x, y, type_id in re.entities:
            draw.rectangle([(x * 16, y * 16), (x * 16 + 15, y * 16 + 15)],
                           outline=0)
            draw.text((x * 16 + 3, y * 16 + 2), "%02X" % (type_id))
        y = 8
        for obj in re.objects:
            if isinstance(obj, ObjectWarp):
                draw.text((8, y), "W:" + str(obj))
                y += 16
        return result
Ejemplo n.º 29
0
def changeEntrances(rom, mapping):
    warp_to_indoor = {}
    warp_to_outdoor = {}
    for key in mapping.keys():
        info = ENTRANCE_INFO[key]
        re = RoomEditor(
            rom, info.alt_room if info.alt_room is not None else info.room)
        warp = re.getWarps()[info.index if info.index not in (None,
                                                              "all") else 0]
        warp_to_indoor[key] = warp
        assert info.target == warp.room, "%s != %03x" % (key, warp.room)

        re = RoomEditor(rom, warp.room)
        for warp in re.getWarps():
            if warp.room == info.room:
                warp_to_outdoor[key] = warp
        assert key in warp_to_outdoor, "Missing warp to outdoor on %s" % (key)

    # First collect all the changes we need to do per room
    changes_per_room = {}

    def addChange(source_room, target_room, new_warp):
        if source_room not in changes_per_room:
            changes_per_room[source_room] = {}
        changes_per_room[source_room][target_room] = new_warp

    for key, target in mapping.items():
        if key == target:
            continue
        info = ENTRANCE_INFO[key]
        # Change the entrance to point to the new indoor room
        addChange(info.room, warp_to_indoor[key].room, warp_to_indoor[target])
        if info.alt_room:
            addChange(info.alt_room, warp_to_indoor[key].room,
                      warp_to_indoor[target])

        # Change the exit to point to the right outside
        addChange(warp_to_indoor[target].room, ENTRANCE_INFO[target].room,
                  warp_to_outdoor[key])
        if ENTRANCE_INFO[target].instrument_room is not None:
            addChange(ENTRANCE_INFO[target].instrument_room,
                      ENTRANCE_INFO[target].room, warp_to_outdoor[key])

    # Finally apply the changes, we need to do this once per room to prevent A->B->C issues.
    for room, changes in changes_per_room.items():
        re = RoomEditor(rom, room)
        for idx, obj in enumerate(re.objects):
            if isinstance(obj, ObjectWarp) and obj.room in changes:
                re.objects[idx] = changes[obj.room].copy()
        re.store(rom)
Ejemplo n.º 30
0
def removeOwlEvents(rom):
    # Remove all the owl events from the entity tables.
    for room in range(0x100):
        re = RoomEditor(rom, room)
        if re.hasEntity(0x41):
            re.removeEntities(0x41)
            re.store(rom)
    # Clear texts used by the owl. Potentially reused somewhere o else.
    rom.texts[0x0D9] = b'\xff'  # used by boomerang
    # 1 Used by empty chest (master stalfos message)
    # 8 unused (0x0C0-0x0C7)
    # 1 used by bowwow in chest
    # 1 used by item for other player message
    # 2 used by arrow chest messages
    # 2 used by tunics
    for idx in range(0x0BE, 0x0CE):
        rom.texts[idx] = b'\xff'


    # Patch the owl entity into a ghost to allow refill of powder/bombs/arrows
    rom.texts[0xC0] = formatText("Everybody hates me, so I give away free things in the hope people will love me. Want something?", ask="Okay No")
    rom.texts[0xC1] = formatText("Good for you.")
    rom.patch(0x06, 0x27F5, 0x2A77, ASM("""
    ; Check if we have powder or bombs.
    ld   e, INV_SIZE
    ld   hl, $DB00
loop:
    ldi  a, [hl]
    cp   $02 ; bombs
    jr   z, hasProperItem
    cp   $0C ; powder
    jr   z, hasProperItem
    cp   $05 ; bow
    jr   z, hasProperItem
    dec  e
    jr   nz, loop
    ret
hasProperItem:
    
    ; Render ghost
    ld de, sprite
    call $3BC0

    call $64C6 ; check if game is busy (pops this stack frame if busy)

    ldh a, [$E7] ; frame counter
    swap a
    and  $01
    call $3B0C ; set entity sprite variant
    call $641A ; check collision
    ldh  a, [$F0] ;entity state
    rst 0
    dw  waitForTalk
    dw  talking

waitForTalk:
    call $645D ; check if talked to
    ret  nc
    ld   a, $C0
    call $2385 ; open dialog
    call $3B12 ; increase entity state
    ret

talking:
    ; Check if we are still talking
    ld   a, [$C19F]
    and  a
    ret  nz
    call $3B12 ; increase entity state
    ld   [hl], $00 ; set to state 0
    ld   a, [$C177] ; get which option we selected
    and  a
    ret  nz

    ; Give powder
    ld   a, [$DB4C]
    cp   $10
    jr   nc, doNotGivePowder
    ld   a, $10
    ld   [$DB4C], a
doNotGivePowder:

    ld   a, [$DB4D]
    cp   $10
    jr   nc, doNotGiveBombs
    ld   a, $10
    ld   [$DB4D], a
doNotGiveBombs:

    ld   a, [$DB45]
    cp   $10
    jr   nc, doNotGiveArrows
    ld   a, $10
    ld   [$DB45], a
doNotGiveArrows:

    ld   a, $C1
    call $2385 ; open dialog
    ret

sprite:
    db   $76, $09, $78, $09, $7A, $09, $7C, $09
""", 0x67F5), fill_nop=True)
    rom.patch(0x20, 0x0322 + 0x41 * 2, "734A", "564B")  # Remove the owl init handler

    re = RoomEditor(rom, 0x2A3)
    re.entities.append((7, 6, 0x41))
    re.store(rom)