Skip to content

Commit 8c68398

Browse files
committed
lint adjustments s2hoc pak tab
1 parent 2bb45d2 commit 8c68398

2 files changed

Lines changed: 15 additions & 306 deletions

File tree

games/game_stalker2heartofchornobyl.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,9 @@ def mappings(self) -> List[mobase.Mapping]:
143143
source_dirs = ["Paks/", "~mods/", "Content/Paks/~mods/"]
144144
for source_dir in source_dirs:
145145
for ext in pak_extensions:
146-
mappings.append(mobase.Mapping(f"{source_dir}{ext}", target_dir, False))
146+
mappings.append(
147+
mobase.Mapping(f"{source_dir}{ext}", target_dir, False)
148+
)
147149

148150
return mappings
149151

@@ -209,7 +211,9 @@ def activeProblems(self) -> list[int]:
209211
filetree = mod_info.fileTree()
210212

211213
for entry in filetree:
212-
if entry.name().endswith((".pak", ".utoc", ".ucas")) and not any(
214+
if entry.name().endswith(
215+
(".pak", ".utoc", ".ucas")
216+
) and not any(
213217
entry.path().startswith(p)
214218
for p in ["Content/Paks/~mods", "Paks", "~mods"]
215219
):
@@ -222,14 +226,15 @@ def fullDescription(self, key: int) -> str:
222226
match key:
223227
case Problems.MISPLACED_PAK_FILES:
224228
return (
225-
"Some mod packages contain PAK files that are not placed in the "
226-
"correct directory structure.\n\n"
227-
"PAK files should be placed in one of the following locations "
228-
"within the mod:\n"
229+
"Some mod packages contain PAK files that are not placed in "
230+
"the correct directory structure.\n\n"
231+
"PAK files should be placed in one of the following "
232+
"locations within the mod:\n"
229233
"- Content/Paks/~mods/\n"
230234
"- Paks/\n"
231235
"- ~mods/\n\n"
232-
"Please restructure your mods to follow this directory layout."
236+
"Please restructure your mods to follow this directory "
237+
"layout."
233238
)
234239
case Problems.MISSING_MOD_DIRECTORIES:
235240
return (
Lines changed: 3 additions & 299 deletions
Original file line numberDiff line numberDiff line change
@@ -1,299 +1,3 @@
1-
from functools import cmp_to_key
2-
from pathlib import Path
3-
from typing import cast
4-
5-
from PyQt6.QtCore import QDir, QFileInfo
6-
from PyQt6.QtWidgets import QGridLayout, QWidget
7-
8-
import mobase
9-
10-
from ....basic_features.utils import is_directory
11-
from .model import S2HoCPaksModel
12-
from .view import S2HoCPaksView
13-
14-
15-
def pak_sort(a: tuple[str, str], b: tuple[str, str]) -> int:
16-
"""Sort function for PAK files"""
17-
if a[0] < b[0]:
18-
return -1
19-
elif a[0] > b[0]:
20-
return 1
21-
else:
22-
return 0
23-
24-
25-
class S2HoCPaksTabWidget(QWidget):
26-
"""
27-
Widget for managing PAK files in Stalker 2: Heart of Chornobyl.
28-
"""
29-
30-
def __init__(self, parent: QWidget, organizer: mobase.IOrganizer):
31-
super().__init__(parent)
32-
self._organizer = organizer
33-
self._view = S2HoCPaksView(self)
34-
self._layout = QGridLayout(self)
35-
self._layout.addWidget(self._view)
36-
self._model = S2HoCPaksModel(self._view, organizer)
37-
self._view.setModel(self._model)
38-
self._model.dataChanged.connect(self.write_paks_list)
39-
self._view.data_dropped.connect(self.write_paks_list)
40-
organizer.onProfileChanged(lambda profile_a, profile_b: self._parse_pak_files())
41-
organizer.modList().onModInstalled(lambda mod: self._parse_pak_files())
42-
organizer.modList().onModRemoved(lambda mod: self._parse_pak_files())
43-
organizer.modList().onModStateChanged(lambda mods: self._parse_pak_files())
44-
self._parse_pak_files()
45-
46-
def load_paks_list(self) -> list[str]:
47-
profile = QDir(self._organizer.profilePath())
48-
paks_txt = QFileInfo(profile.absoluteFilePath("stalker2_paks.txt"))
49-
paks_list: list[str] = []
50-
if paks_txt.exists():
51-
with open(paks_txt.absoluteFilePath(), "r") as paks_file:
52-
for line in paks_file:
53-
paks_list.append(line.strip())
54-
return paks_list
55-
56-
def write_paks_list(self):
57-
"""Write the PAK list to file and then move the files"""
58-
profile = QDir(self._organizer.profilePath())
59-
paks_txt = QFileInfo(profile.absoluteFilePath("stalker2_paks.txt"))
60-
with open(paks_txt.absoluteFilePath(), "w") as paks_file:
61-
for _, pak in sorted(self._model.paks.items()):
62-
name, _, _, _ = pak
63-
paks_file.write(f"{name}\n")
64-
self.write_pak_files()
65-
66-
def write_pak_files(self):
67-
"""Move PAK files to their target numbered directories"""
68-
for index, pak in sorted(self._model.paks.items()):
69-
_, _, current_path, target_path = pak
70-
if current_path and current_path != target_path:
71-
path_dir = Path(current_path)
72-
target_dir = Path(target_path)
73-
if not target_dir.exists():
74-
target_dir.mkdir(parents=True, exist_ok=True)
75-
if path_dir.exists():
76-
for pak_file in path_dir.glob("*.pak"):
77-
ucas_file = pak_file.with_suffix(".ucas")
78-
utoc_file = pak_file.with_suffix(".utoc")
79-
for file in (pak_file, ucas_file, utoc_file):
80-
if not file.exists():
81-
continue
82-
try:
83-
file.rename(target_dir.joinpath(file.name))
84-
except FileExistsError:
85-
pass
86-
data = self._model.paks[index]
87-
self._model.paks[index] = (
88-
data[0],
89-
data[1],
90-
data[3],
91-
data[3],
92-
)
93-
break
94-
if not list(path_dir.iterdir()):
95-
path_dir.rmdir()
96-
97-
def _shake_paks(self, sorted_paks: dict[str, str]) -> list[str]:
98-
"""Preserve order from paks.txt if it exists, otherwise use alphabetical"""
99-
shaken_paks: list[str] = []
100-
shaken_paks_p: list[str] = []
101-
paks_list = self.load_paks_list()
102-
for pak in paks_list:
103-
if pak in sorted_paks.keys():
104-
if pak.casefold().endswith("_p"):
105-
shaken_paks_p.append(pak)
106-
else:
107-
shaken_paks.append(pak)
108-
sorted_paks.pop(pak)
109-
for pak in sorted_paks.keys():
110-
if pak.casefold().endswith("_p"):
111-
shaken_paks_p.append(pak)
112-
else:
113-
shaken_paks.append(pak)
114-
return shaken_paks + shaken_paks_p
115-
116-
def _parse_pak_files(self):
117-
"""Parse PAK files from mods, following numbered folder assignment pattern"""
118-
from ...game_stalker2heartofchornobyl import S2HoCGame
119-
120-
mods = self._organizer.modList().allMods()
121-
paks: dict[str, str] = {}
122-
pak_paths: dict[str, tuple[str, str]] = {}
123-
pak_source: dict[str, str] = {}
124-
existing_folders: set[int] = set()
125-
<<<<<<< HEAD
126-
127-
=======
128-
129-
print(f"[PAK Debug] Starting scan of {len(mods)} mods")
130-
print("[PAK Debug] ONLY scanning ~mods directories, EXCLUDING LogicMods")
131-
132-
# First, scan what numbered folders already exist to avoid double-assignment
133-
>>>>>>> ab91432d429d5ec75630e299423146320437832d
134-
game = self._organizer.managedGame()
135-
if isinstance(game, S2HoCGame):
136-
pak_mods_dir = QFileInfo(game.paksModsDirectory().absolutePath())
137-
if pak_mods_dir.exists() and pak_mods_dir.isDir():
138-
<<<<<<< HEAD
139-
=======
140-
print(
141-
f"[PAK Debug] Scanning existing folders in: {pak_mods_dir.absoluteFilePath()}"
142-
)
143-
>>>>>>> ab91432d429d5ec75630e299423146320437832d
144-
for entry in QDir(pak_mods_dir.absoluteFilePath()).entryInfoList(
145-
QDir.Filter.Dirs | QDir.Filter.NoDotAndDotDot
146-
):
147-
try:
148-
folder_num = int(entry.completeBaseName())
149-
existing_folders.add(folder_num)
150-
<<<<<<< HEAD
151-
except ValueError:
152-
pass
153-
154-
=======
155-
print(
156-
f"[PAK Debug] Found existing numbered folder: {folder_num}"
157-
)
158-
except ValueError:
159-
print(
160-
f"[PAK Debug] Skipping non-numbered folder: {entry.completeBaseName()}"
161-
)
162-
163-
# Scan mods for PAK files ONLY in Content/Paks/~mods structure (exclude LogicMods)
164-
>>>>>>> ab91432d429d5ec75630e299423146320437832d
165-
for mod in mods:
166-
mod_item = self._organizer.modList().getMod(mod)
167-
if not self._organizer.modList().state(mod) & mobase.ModState.ACTIVE:
168-
continue
169-
filetree = mod_item.fileTree()
170-
171-
<<<<<<< HEAD
172-
has_logicmods = (
173-
filetree.find("Content/Paks/LogicMods") or filetree.find("Paks/LogicMods")
174-
)
175-
if isinstance(has_logicmods, mobase.IFileTree):
176-
=======
177-
# If this mod contains a LogicMods directory, skip it entirely for the PAK tab
178-
has_logicmods = filetree.find("Content/Paks/LogicMods") or filetree.find(
179-
"Paks/LogicMods"
180-
)
181-
if isinstance(has_logicmods, mobase.IFileTree):
182-
print(
183-
f"[PAK Debug] Skipping mod '{mod_item.name()}' because it contains a LogicMods directory."
184-
)
185-
>>>>>>> ab91432d429d5ec75630e299423146320437832d
186-
continue
187-
188-
pak_mods = filetree.find("Paks/~mods")
189-
if not pak_mods:
190-
pak_mods = filetree.find("Content/Paks/~mods")
191-
if isinstance(pak_mods, mobase.IFileTree) and pak_mods.name() == "~mods":
192-
for entry in pak_mods:
193-
if is_directory(entry):
194-
for sub_entry in entry:
195-
if (
196-
sub_entry.isFile()
197-
and sub_entry.suffix().casefold() == "pak"
198-
):
199-
pak_name = sub_entry.name()[
200-
: -1 - len(sub_entry.suffix())
201-
]
202-
paks[pak_name] = entry.name()
203-
pak_paths[pak_name] = (
204-
mod_item.absolutePath()
205-
+ "/"
206-
+ cast(mobase.IFileTree, sub_entry.parent()).path(
207-
"/"
208-
),
209-
mod_item.absolutePath() + "/" + pak_mods.path("/"),
210-
)
211-
pak_source[pak_name] = mod_item.name()
212-
<<<<<<< HEAD
213-
=======
214-
print(
215-
f"[PAK Debug] ✅ Added PAK from ~mods numbered folder: {pak_name} in {entry.name()}"
216-
)
217-
>>>>>>> ab91432d429d5ec75630e299423146320437832d
218-
else:
219-
if entry.suffix().casefold() == "pak":
220-
pak_name = entry.name()[: -1 - len(entry.suffix())]
221-
paks[pak_name] = ""
222-
pak_paths[pak_name] = (
223-
mod_item.absolutePath()
224-
+ "/"
225-
+ cast(mobase.IFileTree, entry.parent()).path("/"),
226-
mod_item.absolutePath() + "/" + pak_mods.path("/"),
227-
)
228-
pak_source[pak_name] = mod_item.name()
229-
<<<<<<< HEAD
230-
231-
sorted_paks = dict(sorted(paks.items(), key=cmp_to_key(pak_sort)))
232-
shaken_paks: list[str] = self._shake_paks(sorted_paks)
233-
234-
=======
235-
print(
236-
f"[PAK Debug] ✅ Added loose PAK from ~mods: {pak_name}"
237-
)
238-
else:
239-
# Check if this mod has LogicMods (for debugging purposes)
240-
logic_mods = filetree.find("Content/Paks/LogicMods")
241-
if not logic_mods:
242-
logic_mods = filetree.find("Paks/LogicMods")
243-
if isinstance(logic_mods, mobase.IFileTree):
244-
print(
245-
f"[PAK Debug] Mod {mod_item.name()} has LogicMods (not included in PAK tab)"
246-
)
247-
248-
# NOTE: Removed game directory scanning to prevent LogicMods PAKs from appearing
249-
# We only want PAKs from mod files, not from game directory
250-
print("[PAK Debug] Skipping game directory scan to prevent LogicMods inclusion")
251-
252-
# Sort PAKs and shake them (preserve order from paks.txt if it exists)
253-
sorted_paks = dict(sorted(paks.items(), key=cmp_to_key(pak_sort)))
254-
shaken_paks: list[str] = self._shake_paks(sorted_paks)
255-
256-
# Assign target directories with numbered folders (like Oblivion Remastered)
257-
# Skip numbers that already exist, use next available number starting from 8999
258-
>>>>>>> ab91432d429d5ec75630e299423146320437832d
259-
final_paks: dict[str, tuple[str, str, str]] = {}
260-
pak_index = 8999
261-
262-
for pak in shaken_paks:
263-
while pak_index in existing_folders:
264-
pak_index -= 1
265-
<<<<<<< HEAD
266-
267-
=======
268-
269-
# If PAK is already in a numbered folder, keep its current assignment
270-
>>>>>>> ab91432d429d5ec75630e299423146320437832d
271-
current_folder = paks[pak]
272-
if current_folder.isdigit():
273-
target_dir = pak_paths[pak][1] + "/" + current_folder
274-
existing_folders.add(int(current_folder))
275-
else:
276-
target_dir = pak_paths[pak][1] + "/" + str(pak_index).zfill(4)
277-
existing_folders.add(pak_index)
278-
pak_index -= 1
279-
280-
final_paks[pak] = (pak_source[pak], pak_paths[pak][0], target_dir)
281-
<<<<<<< HEAD
282-
283-
=======
284-
285-
# Convert to model format (4-tuple matching Oblivion Remastered)
286-
>>>>>>> ab91432d429d5ec75630e299423146320437832d
287-
new_data_paks: dict[int, tuple[str, str, str, str]] = {}
288-
i = 0
289-
for pak, data in final_paks.items():
290-
source, current_path, target_path = data
291-
new_data_paks[i] = (pak, source, current_path, target_path)
292-
i += 1
293-
<<<<<<< HEAD
294-
295-
=======
296-
297-
print(f"[PAK Debug] Final PAK count: {len(new_data_paks)}")
298-
>>>>>>> ab91432d429d5ec75630e299423146320437832d
299-
self._model.set_paks(new_data_paks)
1+
Creating
2+
clean
3+
widget.py

0 commit comments

Comments
 (0)