Files

293 lines
9.9 KiB
Python

from enum import IntEnum
from pathlib import Path
import mobase
from PyQt6.QtCore import QDir, QFileInfo, Qt
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QWidget
from ..basic_features.basic_save_game_info import (
BasicGameSaveGame,
BasicGameSaveGameInfo,
)
from ..basic_game import BasicGame
from .stalkeranomaly import XRSave
class StalkerAnomalyModDataChecker(mobase.ModDataChecker):
_valid_folders: list[str] = [
"appdata",
"bin",
"db",
"gamedata",
]
def hasValidFolders(self, tree: mobase.IFileTree) -> bool:
for e in tree:
if e.isDir():
if e.name().lower() in self._valid_folders:
return True
return False
def findLostData(self, tree: mobase.IFileTree) -> list[mobase.FileTreeEntry]:
lost_db: list[mobase.FileTreeEntry] = []
for e in tree:
if e.isFile():
if e.suffix().lower().startswith("db"):
lost_db.append(e)
return lost_db
def dataLooksValid(
self, filetree: mobase.IFileTree
) -> mobase.ModDataChecker.CheckReturn:
if self.hasValidFolders(filetree):
return mobase.ModDataChecker.VALID
if self.findLostData(filetree):
return mobase.ModDataChecker.FIXABLE
return mobase.ModDataChecker.INVALID
def fix(self, filetree: mobase.IFileTree) -> mobase.IFileTree:
lost_db = self.findLostData(filetree)
if lost_db:
rfolder = filetree.addDirectory("db").addDirectory("mods")
for r in lost_db:
rfolder.insert(r, mobase.IFileTree.REPLACE)
return filetree
class Content(IntEnum):
INTERFACE = 0
TEXTURE = 1
MESH = 2
SCRIPT = 3
SOUND = 4
MCM = 5
CONFIG = 6
class StalkerAnomalyModDataContent(mobase.ModDataContent):
content: list[int] = []
def getAllContents(self) -> list[mobase.ModDataContent.Content]:
return [
mobase.ModDataContent.Content(
Content.INTERFACE, "Interface", ":/MO/gui/content/interface"
),
mobase.ModDataContent.Content(
Content.TEXTURE, "Textures", ":/MO/gui/content/texture"
),
mobase.ModDataContent.Content(
Content.MESH, "Meshes", ":/MO/gui/content/mesh"
),
mobase.ModDataContent.Content(
Content.SCRIPT, "Scripts", ":/MO/gui/content/script"
),
mobase.ModDataContent.Content(
Content.SOUND, "Sounds", ":/MO/gui/content/sound"
),
mobase.ModDataContent.Content(Content.MCM, "MCM", ":/MO/gui/content/menu"),
mobase.ModDataContent.Content(
Content.CONFIG, "Configs", ":/MO/gui/content/inifile"
),
]
def walkContent(
self, path: str, entry: mobase.FileTreeEntry
) -> mobase.IFileTree.WalkReturn:
name = entry.name().lower()
if entry.isFile():
ext = entry.suffix().lower()
if ext in ["dds", "thm"]:
self.content.append(Content.TEXTURE)
if path.startswith("gamedata/textures/ui"):
self.content.append(Content.INTERFACE)
elif ext in ["omf", "ogf"]:
self.content.append(Content.MESH)
elif ext in ["script"]:
self.content.append(Content.SCRIPT)
if "_mcm" in name:
self.content.append(Content.MCM)
elif ext in ["ogg"]:
self.content.append(Content.SOUND)
elif ext in ["ltx", "xml"]:
self.content.append(Content.CONFIG)
if path.startswith("gamedata/configs/ui"):
self.content.append(Content.INTERFACE)
return mobase.IFileTree.WalkReturn.CONTINUE
def getContentsFor(self, filetree: mobase.IFileTree) -> list[int]:
self.content = []
filetree.walk(self.walkContent, "/")
return self.content
class StalkerAnomalySaveGame(BasicGameSaveGame):
_filepath: Path
xr_save: XRSave
def __init__(self, filepath: Path):
super().__init__(filepath)
self._filepath = filepath
self.xr_save = XRSave(self._filepath)
def getName(self) -> str:
xr_save = self.xr_save
player = xr_save.player
if player:
name = player.character_name_str
time = xr_save.time_fmt
return f"{name}, {xr_save.save_fmt} [{time}]"
return ""
def allFiles(self) -> list[str]:
filepath = str(self._filepath)
paths = [filepath]
scoc = filepath.replace(".scop", ".scoc")
if Path(scoc).exists():
paths.append(scoc)
dds = filepath.replace(".scop", ".dds")
if Path(dds).exists():
paths.append(dds)
return paths
class StalkerAnomalySaveGameInfoWidget(mobase.ISaveGameInfoWidget):
def __init__(self, parent: QWidget | None):
super().__init__(parent)
layout = QVBoxLayout()
self._labelSave = self.newLabel(layout)
self._labelName = self.newLabel(layout)
self._labelFaction = self.newLabel(layout)
self._labelHealth = self.newLabel(layout)
self._labelMoney = self.newLabel(layout)
self._labelRank = self.newLabel(layout)
self._labelRep = self.newLabel(layout)
self.setLayout(layout)
palette = self.palette()
palette.setColor(self.backgroundRole(), Qt.GlobalColor.black)
self.setAutoFillBackground(True)
self.setPalette(palette)
self.setWindowFlags(
Qt.WindowType.ToolTip | Qt.WindowType.BypassGraphicsProxyWidget
)
def newLabel(self, layout: QVBoxLayout) -> QLabel:
label = QLabel()
label.setAlignment(Qt.AlignmentFlag.AlignLeft)
palette = label.palette()
palette.setColor(label.foregroundRole(), Qt.GlobalColor.white)
label.setPalette(palette)
layout.addWidget(label)
layout.addStretch()
return label
def setSave(self, save: mobase.ISaveGame):
self.resize(240, 32)
if not isinstance(save, StalkerAnomalySaveGame):
return
xr_save = save.xr_save
player = xr_save.player
if player:
self._labelSave.setText(f"Save: {xr_save.save_fmt}")
self._labelName.setText(f"Name: {player.character_name_str}")
self._labelFaction.setText(f"Faction: {xr_save.getFaction()}")
self._labelHealth.setText(f"Health: {player.health:.2f}%")
self._labelMoney.setText(f"Money: {player.money} RU")
self._labelRank.setText(f"Rank: {xr_save.getRank()} ({player.rank})")
self._labelRep.setText(
f"Reputation: {xr_save.getReputation()} ({player.reputation})"
)
class StalkerAnomalySaveGameInfo(BasicGameSaveGameInfo):
def getSaveGameWidget(self, parent: QWidget | None = None):
return StalkerAnomalySaveGameInfoWidget(parent)
class StalkerAnomalyGame(BasicGame, mobase.IPluginFileMapper):
Name = "STALKER Anomaly"
Author = "Qudix"
Version = "0.5.0"
Description = "Adds support for STALKER Anomaly"
GameName = "STALKER Anomaly"
GameShortName = "stalkeranomaly"
GameNexusName = "stalkeranomaly"
GameNexusId = 3743
GameBinary = "AnomalyLauncher.exe"
GameDataPath = ""
GameDocumentsDirectory = "%GAME_PATH%/appdata"
GameSupportURL = (
r"https://github.com/ModOrganizer2/modorganizer-basic_games/wiki/"
"Game:-S.T.A.L.K.E.R.-Anomaly"
)
GameSaveExtension = "scop"
GameSavesDirectory = "%GAME_DOCUMENTS%/savedgames"
def __init__(self):
BasicGame.__init__(self)
mobase.IPluginFileMapper.__init__(self)
def init(self, organizer: mobase.IOrganizer):
BasicGame.init(self, organizer)
self._featureMap[mobase.ModDataChecker] = StalkerAnomalyModDataChecker()
self._featureMap[mobase.ModDataContent] = StalkerAnomalyModDataContent()
self._featureMap[mobase.SaveGameInfo] = StalkerAnomalySaveGameInfo()
organizer.onAboutToRun(lambda _str: self.aboutToRun(_str))
return True
def aboutToRun(self, _str: str) -> bool:
gamedir = self.gameDirectory()
if gamedir.exists():
# For mappings
gamedir.mkdir("appdata")
# The game will crash if this file exists in the
# virtual tree rather than the game dir
dbg_path = Path(self._gamePath, "gamedata/configs/cache_dbg.ltx")
if not dbg_path.exists():
dbg_path.parent.mkdir(parents=True, exist_ok=True)
with open(dbg_path, "w", encoding="utf-8"):
pass
return True
def executables(self) -> list[mobase.ExecutableInfo]:
info = [
["Anomaly Launcher", "AnomalyLauncher.exe"],
["Anomaly (DX11-AVX)", "bin/AnomalyDX11AVX.exe"],
["Anomaly (DX11)", "bin/AnomalyDX11.exe"],
["Anomaly (DX10-AVX)", "bin/AnomalyDX10AVX.exe"],
["Anomaly (DX10)", "bin/AnomalyDX10.exe"],
["Anomaly (DX9-AVX)", "bin/AnomalyDX9AVX.exe"],
["Anomaly (DX9)", "bin/AnomalyDX9.exe"],
["Anomaly (DX8-AVX)", "bin/AnomalyDX8AVX.exe"],
["Anomaly (DX8)", "bin/AnomalyDX8.exe"],
]
gamedir = self.gameDirectory()
return [
mobase.ExecutableInfo(inf[0], QFileInfo(gamedir, inf[1])) for inf in info
]
def listSaves(self, folder: QDir) -> list[mobase.ISaveGame]:
ext = self._mappings.savegameExtension.get()
return [
StalkerAnomalySaveGame(path)
for path in Path(folder.absolutePath()).glob(f"*.{ext}")
]
def mappings(self) -> list[mobase.Mapping]:
appdata = self.gameDirectory().filePath("appdata")
m = mobase.Mapping()
m.createTarget = True
m.isDirectory = True
m.source = appdata
m.destination = appdata
return [m]