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)
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 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 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)
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)
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 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)
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)
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)
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 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'
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)
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 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
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)
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)
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)
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)
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)
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)
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)
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 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)
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)
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 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)
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 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)
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)