mirror of
https://github.com/ModOrganizer2/modorganizer-basic_games.git
synced 2026-07-27 14:07:29 -07:00
Initial commit.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
.mypy_cache
|
||||
__pycache__
|
||||
@@ -0,0 +1,7 @@
|
||||
Copyright 2020 Holt59
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,108 @@
|
||||
# Mod Organizer 2 - Simple Games Plugin
|
||||
|
||||
Mod Organizer 2 meta-plugin to make creating game plugins easier and faster.
|
||||
|
||||
## Why?
|
||||
|
||||
In order to create a MO2 game plugin, one must implements the `IPluginGame` interface,
|
||||
but the interface contains a lot of things that most games do not require.
|
||||
|
||||
The goal of this meta-plugin is to load create plugins for "simple" games by simply
|
||||
providing a `.ini` file or a very simply python class.
|
||||
|
||||
## How to?
|
||||
|
||||
You can either provide a python class or a `.ini` file.
|
||||
|
||||
### Using a `.ini` file
|
||||
|
||||
You simply need to put your `.ini` file in games with the following content:
|
||||
|
||||
```ini
|
||||
[DEFAULT]
|
||||
# Name of the plugin (avoid space, whatever, ...):
|
||||
Name=Witcher 3 Support Plugin
|
||||
|
||||
# Your name or username:
|
||||
Author=Holt59
|
||||
|
||||
# Version of the plugin - Does not really make sense for .ini:
|
||||
Version=1.0.0
|
||||
|
||||
# Name of the game, as you want it displayed by MO2:
|
||||
GameName=The Witcher 3
|
||||
|
||||
# Short name of the game, used, e.g., for nexus:
|
||||
GameShortName=witcher3
|
||||
|
||||
# Path to the executable, relative to the game folder:
|
||||
GameBinary=bin/x64/witcher3.exe
|
||||
|
||||
# Name of the folder containing the data, relative to the game folder:
|
||||
GameDataPath=mods
|
||||
|
||||
# Savegame extensions for the game:
|
||||
GameSaveExtension=sav
|
||||
```
|
||||
|
||||
|
||||
### Using a Python file
|
||||
|
||||
You need to create a class that inherits `BasicGame` and put it in a `.py` file in `games`. Below is
|
||||
an example for The Witcher 3:
|
||||
|
||||
```python
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from PyQt5.QtCore import QDir
|
||||
|
||||
|
||||
from ..basic_game import BasicGame
|
||||
|
||||
|
||||
class Witcher3Game(BasicGame):
|
||||
|
||||
Name: str = "Witcher 3 Support Plugin"
|
||||
Author: str = "Holt59"
|
||||
Version: str = "1.0.0a"
|
||||
|
||||
GameName: str = "The Witcher 3"
|
||||
GameShortName: str = "witcher3"
|
||||
GameBinary: str = "bin/x64/witcher3.exe"
|
||||
GameDataPath: str = "Mods"
|
||||
GameSaveExtension: str = "sav"
|
||||
|
||||
def steamAPPId(self):
|
||||
return "292030"
|
||||
|
||||
def savesDirectory(self):
|
||||
return QDir(self.documentsDirectory().absoluteFilePath("gamesaves"))
|
||||
```
|
||||
|
||||
`BasicGame` inherits `IPluginGame` so you can override methods if you need to.
|
||||
Each attribute you provide corresponds to a method (e.g., `Version` corresponds
|
||||
to the `version` method). If you override the method, you do not have to provide
|
||||
the attribute:
|
||||
|
||||
```python
|
||||
class Witcher3Game(BasicGame):
|
||||
|
||||
Name: str = "Witcher 3 Support Plugin"
|
||||
Author: str = "Holt59"
|
||||
|
||||
GameName: str = "The Witcher 3"
|
||||
GameShortName: str = "witcher3"
|
||||
GameBinary: str = "bin/x64/witcher3.exe"
|
||||
GameDataPath: str = "Mods"
|
||||
GameSaveExtension: str = "sav"
|
||||
|
||||
def version(self):
|
||||
# Don't forget to import mobase!
|
||||
return mobase.VersionInfo(1, 0, 0, mobase.ReleaseType.final)
|
||||
|
||||
def steamAPPId(self):
|
||||
return "292030"
|
||||
|
||||
def savesDirectory(self):
|
||||
return QDir(self.documentsDirectory().absoluteFilePath("gamesaves"))
|
||||
```
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import importlib
|
||||
import glob
|
||||
import os
|
||||
import typing
|
||||
|
||||
from .basic_game import BasicGame
|
||||
from .basic_game_ini import BasicIniGame
|
||||
|
||||
# List of game class from python:
|
||||
game_plugins: typing.List[BasicGame] = []
|
||||
|
||||
# We are going to list all game plugins:
|
||||
curpath = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# List all the .ini files:
|
||||
for file in glob.glob(os.path.join(curpath, "games", "*.ini")):
|
||||
game_plugins.append(BasicIniGame(file))
|
||||
|
||||
# List all the python plugins:
|
||||
for file in glob.glob(os.path.join(curpath, "games", "*.py")):
|
||||
module_p = os.path.relpath(file, os.path.join(curpath, "games"))
|
||||
if module_p == "__init__.py":
|
||||
continue
|
||||
|
||||
# Import the module:
|
||||
module = importlib.import_module(".games." + module_p[:-3], __package__)
|
||||
|
||||
# Lookup game plugins:
|
||||
for name in dir(module):
|
||||
if hasattr(module, name):
|
||||
obj = getattr(module, name)
|
||||
if (
|
||||
isinstance(obj, type)
|
||||
and issubclass(obj, BasicGame)
|
||||
and obj is not BasicGame
|
||||
):
|
||||
game_plugins.append(obj())
|
||||
|
||||
|
||||
def createPlugins():
|
||||
return game_plugins
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import typing
|
||||
|
||||
from PyQt5.QtCore import QDir, QFileInfo, QStandardPaths
|
||||
|
||||
import mobase
|
||||
|
||||
|
||||
class BasicGame(mobase.IPluginGame):
|
||||
|
||||
""" This class implements some methods from mobase.IPluginGame
|
||||
to make it easier to create game plugins without having to implement
|
||||
all the methods of mobase.IPluginGame. """
|
||||
|
||||
# List of fields that can be provided by child class:
|
||||
Name: str
|
||||
Author: str
|
||||
Version: str
|
||||
|
||||
GameName: str
|
||||
GameShortName: str
|
||||
GameBinary: str
|
||||
GameDataPath: str
|
||||
GameSaveExtension: str
|
||||
|
||||
# File containing the plugin:
|
||||
_fromName: str
|
||||
|
||||
# Organizer obtained in init:
|
||||
_organizer: mobase.IOrganizer
|
||||
|
||||
# Path to the game, as set by MO2:
|
||||
_gamePath: str
|
||||
|
||||
# The feature map:
|
||||
_featureMap: typing.Dict = {}
|
||||
|
||||
# List of attributes - These should be the same as function with a
|
||||
# _ prefix:
|
||||
_name: str
|
||||
_author: str
|
||||
_version: mobase.VersionInfo
|
||||
_gameName: str
|
||||
_gameShortName: str
|
||||
_binaryName: str
|
||||
_dataDirectory: str
|
||||
_savegameExtension: str
|
||||
|
||||
# Match the name of the public attribute (from child class), to the
|
||||
# name of the protected attribute and the corresponding function:
|
||||
NAME_MAPPING = [
|
||||
["Name", "name"],
|
||||
["Author", "author"],
|
||||
["Version", "version"],
|
||||
["GameName", "gameName"],
|
||||
["GameShortName", "gameShortName"],
|
||||
["GameBinary", "binaryName"],
|
||||
["GameDataPath", "dataDirectory"],
|
||||
["GameSaveExtension", "savegameExtension"],
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super(BasicGame, self).__init__()
|
||||
|
||||
# if not hasattr(self, "_fromName"):
|
||||
self._fromName = self.__class__.__name__
|
||||
|
||||
# We init the member and check that everything is provided:
|
||||
for pub, prt in self.NAME_MAPPING:
|
||||
if hasattr(self, pub):
|
||||
value = getattr(self, pub)
|
||||
if pub == "Version":
|
||||
value = mobase.VersionInfo(value)
|
||||
setattr(self, "_" + prt, value)
|
||||
elif getattr(self, prt) is getattr(BasicGame, prt):
|
||||
print(
|
||||
"Basic game plugin from {} is missing {} property.".format(
|
||||
self._fromName, pub
|
||||
),
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
"""
|
||||
Here IPlugin interface stuff.
|
||||
"""
|
||||
|
||||
def init(self, organizer):
|
||||
self._organizer = organizer
|
||||
return True
|
||||
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
def author(self):
|
||||
return self._author
|
||||
|
||||
def description(self):
|
||||
return "Adds basic support for game {}.".format(self.gameName())
|
||||
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
def isActive(self):
|
||||
return True
|
||||
|
||||
def settings(self):
|
||||
return []
|
||||
|
||||
def gameName(self):
|
||||
return self._gameName
|
||||
|
||||
def gameShortName(self):
|
||||
return self._gameShortName
|
||||
|
||||
def gameIcon(self):
|
||||
return mobase.getIconForExecutable(
|
||||
self.gameDirectory().absoluteFilePath(self.binaryName())
|
||||
)
|
||||
|
||||
def validShortNames(self):
|
||||
return []
|
||||
|
||||
def gameNexusName(self):
|
||||
return ""
|
||||
|
||||
def nexusModOrganizerID(self):
|
||||
return 0
|
||||
|
||||
def nexusGameID(self):
|
||||
return 0
|
||||
|
||||
def steamAPPId(self):
|
||||
return ""
|
||||
|
||||
def binaryName(self):
|
||||
return self._binaryName
|
||||
|
||||
def getLauncherName(self):
|
||||
return ""
|
||||
|
||||
def executables(self):
|
||||
return [
|
||||
mobase.ExecutableInfo(
|
||||
self.gameName(),
|
||||
QFileInfo(self.gameDirectory().absoluteFilePath(self.binaryName())),
|
||||
)
|
||||
]
|
||||
|
||||
def savegameExtension(self):
|
||||
return self._savegameExtension
|
||||
|
||||
def savegameSEExtension(self):
|
||||
return ""
|
||||
|
||||
def initializeProfile(self, path, settings):
|
||||
pass
|
||||
|
||||
def primarySources(self):
|
||||
return []
|
||||
|
||||
def primaryPlugins(self):
|
||||
return []
|
||||
|
||||
def gameVariants(self):
|
||||
return []
|
||||
|
||||
def setGameVariant(self, variantStr):
|
||||
pass
|
||||
|
||||
def gameVersion(self):
|
||||
pversion = mobase.getProductVersion(
|
||||
self.gameDirectory().absoluteFilePath(self.binaryName())
|
||||
)
|
||||
if not pversion:
|
||||
pversion = mobase.getFileVersion(
|
||||
self.gameDirectory().absoluteFilePath(self.binaryName())
|
||||
)
|
||||
return pversion
|
||||
|
||||
def iniFiles(self):
|
||||
return []
|
||||
|
||||
def DLCPlugins(self):
|
||||
return []
|
||||
|
||||
def CCPlugins(self):
|
||||
return []
|
||||
|
||||
def loadOrderMechanism(self):
|
||||
return mobase.LoadOrderMechanism.PluginsTxt
|
||||
|
||||
def sortMechanism(self):
|
||||
return mobase.SortMechanism.NONE
|
||||
|
||||
def looksValid(self, aQDir: QDir):
|
||||
return aQDir.exists(self.binaryName())
|
||||
|
||||
def isInstalled(self):
|
||||
return False
|
||||
|
||||
def gameDirectory(self):
|
||||
"""
|
||||
@return directory (QDir) to the game installation.
|
||||
"""
|
||||
return QDir(self._gamePath)
|
||||
|
||||
def dataDirectory(self):
|
||||
return QDir(self.gameDirectory().absoluteFilePath(self._dataDirectory))
|
||||
|
||||
def setGamePath(self, pathStr):
|
||||
self._gamePath = pathStr
|
||||
|
||||
def documentsDirectory(self):
|
||||
folders = [
|
||||
"{}/My Games/{}".format(
|
||||
QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation),
|
||||
self.gameName(),
|
||||
),
|
||||
"{}/{}".format(
|
||||
QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation),
|
||||
self.gameName(),
|
||||
),
|
||||
]
|
||||
for folder in folders:
|
||||
qdir = QDir(folder)
|
||||
if qdir.exists():
|
||||
return qdir
|
||||
|
||||
return None
|
||||
|
||||
def savesDirectory(self):
|
||||
return self.documentsDirectory()
|
||||
|
||||
def _featureList(self):
|
||||
return self._featureMap
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import configparser
|
||||
import os
|
||||
|
||||
from .basic_game import BasicGame
|
||||
|
||||
|
||||
class BasicIniGame(BasicGame):
|
||||
def __init__(self, path: str):
|
||||
# Set the _fromName to get more "correct" errors:
|
||||
self._fromName = os.path.basename(path)
|
||||
|
||||
# Read the file:
|
||||
config = configparser.ConfigParser()
|
||||
config.read(path)
|
||||
|
||||
# Just fill the class with values:
|
||||
for k, v in config["DEFAULT"].items():
|
||||
setattr(self, k, v)
|
||||
|
||||
super().__init__()
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from PyQt5.QtCore import QDir
|
||||
|
||||
|
||||
from ..basic_game import BasicGame
|
||||
|
||||
|
||||
class Witcher3Game(BasicGame):
|
||||
|
||||
Name: str = "Witcher 3 Support Plugin"
|
||||
Author: str = "Holt59"
|
||||
Version: str = "1.0.0a"
|
||||
|
||||
GameName: str = "The Witcher 3"
|
||||
GameShortName: str = "witcher3"
|
||||
GameBinary: str = "bin/x64/witcher3.exe"
|
||||
GameDataPath: str = "Mods"
|
||||
GameSaveExtension: str = "sav"
|
||||
|
||||
def steamAPPId(self):
|
||||
return "292030"
|
||||
|
||||
def savesDirectory(self):
|
||||
return QDir(self.documentsDirectory().absoluteFilePath("gamesaves"))
|
||||
Reference in New Issue
Block a user