Skip to content

Commit

Permalink
Merge pull request #52 from Wolfmyths/future-update
Browse files Browse the repository at this point in the history
1.5.0
  • Loading branch information
Wolfmyths authored Apr 14, 2024
2 parents 527742c + 0ca357b commit ea8409c
Show file tree
Hide file tree
Showing 46 changed files with 1,237 additions and 558 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ MANIFEST
*.manifest
*.exe

# Save files
# Save files `mods.ini` is an older version of how mod data was stored
config.ini
mods.ini
mods.json
profiles.json
externalshortcuts.json

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ A simple mod manager for PAYDAY 2 to make managing all of those files a little b

* Create profiles to organize your mods.

* Assign tags to mods

* Delete mods from your computer.

* Automatic installation by dragging and dropping files into the program.
Expand Down Expand Up @@ -92,7 +94,7 @@ You may download any version of Myth Mod Manager and view changelogs at the [rel
+ Rewrite Settings UI
+ New Icon/Logo
+ Auto mod type detection
+ Muli-Language Support [See issue #44](https://github.com/Wolfmyths/Myth-Mod-Manager/issues/44)
+ Multi-Language Support [See issue #44](https://github.com/Wolfmyths/Myth-Mod-Manager/issues/44)
+ Some kind of [modworkshop.net](https://modworkshop.net/g/payday-2) integration, see [issue #14](https://github.com/Wolfmyths/Myth-Mod-Manager/issues/14)

*Suggestions are appreciated!*
28 changes: 28 additions & 0 deletions src/JSONParser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import json
import logging
import os

class JSONParser():
file: dict[str:list[str]] = None
def __init__(self, path: str, default: dict = {}) -> None:
self.path = path
self.default = default
try:
self.loadJSON()
except (json.decoder.JSONDecodeError, FileNotFoundError):
with open(self.path, 'w') as f:
f.write(json.dumps(default))

self.loadJSON()

def loadJSON(self):
with open(self.path, 'r') as f:
self.file = json.loads(f.read())

def saveJSON(self):
with open(self.path, 'w') as f:
f.seek(0)
f.write(json.dumps(self.file, indent=2))
f.truncate()

logging.info('%s has been saved.', os.path.basename(self.path))
5 changes: 2 additions & 3 deletions src/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from src.main_window import MainWindow
from src.save import Save, OptionsManager
from src.widgets.QDialog.gamepathQDialog import GamePathNotFound
from src.constant_vars import VERSION, PROGRAM_NAME, LOG, IS_SCRIPT, OLD_EXE, ROOT_PATH, ICON
from src.constant_vars import VERSION, PROGRAM_NAME, LOG, IS_SCRIPT, OLD_EXE, ROOT_PATH, OptionKeys
import src.errorChecking as errorChecking
from src.style import StyleManager

Expand Down Expand Up @@ -38,8 +38,7 @@
app.setStyleSheet(StyleManager().getStyleSheet(optionsManager.getTheme()))

# Checking game path
if not errorChecking.validGamePath():

if not optionsManager.hasOption(OptionKeys.game_path):
warning = GamePathNotFound(app)
warning.exec()

Expand Down
19 changes: 10 additions & 9 deletions src/constant_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ def all_types() -> list[str]:

class ModKeys(StrEnum):
'''Keys that a mod has in `MOD_CONFIG`'''

enabled = auto()
type = auto()
modworkshopid = auto()
ignored = auto()
tags = auto()

class OptionKeys(StrEnum):
'''Option's keys in `OPTIONS_CONFIG`'''
Expand All @@ -37,12 +37,19 @@ class OptionKeys(StrEnum):
windowsize_h = auto()
mmm_update_alert = auto()

def all_keys() -> list[str]:
# Splice removes section key
return [enum.value for enum in OptionKeys][1:]

class ProfileRole():

parent = 33 # Role ID for an item's parent
type = 32 # Role ID for an item's type
installed = 34 # Role ID if a mod is installed or not

class ModRole():
tags = 33 # Role ID for a mod's tags

# Detection if the program is being run through an exe or the script
IS_SCRIPT = not getattr(sys, 'frozen', False)

Expand All @@ -53,7 +60,7 @@ class ProfileRole():
ICON = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'icon.ico')

# File names
MOD_CONFIG = 'mods.ini'
MOD_CONFIG = 'mods.json'
OPTIONS_CONFIG = 'config.ini'
PROFILES_JSON = 'profiles.json'
TOOLS_JSON = 'externalshortcuts.json'
Expand All @@ -70,9 +77,6 @@ class ProfileRole():
GITHUB_LOGO_B = 'github-mark.svg'
KOFI_LOGO_B = 'kofi_s_logo_nolabel.webp'

# Mod Table Object Name
MOD_TABLE_OBJECT = 'mod_table'

# Files in PAYDAY2/Mods/ to ignore
MODSIGNORE = ('base', 'logs', 'saves', 'downloads')

Expand All @@ -83,9 +87,6 @@ class ProfileRole():
# Default Disabled Folder
MODS_DISABLED_PATH_DEFAULT = os.path.join(os.path.abspath(ROOT_PATH), DISABLED_MODS)

# START_PAYDAY Path
START_PAYDAY_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), START_PAYDAY)

# Graphics folder path
UI_GRAPHICS_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'graphics')

