Files
dockipelago/worlds/kss/options.py
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

249 lines
7.6 KiB
Python

from __future__ import annotations
from Options import (PerGameCommonOptions, Range, Choice, OptionSet, OptionDict, DeathLinkMixin, Toggle,
OptionCounter, Visibility, TextChoice)
from dataclasses import dataclass
from schema import Schema, And, Use, Optional, Or
from typing import Any
import random
from .aesthetics import palette_addresses
subgame_mapping = {
0: "Spring Breeze",
1: "Dyna Blade",
2: "Gourmet Race",
3: "The Great Cave Offensive",
4: "Revenge of Meta Knight",
5: "Milky Way Wishes",
6: "The Arena"
}
class RequiredSubgameCompletions(Range):
"""
How many subgames must be completed for the game to be considered complete.
"""
display_name = "Required Subgame Completions"
range_start = 1
range_end = 7
default = 6
class RequiredSubgames(OptionSet):
"""
Which subgames are required to be completed for the game to be considered complete.
"""
display_name = "Required Subgames"
valid_keys = frozenset(subgame_mapping.values())
default = {"Milky Way Wishes"}
class StartingSubgame(Choice):
"""
The subgame that will be unlocked by default.
"""
display_name = "Starting Subgame"
option_spring_breeze = 0
option_dyna_blade = 1
option_gourmet_race = 2
option_the_great_cave_offensive = 3
option_revenge_of_meta_knight = 4
option_milky_way_wishes = 5
option_the_arena = 6
default = 0
class IncludedSubgames(OptionSet):
"""
Which subgames should be included as locations.
"""
display_name = "Included Subgames"
valid_keys = frozenset(subgame_mapping.values())
default = sorted(valid_keys)
class TheGreatCaveOffensiveRequiredGold(Range):
"""
Required amount of gold that is needed in order to complete The Great Cave Offensive
"""
display_name = "The Great Cave Offensive Required Gold"
range_start = 2500000
range_end = 9999990
default = range_end
class TheGreatCaveOffensiveGoldThresholds(OptionCounter):
"""
What percent of the required gold is required before allowing access to
Crystal/Old Tower/Garden areas in The Great Cave Offensive
"""
display_name = "The Great Cave Offensive Gold Thresholds"
valid_keys = ("Crystal", "Old Tower", "Garden")
schema = Schema({
area: And(int, lambda i: 0 <= i <= 100, error="Value must be between 0 and 100")
for area in ["Crystal", "Old Tower", "Garden"]
})
min = 0
max = 100
default = {
"Crystal": 25,
"Old Tower": 50,
"Garden": 75
}
class TheGreatCaveOffensiveExcessGold(Range):
"""
How much of the excess gold should be kept within the multiworld.
"""
display_name = "The Great Cave Offensive Excess Gold"
range_start = 0
range_end = 100
class MilkyWayWishesMode(Choice):
"""
Determines how Marx is unlocked in Milky Way Wishes.
Local: Marx is unlocked after completing the 7 main planets
(Floria, Aqualiss, Skyhigh, Hotbeat, Cavios, Mecheye, Halfmoon)
Multiworld: Marx is unlocked after receiving 7 Rainbow Stars scattered across the multiworld
"""
display_name = "Milky Way Wishes Mode"
option_local = 0
option_multiworld = 1
default = 0
class Consumables(OptionSet):
"""
Adds the specified consumables to the location pool. Options are Maxim Tomato, 1-Up,
and Invincibility Candy.
"""
display_name = "Consumable Checks"
valid_keys = {"Maxim Tomato", "1-Up", "Invincibility Candy", "Arena Maxim Tomato"}
default = frozenset()
class Essences(Toggle):
"""
Adds Copy Essence pedestals to the location pool.
"""
display_name = "Essence-sanity"
class KirbyFlavorPreset(TextChoice, OptionDict):
"""
The color of Kirby, from a list of presets.
Can also accept a valid preset defined in `kirby_flavors`, 1/2 direct hex colors (#RRGGBB|#RRGGBB), a single hex
color and a method to derive a second color (#RRGGBB|complement/analogous/intensify), or a dict taking each Copy
Ability (and Kirby) as a key and one of the former options as the value.
kirby_flavor_preset:
kirby: #99EEDD|analogous
Copy: bubblegum
Cutter: #AAEEDD
"""
display_name = "Kirby Flavor"
valid_keys = sorted(palette_addresses.keys())
# primary validation, perform secondary validation in generate_early
schema = Schema(Or(str, int, {
Optional(And(str, Use(str.title), lambda s: s in palette_addresses)): str
}))
default = 0
option_default = 0
option_bubblegum = 1
option_cherry = 2
option_blueberry = 3
option_lemon = 4
option_kiwi = 5
option_grape = 6
option_chocolate = 7
option_marshmallow = 8
option_licorice = 9
option_watermelon = 10
option_orange = 11
option_lime = 12
option_lavender = 13
option_miku = 14
def __init__(self, value: int | str | dict[str, Any]) -> None:
self.value: int | str | dict[str, Any] = value
@classmethod
def parse_weighted_option(cls, value: dict[str, int]) -> str:
return random.choices(list(value.keys()), weights=list(value.values()), k=1)[0]
@classmethod
def from_any(cls, value: Any) -> TextChoice | OptionDict:
if isinstance(value, dict):
if any(key not in cls.valid_keys for key in value.keys()):
# We have to assume that this is a weighted option
val = cls.parse_weighted_option(value)
return super().from_any(val)
for key in value.keys():
if value[key].lower() == "random":
value[key] = random.choice(list([key for key, val in cls.options.items() if val >= 0]))
return cls(value)
else:
return super().from_any(value)
def verify_keys(self) -> None:
if isinstance(self.value, dict):
super().verify_keys()
@classmethod
def get_option_name(cls, value: int | dict[str, str]) -> str:
if isinstance(value, int):
return cls.name_lookup[value].replace("_", " ").title()
elif isinstance(value, str):
return value
else:
return ", ".join(f"{key}: {v}" for key, v in value.items())
class KirbyFlavor(OptionDict):
"""
Define custom colors for Kirby. To use a custom color, set the preset to the name defined and then define a dict of keys from "1" to
"8", with their values being an HTML hex color.
"""
display_name = "Custom Kirby Flavors"
schema = Schema(
{
Optional(str): {
f"{x}": And(str, Use(str.lower), lambda x: all(y in "abcdef0123456789#" for y in x))
for x in range(1, 9)
}
}
)
default = {
"default_kirby": {
"1": "F8F8F8",
"2": "F0E0E8",
"3": "E8D0D0",
"4": "F0A0B8",
"5": "C8A0A8",
"6": "A85048",
"7": "E02018",
"8": "E85048",
}
}
visibility = Visibility.template | Visibility.spoiler # never supported on guis
@dataclass
class KSSOptions(PerGameCommonOptions, DeathLinkMixin):
required_subgame_completions: RequiredSubgameCompletions
required_subgames: RequiredSubgames
starting_subgame: StartingSubgame
included_subgames: IncludedSubgames
consumables: Consumables
essences: Essences
the_great_cave_offensive_required_gold: TheGreatCaveOffensiveRequiredGold
the_great_cave_offensive_excess_gold: TheGreatCaveOffensiveExcessGold
the_great_cave_offensive_gold_thresholds: TheGreatCaveOffensiveGoldThresholds
milky_way_wishes_mode: MilkyWayWishesMode
kirby_flavor_preset: KirbyFlavorPreset
kirby_flavors: KirbyFlavor