Skip to content

Commit

Permalink
add informational coop support field
Browse files Browse the repository at this point in the history
hope being by having a dedicated field people will actually fill it in
  • Loading branch information
apple1417 committed Aug 22, 2024
1 parent bd38161 commit db89e4b
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 56 deletions.
2 changes: 1 addition & 1 deletion src/bl3_mod_menu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"__version_info__",
]

__version_info__: tuple[int, int] = (1, 2)
__version_info__: tuple[int, int] = (1, 3)
__version__: str = f"{__version_info__[0]}.{__version_info__[1]}"
__author__: str = "bl-sdk"

Expand Down
79 changes: 61 additions & 18 deletions src/bl3_mod_menu/options_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
BaseOption,
BoolOption,
ButtonOption,
CoopSupport,
DropdownOption,
Game,
GroupedOption,
Expand Down Expand Up @@ -189,6 +190,64 @@ def get_option_header() -> str:
)


def create_description_title(mod: Mod) -> str:
"""
Creates the description title to use for a mod's description option.
Args:
mod: The mod to create the title for.
Returns:
The description title.
"""
description_title = ""
if mod.author:
description_title += f"By {mod.author}"
if mod.author and mod.version:
description_title += " - "
if mod.version:
description_title += mod.version
return description_title or "Description"


def create_description_text(mod: Mod) -> str:
"""
Creates the text to use for a mod's description option.
Args:
mod: The mod to create the title for.
Returns:
The description text.
"""
blocks: list[str] = []

if Game.get_current() not in mod.supported_games:
supported = [g.name for g in Game if g in mod.supported_games and g.name is not None]
blocks.append(
"<font color='#ffff00'>Incompatible Game!</font>\n"
"This mod supports: " + ", ".join(supported),
)

if mod.description:
blocks.append(mod.description)

match mod.coop_support:
case CoopSupport.Unknown:
blocks.append("<font color='#e0e0e0'>Coop Support: Unknown</font>")
case CoopSupport.Incompatible:
blocks.append(
"<font color='#e0e0e0'>Coop Support:</font>"
" <font color='#ffff00'>Incompatible</font>",
)
case CoopSupport.RequiresAllPlayers:
blocks.append(
"<font color='#e0e0e0'>Coop Support: Requires All Players</font>",
)
case CoopSupport.ClientSide:
blocks.append("<font color='#e0e0e0'>Coop Support: Client Side</font>")

return "\n\n".join(blocks)


