Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MO 2.5.2 Support #3

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Typecheck

on: [push, workflow_dispatch]

jobs:
build:
name: MO ${{ matrix.config.mo }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
config:
- { py: '3.8.7', qt: '5', qtm: '15.2', mo: '2.4.0', md: '' }
- { py: '3.11.5', qt: '6', qtm: '5.3', mo: '2.5.0', md: '.dev16' }
- { py: '3.12.3', qt: '6', qtm: '7.1', mo: '2.5.2', md: '' }
steps:
- uses: actions/checkout@v4
- name: Install Qt
uses: jurplel/[email protected]
with:
version: ${{ matrix.config.qt }}.${{ matrix.config.qtm }}
- name: Set up Python ${{ matrix.config.py }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.config.py }}
- name: Install dependencies
run: |
python -m pip install --upgrade --force-reinstall pip
pip install mypy mobase-stubs==${{ matrix.config.mo }}${{ matrix.config.md }} PyQt${{ matrix.config.qt }}==${{ matrix.config.qt }}.${{ matrix.config.qtm }}
- name: Typecheck
run: |
mypy $(git ls-files '*.py')
2 changes: 2 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[MESSAGES CONTROL]
disable=C0103,C0114,C0115,C0116,C0415,W1203,R0903
158 changes: 106 additions & 52 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import mobase
import logging
import re
from typing import List
import sys
from typing import Callable, Any
import mobase

if sys.version_info >= (3, 9):
from builtins import list as List
from builtins import tuple as Tuple
from builtins import dict as Dict
else:
from typing import List, Tuple, Dict

def has(obj: Any, attr: str) -> Any:
if not obj:
return None
return getattr(obj, attr, lambda *args, **kwargs: None)

class Plugin:
def __init__(self, priority, name):
def __init__(self, priority, name) -> None:
self.priority = priority
self.name = name

