Skip to content

Commit

Permalink
Merge pull request #92 from ZashIn/subnautica/feat/checker+root
Browse files Browse the repository at this point in the history
Subnautica: feature update v2.1
  • Loading branch information
Holt59 authored Oct 22, 2022
2 parents 7a5d869 + ca984c2 commit 6a9a9bb
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 16 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ You can rename `modorganizer-basic_games-xxx` to whatever you want (e.g., `basic
| S.T.A.L.K.E.R. Anomaly — [MOD](https://www.stalker-anomaly.com/) | [Qudix](https://github.com/Qudix) | [game_stalkeranomaly.py](games/game_stalkeranomaly.py) | <ul><li>mod data checker</li></ul> |
| Stardew Valley — [GOG](https://www.gog.com/game/stardew_valley) / [STEAM](https://store.steampowered.com/app/413150/Stardew_Valley/) | [Syer10](https://github.com/Syer10), [Holt59](https://github.com/holt59/) | [game_stardewvalley.py](games/game_stardewvalley.py) | <ul><li>mod data checker</li></ul> |
| STAR WARS™ Empire at War: Gold Pack - [GOG](https://www.gog.com/game/star_wars_empire_at_war_gold_pack) / [STEAM](https://store.steampowered.com/app/32470/) | [erri120](https://github.com/erri120) | <ul><li>Empire at War: [game_starwars-empire-at-war.py](games/game_starwars-empire-at-war.py)</li><li>Force of Corruption: [game_starwars-empire-at-war-foc.py](games/game_starwars-empire-at-war-foc.py)</li></ul> | |
| Subnautica — [STEAM](https://store.steampowered.com/app/264710/) / [Epic](https://store.epicgames.com/p/subnautica) | dekart811 | [game_subnautica.py](games/game_subnautica.py) | |
| Subnautica — [STEAM](https://store.steampowered.com/app/264710/) / [Epic](https://store.epicgames.com/p/subnautica) | [dekart811](https://github.com/dekart811), [Zash](https://github.com/ZashIn) | [game_subnautica.py](games/game_subnautica.py) | <ul><li>mod data checker</li><li>save game preview</li></ul> |
| Subnautica: Below Zero — [STEAM](https://store.steampowered.com/app/848450/) | [dekart811](https://github.com/dekart811), [Zash](https://github.com/ZashIn) | [game_subnautica-below-zero.py](games/game_subnautica-below-zero.py) | <ul><li>mod data checker</li><li>save game preview</li></ul> |
| Valheim — [STEAM](https://store.steampowered.com/app/892970/Valheim/) | [Zash](https://github.com/ZashIn) | [game_valheim.py](games/game_valheim.py) | <ul><li>mod data checker</li><li>overwrite config sync</li><li>save game support (no preview)</li></ul> |
| The Witcher: Enhanced Edition - [GOG](https://www.gog.com/game/the_witcher) / [STEAM](https://store.steampowered.com/app/20900/The_Witcher_Enhanced_Edition_Directors_Cut/) | [erri120](https://github.com/erri120) | [game_witcher1.py](games/game_witcher1.py) | <ul><li>save game parsing (no preview)</li></ul> |
| The Witcher 3: Wild Hunt — [GOG](https://www.gog.com/game/the_witcher_3_wild_hunt) / [STEAM](https://store.steampowered.com/app/292030/The_Witcher_3_Wild_Hunt/) | [Holt59](https://github.com/holt59/) | [game_witcher3.py](games/game_witcher3.py) | <ul><li>save game preview</li></ul> |
Expand Down Expand Up @@ -181,7 +182,7 @@ The meta-plugin provides some useful extra feature:
3. **Basic mod data checker** (Python):
Check and fix different mod archive layouts for an automatic installation with the proper
file structure, using simple (glob) patterns via `BasicModDataChecker`.
See [games/game_valheim.py](games/game_valheim.py) for an example.
See [games/game_valheim.py](games/game_valheim.py) and [game_subnautica.py](games/game_subnautica.py) for an example.

Game IDs can be found here:

Expand Down
28 changes: 21 additions & 7 deletions games/game_subnautica-below-zero.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
from ..basic_game import BasicGame
from __future__ import annotations

import mobase

class SubnauticaGame(BasicGame):
from . import game_subnautica # namespace to not load SubnauticaGame here, too!


class SubnauticaBelowZeroGame(game_subnautica.SubnauticaGame):

Name = "Subnautica Below Zero Support Plugin"
Author = "dekart811"
Version = "1.0"
Author = "dekart811, Zash"
Version = "2.1"

GameName = "Subnautica: Below Zero"
GameShortName = "subnauticabelowzero"
GameNexusName = "subnauticabelowzero"
GameSteamId = 848450
GameBinary = "SubnauticaZero.exe"
GameDataPath = "QMods"
GameDataPath = "_ROOT"
GameDocumentsDirectory = "%GAME_PATH%"
GameSupportURL = (
r"https://github.com/ModOrganizer2/modorganizer-basic_games/wiki/"
"Game:-Subnautica:-Below-Zero"
)
GameSavesDirectory = r"%GAME_PATH%\SNAppData\SavedGames"

_game_extra_save_paths = [
r"%USERPROFILE%\Appdata\LocalLow\Unknown Worlds"
r"\Subnautica Below Zero\SubnauticaZero\SavedGames"
]

def iniFiles(self):
return ["doorstop_config.ini"]
def init(self, organizer: mobase.IOrganizer) -> bool:
super().init(organizer)
checker = game_subnautica.SubnauticaModDataChecker()
checker.update_patterns({"unfold": ["BepInExPack_BelowZero"]})
self._featureMap[mobase.ModDataChecker] = checker
return True
165 changes: 158 additions & 7 deletions games/game_subnautica.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,175 @@
from __future__ import annotations
from enum import Enum

import os
from collections.abc import Iterable
from pathlib import Path

from PyQt6.QtCore import QDir, qWarning

import mobase

from ..basic_features import BasicModDataChecker
from ..basic_features.basic_save_game_info import (
BasicGameSaveGame,
BasicGameSaveGameInfo,
)
from ..basic_game import BasicGame


class SubnauticaGame(BasicGame):
class SubnauticaModDataChecker(BasicModDataChecker):
default_file_patterns = {
"unfold": ["BepInExPack_Subnautica"],
"valid": ["winhttp.dll", "doorstop_config.ini", "BepInEx", "QMods"],
"delete": [
"*.txt",
"*.md",
"icon.png",
"license",
"manifest.json",
],
"move": {"plugins": "BepInEx/", "patchers": "BepInEx/", "*": "QMods/"},
}


class SubnauticaGame(BasicGame, mobase.IPluginFileMapper):

Name = "Subnautica Support Plugin"
Author = "dekart811"
Version = "1.0"
Author = "dekart811, Zash"
Version = "2.1.1"

GameName = "Subnautica"
GameShortName = "subnautica"
GameNexusName = "subnautica"
GameSteamId = 264710
GameEpicId = "Jaguar"
GameBinary = "Subnautica.exe"
GameDataPath = "QMods"
GameDocumentsDirectory = "%GAME_PATH%"
GameDataPath = "_ROOT" # Custom mappings to actual root folders below.
GameDocumentsDirectory = r"%GAME_PATH%"
GameSupportURL = (
r"https://github.com/ModOrganizer2/modorganizer-basic_games/wiki/"
"Game:-Subnautica"
)
GameSavesDirectory = r"%GAME_PATH%\SNAppData\SavedGames"

_game_extra_save_paths = [
r"%USERPROFILE%\Appdata\LocalLow\Unknown Worlds"
r"\Subnautica\Subnautica\SavedGames"
]

_forced_libraries = ["winhttp.dll"]

_root_blacklist = {GameDataPath.casefold()}

class MapType(Enum):
FILE = 0
FOLDER = 1

_root_extra_overwrites: dict[str, MapType] = {
"qmodmanager_log-Subnautica.txt": MapType.FILE,
"qmodmanager-config.json": MapType.FILE,
"BepInEx_Shim_Backup": MapType.FOLDER,
}
"""Extra files & folders created in game root by mods / BepInEx after game launch,
but not included in the mod archives.
"""

def __init__(self):
super().__init__()
mobase.IPluginFileMapper.__init__(self)

def init(self, organizer: mobase.IOrganizer) -> bool:
super().init(organizer)
self._featureMap[mobase.ModDataChecker] = SubnauticaModDataChecker()
self._featureMap[mobase.SaveGameInfo] = BasicGameSaveGameInfo(
lambda s: os.path.join(s, "screenshot.jpg")
)
return True

def listSaves(self, folder: QDir) -> list[mobase.ISaveGame]:
return [
BasicGameSaveGame(folder)
for save_path in (
folder.absolutePath(),
*(os.path.expandvars(p) for p in self._game_extra_save_paths),
)
for folder in Path(save_path).glob("slot*")
]

def executableForcedLoads(self) -> list[mobase.ExecutableForcedLoadSetting]:
return [
mobase.ExecutableForcedLoadSetting(self.binaryName(), lib).withEnabled(True)
for lib in self._forced_libraries
]

def mappings(self) -> list[mobase.Mapping]:
game = self._organizer.managedGame()
game_path = Path(game.gameDirectory().absolutePath())
overwrite_path = Path(self._organizer.overwritePath())

return [
*(
# Extra overwrites
self._overwrite_mapping(
overwrite_path / name,
dest,
is_dir=(map_type is self.MapType.FOLDER),
)
for name, map_type in self._root_extra_overwrites.items()
if not (dest := game_path / name).exists()
),
*self._root_mappings(game_path, overwrite_path),
]

def _root_mappings(
self, game_path: Path, overwrite_path: Path
) -> Iterable[mobase.Mapping]:
for mod_path in self._active_mod_paths():
mod_name = mod_path.name

for child in mod_path.iterdir():
# Check blacklist
if child.name.casefold() in self._root_blacklist:
qWarning(f"Skipping {child.name} ({mod_name})")
continue
destination = game_path / child.name
# Check existing
if destination.exists():
qWarning(
f"Overwriting of existing game files/folders is not supported! "
f"{destination.as_posix()} ({mod_name})"
)
continue
# Mapping: mod -> root
yield mobase.Mapping(
source=str(child),
destination=str(destination),
is_directory=child.is_dir(),
create_target=False,
)
if child.is_dir():
# Mapping: overwrite <-> root
yield self._overwrite_mapping(
overwrite_path / child.name, destination, is_dir=True
)

def _active_mod_paths(self) -> Iterable[Path]:
mods_parent_path = Path(self._organizer.modsPath())
modlist = self._organizer.modList().allModsByProfilePriority()
for mod in modlist:
if self._organizer.modList().state(mod) & mobase.ModState.ACTIVE:
yield mods_parent_path / mod

def iniFiles(self):
return ["doorstop_config.ini"]
def _overwrite_mapping(
self, overwrite_source: Path, destination: Path, is_dir: bool
) -> mobase.Mapping:
"""Mapping: overwrite <-> root"""
if is_dir:
# Root folders in overwrite need to exits.
overwrite_source.mkdir(parents=True, exist_ok=True)
return mobase.Mapping(
str(overwrite_source),
str(destination),
is_dir,
create_target=True,
)

0 comments on commit 6a9a9bb

Please sign in to comment.