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")