Files
dockipelago/worlds/dk64/randomizer/Patching/FairyPlacer.py
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

335 lines
15 KiB
Python

"""Place fairies into the world."""
from randomizer.Enums.Enemies import Enemies
from randomizer.Lists.FairyLocations import fairy_locations, relocated_5ds_fairy
from randomizer.Enums.Maps import Maps
from randomizer.Patching.Patcher import LocalROM
from randomizer.Patching.Library.Assets import getPointerLocation, TableNames
def ReplaceShipFairy(ROM_COPY: LocalROM):
"""Replace the fairy inside 5DS with an easier to get fairy."""
file_start = getPointerLocation(TableNames.Spawners, Maps.Galleon5DShipDKTiny)
ROM_COPY.seek(file_start)
fence_count = int.from_bytes(ROM_COPY.readBytes(2), "big")
offset = 2
fence_bytes = []
used_fence_ids = []
if fence_count > 0:
for x in range(fence_count):
fence = []
fence_start = file_start + offset
ROM_COPY.seek(file_start + offset)
point_count = int.from_bytes(ROM_COPY.readBytes(2), "big")
offset += (point_count * 6) + 2
ROM_COPY.seek(file_start + offset)
point0_count = int.from_bytes(ROM_COPY.readBytes(2), "big")
offset += (point0_count * 10) + 6
fence_finish = file_start + offset
fence_size = fence_finish - fence_start
ROM_COPY.seek(fence_finish - 4)
used_fence_ids.append(int.from_bytes(ROM_COPY.readBytes(2), "big"))
ROM_COPY.seek(fence_start)
for y in range(int(fence_size / 2)):
fence.append(int.from_bytes(ROM_COPY.readBytes(2), "big"))
fence_bytes.append(fence)
ROM_COPY.seek(fence_finish)
spawner_count_location = file_start + offset
ROM_COPY.seek(spawner_count_location)
spawner_count = int.from_bytes(ROM_COPY.readBytes(2), "big")
offset += 2
spawner_bytes = []
fairy_spawner_id = None
for x in range(spawner_count):
# Parse spawners
ROM_COPY.seek(file_start + offset)
enemy_id = int.from_bytes(ROM_COPY.readBytes(1), "big")
ROM_COPY.seek(file_start + offset + 0x13)
enemy_index = int.from_bytes(ROM_COPY.readBytes(1), "big")
init_offset = offset
ROM_COPY.seek(file_start + offset + 0x11)
extra_count = int.from_bytes(ROM_COPY.readBytes(1), "big")
offset += 0x16 + (extra_count * 2)
end_offset = offset
if enemy_id != Enemies.Fairy:
# Keep enemy if not fairy
data_bytes = []
spawner_size = end_offset - init_offset
ROM_COPY.seek(file_start + init_offset)
for x in range(spawner_size):
data_bytes.append(int.from_bytes(ROM_COPY.readBytes(1), "big"))
spawner_bytes.append(data_bytes)
else:
# Is fairy
fairy_spawner_id = enemy_index
fence_index = 1
if fence_index in used_fence_ids:
while fence_index in used_fence_ids:
fence_index += 1
used_fence_ids.append(fence_index)
# Spawner
data_bytes = []
data_bytes.append(Enemies.Fairy)
data_bytes.append(0)
for x in range(2):
data_bytes.append(0)
for x in [relocated_5ds_fairy.fence.center_x, relocated_5ds_fairy.spawn_y, relocated_5ds_fairy.fence.center_z]:
if x < 0:
x += 65536 # Convert to unsigned
data_bytes.append(int(x / 256))
data_bytes.append(int(x % 256))
for x in range(2):
data_bytes.append(0)
data_bytes.append(0x23) # Idle Speed
data_bytes.append(0x3C) # Aggro Speed
data_bytes.append(fence_index) # Fence ID
data_bytes.append(0x32) # Scale
data_bytes.append(1) # Init Control State
data_bytes.append(0) # Extra Data Count
data_bytes.append(2) # Init Spawn State
data_bytes.append(fairy_spawner_id) # Spawn Index
data_bytes.append(0) # Init Respawn Timer
data_bytes.append(0)
spawner_bytes.append(data_bytes)
# Fence
new_fence_bytes = []
a_0 = [relocated_5ds_fairy.fence.min_x, 0, relocated_5ds_fairy.fence.min_z]
a_1 = [relocated_5ds_fairy.fence.max_x, 0, relocated_5ds_fairy.fence.min_z]
a_2 = [relocated_5ds_fairy.fence.max_x, 0, relocated_5ds_fairy.fence.max_z]
a_3 = [relocated_5ds_fairy.fence.min_x, 0, relocated_5ds_fairy.fence.max_z]
a_01 = []
a_12 = []
a_23 = []
a_30 = []
for x in range(3):
a_01.append(int((a_0[x] + a_1[x]) / 2))
a_12.append(int((a_1[x] + a_2[x]) / 2))
a_23.append(int((a_2[x] + a_3[x]) / 2))
a_30.append(int((a_3[x] + a_0[x]) / 2))
fence_coords = [a_0, a_01, a_1, a_12, a_2, a_23, a_3, a_30]
new_fence_bytes.append(len(fence_coords)) # 0: Fence Block 0x6 Count, 1: Fence Block 0xA Count
for x in fence_coords:
for y in x:
if y < 0:
y += 65536 # Signed to unsigned conversion
new_fence_bytes.append(y)
new_fence_bytes.append(0)
new_fence_bytes.append(fence_index)
new_fence_bytes.append(1)
fence_bytes.append(new_fence_bytes)
# Repack
ROM_COPY.seek(file_start)
ROM_COPY.writeMultipleBytes(len(fence_bytes), 2)
for x in fence_bytes:
for y in x:
ROM_COPY.writeMultipleBytes(y, 2)
ROM_COPY.writeMultipleBytes(len(spawner_bytes), 2)
for x in spawner_bytes:
for y in x:
ROM_COPY.writeMultipleBytes(y, 1)
def PlaceFairies(spoiler, ROM_COPY: LocalROM):
"""Write Fairies to ROM."""
ReplaceShipFairy(ROM_COPY)
sav = spoiler.settings.rom_data
ROM_COPY.seek(sav + 0xE0)
ROM_COPY.writeMultipleBytes(0, 2)
if spoiler.settings.random_fairies:
action_maps = [
Maps.JungleJapes,
Maps.JapesLankyCave,
Maps.AztecLlamaTemple,
Maps.AztecTiny5DTemple,
Maps.FranticFactory,
Maps.GloomyGalleon,
Maps.Galleon5DShipDKTiny,
Maps.ForestThornvineBarn,
Maps.ForestRafters,
Maps.CavesTinyIgloo,
Maps.CavesDiddyUpperCabin,
Maps.CastleTree,
Maps.CastleMuseum,
Maps.Isles,
Maps.FranticFactoryLobby,
Maps.FungiForestLobby,
Maps.HideoutHelm,
]
# Append new maps to action map list
for level in spoiler.fairy_locations:
for item in spoiler.fairy_locations[level]:
if fairy_locations[level][item].map not in action_maps:
action_maps.append(fairy_locations[level][item].map)
# Pull all character spawner files that are part of the action map list
for map in action_maps:
file_start = getPointerLocation(TableNames.Spawners, map)
ROM_COPY.seek(file_start)
fence_count = int.from_bytes(ROM_COPY.readBytes(2), "big")
offset = 2
fence_bytes = []
used_fence_ids = []
if fence_count > 0:
for x in range(fence_count):
fence = []
fence_start = file_start + offset
ROM_COPY.seek(file_start + offset)
point_count = int.from_bytes(ROM_COPY.readBytes(2), "big")
offset += (point_count * 6) + 2
ROM_COPY.seek(file_start + offset)
point0_count = int.from_bytes(ROM_COPY.readBytes(2), "big")
offset += (point0_count * 10) + 6
fence_finish = file_start + offset
fence_size = fence_finish - fence_start
ROM_COPY.seek(fence_finish - 4)
used_fence_ids.append(int.from_bytes(ROM_COPY.readBytes(2), "big"))
ROM_COPY.seek(fence_start)
for y in range(int(fence_size / 2)):
fence.append(int.from_bytes(ROM_COPY.readBytes(2), "big"))
fence_bytes.append(fence)
ROM_COPY.seek(fence_finish)
spawner_count_location = file_start + offset
ROM_COPY.seek(spawner_count_location)
spawner_count = int.from_bytes(ROM_COPY.readBytes(2), "big")
offset += 2
spawner_bytes = []
used_enemy_indexes = []
for x in range(spawner_count):
# Parse spawners
ROM_COPY.seek(file_start + offset)
enemy_id = int.from_bytes(ROM_COPY.readBytes(1), "big")
ROM_COPY.seek(file_start + offset + 0x4)
enemy_coords = []
for y in range(3):
coord = int.from_bytes(ROM_COPY.readBytes(2), "big")
if coord > 32767:
coord -= 65536
enemy_coords.append(coord)
ROM_COPY.seek(file_start + offset + 0x13)
enemy_index = int.from_bytes(ROM_COPY.readBytes(1), "big")
used_enemy_indexes.append(enemy_index)
init_offset = offset
ROM_COPY.seek(file_start + offset + 0x11)
extra_count = int.from_bytes(ROM_COPY.readBytes(1), "big")
offset += 0x16 + (extra_count * 2)
end_offset = offset
is_vanilla = False
# Check if fairy is a vanilla fairy
for level in spoiler.fairy_locations:
fairies = [fairy_locations[level][x] for x in spoiler.fairy_locations[level]]
for fairy in fairies:
if fairy.is_vanilla and fairy.map == map:
coord_match_count = 0
for y in range(3):
if enemy_coords[y] == fairy.spawn_xyz[y]:
coord_match_count += 1
if coord_match_count == 3:
is_vanilla = True
if enemy_id != Enemies.Fairy or is_vanilla:
# Keep enemy if not fairy or is a vanilla fairy that's going to be kept
data_bytes = []
spawner_size = end_offset - init_offset
ROM_COPY.seek(file_start + init_offset)
for x in range(spawner_size):
data_bytes.append(int.from_bytes(ROM_COPY.readBytes(1), "big"))
spawner_bytes.append(data_bytes)
spawn_index = 1
fence_index = 1
for level in spoiler.fairy_locations:
# Check for new fairies
fairies = [fairy_locations[level][x] for x in spoiler.fairy_locations[level]]
for sub_index, fairy in enumerate(fairies):
if map == fairy.map and not fairy.is_vanilla:
# Fairy is not vanilla
if spawn_index in used_enemy_indexes:
while spawn_index in used_enemy_indexes:
spawn_index += 1
used_enemy_indexes.append(spawn_index)
if fence_index in used_fence_ids:
while fence_index in used_fence_ids:
fence_index += 1
used_fence_ids.append(fence_index)
# Spawner
data_bytes = []
data_bytes.append(Enemies.Fairy)
data_bytes.append(0)
for x in range(2):
data_bytes.append(0)
for x in [fairy.fence.center_x, fairy.spawn_y, fairy.fence.center_z]:
if x < 0:
x += 65536 # Convert to unsigned
data_bytes.append(int(x / 256))
data_bytes.append(int(x % 256))
for x in range(2):
data_bytes.append(0)
data_bytes.append(0x23) # Idle Speed
data_bytes.append(0x3C) # Aggro Speed
data_bytes.append(fence_index) # Fence ID
data_bytes.append(0x32) # Scale
data_bytes.append(1) # Init Control State
data_bytes.append(0) # Extra Data Count
data_bytes.append(2) # Init Spawn State
data_bytes.append(spawn_index) # Spawn Index
data_bytes.append(0) # Init Respawn Timer
data_bytes.append(0)
spawner_bytes.append(data_bytes)
# Set ID for array
for item in spoiler.fairy_data_table:
if item["level"] == level and spoiler.fairy_locations[level][sub_index] == item["fairy_index"]:
item["id"] = spawn_index
# Fence
new_fence_bytes = []
a_0 = [fairy.fence.min_x, 0, fairy.fence.min_z]
a_1 = [fairy.fence.max_x, 0, fairy.fence.min_z]
a_2 = [fairy.fence.max_x, 0, fairy.fence.max_z]
a_3 = [fairy.fence.min_x, 0, fairy.fence.max_z]
a_01 = []
a_12 = []
a_23 = []
a_30 = []
for x in range(3):
a_01.append(int((a_0[x] + a_1[x]) / 2))
a_12.append(int((a_1[x] + a_2[x]) / 2))
a_23.append(int((a_2[x] + a_3[x]) / 2))
a_30.append(int((a_3[x] + a_0[x]) / 2))
fence_coords = [a_0, a_01, a_1, a_12, a_2, a_23, a_3, a_30]
new_fence_bytes.append(len(fence_coords)) # 0: Fence Block 0x6 Count, 1: Fence Block 0xA Count
for x in fence_coords:
for y in x:
if y < 0:
y += 65536 # Signed to unsigned conversion
new_fence_bytes.append(y)
new_fence_bytes.append(0)
new_fence_bytes.append(fence_index)
new_fence_bytes.append(1)
fence_bytes.append(new_fence_bytes)
# Repack
ROM_COPY.seek(file_start)
ROM_COPY.writeMultipleBytes(len(fence_bytes), 2)
for x in fence_bytes:
for y in x:
ROM_COPY.writeMultipleBytes(y, 2)
ROM_COPY.writeMultipleBytes(len(spawner_bytes), 2)
for x in spawner_bytes:
for y in x:
ROM_COPY.writeMultipleBytes(y, 1)
# Non-Spawner files
# Setting Enable
ROM_COPY.seek(sav + 0x100)
ROM_COPY.write(1)
# Array construction
write_data = [255, 255]
for index, item in enumerate(spoiler.fairy_data_table):
ROM_COPY.seek(0x1FFC000 + (4 * index))
ROM_COPY.writeMultipleBytes(item["flag"], 2)
item_level = item["level"]
item_map = fairy_locations[item_level][item["fairy_index"]].map
ROM_COPY.writeMultipleBytes(item_map, 1)
ROM_COPY.writeMultipleBytes(item["id"], 1) # Get Spawner ID
if item["shift"] >= 0:
offset = int(item["shift"] >> 3)
check = int(item["shift"] % 8)
write_data[offset] &= 0xFF - (0x80 >> check)
ROM_COPY.seek(sav + 0xE0)
for byte_data in write_data:
ROM_COPY.writeMultipleBytes(byte_data, 1)