Skip to content

Commit 3c490ad

Browse files
authored
Merge pull request #2612 from 2dos/dev
dtm
2 parents 7411f69 + 3bd7ca5 commit 3c490ad

File tree

28 files changed

+1015
-676
lines changed

28 files changed

+1015
-676
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[bumpversion]
22
commit = True
33
tag = False
4-
current_version = 4.11.25
4+
current_version = 4.11.33
55

66
[bumpversion:file:version.py]
77
search = version = "{current_version}"

__init__.py

Lines changed: 109 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import pkgutil
1313
import shutil
1414
import sys
15+
import tempfile
16+
1517

1618
from worlds.dk64.ap_version import version as ap_version
1719

@@ -24,9 +26,6 @@
2426
except ImportError:
2527
pass
2628
if baseclasses_loaded:
27-
baseclasses_path = os.path.dirname(os.path.dirname(BaseClasses.__file__))
28-
if not baseclasses_path.endswith("lib"):
29-
baseclasses_path = os.path.join(baseclasses_path, "lib")
3029

3130
def display_error_box(title: str, text: str) -> bool | None:
3231
"""Display an error message box."""
@@ -38,32 +37,26 @@ def display_error_box(title: str, text: str) -> bool | None:
3837
root.update()
3938

4039
def copy_dependencies(zip_path, file):
41-
"""Copy a ZIP file from the package to a local directory, extracts its contents.
40+
"""Copy a ZIP file from the package to a temporary directory, extracts its contents.
4241
43-
Ensures the destination directory exists.
42+
Ensures the temporary directory exists.
4443
Args:
4544
zip_path (str): The relative path to the ZIP file within the package.
4645
Behavior:
47-
- Creates a `./lib` directory if it does not exist.
46+
- Creates a temporary directory if it does not exist.
4847
- Reads the ZIP file from the package using `pkgutil.get_data`.
49-
- Writes the ZIP file to the `./lib` directory if it does not already exist.
50-
- Extracts the contents of the ZIP file into the `./lib` directory.
48+
- Writes the ZIP file to the temporary directory if it does not already exist.
49+
- Extracts the contents of the ZIP file into the temporary directory.
5150
Prints:
5251
- A message if the ZIP file could not be read.
5352
- A message when the ZIP file is successfully copied.
5453
- A message when the ZIP file is successfully extracted.
5554
"""
56-
# Find the path of BaseClasses, we want to work in the AP directory
57-
# This is a bit of a hack, but it works
58-
# Get the path of BaseClasses
59-
dest_dir = baseclasses_path
60-
# if baseclasses_path does not end in lib, add lib to the end
55+
# Create a temporary directory
56+
temp_dir = tempfile.mkdtemp()
6157

62-
zip_dest = os.path.join(dest_dir, file)
58+
zip_dest = os.path.join(temp_dir, file)
6359
try:
64-
# Ensure the destination directory exists
65-
os.makedirs(dest_dir, exist_ok=True)
66-
6760
# Load the ZIP file from the package
6861
zip_data = pkgutil.get_data(__name__, zip_path)
6962
# Check if the zip already exists in the destination
@@ -78,26 +71,37 @@ def copy_dependencies(zip_path, file):
7871

7972
# Extract the ZIP file
8073
with zipfile.ZipFile(zip_dest, "r") as zip_ref:
81-
zip_ref.extractall(dest_dir)
82-
print(f"Extracted {zip_dest} into {dest_dir}")
74+
zip_ref.extractall(temp_dir)
75+
print(f"Extracted {zip_dest} into {temp_dir}")
76+
8377
except PermissionError:
8478
display_error_box("Permission Error", "Unable to install Dependencies to AP, please try to install AP as an admin.")
8579
raise PermissionError("Permission Error: Unable to install Dependencies to AP, please try to install AP as an admin.")
8680

81+
# Add the temporary directory to sys.path
82+
if temp_dir not in sys.path:
83+
sys.path.insert(0, temp_dir)
84+
8785
platform_type = sys.platform
88-
# if the file pyxdelta.cp310-win_amd64.pyd exists, delete pyxdelta.cp310-win_amd64.pyd and PIL and pillow-10.3.0.dist-info and pyxdelta-0.2.0.dist-info
89-
if os.path.exists(f"{baseclasses_path}/pyxdelta.cp310-win_amd64.pyd"):
90-
os.remove(f"{baseclasses_path}/pyxdelta.cp310-win_amd64.pyd")
91-
if os.path.exists(f"{baseclasses_path}/PIL"):
92-
shutil.rmtree(f"{baseclasses_path}/PIL")
93-
if os.path.exists(f"{baseclasses_path}/pillow-10.3.0.dist-info"):
94-
shutil.rmtree(f"{baseclasses_path}/pillow-10.3.0.dist-info")
95-
if os.path.exists(f"{baseclasses_path}/pyxdelta-0.2.0.dist-info"):
96-
shutil.rmtree(f"{baseclasses_path}/pyxdelta-0.2.0.dist-info")
97-
if os.path.exists(f"{baseclasses_path}/windows.zip"):
98-
os.remove(f"{baseclasses_path}/windows.zip")
99-
if os.path.exists(f"{baseclasses_path}/linux.zip"):
100-
os.remove(f"{baseclasses_path}/linux.zip")
86+
baseclasses_path = os.path.dirname(os.path.dirname(BaseClasses.__file__))
87+
if not baseclasses_path.endswith("lib"):
88+
baseclasses_path = os.path.join(baseclasses_path, "lib")
89+
# Remove ANY PIL folders from the baseclasses_path
90+
# Or Pyxdelta or pillow folders
91+
try:
92+
for folder in os.listdir(baseclasses_path):
93+
if folder.startswith("PIL") or folder.startswith("pyxdelta") or folder.startswith("pillow"):
94+
folder_path = os.path.join(baseclasses_path, folder)
95+
if os.path.isdir(folder_path):
96+
shutil.rmtree(folder_path)
97+
elif os.path.isfile(folder_path):
98+
os.remove(folder_path)
99+
# Also if its windows.zip or linux.zip, remove it
100+
if folder.startswith("windows.zip") or folder.startswith("linux.zip"):
101+
os.remove(os.path.join(baseclasses_path, folder))
102+
except Exception as e:
103+
pass
104+
101105
if platform_type == "win32":
102106
zip_path = "vendor/windows.zip" # Path inside the package
103107
copy_dependencies(zip_path, "windows.zip")
@@ -111,6 +115,7 @@ def copy_dependencies(zip_path, file):
111115
sys.path.append("worlds/dk64/archipelago/")
112116
sys.path.append("custom_worlds/dk64.apworld/dk64/")
113117
sys.path.append("custom_worlds/dk64.apworld/dk64/archipelago/")
118+
114119
import randomizer.ItemPool as DK64RItemPool
115120

116121
from randomizer.Enums.Items import Items as DK64RItems
@@ -133,9 +138,11 @@ def copy_dependencies(zip_path, file):
133138
from randomizer.CompileHints import compileMicrohints
134139
from randomizer.Enums.Types import Types
135140
from randomizer.Enums.Kongs import Kongs
141+
from randomizer.Enums.Levels import Levels
136142
from randomizer.Enums.Maps import Maps
137143
from randomizer.Enums.Locations import Locations as DK64RLocations
138-
from randomizer.Enums.Settings import WinConditionComplex
144+
from randomizer.Enums.Settings import WinConditionComplex, SwitchsanityLevel
145+
from randomizer.Enums.Switches import Switches
139146
from randomizer.Lists import Item as DK64RItem
140147
from worlds.LauncherComponents import Component, components, Type, icon_paths
141148
import randomizer.ShuffleExits as ShuffleExits
@@ -267,6 +274,16 @@ def generate_early(self):
267274
settings_dict = decrypt_settings_string_enum(self.settings_string)
268275
settings_dict["archipelago"] = True
269276
settings_dict["starting_kongs_count"] = self.options.starting_kong_count.value
277+
settings_dict["open_lobbies"] = self.options.open_lobbies.value
278+
settings_dict["krool_in_boss_pool"] = self.options.krool_in_boss_pool.value
279+
settings_dict["helm_phase_count"] = self.options.helm_phase_count.value
280+
settings_dict["krool_phase_count"] = self.options.krool_phase_count.value
281+
settings_dict["medal_cb_req"] = self.options.medal_cb_req.value
282+
settings_dict["mermaid_gb_pearls"] = self.options.mermaid_gb_pearls.value
283+
settings_dict["medal_requirement"] = self.options.medal_requirement.value
284+
settings_dict["rareware_gb_fairies"] = self.options.rareware_gb_fairies.value
285+
settings_dict["krool_key_count"] = self.options.krool_key_count.value
286+
settings_dict["switchsanity"] = self.options.switchsanity.value
270287
settings_dict["starting_keys_list_selected"] = []
271288
for item in self.options.start_inventory:
272289
if item == "Key 1":
@@ -289,9 +306,29 @@ def generate_early(self):
289306
settings_dict["win_condition_item"] = WinConditionComplex.req_key
290307
settings_dict["win_condition_count"] = 8
291308
settings = Settings(settings_dict, self.random)
309+
# 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.
310+
if hasattr(self.multiworld, "generation_is_fake"):
311+
if hasattr(self.multiworld, "re_gen_passthrough"):
312+
if "Donkey Kong 64" in self.multiworld.re_gen_passthrough:
313+
passthrough = self.multiworld.re_gen_passthrough["Donkey Kong 64"]
314+
settings.level_order = passthrough["LevelOrder"]
315+
settings.starting_kong_list = passthrough["StartingKongs"]
316+
settings.BossBananas = passthrough["BossBananas"]
317+
settings.boss_maps = passthrough["BossMaps"]
318+
settings.boss_kongs = passthrough["BossKongs"]
319+
settings.lanky_freeing_kong = passthrough["LankyFreeingKong"]
320+
settings.helm_order = passthrough["HelmOrder"]
321+
# There's multiple sources of truth for helm order.
322+
settings.helm_donkey = 0 in settings.helm_order
323+
settings.helm_diddy = 4 in settings.helm_order
324+
settings.helm_lanky = 3 in settings.helm_order
325+
settings.helm_tiny = 2 in settings.helm_order
326+
settings.helm_chunky = 1 in settings.helm_order
292327
# We need to set the freeing kongs here early, as they won't get filled in any other part of the AP process
293328
settings.diddy_freeing_kong = self.random.randint(0, 4)
294-
settings.lanky_freeing_kong = self.random.randint(0, 4)
329+
# Lanky freeing kong actually changes logic, so UT should use the slot data rather than genning a new one.
330+
if not hasattr(self.multiworld, "generation_is_fake"):
331+
settings.lanky_freeing_kong = self.random.randint(0, 4)
295332
settings.tiny_freeing_kong = self.random.randint(0, 4)
296333
settings.chunky_freeing_kong = self.random.randint(0, 4)
297334
spoiler = Spoiler(settings)
@@ -314,7 +351,9 @@ def generate_early(self):
314351
randomize_enemies_0(spoiler)
315352
# Handle Loading Zones - this will handle LO and (someday?) LZR appropriately
316353
if spoiler.settings.shuffle_loading_zones != ShuffleLoadingZones.none:
317-
ShuffleExits.ExitShuffle(spoiler, skip_verification=True)
354+
# UT should not reshuffle the level order, but should update the exits
355+
if not hasattr(self.multiworld, "generation_is_fake"):
356+
ShuffleExits.ExitShuffle(spoiler, skip_verification=True)
318357
spoiler.UpdateExits()
319358

320359
def create_regions(self) -> None:
@@ -549,6 +588,14 @@ def fill_slot_data(self) -> dict:
549588
"FairyRequirement": self.logic_holder.settings.rareware_gb_fairies,
550589
"MermaidPearls": self.logic_holder.settings.mermaid_gb_pearls,
551590
"JetpacReq": self.logic_holder.settings.medal_requirement,
591+
"BossBananas": ", ".join([str(cost) for cost in self.logic_holder.settings.BossBananas]),
592+
"BossMaps": ", ".join(map.name for map in self.logic_holder.settings.boss_maps),
593+
"BossKongs": ", ".join(kong.name for kong in self.logic_holder.settings.boss_kongs),
594+
"LankyFreeingKong": self.logic_holder.settings.lanky_freeing_kong,
595+
"HelmOrder": ", ".join([str(room) for room in self.logic_holder.settings.helm_order]),
596+
"OpenLobbies": self.logic_holder.settings.open_lobbies,
597+
"KroolInBossPool": self.logic_holder.settings.krool_in_boss_pool,
598+
"SwitchSanity": {switch.name: {"kong": data.kong.name, "type": data.switch_type.name} for switch, data in self.logic_holder.settings.switchsanity_data.items()},
552599
}
553600

554601
def write_spoiler(self, spoiler_handle: typing.TextIO):
@@ -579,6 +626,13 @@ def write_spoiler(self, spoiler_handle: typing.TextIO):
579626
spoiler_handle.write("\n")
580627
spoiler_handle.write("Removed Barriers: " + ", ".join([barrier.name for barrier in self.logic_holder.settings.remove_barriers_selected]))
581628
spoiler_handle.write("\n")
629+
if self.logic_holder.settings.switchsanity != SwitchsanityLevel.off:
630+
spoiler_handle.write("Switchsanity Settings: \n")
631+
for switch, data in self.logic_holder.settings.switchsanity_data.items():
632+
if self.logic_holder.settings.switchsanity == SwitchsanityLevel.helm_access:
633+
if switch not in (Switches.IslesHelmLobbyGone, Switches.IslesMonkeyport):
634+
continue
635+
spoiler_handle.write(f" - {switch.name}: {data.kong.name} with {data.switch_type.name}\n")
582636
spoiler_handle.write("Generated Time: " + time.strftime("%d-%m-%Y %H:%M:%S", time.gmtime()) + " GMT")
583637
spoiler_handle.write("\n")
584638
spoiler_handle.write("Randomizer Version: " + self.logic_holder.settings.version)
@@ -607,3 +661,23 @@ def collect(self, state: CollectionState, item: Item) -> bool:
607661
if change:
608662
self.logic_holder.UpdateFromArchipelagoItems(state)
609663
return change
664+
665+
def interpret_slot_data(self, slot_data: dict[str, any]) -> dict[str, any]:
666+
"""Parse slot data for any logical bits that need to match the real generation. Used by Universal Tracker."""
667+
# Parse the string data
668+
level_order = slot_data["LevelOrder"].split(", ")
669+
starting_kongs = slot_data["StartingKongs"].split(", ")
670+
boss_bananas = slot_data["BossBananas"].split(", ")
671+
boss_maps = slot_data["BossMaps"].split(", ")
672+
boss_kongs = slot_data["BossKongs"].split(", ")
673+
helm_order = slot_data["HelmOrder"].split(", ")
674+
675+
relevant_data = {}
676+
relevant_data["LevelOrder"] = dict(enumerate([Levels[level] for level in level_order], start=1))
677+
relevant_data["StartingKongs"] = [Kongs[kong] for kong in starting_kongs]
678+
relevant_data["BossBananas"] = [int(cost) for cost in boss_bananas]
679+
relevant_data["BossMaps"] = [Maps[map] for map in boss_maps]
680+
relevant_data["BossKongs"] = [Kongs[kong] for kong in boss_kongs]
681+
relevant_data["LankyFreeingKong"] = slot_data["LankyFreeingKong"]
682+
relevant_data["HelmOrder"] = [int(room) for room in helm_order]
683+
return relevant_data

ap_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Holds the version for Archipelago."""
22

3-
version = "1.0.22"
3+
version = "1.0.27"

archipelago/Logic.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,15 @@ def galleonGatesStayOpen(self) -> bool:
657657
MiscChangesSelected.remove_galleon_ship_timers,
658658
)
659659

660+
@lru_cache(maxsize=None)
661+
def cabinBarrelMoved(self) -> bool:
662+
"""Determine whether the upper cabin rocketbarrel has been moved."""
663+
return IsItemSelected(
664+
self.settings.quality_of_life,
665+
self.settings.misc_changes_selected,
666+
MiscChangesSelected.move_spring_cabin_rocketbarrel,
667+
)
668+
660669
def canOpenLlamaTemple(self):
661670
"""Determine whether the switches on the Llama Temple can be shot."""
662671
if not (self.checkBarrier(RemovedBarriersSelected.aztec_llama_switches) or Events.LlamaFreed in self.Events):

0 commit comments

Comments
 (0)