Expand All @@ -96,4 +97,4 @@ class ProfileRole():
# Program Info
PROGRAM_NAME = 'Myth Mod Manager'

VERSION = semantic_version.Version(major=1, minor=4, patch=1)
VERSION = semantic_version.Version(major=1, minor=5, patch=0)
27 changes: 9 additions & 18 deletions src/errorChecking.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,6 @@ def openWebPage(link: str) -> bool:

return outcome

def validGamePath(optionsPath: str = OPTIONS_CONFIG) -> bool:
'''Gets the gamepath from OPTIONS_CONFIG and checks if the paths contains the PAYDAY 2 exe'''

gamePath = OptionsManager(optionsPath).getGamepath()

gameEx = 'payday2_win32_release.exe' if sys.platform.startswith('win') else 'payday2_release'

if os.path.isdir(gamePath) and gameEx in os.listdir(gamePath):

logging.info('Gamepath: %s', gamePath)
return True

logging.warning('There is no gamepath')
return False

def createModDirs(optionsPath: str = OPTIONS_CONFIG) -> None:
path = Pathing(optionsPath)
disPath = OptionsManager(optionsPath).getDispath()
Expand All @@ -52,7 +37,6 @@ def createModDirs(optionsPath: str = OPTIONS_CONFIG) -> None:
if not os.path.isdir(modDir):
os.mkdir(modDir)


def isInstalled(mod: str, optionsPath: str = OPTIONS_CONFIG) -> bool:
'''Checks if the mod is installed on the system'''

Expand Down Expand Up @@ -120,7 +104,7 @@ def permissionCheck(src: str) -> int:
permission = str(oct(os.stat(src).st_mode))[-3:]

if int(permission) != 777:
logging.info('Permission error found, fixing...')
logging.warning('Permission error found, fixing...')
os.chmod(src, stat.S_IRWXU)

result = 0
Expand All @@ -134,13 +118,20 @@ def permissionCheck(src: str) -> int:
def startFile(path: str) -> None:
'''A cross-platform version of `os.startfile()`'''

logging.info('Starting program "%s"', path)

try:
if not os.path.isabs(path):
raise Exception('Please use a full path to the program you are starting.')

if sys.platform.startswith('win'):
os.startfile(path)
else:
cmd = 'open' if sys.platform == 'darwin' else 'xdg-open'
subprocess.call([cmd, path])
returnCode = subprocess.run([cmd, path], shell=True)
returnCode.check_returncode()

