Exemple #1
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)
Exemple #2
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)
Exemple #3
0
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)
Exemple #4
0
def readEntrances(rom):
    result = {}
    for key, info in ENTRANCE_INFO.items():
        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]
        for other_key, other_info in ENTRANCE_INFO.items():
            if warp.room == other_info.target:
                result[key] = other_key
    return result
Exemple #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)

    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)
Exemple #6
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)
Exemple #7
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)
Exemple #8
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"))
Exemple #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)

    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]
Exemple #10
0
def warpHome(rom):
    # Patch the S&Q menu to allow 3 options
    rom.patch(0x01,
              0x012A,
              0x0150,
              ASM("""
        ld   hl, $C13F
        call $6BA8 ; make sound on keypress
        ldh  a, [$CC] ; load joystick status
        and  $04      ; if up
        jr   z, noUp
        dec  [hl]
noUp:
        ldh  a, [$CC] ; load joystick status
        and  $08      ; if down
        jr   z, noDown
        inc  [hl]
noDown:

        ld   a, [hl]
        cp   $ff
        jr   nz, noWrapUp
        ld   a, $02
noWrapUp:
        cp   $03
        jr   nz, noWrapDown
        xor  a
noWrapDown:
        ld   [hl], a
        jp   $7E02
    """),
              fill_nop=True)
    rom.patch(0x01,
              0x3E02,
              0x3E20,
              ASM("""
        swap a
        add  a, $48
        ld   hl, $C018
        ldi  [hl], a
        ld   a, $24
        ldi  [hl], a
        ld   a, $BE
        ldi  [hl], a
        ld   [hl], $00
        ret
    """),
              fill_nop=True)

    rom.patch(0x01,
              0x00B7,
              ASM("""
        ld   a, [$C13F]
        cp   $01
        jr   z, $3B
    """),
              ASM("""
        ld   a, [$C13F]
        jp $7E20
    """),
              fill_nop=True)

    re = RoomEditor(rom, 0x2a3)
    warp = re.getWarps()[0]
    rom.patch(0x01,
              0x3E20,
              0x4000,
              ASM("""
        ; First, handle save & quit
        cp   $01
        jp   z, $40F9
        and  a
        jp   z, $40BE ; return to normal "return to game" handling

        ld   a, [$C509] ; Check if we have an item in the shop
        and  a
        jp   nz, $40BE ; return to normal "return to game" handling

        ld   a, $0B
        ld   [$DB95], a
        call $0C7D

        ; Replace warp0 tile data, and put link on that tile.
        xor  a
        ld   [$D401], a
        ld   [$D402], a
        ld   a, $%02x ; Room
        ld   [$D403], a
        ld   a, $%02x ; X
        ld   [$D404], a
        ld   a, $%02x ; Y
        ld   [$D405], a

        ldh  a, [$98]
        swap a
        and  $0F
        ld   e, a
        ldh  a, [$99]
        sub  $08
        and  $F0
        or   e
        ld   [$D416], a

        ld   a, $07
        ld   [$DB96], a
        ret
        jp   $40BE  ; return to normal "return to game" handling
    """ % (warp.room, warp.target_x, warp.target_y)),
              fill_nop=True)

    # Patch the S&Q screen to have 3 options.
    be = BackgroundEditor(rom, 0x0D)
    for n in range(2, 18):
        be.tiles[0x99C0 + n] = be.tiles[0x9980 + n]
        be.tiles[0x99A0 + n] = be.tiles[0x9960 + n]
        be.tiles[0x9980 + n] = be.tiles[0x9940 + n]
        be.tiles[0x9960 + n] = be.tiles[0x98e0 + n]
    be.tiles[0x9960 + 10] = 0xCE
    be.tiles[0x9960 + 11] = 0xCF
    be.tiles[0x9960 + 12] = 0xC4
    be.tiles[0x9960 + 13] = 0x7F
    be.tiles[0x9960 + 14] = 0x7F
    be.store(rom)

    sprite_data = [
        0b00000000,
        0b01000100,
        0b01000101,
        0b01000101,
        0b01111101,
        0b01000101,
        0b01000101,
        0b01000100,
        0b00000000,
        0b11100100,
        0b00010110,
        0b00010101,
        0b00010100,
        0b00010100,
        0b00010100,
        0b11100100,
    ]
    for n in range(32):
        rom.banks[0x0F][0x08E0 + n] = sprite_data[n // 2]
Exemple #11
0
def addMultiworldShop(rom, this_player, player_count):
    # Make a copy of the shop into GrandpaUlrira house
    re = RoomEditor(rom, 0x2A9)
    re.objects = [
        ObjectHorizontal(1, 1, 0x00, 8),
        ObjectHorizontal(1, 2, 0x00, 8),
        ObjectHorizontal(1, 3, 0xCD, 8),
        Object(2, 0, 0xC7),
        Object(7, 0, 0xC7),
        Object(7, 7, 0xFD),
    ] + re.getWarps()
    re.entities = [(0, 6, 0xD4)]
    for n in range(player_count):
        if n != this_player:
            re.entities.append((n + 1, 6, 0xD4))
    re.animation_id = 0x04
    re.floor_object = 0x0D
    re.store(rom)
    # Fix the tileset
    rom.banks[0x20][0x2EB3 + 0x2A9 - 0x100] = rom.banks[0x20][0x2EB3 + 0x2A1 -
                                                              0x100]

    re = RoomEditor(rom, 0x0B1)
    re.getWarps()[0].target_x = 128
    re.store(rom)

    # Load the shopkeeper sprites
    entityData.SPRITE_DATA[0xD4] = entityData.SPRITE_DATA[0x4D]
    rom.patch(0x03, 0x01CF, "00",
              "98")  # Fix the hitbox of the ghost to be 16x16

    # Patch Ghost to work as a multiworld shop
    rom.patch(0x19,
              0x1E18,
              0x20B0,
              ASM(
                  """
    ld   a, $01
    ld   [$C50A], a ; this stops link from using items

    ldh  a, [$EE] ; X
    cp   $08
    ; Jump to other code which is placed on the old owl code. As we do not have enough space here.
    jp   z, shopItemsHandler

;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 $7CA2 ; prevent link from moving into the sprite
    call $7CF0 ; check if talking to NPC
    call c, talkHandler ; talk handling
    ret

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

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

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"10 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 ; set substract buffer
    
    ; Set the item to send
    ld  hl, $DDFE
    ld  a, [$C509] ; currently picked up item 
    ldi [hl], a
    ldh a, [$EE]   ; X position of NPC
    ldi [hl], a
    ld  hl, $DDF7
    set 2, [hl]

    ; 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
    """, 0x5E18),
              fill_nop=True)