Compare commits

..

12 Commits

Author SHA1 Message Date
cclecle
43721835be feat: add cod4_gungame config support 2023-09-27 14:44:37 +01:00
cclecle
29ec82d9ec fix format
implement most used cod4 config keys
complete unittest
2023-09-24 23:18:23 +01:00
cclecle
9250102184 fix mkdoc gen :) 2023-09-24 18:47:47 +01:00
cclecle
04fbd77a97 fix doc gen 2023-09-24 17:21:48 +01:00
cclecle
d9f133bb14 test: try to fix doc gen 2023-09-24 11:00:42 +01:00
cclecle
af2aebd110 fix: try to fix doc 2023-09-23 23:37:11 +01:00
cclecle
5d0ff79d05 fix: type hint
chore: extract common UT GameOption(s)
2023-09-23 22:06:52 +01:00
cclecle
00919c081f chore: complete / fix typing 2023-09-23 21:49:00 +01:00
cclecle
99beee0937 chore: improve type hint 2023-09-23 20:31:15 +01:00
cclecle
31b9f97d35 fix: add missing type hint
chore: remove unused directory
2023-09-23 20:19:31 +01:00
cclecle
d8c78e7fc1 fix: dependency format 2023-09-23 20:06:57 +01:00
cclecle
246d4c9529 fix: fix most pylint / type hint warnings/errors 2023-09-23 20:03:24 +01:00
27 changed files with 1594 additions and 533 deletions

View File

@@ -1,3 +1,11 @@
eclipse.preferences.version=1
encoding//src/pygamecfg/__init__.py=utf-8
encoding//src/pygamecfg/__main__.py=utf-8
encoding//src/pygamecfg/common_ut.py=utf-8
encoding//src/pygamecfg/core_gamecfg.py=utf-8
encoding//src/pygamecfg/game_cod4.py=utf-8
encoding//src/pygamecfg/game_cod4_gungame.py=utf-8
encoding//src/pygamecfg/game_ut2k4.py=utf-8
encoding//src/pygamecfg/game_ut99.py=utf-8
encoding//src/pygamecfg/tool_ini.py=utf-8
encoding/<project>=UTF-8

View File

