mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 04:01:32 -06:00
Jak and Daxter: Post-merge Polish (#5031)
- Cleans up a few missed references in the setup guide. - Refactors Options class to use metaclass and decorators to enforce friendly limits on multiple levels. - Templates generated from the website, even ones with `random` should not fail generation because the website will only allow values inside the friendly limits. - _Uploaded_ yamls to the website with `random`, should also now respect friendly limits without the need for `random-range` shenanigans. - _Uploaded_ yamls to the website, or yamls that are used to generate locally, that have hard-defined values outside the friendly limits, will be clamped/dragged/massaged into those limits (with logged warnings). - Removed an early completion goal that was playing havoc with fill. Not enough people seem to use this goal, so its loss will not be mourned.
This commit is contained in:

committed by
GitHub

parent
b0f41c0360
commit
d19bf98dc4
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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}")
|
||||
|
@@ -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("")
|
||||
|
Reference in New Issue
Block a user