From b2fe15aa2de4307ac4b1a0536be4dadcc36f17b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brostr=C3=B6m=2EA=20=7C=20Evul?= Date: Mon, 29 Jul 2024 10:47:47 +0200 Subject: [PATCH] Updated tools and workflows (#255) * Updated tools and workflows * Fixed script_component.hpp * Run all validations always * Arma is now testing in action caption * Added manual deploy --- .github/workflows/arma.yml | 6 +- .github/workflows/hemtt-deploy.yml | 39 ++ .github/workflows/hemtt.yml | 38 ++ .../pronelauncher/functions/fnc_onKeyDown.sqf | 2 +- .../functions/script_component.hpp | 1 - tools/check_strings.py | 53 +- tools/compileExtensions.py | 97 +++ tools/config_style_checker.py | 46 +- tools/config_validator.py | 2 +- tools/deploy_both.ps1 | 46 -- tools/deploy_legacy.ps1 | 29 - tools/extract_dependencies.py | 111 ++++ tools/generate_airfriction_config.py | 555 ++++++++++++++++++ tools/publish.py | 122 ++++ tools/search_undefinedFunctions.py | 101 ++-- tools/setup.bat | 16 + tools/setupEditorPreviewImages.py | 84 --- tools/sqf_linter.py | 99 ++-- tools/sqf_validator.py | 19 +- tools/sqfvmChecker.py | 96 +++ tools/stringtable_validator.py | 39 +- tools/stringtablediag.py | 2 +- tools/updateIncludes.py | 25 + 23 files changed, 1335 insertions(+), 293 deletions(-) create mode 100644 .github/workflows/hemtt-deploy.yml create mode 100644 .github/workflows/hemtt.yml delete mode 100644 addons/pronelauncher/functions/script_component.hpp create mode 100644 tools/compileExtensions.py delete mode 100644 tools/deploy_both.ps1 delete mode 100644 tools/deploy_legacy.ps1 create mode 100644 tools/extract_dependencies.py create mode 100644 tools/generate_airfriction_config.py create mode 100644 tools/publish.py create mode 100644 tools/setup.bat delete mode 100644 tools/setupEditorPreviewImages.py create mode 100644 tools/sqfvmChecker.py create mode 100644 tools/updateIncludes.py diff --git a/.github/workflows/arma.yml b/.github/workflows/arma.yml index a2f60ae7..ffc15edf 100644 --- a/.github/workflows/arma.yml +++ b/.github/workflows/arma.yml @@ -1,4 +1,4 @@ -name: Arma +name: Testing on: push: @@ -13,13 +13,17 @@ jobs: - name: Checkout the source code uses: actions/checkout@master - name: Validate SQF + if: always() run: python3 tools/sqf_validator.py - name: Validate Config + if: always() run: python3 tools/config_style_checker.py - name: Validate Stringtables + if: always() run: python3 tools/stringtable_validator.py continue-on-error: true - name: Check Strings + if: always() run: python3 tools/check_strings.py # - name: Check for BOM # uses: arma-actions/bom-check@master diff --git a/.github/workflows/hemtt-deploy.yml b/.github/workflows/hemtt-deploy.yml new file mode 100644 index 00000000..bf11e3f7 --- /dev/null +++ b/.github/workflows/hemtt-deploy.yml @@ -0,0 +1,39 @@ +name: Deploy + +on: + workflow_dispatch: + inputs: + environment: + description: 'Selected deploy enviroment' + required: true + default: 'dev' + type: choice + options: + - dev + - live +jobs: + windows: + runs-on: windows-latest + steps: + - name: Checkout the source code + uses: actions/checkout@v4 + - name: Setup HEMTT + uses: arma-actions/hemtt@v1 + - name: Checkout pull request + uses: actions/checkout@v4 + - name: Run HEMTT release + run: hemtt release + #- name: Rename build folder + # run: mv .hemttout/build .hemttout/@cav + - name: DEBUG ending... + run: | + Write-Host "Environment: $ENVIRONMENT" + Get-Content -Path .hemttout + env: + ENVIRONMENT: ${{ inputs.environment }} + shell: pwsh + #- name: Upload Artifact + # uses: actions/upload-artifact@v4 + # with: + # name: cav-${{ github.sha }} + # path: .hemttout/@* \ No newline at end of file diff --git a/.github/workflows/hemtt.yml b/.github/workflows/hemtt.yml new file mode 100644 index 00000000..3ca73d8e --- /dev/null +++ b/.github/workflows/hemtt.yml @@ -0,0 +1,38 @@ +name: HEMTT + +on: + push: + branches: + - main + pull_request_target: + +jobs: + windows: + runs-on: windows-latest + steps: + - name: Checkout the source code + uses: actions/checkout@v4 + - name: Setup HEMTT + uses: arma-actions/hemtt@v1 + - name: Checkout pull request + uses: actions/checkout@v4 + if: ${{ github.event_name == 'pull_request_target' }} + with: + path: pullrequest + ref: 'refs/pull/${{ github.event.number }}/merge' + - name: Replace addons with pull request addons + if: ${{ github.event_name == 'pull_request_target' }} + run: | + rm -r addons\ + rm -r include\ + xcopy /e /h /q pullrequest\addons addons\ + xcopy /e /h /q pullrequest\include include\ + - name: Run HEMTT build + run: hemtt build + - name: Rename build folder + run: mv .hemttout/build .hemttout/@cav + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: cav-${{ github.sha }} + path: .hemttout/@* \ No newline at end of file diff --git a/addons/pronelauncher/functions/fnc_onKeyDown.sqf b/addons/pronelauncher/functions/fnc_onKeyDown.sqf index fc92c1ed..7f35b081 100644 --- a/addons/pronelauncher/functions/fnc_onKeyDown.sqf +++ b/addons/pronelauncher/functions/fnc_onKeyDown.sqf @@ -1,4 +1,4 @@ -#include "script_component.hpp" +#include "..\script_component.hpp" /* * Author: PiZZADOX, Jonpas * Handles keyDown EH for overriding engine stance changes when in AT launcher stance. diff --git a/addons/pronelauncher/functions/script_component.hpp b/addons/pronelauncher/functions/script_component.hpp deleted file mode 100644 index cdc3dede..00000000 --- a/addons/pronelauncher/functions/script_component.hpp +++ /dev/null @@ -1 +0,0 @@ -#include "../script_component.hpp" diff --git a/tools/check_strings.py b/tools/check_strings.py index 09897781..bfe7ddf8 100644 --- a/tools/check_strings.py +++ b/tools/check_strings.py @@ -11,14 +11,15 @@ def getDefinedStrings(filepath): # print("getDefinedStrings {0}".format(filepath)) with open(filepath, 'r', encoding="latin-1") as file: content = file.read() - srch = re.compile('Key ID\=\"(STR_CAV_[_a-zA-Z0-9]*)"', re.IGNORECASE) + srch = re.compile('Key ID\=\"(STR_ACE_[_a-zA-Z0-9]*)"', re.IGNORECASE) modStrings = srch.findall(content) modStrings = [s.lower() for s in modStrings] return modStrings def getStringUsage(filepath): - selfmodule = (re.search('addons[\W]*([_a-zA-Z0-9]*)', filepath)).group(1) - # print("Checking {0} from {1}".format(filepath,selfmodule)) + selfmodule = (re.search('(addons|optionals)[\W]*([_a-zA-Z0-9]*)', filepath)).group(2) + submodule = (re.search(f'(addons|optionals)[\W]*{selfmodule}[\W]*([_a-zA-Z0-9]*)', filepath)).group(2) + # print(f"Checking {filepath} from {selfmodule} ({submodule})") fileStrings = [] with open(filepath, 'r') as file: @@ -27,7 +28,7 @@ def getStringUsage(filepath): srch = re.compile('(STR_CAV_[_a-zA-Z0-9]*)', re.IGNORECASE) fileStrings = srch.findall(content) - srch = re.compile('[^E][CL]STRING\(([_a-zA-Z0-9]*)\)', re.IGNORECASE) + srch = re.compile('[^EB][CL]STRING\(([_a-zA-Z0-9]*)\)', re.IGNORECASE) modStrings = srch.findall(content) for localString in modStrings: fileStrings.append("STR_CAV_{0}_{1}".format(selfmodule, localString)) @@ -37,6 +38,11 @@ def getStringUsage(filepath): for (exModule, exString) in exStrings: fileStrings.append("STR_CAV_{0}_{1}".format(exModule, exString)) + srch = re.compile('SUB[CL]STRING\(([_a-zA-Z0-9]*)\)') + subStrings = srch.findall(content) + for (subString) in subStrings: + fileStrings.append(f"STR_CAV_{submodule}_{subString}") + srch = re.compile('IGNORE_STRING_WARNING\([\'"]*([_a-zA-Z0-9]*)[\'"]*\)') ignoreWarnings = srch.findall(content) @@ -51,23 +57,24 @@ def main(argv): allDefinedStrings = [] allUsedStrings = [] - # Allow running from root directory as well as from inside the tools directory - rootDir = "../addons" - if (os.path.exists("addons")): - rootDir = "addons" - - for root, dirnames, filenames in os.walk(rootDir): - for filename in fnmatch.filter(filenames, '*.sqf'): - sqf_list.append(os.path.join(root, filename)) - for filename in fnmatch.filter(filenames, '*.cpp'): - sqf_list.append(os.path.join(root, filename)) - for filename in fnmatch.filter(filenames, '*.hpp'): - sqf_list.append(os.path.join(root, filename)) - for filename in fnmatch.filter(filenames, '*.h'): - sqf_list.append(os.path.join(root, filename)) - - for filename in fnmatch.filter(filenames, '*.xml'): - xml_list.append(os.path.join(root, filename)) + for folder in ['addons', 'optionals']: + # Allow running from root directory as well as from inside the tools directory + rootDir = "../" + folder + if (os.path.exists(folder)): + rootDir = folder + + for root, dirnames, filenames in os.walk(rootDir): + for filename in fnmatch.filter(filenames, '*.sqf'): + sqf_list.append(os.path.join(root, filename)) + for filename in fnmatch.filter(filenames, '*.cpp'): + sqf_list.append(os.path.join(root, filename)) + for filename in fnmatch.filter(filenames, '*.hpp'): + sqf_list.append(os.path.join(root, filename)) + for filename in fnmatch.filter(filenames, '*.h'): + sqf_list.append(os.path.join(root, filename)) + + for filename in fnmatch.filter(filenames, '*.xml'): + xml_list.append(os.path.join(root, filename)) for filename in xml_list: allDefinedStrings = allDefinedStrings + getDefinedStrings(filename) @@ -77,6 +84,8 @@ def main(argv): allDefinedStrings = list(sorted(set(allDefinedStrings))) allUsedStrings = list(sorted(set(allUsedStrings))) + if ("str_cav_tagging_name" in allUsedStrings): allUsedStrings.remove("str_cav_tagging_name") # Handle tagging macro + print("-----------") countUnusedStrings = 0 countUndefinedStrings = 0 @@ -98,4 +107,4 @@ def main(argv): return countUndefinedStrings if __name__ == "__main__": - main(sys.argv) \ No newline at end of file + main(sys.argv) diff --git a/tools/compileExtensions.py b/tools/compileExtensions.py new file mode 100644 index 00000000..8864d98e --- /dev/null +++ b/tools/compileExtensions.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# vim: set fileencoding=utf-8 : + +# compileExtensions.py (from acre2's make.py) + +############################################################################### + +# The MIT License (MIT) + +# Copyright (c) 2013-2014 Ryan Schultz + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +############################################################################### + +import sys +import os.path +import subprocess +import shutil +import time +import timeit + +if sys.platform == "win32": + import winreg + + +def compile_extensions(force_build): + originalDir = os.getcwd() + extensions_root = os.path.join(os.path.dirname(os.getcwd()), "extensions") + os.chdir(extensions_root) + print("\nCompiling extensions in {} with rebuild:{}\n".format(extensions_root, force_build)) + + if shutil.which("git") == None: + print("Failed to find Git!") + return + if shutil.which("cmake") == None: + print("Failed to find CMake!") + return + if shutil.which("msbuild") == None: + print("Failed to find MSBuild!") + return + + try: + buildType = "rebuild" if force_build else "build" + # 32-bit + vcproj32 = os.path.join(extensions_root, "vcproj32") + if not os.path.exists(vcproj32): + os.mkdir(vcproj32) + os.chdir(vcproj32) + subprocess.call(["cmake", "..", "-A", "Win32"]) # note: cmake will update ace_version stuff + subprocess.call(["msbuild", "ACE.sln", "/m", "/t:{}".format(buildType), "/p:Configuration=Release"]) + + # 64-bit + vcproj64 = os.path.join(extensions_root, "vcproj64") + if not os.path.exists(vcproj64): + os.mkdir(vcproj64) + os.chdir(vcproj64) + subprocess.call(["cmake", "..", "-A", "x64"]) + subprocess.call(["msbuild", "ACE.sln", "/m", "/t:{}".format(buildType), "/p:Configuration=Release"]) + except Exception as e: + print("Error: COMPILING EXTENSIONS - {}".format(e)) + raise + finally: + os.chdir(originalDir) + + +def main(argv): + if "force" in argv: + argv.remove("force") + force_build = True + else: + force_build = False + + compile_extensions(force_build) + + +if __name__ == "__main__": + start_time = timeit.default_timer() + main(sys.argv) + print("\nTotal Program time elapsed: {0} sec".format(timeit.default_timer() - start_time)) + input("Press Enter to continue...") diff --git a/tools/config_style_checker.py b/tools/config_style_checker.py index 03d8f222..afa78a2d 100644 --- a/tools/config_style_checker.py +++ b/tools/config_style_checker.py @@ -16,6 +16,14 @@ def pushClosing(t): def popClosing(): closing << closingStack.pop() + reIsClass = re.compile(r'^\s*class(.*)') + reIsClassInherit = re.compile(r'^\s*class(.*):') + reIsClassBody = re.compile(r'^\s*class(.*){') + reBadColon = re.compile(r'\s*class (.*) :') + reSpaceAfterColon = re.compile(r'\s*class (.*): ') + reSpaceBeforeCurly = re.compile(r'\s*class (.*) {') + reClassSingleLine = re.compile(r'\s*class (.*)[{;]') + with open(filepath, 'r', encoding='utf-8', errors='ignore') as file: content = file.read() @@ -118,6 +126,23 @@ def popClosing(): if brackets_list.count('{') != brackets_list.count('}'): print("ERROR: A possible missing curly brace {{ or }} in file {0} {{ = {1} }} = {2}".format(filepath,brackets_list.count('{'),brackets_list.count('}'))) bad_count_file += 1 + + file.seek(0) + for lineNumber, line in enumerate(file.readlines()): + if reIsClass.match(line): + if reBadColon.match(line): + print(f"WARNING: bad class colon {filepath} Line number: {lineNumber+1}") + # bad_count_file += 1 + if reIsClassInherit.match(line): + if not reSpaceAfterColon.match(line): + print(f"WARNING: bad class missing space after colon {filepath} Line number: {lineNumber+1}") + if reIsClassBody.match(line): + if not reSpaceBeforeCurly.match(line): + print(f"WARNING: bad class inherit missing space before curly braces {filepath} Line number: {lineNumber+1}") + if not reClassSingleLine.match(line): + print(f"WARNING: bad class braces placement {filepath} Line number: {lineNumber+1}") + # bad_count_file += 1 + return bad_count_file def main(): @@ -131,16 +156,17 @@ def main(): parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default="") args = parser.parse_args() - # Allow running from root directory as well as from inside the tools directory - rootDir = "../addons" - if (os.path.exists("addons")): - rootDir = "addons" - - for root, dirnames, filenames in os.walk(rootDir + '/' + args.module): - for filename in fnmatch.filter(filenames, '*.cpp'): - sqf_list.append(os.path.join(root, filename)) - for filename in fnmatch.filter(filenames, '*.hpp'): - sqf_list.append(os.path.join(root, filename)) + for folder in ['addons', 'optionals']: + # Allow running from root directory as well as from inside the tools directory + rootDir = "../" + folder + if (os.path.exists(folder)): + rootDir = folder + + for root, dirnames, filenames in os.walk(rootDir + '/' + args.module): + for filename in fnmatch.filter(filenames, '*.cpp'): + sqf_list.append(os.path.join(root, filename)) + for filename in fnmatch.filter(filenames, '*.hpp'): + sqf_list.append(os.path.join(root, filename)) for filename in sqf_list: bad_count = bad_count + check_config_style(filename) diff --git a/tools/config_validator.py b/tools/config_validator.py index 71076007..50722319 100644 --- a/tools/config_validator.py +++ b/tools/config_validator.py @@ -13,7 +13,7 @@ ######## GLOBALS ######### MAINPREFIX = "Z" -PREFIX = "cav" +PREFIX = "ACE" ########################## def Fract_Sec(s): diff --git a/tools/deploy_both.ps1 b/tools/deploy_both.ps1 deleted file mode 100644 index c22ec925..00000000 --- a/tools/deploy_both.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -if ( ![bool](Test-Path -Path "P:") ) { - Write-Host "Arma 3 P: is not mounted..." -ForegroundColor Red - exit 1 -} - -cd $PSScriptRoot\.. - -# Clear folder -Remove-Item releases -Force -Recurse -ErrorAction 'silentlycontinue' - -$tagVersion = git describe --tags --abbrev=0 -Write-Host "Build release versions for tag $tagVersion" -ForegroundColor Yellow - -$version = $tagVersion.Split(".") -$versionMajor = $version[0] -$versionMinor = $version[1] -$versionPatch = $version[2] -$versionBuild = 0 - -# Set version -Write-Host "Build both Hemtt and PBO Project Releases" -ForegroundColor Blue - -# Hemtt -Write-Host "Build Hemtt" -ForegroundColor Blue -cd $PSScriptRoot -.\deploy.ps1 - -# Rename hemtt zip -cd $PSScriptRoot\.. - -Write-Host "Renaming and saving hemtt release in temp" -New-Item -ItemType Directory -Path tools\temp -Force >$null 2>&1 -Rename-Item -Path .\releases\7CavAddon_$tagVersion.zip -NewName 7CavAddon_$tagVersion-hemtt.zip -Move-Item -Path .\releases\7CavAddon_$tagVersion-hemtt.zip -Destination .\tools\temp\. - - -# Pbo Project -Write-Host "Build Pbo Project" -ForegroundColor Blue -cd $PSScriptRoot -.\deploy_legacy.ps1 -cd $PSScriptRoot\.. - -# Rename hemtt zip -Rename-Item -Path .\releases\7CavAddon_$tagVersion.zip -NewName 7CavAddon_$tagVersion-pboProject.zip -Move-Item -Path .\tools\temp\7CavAddon_$tagVersion-hemtt.zip -Destination .\releases\. -Remove-Item .\tools\temp -Force -Recurse -ErrorAction 'SilentlyContinue' \ No newline at end of file diff --git a/tools/deploy_legacy.ps1 b/tools/deploy_legacy.ps1 deleted file mode 100644 index 9a0da7d7..00000000 --- a/tools/deploy_legacy.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -if ( ![bool](Test-Path -Path "P:") ) { - Write-Host "Arma 3 P: is not mounted..." -ForegroundColor Red - exit 1 -} - -# Set version -$tagVersion = git describe --tags --abbrev=0 -Write-Host "Build version $tagVersion" - -$version = $tagVersion.Split(".") -$versionMajor = $version[0] -$versionMinor = $version[1] -$versionPatch = $version[2] -$versionBuild = 0 - -sed -e "s/DevBuild/$tagVersion/g" "../mod.cpp" | Set-Content "../mod.cpp" - -Set-Content -Path '../addons/main/script_version.hpp' -Value "#define MAJOR $versionMajor -#define MINOR $versionMinor -#define PATCHLVL $versionPatch -#define BUILD $versionBuild" - -# Build release -py make.py release ci - -# Clean up -Write-Host "Restoring version files..." -git checkout origin/main ../addons/main/script_version.hpp -git checkout origin/main ../mod.cpp diff --git a/tools/extract_dependencies.py b/tools/extract_dependencies.py new file mode 100644 index 00000000..e28eb6cd --- /dev/null +++ b/tools/extract_dependencies.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +# Author: Jonpas +# Extracts dependencies to "docs/_includes/dependencies_list.md" for use with Jekyll include statement. +# Use the following line to add dependencies to an ACE3 feature page: {% include dependencies_list.md component="" %} + + +import os +import sys +import re + + +def get_dependencies(line): + dependencies = re.findall(r'"(.*?)"', line) + return dependencies + + +def main(): + if "--markdown" not in sys.argv: + print(""" + ############################################# + # Extract ACE3 Module Dependencies # + # (for Jekyll include) # + ############################################# + """) + + # Mod paths + script_path = os.path.realpath(__file__) + project_path = os.path.dirname(os.path.dirname(script_path)) + addons_path = os.path.join(project_path, "addons") + optionals_path = os.path.join(project_path, "optionals") + + # Documentation paths + include_path = os.path.join(project_path, "docs", "_includes") + dependencies_path = os.path.join(include_path, "dependencies_list.md") + + # Prepare files and paths list + if not os.path.exists(include_path): + print("Jekyll documentation not found!") + sys.exit(0) + + open(dependencies_path, "w", newline="\n").close() + if os.path.exists(addons_path): + addons = sorted(next(os.walk(addons_path))[1]) + if os.path.exists(optionals_path): + addons += ["."] + sorted(next(os.walk(optionals_path))[1]) + + dependencies_path_current = dependencies_path + addons_path_current = addons_path + + # Iterate through folders in the addons directories + for folder in addons: + # Ignore "main" component + if folder == "main": + continue + + # Change to optionals list on "." separator + if folder == ".": + if addons_path_current == addons_path: + addons_path_current = optionals_path + continue + + # Open config.cpp file and extract dependencies + data = [] + configfile = os.path.join(addons_path_current, folder, "config.cpp") + + if os.path.exists(configfile): + with open(os.path.join(addons_path_current, folder, "config.cpp")) as file: + match = False + for line in file: + # One-line + if not match and re.match(r"\s+requiredAddons\[\]\ = {.+?};", line): + data += get_dependencies(line) + break + # Multi-line + else: + if re.match(r"\s+requiredAddons\[\]\ = {", line): + # First line + match = True + data += get_dependencies(line) + continue + elif match and re.match(r"\s+};", line): + # Final line + data += get_dependencies(line) + match = False + break + elif match: + # All lines between + data += get_dependencies(line) + continue + + data = "`, `".join(data) + data = "`{}`".format(data) + + jekyll_statement = "".join([ + "{% if include.component == \"" + folder + "\" %}\n", + "- {}\n".format(data.replace(", ", "\n- ")), + "{% endif %}\n" + ]) + + with open(dependencies_path_current, "a", newline="\n") as file: + file.writelines([jekyll_statement, "\n"]) + + if "--markdown" not in sys.argv: + print("{}: {}".format(folder, data)) + else: + print(jekyll_statement) + + +if __name__ == "__main__": + main() diff --git a/tools/generate_airfriction_config.py b/tools/generate_airfriction_config.py new file mode 100644 index 00000000..70f87a22 --- /dev/null +++ b/tools/generate_airfriction_config.py @@ -0,0 +1,555 @@ +import math + +def retard(dm, bc, v): + mach = v / 340.276 + CDs = [] + Ms = [] + if (dm) == 1: + CDs = [0.2629, 0.2558, 0.2487, 0.2413, 0.2344, 0.2278, 0.2214, 0.2155, 0.2104, 0.2061, 0.2032, 0.2020, 0.2034, 0.2165, 0.2230, 0.2313, 0.2417, 0.2546, 0.2706, 0.2901, 0.3136, 0.3415, 0.3734, 0.4084, 0.4448, 0.4805, 0.5136, 0.5427, 0.5677, 0.5883, 0.6053, 0.6191, 0.6393, 0.6518, 0.6589, 0.6621, 0.6625, 0.6607, 0.6573, 0.6528, 0.6474, 0.6413, 0.6347, 0.6280, 0.6210, 0.6141, 0.6072, 0.6003, 0.5934, 0.5867, 0.5804, 0.5743, 0.5685, 0.5630, 0.5577, 0.5527, 0.5481, 0.5438, 0.5397, 0.5325, 0.5264, 0.5211, 0.5168, 0.5133, 0.5105, 0.5084, 0.5067, 0.5054, 0.5040, 0.5030, 0.5022, 0.5016, 0.5010, 0.5006, 0.4998, 0.4995, 0.4992, 0.4990, 0.4988] + Ms = [0.00, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.70, 0.725, 0.75, 0.775, 0.80, 0.825, 0.85, 0.875, 0.90, 0.925, 0.95, 0.975, 1.0, 1.025, 1.05, 1.075, 1.10, 1.125, 1.15, 1.20, 1.25, 1.30, 1.35, 1.40, 1.45, 1.50, 1.55, 1.60, 1.65, 1.70, 1.75, 1.80, 1.85, 1.90, 1.95, 2.00, 2.05, 2.10, 2.15, 2.20, 2.25, 2.30, 2.35, 2.40, 2.45, 2.50, 2.60, 2.70, 2.80, 2.90, 3.00, 3.10, 3.20, 3.30, 3.40, 3.50, 3.60, 3.70, 3.80, 3.90, 4.00, 4.20, 4.40, 4.60, 4.80, 5.00] + elif (dm) == 2: + CDs = [0.2303, 0.2298, 0.2287, 0.2271, 0.2251, 0.2227, 0.2196, 0.2156, 0.2107, 0.2048, 0.1980, 0.1905, 0.1828, 0.1758, 0.1702, 0.1669, 0.1664, 0.1667, 0.1682, 0.1711, 0.1761, 0.1831, 0.2004, 0.2589, 0.3492, 0.3983, 0.4075, 0.4103, 0.4114, 0.4106, 0.4089, 0.4068, 0.4046, 0.4021, 0.3966, 0.3904, 0.3835, 0.3759, 0.3678, 0.3594, 0.3512, 0.3432, 0.3356, 0.3282, 0.3213, 0.3149, 0.3089, 0.3033, 0.2982, 0.2933, 0.2889, 0.2846, 0.2806, 0.2768, 0.2731, 0.2696, 0.2663, 0.2632, 0.2602, 0.2572, 0.2543, 0.2515, 0.2487, 0.2460, 0.2433, 0.2408, 0.2382, 0.2357, 0.2333, 0.2309, 0.2262, 0.2217, 0.2173, 0.2132, 0.2091, 0.2052, 0.2014, 0.1978, 0.1944, 0.1912, 0.1851, 0.1794, 0.1741, 0.1693, 0.1648] + Ms = [0.00, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.775, 0.80, 0.825, 0.85, 0.875, 0.90, 0.925, 0.95, 0.975, 1.0, 1.025, 1.05, 1.075, 1.10, 1.125, 1.15, 1.175, 1.20, 1.25, 1.30, 1.35, 1.40, 1.45, 1.50, 1.55, 1.60, 1.65, 1.70, 1.75, 1.80, 1.85, 1.90, 1.95, 2.00, 2.05, 2.10, 2.15, 2.20, 2.25, 2.30, 2.35, 2.40, 2.45, 2.50, 2.55, 2.60, 2.65, 2.70, 2.75, 2.80, 2.85, 2.90, 2.95, 3.00, 3.10, 3.20, 3.30, 3.40, 3.50, 3.60, 3.70, 3.80, 3.90, 4.00, 4.20, 4.40, 4.60, 4.80, 5.00] + elif (dm) == 5: + CDs = [0.1710, 0.1719, 0.1727, 0.1732, 0.1734, 0.1730, 0.1718, 0.1696, 0.1668, 0.1637, 0.1603, 0.1566, 0.1529, 0.1497, 0.1473, 0.1463, 0.1489, 0.1583, 0.1672, 0.1815, 0.2051, 0.2413, 0.2884, 0.3379, 0.3785, 0.4032, 0.4147, 0.4201, 0.4278, 0.4338, 0.4373, 0.4392, 0.4403, 0.4406, 0.4401, 0.4386, 0.4362, 0.4328, 0.4286, 0.4237, 0.4182, 0.4121, 0.4057, 0.3991, 0.3926, 0.3861, 0.3800, 0.3741, 0.3684, 0.3630, 0.3578, 0.3529, 0.3481, 0.3435, 0.3391, 0.3349, 0.3269, 0.3194, 0.3125, 0.3060, 0.2999, 0.2942, 0.2889, 0.2838, 0.2790, 0.2745, 0.2703, 0.2662, 0.2624, 0.2588, 0.2553, 0.2488, 0.2429, 0.2376, 0.2326, 0.2280] + Ms = [0.00, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.875, 0.90, 0.925, 0.95, 0.975, 1.0, 1.025, 1.05, 1.075, 1.10, 1.15, 1.20, 1.25, 1.30, 1.35, 1.40, 1.45, 1.50, 1.55, 1.60, 1.65, 1.70, 1.75, 1.80, 1.85, 1.90, 1.95, 2.00, 2.05, 2.10, 2.15, 2.20, 2.25, 2.30, 2.35, 2.40, 2.45, 2.50, 2.60, 2.70, 2.80, 2.90, 3.00, 3.10, 3.20, 3.30, 3.40, 3.50, 3.60, 3.70, 3.80, 3.90, 4.00, 4.20, 4.40, 4.60, 4.80, 5.00] + elif (dm) == 6: + CDs = [0.2617, 0.2553, 0.2491, 0.2432, 0.2376, 0.2324, 0.2278, 0.2238, 0.2205, 0.2177, 0.2155, 0.2138, 0.2126, 0.2121, 0.2122, 0.2132, 0.2154, 0.2194, 0.2229, 0.2297, 0.2449, 0.2732, 0.3141, 0.3597, 0.3994, 0.4261, 0.4402, 0.4465, 0.4490, 0.4497, 0.4494, 0.4482, 0.4464, 0.4441, 0.4390, 0.4336, 0.4279, 0.4221, 0.4162, 0.4102, 0.4042, 0.3981, 0.3919, 0.3855, 0.3788, 0.3721, 0.3652, 0.3583, 0.3515, 0.3447, 0.3381, 0.3314, 0.3249, 0.3185, 0.3122, 0.3060, 0.3000, 0.2941, 0.2883, 0.2772, 0.2668, 0.2574, 0.2487, 0.2407, 0.2333, 0.2265, 0.2202, 0.2144, 0.2089, 0.2039, 0.1991, 0.1947, 0.1905, 0.1866, 0.1794, 0.1730, 0.1673, 0.1621, 0.1574] + Ms = [0.00, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.875, 0.90, 0.925, 0.95, 0.975, 1.0, 1.025, 1.05, 1.075, 1.10, 1.125, 1.15, 1.175, 1.20, 1.225, 1.25, 1.30, 1.35, 1.40, 1.45, 1.50, 1.55, 1.60, 1.65, 1.70, 1.75, 1.80, 1.85, 1.90, 1.95, 2.00, 2.05, 2.10, 2.15, 2.20, 2.25, 2.30, 2.35, 2.40, 2.45, 2.50, 2.60, 2.70, 2.80, 2.90, 3.00, 3.10, 3.20, 3.30, 3.40, 3.50, 3.60, 3.70, 3.80, 3.90, 4.00, 4.20, 4.40, 4.60, 4.80, 5.00] + elif (dm) == 7: + CDs = [0.1198, 0.1197, 0.1196, 0.1194, 0.1193, 0.1194, 0.1194, 0.1194, 0.1193, 0.1193, 0.1194, 0.1193, 0.1194, 0.1197, 0.1202, 0.1207, 0.1215, 0.1226, 0.1242, 0.1266, 0.1306, 0.1368, 0.1464, 0.1660, 0.2054, 0.2993, 0.3803, 0.4015, 0.4043, 0.4034, 0.4014, 0.3987, 0.3955, 0.3884, 0.3810, 0.3732, 0.3657, 0.3580, 0.3440, 0.3376, 0.3315, 0.3260, 0.3209, 0.3160, 0.3117, 0.3078, 0.3042, 0.3010, 0.2980, 0.2951, 0.2922, 0.2892, 0.2864, 0.2835, 0.2807, 0.2779, 0.2752, 0.2725, 0.2697, 0.2670, 0.2643, 0.2615, 0.2588, 0.2561, 0.2533, 0.2506, 0.2479, 0.2451, 0.2424, 0.2368, 0.2313, 0.2258, 0.2205, 0.2154, 0.2106, 0.2060, 0.2017, 0.1975, 0.1935, 0.1861, 0.1793, 0.1730, 0.1672, 0.1618] + Ms = [0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70, 0.725, 0.75, 0.775, 0.80, 0.825, 0.85, 0.875, 0.90, 0.925, 0.95, 0.975, 1.0, 1.025, 1.05, 1.075, 1.10, 1.125, 1.15, 1.20, 1.25, 1.30, 1.35, 1.40, 1.50, 1.55, 1.60, 1.65, 1.70, 1.75, 1.80, 1.85, 1.90, 1.95, 2.00, 2.05, 2.10, 2.15, 2.20, 2.25, 2.30, 2.35, 2.40, 2.45, 2.50, 2.55, 2.60, 2.65, 2.70, 2.75, 2.80, 2.85, 2.90, 2.95, 3.00, 3.10, 3.20, 3.30, 3.40, 3.50, 3.60, 3.70, 3.80, 3.90, 4.00, 4.20, 4.40, 4.60, 4.80, 5.00] + elif (dm) == 8: + CDs = [0.2105, 0.2105, 0.2104, 0.2104, 0.2103, 0.2103, 0.2103, 0.2103, 0.2103, 0.2102, 0.2102, 0.2102, 0.2102, 0.2102, 0.2103, 0.2103, 0.2104, 0.2104, 0.2105, 0.2106, 0.2109, 0.2183, 0.2571, 0.3358, 0.4068, 0.4378, 0.4476, 0.4493, 0.4477, 0.4450, 0.4419, 0.4353, 0.4283, 0.4208, 0.4133, 0.4059, 0.3986, 0.3915, 0.3845, 0.3777, 0.3710, 0.3645, 0.3581, 0.3519, 0.3458, 0.3400, 0.3343, 0.3288, 0.3234, 0.3182, 0.3131, 0.3081, 0.3032, 0.2983, 0.2937, 0.2891, 0.2845, 0.2802, 0.2720, 0.2642, 0.2569, 0.2499, 0.2432, 0.2368, 0.2308, 0.2251, 0.2197, 0.2147, 0.2101, 0.2058, 0.2019, 0.1983, 0.1950, 0.1890, 0.1837, 0.1791, 0.1750, 0.1713] + Ms = [0.00, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.825, 0.85, 0.875, 0.90, 0.925, 0.95, 0.975, 1.0, 1.025, 1.05, 1.075, 1.10, 1.125, 1.15, 1.20, 1.25, 1.30, 1.35, 1.40, 1.45, 1.50, 1.55, 1.60, 1.65, 1.70, 1.75, 1.80, 1.85, 1.90, 1.95, 2.00, 2.05, 2.10, 2.15, 2.20, 2.25, 2.30, 2.35, 2.40, 2.45, 2.50, 2.60, 2.70, 2.80, 2.90, 3.00, 3.10, 3.20, 3.30, 3.40, 3.50, 3.60, 3.70, 3.80, 3.90, 4.00, 4.20, 4.40, 4.60, 4.80, 5.00] + for i in range(len(Ms)): + if Ms[i] >= mach: + previousIdx = max(0, i - 1); + lc = CDs[previousIdx]; + lm = Ms[previousIdx]; + tc = lc + (CDs[i] - lc) * (mach - lm) / (Ms[i] - lm) + return 0.00068418 * (tc / bc) * pow(v, 2) + return 0 + +def distanceAtTOF2(tof, v, dM, bc): + lx = 0 + lt = 0 + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + t = 0 + while t <= tof: + lx = dx + lt = t + drag = retard(dM, bc, v) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * 9.8066 + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + t = t+1/2 + return lx + (tof - lt) * (dx - lx) / (t - lt) + +def velocityAtM(m, v, a): + lx = 0 + lv = 0 + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + while dx <= m: + lx = dx + lv = v + drag = a*pow(v,2) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * 9.8066 + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + return lv + (m - lx) * (v - lv) / (dx - lx) + +def velocityAtM2(m, v, dM, bc): + lx = 0 + lv = 0 + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + while dx <= m: + lx = dx + lv = v + drag = retard(dM, bc, v) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * 9.8066 + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + return lv + (m - lx) * (v - lv) / (dx - lx) + +def velocityTillM(m, v, a, g): + velocities = [0] * round(m + 1) + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + while dx <= m: + velocities[round(dx)] = v + drag = a*pow(v,2) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * g + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + + return velocities + +def velocityTillM2(m, v, dM, bc): + velocities = [0] * round(m + 1) + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + while dx <= m: + velocities[round(dx)] = v + drag = retard(dM, bc, v) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * 9.8066 + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + + return velocities + +def tofAtM(m, v, a): + lx = 0 + lt = 0 + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + t = 0 + while dx <= m: + lx = dx + lt = t + drag = a*pow(v,2) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * 9.8066 + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + t = t+1/2 + return lt + (m - lx) * (t - lt) / (dx - lx) + +def tofAtM2(m, v, dM, bc): + lx = 0 + lt = 0 + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + t = 0 + while dx <= m: + lx = dx + lt = t + drag = retard(dM, bc, v) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * 9.8066 + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + t = t+1/2 + return lt + (m - lx) * (t - lt) / (dx - lx) + +def tofTillM(m, v, a, g): + times = [0] * round(m + 1) + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + t = 0 + while dx <= m: + times[round(dx)] = t + drag = a*pow(v,2) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * g + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + t = t+1/2 + return times + +def tofTillM2(m, v, dM, bc): + times = [0] * round(m + 1) + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + t = 0 + while dx <= m: + times[round(dx)] = t + drag = retard(dM, bc, v) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * 9.8066 + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + t = t+1/2 + return times + +def dropAtM(m, v, a): + lx = 0 + ly = 0 + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + while dx <= m: + lx = dx + ly = dy + drag = a*pow(v,2) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * 9.8066 + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + return ly + (m - lx) * (dy - ly) / (dx - lx) + +def dropAtM2(m, v, dM, bc): + lx = 0 + ly = 0 + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + while dx <= m: + lx = dx + ly = dy + drag = retard(dM, bc, v) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * 9.8066 + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + return ly + (m - lx) * (dy - ly) / (dx - lx) + +def dropTillM(m, v, a, g): + drops = [0] * round(m + 1) + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + while dx < m: + drops[round(dx)] = dy + drag = a*v*v + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * g + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + + return drops + +def dropTillM2(m, v, dM, bc): + drops = [0] * round(m + 1) + dx = 0 + dy = 0 + vx = v + vy = 0 + drag = 0 + while dx < m: + drops[round(dx)] = dy + drag = retard(dM, bc, v) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + vx = vx - (1/2000) * (vx/v) * drag + vy = vy - (1/2000) * (vy/v) * drag + vy = vy + (1/2000) * 9.8066 + v = math.sqrt(vx*vx+vy*vy) + dx = dx+(1/2000)*vx*0.5 + dy = dy+(1/2000)*vy*0.5 + + return drops + +def tofAtMDiff(m, v, a, d, bc): + return tofAtM2(m, v, d, bc)-tofAtM(m, v, a) + +def tofAtMDiffBC(m, v, dM1, bc1, dM2, bc2): + return tofAtM2(m, v, dM1, bc1)-tofAtM2(m, v, dM2, bc2) + +def velocityAtMDiff(m, v, a, d, bc): + return velocityAtM2(m, v, d, bc)-velocityAtM(m, v, a) + +def velocityAtMDiffBC(m, v, dM1, bc1, dM2, bc2): + return velocityAtM2(m, v, dM1, bc1)-velocityAtM2(m, v, dM2, bc2) + +def dragAtV(v, a): + return a*v^2 + +def dragAtV2(v, d, bc): + return retard(d, bc, v) + +def dragAtVDiff(v, a, d, bc): + return retard(d, bc, v)-a*pow(v,2) + +def dropAtMDiff(m, v, a, d, bc): + return dropAtM2(m, v, d, bc)-dropAtM(m, v, a) + +def dropAtMDiffBC(m, v, dM1, bc1, dM2, bc2): + return dropAtM2(m, v, dM1, bc1)-dropAtM2(m, v, dM2, bc2) + +def airFrictionAtV(v, d, bc): + return retard(d, bc, v)/pow(v,2) + +def maxDropDiff(m, v, a, dM, bc): + maxDiff = 0 + drops1 = dropTillM(m, v, a, 9.8066) + drops2 = dropTillM2(m, v, dM, bc) + for d in range(0, m): + maxDiff = max(maxDiff, abs(drops2[d] - drops1[d])) + return maxDiff + +def maxDropDiffG(m, v, a, dM, bc, g): + maxDiff = 0 + drops1 = dropTillM(m, v, a, g) + drops2 = dropTillM2(m, v, dM, bc) + for d in range(0, m): + maxDiff = max(maxDiff, abs(drops2[d] - drops1[d])) + return maxDiff + +def maxDropDiffV(distances, velocities, a, dM, bc): + maxDiff = 0 + for m, v in zip(distances, velocities): + drops1 = dropTillM(m, v, a, 9.8066) + drops2 = dropTillM2(m, v, dM, bc) + for d in range(0, m): + maxDiff = max(maxDiff, abs(drops2[d] - drops1[d])) + return maxDiff + +def maxDropDiffBC(m, v, dM1, bc1, dM2, bc2): + maxDiff = 0 + drops1 = dropTillM2(m, v, dM1, bc1) + drops2 = dropTillM2(m, v, dM2, bc2) + for d in range(0, m): + maxDiff = max(maxDiff, abs(drops2[d] - drops1[d])) + return maxDiff + +def maxVelocityDiff(m, v, a, dM, bc): + maxDiff = 0 + velocities1 = velocityTillM(m, v, a, 9.8066) + velocities2 = velocityTillM2(m, v, dM, bc) + for d in range(0, m): + maxDiff = max(maxDiff, abs(velocities2[d] - velocities1[d])) + return maxDiff + +def maxVelocityDiffG(m, v, a, dM, bc, g): + maxDiff = 0 + velocities1 = velocityTillM(m, v, a, g) + velocities2 = velocityTillM2(m, v, dM, bc) + for d in range(0, m): + maxDiff = max(maxDiff, abs(velocities2[d] - velocities1[d])) + return maxDiff + +def maxVelocityDiffV(distances, velocities, a, dM, bc): + maxDiff = 0 + for m, v in zip(distances, velocities): + velocities1 = velocityTillM(m, v, a, 9.8066) + velocities2 = velocityTillM2(m, v, dM, bc) + for d in range(0, m): + maxDiff = max(maxDiff, abs(velocities2[d] - velocities1[d])) + return maxDiff + +def maxVelocityDiffBC(m, v, dM1, bc1, dM2, bc2): + maxDiff = 0 + velocities1 = velocityTillM2(m, v, dM1, bc1) + velocities2 = velocityTillM2(m, v, dM2, bc2) + for d in range(0, m): + maxDiff = max(maxDiff, abs(velocities2[d] - velocities1[d])) + return maxDiff + +def maxTOFDiff(m, v, a, dM, bc): + maxDiff = 0 + times1 = tofTillM(m, v, a, 9.8066) + times2 = tofTillM2(m, v, dM, bc) + for d in range(0, m): + maxDiff = max(maxDiff, abs(times2[d] - times1[d])) + return maxDiff + +def maxTOFDiffG(m, v, a, dM, bc, g): + maxDiff = 0 + times1 = tofTillM(m, v, a, g) + times2 = tofTillM2(m, v, dM, bc) + for d in range(0, m): + maxDiff = max(maxDiff, abs(times2[d] - times1[d])) + return maxDiff + +def maxTOFDiffV(distances, velocities, a, dM, bc): + maxDiff = 0 + for m, v in zip(distances, velocities): + times1 = tofTillM(m, v, a, 9.8066) + times2 = tofTillM2(m, v, dM, bc) + for d in range(0, m): + maxDiff = max(maxDiff, abs(times2[d] - times1[d])) + return maxDiff + +def maxTOFDiffBC(m, v, dM1, bc1, dM2, bc2): + maxDiff = 0 + times1 = tofTillM2(m, v, dM1, bc1) + times2 = tofTillM2(m, v, dM2, bc2) + for d in range(0, m): + maxDiff = max(maxDiff, abs(times2[d] - times1[d])) + return maxDiff + +def predictAirFriction(mv, thresholdVelocity, dragModel, bc): + airFrictions = [] + for v in range(1,1000): + airFrictions.append(retard(dragModel, bc, v) / pow(v, 2)) + + arr = airFrictions[thresholdVelocity:mv] + return sum(arr)/len(arr) + +def calculateAirFriction(v, dragModel, bc): + return retard(dragModel, bc, v) / pow(v, 2) + +# Each list entry needs to be in the following form: +# [ammo name, list of engagement ranges, list of muzzle velocities, drag model, ballistic coefficient] +ammoList = [["B_556x45_Ball", [300, 500, 500], [750, 870, 910], 7, 0.151], + ["ACE_556x45_Ball_Mk262", [600, 600], [810, 840], 1, 0.361], + ["ACE_556x45_Ball_Mk318", [300, 500, 500], [780, 880, 950], 1, 0.307], + ["ACE_556x45_Ball_M995_AP", [400, 500], [820, 880], 1, 0.310], + ["B_545x39_Ball_F", [400, 500], [735, 892], 7, 0.168], + ["B_580x42_Ball_F", [500, 500], [930, 970], 7, 0.156], + ["ACE_580x42_DBP88_Ball", [950, 950], [890, 900], 7, 0.21], + ["B_65x39_Caseless", [400, 800, 800], [730, 800, 830], 7, 0.263], + ["ACE_65x47_Ball_Scenar", [500, 800], [730, 830], 7, 0.290], + ["ACE_65_Creedmor_Ball", [600, 1000], [750, 860], 7, 0.317], + ["B_762x51_Ball", [500, 800], [700, 833], 7, 0.2], + ["ACE_762x51_Ball_M118LR", [600, 800], [750, 795], 7, 0.243], + ["ACE_762x51_Ball_Mk316_Mod_0", [600, 800], [780, 810], 7, 0.243], + ["ACE_762x51_Ball_Mk319_Mod_0", [600, 800], [840, 910], 1, 0.377], + ["ACE_762x51_Ball_M993_AP", [600, 800], [875, 930], 1, 0.359], + ["ACE_30_06_M1_Ball", [600, 800, 900], [700, 800, 840], 1, 0.494], + ["ACE_7_Remington_Magnum_Ball", [600, 800, 1000], [720, 812, 830], 7, 0.345], + ["ACE_243_Winchester_Ball", [700, 900, 900], [830, 900, 920], 7, 0.278], + ["ACE_762x67_Ball_Mk248_Mod_0", [800, 900, 900], [865, 900, 924], 7, 0.268], + ["ACE_762x67_Ball_Mk248_Mod_1", [800, 900, 900], [847, 867, 877], 7, 0.310], + ["ACE_762x67_Ball_Berger_Hybrid_OTM", [1100, 1200, 1300], [800, 853, 884], 7, 0.368], + ["B_762x54_Ball", [500, 800, 800], [760, 835, 865], 1, 0.4], + ["ACE_762x35_Ball", [400, 500], [620, 675], 1, 0.330], + ["ACE_762x39_Ball", [400, 600], [650, 750], 1, 0.275], + ["ACE_762x54_Ball_7T2", [500, 800, 800], [735, 809, 838], 1, 0.395], + ["ACE_303_Ball", [900, 1000], [748, 765], 1, 0.493], + ["B_93x64_Ball", [900, 1000], [850, 880], 1, 0.368], + ["B_408_Ball", [1600,1600], [862, 872], 7, 0.434], + ["ACE_408_Ball", [1200,1200], [1057, 1067], 7, 0.279], + ["ACE_106x83mm_Ball", [1500, 1500], [955, 965], 1, 0.72], + ["B_338_Ball", [1100, 1300], [880, 925], 7, 0.322], + ["B_338_NM_Ball", [1100, 1300], [790, 820], 7, 0.381], + ["ACE_338_Ball", [1200, 1300], [800, 830], 7, 0.368], + ["ACE_338_Ball_API526", [1200, 1300], [880, 920], 7, 0.29], + ["B_50BW_Ball_F", [300, 400], [510, 596], 1, 0.21], + ["B_127x99_Ball", [1300, 1300], [895, 905], 1, 0.67], + ["ACE_127x99_Ball_AMAX", [1600, 1600], [855, 865], 1, 1.05], + ["B_127x108_Ball", [1300, 1300], [815, 825], 1, 0.63], + ["ACE_762x51_Ball_Subsonic", [200, 300], [305, 340], 7, 0.235], + ["B_9x21_Ball", [200, 300], [380, 420], 1, 0.165], + ["ACE_9x18_Ball_57N181S", [100, 200, 200], [298, 330, 350], 1, 0.125], + ["ACE_9x19_Ball", [100, 200, 200], [340, 370, 400], 1, 0.165], + ["ACE_10x25_Ball", [200, 300, 300], [360, 400, 430], 1, 0.189], + ["ACE_765x17_Ball", [100, 200, 200], [282, 300, 320], 1, 0.118], + ["B_127x54_Ball", [500, 500], [295, 305], 1, 1.050], + ["B_45ACP_Ball", [100, 200, 200], [230, 250, 285], 1, 0.195]] + +print ("Calculating ...") +print ("") + +open('../extras/airFrictionAnalysis.txt', 'w').close() + +for ammo in ammoList: + name = ammo[0] + maxRanges = ammo[1] + mvs = ammo[2] + dragModel = ammo[3] + BC = ammo[4] + + mv = round(sum(mvs) / len(mvs)) + bestA = calculateAirFriction(mv, dragModel, BC) + if (mv < 360): + bestA = predictAirFriction(mv, mv - 20, dragModel, BC) + else: + bestA = predictAirFriction(mv, 340, dragModel, BC) + minDropDiff = 100 + interval = 0.0003 + + while interval > 0.0000001: + low = bestA - interval + high = bestA + interval + a = low + while a < high: + dropDiff = maxDropDiffV(maxRanges, mvs, a, dragModel, BC) + if dropDiff < minDropDiff: + minDropDiff = dropDiff + bestA = a + a = a + interval / 10 + + interval = interval / 2 + + print (str(name) + " -> " + str(round(bestA, 8))) + with open('../extras/airFrictionAnalysis.txt', 'a') as f: + print ("##########################################", file=f) + print ("Ammo Class: " + name, file=f) + print ("MaxRanges (m): " + str(maxRanges), file=f) + print ("MuzzleVelocities (m/s): " + str(mvs), file=f) + print ("Max. Velocity diff (m/s): " + str(round(maxVelocityDiffV(maxRanges, mvs, bestA, dragModel, BC), 2)), file=f) + print ("Max. Drop diff (cm): " + str(round(minDropDiff * 100, 2)), file=f) + print ("Max. Tof diff (ms): " + str(maxTOFDiffV(maxRanges, mvs, bestA, dragModel, BC)), file=f) + print ("Optimal airFriction: " + str(round(bestA, 8)), file=f) + diff --git a/tools/publish.py b/tools/publish.py new file mode 100644 index 00000000..5ddc6980 --- /dev/null +++ b/tools/publish.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + +# Author: PabstMirror + +# Uploads ace relases to workshop +# Will slice up compats to their own folders + +import sys + +if sys.version_info[0] == 2: + print("Python 3 is required.") + sys.exit(1) + +import os +import os.path +import shutil +import platform +import glob +import subprocess +import hashlib +import configparser +import json +import traceback +import time +import timeit +import re +import fnmatch + +if sys.platform == "win32": + import winreg + +def find_bi_tools(): + reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) + try: + k = winreg.OpenKey(reg, r"Software\bohemia interactive\arma 3 tools") + arma3tools_path = winreg.QueryValueEx(k, "path")[0] + winreg.CloseKey(k) + except: + raise Exception("BadTools","Arma 3 Tools are not installed correctly or the P: drive needs to be created.") + + publisher_path = os.path.join(arma3tools_path, "Publisher", "PublisherCmd.exe") + + if os.path.isfile(publisher_path): + return publisher_path + else: + raise Exception("BadTools","Arma 3 Tools are not installed correctly or the P: drive needs to be created.") + + +def publishFolder(folder,modID,changeNotes): + cmd = [publisherTool_path, "update", "/id:{}".format(modID), "/changeNoteFile:{}".format(changeNotes), "/path:{}".format(folder)] + + print ("running: {}".format(cmd)) + + print("") + print("Publishing folder {} to workshop ID {}".format(folder,modID)) + print("") + if (not do_publish): + print("Just doing test build") + return + ret = subprocess.call(cmd) + if ret != 0: + print("publisher faild with code {}".format(ret)) + raise Exception("Publisher","Publisher had problems") + + +#GLOBALS +release_dir = "P:\\z\\ace\\release" +project = "@ace" +publisherTool_path = find_bi_tools() +changelog_path = os.path.join(release_dir,"changelog.txt") +ace_release_dir = os.path.join(release_dir, project) +ace_optionals_dir = os.path.join(ace_release_dir, "optionals") + +do_publish = True +# do_publish = False #will let you just build dirs and test without running publisher + + +def main(argv): + if not os.path.exists(ace_release_dir): + raise Exception("ace_release_dir not found","ACE not built or in wrong path") + if not os.path.exists(ace_optionals_dir): + raise Exception("ace_optionals_dir not found","ACE not built or in wrong path") + if not os.path.exists(publisherTool_path): + raise Exception("publisherTool_path not found","Arma Tools not found") + if not os.path.exists(changelog_path): + raise Exception("changelog_path not found","Requires changelog.txt be present in the release dir") + + if do_publish: + repl = input("\nThis will publish to steam, are you positive release dir has correct files? (y/n): ") + if repl.lower() != "y": + return 0 + + #ACE Main - http://steamcommunity.com/sharedfiles/filedetails/?id=463939057 + # Note: command line publisher doesn't like our file structure, just upload this one manually + + #noactionmenu: - https://steamcommunity.com/sharedfiles/filedetails/?id=2202412030 + publishFolder(os.path.join(ace_optionals_dir,"@ace_noactionmenu"), "2202412030", changelog_path) + + #nocrosshair: - https://steamcommunity.com/sharedfiles/filedetails/?id=2202412481 + publishFolder(os.path.join(ace_optionals_dir,"@ace_nocrosshair"), "2202412481", changelog_path) + + #nomedical: - https://steamcommunity.com/sharedfiles/filedetails/?id=3053169823 + publishFolder(os.path.join(ace_optionals_dir,"@ace_nomedical"), "3053169823", changelog_path) + + #norealisticnames: - https://steamcommunity.com/sharedfiles/filedetails/?id=3053177117 + publishFolder(os.path.join(ace_optionals_dir,"@ace_norealisticnames"), "3053177117", changelog_path) + + #nouniformrestrictions: - https://steamcommunity.com/sharedfiles/filedetails/?id=2202413047 + publishFolder(os.path.join(ace_optionals_dir,"@ace_nouniformrestrictions"), "2202413047", changelog_path) + + #particles: - https://steamcommunity.com/sharedfiles/filedetails/?id=2202413537 + publishFolder(os.path.join(ace_optionals_dir,"@ace_particles"), "2202413537", changelog_path) + + #realisticdispersion: - https://steamcommunity.com/sharedfiles/filedetails/?id=2202414018 + publishFolder(os.path.join(ace_optionals_dir,"@ace_realisticdispersion"), "2202414018", changelog_path) + + #tracers: - https://steamcommunity.com/sharedfiles/filedetails/?id=2202414450 + publishFolder(os.path.join(ace_optionals_dir,"@ace_tracers"), "2202414450", changelog_path) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/tools/search_undefinedFunctions.py b/tools/search_undefinedFunctions.py index 2c4fbeb5..6789bbe9 100644 --- a/tools/search_undefinedFunctions.py +++ b/tools/search_undefinedFunctions.py @@ -7,86 +7,104 @@ import sys import argparse +# handle x64 python clipboard, ref https://forums.autodesk.com/t5/maya-programming/ctypes-bug-cannot-copy-data-to-clipboard-via-python/m-p/9197068/highlight/true#M10992 import ctypes - -#from http://stackoverflow.com/a/3429034 -#Get required functions, strcpy.. -strcpy = ctypes.cdll.msvcrt.strcpy -ocb = ctypes.windll.user32.OpenClipboard #Basic Clipboard functions -ecb = ctypes.windll.user32.EmptyClipboard -gcd = ctypes.windll.user32.GetClipboardData -scd = ctypes.windll.user32.SetClipboardData -ccb = ctypes.windll.user32.CloseClipboard -ga = ctypes.windll.kernel32.GlobalAlloc # Global Memory allocation -gl = ctypes.windll.kernel32.GlobalLock # Global Memory Locking -gul = ctypes.windll.kernel32.GlobalUnlock -GMEM_DDESHARE = 0x2000 - -def Get( ): - ocb(None) # Open Clip, Default task - pcontents = gcd(1) # 1 means CF_TEXT.. too lazy to get the token thingy ... - data = ctypes.c_char_p(pcontents).value - #gul(pcontents) ? - ccb() - return data +from ctypes import wintypes +CF_UNICODETEXT = 13 + + +user32 = ctypes.WinDLL('user32') +kernel32 = ctypes.WinDLL('kernel32') + +OpenClipboard = user32.OpenClipboard +OpenClipboard.argtypes = wintypes.HWND, +OpenClipboard.restype = wintypes.BOOL +CloseClipboard = user32.CloseClipboard +CloseClipboard.restype = wintypes.BOOL +EmptyClipboard = user32.EmptyClipboard +EmptyClipboard.restype = wintypes.BOOL +GetClipboardData = user32.GetClipboardData +GetClipboardData.argtypes = wintypes.UINT, +GetClipboardData.restype = wintypes.HANDLE +SetClipboardData = user32.SetClipboardData +SetClipboardData.argtypes = (wintypes.UINT, wintypes.HANDLE) +SetClipboardData.restype = wintypes.HANDLE +GlobalLock = kernel32.GlobalLock +GlobalLock.argtypes = wintypes.HGLOBAL, +GlobalLock.restype = wintypes.LPVOID +GlobalUnlock = kernel32.GlobalUnlock +GlobalUnlock.argtypes = wintypes.HGLOBAL, +GlobalUnlock.restype = wintypes.BOOL +GlobalAlloc = kernel32.GlobalAlloc +GlobalAlloc.argtypes = (wintypes.UINT, ctypes.c_size_t) +GlobalAlloc.restype = wintypes.HGLOBAL +GlobalSize = kernel32.GlobalSize +GlobalSize.argtypes = wintypes.HGLOBAL, +GlobalSize.restype = ctypes.c_size_t + +GMEM_MOVEABLE = 0x0002 +GMEM_ZEROINIT = 0x0040 def Paste( data ): - ocb(None) # Open Clip, Default task - ecb() - hCd = ga( GMEM_DDESHARE, len( bytes(data,"ascii") )+1 ) - pchData = gl(hCd) - strcpy(ctypes.c_char_p(pchData),bytes(data,"ascii")) - gul(hCd) - scd(1,hCd) - ccb() + data = data.encode('utf-16le') + OpenClipboard(None) + EmptyClipboard() + handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, len(data) + 2) + pcontents = GlobalLock(handle) + ctypes.memmove(pcontents, data, len(data)) + GlobalUnlock(handle) + SetClipboardData(CF_UNICODETEXT, handle) + CloseClipboard() def getFunctions(filepath): - selfmodule = (re.search('addons[\W]*([_a-zA-Z0-9]*)', filepath)).group(1) + selfmodule = (re.search(r'addons[\W]*([_a-zA-Z0-9]*)', filepath)).group(1) # print("Checking {0} from {1}".format(filepath,selfmodule)) + if (selfmodule.startswith("compat")): return [] with open(filepath, 'r') as file: content = file.read() - srch = re.compile('[^E]FUNC\(([_a-zA-Z0-9]*)\)') + srch = re.compile(r'[^E]FUNC\(([_a-zA-Z0-9]*)\)') modfuncs = srch.findall(content) modfuncs = sorted(set(modfuncs)) - srch = re.compile('EFUNC\(([_a-zA-Z0-9]*),([_a-zA-Z0-9]*)\)') + srch = re.compile(r'EFUNC\(([_a-zA-Z0-9]*),([_a-zA-Z0-9]*)\)') exfuncs = srch.findall(content) exfuncs = sorted(set(exfuncs)) fileFuncs = [] for func in modfuncs: - fileFuncs.append("cav_{0}_fnc_{1}".format(selfmodule,func)) + fileFuncs.append("ace_{0}_fnc_{1}".format(selfmodule,func)) for exModule,func in exfuncs: - fileFuncs.append("cav_{0}_fnc_{1}".format(exModule, func)) + fileFuncs.append("ace_{0}_fnc_{1}".format(exModule, func)) return fileFuncs def getStrings(filepath): - selfmodule = (re.search('addons[\W]*([_a-zA-Z0-9]*)', filepath)).group(1) + selfmodule = (re.search(r'addons[\W]*([_a-zA-Z0-9]*)', filepath)).group(1) # print("Checking {0} from {1}".format(filepath,selfmodule)) + if (selfmodule.startswith("compat")): return [] with open(filepath, 'r') as file: content = file.read() - srch = re.compile('[^E][CL]STRING\(([_a-zA-Z0-9]*)\)') + srch = re.compile(r'[^E][CL]STRING\(([_a-zA-Z0-9]*)\)') modStrings = srch.findall(content) modStrings = sorted(set(modStrings)) - srch = re.compile('E[CL]STRING\(([_a-zA-Z0-9]*),([_a-zA-Z0-9]*)\)') + srch = re.compile(r'E[CL]STRING\(([_a-zA-Z0-9]*),([_a-zA-Z0-9]*)\)') exStrings = srch.findall(content) exStrings = sorted(set(exStrings)) fileStrings = [] for localString in modStrings: - fileStrings.append("STR_CAV_{0}_{1}".format(selfmodule, localString)) + fileStrings.append("STR_ACE_{0}_{1}".format(selfmodule, localString)) for (exModule, exString) in exStrings: - fileStrings.append("STR_CAV_{0}_{1}".format(exModule, exString)) + fileStrings.append("STR_ACE_{0}_{1}".format(exModule, exString)) return fileStrings @@ -105,7 +123,8 @@ def main(): parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default=".") args = parser.parse_args() - for root, dirnames, filenames in os.walk('../addons' + '/' + args.module): + addon_base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + for root, dirnames, filenames in os.walk(addon_base_path +"/" + 'addons' + '/' + args.module): for filename in fnmatch.filter(filenames, '*.sqf'): sqf_list.append(os.path.join(root, filename)) for filename in fnmatch.filter(filenames, '*.cpp'): @@ -126,7 +145,7 @@ def main(): outputCode = "{0} allFunctions = {1}; allStrings = {2}; {3} {4}".format(codeHeader, list(set(allFunctions)), list(set(allStrings)), codeFuncCheck, codeStringCheck) print(outputCode) - Paste(outputCode); + Paste(outputCode) print ("") print ("Copied to clipboard, [funcs {0} / strings {1}]'".format(len(set(allFunctions)), len(set(allStrings)))) diff --git a/tools/setup.bat b/tools/setup.bat new file mode 100644 index 00000000..8d7245ab --- /dev/null +++ b/tools/setup.bat @@ -0,0 +1,16 @@ +;@Findstr -bv ;@F "%~f0" | powershell -Command - & pause & goto:eof + +Write-Output "=> Downloading ..." +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +$url = "https://github.com/BrettMayson/HEMTT/releases/latest/download/windows-x64.zip" +(New-Object Net.WebClient).DownloadFile($url, "hemtt.zip"); Write-Output "$url => hemtt.zip" + +Write-Output "`n=> Extracting ..." +Expand-Archive -Path "hemtt.zip" -DestinationPath "..\." -Force; Write-Output "hemtt.zip" +Remove-Item "hemtt.zip" + +Write-Output "`n=> Verifying ..." +Start-Process -FilePath ..\hemtt.exe -ArgumentList --version -NoNewWindow -Wait + +Write-Output "`nTools successfully installed to project!" diff --git a/tools/setupEditorPreviewImages.py b/tools/setupEditorPreviewImages.py deleted file mode 100644 index b659ad89..00000000 --- a/tools/setupEditorPreviewImages.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 - -# requires python-resize-image (PIL) -# pip install python-resize-image - -import os -import sys -from resizeimage import resizeimage -from PIL import Image - -__version__ = 0.1 - -######## GLOBALS ######### -DIRECTORY = "C:\\Users\\andre\\OneDrive\\Dokument\\Arma 3 - Other Profiles\\Camera\\Screenshots\\EditorPreviews\\@BlackOrder" -SIZE = [455,256] -########################## - -# Sets up arguments -PARAMS = sys.argv -paramsList = ["-config"] -param_config = [paramsList[0],"creates and add each object to a config file"] - -def main(): - print(""" -Editor Preview Image Creator {} -This script resize and convert the Arma 3 Snapshots to correct size and format. -Created by: Andreas Brostrom | Evul - """.format(__version__)) - - if "-h" in PARAMS or "--help" in PARAMS: - print("usage: {}".format(sys.argv[0]),end=' ') - for p in paramsList: - print("[{}]".format(p),end=' ') - print("[-h, --help]\n") - print("optional arguments:\n -h, --help show this help message and exit") - print(" {} {}".format(param_config[0],param_config[1])) - sys.exit() - - os.chdir(DIRECTORY) - imageList = [f for f in os.listdir(DIRECTORY) if os.path.isfile(os.path.join(DIRECTORY, f)) and ".png" in f] - - - if len(imageList) >= 1: - print("Found {} images".format(len(imageList))) - else: - sys.exit("No image files could be found in directory:\n\"{}\"\n\nPlease check the path or consult the BIS guide for how to set up Eden Preview images:\nhttps://community.bistudio.com/wiki/Eden_Editor:_Configuring_Asset_Previews".format(DIRECTORY)) - print("Prepering resize") - - for pic in imageList: - print ('resizing "{}" to {}x{}'.format(pic,SIZE[0],SIZE[1])) - - with open(pic, 'r+b') as f: - with Image.open(pic) as image: - # resizing - cover = resizeimage.resize_cover(image, [SIZE[0],SIZE[1]]) - cover.save(pic, image.format) - # converting image - with Image.open(pic) as image: - print("Converting and saving {} [{}] to jpg".format(pic[:-4],cover.mode)) - cover = image.convert('RGB') - cover.save('{}.jpg'.format(pic[:-4]), quality=95) - print("{}.jpg [{}] is converted and saved".format(pic[:-4],cover.mode)) - - # Remove png - for file in imageList: - print("Removing {}".format(file)) - os.remove(file) - print("All files are replaced with jpg") - - # Create config - if "-config" in PARAMS: - print("Creating config file") - configFile = open('EditorPreview.hpp', 'w') - configFile.write('class CfgVehicles'+' {\n') - for file in imageList: - print("Writing and adding class \"{}\" to config".format(file[:-4])) - configFile.write(' class {}'.format(file[:-4])+' {\n') - configFile.write(' QPATHTOF(EditorPreviews\\{}.jpg);\n'.format(file[:-4])) - configFile.write(' };\n') - configFile.write('};\n') - configFile.close() - print("Config created") -if __name__ == "__main__": - sys.exit(main()) diff --git a/tools/sqf_linter.py b/tools/sqf_linter.py index 79df3919..c71c7209 100644 --- a/tools/sqf_linter.py +++ b/tools/sqf_linter.py @@ -1,66 +1,83 @@ #!/usr/bin/env python3 - # Requires: https://github.com/LordGolias/sqf -import fnmatch import os import sys import argparse +import concurrent.futures from sqf.parser import parse import sqf.analyzer from sqf.exceptions import SQFParserError +addon_base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + + +def get_files_to_process(basePath): + arma_files = [] + for (root, _dirs, files) in os.walk(basePath): + for file in files: + if file.endswith(".sqf"): + if file.endswith(".inc.sqf"): + continue + filePath = os.path.join(root, file) + arma_files.append(filePath) + return arma_files -def analyze(filename, writer=sys.stdout): - warnings = 0 - errors = 0 - with open(filename, 'r') as file: - code = file.read() - try: - result = parse(code) - except SQFParserError as e: - print("{}:".format(filename)) - writer.write(' [%d,%d]:%s\n' % (e.position[0], e.position[1] - 1, e.message)) - return 0, 1 - exceptions = sqf.analyzer.analyze(result).exceptions +def process_file(filePath): + errors = [] + warnings = [] + try: + with open(filePath, "r", encoding="utf-8", errors="ignore") as file: + content = file.read() + if "#ASC_ignoreFile" in content: + return (filePath, errors, warnings) + sqfLintParse = parse(content) + exceptions = sqf.analyzer.analyze(sqfLintParse).exceptions if (exceptions): - print("{}:".format(filename)) for e in exceptions: - if (e.message.startswith("error")): - errors += 1 - else: - warnings += 1 - writer.write(' [%d,%d]:%s\n' % (e.position[0], e.position[1] - 1, e.message)) + if ("assigned to an outer scope" in e.message): + warnings.append(f"[{e.position[0]},{e.position[1]}] {e.message}") + if ("is not from this scope" in e.message): + warnings.append(f"[{e.position[0]},{e.position[1]}] {e.message}") + if ("not used" in e.message): + warnings.append(f"[{e.position[0]},{e.position[1]}] {e.message}") - return warnings, errors + # most of this is just noise about macro parsing: + # if (e.message.startswith("error")): + # errors.append(f"[{e.position[0]},{e.position[1]}] {e.message}") + # else: + # warnings.append(f"[{e.position[0]},{e.position[1]}] {e.message}") + except Exception as e: + # errors.append(f"Exception {e}") + pass + return (filePath, errors, warnings) -def main(): - print("#########################") - print("# Lint Check #") - print("#########################") - sqf_list = [] - all_warnings = 0 - all_errors = 0 +def main(): parser = argparse.ArgumentParser() - parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default=".") + parser.add_argument('-m', '--module', help='only search specified module addon folder', required=False, default=".") args = parser.parse_args() - for root, dirnames, filenames in os.walk('../addons' + '/' + args.module): - for filename in fnmatch.filter(filenames, '*.sqf'): - sqf_list.append(os.path.join(root, filename)) - - for filename in sqf_list: - warnings, errors = analyze(filename) - all_warnings += warnings - all_errors += errors + error_count = 0 + addon_base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + if (args.module): addon_base_path = os.path.join(addon_base_path, "addons", args.module) + arma_files = get_files_to_process(addon_base_path) + print(f"Checking {len(arma_files)} files from {addon_base_path}") + with concurrent.futures.ThreadPoolExecutor(max_workers=12) as executor: + for (filePath, errors, warnings) in executor.map(process_file, arma_files): + if errors or warnings: + error_count += 1 + print(f"{filePath}") + for e in errors: + print(f" {e}") + for e in warnings: + print(f" {e}") - print ("Parse Errors {0} - Warnings {1}".format(all_errors,all_warnings)) + print("Errors: {}".format(error_count)) + return error_count - # return (all_errors + all_warnings) - return all_errors if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/tools/sqf_validator.py b/tools/sqf_validator.py index 6686004a..facdb114 100644 --- a/tools/sqf_validator.py +++ b/tools/sqf_validator.py @@ -149,6 +149,10 @@ def popClosing(): if pattern.match(content): print("ERROR: A found #include after block comment in file {0}".format(filepath)) bad_count_file += 1 + if ("functions" in filepath): + if (content.startswith("#include \"script_component.hpp\"")): + print(f"ERROR: Using old script_component.hpp in {filepath}") + bad_count_file += 1 @@ -165,14 +169,15 @@ def main(): parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default="") args = parser.parse_args() - # Allow running from root directory as well as from inside the tools directory - rootDir = "../addons" - if (os.path.exists("addons")): - rootDir = "addons" + for folder in ['addons', 'optionals']: + # Allow running from root directory as well as from inside the tools directory + rootDir = "../" + folder + if (os.path.exists(folder)): + rootDir = folder - for root, dirnames, filenames in os.walk(rootDir + '/' + args.module): - for filename in fnmatch.filter(filenames, '*.sqf'): - sqf_list.append(os.path.join(root, filename)) + for root, dirnames, filenames in os.walk(rootDir + '/' + args.module): + for filename in fnmatch.filter(filenames, '*.sqf'): + sqf_list.append(os.path.join(root, filename)) for filename in sqf_list: bad_count = bad_count + check_sqf_syntax(filename) diff --git a/tools/sqfvmChecker.py b/tools/sqfvmChecker.py new file mode 100644 index 00000000..69d75661 --- /dev/null +++ b/tools/sqfvmChecker.py @@ -0,0 +1,96 @@ +import os +import sys +import subprocess +import concurrent.futures +import tomllib + +addon_base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + +sqfvm_exe = os.path.join(addon_base_path, "sqfvm.exe") +virtual_paths = [ + # would need to add more even more to /include to use it + "P:/a3|/a3", # "{}|/a3".format(os.path.join(addon_base_path, "include", "a3")), + "P:/a3|/A3", + "P:/x/cba|/x/cba", + "{}|/z/ace".format(addon_base_path), +] + + +def get_files_to_process(basePath): + arma_files = [] + for root, _dirs, files in os.walk(os.path.join(addon_base_path, "addons")): + for file in files: + if file.endswith(".sqf") or file == "config.cpp": + if file.endswith(".inc.sqf"): + continue + skipPreprocessing = False + for addonTomlPath in [os.path.join(root, "addon.toml"), os.path.join(os.path.dirname(root), "addon.toml")]: + if os.path.isfile(addonTomlPath): + with open(addonTomlPath, "rb") as f: + tomlFile = tomllib.load(f) + try: + skipPreprocessing = tomlFile.get('tools')['sqfvm_skipConfigChecks'] + except: + pass + if file == "config.cpp" and skipPreprocessing: + continue # ignore configs with __has_include + filePath = os.path.join(root, file) + arma_files.append(filePath) + return arma_files + + +def process_file(filePath, skipA3Warnings=True, skipPragmaHemtt=True): + with open(filePath, "r", encoding="utf-8", errors="ignore") as file: + content = file.read() + if content.startswith("//pragma SKIP_COMPILE"): + return False + cmd = [sqfvm_exe, "--input", filePath, "--parse-only", "--automated"] + for v in virtual_paths: + cmd.append("-v") + cmd.append(v) + # cmd.append("-V") + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) + try: + ret = proc.wait(12) # max wait - seconds + except Exception as _e: + print("sqfvm timed out: {}".format(filePath)) + return True + # print("{} = {}".format(filePath, ret)) + + fileHasError = False + keepReadingLines = True + while keepReadingLines: + line = proc.stdout.readline() + if not line: + keepReadingLines = False + else: + line = line.rstrip() + if line.startswith("[ERR]"): + fileHasError = True + if not ( + (skipA3Warnings and line.startswith("[WRN]") and ("a3/" in line) and (("Unexpected IFDEF" in line) or ("defined twice" in line))) + or (skipPragmaHemtt and line.startswith("[WRN]") and ("Unknown pragma instruction 'hemtt'" in line)) + ): + print(" {}".format(line)) + return fileHasError + + +def main(): + if not os.path.isfile(sqfvm_exe): + print("Error: sqfvm.exe not found in base folder [{}]".format(sqfvm_exe)) + return 1 + + error_count = 0 + arma_files = get_files_to_process(addon_base_path) + print("Checking {} files".format(len(arma_files))) + with concurrent.futures.ThreadPoolExecutor(max_workers=12) as executor: + for fileError in executor.map(process_file, arma_files): + if fileError: + error_count += 1 + + print("Errors: {}".format(error_count)) + return error_count + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/stringtable_validator.py b/tools/stringtable_validator.py index d86bbd96..8cd00014 100644 --- a/tools/stringtable_validator.py +++ b/tools/stringtable_validator.py @@ -16,15 +16,15 @@ ######## GLOBALS ######### -PROJECT_NAME = "cav" +PROJECT_NAME = "CAV" ########################## def check_stringtable(filepath): try: tree = ET.parse(filepath) - except: - print(" ERROR: Failed to parse file.") + except Exception as e: + print(" ERROR: Failed to parse file. {}".format(e)) return 1 errors = 0 @@ -53,7 +53,8 @@ def check_stringtable(filepath): print(" ERROR: Package name attribute '{}' is all lowercase, should be in titlecase.".format(package_name)) errors += 1 - if package_name.lower() != os.path.basename(os.path.dirname(filepath)): + component_folder = os.path.basename(os.path.dirname(filepath)) + if package_name.lower() != component_folder: print(" ERROR: Package name attribute '{}' does not match the component folder name.".format(package_name)) errors += 1 @@ -115,6 +116,29 @@ def check_stringtable(filepath): print(" ERROR: Key '{}' is defined {} times.".format(id, count)) errors += 1 + # Check whitespace for tabs and correct number of indenting spaces + with open(filepath, "r", encoding = "utf-8") as file: + spacing_depth = 0 + + for line_number, line in enumerate(file, 1): + if "\t" in line: + print(" ERROR: Found a tab on line {}.".format(line_number)) + errors += 1 + + line_clean = line.lstrip().lower() + + if line_clean.startswith("