Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .cruft.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"template": "git@github.com:bl-sdk/common_dotfiles.git",
"commit": "d03eee713ad436d20033d0598eb88f1529c56ca8",
"commit": "3d7189e61172cb95877261df8313ae595fe6c02d",
"checkout": null,
"context": {
"cookiecutter": {
Expand All @@ -15,7 +15,8 @@
"__project_slug": "mods_base",
"include_cpp": false,
"include_py": true,
"_template": "git@github.com:bl-sdk/common_dotfiles.git"
"_template": "git@github.com:bl-sdk/common_dotfiles.git",
"_commit": "3d7189e61172cb95877261df8313ae595fe6c02d"
}
},
"directory": null
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.vs
.vscode
.idea

__pycache__
8 changes: 8 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ game specific things:
# Changelog

### v1.9
- Added a new `CoopSupport.HostOnly` value.

- Specifying a custom class when calling `build_mod` now type hints returning an instance of it,
instead of just `Mod`.

- `SliderOption`s now throw if initialized with a step larger than their allowed range.

- Added `_(to|from)_json()` methods to all options.

- Changed settings saving and loading to use above methods.

### v1.8
Expand Down
14 changes: 13 additions & 1 deletion mod.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@


class Game(Flag):
"""A flags enum of the supported games."""

BL2 = auto()
TPS = auto()
AoDK = auto()
Expand Down Expand Up @@ -86,15 +88,25 @@ def get_tree() -> Literal[Game.Willow2, Game.Oak]:


class ModType(Enum):
"""
What type of mod this is.

This does not influence functionality. It's only used for categorization - e.g. influencing
ordering in the mod list.
"""

Standard = auto()
Library = auto()


class CoopSupport(Enum):
"""Enum for how well a mod supports coop. This is informational only."""

Unknown = auto()
Incompatible = auto()
RequiresAllPlayers = auto()
ClientSide = auto()
HostOnly = auto()


@dataclass
Expand All @@ -112,7 +124,7 @@ class Mod:
description: A short description of the mod.
version: A string holding the mod's version. This is purely a display value, the module
level attributes should be used for version checking.
mod_type: What type of mod this is. This influences ordering in the mod list.
mod_type: What type of mod this is. This does not influence functionality.
supported_games: The games this mod supports. When loaded in an unsupported game, a warning
will be displayed and the mod will be blocked from enabling.
coop_support: How well the mod supports coop, if known. This is purely a display value.
Expand Down
13 changes: 8 additions & 5 deletions mod_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
from .settings import SETTINGS_DIR


def build_mod(
def build_mod[T: Mod = Mod](
*,
cls: type[Mod] = Mod,
cls: type[T] = Mod,
deregister_same_settings: bool = True,
inject_version_from_pyproject: bool = True,
version_info_parser: Callable[[str], tuple[int, ...]] = (
Expand All @@ -43,7 +43,7 @@ def build_mod(
auto_enable: bool | None = None,
on_enable: Callable[[], None] | None = None,
on_disable: Callable[[], None] | None = None,
) -> Mod:
) -> T:
"""
Factory function to create and register a mod.

Expand Down Expand Up @@ -73,13 +73,16 @@ def build_mod(

^1: Multiple authors are joined into a single string using commas + spaces.
^2: A string of one of the ModType enum value's name. Case sensitive.
Note this only influences ordering in the mod menu. Setting 'mod_type = "Library"' is *not*
equivalent to specifying cls=Library when calling this function.
^3: A list of strings of Game enum values' names. Case sensitive.
^4: A string of one of the CoopSupport enum value's name. Case sensitive.
^5: GroupedOption and NestedOption instances are deliberately ignored, to avoid possible issues
gathering their child options twice. They must be explicitly passed via the arg.

Missing fields are not passed on to the mod constructor - e.g. by never specifying supported
games, they won't be passed on and it will use the default, all of them.
Any given fields are passed directly to the mod class constructor - and any missing ones are
not. This means not specifying a field is equivalent to the class default - for example, usually
mod type defaults to Standard, but when using 'cls=Library' it will default to Library.

Extra Args:
cls: The mod class to construct using. Can be used to select a subclass.
Expand Down
11 changes: 10 additions & 1 deletion options.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ class SliderOption(ValueOption[float]):
value: The option's value.
min_value: The minimum value.
max_value: The maximum value.
step: How much the value should move each step of the slider.
step: How much the value should move each step of the slider. This does not mean your value
will always be a multiple of the step, it may be possible to get intermediate values.
is_integer: If True, the value is treated as an integer.
Keyword Args:
display_name: The option name to use for display. Defaults to copying the identifier.
Expand All @@ -214,6 +215,14 @@ class SliderOption(ValueOption[float]):
step: float = 1
is_integer: bool = True

def __post_init__(self) -> None:
super().__post_init__()

# This is generally non-sensical, and we expect most menus will have problems with it
# While you can easily get around it, block it as a first layer of defence
if self.step > (self.max_value - self.min_value):
raise ValueError("Can't give slider option a step larger than its allowed range")

def _from_json(self, value: JSON) -> None:
try:
self.value = float(value) # type: ignore
Expand Down
56 changes: 28 additions & 28 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,9 @@ target-version = "py313"
line-length = 100

[tool.ruff.lint]
# Last time rules scrutinised: ruff 0.6.9 / 2024-10-08
# Last time rules scrutinised: ruff 0.11.0 / 2025-03-20
select = [
"F",
"W",
"E",
"C90",
"I",
"N",
"D",
"UP",
"ERA",
"YTT",
"ANN",
"ASYNC",
Expand All @@ -31,34 +24,51 @@ select = [
"C4",
"DTZ",
"T10",
"FIX",
"FA",
"INT",
"ISC",
"ICN",
"LOG",
"G",
"PIE",
"T20",
"PYI",
"Q",
"RSE",
"RET",
"SLOT",
"SIM",
"SLOT",
"TID",
"TCH",
"INT",
"TD",
"TC",
"ARG",
"PTH",
"TD",
"FIX",
"ERA",
"PGH",
"PL",
"FLY",
"I",
"C90",
"N",
"PERF",
"E",
"W",
"D",
"F",
"PGH",
"PL",
"UP",
"FURB",
"RUF",
]
ignore = [
"ANN401",
"S101",
"S603",
"S607",
"PYI011",
"PYI021",
"PYI029",
"PYI044",
"TC006",
"D100",
"D101",
"D104",
Expand All @@ -76,16 +86,6 @@ ignore = [
"D410",
"D411",
"D413",
"ANN101",
"ANN102",
"ANN401",
"S101",
"S603",
"S607",
"PYI011",
"PYI021",
"PYI029",
"PYI044",
"PGH003",
"PLR0904",
"PLR0911",
Expand All @@ -100,4 +100,4 @@ ignore = [
]

[tool.ruff.lint.per-file-ignores]
"*.pyi" = ["D418", "A002", "A003"]
"*.pyi" = ["A002", "A003", "D418"]