@@ -8,46 +8,8 @@
![](docs-static/Library.jpg)
# Python project template
# pyGameCFG
A nice template to start blank python projets.
This template automate a lot of handy things and allow CI/CD automatic releases generation.
It is also collectings data to feed Jenkins build.
Checkout [Latest Documentation](https://chacha.ddns.net/mkdocs-web/chacha/{{repository}}/{{branch}}/latest/).
## Features
### Generic pipeline skeleton:
- Prepare
- GetCode
- BuildPackage
- Install
- CheckCode
- PlotMetrics
- RunUnitTests
- GenDOC
- PostRelease
### CI/CD Environment
- Jenkins
- Gitea (with patch for dynamic Readme variables: https://chacha.ddns.net/gitea/chacha/GiteaMarkupVariable)
- Docker
- MkDocsWeb
### CI/CD Helper libs
- VirtualEnv
- Changelog generation based on commits
- copier
- pylint + pylint_json2html
- mypy
- unittest + xmlrunner + junitparser + junit2htmlreport
- mkdocs
### Python project
- Full .toml implementation
- .whl automatic generation
- dynamic versionning using git repository
- embedded unit-test

View File

@@ -1,16 +1 @@
# Usage
## Pulvinar dolor
Donec dapibus est fermentum justo volutpat condimentum. Integer quis nunc neque. Donec dictum vehicula justo, in facilisis ex tincidunt in.
Vivamus sollicitudin sem dui, id mollis orci facilisis ut. Proin sed pulvinar dolor. Donec volutpat commodo urna imperdiet pulvinar. Fusce eget aliquam risus.
Vivamus viverra luctus ex, in finibus mi. Nullam elementum dapibus mollis. Ut suscipit volutpat ex, quis feugiat lacus consectetur eu.
## Condimentum faucibus
Quisque auctor egestas sem, luctus suscipit ex maximus vitae. Duis facilisis augue et condimentum faucibus.
Donec cursus, enim a sagittis egestas, lectus lorem eleifend libero, at tincidunt leo magna at libero.
Nunc eros velit, suscipit luctus tempor vel, finibus et est. Curabitur efficitur pretium pulvinar.
Donec urna lectus, vulputate quis turpis sed, placerat congue urna. Phasellus aliquet fermentum quam, non auctor elit porta nec. Morbi eu ligula at nisl ultricies condimentum vitae id ante.
## Aliquam lacinia
In volutpat lorem ex, et fringilla nibh faucibus quis. Mauris et arcu elementum, auctor dui vitae, egestas arcu. Duis sit amet aliquam quam.
Phasellus a odio turpis. Etiam tristique mi eu enim varius, eget facilisis est vestibulum. Aliquam lacinia nec purus sed luctus. Cras at laoreet erat.

View File

@@ -50,6 +50,7 @@ class doc_gen(helper_withresults_base):
reference_path = doc_path / "reference"
cls._reset_dir(reference_path)
# create one .md per python module
for path in sorted((cls.project_rootdir_path / "src").rglob("*.py")):
module_path = path.relative_to(cls.project_rootdir_path / "src").with_suffix("")
doc_path = path.relative_to(cls.project_rootdir_path / "src").with_suffix(".md")
@@ -57,14 +58,12 @@ class doc_gen(helper_withresults_base):
parts = list(module_path.parts)
if parts[-1] == "__init__":
parts = parts[:-1]
elif parts[-1] == "__main__":
if parts[-1] in ("__init__", "__main__"):
continue
cls._reset_dir(os.path.dirname(full_doc_path))
cls._create_dir(full_doc_path.parent.resolve())
with open(full_doc_path, "w+") as fd:
identifier = "src." + ".".join(parts)
identifier = ".".join(parts)
print("::: " + identifier, file=fd)
cmdopts = [f"{sys.executable}", "-m", "mkdocs", "-v", "build", "--site-dir", str(site_path), "--clean"]
@@ -93,6 +92,7 @@ class doc_gen(helper_withresults_base):
with open(cls.project_rootdir_path / "mkdocs.yml", "w") as mkdocsCfgFile:
mkdocsCfgFile.write(yaml.dump(mkdocsCfg, Dumper=Dumper, default_flow_style=False, sort_keys=False))
print(" !! start doc generation")
res = cls.run_cmd(cmdopts)
print(res.decode())
print(" !! done")

View File

@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING
from abc import ABC, abstractmethod
import os
import shutil
from pathlib import Path
import subprocess
@@ -34,11 +35,17 @@ class helper_base(ABC):
return None
@staticmethod
def _reset_dir(dirpath: Path):
def _create_dir(dirpath: Path):
dirpath = Path(dirpath)
if not os.path.exists(dirpath):
os.makedirs(dirpath)
[f.unlink() for f in Path(dirpath).glob("*") if f.is_file()]
@staticmethod
def _reset_dir(dirpath: Path):
dirpath = Path(dirpath)
if os.path.exists(dirpath):
shutil.rmtree(dirpath)
os.makedirs(dirpath)
@classmethod
def reset_result_dir(cls):

View File

@@ -63,7 +63,7 @@ class quality_check(helper_withresults_base):
[
"--load-plugins=pylint.extensions.mccabe",
"--output-format=json,parseable",
"--disable=invalid-name",
"--disable=invalid-name,too-few-public-methods,too-many-arguments", # ignore
"--ignore=_version.py",
"--reports=y",
"--score=yes",

View File

@@ -1,19 +1,11 @@
# pyChaChaDummyProject (c) by chacha
#
# pyChaChaDummyProject is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
docs_dir: docs
site_name: 'pygamecfg'
site_url: 'https://chacha.ddns.net/mkdocs-web/chacha/pygamecfg/latest/'
site_description: 'A simple game config tool that provide bash API to read / write game config files (cod, ut ...)'
site_author: 'chacha'
repo_url: 'https://chacha.ddns.net/gitea/chacha/pygamecfg'
site_name: pygamecfg
site_url: https://chacha.ddns.net/mkdocs-web/chacha/pygamecfg/latest/
site_description: A simple ini parser / factory
site_author: chacha
repo_url: https://chacha.ddns.net/gitea/chacha/pygamecfg
use_directory_urls: false
copyright: 'CC BY-NC-SA 4.0'
copyright: CC BY-NC-SA 4.0
theme:
name: material
features:
@@ -22,11 +14,11 @@ theme:
- navigation.tabs
- navigation.tabs.sticky
- navigation.footer
- toc.integrate
- navigation.path
- navigation.top
- navigation.section
- content.code.annotate
- navigation.prune
- navigation.expand
- toc.follow
palette:
- media: '(prefers-color-scheme: dark)'
@@ -52,19 +44,30 @@ plugins:
default_handler: python
handlers:
python:
path:
- src
options:
filters:
- '!^_[^_]'
inherited_members: true
inherited_members: false
show_if_no_docstring: true
show_signature_annotations: true
show_source: false
show_category_heading: true
group_by_category: true
docstring_section_style: spacy
show_root_full_path: false
merge_init_into_class: true
separate_signature: true
heading_level: 2
docstring_section_style: spacy
show_root_toc_entry: false
- with-pdf:
cover_subtitle: User Manual
cover_logo: C:\Users\chacha\git\pygamecfg\docs-static\Library.jpg
verbose: false
exclude_pages:
- LICENSE
output_path: C:\Users\chacha\git\pygamecfg\helpers-results\doc_gen\site\pdf\manual.pdf
markdown_extensions:
- def_list
- tables
@@ -115,4 +118,4 @@ markdown_extensions:
emoji_generator: !!python/name:materialx.emoji.to_svg
extra:
branch: master
repository: pygitversionhelper
repository: pygamecfg

View File

@@ -35,7 +35,8 @@ classifiers = [
dependencies = [
'importlib-metadata; python_version<"3.9"',
'packaging',
'pysimpleini>=0.3.1'
'pysimpleini>=0.3.1',
'typed-argument-parser==1.*'
]
dynamic = ["version"]
@@ -48,6 +49,7 @@ where = ["src"]
[tool.setuptools.package-data]
"pygamecfg.data" = ["*.*"]
"pysimpleini" = ["py.typed"]
[project.urls]
Homepage = "https://chacha.ddns.net/gitea/chacha/pygamecfg"
@@ -60,8 +62,8 @@ coverage-check = ["coverage>=7.0"]
complexity-check = ["radon>=5.1"]
quality-check = ["pylint>=2.15","pylint-json2html>=0.4","pandas>=1.5"]
type-check = ["mypy[reports]>=0.99" ]
doc-gen = ["mkdocs>=1.4.0", "mkdocs-material>=8.5","mkdocs-pymdownx-material-extras", "mkdocs-localsearch>=0.9.0", "mkdocstrings[python]>=0.19", "mkdocs-with-pdf>=0.9.3","pyyaml>=6.0","pymdown-extensions>=9","mkdocs-markdownextradata-plugin","mkdocs-mermaid2-plugin"]
doc-gen = ["mkdocs>=1.4.0", "mkdocs-material>=8.5","mkdocs-material-extensions","mkdocs-pymdownx-material-extras", "mkdocs-localsearch>=0.9.0", "mkdocstrings[python]>=0.19", "mkdocs-with-pdf>=0.9.3","pyyaml>=6.0","pymdown-extensions>=9","mkdocs-markdownextradata-plugin","mkdocs-mermaid2-plugin","mkdocs-autorefs"]
#[project.scripts]
#my-script = "my_package.module:function"
# [project.scripts]
# my-script = "my_package.module:function"

68
pyproject.toml.bak Normal file
View File

@@ -0,0 +1,68 @@
# pyChaChaDummyProject (c) by chacha
#
# pyChaChaDummyProject is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
[build-system]
requires = ["setuptools>=63", "wheel", "setuptools_scm"]
build-backend = "setuptools.build_meta"
[tool.setuptools_scm]
version_scheme= "post-release"
[project]
name = "pygamecfg"
description = "A simple game config tool that provide bash API to read / write game config files (cod, ut ...)"
readme = "README.md"
requires-python = ">=3.9"
keywords = ["chacha","chacha","template","pygamecfg"]
license = { file = "LICENSE.md" }
authors = [
{name="chacha",email="1000CHACHA0001@gmail.com"},
]
maintainers = [
{name="chacha",email="1000CHACHA0001@gmail.com"},
]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
]
dependencies = [
'importlib-metadata; python_version<"3.9"',
'packaging',
'pysimpleini>=0.3.1',
'typed-argument-parser==1.*'
]
dynamic = ["version"]
[tool.setuptools]
platforms = ["any"]
include-package-data = true
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
"pygamecfg.data" = ["*.*"]
[project.urls]
Homepage = "https://chacha.ddns.net/gitea/chacha/pygamecfg"
Documentation = "https://chacha.ddns.net/mkdocs-web/chacha/pygamecfg/master/latest/"
Tracker = "https://chacha.ddns.net/gitea/chacha/pygamecfg/issues"
[project.optional-dependencies]
test = ["junitparser>=2.8","junit2html>=30.1","xmlrunner>=1.7","mypy>=0.99" ]
coverage-check = ["coverage>=7.0"]
complexity-check = ["radon>=5.1"]
quality-check = ["pylint>=2.15","pylint-json2html>=0.4","pandas>=1.5"]
type-check = ["mypy[reports]>=0.99" ]
doc-gen = ["mkdocs>=1.4.0", "mkdocs-material>=8.5","mkdocs-material-extensions","mkdocs-pymdownx-material-extras", "mkdocs-localsearch>=0.9.0", "mkdocstrings[python]>=0.19", "mkdocs-with-pdf>=0.9.3","pyyaml>=6.0","pymdown-extensions>=9","mkdocs-markdownextradata-plugin","mkdocs-mermaid2-plugin","mkdocs-autorefs"]
# [project.scripts]
# my-script = "my_package.module:function"

View File

@@ -1,11 +1,14 @@
# pygamecfg (c) by chacha
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pyGameCFG(c) by chacha
#
# pygamecfg is licensed under a
# pyGameCFG is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
# pylint: disable=wrong-import-position
"""
Main module __init__ file.
"""
@@ -13,9 +16,6 @@ Main module __init__ file.
from importlib.metadata import distribution, version, PackageNotFoundError
import warnings
from .core import GameOptions_Factory
from . import ut99, ut2k4, cod4
try: # pragma: no cover
__version__ = version("pygamecfg")
@@ -36,3 +36,9 @@ try: # pragma: no cover
except PackageNotFoundError: # pragma: no cover
warnings.warn('can not read dist.metadata["Name"], assuming local test context, setting it to <pygamecfg>')
__Name__ = "pygamecfg"
from .core_gamecfg import GameOptions_Factory
from . import game_cod4
from . import game_cod4_gungame
from . import game_ut99
from . import game_ut2k4

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# PySimpleINI (c) by chacha
# pyGameCFG(c) by chacha
#
# PySimpleINI is licensed under a
# pyGameCFG is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
@@ -10,55 +11,98 @@
"""CLI interface module"""
from __future__ import annotations
from typing import Literal, cast, Union
import sys
from argparse import ArgumentParser
from tap import Tap
from . import __Summuary__, __Name__
from . import GameOptions_Factory
def fct_main(args: list[str]) -> int:
parser = ArgumentParser(prog=__Name__, description=__Summuary__)
parser.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity")
class pygamecfg_args_SetOption(Tap):
"""SetOption CLI arg subparser"""
parser.add_argument("-b", "--basegamedir", help="set the game base dir", default="./")
parser.add_argument("-c", "--configfile", help="set the default config file", default="")
parser.add_argument("-g", "--game", help="the target game", choices=["ut99", "cod4", "ut2k4"], required=True)
subparsers = parser.add_subparsers(dest="command", help="command type", required=True)
option: str
value: str = ""
SetOption_subparser = subparsers.add_parser("SetOption", help="Set/Add a game config file option value (may need reboot)")
SetOption_subparser.add_argument("option")
SetOption_subparser.add_argument("value", nargs="?")
def configure(self) -> None:
self.add_argument("option")
self.add_argument("value")
RemOption_subparser = subparsers.add_parser("RemOption", help="Remove a game config file option, w or w/o value (may need reboot)")
RemOption_subparser.add_argument("option")
RemOption_subparser.add_argument("value", nargs="?")
GetOption_subparser = subparsers.add_parser("GetOption", help="Get a game config file option value")
GetOption_subparser.add_argument("option")
class pygamecfg_args_RemOption(Tap):
"""RemOption CLI arg subparser"""
args = parser.parse_args(args)
option: str
value: str = ""
def configure(self) -> None:
self.add_argument("option")
self.add_argument("value")
class pygamecfg_args_GetOption(Tap):
"""GetOption CLI arg subparser"""
option: str
def configure(self) -> None:
self.add_argument("option")
class pygamecfg_args(Tap):
"""Main CLI arg parser"""
verbosity: int = 0
basegamedir: str = "./"
configfile: str = ""
game: Literal["ut99", "cod4", "cod4_gungame", "ut2k4"]
def configure(self) -> None:
self.add_argument("-v", "--verbosity", action="count", help="increase output verbosity")
self.add_argument("-b", "--basegamedir", help="set the game base dir")
self.add_argument("-c", "--configfile", help="set the default config file")
self.add_argument("-g", "--game", help="the target game")
self.add_subparsers(dest="command", help="command type", required=True)
self.add_subparser("SetOption", pygamecfg_args_SetOption, help="Set/Add a game config file option value")
self.add_subparser("RemOption", pygamecfg_args_RemOption, help="Remove a game config file option, w or w/o value")
self.add_subparser("GetOption", pygamecfg_args_GetOption, help="Get a game config file option value")
def process_args(self) -> None:
"""dynamically add self.command to avoid conflict with Tap/argparse while keep pylint happy"""
self.command: Union[str, None] = cast(Union[str, None], self.command) # pylint: disable=attribute-defined-outside-init
def fct_main(i_args: list[str]) -> None:
"""CLI main function"""
parser: pygamecfg_args = pygamecfg_args(prog=__Name__, description=__Summuary__)
args: pygamecfg_args = parser.parse_args(i_args)
if args.verbosity:
print("Using base game dir: {0}".format(args.basegamedir))
print("Using config file: {0}".format(args.configfile))
print(f"Using base game dir: {args.basegamedir}")
print(f"Using config file: {args.configfile}")
if args.configfile == "":
if args.game == "ut99":
args.configfile = "./System/UnrealTournament.ini"
if args.game == "ut2k4":
args.configfile = "./System/UT2004.ini"
elif args.game == "cod4":
elif args.game in ("cod4", "cod4_gungame"):
args.configfile = "./main/server.cfg"
_GameOptions = GameOptions_Factory(args.game, args.basegamedir, args.configfile)
GameOptions = GameOptions_Factory(args.game, args.basegamedir, args.configfile)
if args.command == "SetOption":
_GameOptions.set(args.option, args.value)
GameOptions.set(
cast(pygamecfg_args_SetOption, args).option, cast(pygamecfg_args_SetOption, args).value # pylint: disable=no-member
)
elif args.command == "RemOption":
_GameOptions.rem(args.option, args.value)
GameOptions.rem(
cast(pygamecfg_args_RemOption, args).option, cast(pygamecfg_args_RemOption, args).value # pylint: disable=no-member
)
elif args.command == "GetOption":
res = _GameOptions.get(args.option)
res = GameOptions.get(cast(pygamecfg_args_GetOption, args).option) # pylint: disable=no-member
print(res)
else:
raise RuntimeError("Invalid argument")

View File

@@ -1,101 +0,0 @@
from __future__ import annotations
from typing import Union
import re
from os.path import join
from .core import GameOptions_Factory_Register, GameOption, OptionType
class GameOption_COD4(GameOption):
szGameType = "cod4"
TValueType = OptionType.OT_INVALID
szOptionName: str = ""
szKeyName: str = ""
bDblQuoted: bool = False
szPrefix = ""
def __init__(self, GameRootDir: str, ConfigFileRelPath: str) -> None:
super().__init__(GameRootDir, ConfigFileRelPath)
self.mainConfigFilePath = join(GameRootDir, ConfigFileRelPath)
self.cfgfile = open(self.mainConfigFilePath, "r")
def set(self, value: str) -> None:
if not self.szOptionName:
raise RuntimeError("szOptionName is not set")
self.format(value)
if self.bDblQuoted:
value = '"' + value + '"'
if self.szPrefix:
value = self.szPrefix + " " + self.szKeyName + " " + value
for line in self.cfgfile.readlines():
if re.search(r"\s+" + self.szPrefix + r"\s+"):
print(f"found: {line}")
def rem(self, value: Union[str, None] = None) -> None:
if not self.szOptionName:
raise RuntimeError("szOptionName is not set")
regex = r"^\s*" + self.szPrefix + r"\s+" + self.szOptionName + r"\s*(?P<value>.*)"
bfound = False
newFile = ""
for line in self.cfgfile.readlines():
if re.search(regex, line):
if bfound:
print("[warning] Option defined multiple time")
bfound = True
else:
newFile += line
if not bfound:
raise RuntimeError("Option not found in file")
self.cfgfile.close()
with open(self.mainConfigFilePath, "w") as ofile:
ofile.write(newFile)
self.cfgfile = open(self.mainConfigFilePath, "r")
def get(self) -> Union[None, str]:
if not self.szOptionName:
raise RuntimeError("szOptionName not set")
if self.bDblQuoted:
regex = r"^\s*" + self.szPrefix + r"\s+" + self.szOptionName + r"\s*\"(?P<value>.*)\""
else:
regex = r"^\s*" + self.szPrefix + r"\s+" + self.szOptionName + r"\s*(?P<value>.*)"
bfound = False
res = None
for line in self.cfgfile.readlines():
if result := re.search(regex, line):
if bfound:
raise RuntimeError("Option defined multiple time")
res = result.groupdict()["value"]
bfound = True
if not bfound:
raise RuntimeError("Option not found in file")
return res
@GameOptions_Factory_Register
class GameOption_COD4_sv_maxclients(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "sv_maxclients"
szKeyName: str = "sv_maxclients"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "12"
szHelp = "Maximum client number"
@GameOptions_Factory_Register
class GameOption_COD4_sv_mapRotation(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "sv_mapRotation"
szKeyName: str = "sv_mapRotation"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "gametype dm map mp_block"
szHelp = "Map rotation list"

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pyGameCFG(c) by chacha
#
# pyGameCFG is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
"""common UT functions"""
from __future__ import annotations
from typing import Union
from os.path import join
from pathlib import Path
from pysimpleini import PySimpleINI
from .core_gamecfg import GameOption, OptionType
class GameOption_UT(GameOption):
"""generic UT Option class"""
szGameType = ""
TValueType = OptionType.OT_INVALID
szOptionName = ""
szSectionName = ""
szKeyName = ""
bForceAdd: bool = False
bRemovable: bool = False
cachedFile: Union[None, PySimpleINI] = None
cachedFilePath: Union[None, Path] = None
Cls_PySimpleINI: type[PySimpleINI] = PySimpleINI
@classmethod
def openFile(cls, filepath: Path) -> PySimpleINI:
"""Open the file"""
if (not cls.cachedFile) or (filepath != cls.cachedFilePath):
cls.cachedFilePath = filepath
cls.cachedFile = cls.Cls_PySimpleINI(filepath)
return cls.cachedFile
def __init__(self, GameRootDir: str, ConfigFileRelPath: str) -> None:
super().__init__(GameRootDir, ConfigFileRelPath)
self.mainConfigFilePath: Path = Path(join(GameRootDir, ConfigFileRelPath))
self.inifile = self.openFile(self.mainConfigFilePath)
def set(self, value: str) -> None:
if not self.szOptionName:
raise RuntimeError("szOptionName is not set")
self.format(value)
self.inifile.setaddkeyvalue(self.szSectionName, self.szKeyName, value, self.bForceAdd)
self.inifile.writefile()
def rem(self, value: Union[None, str]) -> None:
if not self.szOptionName:
raise RuntimeError("szOptionName is not set")
if not self.bRemovable:
raise RuntimeError("this options is not removable")
self.inifile.delkey_ex(self.szSectionName, self.szKeyName, None, value)
self.inifile.writefile()
def get(self) -> Union[str, list[str]]:
if not self.szOptionName:
raise RuntimeError("szOptionName not set")
return self.inifile.getkeyvalue(self.szSectionName, self.szKeyName)

View File

@@ -1,97 +0,0 @@
from __future__ import annotations
from typing import Union
from abc import ABCMeta, abstractmethod
from enum import Enum
class OptionType(Enum):
OT_INVALID = 0
OT_STRING = 1
OT_INTEGER = 2
OT_BOOLEAN = 3
OT_FLOAT = 4
class GameOption(metaclass=ABCMeta):
szGameType: str = ""
szOptionName: str = ""
TValueType: OptionType = OptionType.OT_INVALID
szDefaultValue: str = ""
szHelp: str = ""
szFormatedValue: str = ""
@abstractmethod
def __init__(self, GameRootDir: str, ConfigFileRelPath: Union[None, str] = None):
self.GameRootDir = GameRootDir
self.ConfigFileRelPath = ConfigFileRelPath
def format(self, value: Union[int, str, float]) -> None:
if self.TValueType == OptionType.OT_STRING:
self.szFormatedValue = str(value)
elif self.TValueType == OptionType.OT_INTEGER:
self.szFormatedValue = str(int(value))
elif self.TValueType == OptionType.OT_BOOLEAN:
try:
intval = int(value)
self.szFormatedValue = str(bool(intval))
except:
self.szFormatedValue = str(True) if str(value).lower() == "true" else str(False)
elif self.TValueType == OptionType.OT_FLOAT:
self.szFormatedValue = str(float(value))
else:
raise RuntimeError("Invalid Option TValueType")
print("setting option <{0}> to: {1}".format(self.szOptionName, self.szFormatedValue))
@abstractmethod
def set(self, value: str) -> None:
raise NotImplementedError("method not implemented")
@abstractmethod
def rem(self, value: Union[None, str]) -> None:
raise NotImplementedError("method not implemented")
@abstractmethod
def get(self) -> Union[None, str]:
raise NotImplementedError("method not implemented")
class GameOptions_Factory:
ar_Options_cls = []
def __init__(self, szGameType: str, GameRootDir: str, ConfigFileRelPath: Union[None, str] = None) -> None:
self.szGameType = szGameType
self.ar_Options = []
for Options_cls in GameOptions_Factory.ar_Options_cls:
if Options_cls.szGameType == szGameType:
self.ar_Options.append(Options_cls(GameRootDir, ConfigFileRelPath))
@classmethod
def GameOptionRegister(cls, Option: GameOption) -> None:
cls.ar_Options_cls.append(Option)
def set(self, OptionName: str, value: str) -> None:
for _option in GameOptions_Factory.ar_Options:
if _option.szOptionName == OptionName:
_option.set(value)
return
raise RuntimeError("Option not found")
def rem(self, OptionName: str, value: Union[None, str]) -> None:
for _option in self.ar_Options:
if _option.szOptionName == OptionName:
_option.rem(value)
return
raise RuntimeError("Option not found")
def get(self, OptionName: str) -> Union[None, str]:
for _option in self.ar_Options:
if _option.szOptionName == OptionName:
return _option.get()
raise RuntimeError("Option not found")
def GameOptions_Factory_Register(cls):
GameOptions_Factory.GameOptionRegister(cls)
return cls

View File

@@ -0,0 +1,171 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pyGameCFG(c) by chacha
#
# pyGameCFG is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
""" Core file of pygamecfg
contain generic management code for GameOption
"""
from __future__ import annotations
from typing import Union
from abc import ABCMeta, abstractmethod
from enum import Enum
class OptionType(Enum):
"""Supported option data type"""
OT_INVALID = 0
OT_STRING = 1
OT_INTEGER = 2
OT_BOOLEAN = 3
OT_FLOAT = 4
class GameOption(metaclass=ABCMeta):
"""Game option base type"""
szGameType: str = ""
szOptionName: str = ""
TValueType: OptionType = OptionType.OT_INVALID
szDefaultValue: str = ""
szHelp: str = ""
def __init__(self, GameRootDir: str, ConfigFileRelPath: Union[None, str] = None):
"""GameOption constructor.
///warning
This object does not aim to be created
///
Args:
GameRootDir: root dir of the game
ConfigFileRelPath: path to the configfile (relative to rootdir)
"""
self.GameRootDir = GameRootDir
self.ConfigFileRelPath = ConfigFileRelPath
def __enter__(self) -> GameOption:
"""contextlib enter hook"""
return self
def __exit__(self, exception_type, exception_value, exception_traceback) -> None:
"""contextlib exit hook"""
self.close()
def close(self) -> None:
"""user-define close() function (for subclassing)"""
def format_OT_STRING(self, value: Union[int, str, float]) -> str:
""" "STRING specific format method (TO file)"""
return str(value)
def format_OT_INTEGER(self, value: Union[int, str, float]) -> str:
""" "INTEGER specific format method (TO file)"""
return str(int(value))
def format_OT_BOOLEAN(self, value: Union[int, str, float]) -> str:
""" "BOOLEAN specific format method (TO file)"""
try:
intval = int(value)
return str(bool(intval))
except ValueError:
return str(True) if str(value).lower() == "true" else str(False)
def format_OT_FLOAT(self, value: Union[int, str, float]) -> str:
""" "FLOAT specific format method (TO file)"""
return str(float(value))
def format(self, value: Union[int, str, float]) -> str:
"""standard method to format options before writing it TO file (overloadable)"""
FormatedValue: str = ""
if self.TValueType == OptionType.OT_STRING:
FormatedValue = self.format_OT_STRING(value)
elif self.TValueType == OptionType.OT_INTEGER:
FormatedValue = self.format_OT_INTEGER(value)
elif self.TValueType == OptionType.OT_BOOLEAN:
FormatedValue = self.format_OT_BOOLEAN(value)
elif self.TValueType == OptionType.OT_FLOAT:
FormatedValue = self.format_OT_FLOAT(value)
else:
raise RuntimeError("Invalid Option TValueType")
print(f"setting option <{self.szOptionName}> to: {FormatedValue}")
return FormatedValue
@abstractmethod
def set(self, value: str) -> None:
"""generic set function"""
raise NotImplementedError("method not implemented")
@abstractmethod
def rem(self, value: Union[None, str]) -> None:
"""generic rem function"""
raise NotImplementedError("method not implemented")
@abstractmethod
def get(self) -> Union[str, list[str]]:
"""generic get function"""
raise NotImplementedError("method not implemented")
class GameOptions_Factory:
"""factory that manage game options based on Game and the option itself"""
ar_Options_cls: list[type[GameOption]] = []
ar_Options_cls_filtered: list[type[GameOption]] = []
GameRootDir: str = "./"
ConfigFileRelPath: str = ""
def __init__(self, szGameType: str, GameRootDir: str, ConfigFileRelPath: str) -> None:
self.szGameType = szGameType
self.GameRootDir = GameRootDir
self.ConfigFileRelPath = ConfigFileRelPath
for Options_cls in GameOptions_Factory.ar_Options_cls:
if Options_cls.szGameType == szGameType:
self.ar_Options_cls_filtered.append(Options_cls)
@classmethod
def GameOptionRegister(cls, Option: type[GameOption]) -> None:
"""interface option used by decorator to register option implementation classes"""
GameOptions_Factory.ar_Options_cls.append(Option)
def set(self, OptionName: str, value: str) -> None:
"""generic set function (API call)"""
for _option in GameOptions_Factory.ar_Options_cls_filtered:
if _option.szOptionName == OptionName:
with _option(self.GameRootDir, self.ConfigFileRelPath) as _optionInst:
_optionInst.set(_optionInst.format(value))
return
raise RuntimeError("Option not found")
def rem(self, OptionName: str, value: Union[None, str]) -> None:
"""generic rem function (API call)"""
for _option in self.ar_Options_cls_filtered:
if _option.szOptionName == OptionName:
with _option(self.GameRootDir, self.ConfigFileRelPath) as _optionInst:
_optionInst.rem(value)
return
raise RuntimeError("Option not found")
def get(self, OptionName: str) -> Union[str, list[str]]:
"""generic get function (API call)"""
for _option in self.ar_Options_cls_filtered:
if _option.szOptionName == OptionName:
with _option(self.GameRootDir, self.ConfigFileRelPath) as _optionInst:
return _optionInst.get()
raise RuntimeError("Option not found")
def GameOptions_Factory_Register(cls: type[GameOption]) -> type[GameOption]:
"""decorator to register game option concrete implementation"""
GameOptions_Factory.GameOptionRegister(cls)
return cls

View File

@@ -1,7 +0,0 @@
# pygamecfg (c) by chacha
#
# pygamecfg is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.

601
src/pygamecfg/game_cod4.py Normal file
View File

@@ -0,0 +1,601 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pyGameCFG(c) by chacha
#
# pyGameCFG is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,duplicate-code
from __future__ import annotations
from typing import Union
import re
from os.path import join
from os import linesep
from .core_gamecfg import GameOptions_Factory_Register, GameOption, OptionType
class GameOption_COD4(GameOption):
szGameType = "cod4"
TValueType = OptionType.OT_INVALID
szOptionName: str = ""
szKeyName: str = ""
bDblQuoted: bool = False
szPrefix = ""
def __init__(self, GameRootDir: str, ConfigFileRelPath: str) -> None:
super().__init__(GameRootDir, ConfigFileRelPath)
self.mainConfigFilePath = join(GameRootDir, ConfigFileRelPath)
self.cfgfile = open(self.mainConfigFilePath, "r", encoding="utf8") # pylint: disable=consider-using-with
def format_OT_BOOLEAN(self, value: Union[int, str, float]) -> str:
return "1" if bool(value) is True else "0"
def close(self) -> None:
self.cfgfile.close()
def set(self, value: str) -> None:
if not self.szOptionName:
raise RuntimeError("szOptionName is not set")
FinalValue: str = value
if self.bDblQuoted:
FinalValue = '"' + FinalValue + '"'
if self.szPrefix:
FinalValue = self.szPrefix + " " + self.szKeyName + " " + FinalValue + linesep
bfound = False
newFile = ""
regex = r"^\s*" + self.szPrefix + r"\s+" + self.szKeyName + r"\s*(?P<value>.*)"
for line in self.cfgfile.readlines():
if re.search(regex, line):
if bfound:
print("[warning] Option defined multiple time")
newFile += FinalValue
bfound = True
else:
newFile += line
if not bfound:
# write new key at the top of the file to be sure we do not erase the map / map_rotate cmd
newFile = value + newFile
self.cfgfile.close()
with open(self.mainConfigFilePath, "w", encoding="utf8") as ofile:
ofile.write(newFile)
self.cfgfile = open(self.mainConfigFilePath, "r", encoding="utf8") # pylint: disable=consider-using-with
def rem(self, value: Union[str, None] = None) -> None:
if not self.szOptionName:
raise RuntimeError("szOptionName is not set")
regex = r"^\s*" + self.szPrefix + r"\s+" + self.szKeyName + r"\s*(?P<value>.*)"
bfound = False
newFile = ""
for line in self.cfgfile.readlines():
if re.search(regex, line):
if bfound:
print("[warning] Option defined multiple time")
bfound = True
else:
newFile += line
if not bfound:
raise RuntimeError("Option not found in file")
self.cfgfile.close()
with open(self.mainConfigFilePath, "w", encoding="utf8") as ofile:
ofile.write(newFile)
self.cfgfile = open(self.mainConfigFilePath, "r", encoding="utf8") # pylint: disable=consider-using-with
def get(self) -> str:
if not self.szOptionName:
raise RuntimeError("szOptionName not set")
if self.bDblQuoted:
regex = r"^\s*" + self.szPrefix + r"\s+" + self.szKeyName + r"\s*\"(?P<value>.*)\""
else:
regex = r"^\s*" + self.szPrefix + r"\s+" + self.szKeyName + r"\s*(?P<value>.*)"
bfound = False
for line in self.cfgfile.readlines():
if result := re.search(regex, line):
if bfound:
raise RuntimeError("Option defined multiple time")
res = result.groupdict()["value"]
bfound = True
if not bfound:
raise RuntimeError("Option not found in file")
return res
@GameOptions_Factory_Register
class GameOption_COD4_Meta_Admin(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "meta_admin"
szKeyName: str = "_Admin"
bDblQuoted: bool = True
szPrefix = "sets"
szDefaultValue = "ServAdmin"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_Meta_Email(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "meta_email"
szKeyName: str = "_Email"
bDblQuoted: bool = True
szPrefix = "sets"
szDefaultValue = "ServEmail"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_Meta_Website(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "meta_website"
szKeyName: str = "_Website"
bDblQuoted: bool = True
szPrefix = "sets"
szDefaultValue = "ServWebsite"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_Meta_Location(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "meta_location"
szKeyName: str = "_Location"
bDblQuoted: bool = True
szPrefix = "sets"
szDefaultValue = "ServLocation"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_Meta_Maps(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "meta_maps"
szKeyName: str = "_Maps"
bDblQuoted: bool = True
szPrefix = "sets"
szDefaultValue = ""
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_Meta_Gametype(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "meta_gametype"
szKeyName: str = "_Gametype"
bDblQuoted: bool = True
szPrefix = "sets"
szDefaultValue = ""
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_Cod4x_AuthToken(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "cod4x_authtoken"
szKeyName: str = "sv_authtoken"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = "Cod4x personnal auth token"
@GameOptions_Factory_Register
class GameOption_COD4_Hostname(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "hostname"
szKeyName: str = "sv_hostname"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = "Server Hostname"
@GameOptions_Factory_Register
class GameOption_COD4_MOTD(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "motd"
szKeyName: str = "g_motd"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "Welcome"
szHelp = "Server Message Of The Day"
@GameOptions_Factory_Register
class GameOption_COD4_dedicated(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "dedicated"
szKeyName: str = "dedicated"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "2"
szHelp = """0 = Listen, 1 = LAN, 2 = Internet ( you probably want 2 )"""
@GameOptions_Factory_Register
class GameOption_COD4_RCON_password(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "rcon_password"
szKeyName: str = "rcon_password"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = "password for remote access, leave empty to deactivate, min 8 characters"
@GameOptions_Factory_Register
class GameOption_COD4_game_password(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "game_password"
szKeyName: str = "g_password"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = "join password, leave empty to deactivate"
@GameOptions_Factory_Register
class GameOption_COD4_nb_private_clients(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "nb_privateClients"
szKeyName: str = "sv_privateClients"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "3"
szHelp = """Private Clients, number of slots that can only be changed with a password"""
@GameOptions_Factory_Register
class GameOption_COD4_private_password(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "private_password"
szKeyName: str = "sv_privatePassword"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = "the password to join private slots"
@GameOptions_Factory_Register
class GameOption_COD4_maxclients(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "maxclients"
szKeyName: str = "sv_maxclients"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "12"
szHelp = """Maximum client number"""
@GameOptions_Factory_Register
class GameOption_COD4_logsync(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "logsync"
szKeyName: str = "g_logsync"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "2"
szHelp = """0=no log, 1=buffered, 2=continuous, 3=append"""
@GameOptions_Factory_Register
class GameOption_COD4_enable_logfile(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "enable_logfile"
szKeyName: str = "logfile"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1"
szHelp = """0 = NO log, 1 = log file enabled"""
@GameOptions_Factory_Register
class GameOption_COD4_logfile(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "logfile"
szKeyName: str = "g_log"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "games_mp.log"
szHelp = "Name of log file, default is games_mp.log"
@GameOptions_Factory_Register
class GameOption_COD4_enable_logdamage(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "enable_logdamage"
szKeyName: str = "sv_log_damage"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1"
szHelp = """0 = NO logdamage, 1 = logdamage enabled"""
@GameOptions_Factory_Register
class GameOption_COD4_statusfile(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "statusfile"
szKeyName: str = "sv_statusfile"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "serverstatus.xml"
szHelp = "writes an xml serverstatus to disc, leave empty to disable"
@GameOptions_Factory_Register
class GameOption_COD4_ney_port(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "net_port"
szKeyName: str = "net_port"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "28960"
szHelp = """network port"""
@GameOptions_Factory_Register
class GameOption_COD4_maxRate(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "maxRate"
szKeyName: str = "sv_maxRate"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "25000"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_minPing(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "minPing"
szKeyName: str = "sv_minPing"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
szHelp = "minimal ping [ms] for a player to join the server"
@GameOptions_Factory_Register
class GameOption_COD4_maxPing(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "maxPing"
szKeyName: str = "sv_maxPing"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "350"
szHelp = "maximal ping [ms] for a player to join the server"
@GameOptions_Factory_Register
class GameOption_COD4_randomMapRotation(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "randomMapRotation"
szKeyName: str = "sv_randomMapRotation"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """0 = sv_mapRotation is randomized, 1 = sequential order of sv_mapRotation"""
@GameOptions_Factory_Register
class GameOption_COD4_teambalance(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "teambalance"
szKeyName: str = "scr_teambalance"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1"
szHelp = "auto-teambalance //0 = no, 1 = yes"
@GameOptions_Factory_Register
class GameOption_COD4_team_fftype(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "team_fftype"
szKeyName: str = "scr_team_fftype"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
szHelp = "friendly-fire //0 = off, 1 = on, //2 = reflect damage, 3 = shared damage"
@GameOptions_Factory_Register
class GameOption_COD4_enable_hardcore(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "hardcore"
szKeyName: str = "scr_hardcore"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "0"
szHelp = """Hardcore Mode //0 = off 1 = on"""
@GameOptions_Factory_Register
class GameOption_COD4_enable_oldschool(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "oldschool"
szKeyName: str = "scr_oldschool"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
szHelp = """Oldschool Mode //0 = off, 1 = on"""
@GameOptions_Factory_Register
class GameOption_COD4_enable_friendlyPlayerCanBlock(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "friendlyPlayerCanBlock"
szKeyName: str = "g_friendlyPlayerCanBlock"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """1 = player collision between friendly players, 0 = collision between friendly players is disabled"""
@GameOptions_Factory_Register
class GameOption_COD4_enable_FFAPlayerCanBlock(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "FFAPlayerCanBlock"
szKeyName: str = "g_FFAPlayerCanBlock"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """1 = player collision, 0 = collision between players is disabled"""
@GameOptions_Factory_Register
class GameOption_COD4_DM_scorelimit(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "dm_scorelimit"
szKeyName: str = "scr_dm_scorelimit"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1000"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_DM_timelimit(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "dm_timelimit"
szKeyName: str = "scr_dm_timelimit"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "15"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_DM_roundlimit(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "dm_roundlimit"
szKeyName: str = "scr_dm_roundlimit"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_TDM_scorelimit(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "tdm_scorelimit"
szKeyName: str = "scr_war_scorelimit"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "2000"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_TDM_timelimit(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "tdm_timelimit"
szKeyName: str = "scr_war_timelimit"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "10"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_TDM_roundlimit(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "tdm_roundlimit"
szKeyName: str = "scr_war_roundlimit"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_gametype(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "gametype"
szKeyName: str = "g_gametype"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "dm"
szHelp = "gamemode, one of [war, dm, sd, sab, koth]"
@GameOptions_Factory_Register
class GameOption_COD4_mapRotation(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "mapRotation"
szKeyName: str = "sv_mapRotation"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "gametype dm map mp_block"
szHelp = """Map rotation list"""
@GameOptions_Factory_Register
class GameOption_COD4_allowdownloadk(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "allowdownload"
szKeyName: str = "sv_allowdownload"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_wwwDownload(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "wwwDownload"
szKeyName: str = "sv_wwwDownload"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_wwwBaseURL(GameOption_COD4):
TValueType = OptionType.OT_STRING
szOptionName: str = "wwwBaseURL"
szKeyName: str = "sv_wwwBaseURL"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_wwwDlDisconnected(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "wwwDlDisconnected"
szKeyName: str = "sv_wwwDlDisconnected"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_nosteamnames(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "nosteamnames"
szKeyName: str = "sv_nosteamnames"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = "1 = Use names from steam if steam is available"

View File

@@ -0,0 +1,135 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pyGameCFG(c) by chacha
#
# pyGameCFG is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,duplicate-code
from __future__ import annotations
from typing import Union
import re
from os.path import join
from os import linesep
from .core_gamecfg import GameOptions_Factory_Register, OptionType
from .game_cod4 import GameOption_COD4
class GameOption_COD4_GunGame(GameOption_COD4):
szGameType = "cod4_gungame"
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_Enable(GameOption_COD4_GunGame):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "mc_enable"
szKeyName: str = "mc_enable"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "0"
szHelp = """Enable rotating messages? [0: disable; 1: new custom messages; 2: standard print messages bottom left]"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_Delay(GameOption_COD4_GunGame):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "mc_delay"
szKeyName: str = "mc_delay"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "20"
szHelp = """Delay between messages (sec)"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_MaxMsg(GameOption_COD4_GunGame):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "mc_max_msg"
szKeyName: str = "mc_max_msg"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "0"
szHelp = """Maximum number of messages to use [max 20]"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_Message_0(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "mc_msg_0"
szKeyName: str = "mc_msg_0"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = """message num 0 in rotation"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_Message_1(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "mc_msg_1"
szKeyName: str = "mc_msg_1"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = """message num 1 in rotation"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_Message_2(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "mc_msg_2"
szKeyName: str = "mc_msg_2"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = """message num 2 in rotation"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_Message_3(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "mc_msg_3"
szKeyName: str = "mc_msg_3"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = """message num 3 in rotation"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_Message_4(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "mc_msg_4"
szKeyName: str = "mc_msg_4"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = """message num 4 in rotation"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_Message_5(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "mc_msg_5"
szKeyName: str = "mc_msg_5"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = """message num 5 in rotation"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_Message_6(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "mc_msg_6"
szKeyName: str = "mc_msg_6"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = ""
szHelp = """message num 6 in rotation"""

View File

@@ -1,74 +1,41 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pyGameCFG(c) by chacha
#
# pyGameCFG is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
# pylint: disable=missing-class-docstring,missing-function-docstring,duplicate-code
"""UT2k4 command set"""
from __future__ import annotations
from typing import Union
from pysimpleini import PySimpleINI, KeyNotFoundError, SectionNotFoundError, Section, Key
from os.path import join
from pysimpleini import KeyNotFoundError
from .core import GameOptions_Factory_Register, GameOption, OptionType
from .core_gamecfg import GameOptions_Factory_Register, OptionType
from .tool_ini import PySimpleINI_GroupKeysInSection
from .common_ut import GameOption_UT
class ChaChaSimpleINI_UT2k4(PySimpleINI):
def GroupKeysInSection(self, szSectionName: str, szKeyName: str) -> None:
try:
_section = self.getSection(szSectionName)
if type(_section) is Section:
ar_ServerPackages = _section.getKey(szKeyName)
if isinstance(ar_ServerPackages, Key):
ar_ServerPackages = [ar_ServerPackages]
else: # array
pass
for ServerPackages in ar_ServerPackages:
_section.delKey(ServerPackages.name, None, ServerPackages.value)
for ServerPackages in ar_ServerPackages:
_section.setAddKeyValue(ServerPackages.name, ServerPackages.value, True)
except SectionNotFoundError:
pass
def writeFile(self, bBeautify: bool = False) -> None:
self.GroupKeysInSection("Engine.GameEngine", "ServerPackages")
self.GroupKeysInSection("Engine.GameEngine", "ServerActors")
self.GroupKeysInSection("Core.System", "Suppress")
self.GroupKeysInSection("Core.System", "Paths")
self.GroupKeysInSection("Editor.EditorEngine", "EditPackages")
self.GroupKeysInSection("Editor.EditorEngine", "CutdownPackages")
super().writeFile(bBeautify)
class PySimpleINI_UT2k4(PySimpleINI_GroupKeysInSection):
GroupRules = [
("Engine.GameEngine", "ServerPackages"),
("Engine.GameEngine", "ServerActors"),
("Core.System", "Suppress"),
("Core.System", "Paths"),
("Core.System", "Paths"),
("Editor.EditorEngine", "EditPackages"),
("Editor.EditorEngine", "CutdownPackages"),
]
class GameOption_UT2k4(GameOption):
class GameOption_UT2k4(GameOption_UT):
szGameType = "ut2k4"
TValueType = OptionType.OT_INVALID
szOptionName = ""
szSectionName = ""
szKeyName = ""
bForceAdd: bool = False
bRemovable: bool = False
def __init__(self, GameRootDir: str, ConfigFileRelPath: Union[None, str]) -> None:
super().__init__(GameRootDir, ConfigFileRelPath)
self.mainConfigFilePath = join(GameRootDir, ConfigFileRelPath)
def set(self, value: str) -> None:
if not self.szOptionName:
raise RuntimeError("szOptionName is not set")
self.format(value)
self.inifile = ChaChaSimpleINI_UT2k4(self.mainConfigFilePath)
self.inifile.setAddKeyValue(self.szSectionName, self.szKeyName, self.szFormatedValue, self.bForceAdd)
self.inifile.writeFile()
def rem(self, value: Union[None, str]) -> None:
if not self.szOptionName:
raise RuntimeError("szOptionName is not set")
if not self.bRemovable:
raise RuntimeError("this options is not removable")
self.inifile.delKeyEx(self.szSectionName, self.szKeyName, None, value)
self.inifile.writeFile()
def get(self) -> Union[None, str]:
if not self.szOptionName:
raise RuntimeError("szOptionName not set")
res = self.inifile.getKeyValue(self.szSectionName, self.szKeyName)
return res
Cls_PySimpleINI = PySimpleINI_UT2k4
class GameOption_UT2k4_GenAdd(GameOption_UT2k4):
@@ -155,10 +122,10 @@ class GameOption_UT2k4_HostName(GameOption_UT2k4):
szDefaultValue = "ChaCha Test Server"
szHelp = "Server's HostName"
def set(self, value: str):
def set(self, value: str) -> None:
super().set(value)
self.inifile.setAddKeyValue("Engine.GameReplicationInfo", "ShortName", value)
self.inifile.writeFile()
self.inifile.setaddkeyvalue("Engine.GameReplicationInfo", "ShortName", value)
self.inifile.writefile()
@GameOptions_Factory_Register
@@ -200,12 +167,12 @@ class GameOption_UT2k4_HTTPDownloadServer(GameOption_UT2k4):
szDefaultValue = "http://chacha.ddns.net/games/ut2k4"
szHelp = "FastDL url"
def set(self, value: str):
def set(self, value: str) -> None:
super().set(value)
self.inifile.setAddKeyValue("IpDrv.HTTPDownload", "UseCompression", "True")
self.inifile.delKey("IpDrv.HTTPDownload", "ProxyServerHost")
self.inifile.delKey("IpDrv.HTTPDownload", "ProxyServerPort")
self.inifile.writeFile()
self.inifile.setaddkeyvalue("IpDrv.HTTPDownload", "UseCompression", "True")
self.inifile.delkey("IpDrv.HTTPDownload", "ProxyServerHost")
self.inifile.delkey("IpDrv.HTTPDownload", "ProxyServerPort")
self.inifile.writefile()
@GameOptions_Factory_Register
@@ -237,10 +204,10 @@ class GameOption_UT2k4_NetServerMaxTickRate(GameOption_UT2k4):
szDefaultValue = "60"
szHelp = "Server Max TickRate"
def set(self, value: str):
def set(self, value: str) -> None:
super().set(value)
self.inifile.setAddKeyValue("Engine.DemoRecDrive", "NetServerMaxTickRate", value)
self.inifile.writeFile()
self.inifile.setaddkeyvalue("Engine.DemoRecDrive", "NetServerMaxTickRate", value)
self.inifile.writefile()
@GameOptions_Factory_Register
@@ -252,10 +219,10 @@ class GameOption_UT2k4_LanServerMaxTickRate(GameOption_UT2k4):
szDefaultValue = "60"
szHelp = "Lan Server Max TickRate"
def set(self, value: str):
def set(self, value: str) -> None:
super().set(value)
self.inifile.setAddKeyValue("Engine.DemoRecDrive", "LanServerMaxTickRate", value)
self.inifile.writeFile()
self.inifile.setaddkeyvalue("Engine.DemoRecDrive", "LanServerMaxTickRate", value)
self.inifile.writefile()
@GameOptions_Factory_Register
@@ -730,7 +697,7 @@ class GameOption_UT2k4_WebServer(GameOption_UT2k4):
def set(self, value: str) -> None:
super().set(value)
if int(value) > 0:
self.inifile.setAddKeyValue("UWeb.WebServer", "bEnabled", "True")
self.inifile.setaddkeyvalue("UWeb.WebServer", "bEnabled", "True")
else:
self.inifile.setAddKeyValue("UWeb.WebServer", "bEnabled", "False")
self.inifile.writeFile()
self.inifile.setaddkeyvalue("UWeb.WebServer", "bEnabled", "False")
self.inifile.writefile()

View File

@@ -1,74 +1,40 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pyGameCFG(c) by chacha
#
# pyGameCFG is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
# pylint: disable=missing-class-docstring,missing-function-docstring,duplicate-code
"""UT99 command set"""
from __future__ import annotations
from typing import Union
from pysimpleini import PySimpleINI, KeyNotFoundError, SectionNotFoundError, Section, Key
from os.path import join
from pysimpleini import KeyNotFoundError, SectionNotFoundError
from .core import GameOptions_Factory_Register, GameOption, OptionType
from .core_gamecfg import GameOptions_Factory_Register, OptionType
from .tool_ini import PySimpleINI_GroupKeysInSection
from .common_ut import GameOption_UT
class PySimpleINI_UT99(PySimpleINI):
def GroupKeysInSection(self, szSectionName: str, szKeyName: str) -> None:
try:
_section = self.getSection(szSectionName)
if type(_section) is Section:
ar_ServerPackages = _section.getKey(szKeyName)
if isinstance(ar_ServerPackages, Key):
ar_ServerPackages = [ar_ServerPackages]
else: # array
pass
for ServerPackages in ar_ServerPackages:
_section.delKey(ServerPackages.name, None, ServerPackages.value)
for ServerPackages in ar_ServerPackages:
_section.setAddKeyValue(ServerPackages.name, ServerPackages.value, True)
except SectionNotFoundError:
pass
def writeFile(self, bBeautify: bool = False) -> None:
self.GroupKeysInSection("XC_Engine.XC_GameEngine", "ServerPackages")
self.GroupKeysInSection("XC_Engine.XC_GameEngine", "ServerActors")
self.GroupKeysInSection("Engine.GameEngine", "ServerPackages")
self.GroupKeysInSection("Engine.GameEngine", "ServerActors")
self.GroupKeysInSection("Core.System", "Suppress")
self.GroupKeysInSection("Editor.EditorEngine", "EditPackages")
super().writeFile(bBeautify)
class PySimpleINI_UT99(PySimpleINI_GroupKeysInSection):
GroupRules = [
("XC_Engine.XC_GameEngine", "ServerPackages"),
("XC_Engine.XC_GameEngine", "ServerActors"),
("Engine.GameEngine", "ServerPackages"),
("Engine.GameEngine", "ServerActors"),
("Core.System", "Suppress"),
("Editor.EditorEngine", "EditPackages"),
]
class GameOption_UT99(GameOption):
class GameOption_UT99(GameOption_UT):
szGameType = "ut99"
TValueType = OptionType.OT_INVALID
szOptionName = ""
szSectionName = ""
szKeyName = ""
bForceAdd: bool = False
bRemovable: bool = False
def __init__(self, GameRootDir: str, ConfigFileRelPath: str) -> None:
super().__init__(GameRootDir, ConfigFileRelPath)
self.mainConfigFilePath = join(GameRootDir, ConfigFileRelPath)
self.inifile = PySimpleINI_UT99(self.mainConfigFilePath)
def set(self, value: str) -> None:
if not self.szOptionName:
raise RuntimeError("szOptionName is not set")
self.format(value)
self.inifile.setAddKeyValue(self.szSectionName, self.szKeyName, self.szFormatedValue, self.bForceAdd)
self.inifile.writeFile()
def rem(self, value: Union[None, str]) -> None:
if not self.szOptionName:
raise RuntimeError("szOptionName is not set")
if not self.bRemovable:
raise RuntimeError("this options is not removable")
self.inifile.delkey_ex(self.szSectionName, self.szKeyName, None, value)
self.inifile.writeFile()
def get(self) -> Union[None, str]:
if not self.szOptionName:
raise RuntimeError("szOptionName not set")
res = self.inifile.getkeyvalue(self.szSectionName, self.szKeyName)
return res
Cls_PySimpleINI = PySimpleINI_UT99
class GameOption_UT99_GenAdd(GameOption_UT99):
@@ -95,7 +61,7 @@ class GameOption_UT99_GenAdd__Engine(GameOption_UT99_GenAdd):
prev = self.szSectionName
try:
self.inifile.getSection("XC_Engine.XC_GameEngine")
self.inifile.getsection("XC_Engine.XC_GameEngine")
self.szSectionName = "XC_Engine.XC_GameEngine"
super().set(value)
except SectionNotFoundError:
@@ -109,7 +75,7 @@ class GameOption_UT99_GenAdd__Engine(GameOption_UT99_GenAdd):
prev = self.szSectionName
try:
self.inifile.getSection("XC_Engine.XC_GameEngine")
self.inifile.getsection("XC_Engine.XC_GameEngine")
self.szSectionName = "XC_Engine.XC_GameEngine"
super().rem(value)
except SectionNotFoundError:
@@ -174,8 +140,8 @@ class GameOption_UT99_HostName(GameOption_UT99):
def set(self, value: str) -> None:
super().set(value)
self.inifile.setAddKeyValue("Engine.GameReplicationInfo", "ShortName", value)
self.inifile.writeFile()
self.inifile.setaddkeyvalue("Engine.GameReplicationInfo", "ShortName", value)
self.inifile.writefile()
@GameOptions_Factory_Register
@@ -239,8 +205,8 @@ class GameOption_UT99_AdminName(GameOption_UT99):
def set(self, value: str) -> None:
super().set(value)
self.inifile.setAddKeyValue("UTServerAdmin.UTServerAdmin", "AdminUsername", value)
self.inifile.writeFile()
self.inifile.setaddkeyvalue("UTServerAdmin.UTServerAdmin", "AdminUsername", value)
self.inifile.writefile()
@GameOptions_Factory_Register
@@ -254,10 +220,10 @@ class GameOption_UT99_HTTPDownloadServer(GameOption_UT99):
def set(self, value: str) -> None:
super().set(value)
self.inifile.setAddKeyValue("IpDrv.HTTPDownload", "UseCompression", "True")
self.inifile.delKey("IpDrv.HTTPDownload", "ProxyServerHost")
self.inifile.delKey("IpDrv.HTTPDownload", "ProxyServerPort")
self.inifile.writeFile()
self.inifile.setaddkeyvalue("IpDrv.HTTPDownload", "UseCompression", "True")
self.inifile.delkey("IpDrv.HTTPDownload", "ProxyServerHost")
self.inifile.delkey("IpDrv.HTTPDownload", "ProxyServerPort")
self.inifile.writefile()
@GameOptions_Factory_Register
@@ -291,8 +257,8 @@ class GameOption_UT99_NetServerMaxTickRate(GameOption_UT99):
def set(self, value: str) -> None:
super().set(value)
self.inifile.setAddKeyValue("Engine.DemoRecDriver", "NetServerMaxTickRate", value)
self.inifile.writeFile()
self.inifile.setaddkeyvalue("Engine.DemoRecDriver", "NetServerMaxTickRate", value)
self.inifile.writefile()
@GameOptions_Factory_Register
@@ -306,8 +272,8 @@ class GameOption_UT99_LanServerMaxTickRate(GameOption_UT99):
def set(self, value: str) -> None:
super().set(value)
self.inifile.setAddKeyValue("Engine.DemoRecDriver", "LanServerMaxTickRate", value)
self.inifile.writeFile()
self.inifile.setaddkeyvalue("Engine.DemoRecDriver", "LanServerMaxTickRate", value)
self.inifile.writefile()
@GameOptions_Factory_Register
@@ -321,10 +287,10 @@ class GameOption_UT99_AdminPassword(GameOption_UT99):
def set(self, value: str) -> None:
super().set(value)
self.inifile.setAddKeyValue("UTServerAdmin.UTServerAdmin", "AdminPassword", value)
self.inifile.writeFile()
self.inifile.setaddkeyvalue("UTServerAdmin.UTServerAdmin", "AdminPassword", value)
self.inifile.writefile()
def get(self) -> Union[None, str]:
def get(self) -> Union[str, list[str]]:
try:
return super().get()
except KeyNotFoundError:
@@ -458,15 +424,15 @@ class GameOption_UT99_WebServer(GameOption_UT99):
def set(self, value: str) -> None:
# fix ut99 v469c
try:
self.inifile.delKey("UWeb.WebServer", "Listenport")
except:
self.inifile.delkey("UWeb.WebServer", "Listenport")
except KeyNotFoundError:
pass
super().set(value)
if int(value) > 0:
self.inifile.setAddKeyValue("UWeb.WebServer", "bEnabled", "True")
self.inifile.setaddkeyvalue("UWeb.WebServer", "bEnabled", "True")
else:
self.inifile.setAddKeyValue("UWeb.WebServer", "bEnabled", "False")
self.inifile.writeFile()
self.inifile.setaddkeyvalue("UWeb.WebServer", "bEnabled", "False")
self.inifile.writefile()
@GameOptions_Factory_Register

1
src/pygamecfg/py.typed Normal file
View File

@@ -0,0 +1 @@
# PlaceHolder

44
src/pygamecfg/tool_ini.py Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pyGameCFG(c) by chacha
#
# pyGameCFG is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
"""utility module that contain PySimpleINI based helpers"""
from __future__ import annotations
from typing import Union
from pysimpleini import PySimpleINI, SectionNotFoundError, Key, Section
class PySimpleINI_GroupKeysInSection(PySimpleINI):
"""a class base on PySimpleINI that allow user to force some key to be group together in a section"""
GroupRules: list[tuple[str, str]] = []
def groupkeysinsection(self, szSectionName: str, szKeyName: str) -> None:
"""internal function that actually group keys"""
try:
_section: list[Section] = self.getsection(szSectionName)
if len(_section) == 1:
ar_ServerPackages: Union[list[Key], Key] = _section[0].getkey(szKeyName)
if isinstance(ar_ServerPackages, Key):
ar_ServerPackages = [ar_ServerPackages]
else: # array
pass
for ServerPackages in ar_ServerPackages:
_section[0].delkey(ServerPackages.getname(), None, ServerPackages.getvalue())
for ServerPackages in ar_ServerPackages:
_section[0].setaddkeyvalue(ServerPackages.getname(), ServerPackages.getvalue(), True)
except SectionNotFoundError:
pass
def writefile(self, bBeautify: bool = False, bWipeComments: bool = False) -> None:
"""overload of the write function to call the group function before"""
for GroupRule in self.GroupRules:
self.groupkeysinsection(GroupRule[0], GroupRule[1])
super().writefile(bBeautify, bWipeComments)

View File

@@ -0,0 +1,143 @@
sets _Admin "TestAdmin"
sets _Email "TestEmail@domain.com"
sets _Website "www.TestWebSite.com"
sets _Location "TestLocation"
sets _Maps "TestMap"
sets _Gametype "TestGametype"
set sv_authtoken "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
set sv_hostname "TestHostName"
set g_motd "TestMOTD"
set dedicated "2"
set rcon_password "TestRconPwd"
set g_password "TestPwd"
set sv_privateClients "3"
set sv_privatePassword "TestPrivatePassword"
set sv_authorizemode "0"
set sv_showasranked 0
set sv_maxclients "11"
set g_logsync "2"
set logfile "1"
set g_log "games_mp.log"
set sv_log_damage "1"
set sv_statusfile "serverstatus.xml"
set net_port 28960
set sv_minPing "0"
set sv_maxPing "350"
set sv_timeout 40
set sv_connectTimeout 90
set sv_zombieTime 2
set sv_reconnectlimit 5
set sv_allowAnonymous "0"
set g_deadChat "1"
set voice_deadChat "0"
set g_gravity "800"
set sv_disableClientConsole "0"
set scr_teambalance "1"
set scr_team_fftype "0"
set scr_game_spectatetype "2"
set scr_hardcore 0
set scr_oldschool "0"
set g_friendlyPlayerCanBlock 1
set g_FFAPlayerCanBlock 1
set scr_drawfriend "1"
set scr_enable_scoretext "1"
set scr_game_allowkillcam "1"
set scr_game_deathpointloss "0"
set scr_game_suicidepointloss "0"
set scr_game_matchstarttime "5"
set scr_game_playerwaittime "0"
set scr_player_forcerespawn "-1"
set scr_player_healthregentime "5"
set scr_player_maxhealth "100"
set scr_player_sprinttime "4"
set scr_game_onlyheadshots "0"
set scr_teamKillPunishCount "3"
set scr_team_teamkillspawndelay "20"
set scr_team_teamkillpointloss "1"
set scr_enable_hiticon "1"
set scr_dm_scorelimit "1000"
set scr_dm_timelimit "15"
set scr_dm_roundlimit "1"
set scr_dm_numlives "0"
set scr_dm_playerrespawndelay "0"
set scr_dm_waverespawndelay "0"
set scr_war_scorelimit "2000"
set scr_war_timelimit "10"
set scr_war_roundlimit "1"
set scr_war_numlives "0"
set scr_war_playerrespawndelay "0"
set scr_war_waverespawndelay "0"
set scr_dom_scorelimit "250"
set scr_dom_timelimit "0"
set scr_dom_numlives "0"
set scr_dom_playerrespawndelay "0"
set scr_dom_roundlimit "1"
set scr_dom_waverespawndelay "0"
set scr_koth_scorelimit "250"
set scr_koth_timelimit "15"
set koth_kothmode "0"
set koth_capturetime "20"
set koth_spawntime "3"
set scr_koth_numlives "0"
set scr_koth_playerrespawndelay "3"
set scr_koth_roundlimit "1"
set scr_koth_roundswitch "1"
set scr_koth_waverespawndelay "0"
set koth_autodestroytime "60"
set koth_delayPlayer "3"
set koth_destroytime "10"
set koth_spawnDelay "3"
set scr_sab_scorelimit "2"
set scr_sab_timelimit "10"
set scr_sab_roundswitch "1"
set scr_sab_bombtimer "30"
set scr_sab_planttime "2.5"
set scr_sab_defusetime "5"
set scr_sab_hotpotato "0"
set scr_sab_numlives "0"
set scr_sab_playerrespawndelay "0"
set scr_sab_roundlimit "0"
set scr_sab_waverespawndelay "0"
set scr_sd_scorelimit "9"
set scr_sd_timelimit "2.5"
set scr_sd_roundswitch "4"
set scr_sd_bombtimer "45"
set scr_sd_planttime "5"
set scr_sd_defusetime "7"
set scr_sd_multibomb "0"
set scr_sd_numlives "1"
set scr_sd_playerrespawndelay "0"
set scr_sd_roundlimit "0"
set scr_sd_waverespawndelay "0"
set g_gametype "dm"
set sv_mapRotation "gametype dm map mp_backlot gametype dm map mp_bloc gametype dm map mp_bog gametype dm map mp_cargoship gametype dm map mp_citystreets gametype dm map mp_convoy gametype dm map mp_countdown gametype dm map mp_crash gametype dm map mp_crossfire gametype dm map mp_farm gametype dm map mp_overgrown gametype dm map mp_pipeline gametype dm map mp_shipment gametype dm map mp_showdown gametype dm map mp_strike gametype dm map mp_vacant"
set sv_allowdownload "1"
set sv_wwwDownload "1"
set sv_wwwBaseURL "http://TestRedirect/"
set sv_wwwDlDisconnected "0"
set sv_nosteamnames 1
map_rotate

68
test/test_cod4.py Normal file
View File

@@ -0,0 +1,68 @@
# pygamecfg (c) by chacha
#
# pygamecfg is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
import unittest
from os import linesep, path, chdir
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
from pathlib import Path
import glob
import shutil
from src import pygamecfg
from src.pygamecfg.__main__ import fct_main
testdir_path = Path(__file__).parent.resolve()
chdir(testdir_path.parent.resolve())
class Testtest_cod4(unittest.TestCase):
def tearDown(self) -> None:
self.CleanTmp()
def setUp(self) -> None:
self.CleanTmp()
def CleanTmp(self):
# remove any file in tmp dir, except .keep
if path.exists(testdir_path / "tmp"):
shutil.rmtree(testdir_path / "tmp")
shutil.copytree(testdir_path / "data", testdir_path / "tmp")
print("======================")
def test_normal_READ_sv_maxclients(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "cod4", "-b", "test/tmp/COD4", "GetOption", "maxclients"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("11\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_READ_sv_mapRotation(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "cod4", "-b", "test/tmp/COD4", "GetOption", "mapRotation"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual(
"gametype dm map mp_backlot gametype dm map mp_bloc gametype dm map mp_bog gametype dm map mp_cargoship gametype dm map mp_citystreets gametype dm map mp_convoy gametype dm map mp_countdown gametype dm map mp_crash gametype dm map mp_crossfire gametype dm map mp_farm gametype dm map mp_overgrown gametype dm map mp_pipeline gametype dm map mp_shipment gametype dm map mp_showdown gametype dm map mp_strike gametype dm map mp_vacant\n",
capted_stdout.getvalue(),
)
self.assertEqual("", capted_stderr.getvalue())
def test_normal_WRITE_sv_maxclients(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "cod4", "-b", "test/tmp/COD4", "SetOption", "maxclients", "17"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("setting option <maxclients> to: 17\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "cod4", "-b", "test/tmp/COD4", "GetOption", "maxclients"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("17\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
# check if other key still there / untouched
self.test_normal_READ_sv_mapRotation()

View File

@@ -10,9 +10,6 @@ import unittest
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
print(__name__)
print(__package__)
from src import pygamecfg
from src.pygamecfg.__main__ import fct_main

View File

@@ -7,21 +7,37 @@
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
import unittest
from os import linesep
from os import linesep, path, chdir
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
print(__name__)
print(__package__)
from pathlib import Path
import glob
import shutil
from src import pygamecfg
from src.pygamecfg.__main__ import fct_main
testdir_path = Path(__file__).parent.resolve()
chdir(testdir_path.parent.resolve())
class Testtest_ut99(unittest.TestCase):
def tearDown(self) -> None:
self.CleanTmp()
def setUp(self) -> None:
self.CleanTmp()
shutil.copytree(testdir_path / "data", testdir_path / "tmp")
print("======================")
def CleanTmp(self):
# remove any file in tmp dir, except .keep
if path.exists(testdir_path / "tmp"):
shutil.rmtree(testdir_path / "tmp")
def test_normal_ServerPackages(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "ServerPackages"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "ServerPackages"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual(
"['SoldierSkins', 'CommandoSkins', 'FCommandoSkins', 'SGirlSkins', 'BossSkins', 'Botpack']\n", capted_stdout.getvalue()
@@ -30,7 +46,7 @@ class Testtest_ut99(unittest.TestCase):
def test_normal_ServerActors(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "ServerActors"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "ServerActors"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual(
"['IpDrv.UdpBeacon', 'IpServer.UdpServerQuery', 'IpServer.UdpServerUplink MasterServerAddress=unreal.epicgames.com MasterServerPort=27900', 'IpServer.UdpServerUplink MasterServerAddress=master0.gamespy.com MasterServerPort=27900', 'IpServer.UdpServerUplink MasterServerAddress=master.mplayer.com MasterServerPort=27900', 'UWeb.WebServer']\n",
@@ -40,273 +56,273 @@ class Testtest_ut99(unittest.TestCase):
def test_normal_Port(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "Port"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "Port"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("7777\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_Map(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "Map"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "Map"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("Index.unr\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_GameType(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "GameType"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "GameType"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("Botpack.DeathMatchPlus\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_HostName(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "HostName"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "HostName"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("Test Server Name FULL\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_MOTD(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MOTD"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "MOTD"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("TestMOTDLine1\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_MOTD2(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MOTD2"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "MOTD2"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("TestMOTDLine2\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_MOTD3(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MOTD3"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "MOTD3"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("TestMOTDLine3\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_MOTD4(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MOTD4"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "MOTD4"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("TestMOTDLine4\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_AdminEmail(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AdminEmail"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "AdminEmail"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("TestAdminName@test.com\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_AdminName(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AdminName"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "AdminName"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("TestAdminName\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_HTTPDownloadServer(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "HTTPDownloadServer"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "HTTPDownloadServer"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("http://uz.ut-files.com/\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_MaxClientRate(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MaxClientRate"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "MaxClientRate"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("20000\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_NetServerMaxTickRate(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "NetServerMaxTickRate"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "NetServerMaxTickRate"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("20\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_LanServerMaxTickRate(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "LanServerMaxTickRate"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "LanServerMaxTickRate"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("35\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_AdminPassword(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AdminPassword"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "AdminPassword"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("TestAdminPwd\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_GamePassword(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "GamePassword"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "GamePassword"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("TestPwd\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_MaxPlayers(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MaxPlayers"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "MaxPlayers"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("4\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_MaxSpectators(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MaxSpectators"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "MaxSpectators"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("1\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_AS_TimeLimit(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AS_TimeLimit"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "AS_TimeLimit"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("123\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_DOM_TimeLimit(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DOM_TimeLimit"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "DOM_TimeLimit"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("423\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_CTF_TimeLimit(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "CTF_TimeLimit"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "CTF_TimeLimit"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("223\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_DM_TimeLimit(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DM_TimeLimit"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "DM_TimeLimit"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("323\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_GoalTeamScore(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "GoalTeamScore"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "GoalTeamScore"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("30\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_MaxTeamSize(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MaxTeamSize"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "MaxTeamSize"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("4\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_DM_FragLimit(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DM_FragLimit"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "DM_FragLimit"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("321\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_ServerLogName(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "ServerLogName"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "ServerLogName"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("server.log\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_WebServer(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "WebServer"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "WebServer"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("9999\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_TournamentMode(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "TournamentMode"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "TournamentMode"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("False\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_InitialBots(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "InitialBots"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "InitialBots"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("7\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_MinPlayers(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MinPlayers"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "MinPlayers"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("11\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_AS_UseTranslocator(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AS_UseTranslocator"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "AS_UseTranslocator"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("True\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_CTF_UseTranslocator(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "CTF_UseTranslocator"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "CTF_UseTranslocator"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("True\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_DM_UseTranslocator(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DM_UseTranslocator"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "DM_UseTranslocator"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("False\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_DOM_UseTranslocator(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DOM_UseTranslocator"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "DOM_UseTranslocator"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("True\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_CTF_ForceRespawn(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "CTF_ForceRespawn"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "CTF_ForceRespawn"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("False\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_DM_ForceRespawn(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DM_ForceRespawn"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "DM_ForceRespawn"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("True\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_DOM_ForceRespawn(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DOM_ForceRespawn"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "DOM_ForceRespawn"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("False\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_AS_ForceRespawn(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AS_ForceRespawn"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "GetOption", "AS_ForceRespawn"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("True\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_customcfgfile_HostName(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "ut99", "-b", "test/data/UT99", "-c", "System/UT99.ini", "GetOption", "HostName"])
fct_main(["-g", "ut99", "-b", "test/tmp/UT99", "-c", "System/UT99.ini", "GetOption", "HostName"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("Alt Serv Name FULL\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())