Files
dockipelago/worlds/dk64/__init__.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

1059 lines
62 KiB
Python

"""File specifically used for the cases of archipelago generation."""
import os
import typing
import math
import threading
import time
import json
import zipfile
import codecs
from io import BytesIO
import pkgutil
import shutil
import sys
import tempfile
from BaseClasses import Location
from worlds.dk64.ap_version import version as ap_version
baseclasses_loaded = False
try:
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification, CollectionState
import BaseClasses
import settings
baseclasses_loaded = True
except ImportError:
pass
if baseclasses_loaded:
def display_error_box(title: str, text: str) -> bool | None:
"""Display an error message box."""
from tkinter import Tk, messagebox
root = Tk()
root.withdraw()
ret = messagebox.showerror(title, text)
root.update()
def copy_dependencies(zip_path, file):
"""Copy a ZIP file from the package to a temporary directory, extracts its contents.
Ensures the temporary directory exists.
Args:
zip_path (str): The relative path to the ZIP file within the package.
Behavior:
- Creates a temporary directory if it does not exist.
- Reads the ZIP file from the package using `pkgutil.get_data`.
- Writes the ZIP file to the temporary directory if it does not already exist.
- Extracts the contents of the ZIP file into the temporary directory.
Prints:
- A message if the ZIP file could not be read.
- A message when the ZIP file is successfully copied.
- A message when the ZIP file is successfully extracted.
"""
# Create a temporary directory
temp_dir = tempfile.mkdtemp()
zip_dest = os.path.join(temp_dir, file)
try:
# Load the ZIP file from the package
zip_data = pkgutil.get_data(__name__, zip_path)
# Check if the zip already exists in the destination
if not os.path.exists(zip_dest):
if zip_data is None:
print(f"Failed to read {zip_path}")
else:
# Write the ZIP file to the destination
with open(zip_dest, "wb") as f:
f.write(zip_data)
print(f"Copied {zip_path} to {zip_dest}")
# Extract the ZIP file
with zipfile.ZipFile(zip_dest, "r") as zip_ref:
zip_ref.extractall(temp_dir)
print(f"Extracted {zip_dest} into {temp_dir}")
except PermissionError:
display_error_box("Permission Error", "Unable to install Dependencies to AP, please try to install AP as an admin.")
raise PermissionError("Permission Error: Unable to install Dependencies to AP, please try to install AP as an admin.")
# Add the temporary directory to sys.path
if temp_dir not in sys.path:
sys.path.insert(0, temp_dir)
platform_type = sys.platform
baseclasses_path = os.path.dirname(os.path.dirname(BaseClasses.__file__))
if not baseclasses_path.endswith("lib"):
baseclasses_path = os.path.join(baseclasses_path, "lib")
# Remove ANY PIL folders from the baseclasses_path
# Or Pyxdelta or pillow folders
try:
for folder in os.listdir(baseclasses_path):
if folder.startswith("PIL") or folder.startswith("pyxdelta") or folder.startswith("pillow"):
folder_path = os.path.join(baseclasses_path, folder)
if os.path.isdir(folder_path):
shutil.rmtree(folder_path)
elif os.path.isfile(folder_path):
os.remove(folder_path)
# Also if its windows.zip or linux.zip, remove it
if folder.startswith("windows.zip") or folder.startswith("linux.zip"):
os.remove(os.path.join(baseclasses_path, folder))
except Exception as e:
pass
if platform_type == "win32":
zip_path = "vendor/windows.zip" # Path inside the package
copy_dependencies(zip_path, "windows.zip")
elif platform_type == "linux":
zip_path = "vendor/linux.zip"
copy_dependencies(zip_path, "linux.zip")
else:
raise Exception(f"Unsupported platform: {platform_type}")
sys.path.append("worlds/dk64/")
sys.path.append("worlds/dk64/archipelago/")
sys.path.append("custom_worlds/dk64.apworld/dk64/")
sys.path.append("custom_worlds/dk64.apworld/dk64/archipelago/")
import randomizer.ItemPool as DK64RItemPool
from randomizer.Enums.Items import Items as DK64RItems
from randomizer.SettingStrings import decrypt_settings_string_enum
from archipelago.Items import DK64Item, full_item_table, setup_items
from archipelago.Options import DK64Options, Goal
from archipelago.Regions import all_locations, create_regions, connect_regions
from archipelago.Rules import set_rules
from archipelago.client.common import check_version
from worlds.AutoWorld import WebWorld, World, AutoLogicRegister
from archipelago.Logic import LogicVarHolder, logic_item_name_to_id
from randomizer.Spoiler import Spoiler
from randomizer.Settings import Settings
from randomizer.ShuffleWarps import LinkWarps
from randomizer.Enums.Settings import LogicType, ShuffleLoadingZones
from randomizer.Patching.ApplyRandomizer import patching_response
from version import version
from randomizer.Patching.EnemyRando import randomize_enemies_0
from randomizer.Fill import ShuffleItems, Generate_Spoiler, IdentifyMajorItems
from randomizer.CompileHints import compileMicrohints
from archipelago.Hints import CompileArchipelagoHints
from randomizer.Enums.Types import Types, BarrierItems
from randomizer.Enums.Kongs import Kongs
from randomizer.Enums.Levels import Levels
from randomizer.Enums.Maps import Maps
from randomizer.Enums.Locations import Locations as DK64RLocations
from randomizer.Enums.Settings import WinConditionComplex, SwitchsanityLevel, GlitchesSelected, MicrohintsEnabled, HardModeSelected, RemovedBarriersSelected, ItemRandoListSelected
from randomizer.Enums.Switches import Switches
from randomizer.Enums.SwitchTypes import SwitchType
from randomizer.Lists import Item as DK64RItem
from randomizer.Lists.Switches import SwitchInfo
from worlds.LauncherComponents import Component, components, Type, icon_paths
import randomizer.ShuffleExits as ShuffleExits
from Utils import open_filename
import shutil
import zlib
boss_map_names = {
Maps.JapesBoss: "Army Dillo 1",
Maps.AztecBoss: "Dogadon 1",
Maps.FactoryBoss: "Mad Jack",
Maps.GalleonBoss: "Pufftoss",
Maps.FungiBoss: "Dogadon 2",
Maps.CavesBoss: "Army Dillo 2",
Maps.CastleBoss: "King Kut Out",
Maps.KroolDonkeyPhase: "DK Phase",
Maps.KroolDiddyPhase: "Diddy Phase",
Maps.KroolLankyPhase: "Lanky Phase",
Maps.KroolTinyPhase: "Tiny Phase",
Maps.KroolChunkyPhase: "Chunky Phase",
}
def crc32_of_file(file_path):
"""Compute CRC32 checksum of a file."""
crc_value = 0
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
crc_value = zlib.crc32(chunk, crc_value)
return f"{crc_value & 0xFFFFFFFF:08X}" # Convert to 8-character hex
def launch_client():
"""Launch the DK64 client."""
from archipelago.DK64Client import launch
from worlds.LauncherComponents import launch as launch_component
launch_component(launch, name="DK64 Client")
components.append(Component("DK64 Client", "DK64Client", func=launch_client, component_type=Type.CLIENT, icon="dk64"))
icon_paths["dk64"] = f"ap:{__name__}/base-hack/assets/DKTV/logo3.png"
class DK64CollectionState(metaclass=AutoLogicRegister):
"""Logic Mixin to handle some awkward situations when the CollectionState is copied."""
def init_mixin(self, parent: MultiWorld):
"""Reset the logic holder in all DK64 worlds. This is called on every CollectionState init."""
dk64_ids = parent.get_game_players(DK64World.game) + parent.get_game_groups(DK64World.game)
self.dk64_logic_holder = {}
for player in dk64_ids:
if hasattr(parent.worlds[player], "spoiler"):
self.dk64_logic_holder[player] = LogicVarHolder(parent.worlds[player].spoiler, player) # If we don't reset here, we double-collect the starting inventory
def copy_mixin(self, ret) -> CollectionState:
"""Update the current logic holder in all DK64 worlds with the current CollectionState. This is called after the CollectionState init inside the copy() method, so this essentially undoes the above method."""
dk64_ids = ret.multiworld.get_game_players(DK64World.game) + ret.multiworld.get_game_groups(DK64World.game)
for player in dk64_ids:
if player in ret.dk64_logic_holder.keys():
ret.dk64_logic_holder[player].UpdateFromArchipelagoItems(ret) # If we don't update here, every copy wipes the logic holder's knowledge
else:
if hasattr(ret.multiworld.worlds[player], "spoiler"):
print("Hey")
return ret
class DK64Settings(settings.Group):
"""Settings for the DK64 randomizer."""
class ReleaseVersion(str):
"""Choose the release version of the DK64 randomizer to use.
By setting it to master (Default) you will always pull the latest stable version.
By setting it to dev you will pull the latest development version.
If you want a specific version, you can set it to a AP version number eg: v1.0.45
"""
release_branch: ReleaseVersion = ReleaseVersion("master")
class DK64Web(WebWorld):
"""WebWorld for DK64."""
theme = "jungle"
setup_en = Tutorial("Multiworld Setup Guide", "A guide to setting up the Donkey Kong 64 randomizer connected to an Archipelago Multiworld.", "English", "setup_en.md", "setup/en", ["PoryGone"])
tutorials = [setup_en]
class DK64World(World):
"""Donkey Kong 64 is a 3D collectathon platforming game.
Play as the whole DK Crew and rescue the Golden Banana hoard from King K. Rool.
"""
game: str = "Donkey Kong 64"
options_dataclass = DK64Options
options: DK64Options
topology_present = False
settings: typing.ClassVar[DK64Settings]
item_name_to_id = {name: data.code for name, data in full_item_table.items()}
location_name_to_id = all_locations
web = DK64Web()
def __init__(self, multiworld: MultiWorld, player: int):
"""Initialize the DK64 world."""
self.rom_name_available_event = threading.Event()
self.hint_data_available = threading.Event()
super().__init__(multiworld, player)
@classmethod
def stage_assert_generate(cls, multiworld: MultiWorld):
"""Assert the stage and generate the world."""
# Check if dk64.z64 exists, if it doesn't prompt the user to provide it
# ANd then we will copy it to the root directory
crc_values = ["D44B4FC6"]
rom_file = "dk64.z64"
if not os.path.exists(rom_file):
print("Please provide a DK64 ROM file.")
file = open_filename("Select DK64 ROM", (("N64 ROM", (".z64", ".n64")),))
if not file:
raise FileNotFoundError("No ROM file selected.")
crc = crc32_of_file(file)
print(f"CRC32: {crc}")
if crc not in crc_values:
print("Invalid DK64 ROM file, please make sure your ROM is big endian.")
raise FileNotFoundError("Invalid DK64 ROM file, please make sure your ROM is a vanilla DK64 file in big endian.")
# Copy the file to the root directory
try:
shutil.copy(file, rom_file)
except Exception as e:
raise FileNotFoundError(f"Failed to copy ROM file, this may be a permissions issue: {e}")
else:
crc = crc32_of_file(rom_file)
print(f"CRC32: {crc}")
if crc not in crc_values:
print("Invalid DK64 ROM file, please make sure your ROM is big endian.")
raise FileNotFoundError("Invalid DK64 ROM file, please make sure your ROM is a vanilla DK64 file in big endian.")
check_version()
def _get_slot_data(self):
"""Get the slot data."""
return {
# "death_link": self.options.death_link.value,
}
def generate_early(self):
"""Generate the world."""
# V1 LIMITATION: We are restricting settings pretty heavily. This string serves as the base for all seeds, with AP options overriding some options
self.settings_string = "fjNPxAMxDIUx0QSpbHPUlZlBLg5gPQ+oBwRDIhKlsa58Iz8fiNEpEtiFKi4bVAhMF6AAd+AAOCAAGGAAGKAAAdm84FBiMhjoStwFIKW2wLcBJIBpmTVRCjFIKUUwGTLK/BQBuAIMAN4CBwBwAYQAOIECQByAoUAOYGCwB0A4YeXIITIagOrIrwAZTiU1QwkoSjuq1ZLEjQxUKi2oy9FRFgETEUAViyxyN2S8XeRQOQ7GXtOQM8nGDIAyqcEQgAFwoAFwwAEw4AExAAD1oADxIACxQABxYADxgACxoAB1wAFp8r0CS5UtnsshhHMk9Gw+M1drAwGcuqwqis0FMqLRjilACgrBovKATiotEkXENPGtLINIiNdHYAHQC8KggJCgsMDQ4QERIUFRYYGRocHR4gISIjJCUmJygpKissLS4vMDEyMzQ1rL4AwADCAMQAnQCyAGkAUQA"
settings_dict = decrypt_settings_string_enum(self.settings_string)
settings_dict["archipelago"] = True
settings_dict["starting_kongs_count"] = self.options.starting_kong_count.value
settings_dict["open_lobbies"] = self.options.open_lobbies.value
settings_dict["krool_in_boss_pool"] = self.options.krool_in_boss_pool.value
settings_dict["helm_phase_count"] = self.options.helm_phase_count.value
settings_dict["krool_phase_count"] = self.options.krool_phase_count.value
settings_dict["medal_cb_req"] = self.options.medal_cb_req.value
settings_dict["randomize_blocker_required_amounts"] = self.options.randomize_blocker_required_amounts.value
settings_dict["blocker_text"] = self.options.blocker_max.value
settings_dict["mermaid_gb_pearls"] = self.options.mermaid_gb_pearls.value
blocker_options = [
self.options.level1_blocker,
self.options.level2_blocker,
self.options.level3_blocker,
self.options.level4_blocker,
self.options.level5_blocker,
self.options.level6_blocker,
self.options.level7_blocker,
self.options.level8_blocker,
]
for i, blocker in enumerate(blocker_options):
settings_dict[f"blocker_{i}"] = blocker.value
settings_dict["item_rando_list_selected"] = []
always_enabled_categories = [
ItemRandoListSelected.shop,
ItemRandoListSelected.banana,
ItemRandoListSelected.toughbanana,
ItemRandoListSelected.crown,
ItemRandoListSelected.blueprint,
ItemRandoListSelected.key,
ItemRandoListSelected.medal,
ItemRandoListSelected.nintendocoin,
ItemRandoListSelected.kong,
ItemRandoListSelected.fairy,
ItemRandoListSelected.rainbowcoin,
ItemRandoListSelected.beanpearl,
ItemRandoListSelected.junkitem,
ItemRandoListSelected.crateitem,
ItemRandoListSelected.rarewarecoin,
ItemRandoListSelected.shockwave,
]
settings_dict["item_rando_list_selected"].extend(always_enabled_categories)
if self.options.hints_in_item_pool.value:
settings_dict["item_rando_list_selected"].append(ItemRandoListSelected.hint)
settings_dict["medal_requirement"] = self.options.medal_requirement.value
settings_dict["rareware_gb_fairies"] = self.options.rareware_gb_fairies.value
settings_dict["mirror_mode"] = self.options.mirror_mode.value
if hasattr(self.multiworld, "generation_is_fake") and hasattr(self.multiworld, "re_gen_passthrough") and "Donkey Kong 64" in self.multiworld.re_gen_passthrough:
settings_dict["hard_shooting"] = self.multiworld.re_gen_passthrough["Donkey Kong 64"]["HardShooting"]
else:
settings_dict["hard_shooting"] = self.options.hard_shooting.value
settings_dict["hard_mode"] = self.options.hard_mode.value
settings_dict["hard_mode_selected"] = []
for hard in self.options.hard_mode_selected:
if hard == "hard_enemies":
settings_dict["hard_mode_selected"].append(HardModeSelected.hard_enemies)
elif hard == "shuffled_jetpac_enemies":
settings_dict["hard_mode_selected"].append(HardModeSelected.shuffled_jetpac_enemies)
elif hard == "strict_helm_timer":
settings_dict["hard_mode_selected"].append(HardModeSelected.strict_helm_timer)
elif hard == "donk_in_the_dark_world":
settings_dict["hard_mode_selected"].append(HardModeSelected.donk_in_the_dark_world)
elif hard == "donk_in_the_sky":
settings_dict["hard_mode_selected"].append(HardModeSelected.donk_in_the_sky)
settings_dict["krool_key_count"] = self.options.krool_key_count.value
if hasattr(self.multiworld, "generation_is_fake"):
settings_dict["krool_key_count"] = 8 # if gen is fake, don't pick random keys to start with, trust the slot data
settings_dict["switchsanity"] = self.options.switchsanity.value
settings_dict["logic_type"] = self.options.logic_type.value
settings_dict["remove_barriers_enabled"] = bool(self.options.remove_barriers_selected)
settings_dict["remove_barriers_selected"] = []
for barrier in self.options.remove_barriers_selected:
if barrier == "japes_coconut_gates":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.japes_coconut_gates)
elif barrier == "japes_shellhive_gate":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.japes_shellhive_gate)
elif barrier == "aztec_tunnel_door":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.aztec_tunnel_door)
elif barrier == "aztec_5dtemple_switches":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.aztec_5dtemple_switches)
elif barrier == "aztec_llama_switches":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.aztec_llama_switches)
elif barrier == "aztec_tiny_temple_ice":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.aztec_tiny_temple_ice)
elif barrier == "factory_testing_gate":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.factory_testing_gate)
elif barrier == "factory_production_room":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.factory_production_room)
elif barrier == "galleon_lighthouse_gate":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.galleon_lighthouse_gate)
elif barrier == "galleon_shipyard_area_gate":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.galleon_shipyard_area_gate)
elif barrier == "castle_crypt_doors":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.castle_crypt_doors)
elif barrier == "galleon_seasick_ship":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.galleon_seasick_ship)
elif barrier == "forest_green_tunnel":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.forest_green_tunnel)
elif barrier == "forest_yellow_tunnel":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.forest_yellow_tunnel)
elif barrier == "caves_igloo_pads":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.caves_igloo_pads)
elif barrier == "caves_ice_walls":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.caves_ice_walls)
elif barrier == "galleon_treasure_room":
settings_dict["remove_barriers_selected"].append(RemovedBarriersSelected.galleon_treasure_room)
settings_dict["glitches_selected"] = []
for glitch in self.options.glitches_selected:
if glitch == "advanced_platforming":
settings_dict["glitches_selected"].append(GlitchesSelected.advanced_platforming)
elif glitch == "moonkicks":
settings_dict["glitches_selected"].append(GlitchesSelected.moonkicks)
elif glitch == "phase_swimming":
settings_dict["glitches_selected"].append(GlitchesSelected.phase_swimming)
elif glitch == "swim_through_shores":
settings_dict["glitches_selected"].append(GlitchesSelected.swim_through_shores)
elif glitch == "troff_n_scoff_skips":
settings_dict["glitches_selected"].append(GlitchesSelected.troff_n_scoff_skips)
elif glitch == "moontail":
settings_dict["glitches_selected"].append(GlitchesSelected.moontail)
settings_dict["starting_keys_list_selected"] = []
for item in self.options.start_inventory:
if item == "Key 1":
settings_dict["starting_keys_list_selected"].append(DK64RItems.JungleJapesKey)
elif item == "Key 2":
settings_dict["starting_keys_list_selected"].append(DK64RItems.AngryAztecKey)
elif item == "Key 3":
settings_dict["starting_keys_list_selected"].append(DK64RItems.FranticFactoryKey)
elif item == "Key 4":
settings_dict["starting_keys_list_selected"].append(DK64RItems.GloomyGalleonKey)
elif item == "Key 5":
settings_dict["starting_keys_list_selected"].append(DK64RItems.FungiForestKey)
elif item == "Key 6":
settings_dict["starting_keys_list_selected"].append(DK64RItems.CrystalCavesKey)
elif item == "Key 7":
settings_dict["starting_keys_list_selected"].append(DK64RItems.CreepyCastleKey)
elif item == "Key 8":
settings_dict["starting_keys_list_selected"].append(DK64RItems.HideoutHelmKey)
if self.options.goal == Goal.option_all_keys:
settings_dict["win_condition_item"] = WinConditionComplex.req_key
settings_dict["win_condition_count"] = 8
if self.options.goal == Goal.option_dk_rap:
settings_dict["win_condition_item"] = WinConditionComplex.dk_rap_items
settings = Settings(settings_dict, self.random)
# Archipelago really wants the number of locations to match the number of items. Keep track of how many locations we've made here
settings.location_pool_size = 0
# Set all the static slot data that UT needs to know. Most of these would have already been decided in normal generation by now, so they are just overwritten here.
if hasattr(self.multiworld, "generation_is_fake"):
if hasattr(self.multiworld, "re_gen_passthrough"):
if "Donkey Kong 64" in self.multiworld.re_gen_passthrough:
passthrough = self.multiworld.re_gen_passthrough["Donkey Kong 64"]
settings.level_order = passthrough["LevelOrder"]
# Switch logic lifted out of level shuffle due to static levels for UT
if settings.alter_switch_allocation:
allocation = [1, 1, 1, 1, 2, 2, 3, 3]
for x in range(8):
level = settings.level_order[x + 1]
settings.switch_allocation[level] = allocation[x]
settings.starting_kong_list = passthrough["StartingKongs"]
settings.starting_kong = settings.starting_kong_list[0] # fake a starting kong so that we don't force a different kong
settings.medal_requirement = passthrough["JetpacReq"]
settings.rareware_gb_fairies = passthrough["FairyRequirement"]
settings.BLockerEntryItems = passthrough["BLockerEntryItems"]
settings.BLockerEntryCount = passthrough["BLockerEntryCount"]
settings.medal_cb_req = passthrough["MedalCBRequirement"]
settings.mermaid_gb_pearls = passthrough["MermaidPearls"]
settings.BossBananas = passthrough["BossBananas"]
settings.boss_maps = passthrough["BossMaps"]
settings.boss_kongs = passthrough["BossKongs"]
settings.lanky_freeing_kong = passthrough["LankyFreeingKong"]
settings.helm_order = passthrough["HelmOrder"]
settings.logic_type = LogicType[passthrough["LogicType"]]
settings.glitches_selected = passthrough["GlitchesSelected"]
settings.open_lobbies = passthrough["OpenLobbies"]
settings.starting_key_list = passthrough["StartingKeyList"]
# There's multiple sources of truth for helm order.
settings.helm_donkey = 0 in settings.helm_order
settings.helm_diddy = 4 in settings.helm_order
settings.helm_lanky = 3 in settings.helm_order
settings.helm_tiny = 2 in settings.helm_order
settings.helm_chunky = 1 in settings.helm_order
# Switchsanity
for switch, data in passthrough["SwitchSanity"].items():
needed_kong = Kongs[data["kong"]]
switch_type = SwitchType[data["type"]]
settings.switchsanity_data[Switches[switch]] = SwitchInfo(switch, needed_kong, switch_type, 0, 0, [])
for loc in passthrough["JunkedLocations"]:
del self.location_name_to_id[loc]
# We need to set the freeing kongs here early, as they won't get filled in any other part of the AP process
settings.diddy_freeing_kong = self.random.randint(0, 4)
# Lanky freeing kong actually changes logic, so UT should use the slot data rather than genning a new one.
if not hasattr(self.multiworld, "generation_is_fake"):
settings.lanky_freeing_kong = self.random.randint(0, 4)
settings.tiny_freeing_kong = self.random.randint(0, 4)
settings.chunky_freeing_kong = self.random.randint(0, 4)
self.spoiler = Spoiler(settings)
# Undo any changes to this location's name, until we find a better way to prevent this from confusing the tracker and the AP code that is responsible for sending out items
self.spoiler.LocationList[DK64RLocations.FactoryDonkeyDKArcade].name = "Factory Donkey DK Arcade Round 1"
self.spoiler.settings.shuffled_location_types.append(Types.ArchipelagoItem)
for item in self.options.start_inventory:
item_obj = DK64RItem.ItemList[logic_item_name_to_id.get(item)]
if item_obj.type not in [Types.Key, Types.Shop, Types.Shockwave, Types.TrainingBarrel, Types.Climbing]:
# Ensure that the items in the start inventory are only keys, shops, shockwaves, training barrels or climbing items
raise ValueError(f"Invalid item type for starting inventory: {item}. Starting inventory can only contain keys or moves.")
Generate_Spoiler(self.spoiler)
# Handle Loading Zones - this will handle LO and (someday?) LZR appropriately
if self.spoiler.settings.shuffle_loading_zones != ShuffleLoadingZones.none:
# UT should not reshuffle the level order, but should update the exits
if not hasattr(self.multiworld, "generation_is_fake"):
ShuffleExits.ExitShuffle(self.spoiler, skip_verification=True)
self.spoiler.UpdateExits()
# Handle hint preparation by initiating some variables
self.hint_data = {
"kong": [],
"key": [],
"woth": [],
"major": [],
"deep": [],
}
self.foreignMicroHints = {}
# Handle locations that start empty due to being junk
self.junked_locations = []
def create_regions(self) -> None:
"""Create the regions."""
create_regions(self.multiworld, self.player, self.spoiler)
def create_items(self) -> None:
"""Create the items."""
itempool: typing.List[DK64Item] = setup_items(self)
self.multiworld.itempool += itempool
def get_filler_item_name(self) -> str:
"""Get the filler item name."""
return DK64RItem.ItemList[DK64RItems.JunkMelon].name
def set_rules(self):
"""Set the rules."""
set_rules(self.multiworld, self.player)
def generate_basic(self):
"""Generate the basic world."""
LinkWarps(self.spoiler) # I am very skeptical that this works at all - must be resolved if we want to do more than Isles warps preactivated
connect_regions(self, self.spoiler.settings)
self.multiworld.get_location("Banana Hoard", self.player).place_locked_item(DK64Item("Banana Hoard", ItemClassification.progression_skip_balancing, 0xD64060, self.player)) # TEMP?
def generate_output(self, output_directory: str):
"""Generate the output."""
try:
spoiler = self.spoiler
spoiler.settings.archipelago = True
spoiler.settings.random = self.random
spoiler.settings.player_name = self.multiworld.get_player_name(self.player)
spoiler.first_move_item = None # Not relevant with Fast Start always enabled
spoiler.pregiven_items = []
for item in self.multiworld.precollected_items[self.player]:
dk64_item = logic_item_name_to_id[item.name]
# Only moves can be pushed to the pregiven_items list
if DK64RItem.ItemList[dk64_item].type in [Types.Shop, Types.Shockwave, Types.TrainingBarrel, Types.Climbing]:
spoiler.pregiven_items.append(dk64_item)
local_trap_count = 0
ap_item_is_major_item = False
self.junked_locations = []
# Read through all item assignments in this AP world and find their DK64 equivalents so we can update our world state for patching purposes
for ap_location in self.multiworld.get_locations(self.player):
# We never need to place Collectibles or Events in our world state
if "Collectible" in ap_location.name or "Event" in ap_location.name:
continue
# Find the corresponding DK64 Locations enum
dk64_location_id = None
for dk64_loc_id, dk64_loc in spoiler.LocationList.items():
if dk64_loc.name == ap_location.name:
dk64_location_id = dk64_loc_id
break
if dk64_location_id is not None and ap_location.item is not None:
ap_item = ap_location.item
# Any item that isn't for this player is placed as an AP item, regardless of whether or not it could be a DK64 item
if ap_item.player != self.player:
spoiler.LocationList[dk64_location_id].PlaceItem(spoiler, DK64RItems.ArchipelagoItem)
# If Jetpac has an progression AP item, we should hint is as if it were a major item
if dk64_location_id == DK64RLocations.RarewareCoin and ap_item.advancement:
ap_item_is_major_item = True
# Collectibles don't get placed in the LocationList
elif "Collectible" in ap_item.name:
continue
else:
dk64_item = logic_item_name_to_id[ap_item.name]
if dk64_item is not None:
if dk64_item in [DK64RItems.IceTrapBubble, DK64RItems.IceTrapReverse, DK64RItems.IceTrapSlow]:
local_trap_count += 1
dk64_location = spoiler.LocationList[dk64_location_id]
# Most of these item restrictions should be handled by item rules, so this is a failsafe.
# Junk items can't be placed in shops, bosses, or arenas. Fortunately this is junk, so we can just patch a NoItem there instead.
# Shops are allowed to get Junk items placed by AP in order to artificially slightly reduce the number of checks in shops.
if DK64RItem.ItemList[dk64_item].type == Types.JunkItem and (dk64_location.type in [Types.Shop, Types.Key, Types.Crown]):
dk64_item = DK64RItems.NoItem
self.junked_locations.append(ap_location.name)
# Blueprints can't be on fairies for technical reasons. Instead we'll patch it in as an AP item and have AP handle it.
if dk64_item in DK64RItemPool.Blueprints() and dk64_location.type == Types.Fairy:
dk64_item = DK64RItems.ArchipelagoItem
# Track explicit "No Item" placements
elif ap_item.name == "No Item":
self.junked_locations.append(ap_location.name)
spoiler.LocationList[dk64_location_id].PlaceItem(spoiler, dk64_item)
else:
print(f"Item {ap_item.name} not found in DK64 item table.")
elif dk64_location_id is not None:
spoiler.LocationList[dk64_location_id].PlaceItem(spoiler, DK64RItems.NoItem)
else:
print(f"Location {ap_location.name} not found in DK64 location table.")
spoiler.settings.ice_trap_count = local_trap_count
ShuffleItems(spoiler)
spoiler.UpdateLocations(spoiler.LocationList)
self.updateBossKongs(spoiler)
compileMicrohints(spoiler)
# Could add a hints on/off setting?
microhints_enabled = True
hints_enabled = True
if hints_enabled or microhints_enabled:
self.hint_data_available.wait()
if hints_enabled:
CompileArchipelagoHints(self, self.hint_data)
if microhints_enabled:
# Finalize microhints
shopkeepers = [DK64RItems.Candy, DK64RItems.Cranky, DK64RItems.Funky, DK64RItems.Snide]
helm_prog_items = [DK64RItems.BaboonBlast, DK64RItems.BaboonBalloon, DK64RItems.Monkeyport, DK64RItems.GorillaGrab, DK64RItems.ChimpyCharge, DK64RItems.GorillaGone]
instruments = [DK64RItems.Bongos, DK64RItems.Guitar, DK64RItems.Trombone, DK64RItems.Saxophone, DK64RItems.Triangle]
hinted_slams = []
if DK64RItems.ProgressiveSlam in self.foreignMicroHints.keys() and DK64RItems.ProgressiveSlam in self.spoiler.microhints:
# Break down the slam hint to retrieve raw data
text1 = "Ladies and Gentlemen! It appears that one fighter has come unequipped to properly handle this reptilian beast. Perhaps they should have looked in "
hinted_slams = self.spoiler.microhints[DK64RItems.ProgressiveSlam].replace(text1, "")
hinted_slams.replace(" for the elusive slam.", "")
hinted_slams.split(" or ")
for hintedItem in self.foreignMicroHints.keys():
text = ""
if hintedItem in instruments or hintedItem in helm_prog_items:
text = f"\x07{self.foreignMicroHints[hintedItem][0]}\x07 would be better off looking in \x07{self.foreignMicroHints[hintedItem][1]}\x07 for this.".upper()
elif hintedItem == DK64RItems.ProgressiveSlam:
for slam in self.foreignMicroHints[DK64RItems.ProgressiveSlam]:
hinted_slams.append(f"\x07{slam[0]}: {slam[1]}\x07")
slam_text = " or ".join(hinted_slams)
text = f"Ladies and Gentlemen! It appears that one fighter has come unequipped to properly handle this reptilian beast. Perhaps they should have looked in {slam_text} for the elusive slam.".upper()
elif hintedItem in shopkeepers:
text = f"{hintedItem.name} has gone on a space mission to \x07{self.foreignMicroHints[hintedItem][0]} {self.foreignMicroHints[hintedItem][1]}\x07.".upper()
for letter in text:
if letter not in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?:;'S-()% \x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d":
text = text.replace(letter, " ")
self.spoiler.microhints[DK64RItem.ItemList[hintedItem].name] = text
spoiler.majorItems = IdentifyMajorItems(spoiler)
if ap_item_is_major_item:
spoiler.majorItems.append(DK64RItems.ArchipelagoItem)
patch_data, _ = patching_response(spoiler)
patch_file = self.update_seed_results(patch_data, spoiler, self.player)
out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.lanky")
print(out_path)
# with open("output/" + f"{self.multiworld.get_out_file_name_base(self.player)}.lanky", "w") as f:
with open(out_path, "w") as f:
f.write(patch_file)
# Copy the patch file to the outpath
# shutil.copy("output/" + f"{self.multiworld.get_out_file_name_base(self.player)}.lanky", out_path)
# Clear the path_data out of memory to flush memory usage
del patch_data
except Exception:
raise
finally:
self.rom_name_available_event.set() # make sure threading continues and errors are collected
@classmethod
def stage_generate_output(cls, multiworld: MultiWorld, output_directory: str):
"""Prepare hint data."""
# Microhint stuff
microHintItemNames = {
"Progressive Slam": DK64RItems.ProgressiveSlam,
"Bongos": DK64RItems.Bongos,
"Guitar": DK64RItems.Guitar,
"Trombone": DK64RItems.Trombone,
"Saxophone": DK64RItems.Saxophone,
"Triangle": DK64RItems.Triangle,
"Baboon Blast": DK64RItems.BaboonBlast,
"Baboon Balloon": DK64RItems.BaboonBalloon,
"Monkeyport": DK64RItems.Monkeyport,
"Gorilla Grab": DK64RItems.GorillaGrab,
"Chimpy Charge": DK64RItems.ChimpyCharge,
"Gorilla Gone": DK64RItems.GorillaGone,
"Candy": DK64RItems.Candy,
"Cranky": DK64RItems.Cranky,
"Funky": DK64RItems.Funky,
"Snide": DK64RItems.Snide,
}
shopkeepers = [DK64RItems.Candy, DK64RItems.Cranky, DK64RItems.Funky, DK64RItems.Snide]
helm_prog_items = [DK64RItems.BaboonBlast, DK64RItems.BaboonBalloon, DK64RItems.Monkeyport, DK64RItems.GorillaGrab, DK64RItems.ChimpyCharge, DK64RItems.GorillaGone]
instruments = [DK64RItems.Bongos, DK64RItems.Guitar, DK64RItems.Trombone, DK64RItems.Saxophone, DK64RItems.Triangle]
microhint_categories = {
MicrohintsEnabled.off: shopkeepers.copy(),
MicrohintsEnabled.base: helm_prog_items.copy() + [DK64RItems.ProgressiveSlam] + shopkeepers.copy(),
MicrohintsEnabled.all: helm_prog_items.copy() + instruments.copy() + shopkeepers.copy() + [DK64RItems.ProgressiveSlam],
}
# Hint stuff
try:
# Get players that have hints enabled.
players = {autoworld.player for autoworld in multiworld.get_game_worlds("Donkey Kong 64")}
# Locations that could get a "deep locations" hint:
deep_location_names = [
"Returning the Banana Fairies",
"Japes Diddy Minecart",
"Aztec Diddy Vulture Race",
"Aztec Tiny Beetle Race",
"Factory Donkey DK Arcade Round 1",
"Forest Chunky Minecart",
"Forest Donkey Baboon Blast",
"Forest Diddy Owl Race",
"Forest Lanky Rabbit Race",
"Caves Donkey Baboon Blast",
"Caves Lanky Beetle Race",
"Castle Donkey Minecart",
"Forest Donkey Mushroom Cannons",
"Isles Battle Arena 2 (Fungi Lobby: Gorilla Gone Box)",
"Isles Diddy Summit Barrel",
"Helm Battle Arena (Top of Blast-o-Matic)",
"Helm Donkey Medal",
"Helm Chunky Medal",
"Helm Tiny Medal",
"Helm Lanky Medal",
"Helm Diddy Medal",
"Helm Fairy (Key 8 Room (1))",
"Helm Fairy (Key 8 Room (2))",
"Galleon Diddy Mechfish",
"Jetpac",
"Aztec Tiny Llama Temple Lava Pedestals",
"Galleon Chunky Cannon Game",
"Galleon Tiny Medal",
"Factory Chunky Toy Monster",
"Castle Tiny Car Race",
"Caves Dirt: Giant Kosha",
"Castle Lanky Tower",
"Castle Donkey Tree Sniping",
"Japes Boss Defeated",
"Aztec Boss Defeated",
"Factory Boss Defeated",
"Galleon Boss Defeated",
"Forest Boss Defeated",
"Caves Boss Defeated",
"Castle Boss Defeated",
]
# Look through every location in the multiworld and find all the DK64 items that are progression
for loc in [location for location in multiworld.get_locations() if not location.is_event]:
player = loc.item.player
autoworld = multiworld.worlds[player]
locworld = multiworld.worlds[loc.player]
if players:
if loc.item.name in ("Donkey", "Diddy", "Lanky", "Tiny", "Chunky") and player in players:
autoworld.hint_data["kong"].append(loc)
if loc.item.name in ("Key 1", "Key 2", "Key 4", "Key 5") and player in players:
autoworld.hint_data["key"].append(loc)
if loc.player in players and loc.name in deep_location_names:
locworld.hint_data["deep"].append(loc)
if player in players and autoworld.isMajorItem(loc.item) and (not autoworld.spoiler.settings.key_8_helm or loc.name != "The End of Helm"):
autoworld.hint_data["major"].append(loc)
# Skip item at location and see if game is still beatable
state = CollectionState(multiworld)
state.locations_checked.add(loc)
if not multiworld.can_beat_game(state):
autoworld.hint_data["woth"].append(loc)
# Also gather any information on microhinted items
if player in players and loc.item.name in microHintItemNames and microHintItemNames[loc.item.name] in microhint_categories[autoworld.spoiler.settings.microhints_enabled]:
if player != loc.player:
if microHintItemNames[loc.item.name] in autoworld.foreignMicroHints.keys():
autoworld.foreignMicroHints[microHintItemNames[loc.item.name]].append([multiworld.get_player_name(loc.player), loc.name[:80]])
else:
autoworld.foreignMicroHints[microHintItemNames[loc.item.name]] = [multiworld.get_player_name(loc.player), loc.name[:80]]
except Exception as e:
raise e
finally:
for autoworld in multiworld.get_game_worlds("Donkey Kong 64"):
autoworld.hint_data_available.set()
def update_seed_results(self, patch, spoiler, player_id):
"""Update the seed results."""
timestamp = time.time()
hash = spoiler.settings.seed_hash
spoiler_log = {}
spoiler_log["Generated Time"] = timestamp
spoiler_log["Settings"] = {}
spoiler_log["Cosmetics"] = {}
# Zip all the data into a single file.
zip_data = BytesIO()
with zipfile.ZipFile(zip_data, "w") as zip_file:
# Write each variable to the zip file
zip_file.writestr("patch", patch)
zip_file.writestr("hash", str(hash))
zip_file.writestr("spoiler_log", str(json.dumps(spoiler_log)))
zip_file.writestr("generated_time", str(timestamp))
zip_file.writestr("version", version)
zip_file.writestr("seed_number", self.multiworld.get_out_file_name_base(self.player))
zip_file.writestr("seed_id", self.multiworld.get_out_file_name_base(self.player))
zip_data.seek(0)
# Convert the zip to a string of base64 data
zip_conv = codecs.encode(zip_data.getvalue(), "base64").decode()
return zip_conv
def modify_multidata(self, multidata: dict):
"""Modify the multidata."""
pass
def fill_slot_data(self) -> dict:
"""Fill the slot data."""
return {
"Goal": self.options.goal.value,
"ClimbingShuffle": self.options.climbing_shuffle.value,
"PlayerNum": self.player,
"death_link": self.options.death_link.value,
"ring_link": self.options.ring_link.value,
"tag_link": self.options.tag_link.value,
"receive_notifications": self.options.receive_notifications.value,
"LevelOrder": ", ".join([level.name for order, level in self.spoiler.settings.level_order.items()]),
"StartingKongs": ", ".join([kong.name for kong in self.spoiler.settings.starting_kong_list]),
"ForestTime": self.spoiler.settings.fungi_time_internal.name,
"GalleonWater": self.spoiler.settings.galleon_water_internal.name,
"MedalCBRequirement": self.spoiler.settings.medal_cb_req,
"BLockerValues": ", ".join(
[
f"{['Japes', 'Aztec', 'Factory', 'Galleon', 'Forest', 'Caves', 'Castle', 'Helm'][i]}: {count} {barrier_type.name}"
for i, (barrier_type, count) in enumerate(zip(self.spoiler.settings.BLockerEntryItems, self.spoiler.settings.BLockerEntryCount))
]
),
"RemovedBarriers": ", ".join([barrier.name for barrier in self.spoiler.settings.remove_barriers_selected]),
"FairyRequirement": self.spoiler.settings.rareware_gb_fairies,
"MermaidPearls": self.spoiler.settings.mermaid_gb_pearls,
"JetpacReq": self.spoiler.settings.medal_requirement,
"BossBananas": ", ".join([str(cost) for cost in self.spoiler.settings.BossBananas]),
"BossMaps": ", ".join(map.name for map in self.spoiler.settings.boss_maps),
"BossKongs": ", ".join(kong.name for kong in self.spoiler.settings.boss_kongs),
"LankyFreeingKong": self.spoiler.settings.lanky_freeing_kong,
"HelmOrder": ", ".join([str(room) for room in self.spoiler.settings.helm_order]),
"OpenLobbies": self.spoiler.settings.open_lobbies,
"KroolInBossPool": self.spoiler.settings.krool_in_boss_pool,
"SwitchSanity": {switch.name: {"kong": data.kong.name, "type": data.switch_type.name} for switch, data in self.spoiler.settings.switchsanity_data.items()},
"LogicType": self.spoiler.settings.logic_type.name,
"GlitchesSelected": ", ".join([glitch.name for glitch in self.spoiler.settings.glitches_selected]),
"StartingKeyList": ", ".join([key.name for key in self.spoiler.settings.starting_key_list]),
"HardShooting": self.options.hard_shooting.value,
"Junk": self.junked_locations,
"HintsInPool": self.options.hints_in_item_pool.value,
"Version": ap_version,
}
def write_spoiler(self, spoiler_handle: typing.TextIO):
"""Write the spoiler."""
spoiler_handle.write("\n")
spoiler_handle.write("Additional Settings info for player: " + self.player_name)
spoiler_handle.write("\n")
spoiler_handle.write("Level Order: " + ", ".join([level.name for order, level in self.spoiler.settings.level_order.items()]))
spoiler_handle.write("\n")
human_boss_order = []
for i in range(len(self.spoiler.settings.boss_maps)):
human_boss_order.append(boss_map_names[self.spoiler.settings.boss_maps[i]])
spoiler_handle.write("Boss Order: " + ", ".join(human_boss_order))
spoiler_handle.write("\n")
spoiler_handle.write("Starting Kongs: " + ", ".join([kong.name for kong in self.spoiler.settings.starting_kong_list]))
spoiler_handle.write("\n")
spoiler_handle.write("Helm Order: " + ", ".join([Kongs(room).name for room in self.spoiler.settings.helm_order]))
spoiler_handle.write("\n")
spoiler_handle.write("K. Rool Order: " + ", ".join([phase.name for phase in self.spoiler.settings.krool_order]))
spoiler_handle.write("\n")
spoiler_handle.write("Forest Time: " + self.spoiler.settings.fungi_time_internal.name)
spoiler_handle.write("\n")
spoiler_handle.write("Galleon Water: " + self.spoiler.settings.galleon_water_internal.name)
spoiler_handle.write("\n")
spoiler_handle.write("CBs for Medal: " + str(self.spoiler.settings.medal_cb_req))
spoiler_handle.write("\n")
# Include both barrier type and count for B. Lockers
blocker_requirements = []
for i, (barrier_type, count) in enumerate(zip(self.spoiler.settings.BLockerEntryItems, self.spoiler.settings.BLockerEntryCount)):
level_names = ["Japes", "Aztec", "Factory", "Galleon", "Forest", "Caves", "Castle", "Helm"]
blocker_requirements.append(f"{level_names[i]}: {count} {barrier_type.name}")
spoiler_handle.write("B. Locker Requirements: " + ", ".join(blocker_requirements))
spoiler_handle.write("\n")
spoiler_handle.write("Removed Barriers: " + ", ".join([barrier.name for barrier in self.spoiler.settings.remove_barriers_selected]))
spoiler_handle.write("\n")
if self.spoiler.settings.switchsanity != SwitchsanityLevel.off:
spoiler_handle.write("Switchsanity Settings: \n")
for switch, data in self.spoiler.settings.switchsanity_data.items():
if self.spoiler.settings.switchsanity == SwitchsanityLevel.helm_access:
if switch not in (Switches.IslesHelmLobbyGone, Switches.IslesMonkeyport):
continue
spoiler_handle.write(f" - {switch.name}: {data.kong.name} with {data.switch_type.name}\n")
spoiler_handle.write("Generated Time: " + time.strftime("%d-%m-%Y %H:%M:%S", time.gmtime()) + " GMT")
spoiler_handle.write("\n")
spoiler_handle.write("Randomizer Version: " + self.spoiler.settings.version)
spoiler_handle.write("\n")
spoiler_handle.write("APWorld Version: " + ap_version)
spoiler_handle.write("\n")
def create_item(self, name: str, force_non_progression=False) -> Item:
"""Create an item."""
data = full_item_table[name]
if force_non_progression:
classification = ItemClassification.filler
elif data.progression:
classification = ItemClassification.progression
elif hasattr(self.multiworld, "generation_is_fake"):
# UT needs to classify things as progression or it won't track them
classification = ItemClassification.progression
else:
classification = ItemClassification.filler
created_item = DK64Item(name, classification, data.code, self.player)
return created_item
def isMajorItem(self, item: DK64Item):
"""Determine whether a DK64Item is a Major Item."""
# Events, colored bananas
if "," in item.name:
return False
# Not progression
if item.classification != ItemClassification.progression and item.classification != ItemClassification.progression_skip_balancing:
return False
# Golden bananas and blueprints
if item.name == "Golden Banana" or "Blueprint" in item.name:
return False
# Hints, medals, Company coins, Banana fairies
if "Hint" in item.name or item.name == "Banana Medal" or "Coin" in item.name or item.name == "Banana Fairy":
return False
# Helm barrels
if "Helm" in item.name and "Barrel" in item.name:
return False
# Misc items
if item.name == "Pearl" or item.name == "The Bean" or "Hoard" in item.name:
return False
return True
def location_starts_empty(self, location: Location):
"""Check if a location starts empty based on item type and location type."""
loc_obj = None
item_obj = None
# Events, collectables
if ", " in location.item.name:
return False
if location.item.name == "BananaHoard":
return False
for loc in self.spoiler.LocationList.keys():
if self.spoiler.LocationList[loc].name == location.name:
loc_obj = self.spoiler.LocationList[loc]
for item in DK64RItem.ItemList.keys():
if DK64RItem.ItemList[item].name == location.item.name:
item_obj = DK64RItem.ItemList[item]
# Completely empty location. Can this happen? No? Anyway...
if location.item is None:
return True
# NoItem
if location.item.name == "No Item":
return True
# Junk item
if item_obj is None:
print(location.item.name)
# TODO, figure out crash
print(f"{item.name}, PLEASE REPORT THIS PRINT!!!!! It's the error, and I don't want to make it crash if I don't have to!")
# raise Exception(f"{item.name} not found in ItemList. (Yes I made it crash again, no it shouldn't run on non-donk games)")
return True
if item_obj.type == Types.JunkItem:
# In a location that can't have junk
if loc_obj.type in (Types.Shop, Types.Shockwave, Types.Crown, Types.PreGivenMove, Types.CrateItem, Types.Enemies) or (loc_obj.type == Types.Key or loc_obj.level == Levels.HideoutHelm):
return True
return False
def updateBossKongs(self, spoiler):
"""Prevent a bug with microhints hinting boss locations as if they were Any Kong locations."""
locations = {
DK64RLocations.JapesKey: spoiler.settings.boss_kongs[Levels.JungleJapes],
DK64RLocations.AztecKey: spoiler.settings.boss_kongs[Levels.AngryAztec],
DK64RLocations.FactoryKey: spoiler.settings.boss_kongs[Levels.FranticFactory],
DK64RLocations.GalleonKey: spoiler.settings.boss_kongs[Levels.GloomyGalleon],
DK64RLocations.ForestKey: spoiler.settings.boss_kongs[Levels.FungiForest],
DK64RLocations.CavesKey: spoiler.settings.boss_kongs[Levels.CrystalCaves],
DK64RLocations.CastleKey: spoiler.settings.boss_kongs[Levels.CreepyCastle],
}
for loc in locations.keys():
spoiler.LocationList[loc].kong = locations[loc]
def collect(self, state: CollectionState, item: Item) -> bool:
"""Collect the item."""
change = super().collect(state, item)
if change:
if self.player in state.dk64_logic_holder.keys():
state.dk64_logic_holder[self.player].UpdateFromArchipelagoItems(state)
elif hasattr(self, "spoiler"):
state.dk64_logic_holder[self.player] = LogicVarHolder(self.spoiler, self.player) # If the CollectionState dodged the creation of a logic_holder object, fix it here
state.dk64_logic_holder[self.player].UpdateFromArchipelagoItems(state)
return change
def interpret_slot_data(self, slot_data: dict[str, any]) -> dict[str, any]:
"""Parse slot data for any logical bits that need to match the real generation. Used by Universal Tracker."""
# Parse the string data
level_order = slot_data["LevelOrder"].split(", ")
starting_kongs = slot_data["StartingKongs"].split(", ")
medal_cb_req = slot_data["MedalCBRequirement"]
fairy_req = slot_data["FairyRequirement"]
pearl_req = slot_data["MermaidPearls"]
jetpac_req = slot_data["JetpacReq"]
boss_bananas = slot_data["BossBananas"].split(", ")
boss_maps = slot_data["BossMaps"].split(", ")
boss_kongs = slot_data["BossKongs"].split(", ")
helm_order = slot_data["HelmOrder"].split(", ")
open_lobbies = slot_data["OpenLobbies"]
switchsanity = slot_data["SwitchSanity"]
logic_type = slot_data["LogicType"]
glitches_selected = slot_data["GlitchesSelected"].split(", ")
starting_key_list = slot_data["StartingKeyList"].split(", ")
hard_shooting = slot_data.get("HardShooting", False)
junk = slot_data["Junk"]
blocker_data = list(map(lambda original_string: original_string[original_string.find(":") + 2 :], slot_data["BLockerValues"].split(", ")))
blocker_item_type = list(map(lambda data: data.split(" ")[1], blocker_data))
blocker_item_quantity = list(map(lambda data: int(data.split(" ")[0]), blocker_data))
relevant_data = {}
relevant_data["LevelOrder"] = dict(enumerate([Levels[level] for level in level_order], start=1))
relevant_data["StartingKongs"] = [Kongs[kong] for kong in starting_kongs]
relevant_data["MedalCBRequirement"] = medal_cb_req
relevant_data["FairyRequirement"] = fairy_req
relevant_data["MermaidPearls"] = pearl_req
relevant_data["JetpacReq"] = jetpac_req
relevant_data["BossBananas"] = [int(cost) for cost in boss_bananas]
relevant_data["BossMaps"] = [Maps[map] for map in boss_maps]
relevant_data["BossKongs"] = [Kongs[kong] for kong in boss_kongs]
relevant_data["LankyFreeingKong"] = slot_data["LankyFreeingKong"]
relevant_data["HelmOrder"] = [int(room) for room in helm_order]
relevant_data["SwitchSanity"] = switchsanity
relevant_data["OpenLobbies"] = open_lobbies
relevant_data["LogicType"] = logic_type
relevant_data["GlitchesSelected"] = [GlitchesSelected[glitch] for glitch in glitches_selected if glitch != ""]
relevant_data["StartingKeyList"] = [DK64RItems[key] for key in starting_key_list if key != ""]
relevant_data["HardShooting"] = hard_shooting
relevant_data["JunkedLocations"] = junk
relevant_data["BLockerEntryItems"] = [BarrierItems[item] for item in blocker_item_type]
relevant_data["BLockerEntryCount"] = blocker_item_quantity
relevant_data["HintsInPool"] = slot_data["HintsInPool"]
return relevant_data