diff --git a/worlds/jakanddaxter/__init__.py b/worlds/jakanddaxter/__init__.py index d508e967..9a2cb302 100644 --- a/worlds/jakanddaxter/__init__.py +++ b/worlds/jakanddaxter/__init__.py @@ -34,9 +34,9 @@ from .locations import (JakAndDaxterLocation, cache_location_table, orb_location_table) from .regions import create_regions -from .rules import (enforce_multiplayer_limits, - enforce_singleplayer_limits, - verify_orb_trade_amounts, +from .rules import (enforce_mp_absolute_limits, + enforce_mp_friendly_limits, + enforce_sp_limits, set_orb_trade_rule) from .locs import (cell_locations as cells, scout_locations as scouts, @@ -258,18 +258,31 @@ class JakAndDaxterWorld(World): self.options.mountain_pass_cell_count.value = self.power_cell_thresholds[1] self.options.lava_tube_cell_count.value = self.power_cell_thresholds[2] - # Store this for remove function. - self.power_cell_thresholds_minus_one = [x - 1 for x in self.power_cell_thresholds] - - # For the fairness of other players in a multiworld game, enforce some friendly limitations on our options, - # so we don't cause chaos during seed generation. These friendly limits should **guarantee** a successful gen. - # We would have done this earlier, but we needed to sort the power cell thresholds first. + # We would have done this earlier, but we needed to sort the power cell thresholds first. Don't worry, we'll + # come back to them. enforce_friendly_options = self.settings.enforce_friendly_options - if enforce_friendly_options: - if self.multiworld.players > 1: - enforce_multiplayer_limits(self) + if self.multiworld.players == 1: + # For singleplayer games, always enforce/clamp the cell counts to valid values. + enforce_sp_limits(self) + else: + if enforce_friendly_options: + # For multiplayer games, we have a host setting to make options fair/sane for other players. + # If this setting is enabled, enforce/clamp some friendly limitations on our options. + enforce_mp_friendly_limits(self) else: - enforce_singleplayer_limits(self) + # Even if the setting is disabled, some values must be clamped to avoid generation errors. + enforce_mp_absolute_limits(self) + + # That's right, set the collection of thresholds again. Don't just clamp the values without updating this list! + self.power_cell_thresholds = [ + self.options.fire_canyon_cell_count.value, + self.options.mountain_pass_cell_count.value, + self.options.lava_tube_cell_count.value, + 100, # The 100 Power Cell Door. + ] + + # Now that the threshold list is finalized, store this for the remove function. + self.power_cell_thresholds_minus_one = [x - 1 for x in self.power_cell_thresholds] # Calculate the number of power cells needed for full region access, the number being replaced by traps, # and the number of remaining filler. @@ -282,11 +295,6 @@ class JakAndDaxterWorld(World): self.options.filler_power_cells_replaced_with_traps.value = self.total_trap_cells self.total_filler_cells = non_prog_cells - self.total_trap_cells - # Verify that we didn't overload the trade amounts with more orbs than exist in the world. - # This is easy to do by accident even in a singleplayer world. - self.total_trade_orbs = (9 * self.options.citizen_orb_trade_amount) + (6 * self.options.oracle_orb_trade_amount) - verify_orb_trade_amounts(self) - # Cache the orb bundle size and item name for quicker reference. if self.options.enable_orbsanity == options.EnableOrbsanity.option_per_level: self.orb_bundle_size = self.options.level_orbsanity_bundle_size.value diff --git a/worlds/jakanddaxter/docs/en_Jak and Daxter The Precursor Legacy.md b/worlds/jakanddaxter/docs/en_Jak and Daxter The Precursor Legacy.md index 6cf8ae54..77fbd514 100644 --- a/worlds/jakanddaxter/docs/en_Jak and Daxter The Precursor Legacy.md +++ b/worlds/jakanddaxter/docs/en_Jak and Daxter The Precursor Legacy.md @@ -18,7 +18,7 @@ - [What do Traps do?](#what-do-traps-do) - [What kind of Traps are there?](#what-kind-of-traps-are-there) - [I got soft-locked and cannot leave, how do I get out of here?](#i-got-soft-locked-and-cannot-leave-how-do-i-get-out-of-here) -- [Why did I get an Option Error when generating a seed, and how do I fix it?](#why-did-i-get-an-option-error-when-generating-a-seed-and-how-do-i-fix-it) +- [How do I generate seeds with 1 Orb Orbsanity and other extreme options?](#how-do-i-generate-seeds-with-1-orb-orbsanity-and-other-extreme-options) - [How do I check my player options in-game?](#how-do-i-check-my-player-options-in-game) - [How does the HUD work?](#how-does-the-hud-work) - [I think I found a bug, where should I report it?](#i-think-i-found-a-bug-where-should-i-report-it) @@ -201,16 +201,19 @@ Open the game's menu, navigate to `Options`, then `Archipelago Options`, then `W Selecting this option will ask if you want to be teleported to Geyser Rock. From there, you can teleport back to the nearest sage's hut to continue your journey. -## Why did I get an Option Error when generating a seed and how do I fix it +## How do I generate seeds with 1 orb orbsanity and other extreme options? Depending on your player YAML, Jak and Daxter can have a lot of items, which can sometimes be overwhelming or disruptive to multiworld games. There are also options that are mutually incompatible with each other, even in a solo game. To prevent the game from disrupting multiworlds, or generating an impossible solo seed, some options have -Singleplayer and Multiplayer Minimums and Maximums, collectively called "friendly limits." +"friendly limits" that prevent you from choosing more extreme values. -If you're generating a solo game, or your multiworld host agrees to your request, you can override those limits by -editing the `host.yaml`. In the Archipelago Launcher, click `Open host.yaml`, then search for `jakanddaxter_options`, -then search for `enforce_friendly_options`, then change this value from `true` to `false`. Disabling this allows for -more disruptive and challenging options, but it may cause seed generation to fail. **Use at your own risk!** +You can override **some**, not all, of those limits by editing the `host.yaml`. In the Archipelago Launcher, click +`Open host.yaml`, then search for `jakanddaxter_options`, then search for `enforce_friendly_options`, then change this +value from `true` to `false`. You can then generate a seed locally, and upload that to the Archipelago website to host +for you (or host it yourself). + +**Remember:** disabling this setting allows for more disruptive and challenging options, but it may cause seed +generation to fail. **Use at your own risk!** ## How do I check my player options in-game When you connect your text client to the Archipelago Server, the server will tell the game what options were chosen diff --git a/worlds/jakanddaxter/docs/setup_en.md b/worlds/jakanddaxter/docs/setup_en.md index 509fb3ad..9cd892a9 100644 --- a/worlds/jakanddaxter/docs/setup_en.md +++ b/worlds/jakanddaxter/docs/setup_en.md @@ -4,7 +4,6 @@ - A legally purchased copy of *Jak And Daxter: The Precursor Legacy.* - [The OpenGOAL Launcher](https://opengoal.dev/) -- [The Jak and Daxter .APWORLD package](https://github.com/ArchipelaGOAL/Archipelago/releases) At this time, this method of setup works on Windows only, but Linux support is a strong likelihood in the near future as OpenGOAL itself supports Linux. @@ -75,7 +74,7 @@ If you are in the middle of an async game, and you do not want to update the mod ### New Game - Run the Archipelago Launcher. -- From the right-most list, find and click `Jak and Daxter Client`. +- From the client list, find and click `Jak and Daxter Client`. - 3 new windows should appear: - The OpenGOAL compiler will launch and compile the game. They should take about 30 seconds to compile. - You should hear a musical cue to indicate the compilation was a success. If you do not, see the Troubleshooting section. diff --git a/worlds/jakanddaxter/options.py b/worlds/jakanddaxter/options.py index bd007e26..d36303b0 100644 --- a/worlds/jakanddaxter/options.py +++ b/worlds/jakanddaxter/options.py @@ -1,22 +1,78 @@ from dataclasses import dataclass from functools import cached_property -from Options import PerGameCommonOptions, StartInventoryPool, Toggle, Choice, Range, DefaultOnToggle, OptionCounter +from Options import PerGameCommonOptions, StartInventoryPool, Toggle, Choice, Range, DefaultOnToggle, OptionCounter, \ + AssembleOptions from .items import trap_item_table -class StaticGetter: - def __init__(self, func): - self.fget = func +class readonly_classproperty: + """This decorator is used for getting friendly or unfriendly range_end values for options like FireCanyonCellCount + and CitizenOrbTradeAmount. We only need to provide a getter as we will only be setting a single int to one of two + values.""" + def __init__(self, getter): + self.getter = getter def __get__(self, instance, owner): - return self.fget(owner) + return self.getter(owner) -@StaticGetter +@readonly_classproperty def determine_range_end(cls) -> int: - from . import JakAndDaxterWorld - enforce_friendly_options = JakAndDaxterWorld.settings.enforce_friendly_options - return cls.friendly_maximum if enforce_friendly_options else cls.absolute_maximum + from . import JakAndDaxterWorld # Avoid circular imports. + friendly = JakAndDaxterWorld.settings.enforce_friendly_options + return cls.friendly_maximum if friendly else cls.absolute_maximum + + +class classproperty: + """This decorator (?) is used for getting and setting friendly or unfriendly option values for the Orbsanity + options.""" + def __init__(self, getter, setter): + self.getter = getter + self.setter = setter + + def __get__(self, obj, value): + return self.getter(obj) + + def __set__(self, obj, value): + self.setter(obj, value) + + +class AllowedChoiceMeta(AssembleOptions): + """This metaclass overrides AssembleOptions and provides inheriting classes a way to filter out "disallowed" values + by way of implementing get_disallowed_options. This function is used by Jak and Daxter to check host.yaml settings + without circular imports or breaking the settings API.""" + _name_lookup: dict[int, str] + _options: dict[str, int] + + def __new__(mcs, name, bases, attrs): + ret = super().__new__(mcs, name, bases, attrs) + ret._name_lookup = attrs["name_lookup"] + ret._options = attrs["options"] + return ret + + def set_name_lookup(cls, value : dict[int, str]): + cls._name_lookup = value + + def get_name_lookup(cls) -> dict[int, str]: + cls._name_lookup = {k: v for k, v in cls._name_lookup.items() if k not in cls.get_disallowed_options()} + return cls._name_lookup + + def set_options(cls, value: dict[str, int]): + cls._options = value + + def get_options(cls) -> dict[str, int]: + cls._options = {k: v for k, v in cls._options.items() if v not in cls.get_disallowed_options()} + return cls._options + + def get_disallowed_options(cls): + return {} + + name_lookup = classproperty(get_name_lookup, set_name_lookup) + options = classproperty(get_options, set_options) + + +class AllowedChoice(Choice, metaclass=AllowedChoiceMeta): + pass class EnableMoveRandomizer(Toggle): @@ -44,12 +100,13 @@ class EnableOrbsanity(Choice): default = 0 -class GlobalOrbsanityBundleSize(Choice): +class GlobalOrbsanityBundleSize(AllowedChoice): """The orb bundle size for Global Orbsanity. This only applies if "Enable Orbsanity" is set to "Global." There are 2000 orbs in the game, so your bundle size must be a factor of 2000. - Multiplayer Minimum: 10 - Multiplayer Maximum: 200""" + This value is restricted to safe minimum and maximum values to ensure valid singleplayer games and + non-disruptive multiplayer games, but the host can remove this restriction by turning off enforce_friendly_options + in host.yaml.""" display_name = "Global Orbsanity Bundle Size" option_1_orb = 1 option_2_orbs = 2 @@ -75,12 +132,33 @@ class GlobalOrbsanityBundleSize(Choice): friendly_maximum = 200 default = 20 + @classmethod + def get_disallowed_options(cls) -> set[int]: + try: + from . import JakAndDaxterWorld + if JakAndDaxterWorld.settings.enforce_friendly_options: + return {cls.option_1_orb, + cls.option_2_orbs, + cls.option_4_orbs, + cls.option_5_orbs, + cls.option_8_orbs, + cls.option_250_orbs, + cls.option_400_orbs, + cls.option_500_orbs, + cls.option_1000_orbs, + cls.option_2000_orbs} + except ImportError: + pass + return set() -class PerLevelOrbsanityBundleSize(Choice): + +class PerLevelOrbsanityBundleSize(AllowedChoice): """The orb bundle size for Per Level Orbsanity. This only applies if "Enable Orbsanity" is set to "Per Level." There are 50, 150, or 200 orbs per level, so your bundle size must be a factor of 50. - Multiplayer Minimum: 10""" + This value is restricted to safe minimum and maximum values to ensure valid singleplayer games and + non-disruptive multiplayer games, but the host can remove this restriction by turning off enforce_friendly_options + in host.yaml.""" display_name = "Per Level Orbsanity Bundle Size" option_1_orb = 1 option_2_orbs = 2 @@ -91,6 +169,18 @@ class PerLevelOrbsanityBundleSize(Choice): friendly_minimum = 10 default = 25 + @classmethod + def get_disallowed_options(cls) -> set[int]: + try: + from . import JakAndDaxterWorld + if JakAndDaxterWorld.settings.enforce_friendly_options: + return {cls.option_1_orb, + cls.option_2_orbs, + cls.option_5_orbs} + except ImportError: + pass + return set() + class FireCanyonCellCount(Range): """The number of power cells you need to cross Fire Canyon. This value is restricted to a safe maximum value to @@ -234,7 +324,7 @@ class CompletionCondition(Choice): option_cross_fire_canyon = 69 option_cross_mountain_pass = 87 option_cross_lava_tube = 89 - option_defeat_dark_eco_plant = 6 + # option_defeat_dark_eco_plant = 6 option_defeat_klaww = 86 option_defeat_gol_and_maia = 112 option_open_100_cell_door = 116 diff --git a/worlds/jakanddaxter/regions.py b/worlds/jakanddaxter/regions.py index 8447f72e..87186c3a 100644 --- a/worlds/jakanddaxter/regions.py +++ b/worlds/jakanddaxter/regions.py @@ -115,8 +115,8 @@ def create_regions(world: "JakAndDaxterWorld"): elif options.jak_completion_condition == CompletionCondition.option_cross_lava_tube: multiworld.completion_condition[player] = lambda state: state.can_reach(gmc, "Region", player) - elif options.jak_completion_condition == CompletionCondition.option_defeat_dark_eco_plant: - multiworld.completion_condition[player] = lambda state: state.can_reach(fjp, "Region", player) + # elif options.jak_completion_condition == CompletionCondition.option_defeat_dark_eco_plant: + # multiworld.completion_condition[player] = lambda state: state.can_reach(fjp, "Region", player) elif options.jak_completion_condition == CompletionCondition.option_defeat_klaww: multiworld.completion_condition[player] = lambda state: state.can_reach(mp, "Region", player) diff --git a/worlds/jakanddaxter/rules.py b/worlds/jakanddaxter/rules.py index 71b94df8..25a8323f 100644 --- a/worlds/jakanddaxter/rules.py +++ b/worlds/jakanddaxter/rules.py @@ -1,3 +1,5 @@ +import logging +import math import typing from BaseClasses import CollectionState from Options import OptionError @@ -131,100 +133,138 @@ def can_fight(state: CollectionState, player: int) -> bool: return state.has_any(("Jump Dive", "Jump Kick", "Punch", "Kick"), player) -def enforce_multiplayer_limits(world: "JakAndDaxterWorld"): +def clamp_cell_limits(world: "JakAndDaxterWorld") -> str: options = world.options friendly_message = "" - if (options.enable_orbsanity == EnableOrbsanity.option_global - and (options.global_orbsanity_bundle_size.value < GlobalOrbsanityBundleSize.friendly_minimum - or options.global_orbsanity_bundle_size.value > GlobalOrbsanityBundleSize.friendly_maximum)): - friendly_message += (f" " - f"{options.global_orbsanity_bundle_size.display_name} must be no less than " - f"{GlobalOrbsanityBundleSize.friendly_minimum} and no greater than " - f"{GlobalOrbsanityBundleSize.friendly_maximum} (currently " - f"{options.global_orbsanity_bundle_size.value}).\n") - - if (options.enable_orbsanity == EnableOrbsanity.option_per_level - and options.level_orbsanity_bundle_size.value < PerLevelOrbsanityBundleSize.friendly_minimum): - friendly_message += (f" " - f"{options.level_orbsanity_bundle_size.display_name} must be no less than " - f"{PerLevelOrbsanityBundleSize.friendly_minimum} (currently " - f"{options.level_orbsanity_bundle_size.value}).\n") - if options.fire_canyon_cell_count.value > FireCanyonCellCount.friendly_maximum: + old_value = options.fire_canyon_cell_count.value + options.fire_canyon_cell_count.value = FireCanyonCellCount.friendly_maximum friendly_message += (f" " f"{options.fire_canyon_cell_count.display_name} must be no greater than " - f"{FireCanyonCellCount.friendly_maximum} (currently " - f"{options.fire_canyon_cell_count.value}).\n") + f"{FireCanyonCellCount.friendly_maximum} (was {old_value}), " + f"changed option to appropriate value.\n") if options.mountain_pass_cell_count.value > MountainPassCellCount.friendly_maximum: + old_value = options.mountain_pass_cell_count.value + options.mountain_pass_cell_count.value = MountainPassCellCount.friendly_maximum friendly_message += (f" " f"{options.mountain_pass_cell_count.display_name} must be no greater than " - f"{MountainPassCellCount.friendly_maximum} (currently " - f"{options.mountain_pass_cell_count.value}).\n") + f"{MountainPassCellCount.friendly_maximum} (was {old_value}), " + f"changed option to appropriate value.\n") if options.lava_tube_cell_count.value > LavaTubeCellCount.friendly_maximum: + old_value = options.lava_tube_cell_count.value + options.lava_tube_cell_count.value = LavaTubeCellCount.friendly_maximum friendly_message += (f" " f"{options.lava_tube_cell_count.display_name} must be no greater than " - f"{LavaTubeCellCount.friendly_maximum} (currently " - f"{options.lava_tube_cell_count.value}).\n") + f"{LavaTubeCellCount.friendly_maximum} (was {old_value}), " + f"changed option to appropriate value.\n") + + return friendly_message + + +def clamp_trade_total_limits(world: "JakAndDaxterWorld"): + """Check if we need to recalculate the 2 trade orb options so the total fits under 2000. If so let's keep them + proportional relative to each other. Then we'll recalculate total_trade_orbs. Remember this situation is + only possible if both values are greater than 0, otherwise the absolute maximums would keep them under 2000.""" + options = world.options + friendly_message = "" + + world.total_trade_orbs = (9 * options.citizen_orb_trade_amount) + (6 * options.oracle_orb_trade_amount) + if world.total_trade_orbs > 2000: + old_total = world.total_trade_orbs + old_citizen_value = options.citizen_orb_trade_amount.value + old_oracle_value = options.oracle_orb_trade_amount.value + + coefficient = old_oracle_value / old_citizen_value + + options.citizen_orb_trade_amount.value = math.floor(2000 / (9 + (6 * coefficient))) + options.oracle_orb_trade_amount.value = math.floor(coefficient * options.citizen_orb_trade_amount.value) + world.total_trade_orbs = (9 * options.citizen_orb_trade_amount) + (6 * options.oracle_orb_trade_amount) + + friendly_message += (f" " + f"Required number of orbs ({old_total}) must be no greater than total orbs in the game " + f"(2000). Reduced the value of {world.options.citizen_orb_trade_amount.display_name} " + f"from {old_citizen_value} to {options.citizen_orb_trade_amount.value} and " + f"{world.options.oracle_orb_trade_amount.display_name} from {old_oracle_value} to " + f"{options.oracle_orb_trade_amount.value}.\n") + + return friendly_message + + +def enforce_mp_friendly_limits(world: "JakAndDaxterWorld"): + options = world.options + friendly_message = "" + + if options.enable_orbsanity == EnableOrbsanity.option_global: + if options.global_orbsanity_bundle_size.value < GlobalOrbsanityBundleSize.friendly_minimum: + old_value = options.global_orbsanity_bundle_size.value + options.global_orbsanity_bundle_size.value = GlobalOrbsanityBundleSize.friendly_minimum + friendly_message += (f" " + f"{options.global_orbsanity_bundle_size.display_name} must be no less than " + f"{GlobalOrbsanityBundleSize.friendly_minimum} (was {old_value}), " + f"changed option to appropriate value.\n") + + if options.global_orbsanity_bundle_size.value > GlobalOrbsanityBundleSize.friendly_maximum: + old_value = options.global_orbsanity_bundle_size.value + options.global_orbsanity_bundle_size.value = GlobalOrbsanityBundleSize.friendly_maximum + friendly_message += (f" " + f"{options.global_orbsanity_bundle_size.display_name} must be no greater than " + f"{GlobalOrbsanityBundleSize.friendly_maximum} (was {old_value}), " + f"changed option to appropriate value.\n") + + if options.enable_orbsanity == EnableOrbsanity.option_per_level: + if options.level_orbsanity_bundle_size.value < PerLevelOrbsanityBundleSize.friendly_minimum: + old_value = options.level_orbsanity_bundle_size.value + options.level_orbsanity_bundle_size.value = PerLevelOrbsanityBundleSize.friendly_minimum + friendly_message += (f" " + f"{options.level_orbsanity_bundle_size.display_name} must be no less than " + f"{PerLevelOrbsanityBundleSize.friendly_minimum} (was {old_value}), " + f"changed option to appropriate value.\n") if options.citizen_orb_trade_amount.value > CitizenOrbTradeAmount.friendly_maximum: + old_value = options.citizen_orb_trade_amount.value + options.citizen_orb_trade_amount.value = CitizenOrbTradeAmount.friendly_maximum friendly_message += (f" " f"{options.citizen_orb_trade_amount.display_name} must be no greater than " - f"{CitizenOrbTradeAmount.friendly_maximum} (currently " - f"{options.citizen_orb_trade_amount.value}).\n") + f"{CitizenOrbTradeAmount.friendly_maximum} (was {old_value}), " + f"changed option to appropriate value.\n") if options.oracle_orb_trade_amount.value > OracleOrbTradeAmount.friendly_maximum: + old_value = options.oracle_orb_trade_amount.value + options.oracle_orb_trade_amount.value = OracleOrbTradeAmount.friendly_maximum friendly_message += (f" " f"{options.oracle_orb_trade_amount.display_name} must be no greater than " - f"{OracleOrbTradeAmount.friendly_maximum} (currently " - f"{options.oracle_orb_trade_amount.value}).\n") + f"{OracleOrbTradeAmount.friendly_maximum} (was {old_value}), " + f"changed option to appropriate value.\n") + + friendly_message += clamp_cell_limits(world) + friendly_message += clamp_trade_total_limits(world) if friendly_message != "": - raise OptionError(f"{world.player_name}: The options you have chosen may disrupt the multiworld. \n" - f"Please adjust the following Options for a multiplayer game. \n" - f"{friendly_message}" - f"Or use 'random-range-x-y' instead of 'random' in your player yaml.\n" - f"Or set 'enforce_friendly_options' in the seed generator's host.yaml to false. " - f"(Use at your own risk!)") + logging.warning(f"{world.player_name}: Your options have been modified to avoid disrupting the multiworld.\n" + f"{friendly_message}" + f"You can access more advanced options by setting 'enforce_friendly_options' in the seed " + f"generator's host.yaml to false and generating locally. (Use at your own risk!)") -def enforce_singleplayer_limits(world: "JakAndDaxterWorld"): - options = world.options +def enforce_mp_absolute_limits(world: "JakAndDaxterWorld"): friendly_message = "" - if options.fire_canyon_cell_count.value > FireCanyonCellCount.friendly_maximum: - friendly_message += (f" " - f"{options.fire_canyon_cell_count.display_name} must be no greater than " - f"{FireCanyonCellCount.friendly_maximum} (currently " - f"{options.fire_canyon_cell_count.value}).\n") - - if options.mountain_pass_cell_count.value > MountainPassCellCount.friendly_maximum: - friendly_message += (f" " - f"{options.mountain_pass_cell_count.display_name} must be no greater than " - f"{MountainPassCellCount.friendly_maximum} (currently " - f"{options.mountain_pass_cell_count.value}).\n") - - if options.lava_tube_cell_count.value > LavaTubeCellCount.friendly_maximum: - friendly_message += (f" " - f"{options.lava_tube_cell_count.display_name} must be no greater than " - f"{LavaTubeCellCount.friendly_maximum} (currently " - f"{options.lava_tube_cell_count.value}).\n") + friendly_message += clamp_trade_total_limits(world) if friendly_message != "": - raise OptionError(f"The options you have chosen may result in seed generation failures. \n" - f"Please adjust the following Options for a singleplayer game. \n" - f"{friendly_message}" - f"Or use 'random-range-x-y' instead of 'random' in your player yaml.\n" - f"Or set 'enforce_friendly_options' in your host.yaml to false. " - f"(Use at your own risk!)") + logging.warning(f"{world.player_name}: Your options have been modified to avoid seed generation failures.\n" + f"{friendly_message}") -def verify_orb_trade_amounts(world: "JakAndDaxterWorld"): +def enforce_sp_limits(world: "JakAndDaxterWorld"): + friendly_message = "" - if world.total_trade_orbs > 2000: - raise OptionError(f"{world.player_name}: Required number of orbs for all trades ({world.total_trade_orbs}) " - f"is more than all the orbs in the game (2000). Reduce the value of either " - f"{world.options.citizen_orb_trade_amount.display_name} " - f"or {world.options.oracle_orb_trade_amount.display_name}.") + friendly_message += clamp_cell_limits(world) + friendly_message += clamp_trade_total_limits(world) + + if friendly_message != "": + logging.warning(f"{world.player_name}: Your options have been modified to avoid seed generation failures.\n" + f"{friendly_message}") diff --git a/worlds/jakanddaxter/test/test_trades.py b/worlds/jakanddaxter/test/test_trades.py index e1d1a2e5..0277a923 100644 --- a/worlds/jakanddaxter/test/test_trades.py +++ b/worlds/jakanddaxter/test/test_trades.py @@ -4,14 +4,14 @@ from .bases import JakAndDaxterTestBase class TradesCostNothingTest(JakAndDaxterTestBase): options = { "enable_orbsanity": 2, - "global_orbsanity_bundle_size": 5, + "global_orbsanity_bundle_size": 10, "citizen_orb_trade_amount": 0, "oracle_orb_trade_amount": 0 } def test_orb_items_are_filler(self): self.collect_all_but("") - self.assertNotIn("5 Precursor Orbs", self.multiworld.state.prog_items) + self.assertNotIn("10 Precursor Orbs", self.multiworld.state.prog_items) def test_trades_are_accessible(self): self.assertTrue(self.multiworld @@ -22,15 +22,15 @@ class TradesCostNothingTest(JakAndDaxterTestBase): class TradesCostEverythingTest(JakAndDaxterTestBase): options = { "enable_orbsanity": 2, - "global_orbsanity_bundle_size": 5, + "global_orbsanity_bundle_size": 10, "citizen_orb_trade_amount": 120, "oracle_orb_trade_amount": 150 } def test_orb_items_are_progression(self): self.collect_all_but("") - self.assertIn("5 Precursor Orbs", self.multiworld.state.prog_items[self.player]) - self.assertEqual(396, self.multiworld.state.prog_items[self.player]["5 Precursor Orbs"]) + self.assertIn("10 Precursor Orbs", self.multiworld.state.prog_items[self.player]) + self.assertEqual(198, self.multiworld.state.prog_items[self.player]["10 Precursor Orbs"]) def test_trades_are_accessible(self): self.collect_all_but("")