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

1303 lines
69 KiB
Python

"""Spoiler class and functions."""
from __future__ import annotations
import json
from copy import deepcopy
from typing import TYPE_CHECKING, Dict, List, Optional, OrderedDict, Union
import randomizer.Lists.Exceptions as Ex
from randomizer.Enums.Events import Events
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.Maps import Maps
from randomizer.Enums.MoveTypes import MoveTypes
from randomizer.Enums.Regions import Regions
from randomizer.Enums.HintRegion import HintRegion
from randomizer.Enums.SwitchTypes import SwitchType
from randomizer.Enums.Settings import (
BananaportRando,
CBRando,
DKPortalRando,
GlitchesSelected,
LogicType,
HardBossesSelected,
MinigameBarrels,
ProgressiveHintItem,
RandomPrices,
ShockwaveStatus,
ShuffleLoadingZones,
ShufflePortLocations,
SpoilerHints,
TrainingBarrels,
WinConditionComplex,
)
from randomizer.Enums.Transitions import Transitions
from randomizer.Enums.Types import Types, BarrierItems
from randomizer.Lists.EnemyTypes import EnemyMetaData
from randomizer.Lists.Item import ItemFromKong, ItemList, KongFromItem, NameFromKong
from randomizer.Lists.Location import LocationListOriginal, PreGivenLocations, TrainingBarrelLocations
from randomizer.Lists.Logic import GlitchLogicItems
from randomizer.Enums.Maps import Maps
from randomizer.Lists.MapsAndExits import GetExitId, GetMapId
from randomizer.Lists.Minigame import (
BarrelMetaData,
HelmMinigameLocations,
MinigameRequirements,
TrainingMinigameLocations,
MinigameSelector,
)
from randomizer.Lists.Multiselectors import FasterCheckSelector, RemovedBarrierSelector, QoLSelector
from randomizer.Lists.EnemyTypes import EnemySelector
from randomizer.Logic import CollectibleRegionsOriginal, LogicVarHolder, RegionsOriginal
from randomizer.Prices import ProgressiveMoves
from randomizer.Settings import Settings
from randomizer.ShuffleBosses import HardBossesEnabled
from randomizer.ShuffleExits import ShufflableExits
from randomizer.ShuffleKasplats import constants, shufflable
from randomizer.Patching.Library.Generic import IsItemSelected
if TYPE_CHECKING:
from randomizer.Lists.Location import Location
from randomizer.LogicClasses import Sphere
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",
}
class Spoiler:
"""Class which contains all spoiler data passed into and out of randomizer."""
def __init__(self, settings: Settings) -> None:
"""Initialize spoiler just with settings."""
self.settings: Settings = settings
self.playthrough = {}
self.woth = {}
self.woth_locations = {}
self.woth_paths = {}
self.krool_paths = {}
self.rap_win_con_paths = {}
self.other_paths = {}
self.shuffled_door_data = {}
self.shuffled_barrel_data = {}
self.shuffled_exit_data = {}
self.shuffled_exit_instructions = []
self.music_bgm_data = {}
self.music_majoritem_data = {}
self.music_minoritem_data = {}
self.music_event_data = {}
self.location_data = {}
self.enemy_replacements = []
self.cb_placements = []
self.LogicVariables = LogicVarHolder(self)
self.RegionList = deepcopy(RegionsOriginal)
self.CollectibleRegions = deepcopy(CollectibleRegionsOriginal)
self.LocationList = deepcopy(LocationListOriginal)
self.move_data = []
# 0: Cranky, 1: Funky, 2: Candy
for move_master_type in range(3):
master_moves = []
if move_master_type == 0:
# Shop
for shop_index in range(3):
moves = []
# One for each kong
for kong_index in range(5):
kongmoves = []
# One for each level
for level_index in range(8):
kongmoves.append({"move_type": None})
moves.append(kongmoves)
master_moves.append(moves)
elif move_master_type == 1:
# Training Barrels
if self.settings.training_barrels == TrainingBarrels.normal:
for tbarrel_type in ["dive", "orange", "barrel", "vine"]:
master_moves.append({"move_type": "flag", "flag": tbarrel_type, "price": 0})
else:
for tbarrel_type in ["dive", "orange", "barrel", "vine"]:
master_moves.append({"move_type": None})
elif move_master_type == 2:
# BFI
if self.settings.shockwave_status == ShockwaveStatus.vanilla:
master_moves = [{"move_type": "flag", "flag": "camera_shockwave", "price": 0}]
else:
master_moves = [{"move_type": None}]
self.move_data.append(master_moves)
self.hint_list = {}
self.short_hint_list = {}
self.tied_hint_flags = {}
self.tied_hint_regions = [HintRegion.NoRegion] * 35
self.settings.finalize_world_settings(self)
self.settings.update_valid_locations(self)
def FlushAllExcessSpoilerData(self):
"""Flush all spoiler data that is not needed for the final result."""
del self.LocationList
del self.RegionList
del self.CollectibleRegions
del self.LogicVariables
def Reset(self) -> None:
"""Reset logic variables and region info that should be reset before a search."""
self.LogicVariables.Reset()
self.ResetRegionAccess()
self.ResetCollectibleRegions()
def ResetRegionAccess(self) -> None:
"""Reset kong access for all regions."""
for region in self.RegionList.values():
region.ResetAccess()
def ResetCollectibleRegions(self) -> None:
"""Reset if each collectible has been added."""
for region in self.CollectibleRegions.values():
for collectible in region:
collectible.added = False
# collectible.enabled = collectible.vanilla
def ClearAllLocations(self) -> None:
"""Clear item from every location."""
for location in self.LocationList.values():
location.item = None
# Always block PreGiven locations and only unblock them as we intentionally place moves there
for location_id in TrainingBarrelLocations:
self.LocationList[location_id].inaccessible = True
for location_id in PreGivenLocations:
self.LocationList[location_id].inaccessible = True
def ResetLocationList(self) -> None:
"""Reset the LocationList to values conducive to a new fill."""
for location in self.LocationList.values():
location.PlaceDefaultItem(self)
# Known to be incomplete - it should also confirm the correct locations of Fairies, Dirt, and Crowns
def InitKasplatMap(self) -> None:
"""Initialize kasplat_map in logic variables with default values."""
# Just use default kasplat associations.
self.LogicVariables.kasplat_map = {}
self.LogicVariables.kasplat_map.update(shufflable)
self.LogicVariables.kasplat_map.update(constants)
def getItemGroup(self, item: Optional[Items]) -> str:
"""Get item group from item."""
if item is None:
item = Items.NoItem
if item == Items.NoItem:
return "Empty"
item_type = ItemList[item].type
type_dict = {
Types.Kong: "Kongs",
Types.Shop: "Moves",
Types.Shockwave: "Moves",
Types.TrainingBarrel: "Moves",
Types.Climbing: "Moves",
Types.Banana: "Golden Bananas",
Types.Blueprint: "Blueprints",
Types.Fairy: "Fairies",
Types.Key: "Keys",
Types.Crown: "Crowns",
Types.Medal: "Medals",
Types.NintendoCoin: "Company Coins",
Types.RarewareCoin: "Company Coins",
Types.Bean: "Miscellaneous Items",
Types.Pearl: "Miscellaneous Items",
Types.RainbowCoin: "Rainbow Coins",
Types.FakeItem: "Ice Traps",
Types.JunkItem: "Junk Items",
Types.CrateItem: "Melon Crates",
Types.Enemies: "Enemy Drops",
Types.Cranky: "Shop Owners",
Types.Funky: "Shop Owners",
Types.Candy: "Shop Owners",
Types.Snide: "Shop Owners",
Types.Hint: "Hints",
}
if item_type in type_dict:
return type_dict[item_type]
return "Unknown"
def dumpMultiselector(self, toggle: bool, settings_list: list, selector_list: list):
"""Dump multiselector list to a response which can be dumped to the spoiler."""
if toggle and any(settings_list):
lst = []
selector_name_dict = {}
for x in selector_list:
selector_name_dict[x["value"]] = x["name"]
for x in settings_list:
if x.name in selector_name_dict:
lst.append(selector_name_dict[x.name])
return lst
return toggle
def createJson(self) -> None:
"""Convert spoiler to JSON and save it."""
# We want to convert raw spoiler data into the important bits and in human-readable formats.
humanspoiler = OrderedDict()
# Settings data
settings = OrderedDict()
settings["Settings String"] = self.settings.settings_string
settings["Seed"] = self.settings.seed_id
# settings["algorithm"] = self.settings.algorithm # Don't need this for now, probably
logic_types = {
LogicType.nologic: "No Logic",
LogicType.glitch: "Glitched Logic",
LogicType.glitchless: "Glitchless Logic",
}
if self.settings.logic_type in logic_types:
settings["Logic Type"] = logic_types[self.settings.logic_type]
else:
settings["Logic Type"] = self.settings.logic_type
if self.settings.logic_type == LogicType.glitch:
settings["Glitches Enabled"] = ", ".join(
[x.name for x in GlitchLogicItems if GlitchesSelected[x.shorthand] in self.settings.glitches_selected or len(self.settings.glitches_selected) == 0]
)
settings["Shuffle Enemies"] = self.settings.enemy_rando
settings["Move Randomization type"] = self.settings.move_rando.name
settings["Loading Zones Shuffled"] = self.settings.shuffle_loading_zones.name
settings["Decoupled Loading Zones"] = self.settings.decoupled_loading_zones
settings["Helm Location Shuffled"] = self.settings.shuffle_helm_location
startKongList = []
for x in self.settings.starting_kong_list:
startKongList.append(x.name.capitalize())
settings["Hard B Lockers"] = self.settings.hard_blockers
if self.settings.randomize_blocker_required_amounts:
settings["Maximum B Locker"] = self.settings.blocker_text
if self.settings.maximize_helm_blocker:
settings["Maximum B Locker ensured"] = self.settings.maximize_helm_blocker
settings["Hard Troff N Scoff"] = self.settings.hard_troff_n_scoff
if self.settings.randomize_cb_required_amounts:
settings["Maximum Troff N Scoff"] = self.settings.troff_text
settings["Open Lobbies"] = self.settings.open_lobbies
settings["Auto Complete Bonus Barrels"] = self.settings.bonus_barrel_auto_complete
settings["Auto Key Turn ins"] = self.settings.auto_keys
settings["Chaos B.Lockers"] = self.settings.chaos_blockers
settings["Chaos Ratio"] = self.settings.chaos_ratio
settings["Complex Level Order"] = self.settings.hard_level_progression
settings["Progressive Switch Strength"] = self.settings.alter_switch_allocation
settings["Hard Shooting"] = self.settings.hard_shooting
settings["Dropsanity"] = self.settings.enemy_drop_rando
settings["Switchsanity"] = self.settings.switchsanity.name
settings["Free Trade Agreement"] = self.settings.free_trade_setting.name
settings["Randomize Pickups"] = self.settings.randomize_pickups
settings["Randomize Patches"] = self.settings.random_patches
settings["Randomize Crates"] = self.settings.random_crates
settings["Randomize CB Locations"] = self.settings.cb_rando_enabled
settings["Randomize Coin Locations"] = self.settings.coin_rando
settings["Randomize Shop Locations"] = self.settings.shuffle_shops
settings["Randomize Kasplats"] = self.settings.kasplat_rando_setting.name
settings["Randomize Banana Fairies"] = self.settings.random_fairies
settings["Randomize Battle Arenas"] = self.settings.crown_placement_rando
settings["Vanilla Door Shuffle"] = self.settings.vanilla_door_rando
settings["Dos' Doors"] = self.settings.dos_door_rando
settings["Randomize Wrinkly Doors"] = self.settings.wrinkly_location_rando
settings["Randomize T&S Portals"] = self.settings.tns_location_rando
settings["Puzzle Randomization"] = self.settings.puzzle_rando_difficulty.name
settings["Crown Door Open"] = self.settings.crown_door_item == BarrierItems.Nothing
settings["Coin Door Open"] = self.settings.coin_door_item == BarrierItems.Nothing
settings["Shockwave Shuffle"] = self.settings.shockwave_status.name
settings["Random Jetpac Medal Requirement"] = self.settings.random_medal_requirement
settings["Bananas Required for Medal"] = self.settings.medal_cb_req
settings["Fairies Required for Rareware GB"] = self.settings.rareware_gb_fairies
settings["Pearls Required for Mermaid GB"] = self.settings.mermaid_gb_pearls
settings["Random Shop Prices"] = self.settings.random_prices.name
settings["Banana Port Randomization"] = self.settings.bananaport_rando.name
settings["Banana port Location Shuffle"] = self.settings.bananaport_placement_rando.name
settings["Activated Warps"] = self.settings.activate_all_bananaports.name
settings["Smaller Shops"] = self.settings.smaller_shops
settings["Irondonk"] = self.settings.perma_death
settings["Disable Tag Barrels"] = self.settings.disable_tag_barrels
settings["Ice Trap Frequency"] = self.settings.ice_trap_frequency.name
settings["Ice Traps Damage Player"] = self.settings.ice_traps_damage
settings["Mirror Mode"] = self.settings.mirror_mode
settings["Damage Amount"] = self.settings.damage_amount.name
settings["Hard Mode Enabled"] = self.settings.hard_mode and len(self.settings.hard_mode_selected) > 0
settings["Hard Bosses Enabled"] = self.settings.hard_bosses and len(self.settings.hard_bosses_selected) > 0
# settings["Krusha Slot"] = self.settings.krusha_ui.name
settings["DK Model"] = self.settings.kong_model_dk.name
settings["Diddy Model"] = self.settings.kong_model_diddy.name
settings["Lanky Model"] = self.settings.kong_model_lanky.name
settings["Tiny Model"] = self.settings.kong_model_tiny.name
settings["Chunky Model"] = self.settings.kong_model_chunky.name
settings["Key 8 Required"] = self.settings.krool_access
settings["Vanilla K. Rool Requirement"] = self.settings.k_rool_vanilla_requirement
settings["Key 8 in Helm"] = self.settings.key_8_helm
settings["Select Starting Keys"] = self.settings.select_keys
if not self.settings.keys_random:
settings["Number of Keys Required"] = self.settings.krool_key_count
settings["Starting Moves Count"] = self.settings.starting_moves_count
settings["Fast Start"] = self.settings.fast_start_beginning_of_game
settings["Helm Setting"] = self.settings.helm_setting.name
settings["Helm Room Bonus Count"] = int(self.settings.helm_room_bonus_count)
settings["Tag Anywhere"] = self.settings.enable_tag_anywhere
settings["Kongless Hint Doors"] = self.settings.wrinkly_available
settings["Quality of Life"] = self.dumpMultiselector(self.settings.quality_of_life, self.settings.misc_changes_selected, QoLSelector)
settings["Fast GBs"] = self.dumpMultiselector(self.settings.faster_checks_enabled, self.settings.faster_checks_selected, FasterCheckSelector)
settings["Barriers Removed"] = self.dumpMultiselector(self.settings.remove_barriers_enabled, self.settings.remove_barriers_selected, RemovedBarrierSelector)
settings["Random Win Condition"] = self.settings.win_condition_random
if not self.settings.win_condition_random:
wc_count = self.settings.win_condition_count
win_con_name_table = {
WinConditionComplex.beat_krool: "Beat K. Rool",
WinConditionComplex.get_key8: "Acquire Key 8",
WinConditionComplex.krem_kapture: "Kremling Kapture",
WinConditionComplex.dk_rap_items: "Complete the Rap",
WinConditionComplex.req_bean: "Acquire the Bean",
WinConditionComplex.req_bp: f"{wc_count} Blueprint{'s' if wc_count != 1 else ''}",
WinConditionComplex.req_companycoins: f"{wc_count} Company Coin{'s' if wc_count != 1 else ''}",
WinConditionComplex.req_crown: f"{wc_count} Crown{'s' if wc_count != 1 else ''}",
WinConditionComplex.req_fairy: f"{wc_count} Fair{'ies' if wc_count != 1 else 'y'}",
WinConditionComplex.req_gb: f"{wc_count} Golden Banana{'s' if wc_count != 1 else ''}",
WinConditionComplex.req_key: f"{wc_count} Key{'s' if wc_count != 1 else ''}",
WinConditionComplex.req_medal: f"{wc_count} Medal{'s' if wc_count != 1 else ''}",
WinConditionComplex.req_pearl: f"{wc_count} Pearl{'s' if wc_count != 1 else ''}",
WinConditionComplex.req_rainbowcoin: f"{wc_count} Rainbow Coin{'s' if wc_count != 1 else ''}",
}
if self.settings.win_condition_item in win_con_name_table:
settings["Win Condition"] = win_con_name_table[self.settings.win_condition_item]
else:
settings["Win Condition"] = self.settings.win_condition_item.name
settings["Fungi Time of Day"] = self.settings.fungi_time.name
settings["Galleon Water Level"] = self.settings.galleon_water.name
settings["Chunky Phase Slam Requirement"] = self.settings.chunky_phase_slam_req.name
settings["Hint Preset"] = self.settings.wrinkly_hints
if self.settings.progressive_hint_item != ProgressiveHintItem.off:
settings["Progressive Hint Item"] = self.settings.progressive_hint_item.name
settings["Progressive Hint Cap"] = int(self.settings.progressive_hint_count)
settings["Dim Solved Hints"] = self.settings.dim_solved_hints
settings["No Joke Hints"] = self.settings.serious_hints
settings["Item Reward Previews"] = self.settings.item_reward_previews
settings["Bonus Barrel Rando"] = self.dumpMultiselector(self.settings.bonus_barrel_rando, self.settings.minigames_list_selected, MinigameSelector)
if self.settings.enemy_rando and any(self.settings.enemies_selected):
value_lst = [x.name for x in self.settings.enemies_selected]
settings["Enemy Rando"] = [enemy["name"] for enemy in EnemySelector if enemy["value"] in value_lst]
else:
settings["Enemy Rando"] = self.settings.enemy_rando
settings["Crown Enemy Rando"] = self.settings.crown_enemy_difficulty.name
if self.settings.helm_hurry:
settings["Game Mode"] = "Helm Hurry"
humanspoiler["Settings"] = settings
humanspoiler["Randomizer Version"] = self.settings.version
humanspoiler["Generation Branch"] = self.settings.branch
humanspoiler["Cosmetics"] = {}
if self.settings.spoiler_hints != SpoilerHints.off:
humanspoiler["Spoiler Hints Data"] = {}
for key in self.level_spoiler.keys():
if key == "point_spread":
humanspoiler["Spoiler Hints Data"][key] = json.dumps(self.level_spoiler[key])
else:
humanspoiler["Spoiler Hints Data"][key] = self.level_spoiler[key].toJSON()
humanspoiler["Spoiler Hints"] = self.level_spoiler_human_readable
humanspoiler["Requirements"] = {}
if self.settings.random_starting_region:
humanspoiler["Game Start"] = {}
humanspoiler["Game Start"]["Starting Kong List"] = startKongList
humanspoiler["Game Start"]["Starting Region"] = self.settings.starting_region["region_name"]
humanspoiler["Game Start"]["Starting Exit"] = self.settings.starting_region["exit_name"]
# GB Counts
gb_counts = {}
level_list = [
"Jungle Japes",
"Angry Aztec",
"Frantic Factory",
"Gloomy Galleon",
"Fungi Forest",
"Crystal Caves",
"Creepy Castle",
"Hideout Helm",
]
for level_index, amount in enumerate(self.settings.BLockerEntryCount):
item = self.settings.BLockerEntryItems[level_index].name
item_total = f" {item}s"
if item == "Percentage":
item_total = "%"
elif item == "Fairy" and amount != 1:
item_total = " Fairies" # LOL @ English Language
elif amount == 1:
item_total = f" {item}"
gb_counts[level_list[level_index]] = f"{amount}{item_total}"
humanspoiler["Requirements"]["B Locker Items"] = gb_counts
# CB Counts
cb_counts = {}
for level_index, amount in enumerate(self.settings.BossBananas):
cb_counts[level_list[level_index]] = amount
humanspoiler["Requirements"]["Troff N Scoff Bananas"] = cb_counts
humanspoiler["Requirements"]["Miscellaneous"] = {}
humanspoiler["Kongs"] = {}
humanspoiler["Kongs"]["Starting Kong List"] = startKongList
humanspoiler["Kongs"]["Japes Kong Puzzle Solver"] = ItemList[ItemFromKong(self.settings.diddy_freeing_kong)].name
humanspoiler["Kongs"]["Tiny Temple Puzzle Solver"] = ItemList[ItemFromKong(self.settings.tiny_freeing_kong)].name
humanspoiler["Kongs"]["Llama Temple Puzzle Solver"] = ItemList[ItemFromKong(self.settings.lanky_freeing_kong)].name
humanspoiler["Kongs"]["Factory Kong Puzzle Solver"] = ItemList[ItemFromKong(self.settings.chunky_freeing_kong)].name
humanspoiler["Requirements"]["Miscellaneous"]["Jetpac Medal Requirement"] = self.settings.medal_requirement
humanspoiler["End Game"] = {
"Helm": {},
"K. Rool": {},
}
humanspoiler["End Game"]["K. Rool"]["Keys Required for K Rool"] = self.GetKroolKeysRequired(self.settings.krool_keys_required)
krool_order = []
for phase in self.settings.krool_order:
krool_order.append(boss_map_names[phase])
humanspoiler["End Game"]["K. Rool"]["K Rool Phases"] = krool_order
humanspoiler["End Game"]["K. Rool"]["Chunky Phase Slam Requirement"] = self.settings.chunky_phase_slam_req_internal.name
humanspoiler["End Game"]["K. Rool"]["DK Phase requires Baboon Blast"] = self.settings.cannons_require_blast
helm_default_order = [Kongs.donkey, Kongs.chunky, Kongs.tiny, Kongs.lanky, Kongs.diddy]
helm_new_order = []
for room in self.settings.helm_order:
helm_new_order.append(helm_default_order[room].name.capitalize())
humanspoiler["End Game"]["Helm"]["Helm Rooms"] = helm_new_order
helm_door_names = {
BarrierItems.Bean: "Bean",
BarrierItems.Blueprint: "Blueprints",
BarrierItems.CompanyCoin: "Company Coins",
BarrierItems.Crown: "Crowns",
BarrierItems.Fairy: "Fairies",
BarrierItems.GoldenBanana: "Golden Bananas",
BarrierItems.Key: "Keys",
BarrierItems.Medal: "Medals",
BarrierItems.Pearl: "Pearls",
BarrierItems.RainbowCoin: "Rainbow Coins",
}
if self.settings.crown_door_item != BarrierItems.Nothing:
item = self.settings.crown_door_item
humanspoiler["End Game"]["Helm"]["Crown Door Item"] = helm_door_names[item]
humanspoiler["End Game"]["Helm"]["Crown Door Item Randomized"] = self.settings.crown_door_random
humanspoiler["End Game"]["Helm"]["Crown Door Item Amount"] = self.settings.crown_door_item_count
if self.settings.coin_door_item != BarrierItems.Nothing:
item = self.settings.coin_door_item
humanspoiler["End Game"]["Helm"]["Coin Door Item"] = helm_door_names[item]
humanspoiler["End Game"]["Helm"]["Coin Door Item Randomized"] = self.settings.coin_door_random
humanspoiler["End Game"]["Helm"]["Coin Door Item Amount"] = self.settings.coin_door_item_count
if self.settings.shuffle_items:
humanspoiler["Item Pool"] = list(set([enum.name for enum in self.settings.shuffled_location_types]))
if self.settings.hard_mode_selected and len(self.settings.hard_mode_selected) > 0:
humanspoiler["Hard Mode"] = list(set([enum.name for enum in self.settings.hard_mode_selected]))
if self.settings.hard_bosses_selected and len(self.settings.hard_bosses_selected) > 0:
humanspoiler["Hard Bosses"] = list(set([enum.name for enum in self.settings.hard_bosses_selected]))
humanspoiler["Items"] = {
"Kongs": {},
"Shops": {},
"DK Isles": {},
"Jungle Japes": {},
"Angry Aztec": {},
"Frantic Factory": {},
"Gloomy Galleon": {},
"Fungi Forest": {},
"Crystal Caves": {},
"Creepy Castle": {},
"Hideout Helm": {},
"Special": {},
}
sorted_item_name = "Items (Sorted by Item)"
humanspoiler[sorted_item_name] = {
"Kongs": {},
"Moves": {},
"Golden Bananas": {},
"Blueprints": {},
"Fairies": {},
"Keys": {},
"Crowns": {},
"Company Coins": {},
"Medals": {},
"Miscellaneous Items": {},
"Rainbow Coins": {},
"Ice Traps": {},
"Junk Items": {},
"Melon Crates": {},
"Hints": {},
"Enemy Drops": {},
"Shop Owners": {},
"Empty": {},
"Unknown": {},
}
self.pregiven_items = []
self.first_move_item = None
for location_id, location in self.LocationList.items():
# No need to spoiler constants
if location.type == Types.Constant or location.inaccessible:
continue
# No hints if hint doors are not in the pool
if location.type == Types.Hint and Types.Hint not in self.settings.shuffled_location_types:
continue
if location.type == Types.ProgressiveHint:
continue
if location_id in PreGivenLocations:
if self.settings.fast_start_beginning_of_game or location_id != Locations.IslesFirstMove:
self.pregiven_items.append(location.item)
else:
self.first_move_item = location.item
# Prevent weird null issues but get the item at the location
if location.item is None:
item = Items.NoItem
else:
item = ItemList[location.item]
# Empty PreGiven locations don't really exist and shouldn't show up in the spoiler log
if location.type in (
Types.PreGivenMove,
Types.Cranky,
Types.Candy,
Types.Funky,
Types.Snide,
) and location.item in (None, Items.NoItem):
continue
# Separate Kong locations
if location.type == Types.Kong:
humanspoiler["Items"]["Kongs"][location.name] = item.name
humanspoiler[sorted_item_name][self.getItemGroup(location.item)][location.name] = item.name
# Separate Shop locations
elif location.type == Types.Shop:
# Ignore shop locations with no items
if location.item is None or location.item == Items.NoItem:
continue
# Gotta dig up the price - progressive moves look a little weird in the spoiler
price = ""
if location.item in ProgressiveMoves.keys():
if location.item == Items.ProgressiveSlam:
price = f"{self.settings.prices[Items.ProgressiveSlam][0]}->{self.settings.prices[Items.ProgressiveSlam][1]}"
elif location.item == Items.ProgressiveAmmoBelt:
price = f"{self.settings.prices[Items.ProgressiveAmmoBelt][0]}->{self.settings.prices[Items.ProgressiveAmmoBelt][1]}"
elif location.item == Items.ProgressiveInstrumentUpgrade:
price = f"{self.settings.prices[Items.ProgressiveInstrumentUpgrade][0]}->{self.settings.prices[Items.ProgressiveInstrumentUpgrade][1]}->{self.settings.prices[Items.ProgressiveInstrumentUpgrade][2]}"
# Vanilla prices are by item, not by location
elif self.settings.random_prices == RandomPrices.vanilla:
price = str(self.settings.prices[location.item])
else:
price = str(self.settings.prices[location_id])
humanspoiler["Items"]["Shops"][location.name] = item.name + f" ({price})"
humanspoiler[sorted_item_name][self.getItemGroup(location.item)][location.name] = item.name
# Filter everything else by level - each location conveniently contains a level-identifying bit in their name
else:
level = "Special"
if "Isles" in location.name or location.type in (
Types.PreGivenMove,
Types.Climbing,
Types.Cranky,
Types.Funky,
Types.Candy,
Types.Snide,
):
level = "DK Isles"
elif "Japes" in location.name:
level = "Jungle Japes"
elif "Aztec" in location.name:
level = "Angry Aztec"
elif "Factory" in location.name:
level = "Frantic Factory"
elif "Galleon" in location.name:
level = "Gloomy Galleon"
elif "Forest" in location.name:
level = "Fungi Forest"
elif "Caves" in location.name:
level = "Crystal Caves"
elif "Castle" in location.name:
level = "Creepy Castle"
elif "Helm" in location.name:
level = "Hideout Helm"
if self.settings.enemy_drop_rando or location.item != Items.EnemyItem:
humanspoiler["Items"][level][location.name] = item.name
humanspoiler[sorted_item_name][self.getItemGroup(location.item)][location.name] = item.name
if not self.settings.enemy_drop_rando:
del humanspoiler[sorted_item_name]["Enemy Drops"]
if self.settings.enemy_rando:
placement_dict = {}
for map_id in self.enemy_rando_data:
map_name = Maps(map_id).name
map_dict = {}
for enemy in self.enemy_rando_data[map_id]:
map_dict[enemy["location"]] = EnemyMetaData[enemy["enemy"]].name
placement_dict[map_name] = map_dict
if not self.settings.enemy_drop_rando:
humanspoiler["Enemy Placement (Stringified JSON)"] = json.dumps(placement_dict)
else:
humanspoiler["Enemy Placement"] = placement_dict
humanspoiler["Bosses"] = {}
if self.settings.boss_location_rando:
shuffled_bosses = OrderedDict()
for i in range(7):
shuffled_bosses["".join(map(lambda x: x if x.islower() else " " + x, Levels(i).name)).strip()] = boss_map_names.get(self.settings.boss_maps[i], Maps(self.settings.boss_maps[i]).name)
humanspoiler["Bosses"]["Shuffled Boss Order"] = shuffled_bosses
humanspoiler["Bosses"]["King Kut Out Properties"] = {}
if self.settings.boss_kong_rando:
shuffled_boss_kongs = OrderedDict()
for i in range(7):
shuffled_boss_kongs["".join(map(lambda x: x if x.islower() else " " + x, Levels(i).name)).strip()] = Kongs(self.settings.boss_kongs[i]).name.capitalize()
humanspoiler["Bosses"]["Shuffled Boss Kongs"] = shuffled_boss_kongs
kutout_order = ""
for kong in self.settings.kutout_kongs:
kutout_order = kutout_order + Kongs(kong).name.capitalize() + ", "
humanspoiler["Bosses"]["King Kut Out Properties"]["Shuffled Kutout Kong Order"] = kutout_order
if HardBossesEnabled(self.settings, HardBossesSelected.kut_out_phase_rando):
phase_names = []
for phase in self.settings.kko_phase_order:
phase_names.append(f"Phase {phase+1}")
humanspoiler["Bosses"]["King Kut Out Properties"]["Shuffled Kutout Phases"] = ", ".join(phase_names)
if self.settings.bonus_barrels == MinigameBarrels.selected and len(self.settings.minigames_list_selected) > 0:
selected_minigames = []
for minigame in self.settings.minigames_list_selected:
selected_minigames.append(minigame.name)
humanspoiler["Selected Minigames"] = selected_minigames
if (
self.settings.bonus_barrels in (MinigameBarrels.random, MinigameBarrels.selected)
or self.settings.helm_barrels == MinigameBarrels.random
or self.settings.training_barrels_minigames == MinigameBarrels.random
):
shuffled_barrels = OrderedDict()
for location, minigame in self.shuffled_barrel_data.items():
if location in HelmMinigameLocations and self.settings.helm_barrels == MinigameBarrels.skip:
continue
if location in TrainingMinigameLocations and self.settings.training_barrels_minigames == MinigameBarrels.skip:
continue
if location not in HelmMinigameLocations and location not in TrainingMinigameLocations and self.settings.bonus_barrels == MinigameBarrels.skip:
continue
shuffled_barrels[self.LocationList[location].name] = MinigameRequirements[minigame].name
if len(shuffled_barrels) > 0:
humanspoiler["Shuffled Bonus Barrels"] = shuffled_barrels
if self.settings.kasplat_rando:
humanspoiler["Shuffled Kasplats"] = self.human_kasplats
if self.settings.random_fairies:
humanspoiler["Shuffled Banana Fairies"] = self.fairy_locations_human
if self.settings.random_patches:
humanspoiler["Shuffled Dirt Patches"] = self.human_patches
if self.settings.random_crates:
humanspoiler["Shuffled Melon Crates"] = self.human_crates
humanspoiler["Settings"]["Shuffled Bananaport Levels"] = False
if self.settings.bananaport_placement_rando != ShufflePortLocations.off and self.settings.bananaport_rando == BananaportRando.off:
shuffled_warp_levels = [x.name for x in self.settings.warp_level_list_selected]
if len(shuffled_warp_levels) == 0:
humanspoiler["Settings"]["Shuffled Bananaport Levels"] = True
else:
humanspoiler["Settings"]["Shuffled Bananaport Levels"] = shuffled_warp_levels
humanspoiler["Shuffled Bananaport Locations"] = self.human_warps
if self.settings.bananaport_rando != BananaportRando.off:
humanspoiler["Shuffled Bananaport Connections (Source -> Destination)"] = self.human_warp_locations
if self.settings.wrinkly_location_rando:
prog_hint_setting = self.settings.progressive_hint_item
item_types = self.settings.shuffled_location_types
if prog_hint_setting == ProgressiveHintItem.off or Types.Hint in item_types:
humanspoiler["Wrinkly Door Locations"] = self.human_hint_doors
if self.settings.tns_location_rando:
humanspoiler["T&S Portal Locations"] = self.human_portal_doors
if self.settings.dk_portal_location_rando_v2 != DKPortalRando.off:
humanspoiler["DK Portal Locations"] = self.human_entry_doors
if self.settings.crown_placement_rando:
humanspoiler["Battle Arena Locations"] = self.human_crowns
if self.settings.switchsanity:
ss_data = {}
ss_name_data = {
Kongs.donkey: {
SwitchType.SlamSwitch: "Donkey Slam Switch",
SwitchType.GunSwitch: "Coconut Switch",
SwitchType.InstrumentPad: "Bongos Pad",
SwitchType.PadMove: "Baboon Blast Pad",
SwitchType.MiscActivator: "Gorilla Grab Lever",
},
Kongs.diddy: {
SwitchType.SlamSwitch: "Diddy Slam Switch",
SwitchType.GunSwitch: "Peanut Switch",
SwitchType.InstrumentPad: "Guitar Pad",
SwitchType.PadMove: "Simian Spring Pad",
SwitchType.MiscActivator: "Gong",
},
Kongs.lanky: {
SwitchType.SlamSwitch: "Lanky Slam Switch",
SwitchType.GunSwitch: "Grape Switch",
SwitchType.InstrumentPad: "Trombone Pad",
SwitchType.PadMove: "Baboon Balloon Pad",
},
Kongs.tiny: {
SwitchType.SlamSwitch: "Tiny Slam Switch",
SwitchType.GunSwitch: "Feather Switch",
SwitchType.InstrumentPad: "Saxophone Pad",
SwitchType.PadMove: "Monkeyport Pad",
},
Kongs.chunky: {
SwitchType.SlamSwitch: "Chunky Slam Switch",
SwitchType.GunSwitch: "Pineapple Switch",
SwitchType.InstrumentPad: "Triangle Pad",
SwitchType.PadMove: "Gorilla Gone Pad",
},
}
for slot in self.settings.switchsanity_data.values():
ss_data[slot.name] = ss_name_data[slot.kong][slot.switch_type]
humanspoiler["Switchsanity"] = ss_data
level_dict = {
Levels.DKIsles: "DK Isles",
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",
}
if self.settings.shuffle_shops:
shop_location_dict = {}
for level in self.shuffled_shop_locations:
level_name = "Unknown Level"
shop_dict = {
Regions.CrankyGeneric: "Cranky",
Regions.CandyGeneric: "Candy",
Regions.FunkyGeneric: "Funky",
Regions.Snide: "Snide",
}
if level in level_dict:
level_name = level_dict[level]
for shop in self.shuffled_shop_locations[level]:
location_name = "Unknown Shop"
shop_name = "Unknown Shop"
new_shop = self.shuffled_shop_locations[level][shop]
if shop in shop_dict:
location_name = shop_dict[shop]
if new_shop in shop_dict:
shop_name = shop_dict[new_shop]
shop_location_dict[f"{level_name} - {location_name}"] = shop_name
humanspoiler["Shop Locations"] = shop_location_dict
for spoiler_dict in ("Items", "Bosses"):
is_empty = True
for y in humanspoiler[spoiler_dict]:
if humanspoiler[spoiler_dict][y] != {}:
is_empty = False
if is_empty:
del humanspoiler[spoiler_dict]
if self.settings.cb_rando_enabled:
human_cb_type_map = {"cb": " Bananas", "balloons": " Balloons"}
humanspoiler["Colored Banana Locations"] = {}
cb_levels = []
level_dict = {
Levels.DKIsles: "DK Isles",
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",
}
cb_levels = [name for lvl, name in level_dict.items() if IsItemSelected(self.settings.cb_rando_enabled, self.settings.cb_rando_list_selected, lvl)]
cb_kongs = ["Donkey", "Diddy", "Lanky", "Tiny", "Chunky"]
for lvl in cb_levels:
for kng in cb_kongs:
humanspoiler["Colored Banana Locations"][f"{lvl} {kng}"] = {"Balloons": [], "Bananas": []}
for group in self.cb_placements:
lvl_name = level_dict[group["level"]]
map_name = "".join(map(lambda x: x if x.islower() else " " + x, Maps(group["map"]).name)).strip()
join_combos = ["2 D Ship", "5 D Ship", "5 D Temple"]
for combo in join_combos:
if combo in map_name:
map_name = map_name.replace(combo, combo.replace(" ", ""))
humanspoiler["Colored Banana Locations"][f"{lvl_name} {NameFromKong(group['kong'])}"][human_cb_type_map[group["type"]].strip()].append(f"{map_name.strip()}: {group['name']}")
if self.settings.coin_rando:
humanspoiler["Coin Locations"] = {}
coin_levels = ["Japes", "Aztec", "Factory", "Galleon", "Fungi", "Caves", "Castle", "Isles"]
coin_kongs = ["Donkey", "Diddy", "Lanky", "Tiny", "Chunky"]
for lvl in coin_levels:
for kng in coin_kongs:
humanspoiler["Coin Locations"][f"{lvl} {kng}"] = []
for group in self.coin_placements:
lvl_name = level_dict[group["level"]]
idx = 1
if group["level"] == Levels.FungiForest:
idx = 0
map_name = "".join(map(lambda x: x if x.islower() else " " + x, Maps(group["map"]).name)).strip()
join_combos = ["2 D Ship", "5 D Ship", "5 D Temple"]
for combo in join_combos:
if combo in map_name:
map_name = map_name.replace(combo, combo.replace(" ", ""))
humanspoiler["Coin Locations"][f"{lvl_name.split(' ')[idx]} {NameFromKong(group['kong'])}"].append(f"{map_name.strip()}: {group['name']}")
# Playthrough data
humanspoiler["Playthrough"] = self.playthrough
# Woth data
humanspoiler["Way of the Hoard"] = self.woth
humanspoiler["WotH Paths"] = {}
slamCount = 0
pearlCount = 0
for loc, path in self.woth_paths.items():
destination_item = ItemList[self.LocationList[loc].item]
path_dict = {}
for path_loc_id in path:
path_location = self.LocationList[path_loc_id]
path_item = ItemList[path_location.item]
path_dict[path_location.name] = path_item.name
extra = ""
if self.LocationList[loc].item == Items.ProgressiveSlam:
slamCount += 1
extra = " " + str(slamCount)
if self.LocationList[loc].item == Items.Pearl:
pearlCount += 1
extra = " " + str(pearlCount)
humanspoiler["WotH Paths"][destination_item.name + extra] = path_dict
for map_id, path in self.krool_paths.items():
path_dict = {}
for path_loc_id in path:
path_location = self.LocationList[path_loc_id]
path_item = ItemList[path_location.item]
path_dict[path_location.name] = path_item.name
phase_name = boss_map_names.get(map_id, Maps(map_id).name)
humanspoiler["WotH Paths"][phase_name] = path_dict
if self.settings.win_condition_item == WinConditionComplex.dk_rap_items:
for verse_name, path in self.rap_win_con_paths.items():
path_dict = {}
for path_loc_id in path:
path_location = self.LocationList[path_loc_id]
path_item = ItemList[path_location.item]
path_dict[path_location.name] = path_item.name
humanspoiler["WotH Paths"][verse_name] = path_dict
humanspoiler["Other Paths"] = {}
for loc, path in self.other_paths.items():
destination_item = ItemList[self.LocationList[loc].item]
path_dict = {}
for path_loc_id in path:
path_location = self.LocationList[path_loc_id]
path_item = ItemList[path_location.item]
path_dict[path_location.name] = path_item.name
extra = ""
if self.LocationList[loc].item == Items.ProgressiveSlam:
slamCount += 1
extra = " " + str(slamCount)
if self.LocationList[loc].item == Items.Pearl:
pearlCount += 1
extra = " " + str(pearlCount)
humanspoiler["Other Paths"][destination_item.name + extra] = path_dict
if self.settings.shuffle_loading_zones == ShuffleLoadingZones.levels:
# Just show level order
shuffled_exits = OrderedDict()
lobby_entrance_order = {
Transitions.IslesMainToJapesLobby: Levels.JungleJapes,
Transitions.IslesMainToAztecLobby: Levels.AngryAztec,
Transitions.IslesMainToFactoryLobby: Levels.FranticFactory,
Transitions.IslesMainToGalleonLobby: Levels.GloomyGalleon,
Transitions.IslesMainToForestLobby: Levels.FungiForest,
Transitions.IslesMainToCavesLobby: Levels.CrystalCaves,
Transitions.IslesMainToCastleLobby: Levels.CreepyCastle,
Transitions.IslesMainToHelmLobby: Levels.HideoutHelm,
}
lobby_exit_order = {
Transitions.IslesJapesLobbyToMain: Levels.JungleJapes,
Transitions.IslesAztecLobbyToMain: Levels.AngryAztec,
Transitions.IslesFactoryLobbyToMain: Levels.FranticFactory,
Transitions.IslesGalleonLobbyToMain: Levels.GloomyGalleon,
Transitions.IslesForestLobbyToMain: Levels.FungiForest,
Transitions.IslesCavesLobbyToMain: Levels.CrystalCaves,
Transitions.IslesCastleLobbyToMain: Levels.CreepyCastle,
Transitions.IslesHelmLobbyToMain: Levels.HideoutHelm,
}
for transition, vanilla_level in lobby_entrance_order.items():
shuffled_level = lobby_exit_order[self.shuffled_exit_data[transition].reverse]
shuffled_exits[vanilla_level.name] = shuffled_level.name
humanspoiler["Shuffled Level Order"] = shuffled_exits
elif self.settings.shuffle_loading_zones != ShuffleLoadingZones.none:
# Show full shuffled_exits data if more than just levels are shuffled
shuffled_exits = OrderedDict()
level_starts = {
"DK Isles": [
"DK Isles",
"Japes Lobby",
"Aztec Lobby",
"Factory Lobby",
"Galleon Lobby",
"Fungi Lobby",
"Caves Lobby",
"Castle Lobby",
"Helm Lobby",
"Snide's Room",
"Training Grounds",
"Banana Fairy Isle",
"DK's Treehouse",
"K-Lumsy",
],
"Jungle Japes": ["Jungle Japes"],
"Angry Aztec": ["Angry Aztec"],
"Frantic Factory": ["Frantic Factory"],
"Gloomy Galleon": ["Gloomy Galleon"],
"Fungi Forest": ["Fungi Forest"],
"Crystal Caves": ["Crystal Caves"],
"Creepy Castle": ["Creepy Castle"],
"Hideout Helm": ["Hideout Helm"],
}
level_data = {"Other": {}}
for level in level_starts:
level_data[level] = {}
for exit, dest in self.shuffled_exit_data.items():
level_name = "Other"
for level in level_starts:
for search_name in level_starts[level]:
if dest.spoilerName.find(search_name) == 0:
level_name = level
shuffled_exits[ShufflableExits[exit].name] = dest.spoilerName
level_data[level_name][ShufflableExits[exit].name] = dest.spoilerName
humanspoiler["Shuffled Exits"] = shuffled_exits
humanspoiler["Shuffled Exits (Sorted by destination)"] = level_data
if self.settings.has_password:
PASS_NAMES = ["ERROR", "Up", "Down", "Left", "Right", "Z", "Start"]
humanspoiler["Password"] = " ".join([PASS_NAMES[x] for x in self.settings.password])
if self.settings.alter_switch_allocation:
SLAM_NAMES = ["No Slam", "Simian Slam", "Super Simian Slam", "Super Duper Simian Slam"]
humanspoiler["Level Switch Strength"] = {
"Jungle Japes": SLAM_NAMES[self.settings.switch_allocation[Levels.JungleJapes]],
"Angry Aztec": SLAM_NAMES[self.settings.switch_allocation[Levels.AngryAztec]],
"Frantic Factory": SLAM_NAMES[self.settings.switch_allocation[Levels.FranticFactory]],
"Gloomy Galleon": SLAM_NAMES[self.settings.switch_allocation[Levels.GloomyGalleon]],
"Fungi Forest": SLAM_NAMES[self.settings.switch_allocation[Levels.FungiForest]],
"Crystal Caves": SLAM_NAMES[self.settings.switch_allocation[Levels.CrystalCaves]],
"Creepy Castle": SLAM_NAMES[self.settings.switch_allocation[Levels.CreepyCastle]],
}
if self.settings.shuffle_helm_location:
humanspoiler["Level Switch Strength"]["Hideout Helm"] = SLAM_NAMES[self.settings.switch_allocation[Levels.HideoutHelm]]
if len(self.microhints) > 0:
human_microhints = {}
for name, hint in self.microhints.items():
filtered_hint = hint.replace("\x04", "")
filtered_hint = filtered_hint.replace("\x05", "")
filtered_hint = filtered_hint.replace("\x06", "")
filtered_hint = filtered_hint.replace("\x07", "")
filtered_hint = filtered_hint.replace("\x08", "")
filtered_hint = filtered_hint.replace("\x09", "")
filtered_hint = filtered_hint.replace("\x0a", "")
filtered_hint = filtered_hint.replace("\x0b", "")
filtered_hint = filtered_hint.replace("\x0c", "")
filtered_hint = filtered_hint.replace("\x0d", "")
human_microhints[name] = filtered_hint
humanspoiler["Direct Item Hints"] = human_microhints
if len(self.hint_list) > 0:
human_hint_list = {}
# Here it filters out the coloring from the hints to make it actually readable in the spoiler log
for name, hint in self.hint_list.items():
filtered_hint = hint.replace("\x04", "")
filtered_hint = filtered_hint.replace("\x05", "")
filtered_hint = filtered_hint.replace("\x06", "")
filtered_hint = filtered_hint.replace("\x07", "")
filtered_hint = filtered_hint.replace("\x08", "")
filtered_hint = filtered_hint.replace("\x09", "")
filtered_hint = filtered_hint.replace("\x0a", "")
filtered_hint = filtered_hint.replace("\x0b", "")
filtered_hint = filtered_hint.replace("\x0c", "")
filtered_hint = filtered_hint.replace("\x0d", "")
human_hint_list[name] = filtered_hint
humanspoiler["Wrinkly Hints"] = human_hint_list
# humanspoiler["Unhinted Score"] = self.unhinted_score
# humanspoiler["Potentially Awful Locations"] = {}
# for location_description in self.poor_scoring_locations:
# humanspoiler["Potentially Awful Locations"][location_description] = self.poor_scoring_locations[location_description]
# if hasattr(self, "hint_swap_advisory"):
# humanspoiler["Hint Swap Advisory"] = self.hint_swap_advisory
self.json = json.dumps(humanspoiler, indent=4)
def UpdateKasplats(self, kasplat_map: Dict[Locations, Kongs]) -> None:
"""Update kasplat data."""
for kasplat, kong in kasplat_map.items():
# Get kasplat info
location = self.LocationList[kasplat]
mapId = location.map
original = location.kong
self.human_kasplats[location.name] = NameFromKong(kong)
map = None
# See if map already exists in enemy_replacements
for m in self.enemy_replacements:
if m["container_map"] == mapId:
map = m
break
# If not, create it
if map is None:
map = {"container_map": mapId}
self.enemy_replacements.append(map)
# Create kasplat_swaps section if doesn't exist
if "kasplat_swaps" not in map:
map["kasplat_swaps"] = []
# Create swap entry and add to map
swap = {"vanilla_location": original, "replace_with": kong}
map["kasplat_swaps"].append(swap)
def UpdateBarrels(self) -> None:
"""Update list of shuffled barrel minigames."""
self.shuffled_barrel_data = {}
for location, minigame in [(key, value.minigame) for (key, value) in BarrelMetaData.items()]:
self.shuffled_barrel_data[location] = minigame
def UpdateExits(self) -> None:
"""Update list of shuffled exits."""
self.shuffled_exit_data = {}
containerMaps = {}
for key, exit in ShufflableExits.items():
if exit.shuffled:
try:
vanillaBack = exit.back
shuffledBack = ShufflableExits[exit.shuffledId].back
self.shuffled_exit_data[key] = shuffledBack
containerMapId = GetMapId(self.settings, exit.region)
if containerMapId not in containerMaps:
containerMaps[containerMapId] = {"container_map": containerMapId, "zones": []} # DK Isles
loading_zone_mapping = {}
loading_zone_mapping["vanilla_map"] = GetMapId(self.settings, vanillaBack.regionId)
loading_zone_mapping["vanilla_exit"] = GetExitId(vanillaBack)
loading_zone_mapping["new_map"] = GetMapId(self.settings, shuffledBack.regionId)
loading_zone_mapping["new_exit"] = GetExitId(shuffledBack)
containerMaps[containerMapId]["zones"].append(loading_zone_mapping)
except Exception as ex:
print("Exit Update Error with:")
print(ex)
for key, containerMap in containerMaps.items():
self.shuffled_exit_instructions.append(containerMap)
def UpdateLocations(self, locations: Dict[Locations, Location]) -> None:
"""Update location list for what was produced by the fill."""
self.location_data = {}
self.shuffled_kong_placement = {}
# Go ahead and set starting kong
startkong = {"kong": self.settings.starting_kong, "write": 0x151}
trainingGrounds = {"locked": startkong}
self.shuffled_kong_placement["TrainingGrounds"] = trainingGrounds
# Write additional starting kongs to empty cages, if any
emptyCages = [x for x in [Locations.DiddyKong, Locations.LankyKong, Locations.TinyKong, Locations.ChunkyKong] if x not in self.settings.kong_locations]
for emptyCage in emptyCages:
self.WriteKongPlacement(emptyCage, Items.NoItem)
# Loop through locations and set necessary data
for id, location in locations.items():
# (There must be an item here) AND (It must not be a constant item expected to be here) AND (It must be in a location not handled by the full item rando shuffler)
if location.item is not None and location.item is not Items.NoItem and not location.constant and location.type not in self.settings.shuffled_location_types:
self.location_data[id] = location.item
if location.type == Types.Shop:
# Get indices from the location
shop_index = 0 # cranky
if location.movetype in [MoveTypes.Guns, MoveTypes.AmmoBelt]:
shop_index = 1 # funky
elif location.movetype == MoveTypes.Instruments:
shop_index = 2 # candy
kong_indices = [location.kong]
if location.kong == Kongs.any:
kong_indices = [Kongs.donkey, Kongs.diddy, Kongs.lanky, Kongs.tiny, Kongs.chunky]
level_index = location.level
# Isles level index is 8, but we need it to be 7 to fit it in move_data
if level_index == 8:
level_index = 7
# Use the item to find the data to write
updated_item = ItemList[location.item]
move_type = updated_item.movetype
# Determine Price
price = 0
if id in self.settings.prices:
price = self.settings.prices[id]
# Vanilla prices are by item, not by location
if self.settings.random_prices == RandomPrices.vanilla:
price = self.settings.prices[location.item]
# Moves that are set with a single flag (e.g. training barrels, shockwave) are handled differently
if move_type == MoveTypes.Flag:
for kong_index in kong_indices:
self.move_data[0][shop_index][kong_index][level_index] = {
"move_type": "flag",
"flag": updated_item.flag,
"price": price,
}
# This is for every other move typically purchased in a shop
else:
move_level = updated_item.index - 1
move_kong = updated_item.kong
for kong_index in kong_indices:
# print(f"Shop {shop_index}, Kong {kong_index}, Level {level_index} | Move: {move_type} lvl {move_level} for kong {move_kong}")
if (
move_type == MoveTypes.Slam
or move_type == MoveTypes.AmmoBelt
or (move_type == MoveTypes.Guns and move_level > 0)
or (move_type == MoveTypes.Instruments and move_level > 0)
):
move_kong = kong_index
self.move_data[0][shop_index][kong_index][level_index] = {
"move_type": ["special", "slam", "gun", "ammo_belt", "instrument"][move_type],
"move_lvl": move_level,
"move_kong": move_kong,
"price": price,
}
elif location.type == Types.Kong:
self.WriteKongPlacement(id, location.item)
elif location.type == Types.TrainingBarrel and self.settings.training_barrels != TrainingBarrels.normal:
# Use the item to find the data to write
updated_item = ItemList[location.item]
move_type = updated_item.movetype
# Determine Price to be zero because this is a training barrel
price = 0
# Moves that are set with a single flag (e.g. training barrels, shockwave) are handled differently
if move_type == MoveTypes.Flag:
self.move_data[1].append({"move_type": "flag", "flag": updated_item.flag, "price": price})
# This is for every other move typically purchased in a shop
else:
move_level = updated_item.index - 1
move_kong = updated_item.kong
self.move_data[1].append(
{
"move_type": ["special", "slam", "gun", "ammo_belt", "instrument"][move_type],
"move_lvl": move_level,
"move_kong": move_kong % 5, # Shared moves are 5 but should be 0
"price": price,
}
)
# Clean up the default value from this list
for x in range(len(self.move_data[1])):
if self.move_data[1][x] == {"move_type": None}:
del self.move_data[1][x]
break
elif location.type == Types.Shockwave and self.settings.shockwave_status != ShockwaveStatus.vanilla:
# Use the item to find the data to write
updated_item = ItemList[location.item]
move_type = updated_item.movetype
# Determine Price to be zero because BFI is free
price = 0
# Moves that are set with a single flag (e.g. training barrels, shockwave) are handled differently
if move_type == MoveTypes.Flag:
self.move_data[2] = [{"move_type": "flag", "flag": updated_item.flag, "price": price}]
# This is for every other move typically purchased in a shop
else:
move_level = updated_item.index - 1
move_kong = updated_item.kong
self.move_data[2] = [
{
"move_type": ["special", "slam", "gun", "ammo_belt", "instrument"][move_type],
"move_lvl": move_level,
"move_kong": move_kong % 5, # Shared moves are 5 but should be 0
"price": price,
}
]
# Uncomment for more verbose spoiler with all locations
# else:
# self.location_data[id] = Items.NoItem
def WriteKongPlacement(self, locationId: Locations, item: Items) -> None:
"""Write kong placement information for the given kong cage location."""
locationName = "Jungle Japes"
unlockKong = self.settings.diddy_freeing_kong
lockedwrite = 0x152
puzzlewrite = 0x153
if locationId == Locations.LankyKong:
locationName = "Llama Temple"
unlockKong = self.settings.lanky_freeing_kong
lockedwrite = 0x154
puzzlewrite = 0x155
elif locationId == Locations.TinyKong:
locationName = "Tiny Temple"
unlockKong = self.settings.tiny_freeing_kong
lockedwrite = 0x156
puzzlewrite = 0x157
elif locationId == Locations.ChunkyKong:
locationName = "Frantic Factory"
unlockKong = self.settings.chunky_freeing_kong
lockedwrite = 0x158
puzzlewrite = 0x159
lockedkong = {}
lockedkong["kong"] = KongFromItem(item)
lockedkong["write"] = lockedwrite
puzzlekong = {"kong": unlockKong, "write": puzzlewrite}
kongLocation = {"locked": lockedkong, "puzzle": puzzlekong}
self.shuffled_kong_placement[locationName] = kongLocation
def UpdatePlaythrough(self, locations: Dict[Locations, Location], playthroughLocations: List[Sphere]) -> None:
"""Write playthrough as a list of dicts of location/item pairs."""
self.playthrough = {}
i = 0
for sphere in playthroughLocations:
newSphere = {}
newSphere["Available GBs"] = sphere.availableGBs
sphereLocations = list(map(lambda l: locations[l], sphere.locations))
sphereLocations.sort(key=self.ScoreLocations)
for location in sphereLocations:
newSphere[location.name] = ItemList[location.item].name
self.playthrough[i] = newSphere
i += 1
def UpdateWoth(self, locations: Dict[Locations, Location], wothLocations: List[Union[Locations, int]]) -> None:
"""Write woth locations as a dict of location/item pairs."""
self.woth = {}
self.woth_locations = wothLocations
for locationId in wothLocations:
location = locations[locationId]
self.woth[location.name] = ItemList[location.item].name
def ScoreLocations(self, location: Location) -> int:
"""Score a location with the given settings for sorting the Playthrough."""
# The Banana Hoard is likely in its own sphere but if it's not put it last
if location == Locations.BananaHoard:
return 250
# GBs go last, there's a lot of them but they arent important
if ItemList[location.item].type == Types.Banana:
return 100
win_con_type_table = {
WinConditionComplex.req_bean: Types.Bean,
WinConditionComplex.req_bp: Types.Blueprint,
WinConditionComplex.req_companycoins: Types.NintendoCoin, # Also Types.RarewareCoin
WinConditionComplex.req_crown: Types.Crown,
WinConditionComplex.req_fairy: Types.Fairy,
WinConditionComplex.req_gb: Types.Banana, # Also Types.ToughBanana
# WinConditionComplex.req_key: Types.Key,
WinConditionComplex.req_medal: Types.Medal,
WinConditionComplex.req_pearl: Types.Pearl,
WinConditionComplex.req_rainbowcoin: Types.RainbowCoin,
}
# Win condition items are more important than GBs but less than moves
if self.settings.win_condition_item in win_con_type_table:
if ItemList[location.item].type == win_con_type_table[self.settings.win_condition_item]:
return 10
if self.settings.win_condition_item == WinConditionComplex.req_companycoins:
if ItemList[location.item].type == Types.RarewareCoin:
return 10
# Kongs are most the single most important thing and should be at the top of spheres
if ItemList[location.item].type == Types.Kong:
return 0
# Keys are best put first
elif ItemList[location.item].type == Types.Key:
return 1
# Moves are pretty important
elif ItemList[location.item].type == Types.Shop:
return 2
# Everything else here is probably something unusual so it's likely important
else:
return 3
@staticmethod
def GetKroolKeysRequired(keyEvents: List[Events]) -> List[str]:
"""Get key names from required key events to print in the spoiler."""
keys = []
if Events.JapesKeyTurnedIn in keyEvents:
keys.append("Jungle Japes Key")
if Events.AztecKeyTurnedIn in keyEvents:
keys.append("Angry Aztec Key")
if Events.FactoryKeyTurnedIn in keyEvents:
keys.append("Frantic Factory Key")
if Events.GalleonKeyTurnedIn in keyEvents:
keys.append("Gloomy Galleon Key")
if Events.ForestKeyTurnedIn in keyEvents:
keys.append("Fungi Forest Key")
if Events.CavesKeyTurnedIn in keyEvents:
keys.append("Crystal Caves Key")
if Events.CastleKeyTurnedIn in keyEvents:
keys.append("Creepy Castle Key")
if Events.HelmKeyTurnedIn in keyEvents:
keys.append("Hideout Helm Key")
return keys