Compare commits

...

42 Commits

Author SHA1 Message Date
b561ac2577 Merge pull request 'Update Jenkinsfile' (#13) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/13
new-tag:0.4.3
2024-10-12 17:33:01 +02:00
7bab9f4a6b Update Jenkinsfile 2024-10-12 16:42:10 +02:00
23a4b3794d Merge pull request 'dev' (#12) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/12
new-tag:0.4.2
2023-11-06 16:16:44 +01:00
cclecle
d80ff553df update from last project template 2023-11-06 15:09:07 +00:00
cclecle
88d06544c6 split quality & types .launch scripts 2023-11-06 15:03:50 +00:00
ea7e642460 Merge pull request 'fix: add missing import' (#11) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/11
new-tag:0.4.1
2023-10-01 00:41:23 +02:00
cclecle
1db94a6c6b fix: add missing import 2023-09-30 23:31:11 +01:00
46f62367f5 Merge pull request 'feat: add cod4 ProMod support' (#10) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/10
new-tag:0.4.0
2023-09-30 23:58:40 +02:00
cclecle
439333ed59 feat: add cod4 ProMod support
feat: add cod4 punkbuster + other config keys support
2023-09-30 22:52:34 +01:00
a249968a9d Merge pull request 'fix: cod4 bool again' (#9) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/9
new-tag:0.3.6
2023-09-30 20:49:28 +02:00
cclecle
7d8d87b178 fix: cod4 bool again 2023-09-30 19:44:04 +01:00
0c59a108f0 Merge pull request 'fix cod4 boolean handling' (#8) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/8
new-tag:0.3.5
2023-09-30 14:37:51 +02:00
cclecle
45d132c5ca fix cod4 boolean handling 2023-09-30 13:16:40 +01:00
0725782101 Merge pull request 'dev' (#7) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/7
new-tag:0.3.4
2023-09-30 02:04:09 +02:00
cclecle
cd3a937423 chore: remove useless data dir from setuptools 2023-09-30 00:19:57 +01:00
cclecle
5fdabae600 fix: switch to pypi version of chacha-cicd-helper 2023-09-29 23:59:11 +01:00
679db81ca6 Merge pull request 'dev' (#6) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/6
new-tag:0.3.3
2023-09-29 23:58:06 +02:00
cclecle
d0cdad663d chore: switch from local helpers to common library 2023-09-29 22:51:50 +01:00
cclecle
2b918837fd ignore tmp unittest files from git 2023-09-28 23:38:05 +01:00
cclecle
b78cbae422 fix: correct the code to add non existing keys in COD4 cfg files 2023-09-28 23:34:54 +01:00
639632b022 Merge pull request 'dev' (#5) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/5
new-tag:0.3.2
2023-09-28 09:50:19 +02:00
cclecle
8797bd3ee1 chore: add missing cod4 configuration keys (numlives,
playerrespawndelay, waverespawndelay)
2023-09-28 08:45:18 +01:00
cclecle
6bfbc85758 chore: fix code quality issues 2023-09-27 22:36:34 +01:00
bce49c5f70 Merge pull request 'chore: make all unittest same' (#4) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/4
new-tag:0.3.1
2023-09-27 20:29:14 +02:00
cclecle
aa4e816bd2 chore: make all unittest same 2023-09-27 19:23:56 +01:00
d6ebb953cf Merge pull request 'dev' (#3) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/3
new-tag: 0.3.0
2023-09-27 20:14:15 +02:00
cclecle
56badcbcf7 chore: complete gungame option list 2023-09-27 19:04:43 +01:00
cclecle
43721835be feat: add cod4_gungame config support 2023-09-27 14:44:37 +01:00
f72833e6c1 Merge pull request 'fix format' (#2) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/2
new-tag:0.2.0
2023-09-25 00:24:04 +02:00
cclecle
29ec82d9ec fix format
implement most used cod4 config keys
complete unittest
2023-09-24 23:18:23 +01:00
085abb31f8 Merge pull request 'dev' (#1) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pygamecfg/pulls/1
new-tag:0.1.0
2023-09-24 20:16:00 +02: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
cclecle
5c2b08c865 test: fix unittest framework + add ut99 and gen unittests 2023-09-23 15:44:31 +01:00
43 changed files with 3404 additions and 1298 deletions

3
.gitignore vendored
View File

@@ -42,4 +42,5 @@ helpers-results
.coverage
/.mypy_cache/
.coverage
.mypy_cache
.mypy_cache
test/tmp

View File

@@ -1,3 +1,12 @@
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_cod4_promod.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

71
Jenkinsfile vendored
View File

@@ -27,7 +27,7 @@ def _bDraft = false
// release content / changelog management
def _bAutoChangelog = true //Not supported yet
def _ReleaseContent_Title = "# _CI/CD Automatic Release_"
def bPushMasterOnPypi = false
def bPushMasterOnPypi = true
// full rebuild toogle
def _bFullRebuilt = true
def _MkDocsWebURL = "dabauto--mkdocs-web.dmz.chacha.home/mkdocs-web/"
@@ -183,8 +183,12 @@ pipeline {
sh("virtualenv --pip=embed --setuptools=embed --wheel=embed --no-periodic-update --activators bash,python TEST_ENV")
sh("virtualenv --pip=embed --setuptools=embed --wheel=embed --no-periodic-update --activators bash,python TOOLS_ENV")
sh(". ~/BUILD_ENV/bin/activate && pip install --upgrade setuptools build pip copier jinja2-slug toml")
sh(". ~/BUILD_ENV/bin/activate && pip install --upgrade setuptools build pip")
sh(". ~/BUILD_ENV/bin/activate && pip install --upgrade 'copier==9.*' jinja2-slug toml")
sh(". ~/TEST_ENV/bin/activate && pip install --upgrade pip")
sh(". ~/TOOLS_ENV/bin/activate && pip install --upgrade pip")
sh(". ~/TOOLS_ENV/bin/activate && pip install simple_rest_client requests twine packaging")
script {
@@ -341,7 +345,7 @@ pipeline {
|'''.strip()
|
|import copier
|copier.run_copy("./", "../_gitrepo",vcs_ref="HEAD",use_prereleases=True,defaults=True,cleanup_on_error=False)
|copier.run_copy("./", "../_gitrepo",vcs_ref="HEAD",use_prereleases=True,defaults=True,cleanup_on_error=False,unsafe=True)
|
|__EOWRAPPER__
""".stripMargin())
@@ -409,31 +413,39 @@ pipeline {
stage("CheckCode") {
steps {
dir("gitrepo") {
sh(". ~/TEST_ENV/bin/activate && python -m helpers --type-check --quality-check")
sh(". ~/TEST_ENV/bin/activate && python -m chacha_cicd_helper --typecheck --qualitycheck")
script {
def jsonObj = readJSON file: "helpers-results/quality_check/metrics.json"
def jsonObj = readJSON file: "helpers-results/cl_quality_check/metrics.json"
quality_score = new BigDecimal(jsonObj["GlobalScore"])
sz_quality_score = quality_score.setScale(2, RoundingMode.HALF_EVEN).toString()
badge_quality.setStatus(sz_quality_score)
badge_quality.setColor(getColorScale(quality_score))
}
sh(". ~/TEST_ENV/bin/activate && python -m helpers --complexity-check")
sh(". ~/TEST_ENV/bin/activate && python -m chacha_cicd_helper --complexitycheck")
}
}
post {
always {
dir("gitrepo") {
publishCoverage adapters: [cobertura(mergeToOneReport: true, path: "helpers-results/types_check/cobertura.xml")]
junit 'helpers-results/types_check/junit.xml'
dir("gitrepo") {
//publish coverage
recordCoverage( sourceDirectories: [[path: 'src']],
tools: [[parser: 'COBERTURA', pattern: 'helpers-results/cl_types_check/cobertura.xml']],
id: 'COBERTURA', name: 'COBERTURA Coverage',
sourceCodeRetention: 'EVERY_BUILD',)
//add type check to junit result set
junit 'helpers-results/cl_types_check/junit.xml'
//publish html reports files
publishHTML([
reportDir: "helpers-results/quality_check",
reportDir: "helpers-results/cl_quality_check",
reportFiles: "report.html",
reportName: "quality-report",
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true])
publishHTML([
reportDir: "helpers-results/types_check",
reportDir: "helpers-results/cl_types_check",
reportFiles: "index.html",
reportName: "types_check",
allowMissing: false,
@@ -447,7 +459,7 @@ pipeline {
steps {
plot([ csvFileName: 'plot-df7f03dc-8146-11ed-a1eb-0242ac120002.csv',
csvSeries: [[ file: 'gitrepo/helpers-results/quality_check/metrics_GlobalScore.csv', inclusionFlag: 'OFF', url: '']],
csvSeries: [[ file: 'gitrepo/helpers-results/cl_quality_check/metrics_GlobalScore.csv', inclusionFlag: 'OFF', url: '']],
group: 'metrics',
title: 'code quality score',
style: 'line',
@@ -457,7 +469,7 @@ pipeline {
yaxisMinimum: '0'])
plot([ csvFileName: 'plot-c731cc84-8145-11ed-a1eb-0242ac120002.csv',
csvSeries: [[ file: 'gitrepo/helpers-results/quality_check/metrics_rawpercent.csv', inclusionFlag: 'OFF', url: '']],
csvSeries: [[ file: 'gitrepo/helpers-results/cl_quality_check/metrics_rawpercent.csv', inclusionFlag: 'OFF', url: '']],
group: 'metrics',
title: 'code composition (%)',
style: 'stackedArea',
@@ -467,7 +479,7 @@ pipeline {
yaxisMinimum: '0'])
plot([ csvFileName: 'plot-cac33982-8145-11ed-a1eb-0242ac120002.csv',
csvSeries: [[ file: 'gitrepo/helpers-results/quality_check/metrics_Statistics.csv', inclusionFlag: 'OFF', url: '']],
csvSeries: [[ file: 'gitrepo/helpers-results/cl_quality_check/metrics_Statistics.csv', inclusionFlag: 'OFF', url: '']],
group: 'metrics',
title: 'general statistics',
style: 'line',
@@ -475,7 +487,7 @@ pipeline {
numBuilds: ''])
plot([ csvFileName: 'plot-cddaced2-8145-11ed-a1eb-0242ac120002.csv',
csvSeries: [[ file: 'gitrepo/helpers-results/quality_check/metrics_MessagesCat.csv', inclusionFlag: 'OFF', url: '']],
csvSeries: [[ file: 'gitrepo/helpers-results/cl_quality_check/metrics_MessagesCat.csv', inclusionFlag: 'OFF', url: '']],
group: 'metrics',
title: 'quality warnings',
style: 'stackedArea',
@@ -483,7 +495,7 @@ pipeline {
numBuilds: ''])
plot([ csvFileName: 'plot-4ceb9ee2-ca78-11ed-afa1-0242ac120002.csv',
csvSeries: [[ file: 'gitrepo/helpers-results/complexity_check/MI.csv', inclusionFlag: 'INCLUDE_BY_STRING',exclusionValues: 'MeanMaintainability', url: '']],
csvSeries: [[ file: 'gitrepo/helpers-results/cl_complexity_check/MI.csv', inclusionFlag: 'INCLUDE_BY_STRING',exclusionValues: 'MeanMaintainability', url: '']],
group: 'metrics',
title: 'maintainability',
style: 'stackedArea',
@@ -495,14 +507,14 @@ pipeline {
stage("RunUnitTests") {
steps {
dir("gitrepo") {
sh(". ~/TEST_ENV/bin/activate && python -m helpers --unit-test --coverage-check")
sh(". ~/TEST_ENV/bin/activate && python -m chacha_cicd_helper --unittest --coveragecheck")
script {
unit_test_full_name__html=findFiles(glob: "helpers-results/unit_test_full/*.html")[0].getName()
unit_test_full_name__html=findFiles(glob: "helpers-results/cl_unit_test_full/*.html")[0].getName()
println unit_test_full_name__html
unit_test_full_name__xml=findFiles(glob: "helpers-results/unit_test_full/*.xml")[0].getName()
unit_test_full_name__xml=findFiles(glob: "helpers-results/cl_unit_test_full/*.xml")[0].getName()
println unit_test_full_name__xml
coverage_report_path = "helpers-results/unit_test_coverage/test_coverage.xml"
coverage_report_path = "helpers-results/cl_unit_test_coverage/test_coverage.xml"
println GetCoverageValue_lines_valid(coverage_report_path)
println GetCoverageValue_lines_covered(coverage_report_path)
println GetCoverageValue_line_rate(coverage_report_path)
@@ -517,7 +529,7 @@ pipeline {
badge_coverage.setColor(getColorScale(full_rate))
//badge_maintainability
records = readCSV file: 'helpers-results/complexity_check/MI.csv'
records = readCSV file: 'helpers-results/cl_complexity_check/MI.csv'
maintainability = records[1][1]
badge_maintainability.setStatus(maintainability)
@@ -532,11 +544,11 @@ pipeline {
post {
always {
dir("gitrepo") {
junit 'helpers-results/unit_test/*.xml'
junit 'helpers-results/cl_unit_test/*.xml'
// using cobertura format (= coverage xml format)
publishCoverage adapters: [cobertura(mergeToOneReport: true, path: "helpers-results/unit_test_coverage/test_coverage.xml")]
recordCoverage(tools: [[parser: 'COBERTURA', pattern: 'helpers-results/cl_unit_test_coverage/test_coverage.xml']])
publishHTML([
reportDir: "helpers-results/unit_test_coverage",
reportDir: "helpers-results/cl_unit_test_coverage",
reportFiles: "index.html",
reportName: "coverage-report-html",
allowMissing: false,
@@ -544,7 +556,7 @@ pipeline {
keepAll: true])
publishHTML([
reportDir: "helpers-results/unit_test_full",
reportDir: "helpers-results/cl_unit_test_full",
reportFiles: unit_test_full_name__html,
reportName: "test-reports-full",
allowMissing: false,
@@ -558,15 +570,14 @@ pipeline {
stage("GenDOC") {
steps {
dir("gitrepo") {
//--doc-gen-pdf
sh(". ~/TEST_ENV/bin/activate && python -m helpers --doc-gen --doc-gen-pdf")
sh(". ~/TEST_ENV/bin/activate && python -m chacha_cicd_helper --docgen --docgenpdf")
}
}
post {
always {
dir("gitrepo") {
publishHTML([
reportDir: "helpers-results/doc_gen/site",
reportDir: "helpers-results/cl_doc_gen/site",
reportFiles: "index.html",
reportName: "doc-html",
allowMissing: false,
@@ -671,11 +682,11 @@ pipeline {
|
|data = {
| "name": "Documentation (pdf)",
| 'attachment': ("${PY_PROJECT_NAME}_${PY_PROJECT_VERSION}_UserManual.pdf", open("helpers-results/doc_gen/site/pdf/manual.pdf", 'rb')),
| 'attachment': ("${PY_PROJECT_NAME}_${PY_PROJECT_VERSION}_UserManual.pdf", open("helpers-results/cl_doc_gen/site/pdf/manual.pdf", 'rb')),
|}
|GiteaApi.assets.post("${_PROJECT_USER_NAME}","${PY_PROJECT_NAME}",new_release_id,files=data)
|
|shutil.make_archive("doc", 'zip', "helpers-results/doc_gen/site")
|shutil.make_archive("doc", 'zip', "helpers-results/cl_doc_gen/site")
|reqData={
| "SECRET": "${MKDOCSTOKEN}",
| "USER": "${_PROJECT_USER_NAME}",

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

@@ -2,14 +2,15 @@
<launchConfiguration type="org.python.pydev.debug.regularLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.core.ATTR_FORCE_SYSTEM_CONSOLE_ENCODING" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/${project_name}/helpers"/>
<listEntry value="/pygamecfg/helpers_proxy"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${project_loc}/helpers"/>
<stringAttribute key="org.eclipse.debug.ui.ATTR_CONSOLE_ENCODING" value="UTF-8"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:pygamecfg/helpers_proxy}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_OTHER_WORKING_DIRECTORY" value=""/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--complexity-check"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--complexitycheck"/>
<stringAttribute key="org.python.pydev.debug.ATTR_INTERPRETER" value="__default"/>
<stringAttribute key="org.python.pydev.debug.ATTR_PROJECT" value="pygamecfg"/>
<stringAttribute key="process_factory_id" value="org.python.pydev.debug.processfactory.PyProcessFactory"/>

View File

@@ -2,17 +2,15 @@
<launchConfiguration type="org.python.pydev.debug.regularLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.core.ATTR_FORCE_SYSTEM_CONSOLE_ENCODING" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/${project_name}/helpers"/>
<listEntry value="/pygamecfg/helpers_proxy"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.environmentVariables">
<mapEntry key="PATH" value="C:\Program Files\GTK3-Runtime Win64\bin"/>
</mapAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${project_loc}/helpers"/>
<stringAttribute key="org.eclipse.debug.ui.ATTR_CONSOLE_ENCODING" value="UTF-8"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:pygamecfg/helpers_proxy}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_OTHER_WORKING_DIRECTORY" value=""/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--doc-gen --doc-gen-pdf"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--docgen --docgenpdf"/>
<stringAttribute key="org.python.pydev.debug.ATTR_INTERPRETER" value="__default"/>
<stringAttribute key="org.python.pydev.debug.ATTR_PROJECT" value="pygamecfg"/>
<stringAttribute key="process_factory_id" value="org.python.pydev.debug.processfactory.PyProcessFactory"/>

View File

@@ -2,14 +2,15 @@
<launchConfiguration type="org.python.pydev.debug.regularLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.core.ATTR_FORCE_SYSTEM_CONSOLE_ENCODING" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/${project_name}/helpers"/>
<listEntry value="/pygamecfg/helpers_proxy"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${project_loc}/helpers"/>
<stringAttribute key="org.eclipse.debug.ui.ATTR_CONSOLE_ENCODING" value="UTF-8"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:pygamecfg/helpers_proxy}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_OTHER_WORKING_DIRECTORY" value=""/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--type-check --quality-check"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--qualitycheck"/>
<stringAttribute key="org.python.pydev.debug.ATTR_INTERPRETER" value="__default"/>
<stringAttribute key="org.python.pydev.debug.ATTR_PROJECT" value="pygamecfg"/>
<stringAttribute key="process_factory_id" value="org.python.pydev.debug.processfactory.PyProcessFactory"/>

View File

@@ -2,14 +2,15 @@
<launchConfiguration type="org.python.pydev.debug.regularLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.core.ATTR_FORCE_SYSTEM_CONSOLE_ENCODING" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/${project_name}/helpers"/>
<listEntry value="/pygamecfg/helpers_proxy"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${project_loc}/helpers"/>
<stringAttribute key="org.eclipse.debug.ui.ATTR_CONSOLE_ENCODING" value="UTF-8"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:pygamecfg/helpers_proxy}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_OTHER_WORKING_DIRECTORY" value=""/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--changelog-gen"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--typecheck"/>
<stringAttribute key="org.python.pydev.debug.ATTR_INTERPRETER" value="__default"/>
<stringAttribute key="org.python.pydev.debug.ATTR_PROJECT" value="pygamecfg"/>
<stringAttribute key="process_factory_id" value="org.python.pydev.debug.processfactory.PyProcessFactory"/>

View File

@@ -2,14 +2,15 @@
<launchConfiguration type="org.python.pydev.debug.regularLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.core.ATTR_FORCE_SYSTEM_CONSOLE_ENCODING" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/${project_name}/helpers"/>
<listEntry value="/pygamecfg/helpers_proxy"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${project_loc}/helpers"/>
<stringAttribute key="org.eclipse.debug.ui.ATTR_CONSOLE_ENCODING" value="UTF-8"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:pygamecfg/helpers_proxy}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_OTHER_WORKING_DIRECTORY" value=""/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--unit-test --coverage-check"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="--unittest --coveragecheck"/>
<stringAttribute key="org.python.pydev.debug.ATTR_INTERPRETER" value="__default"/>
<stringAttribute key="org.python.pydev.debug.ATTR_PROJECT" value="pygamecfg"/>
<stringAttribute key="process_factory_id" value="org.python.pydev.debug.processfactory.PyProcessFactory"/>

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.

1
helpers/.gitignore vendored
View File

@@ -1 +0,0 @@
/.mypy_cache/

View File

@@ -1,7 +0,0 @@
# 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/>.

View File

@@ -1,108 +0,0 @@
# 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/>.
from __future__ import annotations
from typing import TYPE_CHECKING
from pathlib import Path
import tomli
import argparse
import os
import logging
import sys
if __package__ == "helpers":
# when calling the module from: > python -m helpers
from .types_check import types_check
from .quality_check import quality_check
from .unit_test import unit_test
from .doc_gen import doc_gen
from .complexity_check import complexity_check
else:
# when calling the __main__.py file (from IDE)
from helpers.types_check import types_check
from helpers.quality_check import quality_check
from helpers.unit_test import unit_test
from helpers.doc_gen import doc_gen
from helpers.complexity_check import complexity_check
logging.getLogger().setLevel(logging.INFO)
if __name__ == "__main__":
project_rootdir_path = Path(__file__).parent.parent.absolute()
with open(project_rootdir_path / "pyproject.toml", mode="rb") as fp:
pyproject = tomli.load(fp)
parser = argparse.ArgumentParser(
prog="continuous-integration-helper", description="A tiny set of scripts to help continous integration on python"
)
parser.add_argument("-tc", "--type-check", dest="typecheck", action="store_true", help="enable static typing check")
parser.add_argument("-ut", "--unit-test", dest="unittest", action="store_true", help="enable unit-test")
parser.add_argument(
"-cc", "--coverage-check", dest="coveragecheck", action="store_true", help="enable unit-test coverage check (requires unit-test)"
)
parser.add_argument("-qc", "--quality-check", dest="qualitycheck", action="store_true", help="enable code quality check")
parser.add_argument("-dg", "--doc-gen", dest="docgen", action="store_true", help="enable documentation generation using MkDoc")
parser.add_argument(
"-pdf", "--doc-gen-pdf", dest="docgenpdf", action="store_true", help="enable pdf documentation export (requires doc-gen)"
)
parser.add_argument("-cpc", "--complexity-check", dest="complexitycheck", action="store_true", help="enable complexity check")
args = parser.parse_args()
##################################
# Dev / Debug forced toogles
#
# --------------------------------
#
# args.typecheck = True
# args.qualitycheck = True
# args.unittest = True
# args.coveragecheck = True
# args.docgen = True
# args.docgenpdf = True
# args.complexitycheck = True
helpers = []
if args.typecheck == True:
helpers.append(types_check)
if args.unittest == True:
helpers.append(unit_test)
if args.coveragecheck == True:
if args.unittest == True:
unit_test.enable_coverage_check = True
else:
raise RuntimeError("unit-test is required to enable coverage-check")
if args.qualitycheck == True:
helpers.append(quality_check)
if args.docgen == True:
helpers.append(doc_gen)
if args.docgenpdf == True:
if args.docgen == True:
doc_gen.enable_gen_pdf = True
else:
raise RuntimeError("doc-gen is required to enable doc-gen-pdf")
if args.complexitycheck == True:
helpers.append(complexity_check)
for helper in helpers:
helper.set_context(project_rootdir_path, pyproject)
helper.reset_result_dir()
helper.do_job()

View File

@@ -1,70 +0,0 @@
# 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/>.
from __future__ import annotations
from typing import TYPE_CHECKING
# from pathlib import Path
# import os
import statistics
import csv
from json import loads as JSON_LOADS
from radon.complexity import cc_rank, SCORE
from radon.cli import Config
from radon.cli.harvest import CCHarvester, HCHarvester, MIHarvester
from .helper_base import helper_withresults_base
from pprint import pprint
class complexity_check(helper_withresults_base):
@classmethod
def do_job(cls):
config = Config(
exclude="__init__\.py",
ignore=None,
order=SCORE,
show_closures=False,
no_assert=True,
min="A",
max="F",
multi=False,
)
h = MIHarvester([str(_) for _ in sorted((cls.project_rootdir_path / "src").rglob("*.py"))], config).as_json()
res = JSON_LOADS(h)
with open(cls.get_result_dir() / "MI.json", "w", newline="") as oFile:
oFile.write(h)
mean = statistics.mean(_["mi"] for _ in res.values())
if mean >= 65:
rank = "A+"
elif mean >= 20:
rank = "A"
elif mean >= 10:
rank = "B"
else:
rank = "C"
RES_MI = {"MeanMaintainability": mean, "MaintainabilityIndex": rank}
with open(cls.get_result_dir() / "MI.csv", "w", newline="") as oFile:
writer = csv.DictWriter(oFile, fieldnames=RES_MI.keys())
writer.writeheader()
writer.writerow(RES_MI)
config = Config(exclude=None, ignore=None, order=SCORE, show_closures=False, no_assert=True, min="A", max="F", multi=False)
h = CCHarvester([str(_) for _ in sorted((cls.project_rootdir_path / "src").rglob("*.py"))], config).as_json()
with open(cls.get_result_dir() / "CC.json", "w", newline="") as oFile:
oFile.write(h)
config = Config(exclude=None, ignore=None, order=SCORE, show_closures=False, no_assert=True, min="A", max="F", by_function=None)
h = HCHarvester([str(_) for _ in sorted((cls.project_rootdir_path / "src").rglob("*.py"))], config).as_json()
with open(cls.get_result_dir() / "HC.json", "w", newline="") as oFile:
oFile.write(h)

View File

@@ -1,98 +0,0 @@
# 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/>.
from __future__ import annotations
from typing import TYPE_CHECKING
import shutil
import os
import sys
import subprocess
from pathlib import Path
from distutils.dir_util import copy_tree
import yaml
try:
from yaml import CLoader as Loader, CDumper as Dumper
except ImportError:
from yaml import Loader, Dumper
from .helper_base import helper_withresults_base
class doc_gen(helper_withresults_base):
enable_gen_pdf: bool = False
@classmethod
def do_job(cls):
# create doc root dir
doc_path = cls.project_rootdir_path / "docs"
cls._reset_dir(doc_path)
site_path = cls.get_result_dir() / "site"
cls._reset_dir(site_path)
# copy files from main project dir
shutil.copyfile(str(cls.project_rootdir_path / "README.md"), str(doc_path / "README.md"))
shutil.copyfile(str(cls.project_rootdir_path / "LICENSE.md"), str(doc_path / "LICENSE.md"))
# copy files from static-doc dir
copy_tree(str(cls.project_rootdir_path / "docs-static"), str(doc_path))
# generating API doc + nav from python docstrings
reference_path = doc_path / "reference"
cls._reset_dir(reference_path)
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")
full_doc_path = Path(reference_path, doc_path)
parts = list(module_path.parts)
if parts[-1] == "__init__":
parts = parts[:-1]
elif parts[-1] == "__main__":
continue
cls._reset_dir(os.path.dirname(full_doc_path))
with open(full_doc_path, "w+") as fd:
identifier = "src." + ".".join(parts)
print("::: " + identifier, file=fd)
cmdopts = [f"{sys.executable}", "-m", "mkdocs", "-v", "build", "--site-dir", str(site_path), "--clean"]
# little hack here, to enable / disable pdf generation using own class config
# => reason is mkdocs seems to try loading the plugin even if we disable it, so we need to
# manually process the configuration file.
with open(cls.project_rootdir_path / "mkdocs.yml", "r") as mkdocsCfgFile:
mkdocsCfg = yaml.load(mkdocsCfgFile, Loader=yaml.Loader)
if "plugins" in mkdocsCfg:
mkdocsCfg["plugins"] = [_ for _ in mkdocsCfg["plugins"] if (not isinstance(_, dict) or "with-pdf" not in _.keys())]
if cls.enable_gen_pdf == True:
mkdocsCfg["plugins"].append(
{
"with-pdf": {
"cover_subtitle": "User Manual",
"cover_logo": str(cls.project_rootdir_path / "docs-static" / "Library.jpg"),
"verbose": False,
"exclude_pages": ["LICENSE"],
"output_path": str(site_path / "pdf" / "manual.pdf"),
}
}
)
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))
res = cls.run_cmd(cmdopts)
print(res.decode())
print(" !! done")

View File

@@ -1,75 +0,0 @@
# 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/>.
from __future__ import annotations
from typing import TYPE_CHECKING
from abc import ABC, abstractmethod
import os
from pathlib import Path
import subprocess
if TYPE_CHECKING: # Only imports the below statements during type checking
from typing import Union
class helper_base(ABC):
project_rootdir_path: Union[Path, None] = None
pyproject: Union[dict, None] = None
current_dir: Union[Path, None] = None
@classmethod
def set_context(cls, project_rootdir_path: Path, pyproject: dict):
cls.project_rootdir_path = project_rootdir_path
cls.pyproject = pyproject
cls.current_dir = Path(__file__).parent.absolute()
@classmethod
def get_result_dir(cls):
return None
@staticmethod
def _reset_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()]
@classmethod
def reset_result_dir(cls):
result_dir = cls.get_result_dir()
if result_dir != None:
cls._reset_dir(result_dir)
@classmethod
@abstractmethod
def do_job(cls):
raise NotImplementedError()
@classmethod
def run_cmd_(cls, cmdarray):
process = subprocess.run(cmdarray, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, check=True)
return process.stdout
@classmethod
def run_cmd(cls, cmdarray, silent: bool = False):
p = subprocess.run(cmdarray, capture_output=True)
if not silent:
print(p.stdout.decode())
print(p.stderr.decode())
return p.stdout
class helper_withresults_base(helper_base):
helper_results_dir: Union[Path, None] = None
@classmethod
def get_result_dir(cls):
if cls.helper_results_dir == None:
cls.helper_results_dir = cls.__name__
return Path(__file__).parent.parent.absolute() / "helpers-results" / cls.helper_results_dir

View File

@@ -1,247 +0,0 @@
# 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/>.
from __future__ import annotations
from typing import TYPE_CHECKING
from contextlib import redirect_stdout
from io import StringIO
import re
import json
from enum import Enum
from contextlib import suppress
import sys
import pandas
import csv
import copy
from pylint.lint import Run as pylint_Run
import pylint_json2html
from .helper_base import helper_withresults_base
class PyLintMetricNotFound(Warning):
pass
class quality_check(helper_withresults_base):
PylintMessageList = dict()
@classmethod
def GetPylintMessageList(cls):
Messagelist = dict()
regex = r"^:([a-zA-Z-]+) \(([^\)]+)\)"
for line in cls.run_cmd([sys.executable, "-m", "pylint", "--list-msgs"], True).splitlines():
if res := re.search(regex, line.decode()):
Messagelist[res.group(1)] = res.group(2)
cls.PylintMessageList = Messagelist
@staticmethod
def TryExtractPYReportMetric(line: str, tag: str):
regex = f"^(?:\|{tag}\s*\|)(\d+)(?=\s*|)"
if res := re.search(regex, line):
return float(res.group(1))
raise PyLintMetricNotFound()
@classmethod
def do_job(cls):
print("checking code quality ...")
cls.GetPylintMessageList()
RES_all = dict()
with StringIO() as StdOutput:
JsonContent = ""
with redirect_stdout(StdOutput):
pylint_Run(
[
"--load-plugins=pylint.extensions.mccabe",
"--output-format=json,parseable",
"--disable=invalid-name",
"--ignore=_version.py",
"--reports=y",
"--score=yes",
"--max-line-length=140",
"src." + cls.pyproject["project"]["name"],
],
exit=False,
)
with open(cls.get_result_dir() / "report.json", "w+", encoding="utf-8") as Outfile:
# hacky way of exctracting json + having overall score...
class TScanState(Enum):
TEXT_REPORT = 1
JSON_REPORT = 2
OTHER_REPORT_START = 3
OTHER_REPORT_STATISTICS = 4
OTHER_REPORT_METRICS = 5
OTHER_REPORT_DUPLICATION = 6
OTHER_REPORT_MESSAGES_CAT = 7
OTHER_REPORT_MESSAGES = 8
OTHER_REPORT_END = 99
RES_all["Statistics"] = dict()
RES_all["RawMetrics"] = dict()
RES_all["RawMetricsPercent"] = dict()
RES_all["Duplication"] = dict()
RES_all["MessagesCat"] = dict()
RES_all["Messages"] = dict()
RES_all["GlobalScore"] = -999
RES_all["NbAnalysedStatments"] = -999
RES_all["NbAnalysedLines"] = -999
ScanState = TScanState.TEXT_REPORT
for line in StdOutput.getvalue().split("\n"):
print(line)
if ScanState == TScanState.TEXT_REPORT:
# ignoring this part, we need json
if line == "[":
JsonContent += line
ScanState = TScanState.JSON_REPORT
elif line == "[]":
JsonContent += line
ScanState = TScanState.OTHER_REPORT_START
elif ScanState == TScanState.JSON_REPORT:
JsonContent += line
if line == "]":
ScanState = TScanState.OTHER_REPORT_START
elif ScanState == TScanState.OTHER_REPORT_START:
if res := re.search(r"^(\d+)(?= statements analysed.)", line):
RES_all["NbAnalysedStatments"] = float(res.group(1))
if line == "Statistics by type":
ScanState = TScanState.OTHER_REPORT_STATISTICS
elif ScanState == TScanState.OTHER_REPORT_STATISTICS:
if res := re.search(r"^(\d+)(?= lines have been analyzed)", line):
RES_all["NbAnalysedLines"] = float(res.group(1))
elif line == "Raw metrics":
ScanState = TScanState.OTHER_REPORT_METRICS
else:
with suppress(PyLintMetricNotFound):
RES_all["Statistics"]["module"] = cls.TryExtractPYReportMetric(line, "module")
with suppress(PyLintMetricNotFound):
RES_all["Statistics"]["class"] = cls.TryExtractPYReportMetric(line, "class")
with suppress(PyLintMetricNotFound):
RES_all["Statistics"]["method"] = cls.TryExtractPYReportMetric(line, "method")
with suppress(PyLintMetricNotFound):
RES_all["Statistics"]["function"] = cls.TryExtractPYReportMetric(line, "function")
elif ScanState == TScanState.OTHER_REPORT_METRICS:
if line == "Duplication":
RES_all["RawMetricsPercent"]["code"] = RES_all["RawMetrics"]["code"] / RES_all["NbAnalysedLines"]
RES_all["RawMetricsPercent"]["docstring"] = RES_all["RawMetrics"]["docstring"] / RES_all["NbAnalysedLines"]
RES_all["RawMetricsPercent"]["comment"] = RES_all["RawMetrics"]["comment"] / RES_all["NbAnalysedLines"]
RES_all["RawMetricsPercent"]["empty"] = RES_all["RawMetrics"]["empty"] / RES_all["NbAnalysedLines"]
ScanState = TScanState.OTHER_REPORT_DUPLICATION
else:
with suppress(PyLintMetricNotFound):
RES_all["RawMetrics"]["code"] = cls.TryExtractPYReportMetric(line, "code")
with suppress(PyLintMetricNotFound):
RES_all["RawMetrics"]["docstring"] = cls.TryExtractPYReportMetric(line, "docstring")
with suppress(PyLintMetricNotFound):
RES_all["RawMetrics"]["comment"] = cls.TryExtractPYReportMetric(line, "comment")
with suppress(PyLintMetricNotFound):
RES_all["RawMetrics"]["empty"] = cls.TryExtractPYReportMetric(line, "empty")
elif ScanState == TScanState.OTHER_REPORT_DUPLICATION:
if line == "Messages by category":
ScanState = TScanState.OTHER_REPORT_MESSAGES_CAT
else:
with suppress(PyLintMetricNotFound):
RES_all["Duplication"]["NbDupLines"] = cls.TryExtractPYReportMetric(line, "nb duplicated lines")
with suppress(PyLintMetricNotFound):
RES_all["Duplication"]["PersentDuplicatedLines"] = cls.TryExtractPYReportMetric(
line, "percent duplicated lines"
)
elif ScanState == TScanState.OTHER_REPORT_MESSAGES_CAT:
if line == "Messages":
ScanState = TScanState.OTHER_REPORT_MESSAGES
else:
with suppress(PyLintMetricNotFound):
RES_all["MessagesCat"]["Convention"] = cls.TryExtractPYReportMetric(line, "convention")
with suppress(PyLintMetricNotFound):
RES_all["MessagesCat"]["Refactor"] = cls.TryExtractPYReportMetric(line, "refactor")
with suppress(PyLintMetricNotFound):
RES_all["MessagesCat"]["Warning"] = cls.TryExtractPYReportMetric(line, "warning")
with suppress(PyLintMetricNotFound):
RES_all["MessagesCat"]["Error"] = cls.TryExtractPYReportMetric(line, "error")
elif ScanState == TScanState.OTHER_REPORT_MESSAGES:
# approx match because the number of '-' depend on screen width..
if line.startswith("--------"):
ScanState = TScanState.OTHER_REPORT_END
else:
for PylintMessage in cls.PylintMessageList.keys():
with suppress(PyLintMetricNotFound):
RES_all["Messages"][PylintMessage] = cls.TryExtractPYReportMetric(line, PylintMessage)
elif ScanState == TScanState.OTHER_REPORT_END:
if res := re.search(r"(?<=Your code has been rated at )(\d+(?:\.\d+)?)/10", line):
RES_all["GlobalScore"] = float(res.group(1))
print(RES_all["GlobalScore"])
else:
raise RuntimeError("Invalid ScanState")
Outfile.write(JsonContent)
with open(cls.get_result_dir() / "metrics.json", "w") as json_file:
json.dump(RES_all, json_file)
# exporting all Data in one csv, unused atm because jenkins seems not able to select columns from csv an keep displaying all...
# => to export a working full csv we need to a 'flat' dict (no more nested dict)
RES_all_trim = copy.deepcopy(RES_all)
del RES_all_trim["Messages"]
flat_RES_all = pandas.json_normalize(RES_all_trim, sep="_").to_dict(orient="records")[0]
with open(cls.get_result_dir() / "metrics.csv", "w", newline="") as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=flat_RES_all.keys())
writer.writeheader()
writer.writerow(flat_RES_all)
# splited csv exports for jenkins plots: RawMetricsPercent
RES_all_percent = RES_all["RawMetricsPercent"]
with open(cls.get_result_dir() / "metrics_rawpercent.csv", "w", newline="") as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=RES_all_percent.keys())
writer.writeheader()
writer.writerow(RES_all_percent)
# splited csv exports for jenkins plots: Statistics + Duplication + NbAnalysedStatments + NbAnalysedLines
RES_all_stats = copy.deepcopy(RES_all["Statistics"])
RES_all_stats["NbDupLines"] = RES_all["Duplication"]["NbDupLines"]
RES_all_stats["PersentDuplicatedLines"] = RES_all["Duplication"]["PersentDuplicatedLines"]
RES_all_stats["NbAnalysedStatments"] = RES_all["NbAnalysedStatments"]
RES_all_stats["NbAnalysedLines"] = RES_all["NbAnalysedLines"]
with open(cls.get_result_dir() / "metrics_Statistics.csv", "w", newline="") as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=RES_all_stats.keys())
writer.writeheader()
writer.writerow(RES_all_stats)
# splited csv exports for jenkins plots: Statistics + Duplication
RES_all_MessagesCat = RES_all["MessagesCat"]
with open(cls.get_result_dir() / "metrics_MessagesCat.csv", "w", newline="") as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=RES_all_MessagesCat.keys())
writer.writeheader()
writer.writerow(RES_all_MessagesCat)
# splited csv exports for jenkins plots: GlobalScore
RES_GlobalScore = {"GlobalScore": RES_all["GlobalScore"]}
with open(cls.get_result_dir() / "metrics_GlobalScore.csv", "w", newline="") as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=RES_GlobalScore.keys())
writer.writeheader()
writer.writerow(RES_GlobalScore)
# converting the report using pylint_json2html (/!\ internal API, but as their is no leading '_' ...)
with open(cls.get_result_dir() / "report.html", "w+", encoding="utf-8") as Outfile:
raw_data = json.loads(JsonContent)
report = pylint_json2html.Report(raw_data)
Outfile.write(report.render())
print("Done")

View File

@@ -1,60 +0,0 @@
# 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/>.
from __future__ import annotations
from typing import TYPE_CHECKING
from pathlib import Path
from mypy import api
from .helper_base import helper_withresults_base
class types_check(helper_withresults_base):
JUnitReportName = "junit.xml"
@classmethod
def do_job(cls):
print("checking code typing ...")
result = api.run(
[ # project path
"-p",
"src." + cls.pyproject["project"]["name"],
# analysis configuration
# "--show-traceback",
"--explicit-package-bases",
# "--strict-equality",
# "--check-untyped-defs",
# reports generation
"--cobertura-xml-report",
str(cls.get_result_dir()),
"--html-report",
str(cls.get_result_dir()),
"--txt-report",
str(cls.get_result_dir()),
"--xml-report",
str(cls.get_result_dir()),
"--junit-xml",
str(cls.get_result_dir()) + "/" + cls.JUnitReportName,
]
)
if result[0]:
print("\nType checking report:\n")
print(result[0]) # stdout
# converting the report using pylint_json2html (/!\ internal API, but as their is no leading '_' ...)
with open(cls.get_result_dir() / "raw_eport.txt", "w+", encoding="utf-8") as Outfile:
Outfile.write(result[0])
if result[1]:
print("\nError report:\n")
print(result[1]) # stderr
print("\nExit status:", result[2])
print("Done")

View File

@@ -1,81 +0,0 @@
# 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/>.
from __future__ import annotations
from typing import TYPE_CHECKING
from pathlib import Path
import os
import datetime
import unittest
import xmlrunner
from junitparser import JUnitXml
from junit2htmlreport import parser as junit2html_parser
from .helper_base import helper_withresults_base
class unit_test(helper_withresults_base):
enable_coverage_check: bool = False
enable_xml_export: bool = True
enable_full_xml_export: bool = True
FullReportName: str = "full_report"
CoverageReportName: str = "test_coverage"
@classmethod
def do_job(cls):
if cls.enable_coverage_check == True:
import coverage
# preparing unittest framework
test_loader = unittest.TestLoader()
if cls.enable_coverage_check == True:
# we start coverage now because module files discovery is part of the coverage measurement
CoverageReportPath = Path(str(cls.get_result_dir()) + "_coverage")
cls._reset_dir(CoverageReportPath)
cov = coverage.Coverage(cover_pylib=False, branch=True, source_pkgs=["src." + cls.pyproject["project"]["name"]])
cov.start()
package_tests = test_loader.discover(
start_dir=str(cls.project_rootdir_path / "test"), top_level_dir=str(cls.project_rootdir_path / "test")
)
if cls.enable_xml_export:
testRunner = xmlrunner.XMLTestRunner(output=str(str(cls.get_result_dir())))
else:
testRunner = unittest.TextTestRunner()
# running the test
testRunner.run(package_tests)
print("Test Finished")
if cls.enable_coverage_check == True:
cov.stop()
cov.save()
cov.html_report(directory=str(CoverageReportPath))
cov.xml_report(outfile=(CoverageReportPath / f"{cls.CoverageReportName}.xml"))
# computing results (Only if xml available)
if cls.enable_full_xml_export == True:
print("Full reports generation...")
FullReportPath = Path(str(cls.get_result_dir()) + "_full")
cls._reset_dir(FullReportPath)
FullJUnitReport = JUnitXml()
for fname in [fname for fname in os.listdir(cls.get_result_dir()) if fname.endswith(".xml")]:
FullJUnitReport += JUnitXml.fromfile(str(cls.get_result_dir() / fname))
current_datetime = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
full_report_base_name = f'{cls.pyproject["project"]["name"]}-{cls.FullReportName}-{current_datetime}'
FullJUnitReport.write(str(FullReportPath / f"{full_report_base_name}.xml"))
report = junit2html_parser.Junit(FullReportPath / f"{full_report_base_name}.xml")
html = report.html()
with open(FullReportPath / f"{full_report_base_name}.html", "wb") as outfile:
outfile.write(html.encode("utf-8"))
print("Done")

View File

@@ -0,0 +1,5 @@
from chacha_cicd_helper.__main__ import fct_main
import sys
if __name__ == "__main__":
fct_main(sys.argv[1:])

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\cl_doc_gen\site\pdf\manual.pdf
markdown_extensions:
- def_list
- tables
@@ -111,8 +114,8 @@ markdown_extensions:
- footnotes
- pymdownx.superfences
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
emoji_index: !!python/name:materialx.emoji.twemoji ''
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"]
@@ -47,7 +48,21 @@ include-package-data = true
where = ["src"]
[tool.setuptools.package-data]
"pygamecfg.data" = ["*.*"]
"pysimpleini" = ["py.typed"]
# [[tool.mypy.overrides]]
# module = ""
# ignore_missing_imports = true
[tool.coverage.run]
cover_pylib = false
branch = true
data_file="helpers-results/cl_unit_test_raw_coverage/.coverage"
# debug = ["config","multiproc","process"]
parallel = true
concurrency = [
'thread'
]
[project.urls]
Homepage = "https://chacha.ddns.net/gitea/chacha/pygamecfg"
@@ -55,13 +70,13 @@ Documentation = "https://chacha.ddns.net/mkdocs-web/chacha/pygamecfg/master/la
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-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"]
test = ["chacha_cicd_helper"]
coverage-check = ["chacha_cicd_helper"]
complexity-check = ["chacha_cicd_helper"]
quality-check = ["chacha_cicd_helper"]
type-check = ["chacha_cicd_helper"]
doc-gen = ["chacha_cicd_helper"]
#[project.scripts]
#my-script = "my_package.module:function"
# [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,7 +16,6 @@ Main module __init__ file.
from importlib.metadata import distribution, version, PackageNotFoundError
import warnings
from .core import GameOptions_Factory
try: # pragma: no cover
__version__ = version("pygamecfg")
@@ -34,3 +36,10 @@ 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_cod4_promod
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,52 +11,102 @@
"""CLI interface module"""
from __future__ import annotations
from typing import Literal, cast, Union
from argparse import ArgumentParser
import sys
from tap import Tap
from . import __Summuary__, __Name__
from . import GameOptions_Factory
parser = ArgumentParser(description=__Summuary__)
parser.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity")
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"])
subparsers = parser.add_subparsers(dest="command", help="command type", required=True)
class pygamecfg_args_SetOption(Tap):
"""SetOption CLI arg subparser"""
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="?")
option: str
value: str = ""
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="?")
class pygamecfg_args_RemOption(Tap):
"""RemOption CLI arg subparser"""
GetOption_subparser = subparsers.add_parser("GetOption", help="Get a game config file option value")
GetOption_subparser.add_argument("option")
option: str
value: str = ""
args = parser.parse_args()
if args.verbosity:
print("Using base game dir: {0}".format(args.basegamedir))
print("Using config file: {0}".format(args.configfile))
if args.configfile == "":
if args.game == "ut99":
args.configfile = "./System/UnrealTournament.ini"
elif args.game == "cod4":
args.configfile = "./main/server.cfg"
_GameOptions = GameOptions_Factory(args.game, args.basegamedir, args.configfile)
def configure(self) -> None:
self.add_argument("option")
self.add_argument("value")
if args.command == "SetOption":
_GameOptions.set(args.option, args.value)
elif args.command == "RemOption":
_GameOptions.rem(args.option, args.value)
elif args.command == "GetOption":
res = _GameOptions.get(args.option)
print(res)
else:
raise RuntimeError("Invalid argument")
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", "cod4_promod", "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(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 in ("cod4", "cod4_gungame", "cod4_promod"):
args.configfile = "./main/server.cfg"
GameOptions = GameOptions_Factory(args.game, args.basegamedir, args.configfile)
if args.command == "SetOption":
GameOptions.set(
cast(pygamecfg_args_SetOption, args).option, cast(pygamecfg_args_SetOption, args).value # pylint: disable=no-member
)
elif args.command == "RemOption":
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(cast(pygamecfg_args_GetOption, args).option) # pylint: disable=no-member
print(res)
else:
raise RuntimeError("Invalid argument")
if __name__ == "__main__":
fct_main(sys.argv[1:])

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(bool(1)) if value.lower() == "true" else str(bool(0))
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 self.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 self.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,179 @@
#!/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 GameCfgException(Exception):
"""Standard Exception to catch non existing key in configuration file"""
class OptionNotFoundError(GameCfgException):
"""Standard Exception to catch non existing configuration option"""
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 OptionNotFoundError("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 OptionNotFoundError("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 OptionNotFoundError("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/>.

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

@@ -0,0 +1,703 @@
#!/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,line-too-long
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, GameCfgException
class COD4KeyNotFoundError(GameCfgException):
"""Exception to catch non existing configuration key in COD4 configuration file"""
class GameOption_COD4(GameOption):
szGameType = "cod4"
TValueType = OptionType.OT_INVALID
szOptionName: str = ""
szKeyName: str = ""
bDblQuoted: bool = False
szPrefix: str = ""
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 super().format_OT_BOOLEAN(value).lower() == "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 = FinalValue + 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 COD4KeyNotFoundError("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 COD4KeyNotFoundError("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_DM_numlives(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "dm_numlives"
szKeyName: str = "scr_dm_numlives"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_DM_playerrespawndelay(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "dm_playerrespawndelay"
szKeyName: str = "scr_dm_playerrespawndelay"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_DM_waverespawndelay(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "dm_waverespawndelay"
szKeyName: str = "scr_dm_waverespawndelay"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
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_TDM_numlives(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "tdm_numlives"
szKeyName: str = "scr_war_numlives"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_TDM_playerrespawndelay(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "tdm_playerrespawndelay"
szKeyName: str = "scr_war_playerrespawndelay"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
szHelp = ""
@GameOptions_Factory_Register
class GameOption_COD4_TDM_waverespawndelay(GameOption_COD4):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "tdm_waverespawndelay"
szKeyName: str = "scr_war_waverespawndelay"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
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"
@GameOptions_Factory_Register
class GameOption_COD4_punkbuster(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "punkbuster"
szKeyName: str = "sv_punkbuster"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1"
szHelp = "Enable Punkbuste (PB is not supported on CoD4x)"
@GameOptions_Factory_Register
class GameOption_COD4_pure(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "pure"
szKeyName: str = "sv_pure"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1"
szHelp = "check IWD-data 0 = off, 1 = on"
@GameOptions_Factory_Register
class GameOption_COD4_antilag(GameOption_COD4):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "antilag"
szKeyName: str = "g_antilag"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "1"
szHelp = "0 = off, 1 = on // Anti lag checks for weapon hits"

View File

@@ -0,0 +1,384 @@
#!/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,line-too-long
from __future__ import annotations
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_kills_for_levelup(GameOption_COD4_GunGame):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "kills_for_levelup"
szKeyName: str = "gg_kills_for_levelup"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "2"
szHelp = """Number of kills you need to level-up"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_turbo(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "turbo"
szKeyName: str = "gg_turbo"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """Turbo Mode: Give the new weapon immediately"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_level_down(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "level_down"
szKeyName: str = "gg_level_down"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """Number of levels you lose on suicide, teamkill..."""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_health_regen_time(GameOption_COD4_GunGame):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "health_regen_time"
szKeyName: str = "gg_health_regen_time"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "6"
szHelp = """Time before health regeneration starts: 5 = default, 0 = disable health regen"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_health_text(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "health_text"
szKeyName: str = "gg_health_text"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """Text in the bottom-left part of the screen that shows your health"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_weapon_drop(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "weapon_drop"
szKeyName: str = "gg_weapon_drop"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """Drop weapons on death?"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_knife_nerf(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "knife_nerf"
szKeyName: str = "gg_knife_nerf"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "0"
szHelp = """0 = default, 1 = knife needs 2 hits to kill"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_dont_cook_frags(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "dont_cook_frags"
szKeyName: str = "gg_dont_cook_frags"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "0"
szHelp = """1 = don't cook frag grenades, 0 = default nades"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_end_music(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "end_music"
szKeyName: str = "gg_end_music"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """Music at the end of the map"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_knife_pro(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "knife_pro"
szKeyName: str = "gg_knife_pro"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """When you knife someone you steal a level from him"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_deadly_snipers(GameOption_COD4_GunGame):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "deadly_snipers"
szKeyName: str = "gg_deadly_snipers"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """1 = increase damage for bolt-action snipers, 2 = increase damage for all snipers"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_handicap(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "handicap"
szKeyName: str = "gg_handicap"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """If a player joins in the middle of the game he is auto leveled to the level the worst player has"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_ammo_frag(GameOption_COD4_GunGame):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "ammo_frag"
szKeyName: str = "gg_ammo_frag"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "6"
szHelp = """Ammo for frag grenades [1-10]"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_ammo_c4(GameOption_COD4_GunGame):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "ammo_c4"
szKeyName: str = "gg_ammo_c4"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "4"
szHelp = """Ammo for C4 [1-10]"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_ammo_rpg(GameOption_COD4_GunGame):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "ammo_rpg"
szKeyName: str = "gg_ammo_rpg"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "3"
szHelp = """Ammo for RPG [1-10]"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_rotate_empty(GameOption_COD4_GunGame):
TValueType = OptionType.OT_INTEGER
szOptionName: str = "rotate_empty"
szKeyName: str = "gg_rotate_empty"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "10"
szHelp = """Minutes after which server rotates to next map if no active players"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_remove_turrets(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "remove_turrets"
szKeyName: str = "gg_remove_turrets"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """Remove stationary turrets [0=turrets allwed; 1=turrets removed]"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_second_weapon(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "second_weapon"
szKeyName: str = "gg_second_weapon"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "deserteagle"
szHelp = """The second weapon the players will have (empty to disable)"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_second_weapon_levels(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "second_weapon_levels"
szKeyName: str = "gg_second_weapon_levels"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "c4,rpg,frag_grenade,rpg"
szHelp = """The second weapon the players will have (empty to disable)"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_weapon_sequence(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "weapon_sequence"
szKeyName: str = "gg_weapon_sequence"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "beretta,usp,colt45,deserteagle,winchester1200,m1014,mp5,m40a3,skorpion,uzi,ak74u,p90,m16,ak47,m4,g3,g36c,m14,mp44,saw,rpd,m60e4,barett,remington700,rpg,frag_grenade,knife"
szHelp = """comma separated list from: beretta,usp,colt45,deserteagle,winchester1200,m1014,mp5,skorpion,uzi,ak74u,p90,m16,ak47,m4,g3,g36c,m14,mp44,saw,rpd,m60e4,m40a3,m21,dragunov,remington700,barrett,frag_grenade,knife,c4,rpg"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_MessageCenter_Enable(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
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"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_WelcomeMessage_Enable(GameOption_COD4_GunGame):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "wc_enable"
szKeyName: str = "wc_enable"
bDblQuoted: bool = False
szPrefix = "set"
szDefaultValue = "1"
szHelp = """Enable welcome message"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_WelcomeMessage_Messsage(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "wc_message"
szKeyName: str = "wc_message"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = r"Welcome to the server <name>!\Please enjoy your stay!"
szHelp = r"""The message [ <name> is replaced by players name and \ indicates a new line ]"""
@GameOptions_Factory_Register
class GameOption_COD4_GunGame_bottom_text(GameOption_COD4_GunGame):
TValueType = OptionType.OT_STRING
szOptionName: str = "bottom_text"
szKeyName: str = "gg_bottom_text"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "GunGame Server"
szHelp = """ Hud [ custom text displayed on the bottom of the screen next to mod info ]"""

View File

@@ -0,0 +1,43 @@
#!/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,line-too-long
from __future__ import annotations
from .core_gamecfg import GameOptions_Factory_Register, OptionType
from .game_cod4 import GameOption_COD4
class GameOption_COD4_ProMod(GameOption_COD4):
szGameType = "cod4_promod"
@GameOptions_Factory_Register
class GameOption_COD4_ProMod_promod_mode(GameOption_COD4_ProMod):
TValueType = OptionType.OT_STRING
szOptionName: str = "promod_mode"
szKeyName: str = "promod_mode"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "comp_public"
szHelp = """comp_public,custom_public,2v2_mr,1v1_mr,knockout_mr,match_mr"""
@GameOptions_Factory_Register
class GameOption_COD4_ProMod_promod_enable_scorebot(GameOption_COD4_ProMod):
TValueType = OptionType.OT_BOOLEAN
szOptionName: str = "promod_enable_scorebot"
szKeyName: str = "promod_enable_scorebot"
bDblQuoted: bool = True
szPrefix = "set"
szDefaultValue = "0"
szHelp = """match-modes only"""

View File

@@ -1,75 +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,line-too-long
"""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")
print("get option <{0}>".format(self.szOptionName))
res = self.inifile.getKeyValue(self.szSectionName, self.szKeyName)
return res
Cls_PySimpleINI = PySimpleINI_UT2k4
class GameOption_UT2k4_GenAdd(GameOption_UT2k4):
@@ -156,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
@@ -201,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
@@ -238,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
@@ -253,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
@@ -731,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,75 +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,line-too-long
"""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.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")
# print("get option <{0}>".format(self.szOptionName))
res = self.inifile.getKeyValue(self.szSectionName, self.szKeyName)
return res
Cls_PySimpleINI = PySimpleINI_UT99
class GameOption_UT99_GenAdd(GameOption_UT99):
@@ -96,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:
@@ -110,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:
@@ -175,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
@@ -240,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
@@ -255,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
@@ -292,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
@@ -307,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
@@ -322,8 +287,19 @@ 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[str, list[str]]:
try:
return super().get()
except KeyNotFoundError:
return self.inifile.getkeyvalue(
"UTServerAdmin.UTServerAdmin",
"AdminPassword",
)
raise NotImplementedError("method not implemented")
@GameOptions_Factory_Register
@@ -448,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

View File

@@ -0,0 +1,520 @@
[URL]
Protocol=unreal
ProtocolDescription=Unreal Protocol
Name=Player
Map=Index.unr
LocalMap=DM-Codex.unr
Host=
Portal=
MapExt=unr
SaveExt=usa
Port=7777
Class=Botpack.TMale1
[FirstRun]
FirstRun=0
[PackageRemap]
UnrealShare=UnrealI
[Engine.Engine]
GameRenderDevice=SoftDrv.SoftwareRenderDevice
AudioDevice=Galaxy.GalaxyAudioSubsystem
NetworkDevice=IpDrv.TcpNetDriver
DemoRecordingDevice=Engine.DemoRecDriver
Console=UTMenu.UTConsole
Language=int
GameEngine=Engine.GameEngine
EditorEngine=Editor.EditorEngine
WindowedRenderDevice=SoftDrv.SoftwareRenderDevice
RenderDevice=GlideDrv.GlideRenderDevice
DefaultGame=Botpack.DeathMatchPlus
DefaultServerGame=Botpack.DeathMatchPlus
ViewportManager=WinDrv.WindowsClient
Render=Render.Render
Input=Engine.Input
Canvas=Engine.Canvas
[Core.System]
PurgeCacheDays=30
SavePath=../Save
CachePath=../Cache
CacheExt=.uxx
Paths=../System/*.u
Paths=../Maps/*.unr
Paths=../Textures/*.utx
Paths=../Sounds/*.uax
Paths=../Music/*.umx
Suppress=DevLoad
Suppress=DevSave
Suppress=DevNetTraffic
Suppress=DevGarbage
Suppress=DevKill
Suppress=DevReplace
Suppress=DevSound
Suppress=DevCompile
Suppress=DevBind
Suppress=DevBsp
[Engine.GameEngine]
CacheSizeMegs=4
UseSound=True
ServerActors=IpDrv.UdpBeacon
ServerActors=IpServer.UdpServerQuery
ServerActors=IpServer.UdpServerUplink MasterServerAddress=unreal.epicgames.com MasterServerPort=27900
ServerActors=IpServer.UdpServerUplink MasterServerAddress=master0.gamespy.com MasterServerPort=27900
ServerActors=IpServer.UdpServerUplink MasterServerAddress=master.mplayer.com MasterServerPort=27900
ServerActors=UWeb.WebServer
ServerPackages=SoldierSkins
ServerPackages=CommandoSkins
ServerPackages=FCommandoSkins
ServerPackages=SGirlSkins
ServerPackages=BossSkins
ServerPackages=Botpack
[WinDrv.WindowsClient]
WindowedViewportX=640
WindowedViewportY=480
WindowedColorBits=16
FullscreenViewportX=640
FullscreenViewportY=480
FullscreenColorBits=16
Brightness=0.500000
MipFactor=1.000000
UseDirectDraw=True
UseJoystick=False
CaptureMouse=True
StartupFullscreen=True
CurvedSurfaces=False
LowDetailTextures=False
ScreenFlashes=True
NoLighting=False
SlowVideoBuffering=True
DeadZoneXYZ=True
DeadZoneRUV=False
InvertVertical=False
ScaleXYZ=1000.0
ScaleRUV=2000.0
MinDesiredFrameRate=30.0
Decals=True
NoDynamicLights=False
[XDrv.XClient]
WindowedViewportX=640
WindowedViewportY=480
WindowedColorBits=16
FullscreenViewportX=640
FullscreenViewportY=480
FullscreenColorBits=16
Brightness=0.500000
MipFactor=1.000000
SlowVideoBuffering=False
StartupFullscreen=True
CurvedSurfaces=False
CaptureMouse=True
LowDetailTextures=False
ScreenFlashes=True
NoLighting=False
DeadZoneXYZ=True
DeadZoneRUV=False
InvertVertical=False
ScaleXYZ=1000.0
ScaleRUV=2000.0
MinDesiredFrameRate=30.0
[Engine.Player]
ConfiguredInternetSpeed=2600
ConfiguredLanSpeed=20000
[Audio.GenericAudioSubsystem]
UseFilter=True
UseSurround=False
UseStereo=True
UseCDMusic=False
UseDigitalMusic=False
UseSpatial=False
UseReverb=False
Use3dHardware=False
LowSoundQuality=False
ReverseStereo=False
Latency=40
OutputRate=22050Hz
Channels=16
MusicVolume=160
SoundVolume=200
AmbientFactor=0.7
[Galaxy.GalaxyAudioSubsystem]
UseDirectSound=True
UseFilter=True
UseSurround=False
UseStereo=True
UseCDMusic=False
UseDigitalMusic=True
UseSpatial=False
UseReverb=True
Use3dHardware=False
LowSoundQuality=False
ReverseStereo=False
Latency=40
OutputRate=22050Hz
EffectsChannels=16
DopplerSpeed=9000.000000
MusicVolume=160
SoundVolume=200
AmbientFactor=0.700000
[IpDrv.TcpNetDriver]
AllowDownloads=True
ConnectionTimeout=15.0
InitialConnectTimeout=120.0
AckTimeout=1.0
KeepAliveTime=0.2
MaxClientRate=20000
MaxDownloadSize=0
SimLatency=0
RelevantTimeout=5.0
SpawnPrioritySeconds=1.0
ServerTravelPause=4.0
NetServerMaxTickRate=20
LanServerMaxTickRate=35
DownloadManagers=IpDrv.HTTPDownload
DownloadManagers=Engine.ChannelDownload
[IpDrv.HTTPDownload]
RedirectToURL=http://uz.ut-files.com/
ProxyServerHost=
ProxyServerPort=3128
UseCompression=True
[Engine.DemoRecDriver]
DemoSpectatorClass=Botpack.CHSpectator
MaxClientRate=25000
ConnectionTimeout=15.0
InitialConnectTimeout=500.0
AckTimeout=1.0
KeepAliveTime=1.0
SimLatency=0
RelevantTimeout=5.0
SpawnPrioritySeconds=1.0
ServerTravelPause=4.0
NetServerMaxTickRate=60
LanServerMaxTickRate=60
[Engine.GameReplicationInfo]
ServerName=Alt Serv Name FULL
ShortName=Alt Serv Name SHORT
AdminName=TestAdminName
AdminEmail=TestAdminName@test.com
Region=0
MOTDLine1=TestMOTDLine1
MOTDLine2=TestMOTDLine2
MOTDLine3=TestMOTDLine3
MOTDLine4=TestMOTDLine4
[IpDrv.TcpipConnection]
SimPacketLoss=0
SimLatency=0
[IpServer.UdpServerQuery]
DoUplink=True
UpdateMinutes=1
MasterServerAddress=unreal.epicgames.com
MasterServerPort=27900
Region=0
[IpDrv.UdpBeacon]
DoBeacon=True
BeaconTime=0.50
BeaconTimeout=5.0
BeaconProduct=ut
[SoftDrv.SoftwareRenderDevice]
Translucency=True
VolumetricLighting=True
ShinySurfaces=False
Coronas=False
HighDetailActors=False
HighResTextureSmooth=True
LowResTextureSmooth=False
FastTranslucency=True
[GlideDrv.GlideRenderDevice]
Translucency=True
VolumetricLighting=True
ShinySurfaces=True
Coronas=True
HighDetailActors=True
DetailBias=-1.500000
RefreshRate=60Hz
DetailTextures=True
FastUglyRefresh=False
ScreenSmoothing=True
Resolution=Default
[MetalDrv.MetalRenderDevice]
Translucency=True
VolumetricLighting=True
ShinySurfaces=True
Coronas=True
HighDetailActors=True
DetailTextures=False
[OpenGLDrv.OpenGLRenderDevice]
Translucency=True
VolumetricLighting=False
ShinySurfaces=True
Coronas=True
HighDetailActors=True
DetailTextures=True
[D3DDrv.D3DRenderDevice]
Translucency=True
VolumetricLighting=False
ShinySurfaces=True
Coronas=True
HighDetailActors=True
UseMipmapping=True
UseTrilinear=False
UseMultitexture=True
UsePageFlipping=True
UsePalettes=True
UseFullscreen=True
UseGammaCorrection=True
DetailTextures=False
Use3dfx=False
UseTripleBuffering=True
UsePrecache=True
Use32BitTextures=False
[SglDrv.SglRenderDevice]
Translucency=True
VolumetricLighting=False
ShinySurfaces=False
Coronas=True
HighDetailActors=False
ColorDepth=16
DetailTextures=False
FastUglyRefresh=False
TextureDetailBias=Near
VertexLighting=False
[Editor.EditorEngine]
UseSound=True
CacheSizeMegs=6
GridEnabled=True
SnapVertices=True
SnapDistance=10.000000
GridSize=(X=16.000000,Y=16.000000,Z=16.000000)
RotGridEnabled=True
RotGridSize=(Pitch=1024,Yaw=1024,Roll=1024)
GameCommandLine=-log
FovAngleDegrees=90.000000
GodMode=True
AutoSave=False
AutoSaveTimeMinutes=5
AutoSaveIndex=6
C_WorldBox=(R=0,G=0,B=107,A=0)
C_GroundPlane=(R=0,G=0,B=63,A=0)
C_GroundHighlight=(R=0,G=0,B=127,A=0)
C_BrushWire=(R=255,G=63,B=63,A=0)
C_Pivot=(R=0,G=255,B=0,A=0)
C_Select=(R=0,G=0,B=127,A=0)
C_AddWire=(R=127,G=127,B=255,A=0)
C_SubtractWire=(R=255,G=192,B=63,A=0)
C_GreyWire=(R=163,G=163,B=163,A=0)
C_Invalid=(R=163,G=163,B=163,A=0)
C_ActorWire=(R=127,G=63,B=0,A=0)
C_ActorHiWire=(R=255,G=127,B=0,A=0)
C_White=(R=255,G=255,B=255,A=0)
C_SemiSolidWire=(R=127,G=255,B=0,A=0)
C_NonSolidWire=(R=63,G=192,B=32,A=0)
C_WireGridAxis=(R=119,G=119,B=119,A=0)
C_ActorArrow=(R=163,G=0,B=0,A=0)
C_ScaleBox=(R=151,G=67,B=11,A=0)
C_ScaleBoxHi=(R=223,G=149,B=157,A=0)
C_Mover=(R=255,G=0,B=255,A=0)
C_OrthoBackground=(R=163,G=163,B=163,A=0)
C_Current=(R=0,G=0,B=0,A=0)
C_BrushVertex=(R=0,G=0,B=0,A=0)
C_BrushSnap=(R=0,G=0,B=0,A=0)
C_Black=(R=0,G=0,B=0,A=0)
C_Mask=(R=0,G=0,B=0,A=0)
C_WireBackground=(R=0,G=0,B=0,A=0)
C_ZoneWire=(R=0,G=0,B=0,A=0)
EditPackages=Core
EditPackages=Engine
EditPackages=Editor
EditPackages=UWindow
EditPackages=Fire
EditPackages=IpDrv
EditPackages=UWeb
EditPackages=UBrowser
EditPackages=UnrealShare
EditPackages=UnrealI
EditPackages=UMenu
EditPackages=IpServer
EditPackages=Botpack
EditPackages=UTServerAdmin
EditPackages=UTMenu
EditPackages=UTBrowser
[UMenu.UnrealConsole]
RootWindow=UMenu.UMenuRootWindow
UWindowKey=IK_Esc
ShowDesktop=True
[UMenu.UMenuMenuBar]
ShowHelp=True
GameUMenuDefault="UTMenu.UTGameMenu"
MultiplayerUMenuDefault="UTMenu.UTMultiplayerMenu"
OptionsUMenuDefault="UTMenu.UTOptionsMenu"
[Botpack.ChallengeBotInfo]
Difficulty=1
[Botpack.DeathMatchPlus]
bNoviceMode=True
bHardCoreMode=True
bUseTranslocator=False
bCoopWeaponMode=False
bForceRespawn=True
TimeLimit=323
FragLimit=321
InitialBots=7
MinPlayers=11
bTournament=False
[Botpack.CTFGame]
bUseTranslocator=True
bCoopWeaponMode=True
GoalTeamScore=3
bForceRespawn=False
TimeLimit=223
[Botpack.Domination]
bDumbDown=True
bUseTranslocator=True
bCoopWeaponMode=True
GoalTeamScore=100
bForceRespawn=False
TimeLimit=423
[Botpack.Assault]
bUseTranslocator=False
bCoopWeaponMode=True
bForceRespawn=True
TimeLimit=123
[Botpack.TeamGamePlus]
bBalanceTeams=True
GoalTeamScore=30
bPlayersBalanceTeams=True
MaxTeamSize=4
[Engine.GameInfo]
bLowGore=False
bVeryLowGore=False
MaxSpectators=1
MaxPlayers=4
ServerLogName=server.log
bWorldLog=True
bBatchLocal=False
DemoBuild=0
DemoHasTuts=0
PlayerViewDelay=1.000000
PlayerSpeechDelay=0.300000
PlayerTauntDelay=2.000000
bLogAdminActions=False
LoginDelaySeconds=0.000000
MaxLoginAttempts=0
ActionToTake=DO_Nothing
IPPolicies[0]=ACCEPT,*
IPPolicies[1]=
GamePassword=TestPwd
[UnrealShare.UnrealGameOptionsMenu]
bCanModifyGore=True
[UBrowser.UBrowserMainClientWindow]
LANTabName=UBrowserLAN
ServerListNames[0]=UBrowserUT
ServerListNames[1]=UBrowserLAN
ServerListNames[2]=UBrowserPopulated
ServerListNames[3]=UBrowserDeathmatch
ServerListNames[4]=UBrowserTeamGames
ServerListNames[5]=UBrowserCTF
ServerListNames[6]=UBrowserDOM
ServerListNames[7]=UBrowserAS
ServerListNames[8]=UBrowserLMS
ServerListNames[9]=UBrowserAll
ServerListNames[10]=None
ServerListNames[11]=None
ServerListNames[12]=None
ServerListNames[13]=None
ServerListNames[14]=None
ServerListNames[15]=None
ServerListNames[16]=None
ServerListNames[17]=None
ServerListNames[18]=None
ServerListNames[19]=None
[UBrowserUT]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,bCompatibleServersOnly=True
[UBrowserLAN]
ListFactories[0]=UBrowser.UBrowserLocalFact,BeaconProduct=ut
URLAppend=?LAN
AutoRefreshTime=10
bNoAutoSort=True
[UBrowserPopulated]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,MinPlayers=1,bCompatibleServersOnly=True
[UBrowserDeathmatch]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=DeathMatchPlus,bCompatibleServersOnly=True
[UBrowserTeamGames]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=TeamGamePlus,bCompatibleServersOnly=True
[UBrowserCTF]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=CTFGame,bCompatibleServersOnly=True
[UBrowserDOM]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=Domination,bCompatibleServersOnly=True
[UBrowserAS]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=Assault,bCompatibleServersOnly=True
[UBrowserLMS]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=LastManStanding,bCompatibleServersOnly=True
[UBrowserAll]
ListFactories[0]=UBrowser.UBrowserGSpyFact,MasterServerAddress=unreal.epicgames.com,MasterServerTCPPort=28900,Region=0,GameName=ut
ListFactories[1]=UBrowser.UBrowserGSpyFact,MasterServerAddress=master0.gamespy.com,MasterServerTCPPort=28900,Region=0,GameName=ut
bHidden=True
bFallbackFactories=True
[UTMenu.UTMultiplayerMenu]
OnlineServices[0]=LOCALIZE,MPlayer
OnlineServices[1]=LOCALIZE,Heat
OnlineServices[2]=LOCALIZE,WON
[UWeb.WebServer]
Applications[0]=UTServerAdmin.UTServerAdmin
ApplicationPaths[0]=/ServerAdmin
Applications[1]=UTServerAdmin.UTImageServer
ApplicationPaths[1]=/images
DefaultApplication=0
bEnabled=True
ListenPort=9999
MaxConnections=30
[UBrowser.UBrowserHTTPClient]
ProxyServerAddress=
ProxyServerPort=
[UTServerAdmin.UTServerAdmin]
AdminUsername=TestAdminUser
AdminPassword=TestAdminPwd

View File

@@ -0,0 +1,520 @@
[URL]
Protocol=unreal
ProtocolDescription=Unreal Protocol
Name=Player
Map=Index.unr
LocalMap=DM-Codex.unr
Host=
Portal=
MapExt=unr
SaveExt=usa
Port=7777
Class=Botpack.TMale1
[FirstRun]
FirstRun=0
[PackageRemap]
UnrealShare=UnrealI
[Engine.Engine]
GameRenderDevice=SoftDrv.SoftwareRenderDevice
AudioDevice=Galaxy.GalaxyAudioSubsystem
NetworkDevice=IpDrv.TcpNetDriver
DemoRecordingDevice=Engine.DemoRecDriver
Console=UTMenu.UTConsole
Language=int
GameEngine=Engine.GameEngine
EditorEngine=Editor.EditorEngine
WindowedRenderDevice=SoftDrv.SoftwareRenderDevice
RenderDevice=GlideDrv.GlideRenderDevice
DefaultGame=Botpack.DeathMatchPlus
DefaultServerGame=Botpack.DeathMatchPlus
ViewportManager=WinDrv.WindowsClient
Render=Render.Render
Input=Engine.Input
Canvas=Engine.Canvas
[Core.System]
PurgeCacheDays=30
SavePath=../Save
CachePath=../Cache
CacheExt=.uxx
Paths=../System/*.u
Paths=../Maps/*.unr
Paths=../Textures/*.utx
Paths=../Sounds/*.uax
Paths=../Music/*.umx
Suppress=DevLoad
Suppress=DevSave
Suppress=DevNetTraffic
Suppress=DevGarbage
Suppress=DevKill
Suppress=DevReplace
Suppress=DevSound
Suppress=DevCompile
Suppress=DevBind
Suppress=DevBsp
[Engine.GameEngine]
CacheSizeMegs=4
UseSound=True
ServerActors=IpDrv.UdpBeacon
ServerActors=IpServer.UdpServerQuery
ServerActors=IpServer.UdpServerUplink MasterServerAddress=unreal.epicgames.com MasterServerPort=27900
ServerActors=IpServer.UdpServerUplink MasterServerAddress=master0.gamespy.com MasterServerPort=27900
ServerActors=IpServer.UdpServerUplink MasterServerAddress=master.mplayer.com MasterServerPort=27900
ServerActors=UWeb.WebServer
ServerPackages=SoldierSkins
ServerPackages=CommandoSkins
ServerPackages=FCommandoSkins
ServerPackages=SGirlSkins
ServerPackages=BossSkins
ServerPackages=Botpack
[WinDrv.WindowsClient]
WindowedViewportX=640
WindowedViewportY=480
WindowedColorBits=16
FullscreenViewportX=640
FullscreenViewportY=480
FullscreenColorBits=16
Brightness=0.500000
MipFactor=1.000000
UseDirectDraw=True
UseJoystick=False
CaptureMouse=True
StartupFullscreen=True
CurvedSurfaces=False
LowDetailTextures=False
ScreenFlashes=True
NoLighting=False
SlowVideoBuffering=True
DeadZoneXYZ=True
DeadZoneRUV=False
InvertVertical=False
ScaleXYZ=1000.0
ScaleRUV=2000.0
MinDesiredFrameRate=30.0
Decals=True
NoDynamicLights=False
[XDrv.XClient]
WindowedViewportX=640
WindowedViewportY=480
WindowedColorBits=16
FullscreenViewportX=640
FullscreenViewportY=480
FullscreenColorBits=16
Brightness=0.500000
MipFactor=1.000000
SlowVideoBuffering=False
StartupFullscreen=True
CurvedSurfaces=False
CaptureMouse=True
LowDetailTextures=False
ScreenFlashes=True
NoLighting=False
DeadZoneXYZ=True
DeadZoneRUV=False
InvertVertical=False
ScaleXYZ=1000.0
ScaleRUV=2000.0
MinDesiredFrameRate=30.0
[Engine.Player]
ConfiguredInternetSpeed=2600
ConfiguredLanSpeed=20000
[Audio.GenericAudioSubsystem]
UseFilter=True
UseSurround=False
UseStereo=True
UseCDMusic=False
UseDigitalMusic=False
UseSpatial=False
UseReverb=False
Use3dHardware=False
LowSoundQuality=False
ReverseStereo=False
Latency=40
OutputRate=22050Hz
Channels=16
MusicVolume=160
SoundVolume=200
AmbientFactor=0.7
[Galaxy.GalaxyAudioSubsystem]
UseDirectSound=True
UseFilter=True
UseSurround=False
UseStereo=True
UseCDMusic=False
UseDigitalMusic=True
UseSpatial=False
UseReverb=True
Use3dHardware=False
LowSoundQuality=False
ReverseStereo=False
Latency=40
OutputRate=22050Hz
EffectsChannels=16
DopplerSpeed=9000.000000
MusicVolume=160
SoundVolume=200
AmbientFactor=0.700000
[IpDrv.TcpNetDriver]
AllowDownloads=True
ConnectionTimeout=15.0
InitialConnectTimeout=120.0
AckTimeout=1.0
KeepAliveTime=0.2
MaxClientRate=20000
MaxDownloadSize=0
SimLatency=0
RelevantTimeout=5.0
SpawnPrioritySeconds=1.0
ServerTravelPause=4.0
NetServerMaxTickRate=20
LanServerMaxTickRate=35
DownloadManagers=IpDrv.HTTPDownload
DownloadManagers=Engine.ChannelDownload
[IpDrv.HTTPDownload]
RedirectToURL=http://uz.ut-files.com/
ProxyServerHost=
ProxyServerPort=3128
UseCompression=True
[Engine.DemoRecDriver]
DemoSpectatorClass=Botpack.CHSpectator
MaxClientRate=25000
ConnectionTimeout=15.0
InitialConnectTimeout=500.0
AckTimeout=1.0
KeepAliveTime=1.0
SimLatency=0
RelevantTimeout=5.0
SpawnPrioritySeconds=1.0
ServerTravelPause=4.0
NetServerMaxTickRate=60
LanServerMaxTickRate=60
[Engine.GameReplicationInfo]
ServerName=Test Server Name FULL
ShortName=Test Server Name SHORT
AdminName=TestAdminName
AdminEmail=TestAdminName@test.com
Region=0
MOTDLine1=TestMOTDLine1
MOTDLine2=TestMOTDLine2
MOTDLine3=TestMOTDLine3
MOTDLine4=TestMOTDLine4
[IpDrv.TcpipConnection]
SimPacketLoss=0
SimLatency=0
[IpServer.UdpServerQuery]
DoUplink=True
UpdateMinutes=1
MasterServerAddress=unreal.epicgames.com
MasterServerPort=27900
Region=0
[IpDrv.UdpBeacon]
DoBeacon=True
BeaconTime=0.50
BeaconTimeout=5.0
BeaconProduct=ut
[SoftDrv.SoftwareRenderDevice]
Translucency=True
VolumetricLighting=True
ShinySurfaces=False
Coronas=False
HighDetailActors=False
HighResTextureSmooth=True
LowResTextureSmooth=False
FastTranslucency=True
[GlideDrv.GlideRenderDevice]
Translucency=True
VolumetricLighting=True
ShinySurfaces=True
Coronas=True
HighDetailActors=True
DetailBias=-1.500000
RefreshRate=60Hz
DetailTextures=True
FastUglyRefresh=False
ScreenSmoothing=True
Resolution=Default
[MetalDrv.MetalRenderDevice]
Translucency=True
VolumetricLighting=True
ShinySurfaces=True
Coronas=True
HighDetailActors=True
DetailTextures=False
[OpenGLDrv.OpenGLRenderDevice]
Translucency=True
VolumetricLighting=False
ShinySurfaces=True
Coronas=True
HighDetailActors=True
DetailTextures=True
[D3DDrv.D3DRenderDevice]
Translucency=True
VolumetricLighting=False
ShinySurfaces=True
Coronas=True
HighDetailActors=True
UseMipmapping=True
UseTrilinear=False
UseMultitexture=True
UsePageFlipping=True
UsePalettes=True
UseFullscreen=True
UseGammaCorrection=True
DetailTextures=False
Use3dfx=False
UseTripleBuffering=True
UsePrecache=True
Use32BitTextures=False
[SglDrv.SglRenderDevice]
Translucency=True
VolumetricLighting=False
ShinySurfaces=False
Coronas=True
HighDetailActors=False
ColorDepth=16
DetailTextures=False
FastUglyRefresh=False
TextureDetailBias=Near
VertexLighting=False
[Editor.EditorEngine]
UseSound=True
CacheSizeMegs=6
GridEnabled=True
SnapVertices=True
SnapDistance=10.000000
GridSize=(X=16.000000,Y=16.000000,Z=16.000000)
RotGridEnabled=True
RotGridSize=(Pitch=1024,Yaw=1024,Roll=1024)
GameCommandLine=-log
FovAngleDegrees=90.000000
GodMode=True
AutoSave=False
AutoSaveTimeMinutes=5
AutoSaveIndex=6
C_WorldBox=(R=0,G=0,B=107,A=0)
C_GroundPlane=(R=0,G=0,B=63,A=0)
C_GroundHighlight=(R=0,G=0,B=127,A=0)
C_BrushWire=(R=255,G=63,B=63,A=0)
C_Pivot=(R=0,G=255,B=0,A=0)
C_Select=(R=0,G=0,B=127,A=0)
C_AddWire=(R=127,G=127,B=255,A=0)
C_SubtractWire=(R=255,G=192,B=63,A=0)
C_GreyWire=(R=163,G=163,B=163,A=0)
C_Invalid=(R=163,G=163,B=163,A=0)
C_ActorWire=(R=127,G=63,B=0,A=0)
C_ActorHiWire=(R=255,G=127,B=0,A=0)
C_White=(R=255,G=255,B=255,A=0)
C_SemiSolidWire=(R=127,G=255,B=0,A=0)
C_NonSolidWire=(R=63,G=192,B=32,A=0)
C_WireGridAxis=(R=119,G=119,B=119,A=0)
C_ActorArrow=(R=163,G=0,B=0,A=0)
C_ScaleBox=(R=151,G=67,B=11,A=0)
C_ScaleBoxHi=(R=223,G=149,B=157,A=0)
C_Mover=(R=255,G=0,B=255,A=0)
C_OrthoBackground=(R=163,G=163,B=163,A=0)
C_Current=(R=0,G=0,B=0,A=0)
C_BrushVertex=(R=0,G=0,B=0,A=0)
C_BrushSnap=(R=0,G=0,B=0,A=0)
C_Black=(R=0,G=0,B=0,A=0)
C_Mask=(R=0,G=0,B=0,A=0)
C_WireBackground=(R=0,G=0,B=0,A=0)
C_ZoneWire=(R=0,G=0,B=0,A=0)
EditPackages=Core
EditPackages=Engine
EditPackages=Editor
EditPackages=UWindow
EditPackages=Fire
EditPackages=IpDrv
EditPackages=UWeb
EditPackages=UBrowser
EditPackages=UnrealShare
EditPackages=UnrealI
EditPackages=UMenu
EditPackages=IpServer
EditPackages=Botpack
EditPackages=UTServerAdmin
EditPackages=UTMenu
EditPackages=UTBrowser
[UMenu.UnrealConsole]
RootWindow=UMenu.UMenuRootWindow
UWindowKey=IK_Esc
ShowDesktop=True
[UMenu.UMenuMenuBar]
ShowHelp=True
GameUMenuDefault="UTMenu.UTGameMenu"
MultiplayerUMenuDefault="UTMenu.UTMultiplayerMenu"
OptionsUMenuDefault="UTMenu.UTOptionsMenu"
[Botpack.ChallengeBotInfo]
Difficulty=1
[Botpack.DeathMatchPlus]
bNoviceMode=True
bHardCoreMode=True
bUseTranslocator=False
bCoopWeaponMode=False
bForceRespawn=True
TimeLimit=323
FragLimit=321
InitialBots=7
MinPlayers=11
bTournament=False
[Botpack.CTFGame]
bUseTranslocator=True
bCoopWeaponMode=True
GoalTeamScore=3
bForceRespawn=False
TimeLimit=223
[Botpack.Domination]
bDumbDown=True
bUseTranslocator=True
bCoopWeaponMode=True
GoalTeamScore=100
bForceRespawn=False
TimeLimit=423
[Botpack.Assault]
bUseTranslocator=False
bCoopWeaponMode=True
bForceRespawn=True
TimeLimit=123
[Botpack.TeamGamePlus]
bBalanceTeams=True
GoalTeamScore=30
bPlayersBalanceTeams=True
MaxTeamSize=4
[Engine.GameInfo]
bLowGore=False
bVeryLowGore=False
MaxSpectators=1
MaxPlayers=4
ServerLogName=server.log
bWorldLog=True
bBatchLocal=False
DemoBuild=0
DemoHasTuts=0
PlayerViewDelay=1.000000
PlayerSpeechDelay=0.300000
PlayerTauntDelay=2.000000
bLogAdminActions=False
LoginDelaySeconds=0.000000
MaxLoginAttempts=0
ActionToTake=DO_Nothing
IPPolicies[0]=ACCEPT,*
IPPolicies[1]=
GamePassword=TestPwd
[UnrealShare.UnrealGameOptionsMenu]
bCanModifyGore=True
[UBrowser.UBrowserMainClientWindow]
LANTabName=UBrowserLAN
ServerListNames[0]=UBrowserUT
ServerListNames[1]=UBrowserLAN
ServerListNames[2]=UBrowserPopulated
ServerListNames[3]=UBrowserDeathmatch
ServerListNames[4]=UBrowserTeamGames
ServerListNames[5]=UBrowserCTF
ServerListNames[6]=UBrowserDOM
ServerListNames[7]=UBrowserAS
ServerListNames[8]=UBrowserLMS
ServerListNames[9]=UBrowserAll
ServerListNames[10]=None
ServerListNames[11]=None
ServerListNames[12]=None
ServerListNames[13]=None
ServerListNames[14]=None
ServerListNames[15]=None
ServerListNames[16]=None
ServerListNames[17]=None
ServerListNames[18]=None
ServerListNames[19]=None
[UBrowserUT]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,bCompatibleServersOnly=True
[UBrowserLAN]
ListFactories[0]=UBrowser.UBrowserLocalFact,BeaconProduct=ut
URLAppend=?LAN
AutoRefreshTime=10
bNoAutoSort=True
[UBrowserPopulated]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,MinPlayers=1,bCompatibleServersOnly=True
[UBrowserDeathmatch]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=DeathMatchPlus,bCompatibleServersOnly=True
[UBrowserTeamGames]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=TeamGamePlus,bCompatibleServersOnly=True
[UBrowserCTF]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=CTFGame,bCompatibleServersOnly=True
[UBrowserDOM]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=Domination,bCompatibleServersOnly=True
[UBrowserAS]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=Assault,bCompatibleServersOnly=True
[UBrowserLMS]
ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=LastManStanding,bCompatibleServersOnly=True
[UBrowserAll]
ListFactories[0]=UBrowser.UBrowserGSpyFact,MasterServerAddress=unreal.epicgames.com,MasterServerTCPPort=28900,Region=0,GameName=ut
ListFactories[1]=UBrowser.UBrowserGSpyFact,MasterServerAddress=master0.gamespy.com,MasterServerTCPPort=28900,Region=0,GameName=ut
bHidden=True
bFallbackFactories=True
[UTMenu.UTMultiplayerMenu]
OnlineServices[0]=LOCALIZE,MPlayer
OnlineServices[1]=LOCALIZE,Heat
OnlineServices[2]=LOCALIZE,WON
[UWeb.WebServer]
Applications[0]=UTServerAdmin.UTServerAdmin
ApplicationPaths[0]=/ServerAdmin
Applications[1]=UTServerAdmin.UTImageServer
ApplicationPaths[1]=/images
DefaultApplication=0
bEnabled=True
ListenPort=9999
MaxConnections=30
[UBrowser.UBrowserHTTPClient]
ProxyServerAddress=
ProxyServerPort=
[UTServerAdmin.UTServerAdmin]
AdminUsername=TestAdminUser
AdminPassword=TestAdminPwd

113
test/test_cod4.py Normal file
View File

@@ -0,0 +1,113 @@
# 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 path, chdir
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
from pathlib import Path
import shutil
from src import pygamecfg
from src.pygamecfg.__main__ import fct_main
from src.pygamecfg.game_cod4 import COD4KeyNotFoundError
testdir_path = Path(__file__).parent.resolve()
class Testtest_cod4(unittest.TestCase):
def setUp(self) -> None:
chdir(testdir_path.parent.resolve())
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_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()
def test_defect_READ_net_port_NONEXISTS(self):
with self.assertRaises(COD4KeyNotFoundError):
fct_main(["-g", "cod4", "-b", "test/tmp/COD4", "GetOption", "net_port"])
def test_normal_WRITE_net_port_NONEXISTS(self):
fct_main(["-g", "cod4", "-b", "test/tmp/COD4", "SetOption", "net_port", "132"])
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "cod4", "-b", "test/tmp/COD4", "GetOption", "net_port"])
self.assertEqual("132\n", capted_stdout.getvalue())
self.assertEqual("", capted_stderr.getvalue())
def test_normal_WRITE_oldschool(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "cod4", "-b", "test/tmp/COD4", "SetOption", "oldschool", "1"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("setting option <oldschool> to: 1\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", "oldschool"])
# /!\ 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())
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
fct_main(["-g", "cod4", "-b", "test/tmp/COD4", "SetOption", "oldschool", "0"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("setting option <oldschool> to: 0\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", "oldschool"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("0\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", "SetOption", "oldschool", "1"])
# /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep
self.assertEqual("setting option <oldschool> to: 1\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", "oldschool"])
# /!\ 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())

38
test/test_gen.py Normal file
View File

@@ -0,0 +1,38 @@
# 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 chdir
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
from pathlib import Path
from src import pygamecfg
from src.pygamecfg.__main__ import fct_main
testdir_path = Path(__file__).parent.resolve()
class Testtest_gen(unittest.TestCase):
def setUp(self) -> None:
chdir(testdir_path.parent.resolve())
def test_version(self):
self.assertNotEqual(pygamecfg.__version__, "?.?.?")
def test_normal_help(self):
with self.assertRaises(SystemExit) as cm:
fct_main(["-h"])
def test_defect_nogame(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
with self.assertRaises(SystemExit) as cm:
fct_main(["GetOption", "test"])
self.assertIn("pygamecfg: error: the following arguments are required: -g/--game", capted_stderr.getvalue())
self.assertIn("", capted_stdout.getvalue())

View File

@@ -7,15 +7,318 @@
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
import unittest
from os import path, chdir
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
print(__name__)
print(__package__)
from pathlib import Path
import shutil
from src import pygamecfg
from src.pygamecfg.__main__ import fct_main
testdir_path = Path(__file__).parent.resolve()
class Testtest_module(unittest.TestCase):
def test_version(self):
self.assertNotEqual(pygamecfg.__version__, "?.?.?")
class Testtest_ut99(unittest.TestCase):
def setUp(self) -> None:
chdir(testdir_path.parent.resolve())
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/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()
)
self.assertEqual("", capted_stderr.getvalue())
def test_normal_ServerActors(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
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",
capted_stdout.getvalue(),
)
self.assertEqual("", capted_stderr.getvalue())
def test_normal_Port(self):
with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr:
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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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())