Skip to content

Commit 7accbb8

Browse files
Add Kingdom Come: Deliverance II Plugin (#221)
1 parent 45359c5 commit 7accbb8

1 file changed

Lines changed: 104 additions & 0 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import os
2+
import xml.etree.ElementTree as ET
3+
from pathlib import Path
4+
5+
from PyQt6.QtCore import QDir, qInfo, qWarning
6+
7+
import mobase
8+
9+
from ..basic_features import BasicModDataChecker, GlobPatterns
10+
from ..basic_game import BasicGame
11+
12+
13+
class KingdomComeDeliverance2Game(BasicGame):
14+
Name = "Kingdom Come: Deliverance 2 Support Plugin"
15+
Author = "TheForgotten69"
16+
Version = "1.0.0"
17+
18+
GameName = "Kingdom Come: Deliverance II"
19+
GameShortName = "kingdomcomedeliverance2"
20+
GameNexusName = "kingdomcomedeliverance2"
21+
GameNexusId = 7286
22+
GameSteamId = [1771300]
23+
GameBinary = "bin/Win64MasterMasterSteamPGO/KingdomCome.exe"
24+
GameDataPath = "Mods"
25+
GameSaveExtension = "whs"
26+
GameDocumentsDirectory = "%GAME_PATH%"
27+
GameSavesDirectory = "%USERPROFILE%/Saved Games/kingdomcome2/saves"
28+
29+
def init(self, organizer: mobase.IOrganizer) -> bool:
30+
super().init(organizer)
31+
self._register_feature(BasicModDataChecker(GlobPatterns(valid=["*"])))
32+
organizer.onAboutToRun(self._write_mod_order)
33+
return True
34+
35+
@staticmethod
36+
def _get_mod_id(mod_path: Path) -> str | None:
37+
"""Return the mod ID for a KCD2 mod.
38+
39+
Checks mod.manifest for <modid> (preferred) or <name>, falling back
40+
to the game mod folder name (the subfolder inside the MO2 mod directory).
41+
"""
42+
for manifest in mod_path.rglob("mod.manifest"):
43+
try:
44+
root = ET.parse(manifest).getroot()
45+
for tag in ("modid", "name"):
46+
elem = root.find(f".//{tag}")
47+
if elem is not None and elem.text and elem.text.strip():
48+
return elem.text.strip()
49+
except ET.ParseError:
50+
qWarning(f"KCD2: failed to parse {manifest}")
51+
# Fall back to the folder that contains mod.manifest
52+
return manifest.parent.name
53+
# No manifest — use first subdirectory name (the game mod folder)
54+
for subdir in mod_path.iterdir():
55+
if subdir.is_dir():
56+
return subdir.name
57+
return None
58+
59+
def _write_mod_order(self, app_path: str, wd: QDir, args: str) -> bool:
60+
if not self.isActive():
61+
return True
62+
63+
modlist = self._organizer.modList()
64+
mods_path = Path(self._organizer.modsPath())
65+
mod_order_path = Path(self._organizer.overwritePath()) / "mod_order.txt"
66+
67+
mod_ids: list[str] = []
68+
for mod_name in modlist.allModsByProfilePriority():
69+
if not (modlist.state(mod_name) & mobase.ModState.ACTIVE):
70+
continue
71+
mod_id = self._get_mod_id(mods_path / mod_name)
72+
if mod_id:
73+
qInfo(f"KCD2: mod '{mod_name}' -> id '{mod_id}'")
74+
mod_ids.append(mod_id)
75+
else:
76+
qWarning(f"KCD2: could not resolve id for mod '{mod_name}', skipping")
77+
78+
if not mod_ids:
79+
mod_order_path.unlink(missing_ok=True)
80+
return True
81+
82+
# MO2 priority 1 = top = highest priority. KCD2 is last-loaded-wins,
83+
# so highest priority must be last in the file.
84+
mod_ids.reverse()
85+
mod_order_path.parent.mkdir(parents=True, exist_ok=True)
86+
mod_order_path.write_text("\n".join(mod_ids))
87+
qInfo(f"KCD2: wrote mod_order.txt with {len(mod_ids)} mods: {mod_ids}")
88+
return True
89+
90+
def iniFiles(self):
91+
return ["custom.cfg", "system.cfg", "user.cfg"]
92+
93+
def initializeProfile(self, directory: QDir, settings: mobase.ProfileSetting):
94+
for iniFile in self.iniFiles():
95+
iniPath = self.documentsDirectory().absoluteFilePath(iniFile)
96+
if not os.path.exists(iniPath):
97+
with open(iniPath, "w") as _:
98+
pass
99+
100+
modsPath = self.dataDirectory().absolutePath()
101+
if not os.path.exists(modsPath):
102+
os.mkdir(modsPath)
103+
104+
super().initializeProfile(directory, settings)

0 commit comments

Comments
 (0)