# add exceptions here:
self.dict = {
# "mod1 regex": ["1st plugin substr", "substr in 2nd", "etc."] ,
# "mod2 regex": ["substr in 1st", "substr in 2nd", "etc."]
self.dict: Dict[str, List[str]] = {
# 'mod1 regex': ['1st plugin substr', 'substr in 2nd', 'etc.'] ,
# 'mod2 regex': ['substr in 1st', 'substr in 2nd', 'etc.']
}

def __lt__(self, other) -> bool:
Expand All @@ -31,15 +45,18 @@ def __lt__(self, other) -> bool:
# should come last. if not enough use self.dict for the exceptions

patts = \
["(:?hot|bug)[ ._-]?fix",
r"\bfix\b",
"patch",
"add[ ._-]?on",
"expansion",
"expanded",
"extension",
"ext",
"remastered"]
['(:?hot|bug)[ ._-]?fix',
r'\bfix\b',
'patch',
'add[ ._-]?on',
'expansion',
'expanded',
'extension',
'ext',
'ng',
'conversion'
'fix'
'remastered']
for pattern in patts:
if re.search(pattern, lc_a) != re.search(pattern, lc_b):
return re.search(pattern, lc_a) is None
Expand All @@ -53,100 +70,137 @@ class PluginSync(mobase.IPluginTool):
_modList: mobase.IModList
_pluginList: mobase.IPluginList

_isMo2Updated: bool
_version: mobase.VersionInfo

def __init__(self):
def __init__(self) -> None:
self._log = logging.getLogger(__name__)
super().__init__()

def init(self, organizer: mobase.IOrganizer):
def init(self, organizer: mobase.IOrganizer) -> bool:
self._organizer = organizer
self._modList = organizer.modList()
self._pluginList = organizer.pluginList()

version = self._organizer.appVersion().canonicalString()
versionx = re.sub("[^0-9.]", "", version)
self._version = float(".".join(versionx.split(".", 2)[:-1]))
self._isMo2Updated = self._version >= 2.5

return True
self._version = self._organizer.appVersion()
isSupported = self._version >= mobase.VersionInfo(2, 4, 0)
if not isSupported:
self._log.error('PluginSync does not support MO2 versions older than 2.4.0')
return isSupported

# Basic info
def name(self) -> str:
return "Sync Plugins"
return 'Sync Plugins'

def author(self) -> str:
return "coldrifting"
return 'coldrifting'

def description(self) -> str:
return "Syncs plugin load order with mod order"
return 'Syncs plugin load order with mod order'

def version(self) -> mobase.VersionInfo:
return mobase.VersionInfo(2, 2, 0, mobase.ReleaseType.FINAL)

# Settings
def isActive(self) -> str:
return (self._organizer.managedGame().feature(mobase.GamePlugins))
return mobase.VersionInfo(2, 2, 1)

def settings(self) -> List[mobase.PluginSetting]:
return [
mobase.PluginSetting("enabled", "enable this plugin", True)
mobase.PluginSetting('enabled', 'enable this plugin', True),
mobase.PluginSetting('enablePlugins', 'Enable plugins if mod is enabled', True),
mobase.PluginSetting('checkMasters', 'Check missing masters', True)
]

def isActive(self) -> bool:
return bool(self._organizer.pluginSetting(self.name(), 'enabled'))

# Display
def displayName(self) -> str:
return "Sync Plugins"
return 'Sync Plugins'

def tooltip(self) -> str:
return "Enables all Mods one at a time to match load order"
return 'Enables & sorts plugins to match mod load order'

def icon(self):
if (self._isMo2Updated):
from PyQt6.QtGui import QIcon
def icon(self) -> Any:
if self._version >= mobase.VersionInfo(2, 5, 0):
from PyQt6.QtGui import QIcon # type: ignore
else:
from PyQt5.QtGui import QIcon

from PyQt5.QtGui import QIcon # type: ignore
return QIcon()

def selectimpl(self, impls: List[Tuple[mobase.VersionInfo, Any]]) -> Any:
for version, impl in impls:
if self._version >= version:
return impl
return None

# Plugin Logic
def display(self) -> bool:
def display(self) -> None:
isMaster = self.selectimpl([(mobase.VersionInfo(2, 5, 0),
has(self._pluginList, 'isMasterFlagged')),
(mobase.VersionInfo(2, 4, 0),
has(self._pluginList, 'isMaster'))])
feature = self.selectimpl([(mobase.VersionInfo(2, 5, 2),
has(has(self._organizer, 'gameFeatures')(), 'gameFeature')),
(mobase.VersionInfo(2, 4, 0),
has(has(self._organizer, 'managedGame')(), 'feature'))])
ACTIVE = self.selectimpl([(mobase.VersionInfo(2, 5, 0), mobase.PluginState.ACTIVE),
(mobase.VersionInfo(2, 4, 0), 2)])
INACTIVE = self.selectimpl([(mobase.VersionInfo(2, 5, 0), mobase.PluginState.INACTIVE),
(mobase.VersionInfo(2, 4, 0), 1)])


checkMasters = bool(self._organizer.pluginSetting(self.name(), 'checkMasters'))
self._log.info('Sync started...')
# Get all plugins as a list
allPlugins = self._pluginList.pluginNames()

# Sort the list by plugin origin
allPlugins = sorted(
allPlugins,
key=lambda
x: Plugin(self._modList.priority(self._pluginList.origin(x)), x)
key=lambda x: Plugin(self._modList.priority(self._pluginList.origin(x)), x)
)

# Split into two lists, master files and regular plugins
plugins = []
masters = []
for plugin in allPlugins:
if (self._isMo2Updated):
isMasterPlugin = self._pluginList.isMasterFlagged(plugin)
else:
isMasterPlugin = self._pluginList.isMaster(plugin)

if (isMasterPlugin):
for plugin in allPlugins:
if isMaster(plugin):
masters.append(plugin)
else:
plugins.append(plugin)

# Merge masters into the plugin list at the begining
allPlugins = masters + plugins
if checkMasters:
allLowered = [x.lower() for x in allPlugins]

# Set load order
self._pluginList.setLoadOrder(allPlugins)

if bool(self._organizer.pluginSetting(self.name(), 'enablePlugins')):
# Scan through all plugins
for plugin in allPlugins:
# Get the masters of the current plugin
pmasters = self._pluginList.masters(plugin)
canEnable = True
# Check if all masters are present
if checkMasters:
for pmaster in pmasters:
if pmaster.lower() not in allLowered:
self._log.warning(f'{pmaster} not present, disabling {plugin}')
canEnable = False
break
# Set the plugin state accordingly
if canEnable:
self._pluginList.setState(plugin, ACTIVE)
else:
self._pluginList.setState(plugin, INACTIVE)

# Update the plugin list to use the new load order
self._organizer.managedGame().feature(
mobase.GamePlugins).writePluginLists(self._pluginList)
feature(mobase.GamePlugins).writePluginLists(self._pluginList)

# Refresh the UI
self._organizer.refresh()

return True
self._log.info('Sync complete')


# Tell Mod Organizer to initialize the plugin
Expand Down