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)
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]
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)
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)
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)
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)
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"))
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
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)
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]