except Exception as e:
logging.error('Error in errorChecking.startFile(%s): %s', path, str(e))
notice = Notice(f'Error in errorChecking.startFile({path}): {e}', 'Could not start program')
notice.exec()
16 changes: 8 additions & 8 deletions src/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,9 @@ def __init__(self, app: qtw.QApplication | None = None, savePath = MOD_CONFIG, o
self.options = Options()
self.about = About()

self.options.ignoredModsListWidget.itemsRemoved.connect(self.manager.modsTable.refreshMods)
self.options.colorThemeDark.clicked.connect(self.manager.modsTable.swapIcons)
self.options.colorThemeLight.clicked.connect(self.manager.modsTable.swapIcons)

self.options.colorThemeDark.clicked.connect(self.about.updateIcons)
self.options.colorThemeLight.clicked.connect(self.about.updateIcons)
self.options.ignoredMods.ignoredModsListWidget.itemsRemoved.connect(self.manager.modsTable.refreshMods)
self.options.themeSwitched.connect(lambda x: self.manager.modsTable.swapIcons(x))
self.options.themeSwitched.connect(lambda x: self.about.updateIcons(x))

for page in (
(self.manager, 'Manager'),
Expand All @@ -69,7 +66,10 @@ def updateDetected(self, latestVersion: str, changelog: str) -> None:
errorChecking.startFile(os.path.join(ROOT_PATH, 'Myth Mod Manager.exe'))
QCoreApplication.quit()

def close(self) -> bool:
def closeEvent(self, event: qtg.QCloseEvent) -> None:
self.optionsManager.setWindowSize(self.size())
self.optionsManager.writeData()
return super().close()

if isinstance(self.app, qtw.QApplication):
self.app.closeAllWindows()
return super().closeEvent(event)
38 changes: 17 additions & 21 deletions src/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from src.widgets.QDialog.announcementQDialog import Notice
from src.save import Save, OptionsManager
import src.errorChecking as errorChecking
from src.constant_vars import START_PAYDAY_PATH, MOD_TABLE_OBJECT, ModType, MOD_CONFIG, OPTIONS_CONFIG
from src.constant_vars import ModType, MOD_CONFIG, OPTIONS_CONFIG

class ModManager(qtw.QWidget):

Expand Down Expand Up @@ -61,13 +61,11 @@ def __init__(self, saveManagerPath = MOD_CONFIG, optionsManagerPath = OPTIONS_CO
self.labelFrame.setLayout(modLabelLayout)

self.search = qtw.QLineEdit()
self.search.setPlaceholderText('Search...')
self.search.setPlaceholderText('Search... use "tag:" with no spaces to search for tags, use a comma "," to seperate tags')
self.search.textChanged.connect(lambda x: self.modsTable.search(x))

self.modsTable = ModListWidget(saveManagerPath, optionsManagerPath)
self.modsTable.itemChanged.connect(self.updateModCount)

self.modsTable.setObjectName(MOD_TABLE_OBJECT)

self.modsTable.refreshMods()

Expand All @@ -88,36 +86,34 @@ def updateModCount(self):

def startPayday(self):

if errorChecking.validGamePath():
gamePath: str = self.optionsManager.getGamepath()

gamePath: str = self.optionsManager.getGamepath()

drive: str = gamePath[0].lower()
try:
if not os.path.isabs(os.path.join(gamePath, gameExe)):
raise Exception('Path is not absolute')

if sys.platform.startswith('win'):

# Starts START_PAYDAY.bat
# First argument is to change the directory to the game's directory
# Second argument the drive for the cd command
# Third argument is the exe name
# Fourth argument is extra arguments for payday 2
subprocess.call([START_PAYDAY_PATH, gamePath, drive, 'payday2_win32_release.exe'])

gameExe = 'payday2_win32_release.exe'

# TODO: Permission error is raised without shell=True, can this be avoided?
cmd = subprocess.run([gamePath[0:2].upper(), '&&', 'cd', gamePath, '&&', gameExe], shell=True)
cmd.check_returncode()
else:
errorChecking.startFile(os.path.join(gamePath, 'payday2_release'))
else:

logging.error('Could not start PAYDAY 2, could not find payday 2 executable in:\n%s', gamePath)
gameExe = 'payday2_release'
errorChecking.startFile(os.path.join(gamePath, gameExe))

notice = Notice(f'Could not find payday 2 executable in:\n{gamePath}', 'Error: Invalid Gamepath')
except Exception as e:
logging.error('An error occured trying to start PAYDAY 2:\n%s', str(e))
notice = Notice(f'An error occured trying to start PAYDAY 2:\n{e}', 'Could not start PAYDAY 2 from MMM')
notice.exec()

def deselectAllShortcut(self):
selectedItems = self.modsTable.selectedItems()
if selectedItems:
for item in selectedItems:
item.setSelected(False)

def keyPressEvent(self, event: qtg.QKeyEvent) -> None:
if event.key() == qt.Key.Key_Delete and self.modsTable.selectedItems():
self.modsTable.deleteItem()
Expand Down
Loading

0 comments on commit ea8409c

Please sign in to comment.