Files
dockipelago/worlds/dk64/randomizer/Patching/ItemRando.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

1024 lines
50 KiB
Python

"""Apply item rando changes."""
from randomizer.Enums.Items import Items
from randomizer.Enums.Kongs import Kongs
from randomizer.Enums.Levels import Levels
from randomizer.Enums.Locations import Locations
from randomizer.Enums.Settings import MicrohintsEnabled
from randomizer.Enums.Types import Types
from randomizer.Enums.MoveTypes import MoveTypes
from randomizer.Lists.Item import ItemList
from randomizer.Patching.Library.DataTypes import float_to_hex, intf_to_float
from randomizer.Lists.EnemyTypes import enemy_location_list
from randomizer.Patching.Library.Generic import setItemReferenceName, CustomActors
from randomizer.Patching.Library.Assets import getPointerLocation, TableNames
from randomizer.Patching.Patcher import LocalROM
from randomizer.CompileHints import getHelmProgItems, GetRegionIdOfLocation
model_two_indexes = {
Types.Banana: 0x74,
Types.Blueprint: [0xDE, 0xE0, 0xE1, 0xDD, 0xDF],
Types.RarewareCoin: 0x28F,
Types.NintendoCoin: 0x48,
Types.Key: 0x13C,
Types.Crown: 0x18D,
Types.Medal: 0x90,
Types.Shop: [0x5B, 0x1F2, 0x59, 0x1F3, 0x1F5, 0x1F6],
Types.TrainingBarrel: 0x1F6,
Types.Climbing: 0x1F6,
Types.Shockwave: 0x1F6,
Types.NoItem: 0, # No Item
Types.Kong: [0x257, 0x258, 0x259, 0x25A, 0x25B],
Types.Bean: 0x198,
Types.Pearl: 0x1B4,
Types.Fairy: 0x25C,
Types.RainbowCoin: 0xB7,
Types.FakeItem: [0x25D, 0x264, 0x265],
Types.JunkItem: [0x56, 0x8F, 0x8E, 0x25E, 0x98], # Orange, Ammo, Crystal, Watermelon, Film
Types.Cranky: 0x25F,
Types.Funky: 0x260,
Types.Candy: 0x261,
Types.Snide: 0x262,
Types.Hint: [638, 649, 650, 651, 652],
Types.ArchipelagoItem: 0x291,
}
model_two_scales = {
# Anything not here is 0.25
Types.Blueprint: 2,
Types.NintendoCoin: 0.4,
Types.RarewareCoin: 0.4,
Types.Key: 0.17,
Types.Crown: 0.25,
Types.Medal: 0.22,
}
actor_indexes = {
Types.Banana: 45,
Types.Blueprint: [78, 75, 77, 79, 76],
Types.Key: 72,
Types.Crown: 86,
Types.NintendoCoin: CustomActors.NintendoCoin,
Types.RarewareCoin: CustomActors.RarewareCoin,
Types.Shop: [
CustomActors.PotionDK,
CustomActors.PotionDiddy,
CustomActors.PotionLanky,
CustomActors.PotionTiny,
CustomActors.PotionChunky,
CustomActors.PotionAny,
],
Types.TrainingBarrel: CustomActors.PotionAny,
Types.Shockwave: CustomActors.PotionAny,
Types.NoItem: CustomActors.Null,
Types.Medal: CustomActors.Medal,
Types.Kong: [
CustomActors.KongDK,
CustomActors.KongDiddy,
CustomActors.KongLanky,
CustomActors.KongTiny,
CustomActors.KongChunky,
],
Types.Bean: CustomActors.Bean,
Types.Pearl: CustomActors.Pearl,
Types.Fairy: CustomActors.Fairy,
Types.RainbowCoin: 0x8C,
Types.FakeItem: CustomActors.IceTrapBubble,
Types.JunkItem: [0x34, 0x33, 0x79, 0x2F, 0], # Orange, Ammo, Crystal, Watermelon, Film
Types.Cranky: CustomActors.CrankyItem,
Types.Funky: CustomActors.FunkyItem,
Types.Candy: CustomActors.CandyItem,
Types.Snide: CustomActors.SnideItem,
Types.Hint: [
CustomActors.HintItemDK,
CustomActors.HintItemDiddy,
CustomActors.HintItemLanky,
CustomActors.HintItemTiny,
CustomActors.HintItemChunky,
],
Types.ArchipelagoItem: CustomActors.ArchipelagoItem,
}
model_indexes = {
Types.Banana: 0x69,
Types.Key: 0xF5,
Types.Crown: 0xF4,
Types.Fairy: 0x3D,
Types.Shop: [0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB],
Types.Shockwave: 0xFB,
Types.TrainingBarrel: 0xFB,
Types.Climbing: 0xFB,
Types.Kong: [4, 1, 6, 9, 0xC],
Types.FakeItem: [-4, -3, -2], # -4 for bubble trap, -3 for reverse trap, -2 for slow trap
Types.Bean: 0x104,
Types.Pearl: 0x106,
Types.Medal: 0x108,
Types.NintendoCoin: 0x10A,
Types.RarewareCoin: 0x10C,
Types.JunkItem: 0x10E,
Types.Cranky: 0x11,
Types.Funky: 0x12,
Types.Candy: 0x13,
Types.Snide: 0x1F,
Types.Hint: [0x11B, 0x11D, 0x11F, 0x121, 0x123],
Types.ArchipelagoItem: 0x125,
}
TRAINING_LOCATIONS = (
Locations.IslesSwimTrainingBarrel,
Locations.IslesVinesTrainingBarrel,
Locations.IslesOrangesTrainingBarrel,
Locations.IslesBarrelsTrainingBarrel,
)
kong_flags = (385, 6, 70, 66, 117)
subitems = (Items.JunkOrange, Items.JunkAmmo, Items.JunkCrystal, Items.JunkMelon, Items.JunkFilm)
shop_owner_types = (Types.Cranky, Types.Funky, Types.Snide, Types.Candy)
class TextboxChange:
"""Class to store information which pertains to a change of textbox information."""
def __init__(
self,
location,
file_index,
textbox_index,
text_replace,
default_type: Types,
replacement_text="|",
force_pipe=False,
):
"""Initialize with given paremeters."""
self.location = location
self.file_index = file_index
self.textbox_index = textbox_index
self.text_replace = text_replace # Text which is going to be replaced with replacement_text
self.replacement_text = replacement_text
self.force_pipe = force_pipe # If True, don't replace with item name upon checking later. Instead, will be replaced in RDRAM dynamically
self.default_type = default_type
textboxes = [
TextboxChange(Locations.AztecTinyBeetleRace, 14, 0, "GOLDEN BANANA", Types.Banana, "\x04|\x04", True),
TextboxChange(Locations.CavesLankyBeetleRace, 14, 0, "GOLDEN BANANA", Types.Banana, "\x04|\x04", True),
TextboxChange(Locations.JapesDiddyMinecarts, 16, 2, "GOLDEN BANANA", Types.Banana),
TextboxChange(Locations.JapesDiddyMinecarts, 16, 3, "BANANA", Types.Banana),
TextboxChange(Locations.JapesDiddyMinecarts, 16, 4, "BANANA", Types.Banana),
TextboxChange(Locations.ForestChunkyMinecarts, 16, 5, "GOLDEN BANANA", Types.Banana),
TextboxChange(Locations.ForestChunkyMinecarts, 16, 7, "BANANA", Types.Banana),
TextboxChange(Locations.CastleDonkeyMinecarts, 16, 8, "BE A WINNER", Types.Banana, "WIN A |"),
TextboxChange(Locations.CastleDonkeyMinecarts, 16, 9, "BANANA", Types.Banana),
TextboxChange(Locations.IslesDonkeyInstrumentPad, 16, 18, "ANOTHER BANANA", Types.Banana, "SOMETHING"),
TextboxChange(Locations.IslesDiddyInstrumentPad, 16, 18, "ANOTHER BANANA", Types.Banana, "SOMETHING"),
TextboxChange(Locations.IslesLankyInstrumentPad, 16, 18, "ANOTHER BANANA", Types.Banana, "SOMETHING"),
TextboxChange(Locations.IslesTinyInstrumentPad, 16, 18, "ANOTHER BANANA", Types.Banana, "SOMETHING"),
TextboxChange(Locations.IslesChunkyInstrumentPad, 16, 18, "ANOTHER BANANA", Types.Banana, "SOMETHING"),
TextboxChange(Locations.FactoryTinyCarRace, 17, 4, "GOLDEN BANANA", Types.Banana),
TextboxChange(
Locations.GalleonTinyPearls,
23,
0,
"PLEASE TRY AND GET THEM BACK",
Types.Banana,
"IF YOU HELP ME FIND THEM, I WILL REWARD YOU WITH A |",
),
TextboxChange(Locations.GalleonTinyPearls, 23, 1, "GOLDEN BANANA", Types.Banana),
TextboxChange(
Locations.GalleonTinyPearls,
23,
2,
"ALTOGETHER.",
Types.Banana,
"ALTOGETHER. IF YOU FIND THEM ALL, YOU WILL RECEIVE A |",
),
TextboxChange(Locations.AztecDiddyVultureRace, 15, 1, "PRIZE", Types.Banana),
TextboxChange(Locations.AztecDonkeyFreeLlama, 10, 1, "ALL THIS SAND", Types.Banana, "THIS |"),
TextboxChange(Locations.AztecDonkeyFreeLlama, 10, 2, "BANANA", Types.Banana),
TextboxChange(Locations.RarewareCoin, 8, 2, "RAREWARE COIN", Types.RarewareCoin), # Rareware Coin
TextboxChange(Locations.RarewareCoin, 8, 34, "RAREWARE COIN", Types.RarewareCoin), # Rareware Coin
TextboxChange(Locations.ForestLankyRabbitRace, 20, 1, "TROPHY", Types.Banana, "| TROPHY"),
TextboxChange(Locations.ForestLankyRabbitRace, 20, 2, "TROPHY", Types.Banana, "| TROPHY"),
TextboxChange(Locations.ForestLankyRabbitRace, 20, 3, "TROPHY", Types.Banana, "| TROPHY"),
TextboxChange(Locations.ForestChunkyApple, 22, 0, "BANANA", Types.Banana),
TextboxChange(Locations.ForestChunkyApple, 22, 1, "BANANA", Types.Banana),
TextboxChange(Locations.ForestChunkyApple, 22, 4, "BANANA", Types.Banana),
TextboxChange(Locations.GalleonDonkeySealRace, 28, 2, "CHEST O' GOLD", Types.Banana),
TextboxChange(Locations.RarewareBanana, 30, 0, "REWARD ANYONE", Types.Banana, "REWARD ANYONE WITH A |"),
TextboxChange(Locations.CavesLankyCastle, 33, 0, "HOW ABOUT IT", Types.Banana, "HOW ABOUT A |"),
TextboxChange(Locations.CastleTinyCarRace, 34, 4, "BANANA", Types.Banana),
TextboxChange(
Locations.ForestDiddyOwlRace,
21,
0,
"WHEN YOU CAN FLY",
Types.Banana,
"WHEN YOU CAN FLY TO HAVE A CHANCE TO RECEIVE A |",
),
TextboxChange(Locations.ForestTinySpiderBoss, 19, 32, "\x04GOLDEN BANANA\x04", Types.Banana),
TextboxChange(Locations.CavesChunky5DoorIgloo, 19, 34, "\x04GOLDEN BANANA\x04", Types.Banana),
]
text_rewards = {
Types.Banana: ("\x04GOLDEN BANANA\x04", "\x04BANANA OF PURE GOLD\x04"),
Types.Blueprint: ("\x04BLUEPRINT\x04", "\x04MAP O' DEATH MACHINE\x04"),
Types.Key: ("\x04BOSS KEY\x04", "\x04KEY TO DAVY JONES LOCKER\x04"),
Types.Crown: ("\x04BATTLE CROWN\x04", "\x04CROWN TO PLACE ATOP YER HEAD\x04"),
Types.Fairy: ("\x04BANANA FAIRY\x04", "\x04MAGICAL FLYING PIXIE\x04"),
Types.Medal: ("\x04BANANA MEDAL\x04", "\x04MEDALLION\x04"),
Types.Shop: ("\x04POTION\x04", "\x04BOTTLE OF GROG\x04"),
Types.Shockwave: ("\x04POTION\x04", "\x04BOTTLE OF GROG\x04"),
Types.TrainingBarrel: ("\x04POTION\x04", "\x04BOTTLE OF GROG\x04"),
Types.Climbing: ("\x04POTION\x04", "\x04BOTTLE OF GROG\x04"),
Types.Kong: ("\x04KONG\x04", "\x04WEIRD MONKEY\x04"),
Types.Bean: ("\x04BEAN\x04", "\x04QUESTIONABLE VEGETABLE\x04"),
Types.Pearl: ("\x04PEARL\x04", "\x04BLACK PEARL\x04"),
Types.RainbowCoin: ("\x04RAINBOW COIN\x04", "\x04COLORFUL COIN HIDDEN FOR 17 YEARS\x04"),
Types.FakeItem: ("\x04GLODEN BANANE\x04", "\x04BANANA OF FOOLS GOLD\x04"),
Types.JunkItem: ("\x04JUNK ITEM\x04", "\x04HEAP OF JUNK\x04"),
Types.NoItem: ("\x04NOTHING\x04", "\x04DIDDLY SQUAT\x04"),
Types.RarewareCoin: ("\x04RAREWARE COIN\x04", "\x04DOUBLOON OF THE RAREST KIND\x04"),
Types.NintendoCoin: ("\x04NINTENDO COIN\x04", "\x04ANCIENT DOUBLOON\x04"),
Types.Snide: ("\x04SHOPKEEPER\x04", "\x04NERDY SOUL\x04"),
Types.Cranky: ("\x04SHOPKEEPER\x04", "\x04BARTERING SOUL\x04"),
Types.Candy: ("\x04SHOPKEEPER\x04", "\x04BARTERING SOUL\x04"),
Types.Funky: ("\x04SHOPKEEPER\x04", "\x04BARTERING SOUL\x04"),
Types.Hint: ("\x04HINT\x04", "\x04LAYTON RIDDLE\x04"),
Types.ArchipelagoItem: ("\x04ARCHIPELAGO ITEM\x04", "\x04ANOTHER SCALLYWAG'S BOOTY\x04"),
}
level_names = {
Levels.JungleJapes: "Jungle Japes",
Levels.AngryAztec: "Angry Aztec",
Levels.FranticFactory: "Frantic Factory",
Levels.GloomyGalleon: "Gloomy Galleon",
Levels.FungiForest: "Fungi Forest",
Levels.CrystalCaves: "Crystal Caves",
Levels.CreepyCastle: "Creepy Castle",
Levels.DKIsles: "DK Isles",
Levels.HideoutHelm: "Hideout Helm",
}
kong_names = {
Kongs.donkey: "Donkey Kong",
Kongs.diddy: "Diddy",
Kongs.lanky: "Lanky",
Kongs.tiny: "Tiny",
Kongs.chunky: "Chunky",
Kongs.any: "Any Kong",
}
def appendTextboxChange(spoiler, file_index: int, textbox_index: int, search: str, target: str):
"""Alter a specific textbox."""
data = {"textbox_index": textbox_index, "mode": "replace", "search": search, "target": target}
if file_index in spoiler.text_changes:
spoiler.text_changes[file_index].append(data)
else:
spoiler.text_changes[file_index] = [data]
NUMBERS_AS_WORDS = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE"]
def alterTextboxRequirements(spoiler):
"""Alters various textboxes based on the requirement count changing."""
pearl_req = spoiler.settings.mermaid_gb_pearls
appendTextboxChange(spoiler, 23, 2, "FIVE MISSING PEARLS", f"{NUMBERS_AS_WORDS[pearl_req]} MISSING PEARL{'S' if pearl_req != 1 else ''}")
all_text = ""
if pearl_req == 5:
all_text = "ALL "
plea_including_pearl_count = f"PLEASE TRY AND GET {all_text}{NUMBERS_AS_WORDS[pearl_req]} OF THEM BACK"
for x in textboxes:
if x.location == Locations.GalleonTinyPearls and x.textbox_index == 0:
x.text_replace = plea_including_pearl_count
x.replacement_text = f"IF YOU HELP ME FIND {all_text}{NUMBERS_AS_WORDS[pearl_req]} OF THEM, I WILL REWARD YOU WITH A |"
appendTextboxChange(spoiler, 23, 0, "PLEASE TRY AND GET THEM BACK", plea_including_pearl_count)
fairy_req = spoiler.settings.rareware_gb_fairies
if fairy_req != 20:
appendTextboxChange(spoiler, 30, 0, "FIND THEM ALL", f"FIND {fairy_req} OF THEM")
appendTextboxChange(spoiler, 40, 0, "RESCUED ALL THE BANANA FAIRIES", "RESCUED THE BANANA FAIRIES")
appendTextboxChange(spoiler, 40, 4, "MUST GET FAIRIES", f"MUST GET {fairy_req} FAIRIES")
def pushItemMicrohints(spoiler):
"""Push hint for the micro-hints system."""
helm_prog_items = getHelmProgItems(spoiler)
hinted_items = [
# Key = Item, Value = (Textbox index in text file 19, (all_accepted_settings))
(helm_prog_items[0], 26, [MicrohintsEnabled.base, MicrohintsEnabled.all]),
(helm_prog_items[1], 25, [MicrohintsEnabled.base, MicrohintsEnabled.all]),
(Items.Bongos, 27, [MicrohintsEnabled.all]),
(Items.Triangle, 28, [MicrohintsEnabled.all]),
(Items.Saxophone, 29, [MicrohintsEnabled.all]),
(Items.Trombone, 30, [MicrohintsEnabled.all]),
(Items.Guitar, 31, [MicrohintsEnabled.all]),
(Items.ProgressiveSlam, 33, [MicrohintsEnabled.base, MicrohintsEnabled.all]),
(Items.Cranky, 35, [MicrohintsEnabled.off, MicrohintsEnabled.base, MicrohintsEnabled.all]),
(Items.Funky, 36, [MicrohintsEnabled.off, MicrohintsEnabled.base, MicrohintsEnabled.all]),
(Items.Candy, 37, [MicrohintsEnabled.off, MicrohintsEnabled.base, MicrohintsEnabled.all]),
(Items.Snide, 38, [MicrohintsEnabled.off, MicrohintsEnabled.base, MicrohintsEnabled.all]),
]
for item_hint, item_data in enumerate(hinted_items):
if spoiler.settings.microhints_enabled in list(item_data[2]):
if ItemList[item_data[0]].name in spoiler.microhints:
data = {
"textbox_index": item_data[1],
"mode": "replace_whole",
"target": spoiler.microhints[ItemList[item_data[0]].name],
}
if 19 in spoiler.text_changes:
spoiler.text_changes[19].append(data)
else:
spoiler.text_changes[19] = [data]
def getTextRewardIndex(item) -> int:
"""Get reward index for text item."""
if item.new_item == Types.RarewareCoin:
return 5
elif item.new_item == Types.NintendoCoin:
return 6
elif item.new_item in (Types.Shop, Types.Shockwave, Types.TrainingBarrel, Types.Climbing):
return 8
elif item.new_item in (Types.Snide, Types.Cranky, Types.Candy, Types.Funky):
return 15
elif item.new_item is None:
return 14
else:
item_text_indexes = (
Types.Banana, # 0
Types.Blueprint, # 1
Types.Key, # 2
Types.Crown, # 3
Types.Fairy, # 4
Types.RarewareCoin, # 5
Types.NintendoCoin, # 6
Types.Medal, # 7
Types.Shop, # 8
Types.Kong, # 9
Types.Bean, # 10
Types.Pearl, # 11
Types.RainbowCoin, # 12
Types.FakeItem, # 13
Types.NoItem, # 14
Types.Cranky, # 15
Types.JunkItem, # 16
Types.ArchipelagoItem, # 17
)
if item.new_item in item_text_indexes:
return item_text_indexes.index(item.new_item)
return 14
def writeNullShopSlot(ROM_COPY: LocalROM, location: int):
"""Write an empty shop slot."""
ROM_COPY.seek(location)
ROM_COPY.writeMultipleBytes(MoveTypes.Nothing, 2)
ROM_COPY.writeMultipleBytes(0, 2)
ROM_COPY.writeMultipleBytes(0, 1)
ROM_COPY.writeMultipleBytes(0, 1)
def writeShopData(ROM_COPY: LocalROM, location: int, item_type: MoveTypes, flag: int, kong: int, price: int):
"""Write shop data to slot."""
ROM_COPY.seek(location)
ROM_COPY.writeMultipleBytes(item_type, 2)
ROM_COPY.writeMultipleBytes(flag, 2)
ROM_COPY.writeMultipleBytes(kong, 1)
ROM_COPY.writeMultipleBytes(price, 1)
def getHintKongFromFlag(flag: int) -> int:
"""Get the kong associated with a hint from it's flag."""
return (flag - 0x384) % 5
def setItemInWorld(ROM_COPY: LocalROM, offset: int, base_flag: int, current_flag: int):
"""Write item to world array."""
delta = current_flag - base_flag
flag_offset = delta >> 3
flag_shift = delta & 7
ROM_COPY.seek(offset + flag_offset)
raw = int.from_bytes(ROM_COPY.readBytes(1), "big")
ROM_COPY.seek(offset + flag_offset)
ROM_COPY.writeMultipleBytes(raw | (1 << flag_shift), 1)
def getActorIndex(item):
"""Get actor index from item."""
if item.new_item is None:
return actor_indexes[Types.NoItem]
elif item.new_item == Types.Blueprint:
return actor_indexes[Types.Blueprint][item.new_kong]
elif item.new_item == Types.JunkItem:
return actor_indexes[Types.JunkItem][subitems.index(item.new_subitem)]
elif item.new_item == Types.FakeItem:
trap_types = {
Items.IceTrapBubble: CustomActors.IceTrapBubble,
Items.IceTrapReverse: CustomActors.IceTrapReverse,
Items.IceTrapSlow: CustomActors.IceTrapSlow,
}
return trap_types.get(item.new_subitem, CustomActors.IceTrapBubble)
elif item.new_item in (Types.Shop, Types.Shockwave, Types.TrainingBarrel, Types.Climbing):
if (item.new_flag & 0x8000) == 0:
slot = 5
else:
slot = (item.new_flag >> 12) & 7
if item.shared or slot > 5:
slot = 5
return actor_indexes[Types.Shop][slot]
elif item.new_item == Types.Kong:
slot = 0
if item.new_flag in kong_flags:
slot = kong_flags.index(item.new_flag)
return actor_indexes[Types.Kong][slot]
elif item.new_item == Types.Hint:
return actor_indexes[Types.Hint][getHintKongFromFlag(item.new_flag)]
return actor_indexes[item.new_item]
def place_randomized_items(spoiler, original_flut: list, ROM_COPY: LocalROM):
"""Place randomized items into ROM."""
sav = spoiler.settings.rom_data
ROM_COPY.seek(sav + 0x1EC)
ROM_COPY.writeMultipleBytes(0xF0, 1)
spoiler.japes_rock_actor = 45
spoiler.aztec_vulture_actor = 45
FAST_START = spoiler.settings.fast_start_beginning_of_game
if spoiler.settings.shuffle_items:
ROM_COPY.seek(sav + 0x034)
ROM_COPY.write(1) # Item Rando Enabled
item_data = spoiler.item_assignment
model_two_items = [
0x74, # GB
0xDE, # BP - DK
0xE0, # BP - Diddy
0xE1, # BP - Lanky
0xDD, # BP - Tiny
0xDF, # BP - Chunky
0x48, # Nintendo Coin
0x28F, # Rareware Coin
0x13C, # Key
0x18D, # Crown
0x90, # Medal
0x288, # Rareware GB
0x198, # Bean
0x1B4, # Pearls
]
map_items = {}
bonus_table_offset = 0
flut_items = original_flut.copy()
pushItemMicrohints(spoiler)
pregiven_shop_owners = None
# Place first move, if fast start is off
if not FAST_START:
placed_item = spoiler.first_move_item
write_space = spoiler.settings.move_location_data + (6 * 125)
if placed_item is None:
# Is Nothing
writeNullShopSlot(ROM_COPY, write_space)
else:
prog_flags = {
Items.ProgressiveSlam: [0x3BC, 0x3BD, 0x3BE],
Items.ProgressiveAmmoBelt: [0x292, 0x293],
Items.ProgressiveInstrumentUpgrade: [0x294, 0x295, 0x296],
}
if placed_item in prog_flags:
item_flag = prog_flags[placed_item][0]
else:
item_flag = ItemList[placed_item].rando_flag
if item_flag is not None and item_flag & 0x8000:
# Is move
item_kong = (item_flag >> 12) & 7
item_subtype = (item_flag >> 8) & 0xF
if item_subtype == 7:
item_subindex = 0
else:
item_subindex = (item_flag & 0xFF) - 1
writeShopData(ROM_COPY, write_space, item_subtype, item_subindex, item_kong, 0)
else:
# Is Flagged Item
writeShopData(ROM_COPY, write_space, MoveTypes.Flag, item_flag, 0, 0)
# Go through bijection
for item in item_data:
if item.can_have_item:
# Write array data for AP
if item.new_item == Types.Medal:
if item.new_flag < (0x225 + 40):
setItemInWorld(ROM_COPY, sav + 4, 0x225, item.new_flag)
else:
# Isles Medals
setItemInWorld(ROM_COPY, sav + 9, 0x3C6, item.new_flag)
elif item.new_item == Types.Crown:
setItemInWorld(ROM_COPY, sav + 1, 0x261, item.new_flag)
elif item.new_item == Types.Pearl:
setItemInWorld(ROM_COPY, sav + 3, 0xBA, item.new_flag)
elif item.new_item == Types.Fairy:
setItemInWorld(ROM_COPY, sav + 0xA, 0x24D, item.new_flag)
# Write placement
if item.is_shop:
# Write in placement index
movespaceOffset = spoiler.settings.move_location_data
if item.location in TRAINING_LOCATIONS:
if not FAST_START:
# Add to bonus table
old_tflag = 0x182 + TRAINING_LOCATIONS.index(item.location)
ROM_COPY.seek(0x1FF1200 + (4 * bonus_table_offset))
ROM_COPY.writeMultipleBytes(old_tflag, 2)
ROM_COPY.writeMultipleBytes(getActorIndex(item), 2)
bonus_table_offset += 1
# Append to FLUT
data = [old_tflag]
if item.new_item is None:
data.append(0)
else:
data.append(item.new_flag)
flut_items.append(data)
for placement in item.placement_index:
write_space = movespaceOffset + (6 * placement)
if item.new_item is None:
# Is Nothing
# First check if there is an item here
ROM_COPY.seek(write_space)
check = int.from_bytes(ROM_COPY.readBytes(2), "big")
if check == MoveTypes.Nothing or placement >= 120: # No Item
writeNullShopSlot(ROM_COPY, write_space)
elif item.new_flag & 0x8000:
# Is Move
item_kong = (item.new_flag >> 12) & 7
item_subtype = (item.new_flag >> 8) & 0xF
if item_subtype == 7:
item_subindex = 0
else:
item_subindex = (item.new_flag & 0xFF) - 1
writeShopData(ROM_COPY, write_space, item_subtype, item_subindex, item_kong, item.price)
else:
# Is Flagged Item
subtype = MoveTypes.Flag
if item.new_item == Types.Banana:
subtype = MoveTypes.GB
elif item.new_item == Types.FakeItem:
trap_types = {
Items.IceTrapBubble: MoveTypes.IceTrapBubble,
Items.IceTrapReverse: MoveTypes.IceTrapReverse,
Items.IceTrapSlow: MoveTypes.IceTrapSlow,
}
subtype = trap_types.get(item.new_subitem, MoveTypes.IceTrapBubble)
price_var = 0
if isinstance(item.price, list):
price_var = 0
else:
price_var = item.price
writeShopData(ROM_COPY, write_space, subtype, item.new_flag, 0, price_var)
elif not item.reward_spot:
for map_id in item.placement_data:
if map_id not in map_items:
map_items[map_id] = []
if item.new_item is None:
map_items[map_id].append(
{
"id": item.placement_data[map_id],
"obj": Types.NoItem,
"kong": 0,
"flag": 0,
"upscale": 1,
"shared": False,
"subitem": 0,
}
)
else:
numerator = model_two_scales.get(item.new_item, 0.25)
denominator = model_two_scales.get(item.old_item, 0.25)
upscale = numerator / denominator
map_items[map_id].append(
{
"id": item.placement_data[map_id],
"obj": item.new_item,
"kong": item.new_kong,
"flag": item.new_flag,
"upscale": upscale,
"shared": item.shared,
"subitem": item.new_subitem,
}
)
if item.location == Locations.NintendoCoin:
spoiler.arcade_item_reward = item.new_subitem
arcade_rewards = (
Types.NoItem, # Or Nintendo Coin
Types.Bean,
Types.Blueprint,
Types.Crown,
Types.Fairy,
Types.Banana,
Types.Key,
Types.Medal,
Types.Pearl,
Types.Shop, # Handled in special case
Types.Shop, # Handled in special case
Types.Shop, # Handled in special case
Types.Shop, # Handled in special case
Types.Shop, # Handled in special case
Types.Shop, # Handled in special case
Types.Kong, # Handled in special case
Types.Kong, # Handled in special case
Types.Kong, # Handled in special case
Types.Kong, # Handled in special case
Types.Kong, # Handled in special case
Types.RainbowCoin,
Types.RarewareCoin, # Flag check handled separately
Types.JunkItem,
)
arcade_reward_index = 0
if item.new_item == Types.Kong:
if item.new_flag in kong_flags:
arcade_reward_index = kong_flags.index(item.new_flag) + 15
elif item.new_item in (Types.Shop, Types.TrainingBarrel, Types.Shockwave, Types.Climbing):
if (item.new_flag & 0x8000) == 0:
slot = 5
else:
slot = (item.new_flag >> 12) & 7
if item.shared or slot > 5:
slot = 5
arcade_reward_index = 9 + slot
elif item.new_item in arcade_rewards:
arcade_reward_index = arcade_rewards.index(item.new_item)
ROM_COPY.seek(sav + 0x110)
ROM_COPY.write(arcade_reward_index)
elif item.location == Locations.RarewareCoin:
spoiler.jetpac_item_reward = item.new_subitem
jetpac_rewards = (
Types.NoItem, # Or RW Coin
Types.Bean,
Types.Blueprint,
Types.Crown,
Types.Fairy,
Types.Banana,
Types.Key,
Types.Medal,
Types.Pearl,
Types.Shop, # Shockwave/Training/Climbing handled separately
Types.Kong,
Types.RainbowCoin,
Types.NintendoCoin, # Flag check handled separately
Types.JunkItem,
)
jetpac_reward_index = 0
if item.new_item in (Types.Shop, Types.TrainingBarrel, Types.Shockwave, Types.Climbing):
jetpac_reward_index = 9
elif item.new_item in jetpac_rewards:
jetpac_reward_index = jetpac_rewards.index(item.new_item)
ROM_COPY.seek(sav + 0x111)
ROM_COPY.write(jetpac_reward_index)
elif item.location in (Locations.ForestDonkeyBaboonBlast, Locations.CavesDonkeyBaboonBlast):
# Autocomplete bonus barrel fix
actor_index = getActorIndex(item)
ROM_COPY.seek(0x1FF1200 + (4 * bonus_table_offset))
ROM_COPY.writeMultipleBytes(item.old_flag, 2)
ROM_COPY.writeMultipleBytes(actor_index, 2)
bonus_table_offset += 1
elif item.location in (Locations.AztecTinyBeetleRace, Locations.CavesLankyBeetleRace):
text_index = getTextRewardIndex(item)
if item.location == Locations.AztecTinyBeetleRace:
ROM_COPY.seek(sav + 0x50)
else:
ROM_COPY.seek(sav + 0x51)
ROM_COPY.write(text_index)
elif item.old_item == Types.Kong:
for i in range(4):
if item.new_item is None or item.new_item == Types.NoItem:
# Write Empty Cage
ROM_COPY.seek(sav + 0x152 + (2 * i))
ROM_COPY.writeMultipleBytes(0xFF, 1)
else:
if item.old_item != Types.Medal:
actor_index = getActorIndex(item)
if item.old_item == Types.Blueprint:
# Write to BP Table
# Just needs to store an array of actors spawned
offset = (item.old_flag - 469) * 2
ROM_COPY.seek(0x1FF0E00 + offset)
ROM_COPY.writeMultipleBytes(actor_index, 2)
elif item.old_item == Types.Crown:
# Write to Crown Table
crown_flags = [0x261, 0x262, 0x263, 0x264, 0x265, 0x268, 0x269, 0x266, 0x26A, 0x267]
ROM_COPY.seek(0x1FF10C0 + (crown_flags.index(item.old_flag) * 2))
ROM_COPY.writeMultipleBytes(actor_index, 2)
elif item.old_item == Types.Key:
key_flags = [26, 74, 138, 168, 236, 292, 317, 380]
ROM_COPY.seek(0x1FF1000 + (key_flags.index(item.old_flag) * 2))
ROM_COPY.writeMultipleBytes(actor_index, 2)
elif item.old_item == Types.RainbowCoin:
index = item.location - Locations.RainbowCoin_Location00
if index < 16:
ROM_COPY.seek(0x1FF10E0 + (index * 2))
ROM_COPY.writeMultipleBytes(actor_index, 2)
else:
print("Dirt Patch Item Placement Error")
elif item.location >= Locations.MelonCrate_Location00 and item.location <= Locations.MelonCrate_Location12:
index = item.location - Locations.MelonCrate_Location00
if index < 13:
ROM_COPY.seek(0x1FF0E80 + (index * 2))
ROM_COPY.writeMultipleBytes(actor_index, 2)
else:
print("Melon Crate Item Placement Error")
elif item.location >= Locations.JapesMainEnemy_Start and item.location <= Locations.IslesMainEnemy_LowerFactoryPath1:
index = item.location - Locations.JapesMainEnemy_Start
ROM_COPY.seek(0x1FF9000 + (index * 4))
ROM_COPY.writeMultipleBytes(enemy_location_list[item.location].map, 1)
ROM_COPY.writeMultipleBytes(enemy_location_list[item.location].id, 1)
ROM_COPY.writeMultipleBytes(actor_index, 2)
elif item.old_item in (Types.Medal, Types.Hint):
# Write to Medal Table
# Just need offset of subtype:
# 0 = Banana
# 1 = BP
# 2 = Key
# 3 = Crown
# 4 = Special Coin
# 5 = Medal
# 6 = Cranky Potion
# 7 = Funky Potion
# 8 = Candy Potion
# 9 = Training Barrel
# 10 = Shockwave
# 11 = Kong
# 12 = Bean
# 13 = Pearl
# 14 = Fairy
# 15 = Rainbow Coin
# 16 = Ice Trap (Bubble)
# 17 = Junk Melon
# 18 = Cranky Item
# 19 = Funky Item
# 20 = Candy Item
# 21 = Snide Item
# 22 = Nothing
# 23 = Ice Trap (Reverse)
# 24 = Ice Trap (Slow)
slots = [
Types.Banana, # GB
Types.Blueprint, # BP
Types.Key, # Key
Types.Crown, # Crown
Types.NintendoCoin, # Special Coin
Types.Medal, # Medal
Types.Shop, # Cranky Item
Types.Shop, # Funky Item
Types.Shop, # Candy Item
Types.TrainingBarrel, # Training Move
Types.Shockwave, # Fairy Item
Types.Kong, # Kong
Types.Bean, # Bean
Types.Pearl, # Pearl
Types.Fairy, # Fairy
Types.RainbowCoin, # Rainbow Coin
Types.FakeItem, # Fake Item (Bubble)
Types.JunkItem, # Junk Item
Types.Cranky, # Cranky Item
Types.Funky, # Funky Item
Types.Candy, # Candy Item
Types.Snide, # Snide Item
None, # No Item
Types.FakeItem, # Fake Item (Reverse)
Types.FakeItem, # Fake Item (Slow)
Types.Hint, # Hint Item
Types.ArchipelagoItem, # Archipelago Item
]
offset = None
base_addr = None
if item.old_item == Types.Medal:
offset = item.old_flag - 549
if item.old_flag >= 0x3C6 and item.old_flag < 0x3CB: # Isles Medals
offset = 40 + (item.old_flag - 0x3C6)
base_addr = 0x1FF1080
elif item.old_item == Types.Hint:
offset = item.old_flag - 0x384
base_addr = 0x1FF0EC0
ROM_COPY.seek(base_addr + offset)
if item.new_item == Types.Shop:
medal_index = 6
if item.new_flag in (0x3BC, 0x3BD, 0x3BE):
medal_index = 6
elif item.new_flag in (0x292, 0x293):
medal_index = 7
elif item.new_flag in (0x294, 0x295, 0x296):
medal_index = 8
else:
subtype = (item.new_flag >> 8) & 0xF
if subtype == 4:
medal_index = 8
elif (subtype == 2) or (subtype == 3):
medal_index = 7
ROM_COPY.write(medal_index)
elif item.new_item == Types.RarewareCoin:
ROM_COPY.write(slots.index(Types.NintendoCoin))
elif item.new_item == Types.Climbing:
ROM_COPY.write(slots.index(Types.TrainingBarrel))
elif item.new_item == Types.FakeItem:
trap_types = {
Items.IceTrapBubble: 16,
Items.IceTrapReverse: 23,
Items.IceTrapSlow: 24,
}
val = trap_types.get(item.new_subitem, 16)
ROM_COPY.write(val)
else:
ROM_COPY.write(slots.index(item.new_item))
elif item.location == Locations.JapesChunkyBoulder:
# Write to Boulder Spawn Location
spoiler.japes_rock_actor = actor_index
elif item.location == Locations.AztecLankyVulture:
# Write to Vulture Spawn Location
spoiler.aztec_vulture_actor = actor_index
elif item.old_item == Types.Banana:
# Bonus GB Table
ROM_COPY.seek(0x1FF1200 + (4 * bonus_table_offset))
ROM_COPY.writeMultipleBytes(item.old_flag, 2)
ROM_COPY.writeMultipleBytes(actor_index, 2)
bonus_table_offset += 1
elif item.old_item == Types.Fairy:
# Fairy Item
if item.new_item in model_indexes:
model = model_indexes[item.new_item]
if item.new_item == Types.Shop:
if (item.new_flag & 0x8000) == 0:
slot = 5
else:
slot = (item.new_flag >> 12) & 7
if item.shared or slot > 5:
slot = 5
model = model_indexes[Types.Shop][slot]
elif item.new_item == Types.Kong:
slot = 0
if item.new_flag in kong_flags:
slot = kong_flags.index(item.new_flag)
model = model_indexes[Types.Kong][slot]
elif item.new_item == Types.FakeItem:
trap_types = {
Items.IceTrapBubble: -4,
Items.IceTrapReverse: -3,
Items.IceTrapSlow: -2,
}
model = trap_types.get(item.new_subitem, -4) + 0x10000
elif item.new_item == Types.Hint:
model = model_indexes[Types.Hint][getHintKongFromFlag(item.new_flag)]
ROM_COPY.seek(0x1FF1040 + (2 * (item.old_flag - 589)))
ROM_COPY.writeMultipleBytes(model, 2)
if item.new_item == Types.Hint:
offset = item.new_flag - 0x384
tied_region = GetRegionIdOfLocation(spoiler, item.location)
spoiler.tied_hint_regions[offset] = spoiler.RegionList[tied_region].hint_name
if not item.is_shop and item.can_have_item and item.old_item != Types.Kong:
# Write flag lookup table
data = [item.old_flag]
if item.new_item is None:
data.append(0)
else:
data.append(item.new_flag)
flut_items.append(data)
ref_index = 0
if item.new_subitem == Items.ProgressiveAmmoBelt:
ref_index = item.new_flag - 0x292
elif item.new_subitem == Items.ProgressiveInstrumentUpgrade:
ref_index = item.new_flag - 0x294
elif item.new_subitem == Items.ProgressiveSlam:
ref_index = item.new_flag - 0x3BC
setItemReferenceName(spoiler, item.new_subitem, ref_index, spoiler.LocationList[item.location].name)
# Handle pre-given shops, only ran into if shop owners are in the pool
if item.old_item in shop_owner_types:
if pregiven_shop_owners is None:
pregiven_shop_owners = []
if item.new_item in shop_owner_types:
pregiven_shop_owners.append(item.new_item)
elif item.new_item != Items.NoItem and item.new_item is not None:
raise Exception(f"Invalid item {item.new_subitem.name} placed in shopkeeper slot. This shouldn't happen.")
# Patch pre-given shops
if pregiven_shop_owners is not None: # Shop owners in pool
data = 0
or_data = {
Types.Cranky: 0x80,
Types.Funky: 0x40,
Types.Candy: 0x20,
Types.Snide: 0x10,
}
for x in or_data:
if x not in spoiler.settings.shuffled_location_types:
data |= or_data[x]
for x in pregiven_shop_owners:
data |= or_data[x]
ROM_COPY.seek(sav + 0x1EC)
ROM_COPY.writeMultipleBytes(data, 1)
# Text stuff
if spoiler.settings.item_reward_previews:
for textbox in textboxes:
new_item = textbox.default_type
flag = 379 # Rareware Coin flag for RW Coin textbox
for item in item_data:
if textbox.location == item.location:
new_item = item.new_item
flag = item.new_flag
replacement = textbox.replacement_text
if not textbox.force_pipe:
reward_text = "|"
reference = None
if new_item in text_rewards.keys():
reference = text_rewards[new_item]
if reference is not None:
# Found reference
reward_text = reference[0]
if textbox.location == Locations.GalleonDonkeySealRace:
# Use pirate text
reward_text = reference[1]
replacement = replacement.replace("|", reward_text)
data = {
"textbox_index": textbox.textbox_index,
"mode": "replace",
"search": textbox.text_replace,
"target": replacement,
}
if textbox.file_index in spoiler.text_changes:
spoiler.text_changes[textbox.file_index].append(data)
else:
spoiler.text_changes[textbox.file_index] = [data]
minor_item = "\x05FOR A FOOLISH GAME\x05"
major_item = "\x04FOR SOMETHING YOU MIGHT NEED ON YOUR QUEST\x04"
if 8 not in spoiler.text_changes:
spoiler.text_changes[8] = []
major_items = spoiler.majorItems
new_item = Items.RarewareCoin
for item in item_data:
if item.location == Locations.RarewareCoin:
new_item = item.new_subitem
placed_text = major_item if new_item in major_items else minor_item
spoiler.text_changes[8].append({"textbox_index": 0, "mode": "replace", "search": "FOR MY AMAZING SURPRISE", "target": placed_text})
# Terminate FLUT
flut_items.append([0xFFFF, 0xFFFF])
flags_to_push = []
if not FAST_START:
flags_to_push = [0x182, 0x183, 0x184, 0x185]
for flut in flut_items:
input_flag = flut[0]
if input_flag in flags_to_push:
flags_to_push = [x for x in flags_to_push if x != input_flag]
for flag in flags_to_push:
flut_items.append([flag, 0])
ROM_COPY.seek(0x1FF2000)
for flut in sorted(flut_items, key=lambda x: x[0]):
for flag in flut:
ROM_COPY.writeMultipleBytes(flag, 2)
# Setup Changes
for map_id in map_items:
cont_map_setup_address = getPointerLocation(TableNames.Setups, map_id)
ROM_COPY.seek(cont_map_setup_address)
model2_count = int.from_bytes(ROM_COPY.readBytes(4), "big")
for item in range(model2_count):
start = cont_map_setup_address + 4 + (item * 0x30)
ROM_COPY.seek(start + 0x2A)
item_id = int.from_bytes(ROM_COPY.readBytes(2), "big")
for item_slot in map_items[map_id]:
if item_slot["id"] == item_id:
ROM_COPY.seek(start + 0x28)
old_item = int.from_bytes(ROM_COPY.readBytes(2), "big")
if old_item in model_two_items:
ROM_COPY.seek(start + 0x28)
item_obj_index = 0
if item_slot["obj"] == Types.Blueprint:
item_obj_index = model_two_indexes[Types.Blueprint][item_slot["kong"]]
elif item_slot["obj"] == Types.JunkItem:
item_obj_index = model_two_indexes[Types.JunkItem][subitems.index(item_slot["subitem"])]
elif item_slot["obj"] == Types.FakeItem:
trap_types = {
Items.IceTrapBubble: 0x25D,
Items.IceTrapReverse: 0x264,
Items.IceTrapSlow: 0x265,
}
item_obj_index = trap_types.get(item_slot["subitem"], 0x25D)
elif item_slot["obj"] == Types.Shop:
if (item_slot["flag"] & 0x8000) == 0:
slot = 5
else:
slot = (item_slot["flag"] >> 12) & 7
if item_slot["shared"] or slot > 5:
slot = 5
item_obj_index = model_two_indexes[Types.Shop][slot]
elif item_slot["obj"] == Types.Kong:
slot = 0
if item_slot["flag"] in kong_flags:
slot = kong_flags.index(item_slot["flag"])
item_obj_index = model_two_indexes[Types.Kong][slot]
elif item_slot["obj"] == Types.Hint:
item_obj_index = model_two_indexes[Types.Hint][getHintKongFromFlag(item_slot["flag"])]
else:
item_obj_index = model_two_indexes[item_slot["obj"]]
ROM_COPY.writeMultipleBytes(item_obj_index, 2)
# Scaling fix
ROM_COPY.seek(start + 0xC)
old_scale = intf_to_float(int.from_bytes(ROM_COPY.readBytes(4), "big"))
new_scale = old_scale * item_slot["upscale"]
ROM_COPY.seek(start + 0xC)
ROM_COPY.writeMultipleBytes(int(float_to_hex(new_scale), 16), 4)