diff --git a/archipelago.json b/archipelago.json index 390569551..754d957a5 100644 --- a/archipelago.json +++ b/archipelago.json @@ -1,6 +1,6 @@ { "minimum_ap_version": "0.6.5", - "world_version": "1.5.5", + "world_version": "1.5.6", "authors": ["2dos", "AlmostSeagull", "Ballaam", "Green Bean", "Killklli", "Lrauq", "PoryGone", "Umed"], "version": 7, "compatible_version": 7, diff --git a/archipelago/FillSettings.py b/archipelago/FillSettings.py index 1e6f5e94e..148a7a024 100644 --- a/archipelago/FillSettings.py +++ b/archipelago/FillSettings.py @@ -4,6 +4,8 @@ that was previously in the generate_early method. """ +from random import Random + from randomizer.Settings import Settings from randomizer.Enums.Settings import ( ActivateAllBananaports, @@ -363,18 +365,22 @@ def apply_archipelago_settings(settings_dict: dict, options, multiworld) -> None settings_dict["no_consumable_upgrades"] = options.remove_bait_potions.value -def apply_blocker_settings(settings_dict: dict, options) -> None: +def generate_blocker(option_value: str, blocker_max: int, random: Random): + """Randomize a B. Locker value, either within a range or up to the maximum.""" + upper_bound = blocker_max if option_value == "random" else int(option_value.split("-")[1]) + 1 + lower_bound = 0 if option_value == "random" else int(option_value.split("-")[0]) + return random.randrange(lower_bound, upper_bound) + + +def apply_blocker_settings(settings_dict: dict, options, random_obj) -> None: """Apply level blocker settings.""" - blocker_options = [ - options.level_blockers.value.get("level_1", 0), - options.level_blockers.value.get("level_2", 0), - options.level_blockers.value.get("level_3", 0), - options.level_blockers.value.get("level_4", 0), - options.level_blockers.value.get("level_5", 0), - options.level_blockers.value.get("level_6", 0), - options.level_blockers.value.get("level_7", 0), - options.level_blockers.value.get("level_8", 64), - ] + blocker_options = [0, 0, 0, 0, 0, 0, 0, 0] + for blocker, amount in options.level_blockers.value.items(): + blocker_number = int(blocker.removeprefix("level_")) - 1 + try: + blocker_options[blocker_number] = int(amount) + except (TypeError, ValueError): + blocker_options[blocker_number] = generate_blocker(amount, options.blocker_max.value, random_obj) # Blocker settings - prioritize chaos blockers, then randomization setting settings_dict["maximize_helm_blocker"] = options.maximize_level8_blocker.value @@ -866,7 +872,7 @@ def fillsettings(options, multiworld, random_obj): # Apply all setting categories apply_archipelago_settings(settings_dict, options, multiworld) - apply_blocker_settings(settings_dict, options) + apply_blocker_settings(settings_dict, options, random_obj) apply_item_randomization_settings(settings_dict, options) apply_hard_mode_settings(settings_dict, options) apply_kong_settings(settings_dict, options) diff --git a/archipelago/Logic.py b/archipelago/Logic.py index d585df6da..92790a41a 100644 --- a/archipelago/Logic.py +++ b/archipelago/Logic.py @@ -1491,33 +1491,11 @@ def CoinDoorOpened(self): def CanFreeDiddy(self): """Check if the cage locking Diddy's vanilla location can be opened.""" - return self.spoiler.LocationList[Locations.DiddyKong].item == Items.NoItem or self.hasMoveSwitchsanity(Switches.JapesFreeKong) + return self.hasMoveSwitchsanity(Switches.JapesFreeKong) def CanOpenJapesGates(self): """Check if we can pick up the item inside Diddy's cage, thus opening the gates in Japes.""" - caged_item_id = self.spoiler.LocationList[Locations.JapesDonkeyFreeDiddy].item - # If it's NoItem, then the gates are already open - if caged_item_id == Items.NoItem: - return True - # If we can't free Diddy, then we can't access the item so we can't reach the item - if not self.CanFreeDiddy(): - return False - # If we are the right kong, then we can always get the item - if self.IsKong(self.settings.diddy_freeing_kong): - return True - # If we aren't the right kong, we need free trade to be on - elif self.settings.free_trade_items: - # During the fill we can't assume this item is accessible quite yet - this could cause errors with placing items in the back of Japes - if caged_item_id is None: - return False - # If it's not a blueprint, free trade gets us the item - if ItemList[caged_item_id].type != Types.Blueprint: - return True - # But if it is a blueprint, we need to check blueprint access (which checks blueprint free trade) - else: - return self.BlueprintAccess(ItemList[caged_item_id]) - # If we failed to hit a successful condition, we failed to reach the caged item - return False + return self.CanFreeDiddy() def CanFreeTiny(self): """Check if kong at Tiny location can be freed.""" diff --git a/archipelago/Options.py b/archipelago/Options.py index 96493f854..0355625d2 100644 --- a/archipelago/Options.py +++ b/archipelago/Options.py @@ -115,7 +115,6 @@ def verify(self, world: type[World], player_name: str, plando_options: PlandoOpt accumulated_errors = [] for key, value in self.value.items(): - print(f"Checking {key}: {value}") max = self.max_values_dict[key] if isinstance(value, numbers.Integral): value = int(value) @@ -140,7 +139,6 @@ def verify(self, world: type[World], player_name: str, plando_options: PlandoOpt accumulated_errors.append(f"{key}: Upper edge of range {bound} is higher than maximum allowed value {max}") elif bound < self.min: accumulated_errors.append(f"{key}: Lower edge of range {bound} is lower than minimum allowed value {self.min}") - print("\n".join(accumulated_errors)) if accumulated_errors: raise OptionError("Found errors with option goal_quantity:\n" + "\n".join(accumulated_errors)) @@ -629,6 +627,7 @@ class LevelBlockers(OptionDict): min = 0 max = 201 + allowed_keys = ["level_1", "level_2", "level_3", "level_4", "level_5", "level_6", "level_7", "level_8"] default = { "level_1": 0, "level_2": 0, @@ -640,6 +639,43 @@ class LevelBlockers(OptionDict): "level_8": 64, } + def verify(self, world: type[World], player_name: str, plando_options: PlandoOptions) -> None: + """Verify B. Lockers.""" + super(LevelBlockers, self).verify(world, player_name, plando_options) + + for key in self.value.keys(): + if key not in self.allowed_keys: + raise OptionError(f"{key} is not a valid key for level_blockers.") + + accumulated_errors = [] + + for key, value in self.value.items(): + if isinstance(value, numbers.Integral): + value = int(value) + if value > self.max: + accumulated_errors.append(f"{key}: {value} is higher than maximum allowed value {self.max}") + elif value < self.min: + accumulated_errors.append(f"{key}: {value} is lower than minimum allowed value {self.min}") + else: + if value == "random": + continue + split = value.split("-") + if len(split) != 2: + accumulated_errors.append(f'{key}: {value} is not an integer or range, nor is it "random".') + else: + for bound in split: + try: + bound = int(bound) + except (ValueError, TypeError): + accumulated_errors.append(f'{key}: {value} is not an integer or range, nor is it "random".') + continue + if bound > self.max: + accumulated_errors.append(f"{key}: Upper edge of range {bound} is higher than maximum allowed value {self.max}") + elif bound < self.min: + accumulated_errors.append(f"{key}: Lower edge of range {bound} is lower than minimum allowed value {self.min}") + if accumulated_errors: + raise OptionError("Found errors with option level_blockers:\n" + "\n".join(accumulated_errors)) + class BouldersInPool(Toggle): """Determines if throwing boulders/barrels spawn a check.""" diff --git a/archipelago/client/emu_loader.py b/archipelago/client/emu_loader.py index b1b0b6998..2fc56f4b7 100644 --- a/archipelago/client/emu_loader.py +++ b/archipelago/client/emu_loader.py @@ -786,7 +786,7 @@ def validate_rom(self) -> bool: EMULATOR_CONFIGS = { - Emulators.Project64_v4: EmulatorInfo(Emulators.Project64_v4, "Project64 4.0", "project64", False, None, False, 0xFDD00000, 0xFE1FFFFF), + Emulators.Project64_v4: EmulatorInfo(Emulators.Project64_v4, "Project64 4.0", "project64", False, None, False, 0xFDD00000, 0xFE1FFFFF, scan_memory_for_signature=True), Emulators.BizHawk: EmulatorInfo(Emulators.BizHawk, "Bizhawk", "emuhawk", True, "mupen64plus.dll", False, 0x5A000, 0x5658DF, linux_dll_name="libmupen64plus.so"), Emulators.RMG: EmulatorInfo(Emulators.RMG, "Rosalie's Mupen GUI", "rmg", True, "mupen64plus.dll", True, 0x29C15D8, 0x2FC15D8, extra_offset=0x80000000, linux_dll_name="libmupen64plus.so"), Emulators.Simple64: EmulatorInfo(Emulators.Simple64, "simple64", "simple64-gui", True, "libmupen64plus.dll", True, 0x1380000, 0x29C95D8, linux_dll_name="libmupen64plus.so"), @@ -799,7 +799,7 @@ def validate_rom(self) -> bool: Emulators.RetroArch: EmulatorInfo( Emulators.RetroArch, "RetroArch", "retroarch", True, "mupen64plus_next_libretro.dll", True, 0, 0xFFFFFF, range_step=4, linux_dll_name="mupen64plus_next_libretro.so" ), - Emulators.Project64: EmulatorInfo(Emulators.Project64, "Project64", "project64", False, None, False, 0xDFD00000, 0xE01FFFFF), + Emulators.Project64: EmulatorInfo(Emulators.Project64, "Project64", "project64", False, None, False, 0xDFD00000, 0xE01FFFFF, scan_memory_for_signature=True), Emulators.Gopher64: EmulatorInfo(Emulators.Gopher64, "Gopher64", "gopher64", False, None, False, 0, 0, scan_memory_for_signature=True), Emulators.Ares: EmulatorInfo(Emulators.Ares, "ares", "ares", False, None, False, 0, 0, scan_memory_for_signature=True, signature_alignment=0x1000), }