Example #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)
Example #2
0
def fixAll(rom):
    # Prevent soft locking in the first mountain cave if we do not have a feather
    re = RoomEditor(rom, 0x2B7)
    re.removeObject(3, 3)
    re.store(rom)

    # Prevent getting stuck in the sidescroll room in the beginning of dungeon 5
    re = RoomEditor(rom, 0x1A9)
    re.objects[6].count = 7
    re.store(rom)

    # Cave that allows you to escape from D4 without flippers, make it no longer require a feather
    re = RoomEditor(rom, 0x1EA)
    re.objects[9].count = 8
    re.removeObject(5, 4)
    re.moveObject(4, 4, 7, 5)
    re.store(rom)

    # D3 west side room requires feather to get the key. But feather is not required to unlock the door, potentially softlocking you.
    re = RoomEditor(rom, 0x155)
    re.changeObject(4, 1, 0xcf)
    re.changeObject(4, 6, 0xd0)
    re.store(rom)

    # D3 boots room requires boots to escape
    re = RoomEditor(rom, 0x146)
    re.removeObject(5, 6)
    re.store(rom)

    allowRaftGameWithoutFlippers(rom)
    # We cannot access thes holes in logic:
    # removeBirdKeyHoleDrop(rom)
    fixDoghouse(rom)
    flameThrowerShieldRequirement(rom)
    fixLessThen3MaxHealth(rom)
Example #3
0
File: core.py Project: daid/LADXR
def fixWrongWarp(rom):
    rom.patch(0x00, 0x18CE, ASM("cp $04"), ASM("cp $03"))
    re = RoomEditor(rom, 0x2b)
    for x in range(10):
        re.removeObject(x, 7)
    re.objects.append(ObjectHorizontal(0, 7, 0x2C, 10))
    while len(re.getWarps()) < 4:
        re.objects.append(ObjectWarp(1, 3, 0x7a, 80, 124))
    re.store(rom)
Example #4
0
def setStartLocation(rom, index):
    if index == 0:
        return

    def swapWarps(r0, r1):
        r0warps = list(
            filter(lambda obj: isinstance(obj, ObjectWarp), r0.objects))
        r1warps = list(
            filter(lambda obj: isinstance(obj, ObjectWarp), r1.objects))
        r0.objects = list(
            filter(lambda obj: not isinstance(obj, ObjectWarp),
                   r0.objects)) + r0warps[:-1] + r1warps[-1:]
        r1.objects = list(
            filter(lambda obj: not isinstance(obj, ObjectWarp),
                   r1.objects)) + r1warps[:-1] + r0warps[-1:]
        return r0warps[-1].room, r1warps[-1].room

    old_room = RoomEditor(rom, START_EXITS[0])
    new_room = RoomEditor(rom, START_EXITS[index])
    old_indoor_room_id, new_indoor_room_id = swapWarps(old_room, new_room)
    old_room.store(rom)
    new_room.store(rom)

    old_room = RoomEditor(rom, old_indoor_room_id)
    new_room = RoomEditor(rom, new_indoor_room_id)
    swapWarps(old_room, new_room)
    old_room.store(rom)
    new_room.store(rom)
Example #5
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)
Example #6
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]
Example #7
0
def allowRaftGameWithoutFlippers(rom):
    # Allow jumping down the waterfall in the raft game without the flippers.
    rom.patch(0x02,
              0x2E8F,
              ASM("ld a, [$DB0C]"),
              ASM("ld a, $01"),
              fill_nop=True)
    # Change the room that goes back up to the raft game from the bottom, so we no longer need flippers
    re = RoomEditor(rom, 0x1F7)
    re.changeObject(3, 2, 0x1B)
    re.changeObject(2, 3, 0x1B)
    re.changeObject(3, 4, 0x1B)
    re.changeObject(4, 5, 0x1B)
    re.changeObject(6, 6, 0x1B)
    re.store(rom)
Example #8
0
def removeKeyDoors(rom):
    for n in range(0x100, 0x316):
        if n == 0x2FF:
            continue
        update = False
        re = RoomEditor(rom, n)
        for obj in re.objects:
            if obj.type_id in KEY_DOORS:
                obj.type_id = KEY_DOORS[obj.type_id]
                update = True
            if obj.type_id == 0xDE:  # Keyblocks
                obj.type_id = re.floor_object
                update = True
        if update:
            re.store(rom)
Example #9
0
def fixDoghouse(rom):
    # Fix entering the dog house from the back, and ending up out of bounds.
    re = RoomEditor(rom, 0x0A1)
    re.objects.append(Object(6, 2, 0x0E2))
    re.objects.append(
        re.objects[20]
    )  # Move the flower patch after the warp entry definition so it overrules the tile
    re.objects.append(re.objects[3])

    re.objects.pop(22)
    re.objects.pop(21)
    re.objects.pop(20)  # Remove the flower patch at the normal entry index
    re.objects.pop(
        11
    )  # Duplicate object, we can just remove it, gives room for our custom entry door
    re.store(rom)
Example #10
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)
Example #11
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'
Example #12
0
    def patch(self, rom, option, *, multiworld=None):
        assert multiworld is None

        if self.give_bowwow:
            option = BOWWOW
            rom.texts[0xC8] = formatText("Got BowWow!")

        if option != SHIELD:
            rom.patch(
                5, 0x0CDA, ASM("ld a, $22"), ASM("ld a, $00")
            )  # do not change links sprite into the one with a shield

        if option in (MAGIC_POWDER, BOMB):
            re = RoomEditor(rom, 0x0A2)
            re.entities.append((1, 3, 0x41))
            re.store(rom)

        super().patch(rom, option)
Example #13
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)
        rom.patch(0x19, 0x0010, "F0007806F008782600007A0600087A26",
                  "F000640FF008642F0000660F0008662F")
        rom.patch(0x19, 0x004F, ASM("cp $01"), ASM("cp $0A"))

        # Do not give the rooster
        rom.patch(0x19, 0x0E9D, ASM("ld [$DB7B], a"), "", fill_nop=True)
Example #14
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)
Example #15
0
File: core.py Project: daid/LADXR
def cleanup(rom):
    # Remove unused rooms to make some space in the rom
    re = RoomEditor(rom, 0x2C4)
    re.objects = []
    re.entities = []
    re.store(rom, 0x2C4)
    re.store(rom, 0x2D4)
    re.store(rom, 0x277)
    re.store(rom, 0x278)
    re.store(rom, 0x279)
    re.store(rom, 0x1ED)
    re.store(rom, 0x1FC)  # Beta room

    rom.texts[0x02B] = b''  # unused text
Example #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)
Example #17
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)
Example #18
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)
Example #19
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)
Example #20
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)
Example #21
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)
Example #22
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)
Example #23
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
Example #24
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)
Example #25
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)
Example #26
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"))
Example #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)
Example #28
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)
Example #29
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)
Example #30
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)