linux-mod-manager/lmm/games/bg3.py

191 lines
5.5 KiB
Python
Raw Normal View History

2023-12-02 16:45:16 +00:00
import os
from pathlib import Path
from enum import Enum, auto
from dataclasses import dataclass
from typing import Generator, Any
from lmm.const import LMM_GAMES_PATH
from lmm.profile import Profile
from lmm.overlayfs import OverlayFSMount
from lmm.games.game import ProtonGame, CannotStartReason, PathNotExistingReason
from lmm.runners.base import runner_from_config
GAME_NAME = "BaldursGate3"
2023-12-02 16:46:48 +00:00
2023-12-02 16:45:16 +00:00
class BaldursGate3ModType(Enum):
# Override the game files
ROOT = 1
# Override the Paks in the compatdata prefix
PAK = 2
2023-12-02 16:46:48 +00:00
2023-12-02 16:45:16 +00:00
@dataclass
class BaldursGate3Mod:
# The directory name
name: str
# The type of mod
type: BaldursGate3ModType
@dataclass
class BaldursGate3Profile(Profile):
mods: list[BaldursGate3Mod]
def __init__(self, name: str, mods: list[BaldursGate3Mod], **kwargs):
super().__init__(GAME_NAME, name, **kwargs)
self.mods = mods
2023-12-02 16:46:48 +00:00
def get_mods(
self, type: BaldursGate3ModType
) -> Generator[BaldursGate3Mod, None, None]:
2023-12-02 16:45:16 +00:00
"""Yields all mods of type @type"""
for mod in self.mods:
if not mod.type == type:
continue
2023-12-02 16:46:48 +00:00
2023-12-02 16:45:16 +00:00
yield mod
def has_mods_of_type(self, type: BaldursGate3ModType) -> bool:
"""Returns whether the profile has any mods of type @type"""
return any(self.get_mods(type))
def get_mod_names(self) -> list[str]:
return [mod.name for mod in self.mods]
2023-12-02 16:46:48 +00:00
2023-12-02 16:45:16 +00:00
class BaldursGate3Game(ProtonGame):
def __init__(self, profiles: list[BaldursGate3Profile], **kwargs):
super().__init__(GAME_NAME, "1086940", profiles, **kwargs)
@property
def installation_path(self) -> Path:
return self.steam_library / "steamapps" / "common" / "Baldurs Gate 3"
@property
def compat_path(self) -> Path:
return self.steam_library / "steamapps" / "compatdata" / self.appid
@property
def user_mods_path(self) -> Path:
2023-12-02 16:46:48 +00:00
return (
self.compat_path
/ "pfx"
/ "drive_c"
/ "users"
/ "steamuser"
/ "AppData"
/ "Local"
/ "Larian Studios"
/ "Baldur's Gate 3"
/ "Mods"
)
2023-12-02 16:45:16 +00:00
def can_start(self) -> bool:
return self.user_mods_path.exists() and self.installation_path.exists()
def cannot_start_reasons(self) -> list[CannotStartReason]:
issues = []
if not self.user_mods_path.exists():
issues.append(
PathNotExistingReason(self.user_mods_path),
)
if not self.installation_path.exists():
issues.append(
PathNotExistingReason(self.installation_path),
)
return issues
def prepare_overlays(self, profile: BaldursGate3Profile) -> list[OverlayFSMount]:
overlays = []
if profile.has_mods_of_type(BaldursGate3ModType.ROOT):
mods = list(profile.get_mods(BaldursGate3ModType.ROOT))
2023-12-02 16:46:48 +00:00
mod_paths = [LMM_GAMES_PATH / GAME_NAME / mod.name for mod in mods]
2023-12-02 16:45:16 +00:00
overlays.append(
OverlayFSMount(
self.installation_path,
mod_paths,
mount=self.installation_path,
),
)
mod_names = ", ".join([mod.name for mod in mods])
print(f"Loaded root mods: {mod_names}")
if profile.has_mods_of_type(BaldursGate3ModType.PAK):
mods = list(profile.get_mods(BaldursGate3ModType.PAK))
2023-12-02 16:46:48 +00:00
mod_paths = [os.path.join("./", mod.name) for mod in mods]
2023-12-02 16:45:16 +00:00
# Merge all mods
overlays.append(
OverlayFSMount(
self.user_mods_path,
mod_paths,
self.user_mods_path,
cd=LMM_GAMES_PATH / self.name,
),
)
mod_names = ", ".join([mod.name for mod in mods])
print(f"Loaded PAK mods: {mod_names}")
return overlays
@property
def game_executable(self) -> Path:
return self.installation_path / "bin" / "bg3.exe"
def to_dict(self) -> dict[str, Any]:
return {
"profiles": [
{
"name": profile.name,
"mods": [
{
"name": mod.name,
"type": mod.type.value,
2023-12-02 16:46:48 +00:00
}
for mod in profile.mods
],
}
for profile in self.profiles
2023-12-02 16:45:16 +00:00
]
}
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "BaldursGate3Game":
profiles = []
for profile in data["profiles"]:
mods = []
for mod in profile["mods"]:
mods.append(
BaldursGate3Mod(
mod["name"],
2023-12-02 16:46:48 +00:00
BaldursGate3ModType.ROOT
if mod["type"] == 1
else BaldursGate3ModType.PAK,
2023-12-02 16:45:16 +00:00
),
)
if "runner" in profile:
runner = runner_from_config(profile["runner"])
else:
runner = None
profiles.append(
BaldursGate3Profile(
profile["name"],
mods,
runner=runner,
),
)
2023-12-02 16:46:48 +00:00
2023-12-02 16:45:16 +00:00
if "default_runner" in data:
default_runner = runner_from_config(data["default_runner"])
else:
default_runner = None
2023-12-02 16:46:48 +00:00
return BaldursGate3Game(profiles, default_runner=default_runner)