diff --git a/Libraries/PyKotor/src/pykotor/common/module.py b/Libraries/PyKotor/src/pykotor/common/module.py
index b90bc4d71..74b6093bf 100644
--- a/Libraries/PyKotor/src/pykotor/common/module.py
+++ b/Libraries/PyKotor/src/pykotor/common/module.py
@@ -6,7 +6,7 @@
from dataclasses import dataclass
from enum import Enum
from functools import lru_cache
-from typing import TYPE_CHECKING, Any, Collection, Generic, Iterable, TypeVar, TypedDict, cast
+from typing import TYPE_CHECKING, Any, Collection, Generic, TypeVar, TypedDict, cast
from pykotor.common.stream import BinaryReader, BinaryWriter
from pykotor.extract.capsule import Capsule
@@ -48,6 +48,7 @@
if TYPE_CHECKING:
from collections.abc import Callable
+ from typing import Iterable, Sequence
from typing_extensions import Self
@@ -352,6 +353,33 @@ def __init__(
def root(self):
return self._root
+ @classmethod
+ def find_capsules(cls, installation: Installation, filename: str) -> Sequence[Capsule]:
+ root = cls.find_root(filename)
+ # Build all capsules relevant to this root in the provided installation
+ capsules: _CapsuleDictTypes = {
+ ModuleType.MAIN.name: None,
+ ModuleType.DATA.name: None,
+ ModuleType.K2_DLG.name: None,
+ ModuleType.MOD.name: None,
+ }
+ if filename.lower().endswith(".mod"):
+ mod_filepath = installation.module_path().joinpath(root + ModuleType.MOD.value)
+ if mod_filepath.safe_isfile():
+ capsules[ModuleType.MOD.name] = ModuleFullOverridePiece(mod_filepath)
+ else:
+ capsules[ModuleType.MAIN.name] = ModuleLinkPiece(installation.module_path().joinpath(root + ModuleType.MAIN.value))
+ capsules[ModuleType.DATA.name] = ModuleDataPiece(installation.module_path().joinpath(root + ModuleType.DATA.value))
+ if installation.game().is_k2():
+ capsules[ModuleType.K2_DLG.name] = ModuleDLGPiece(installation.module_path().joinpath(root + ModuleType.K2_DLG.value))
+ else:
+ capsules[ModuleType.MAIN.name] = ModuleLinkPiece(installation.module_path().joinpath(root + ModuleType.MAIN.value))
+ capsules[ModuleType.DATA.name] = ModuleDataPiece(installation.module_path().joinpath(root + ModuleType.DATA.value))
+ if installation.game().is_k2():
+ capsules[ModuleType.K2_DLG.name] = ModuleDLGPiece(installation.module_path().joinpath(root + ModuleType.K2_DLG.value))
+ return [capsule for capsule in capsules.values() if capsule is not None]
+
+
def get_capsules(self) -> list[ModulePieceResource]:
"""Returns all relevant ERFs/RIMs for this module."""
return list(self._capsules.values())
diff --git a/Libraries/PyKotor/src/pykotor/extract/file.py b/Libraries/PyKotor/src/pykotor/extract/file.py
index 05481088d..ec5d85bbd 100644
--- a/Libraries/PyKotor/src/pykotor/extract/file.py
+++ b/Libraries/PyKotor/src/pykotor/extract/file.py
@@ -386,7 +386,7 @@ def from_path(cls, file_path: os.PathLike | str) -> ResourceIdentifier:
- Handles exceptions during processing
"""
try:
- path_obj = PurePath(file_path)
+ path_obj = PurePath.pathify(file_path)
except Exception:
return ResourceIdentifier("", ResourceType.from_extension(""))
diff --git a/Libraries/PyKotor/src/pykotor/extract/installation.py b/Libraries/PyKotor/src/pykotor/extract/installation.py
index 520b13004..173525b21 100644
--- a/Libraries/PyKotor/src/pykotor/extract/installation.py
+++ b/Libraries/PyKotor/src/pykotor/extract/installation.py
@@ -352,9 +352,9 @@ def save_locations(self) -> list[Path]:
elif system == "Linux": # TODO
xdg_data_home = os.getenv("XDG_DATA_HOME", "")
remaining_path_parts = PurePath("aspyr-media", "kotor2", "saves")
- if xdg_data_home.strip() and Path(xdg_data_home).safe_isdir():
- save_paths.append(Path(xdg_data_home, remaining_path_parts))
- save_paths.append(Path.home().joinpath(".local", "share", remaining_path_parts))
+ if xdg_data_home.strip() and CaseAwarePath(xdg_data_home).safe_isdir():
+ save_paths.append(CaseAwarePath(xdg_data_home, remaining_path_parts))
+ save_paths.append(CaseAwarePath.home().joinpath(".local", "share", remaining_path_parts))
# Filter and return existing paths
return [path for path in save_paths if path.safe_isdir()]
diff --git a/Libraries/PyKotor/src/pykotor/tslpatcher/mods/nss.py b/Libraries/PyKotor/src/pykotor/tslpatcher/mods/nss.py
index afbc0a477..24d912f2c 100644
--- a/Libraries/PyKotor/src/pykotor/tslpatcher/mods/nss.py
+++ b/Libraries/PyKotor/src/pykotor/tslpatcher/mods/nss.py
@@ -186,7 +186,7 @@ def _compile_with_external(
game: Game,
) -> bytes | Literal[True]:
with TemporaryDirectory() as tempdir:
- tempcompiled_filepath: Path = Path(tempdir) / "temp_script.ncs"
+ tempcompiled_filepath: Path = Path(tempdir, "temp_script.ncs")
stdout, stderr = nwnnsscompiler.compile_script(temp_script_file, tempcompiled_filepath, game)
result: bool | bytes = "File is an include file, ignored" in stdout
if not result:
diff --git a/Libraries/PyKotor/src/pykotor/tslpatcher/uninstall.py b/Libraries/PyKotor/src/pykotor/tslpatcher/uninstall.py
index 2d1ed78a3..9893000e6 100644
--- a/Libraries/PyKotor/src/pykotor/tslpatcher/uninstall.py
+++ b/Libraries/PyKotor/src/pykotor/tslpatcher/uninstall.py
@@ -170,7 +170,7 @@ def restore_backup(
restore_backup(Path('backup'), {'file1.txt', 'file2.txt'}, [Path('backup/file1.txt'), Path('backup/file2.txt')])
"""
for file_str in existing_files:
- file_path = Path.pathify(file_str)
+ file_path = Path(file_str)
rel_filepath: Path = file_path.relative_to(self.game_path) # type: ignore[attr-defined]
file_path.unlink(missing_ok=True) # type: ignore[attr-defined]
self.log.add_note(f"Removed {rel_filepath}...")
diff --git a/Libraries/Utility/src/utility/system/path.py b/Libraries/Utility/src/utility/system/path.py
index 272c7a8d3..9151b34d4 100644
--- a/Libraries/Utility/src/utility/system/path.py
+++ b/Libraries/Utility/src/utility/system/path.py
@@ -495,7 +495,7 @@ def safe_isdir(self) -> bool | None:
try:
check = self.is_dir()
except (OSError, ValueError):
- RobustRootLogger().debug("This exception has been suppressed and is only relevant for debug purposes.", exc_info=True)
+ #RobustRootLogger().debug("This exception has been suppressed and is only relevant for debug purposes.", exc_info=True)
return None
else:
return check
@@ -506,7 +506,7 @@ def safe_isfile(self) -> bool | None:
try:
check = self.is_file()
except (OSError, ValueError):
- RobustRootLogger().debug("This exception has been suppressed and is only relevant for debug purposes.", exc_info=True)
+ #RobustRootLogger().debug("This exception has been suppressed and is only relevant for debug purposes.", exc_info=True)
return None
else:
return check
@@ -517,7 +517,7 @@ def safe_exists(self) -> bool | None:
try:
check = self.exists()
except (OSError, ValueError):
- RobustRootLogger().debug("This exception has been suppressed and is only relevant for debug purposes.", exc_info=True)
+ #RobustRootLogger().debug("This exception has been suppressed and is only relevant for debug purposes.", exc_info=True)
return None
else:
return check
diff --git a/Tools/HolocronToolset/src/toolset/config.py b/Tools/HolocronToolset/src/toolset/config.py
index 182438ce1..1e6607795 100644
--- a/Tools/HolocronToolset/src/toolset/config.py
+++ b/Tools/HolocronToolset/src/toolset/config.py
@@ -51,7 +51,7 @@
}
},
"toolsetLatestNotes": "Fixed major bug that was causing most editors to load data incorrectly.",
- "toolsetLatestBetaNotes": "A hotfix has been released:
- Fix GITEditor/Module Designer not finding locations defined in .git.
- Handle all logging asynchronously, so it will not lag the main thread.
- Fix case-insensitive pathing on linux
- Fix right click context menus on Linux/Mac",
+ "toolsetLatestBetaNotes": "A hotfix has been released:
- Fix watchdog file reloader
- Fix GITEditor/Module Designer not finding locations defined in .git.
- Handle all logging asynchronously, so it will not lag the main thread.
- Fix case-insensitive pathing on linux
- Fix right click context menus on Linux/Mac",
"kits": {
"Black Vulkar Base": {"version": 1, "id": "blackvulkar"},
"Endar Spire": {"version": 1, "id": "endarspire"},
diff --git a/Tools/HolocronToolset/src/toolset/gui/dialogs/inventory.py b/Tools/HolocronToolset/src/toolset/gui/dialogs/inventory.py
index 6041f64df..446855644 100644
--- a/Tools/HolocronToolset/src/toolset/gui/dialogs/inventory.py
+++ b/Tools/HolocronToolset/src/toolset/gui/dialogs/inventory.py
@@ -34,6 +34,8 @@
if TYPE_CHECKING:
import os
+ from typing import Sequence
+
from qtpy.QtCore import QModelIndex, QPoint
from qtpy.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent
from qtpy.QtWidgets import QLabel, QWidget
@@ -59,7 +61,7 @@ def __init__(
self,
parent: QWidget,
installation: HTInstallation,
- capsules: list[Capsule],
+ capsules: Sequence[Capsule],
folders: list[str],
inventory: list[InventoryItem],
equipment: dict[EquipmentSlot, InventoryItem],
diff --git a/Tools/HolocronToolset/src/toolset/gui/editors/utc.py b/Tools/HolocronToolset/src/toolset/gui/editors/utc.py
index e6e7eff7f..5cb69f82a 100644
--- a/Tools/HolocronToolset/src/toolset/gui/editors/utc.py
+++ b/Tools/HolocronToolset/src/toolset/gui/editors/utc.py
@@ -623,7 +623,7 @@ def openInventory(self):
inventoryEditor = InventoryEditor(
self,
self._installation,
- Module.get_capsules(self._installation, Module.find_root(self._filepath.name)),
+ Module.find_capsules(self._installation, self._filepath.name),
[],
self._utc.inventory,
self._utc.equipment,
diff --git a/Tools/HolocronToolset/src/toolset/gui/windows/module_designer.py b/Tools/HolocronToolset/src/toolset/gui/windows/module_designer.py
index b1b95aa13..997a04cab 100644
--- a/Tools/HolocronToolset/src/toolset/gui/windows/module_designer.py
+++ b/Tools/HolocronToolset/src/toolset/gui/windows/module_designer.py
@@ -1238,7 +1238,7 @@ def on2dKeyboardReleased(self, buttons: set[int], keys: set[int]):
self._controls2d.onKeyboardReleased(buttons, keys)
def on2dMouseScrolled(self, delta: Vector2, buttons: set[int], keys: set[int]):
- self.log.debug("on2dMouseScrolled, delta: %s, buttons: %s, keys: %s", delta, buttons, keys)
+ #self.log.debug("on2dMouseScrolled, delta: %s, buttons: %s, keys: %s", delta, buttons, keys)
self._controls2d.onMouseScrolled(delta, buttons, keys)
def on2dMousePressed(self, screen: Vector2, buttons: set[int], keys: set[int]):
@@ -2044,6 +2044,12 @@ def onMouseMoved(self, screen: Vector2, screenDelta: Vector2, world: Vector2, wo
self.editor.moveSelected(worldDelta.x, worldDelta.y, noUndoStack=True, noZCoord=True)
if self.rotateSelected.satisfied(buttons, keys) and isinstance(self._mode, _InstanceMode):
+ for instance in self.editor.selectedInstances:
+ if not isinstance(instance, (GITCamera, GITCreature, GITDoor, GITPlaceable, GITStore, GITWaypoint)):
+ continue # doesn't support rotations.
+ self.editor.initialRotations[instance] = instance.orientation if isinstance(instance, GITCamera) else instance.bearing
+ self.editor.log.debug("ModuleDesignerControls2d rotate set isDragRotating")
+ self.isDragRotating = True
self._mode.rotateSelectedToPoint(world.x, world.y)
if not self.editor.isDragRotating:
print("2d rotate set isDragRotating")