def get_mod_options(mod: Mod) -> tuple[BaseOption, ...]:
"""
Gets the full list of mod options to display, including our custom header.
Expand All @@ -201,27 +260,11 @@ def get_mod_options(mod: Mod) -> tuple[BaseOption, ...]:

def inner() -> Iterator[BaseOption]:
# Display the author and version in the title, if they're not the empty string
description_title = ""
if mod.author:
description_title += f"By {mod.author}"
if mod.author and mod.version:
description_title += " - "
if mod.version:
description_title += mod.version
description_title = description_title or "Description"

description = mod.description
if Game.get_current() not in mod.supported_games:
supported = [g.name for g in Game if g in mod.supported_games and g.name is not None]
description = (
"<font color='#ffff00'>Incompatible Game!</font>\r"
"This mod supports: " + ", ".join(supported) + "\n\n" + description
)

yield ButtonOption(
"Description",
description=description,
description_title=description_title,
description=create_description_text(mod),
description_title=create_description_title(mod),
)

if not mod.enabling_locked:
Expand Down
2 changes: 1 addition & 1 deletion src/console_mod_menu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"__version_info__",
)

__version_info__: tuple[int, int] = (1, 2)
__version_info__: tuple[int, int] = (1, 3)
__version__: str = f"{__version_info__[0]}.{__version_info__[1]}"
__author__: str = "bl-sdk"

Expand Down
18 changes: 17 additions & 1 deletion src/console_mod_menu/screens/mod.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
BaseOption,
BoolOption,
ButtonOption,
CoopSupport,
DropdownOption,
GroupedOption,
KeybindOption,
Expand Down Expand Up @@ -124,7 +125,8 @@ class ModScreen(OptionListScreen):
def __post_init__(self) -> None:
self.name = self.mod.name

def draw(self) -> None: # noqa: D102
def draw_mod_header(self) -> None:
"""Draws the header for the mod, everything before the options."""
draw_stack_header()

header = ""
Expand All @@ -137,12 +139,26 @@ def draw(self) -> None: # noqa: D102
draw(header)

draw(self.mod.get_status())

match self.mod.coop_support:
case CoopSupport.Unknown:
draw("Coop Support: Unknown")
case CoopSupport.Incompatible:
draw("Coop Support: Incompatible")
case CoopSupport.RequiresAllPlayers:
draw("Coop Support: Requires All Players")
case CoopSupport.ClientSide:
draw("Coop Support: Client Side")

draw("")

if self.mod.description:
draw_description(self.mod.description)
draw("")

def draw(self) -> None: # noqa: D102
self.draw_mod_header()

if not self.mod.enabling_locked:
if self.mod.is_enabled:
draw("[D] Disable")
Expand Down
5 changes: 3 additions & 2 deletions src/mods_base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .dot_sdkmod import open_in_mod_dir

# Need to define a few things first to avoid circular imports
__version_info__: tuple[int, int] = (1, 3)
__version_info__: tuple[int, int] = (1, 4)
__version__: str = f"{__version_info__[0]}.{__version_info__[1]}"
__author__: str = "bl-sdk"

Expand All @@ -31,7 +31,7 @@
from .hook import HookProtocol, hook
from .html_to_plain_text import html_to_plain_text
from .keybinds import EInputEvent, KeybindType, keybind
from .mod import Game, Library, Mod, ModType
from .mod import CoopSupport, Game, Library, Mod, ModType
from .mod_factory import build_mod
from .mod_list import (
deregister_mod,
Expand Down Expand Up @@ -65,6 +65,7 @@
"ButtonOption",
"capture_next_console_line",
"command",
"CoopSupport",
"deregister_mod",
"DropdownOption",
"EInputEvent",
Expand Down
9 changes: 9 additions & 0 deletions src/mods_base/mod.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ class ModType(Enum):
Library = auto()


class CoopSupport(Enum):
Unknown = auto()
Incompatible = auto()
RequiresAllPlayers = auto()
ClientSide = auto()


@dataclass
class Mod:
"""
Expand All @@ -108,6 +115,7 @@ class Mod:
mod_type: What type of mod this is. This influences ordering in the mod list.
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.
settings_file: The file to save settings to. If None (the default), won't save settings.
Attributes - Functionality:
Expand All @@ -131,6 +139,7 @@ class Mod:
version: str = "Unknown Version"
mod_type: ModType = ModType.Standard
supported_games: Game = field(default=Game.get_tree())
coop_support: CoopSupport = CoopSupport.Unknown
settings_file: Path | None = None

