forked from 2dos/DK64-Randomizer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__init__.py
More file actions
1058 lines (974 loc) · 61.7 KB
/
__init__.py
File metadata and controls
1058 lines (974 loc) · 61.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""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]