# Set the default to None so we can detect when these aren't provided
Expand Down
95 changes: 63 additions & 32 deletions src/mods_base/mod_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from .dot_sdkmod import open_in_mod_dir
from .hook import HookProtocol
from .keybinds import KeybindType
from .mod import Game, Mod, ModType
from .mod import CoopSupport, Game, Mod, ModType
from .mod_list import deregister_mod, mod_list, register_mod
from .options import BaseOption, GroupedOption, NestedOption
from .settings import SETTINGS_DIR
Expand All @@ -34,6 +34,7 @@ def build_mod(
version: str | None = None,
mod_type: ModType | None = None,
supported_games: Game | None = None,
coop_support: CoopSupport | None = None,
settings_file: Path | None = None,
keybinds: Sequence[KeybindType] | None = None,
options: Sequence[BaseOption] | None = None,
Expand All @@ -60,9 +61,10 @@ def build_mod(
| project.version | __version_info__
mod_type | tool.sdkmod.mod_type ^2 |
supported_games | tool.sdkmod.supported_games ^3 |
coop_support | tool.sdkmod.coop_support ^4 |
settings_file | | f"{__name__}.json" in the settings dir
keybinds | | Keybind instances
options | | OptionBase instances ^4
options | | OptionBase instances ^5
hooks | | Objects matching HookProtocol
commands | | AbstractCommand instances
auto_enable | tool.sdkmod.auto_enable |
Expand All @@ -72,7 +74,8 @@ 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.
^3: A list of strings of Game enum values' names. Case sensitive.
^4: GroupedOption and NestedOption instances are deliberately ignored, to avoid possible issues
^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
Expand Down Expand Up @@ -105,6 +108,7 @@ def build_mod(
"_version_info": None,
"mod_type": mod_type,
"supported_games": supported_games,
"coop_support": coop_support,
"settings_file": settings_file,
"keybinds": keybinds,
"options": options,
Expand Down Expand Up @@ -149,6 +153,7 @@ class ModFactoryFields(TypedDict):
_version_info: str | None
mod_type: ModType | None
supported_games: Game | None
coop_support: CoopSupport | None
settings_file: Path | None
keybinds: Sequence[KeybindType] | None
options: Sequence[BaseOption] | None
Expand Down Expand Up @@ -179,6 +184,59 @@ def load_pyproject(module: ModuleType) -> dict[str, Any]:
return {}


def update_fields_with_pyproject_tool_sdkmod(
sdkmod: dict[str, Any],
fields: ModFactoryFields,
) -> None:
"""
Updates the mod factory fields with data from the `tools.sdkmod` section of a`pyproject.toml`.
Args:
sdkmod: The `tools.sdkmod` section.
fields: The current set of fields. Modified in place.
"""
for simple_field in ("name", "version", "auto_enable"):
if fields[simple_field] is None and simple_field in sdkmod:
fields[simple_field] = sdkmod[simple_field]

if fields["mod_type"] is None and "mod_type" in sdkmod:
fields["mod_type"] = ModType.__members__.get(sdkmod["mod_type"])

if fields["supported_games"] is None and "supported_games" in sdkmod:
valid_games = [Game[name] for name in sdkmod["supported_games"] if name in Game.__members__]
if valid_games:
fields["supported_games"] = functools.reduce(operator.or_, valid_games)

if fields["coop_support"] is None and "coop_support" in sdkmod:
fields["coop_support"] = CoopSupport.__members__.get(sdkmod["coop_support"])


def update_fields_with_pyproject_project(
project: dict[str, Any],
fields: ModFactoryFields,
) -> None:
"""
Updates the mod factory fields with data from the `project` section of a`pyproject.toml`.
Args:
project: The `project` section.
fields: The current set of fields. Modified in place.
"""
for simple_field, project_field in (
("name", "name"),
("version", "version"),
("description", "description"),
("_version_info", "version"),
):
if fields[simple_field] is None and project_field in project:
fields[simple_field] = project[project_field]

if fields["author"] is None and "authors" in project:
fields["author"] = ", ".join(
author["name"] for author in project["authors"] if "name" in author
)


def update_fields_with_pyproject(module: ModuleType, fields: ModFactoryFields) -> None:
"""
Updates the mod factory fields with data gathered from the `pyproject.toml`.
Expand All @@ -191,37 +249,10 @@ def update_fields_with_pyproject(module: ModuleType, fields: ModFactoryFields) -

# Check `tool.sdkmod` first, since we want it to have priority in cases we have multiple keys
if ("tool" in toml_data) and ("sdkmod" in toml_data["tool"]):
sdkmod = toml_data["tool"]["sdkmod"]

for simple_field in ("name", "version", "auto_enable"):
if fields[simple_field] is None and simple_field in sdkmod:
fields[simple_field] = sdkmod[simple_field]

if fields["mod_type"] is None and "mod_type" in sdkmod:
fields["mod_type"] = ModType[sdkmod["mod_type"]]

if fields["supported_games"] is None and "supported_games" in sdkmod:
fields["supported_games"] = functools.reduce(
operator.or_,
(Game[g] for g in sdkmod["supported_games"]),
)
update_fields_with_pyproject_tool_sdkmod(toml_data["tool"]["sdkmod"], fields)

if "project" in toml_data:
project = toml_data["project"]

for simple_field, project_field in (
("name", "name"),
("version", "version"),
("description", "description"),
("_version_info", "version"),
):
if fields[simple_field] is None and project_field in project:
fields[simple_field] = project[project_field]

if fields["author"] is None and "authors" in project:
fields["author"] = ", ".join(
author["name"] for author in project["authors"] if "name" in author
)
update_fields_with_pyproject_project(toml_data["project"], fields)


def update_fields_with_module_attributes(module: ModuleType, fields: ModFactoryFields) -> None:
Expand Down
3 changes: 2 additions & 1 deletion src/mods_base/mod_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .hook import HookProtocol
from .html_to_plain_text import html_to_plain_text
from .keybinds import KeybindType
from .mod import Game, Library, Mod, ModType
from .mod import CoopSupport, Game, Library, Mod, ModType
from .options import BaseOption, ButtonOption
from .settings import SETTINGS_DIR

Expand All @@ -32,6 +32,7 @@ class BaseMod(Library):
name: str = "Python SDK"
author: str = "bl-sdk"
version: str = _MANAGER_VERSION
coop_support: CoopSupport = CoopSupport.ClientSide
settings_file: Path | None = SETTINGS_DIR / "python-sdk.json"

keybinds: list[KeybindType] = field(default_factory=list) # type: ignore
Expand Down

0 comments on commit db89e4b

Please sign in to comment.