This commit is contained in:
PabstMirror 2019-03-02 00:26:02 -06:00
parent 28620d86d1
commit 4120eca98b
29 changed files with 1280 additions and 2 deletions

BIN
ace_artillerytables_x64.dll Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
z\ace\addons\artillerytables

View File

@ -0,0 +1,15 @@
class Extended_PreStart_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_preStart));
};
};
class Extended_PreInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_preInit));
};
};
class Extended_PostInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_postInit));
};
};

View File

@ -0,0 +1,3 @@
class CfgVehicles {
};

View File

@ -0,0 +1,15 @@
class CfgWeapons {
class ACE_ItemCore;
class CBA_MiscItem_ItemInfo;
class ACE_artilleryTable: ACE_ItemCore {
author = ECSTRING(common,ACETeam);
scope = 2;
displayName = CSTRING(rangetable_displayName);
descriptionShort = CSTRING(rangetable_description);
picture = QPATHTOF(UI\icon_rangeTable.paa);
class ItemInfo: CBA_MiscItem_ItemInfo {
mass = 0.5;
};
};
};

View File

@ -0,0 +1,14 @@
ace_artillerytables
==========
Universal artillertables.
#### Items Added:
`ACE_artilleryTable`
## Maintainers
The people responsible for merging changes to this component or answering potential questions.
- [PabstMirror](https://github.com/PabstMirror)

View File

@ -0,0 +1,102 @@
class GVAR(rangeTableDialog) {
idd = -1;
movingEnable = 1;
onLoad = QUOTE(with uiNameSpace do { GVAR(rangeTableDialog) = _this select 0 };);
objects[] = {};
class ControlsBackground {
class TableBackground: RscPicture {
idc = -1;
text = QPATHTOF(UI\RangeTable_background.paa);
x = "18 *(safeZoneH / 40) + (safezoneX + (safezoneW - safeZoneH)/2)";
y = "1 * ((safeZoneH / 1.2) / 25) + (safezoneY + (safezoneH - (safeZoneH / 1.2))/2)";
w = "16.2634559672906 * (safeZoneH / 40)";
h = "23 * ((safeZoneH / 1.2) / 25)";
colorBackground[] = {1,1,1,1};
};
class ChargeBackground: RscText {
idc = -1;
x = "14 *(safeZoneH / 40) + (safezoneX + (safezoneW - safeZoneH)/2)";
y = "1 * ((safeZoneH / 1.2) / 25) + (safezoneY + (safezoneH - (safeZoneH / 1.2))/2)";
w = "4 * (safeZoneH / 40)";
h = "8 * ((safeZoneH / 1.2) / 25)";
colorBackground[] = {0,0,0,0.9};
};
};
class controls {
class TheTable: RscListNBox {
idc = IDC_TABLE;
// style = ST_CENTER + ST_MULTI + LB_TEXTURES;
// style = ST_LEFT + ST_MULTI + LB_TEXTURES;
// style = LB_MULTI + ST_LEFT; // Style
x = "18 *(safeZoneH / 40) + (safezoneX + (safezoneW - safeZoneH)/2)";
y = "3.76 * ((safeZoneH / 1.2) / 25) + (safezoneY + (safezoneH - (safeZoneH / 1.2))/2)";
w = "16.2634559672906 * (safeZoneH / 40)";
h = "20.24 * ((safeZoneH / 1.2) / 25)";
columns[] = {(10/867),(86/867),(171/867),(238/867),(320/867),(405/867),
(485/867),(546/867),(607/867),(668/867),(729/867),(790/867)};
rowHeight = 0.015 * safeZoneH;
sizeEx = "0.014 * safeZoneH";
font = "EtelkaMonospacePro";
drawSideArrows = 1;
idcLeft = 14124;
idcRight = 412343243;
colorText[] = {0, 0, 0, 1};
shadow = "0";
// colorBorder[] = {1,0,0,1};
// colorBackground[] = {1, 0, 0, 1};
colorSelectBackground[] = {0, 0, 0, 0.025};
colorSelectBackground2[] = {0, 0, 0, 0.025};
colorScrollbar[] = {0.95,0,0.95,1};
class ListScrollBar: ScrollBar{
color[] = {0,0,0,0.6};
};
};
class ChargeListBox: RscListbox {
idc = IDC_CHARGELIST;
style = ST_RIGHT;
x = "14 *(safeZoneH / 40) + (safezoneX + (safezoneW - safeZoneH)/2)";
y = "2 * ((safeZoneH / 1.2) / 25) + (safezoneY + (safezoneH - (safeZoneH / 1.2))/2)";
w = "4 * (safeZoneH / 40)";
h = "7 * ((safeZoneH / 1.2) / 25)";
onLBSelChanged = QUOTE([] call FUNC(rangeTableUpdate));
};
class elevationHigh: ctrlButton {
idc = IDC_BUTTON_ELEV_HIGH;
text = "High";
onButtonClick = QUOTE([true] call FUNC(rangeTableUpdate));
x = "14.1 *(safeZoneH / 40) + (safezoneX + (safezoneW - safeZoneH)/2)";
y = "1.1 * ((safeZoneH / 1.2) / 25) + (safezoneY + (safezoneH - (safeZoneH / 1.2))/2)";
w = "1.8 * (safeZoneH / 40)";
h = "0.8 * ((safeZoneH / 1.2) / 25)";
};
class elevationLow: elevationHigh {
idc = IDC_BUTTON_ELEV_LOW;
text = "Low";
onButtonClick = QUOTE([false] call FUNC(rangeTableUpdate));
x = "16.1 *(safeZoneH / 40) + (safezoneX + (safezoneW - safeZoneH)/2)";
};
class CloseBackground: RscText {
idc = -1;
x = "33.7634559672906 *(safeZoneH / 40) + (safezoneX + (safezoneW - safeZoneH)/2)";
y = "1 * ((safeZoneH / 1.2) / 25) + (safezoneY + (safezoneH - (safeZoneH / 1.2))/2)";
w = "0.5 * (safeZoneH / 40)";
h = "0.5 * ((safeZoneH / 1.2) / 25)";
colorBackground[] = {0,0,0,0.5};
};
class CloseActiveText: RscActiveText {
idc = -1;
style = 48;
color[] = {1,1,1,0.7};
text = "A3\Ui_f\data\GUI\Rsc\RscDisplayArcadeMap\icon_exit_cross_ca.paa";
x = "33.7634559672906 *(safeZoneH / 40) + (safezoneX + (safezoneW - safeZoneH)/2)";
y = "1 * ((safeZoneH / 1.2) / 25) + (safezoneY + (safezoneH - (safeZoneH / 1.2))/2)";
w = "0.5 * (safeZoneH / 40)";
h = "0.5 * ((safeZoneH / 1.2) / 25)";
colorText[] = {1,1,1,0.7};
colorActive[] = {1,1,1,1};
tooltip = "Close";
onButtonClick = "closeDialog 0";
};
};
};

View File

@ -0,0 +1,42 @@
class RscTitles {
class GVAR(modeDisplay) {
idd = -1;
onLoad = QUOTE(with uiNameSpace do { GVAR(display) = _this select 0 };);
movingEnable = 0;
duration = 60;
fadeIn = "false";
fadeOut = "false";
class controls {
class ModeControlGroup: RscControlsGroupNoScrollbars {
idc = IDC_MODECONTROLGROUP;
x = "3.8 * (((safezoneW / safezoneH) min 1.2) / 40) + (profilenamespace getvariable ['IGUI_GRID_WEAPON_X',((safezoneX + safezoneW) - (10 * (((safezoneW / safezoneH) min 1.2) / 40)) - 4.3 * (((safezoneW / safezoneH) min 1.2) / 40))])";
y = "2.5 * ((((safezoneW / safezoneH) min 1.2) / 1.2) / 25) + (profilenamespace getVariable ['IGUI_GRID_WEAPON_Y', (safezoneY + 0.5 * ((((safezoneW / safezoneH) min 1.2) / 1.2) / 25))])";
w = "10 * (((safezoneW / safezoneH) min 1.2) / 40)";
h = "1 * ((((safezoneW / safezoneH) min 1.2) / 1.2) / 25)";
class controls {
class Charge: RscText {
idc = IDC_CHARGE;
colorText[] = {1, 1, 1, 1};
colorBackground[] = {0, 0, 0, 0};
x = "0";
y = "0";
w = "(2) * (((safezoneW / safezoneH) min 1.2) / 40)";
h = "1 * ((((safezoneW / safezoneH) min 1.2) / 1.2) / 25)";
sizeEx = "0.8 * ((((safezoneW / safezoneH) min 1.2) / 1.2) / 25)";
};
class Azimuth: Charge {
idc = IDC_AZIMUTH;
x = "(2) * ((((safezoneW / safezoneH) min 1.2) / 1.2) / 25)";
w = "(3) * (((safezoneW / safezoneH) min 1.2) / 40)";
};
class Elevation: Azimuth {
idc = IDC_ELEVATION;
x = "(5) * ((((safezoneW / safezoneH) min 1.2) / 1.2) / 25)";
};
};
};
};
};
};

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,8 @@
TRACE_1("prep",_this);
PREP(firedEH);
PREP(interactMenuOpened);
PREP(rangeTableOpen);
PREP(rangeTableUpdate);
PREP(turretChanged);
PREP(turretPFEH);

View File

@ -0,0 +1,32 @@
#include "script_component.hpp"
["ace_settingsInitialized", {
TRACE_2("ace_settingsInitialized",GVAR(advancedCorrections),GVAR(disableArtilleryComputer));
if (hasInterface) then {
// Add hud overlay for actuall azimuth and elevation:
GVAR(pfID) = -1;
["turret", LINKFUNC(turretChanged), true] call CBA_fnc_addPlayerEventHandler;
// Add ability to dynamically open rangetables:
["ace_interactMenuOpened", LINKFUNC(interactMenuOpened)] call CBA_fnc_addEventHandler;
};
if (GVAR(advancedCorrections)) then {
[{ // add a frame later to allow other modules to set variables
["LandVehicle", "init", {
params ["_vehicle"];
private _enabled = _vehicle getVariable [QGVAR(enabled), getNumber (configFile >> "CfgVehicles" >> typeOf _vehicle >> "artilleryScanner")];
if (_enabled in [0, false]) exitWith {};
if (1 == getNumber (configFile >> "CfgVehicles" >> typeOf _vehicle >> QGVAR(skipCorrections))) exitWith {};
TRACE_3("enabled",_vehicle,typeOf _vehicle,_enabled);
_vehicle addEventHandler ["Fired", {call FUNC(firedEH)}];
}, true, [], true] call CBA_fnc_addClassEventHandler;
}, []] call CBA_fnc_execNextFrame;
};
}] call CBA_fnc_addEventHandler;
#ifdef DEBUG_MODE_FULL
#include "dev\showShotInfo.sqf"
#endif

View File

@ -0,0 +1,25 @@
#include "script_component.hpp"
ADDON = false;
PREP_RECOMPILE_START;
#include "XEH_PREP.hpp"
PREP_RECOMPILE_END;
#include "initSettings.sqf"
DFUNC(rotateVector3d) = {
params ["_vector", "_rotAxis", "_angle"];
_vector params ["_x", "_y", "_z"];
(vectorNormalized _rotAxis) params ["_u", "_v", "_w"];
private _f = (_u*_x + _v*_y + _w*_z) * (1-cos(_angle));
[
_u*_f + _x*cos(_angle) + (_v*_z - _w*_y)*sin(_angle),
_v*_f + _y*cos(_angle) + (_w*_x - _u*_z)*sin(_angle),
_w*_f + _z*cos(_angle) + (_u*_y - _v*_x)*sin(_angle)
]
};
ADDON = true;

View File

@ -0,0 +1,3 @@
#include "script_component.hpp"
#include "XEH_PREP.hpp"

View File

@ -0,0 +1,40 @@
#include "script_component.hpp"
class CfgPatches {
class ADDON {
name = COMPONENT_NAME;
units[] = {};
weapons[] = {};
requiredVersion = REQUIRED_VERSION;
requiredAddons[] = {"ace_interaction"};
author = ECSTRING(common,ACETeam);
authors[] = {"PabstMirror"};
url = ECSTRING(main,URL);
VERSION_CONFIG;
};
};
class ACE_Extensions {
extensions[] += {"ace_artillerytables"};
};
#include "CfgEventHandlers.hpp"
#include "CfgVehicles.hpp"
#include "CfgWeapons.hpp"
// Common UI Stuff:
class RscText;
class RscListbox;
class RscListNBox;
class RscPicture;
class RscControlsGroup;
class RscControlsGroupNoScrollbars;
class ScrollBar;
class RscActiveText;
class RscStructuredText;
class ctrlButton;
#include "RscTitles.hpp"
#include "RscRangeTable.hpp"

View File

@ -0,0 +1,43 @@
// #include "\z\ace\addons\artillerytables\script_component.hpp"
INFO("showing shot info");
["LandVehicle", "fired", {
params ["_shooter", "", "", "", "_ammo", "", "_proj"];
((velocity _proj) call CBA_fnc_vect2Polar) params ["_mag", "_dir", "_elev"];
private _shootPos = getPosASL _shooter;
if (_dir < 0) then {_dir = _dir + 360;};
hintSilent format ["%1 m/s\nAz: %2 [%3]\nEl: %4 [%5]\nError: %6",_mag toFixed 1, _dir toFixed 2, ((6400 / 360) * _dir) toFixed 0, _elev toFixed 2, ((6400 / 360) * _elev) toFixed 0,
_elev - (missionNamespace getVariable [QGVAR(prediction), 0])];
TRACE_1("elev offset",_elev - GVAR(prediction));
private _submunitionAmmo = getText (configFile >> "CfgAmmo" >> _ammo >> "submunitionAmmo");
[{
params ["_proj", "_shootPos", "_lastPos", "_submunitionAmmo"];
if ((isNull _proj) && {_submunitionAmmo != ""}) then {
_proj = nearestObject [_lastPos, _submunitionAmmo];
_this set [0, _proj];
};
if (isNull _proj) exitWith {true};
_this set [2, getPosASL _proj];
false
}, {
params ["", "_shootPos", "_lastPos"];
private _mkrB = createMarker [format ["shotInfo-%1-%2",_shootPos,_lastPos], _lastPos];
_mkrB setMarkerType "mil_dot";
_mkrB setMarkerColor "ColorGreen";
_mkrB setMarkerSize [0.5,0.5];
private _diff = _lastPos vectorDiff _shootPos;
_mkrB setMarkerText format ["%1", _diff apply {round _x}];
private _dist2d = _shootPos distance2d _lastPos;
private _dir = _shootPos getDir _lastPos;
private _height = (_lastPos select 2) - (_shootPos select 2);
_mkrB setMarkerText format ["Dist: %1m Az: %2[%3] Height:%4", _dist2d toFixed 0, _dir toFixed 2, ((6400 / 360) * _dir) toFixed 0, _height toFixed 0];
}, [_proj, _shootPos, [0,0,0], _submunitionAmmo]] call CBA_fnc_waitUntilAndExecute;
private _mkrA = createMarker [format ["shotInfo-%1",_shootPos], _shootPos];
_mkrA setMarkerType "mil_dot";
_mkrA setMarkerColor "ColorRed";
_mkrA setMarkerSize [0.5,0.5];
}] call CBA_fnc_addClassEventHandler;

View File

@ -0,0 +1,88 @@
#include "script_component.hpp"
/*
* Author: PabstMirror
* Called when the mortar is fired.
*
* Arguments:
* 0: mortar - Object the event handler is assigned to <OBJECT>
* 1: weapon - Fired weapon <STRING>
* 2: muzzle - Muzzle that was used <STRING>
* 3: mode - Current mode of the fired weapon <STRING>
* 4: ammo - Ammo used <STRING>
* 5: magazine - magazine name which was used <STRING>
* 6: projectile - Object of the projectile that was shot <OBJECT>
* 7: gunner - Gunner <OBJECT>
*
* Return Value:
* None
*
* Example:
* [bisFiredEH] call ace_artillerytables_fnc_firedEH
*
* Public: No
*/
params ["_vehicle", "", "", "", "", "_magazine", "_projectile", "_gunner"];
TRACE_4("firedEH",_vehicle,_magazine,_projectile,_gunner);
if (!([_gunner] call EFUNC(common,isPlayer))) exitWith {}; // AI don't know how to use (this does give them more range than a player)
if ((_vehicle turretUnit [0]) != _gunner) exitWith {};
// Get airFriction
private _airFriction = -0.00005;
if (!isNull (configFile >> "CfgMagazines" >> _magazine >> QGVAR(airFriction))) then {
_airFriction = getNumber (configFile >> "CfgMagazines" >> _magazine >> QGVAR(airFriction));
};
TRACE_1("",_airFriction);
if (_airFriction == 0) exitWith {}; // 0 disables everything
BEGIN_COUNTER(adjustmentsCalc);
// Calculate air density
private _altitude = (getPosASL _vehicle) select 2;
private _temperature = _altitude call EFUNC(weather,calculateTemperatureAtHeight);
private _pressure = _altitude call EFUNC(weather,calculateBarometricPressure);
private _relativeHumidity = EGVAR(weather,currentHumidity);
private _airDensity = [_temperature, _pressure, _relativeHumidity] call EFUNC(weather,calculateAirDensity);
private _relativeDensity = _airDensity / 1.225;
TRACE_5("Weather",_temperature,_pressure,_relativeHumidity,_airDensity,_relativeDensity);
private _kFactor = _airFriction * _relativeDensity;
private _newMuzzleVelocityCoefficent = (((_temperature + 273.13) / 288.13 - 1) / 40 + 1);
TRACE_2("",_kFactor,_newMuzzleVelocityCoefficent);
// Apply powder effects
if (_newMuzzleVelocityCoefficent != 1) then {
private _bulletVelocity = velocity _projectile;
private _bulletSpeed = vectorMagnitude _bulletVelocity;
_bulletVelocity = (vectorNormalized _bulletVelocity) vectorMultiply (_bulletSpeed * _newMuzzleVelocityCoefficent);
_projectile setVelocity _bulletVelocity;
};
[{
params ["_projectile", "_kFactor", "_time"];
if (isNull _projectile) exitWith {true};
private _deltaT = CBA_missionTime - _time;
_this set[2, CBA_missionTime];
private _bulletVelocity = velocity _projectile;
private _trueVelocity = _bulletVelocity vectorDiff wind;
private _trueSpeed = vectorMagnitude _trueVelocity;
private _drag = _deltaT * _kFactor * _trueSpeed;
private _accel = _trueVelocity vectorMultiply _drag;
// TRACE_3("",_bulletVelocity,_trueSpeed,_accel);
_bulletVelocity = _bulletVelocity vectorAdd _accel;
_projectile setVelocity _bulletVelocity;
false
}, {
// TRACE_1("done",_this);
}, [_projectile, _kFactor, CBA_missionTime]] call CBA_fnc_waitUntilAndExecute;
END_COUNTER(adjustmentsCalc);

View File

@ -0,0 +1,83 @@
#include "script_component.hpp"
/*
* Author: PabstMirror
* Interaction menu opened, search for nearby artillery vehicles.
*
* Arguments:
* 0: Vehicle <OBJECT>
* 1: Player <OBJECT>
*
* Return Value:
* Can Open <BOOL>
*
* Example:
* [bob, bob] call ace_artillerytables_fnc_interactMenuOpened
*
* Public: No
*/
params ["_menuType"];
TRACE_1("interactMenuOpened",_menuType);
if (_menuType != 1) exitWith {};
// if (!("ACE_artilleryTable" in (ace_player call EFUNC(common,uniqueItems)))) exitWith {};
private _vehicleAdded = ace_player getVariable [QGVAR(vehiclesAdded), []];
private _rangeTablesShown = ace_player getVariable [QGVAR(rangeTablesShown), []];
TRACE_2("",_vehicleAdded,_rangeTablesShown);
{
private _vehicle = _x;
private _typeOf = typeOf _vehicle;
private _enabled = _vehicle getVariable [QGVAR(enabled), getNumber (configFile >> "CfgVehicles" >> _typeOf >> "artilleryScanner")];
TRACE_3("",_vehicle,_typeOf,_enabled);
if ((_enabled in [1,true]) && {!(_vehicle in _vehicleAdded)}) then {
private _vehicle = _vehicle;
private _weaponsTurret = _vehicle weaponsTurret [0];
if ((count _weaponsTurret) != 1) exitWith { WARNING_1("multiple weapons - %1",_typeOf); };
private _weapon = _weaponsTurret select 0;
private _weaponDir = _vehicle weaponDirection _weapon;
private _turretRot = [vectorDir _vehicle, vectorUp _vehicle, (180 / PI) * (_vehicle animationPhase "mainTurret")] call FUNC(rotateVector3d);
private _neutralX = acos (_turretRot vectorDotProduct _weaponDir) - (180 / PI) * (_vehicle animationPhase "mainGun");
_neutralX = (round (_neutralX * 10)) / 10; // minimize floating point errors
private _turretCfg = [_typeOf, [0]] call CBA_fnc_getTurret;
private _minElev = _neutralX + getNumber (_turretCfg >> "minElev");
private _maxElev = _neutralX + getNumber (_turretCfg >> "maxElev");
private _advCorrection = GVAR(advancedCorrections) && {(getNumber (configFile >> "CfgVehicles" >> _typeOf >> QGVAR(skipCorrections))) != 1};
private _info = [_weapon, _minElev, _maxElev, _advCorrection]; // in case multiple vehicle types use the same weapon
_vehicleAdded pushBack _vehicle;
ace_player setVariable [QGVAR(vehiclesAdded), _vehicleAdded];
private _index = _rangeTablesShown pushBackUnique _info;
TRACE_2("",_info,_index);
if (_index != -1) then {
private _statement = {
params ["", "", "_info"];
TRACE_1("interaction statement",_info);
[{
_this call FUNC(rangeTableOpen); // delay a frame because of interaction menu closing dialogs
}, _info] call CBA_fnc_execNextFrame;
};
private _condition = {
//IGNORE_PRIVATE_WARNING ["_player"];
// ("ACE_artilleryTable" in (_player call EFUNC(common,uniqueItems)))
// && {[_player, objNull, ["notOnMap", "isNotSitting"]] call EFUNC(common,canInteractWith)}
true
};
private _displayName = format ["%1%2", getText (configFile >> "CfgVehicles" >> _typeOf >> "displayName"),["","*"] select _advCorrection];
private _action = [format ['QGVAR(%1)',_index], _displayName, QPATHTOF(UI\icon_rangeTable.paa), _statement, _condition, {}, _info] call EFUNC(interact_menu,createAction);
private _ret = [ace_player, 1, ["ACE_SelfActions", "ACE_Equipment"], _action] call EFUNC(interact_menu,addActionToObject);
TRACE_1("added action",_ret);
ace_player setVariable [QGVAR(rangeTablesShown), _rangeTablesShown];
};
};
} forEach (nearestObjects [ace_player, ["LandVehicle"], 25]);

View File

@ -0,0 +1,78 @@
#include "script_component.hpp"
/*
* Author: PabstMirror
* Opens the rangetable and fills the charge listbox.
*
* Arguments:
* None
*
* Return Value:
* None
*
* Example:
* [] call ace_artillerytables_fnc_rangeTableOpen
*
* Public: No
*/
params ["_weaponName", "_elevMin", "_elevMax", "_advCorrection"];
TRACE_3("rangeTableOpen",_weaponName,_elevMin,_elevMax);
createDialog QGVAR(rangeTableDialog);
private _dialog = uiNamespace getVariable [QGVAR(rangeTableDialog), displayNull];
if (isNull _dialog) exitWith{ERROR("Dialog failed to open");};
private _ctrlChargeList = _dialog displayCtrl IDC_CHARGELIST;
_dialog setVariable [QGVAR(elevMin),_elevMin];
_dialog setVariable [QGVAR(elevMax),_elevMax];
_dialog setVariable [QGVAR(advCorrection),_advCorrection];
TRACE_2("created dialog",_dialog,_ctrlChargeList);
// Get Mags:
private _mags = [_weaponName] call CBA_fnc_compatibleMagazines;
if (_mags isEqualTo []) exitWith {WARNING_1("No Mags",_weaponName);};
private _magCfg = configFile >> "CfgMagazines";
private _firstSpeed = getNumber (_magCfg >> (_mags select 0) >> "initSpeed");
private _allSame = true;
_mags = _mags apply {
private _initSpeed = getNumber (_magCfg >> _x >> "initSpeed");
if (_initSpeed != _firstSpeed) then {_allSame = false};
[getText (_magCfg >> _x >> "displayNameShort"), _initSpeed]
};
if (_allSame) then { _mags = [["-", _firstSpeed]]; };
TRACE_2("",_allSame,_mags);
// Get Firemodes:
private _fireModes = getArray (configFile >> "CfgWeapons" >> _weaponName >> "modes");
_fireModes = (_fireModes apply {configFile >> "CfgWeapons" >> _weaponName >> _x}) select {1 == getNumber (_x >> "showToPlayer")};
_fireModes = _fireModes apply {[getNumber (_x >> "artilleryCharge"), configName _x]};
_fireModes sort true;
private _allSameCharge = ((count _fireModes) == 1);
TRACE_2("",_allSameCharge,_fireModes);
private _index = 0;
{
_x params ["_xMagName", "_xMagInitSpeed"];
if (_allSameCharge) then {
_ctrlChargeList lbAdd format ["%1", _xMagName];
_ctrlChargeList lbSetTooltip [_index, format ["%1 m/s",_xMagInitSpeed toFixed 1]];
_ctrlChargeList lbSetData [_index, str (_xMagInitSpeed)];
_index = _index + 1;
} else {
{
_x params ["_xArtilleryCharge"];
_ctrlChargeList lbAdd format ["%1 Charge: %2", _xMagName, _forEachIndex]; // forEachIndex is for firemodes
_ctrlChargeList lbSetTooltip [_index, format ["%1 m/s", (_xMagInitSpeed * _xArtilleryCharge) toFixed 1]];
_ctrlChargeList lbSetData [_index, str (_xMagInitSpeed * _xArtilleryCharge)];
_index = _index + 1;
} forEach _fireModes;
};
} forEach _mags;
if (_index == 0) exitWith {ERROR_1("no modes %1",_weaponName);};
if (isNil QGVAR(lastElevationMode)) then {GVAR(lastElevationMode) = true;};
if (isNil QGVAR(lastCharge)) then {GVAR(lastCharge) = 0;};
if ((GVAR(lastCharge) >= _index) || {GVAR(lastCharge) < 0}) then { GVAR(lastCharge) = 0; };
TRACE_2("",GVAR(lastElevationMode),GVAR(lastCharge));
_ctrlChargeList lbSetCurSel GVAR(lastCharge); // triggers call to FUNC(rangeTableUpdate)

View File

@ -0,0 +1,71 @@
#include "script_component.hpp"
/*
* Author: PabstMirror
* Called when listbox selection changes. Updates the rangetable with new values.
*
* Arguments:
* 0: Elevation Mode (true = high,false=low) <BOOL><OPTIONAL>
*
* Return Value:
* None
*
* Example:
* [] call ace_artillerytables_fnc_rangeTableUpdate
*
* Public: No
*/
private _dialog = uiNamespace getVariable [QGVAR(rangeTableDialog), displayNull];
private _ctrlRangeTable = _dialog displayCtrl IDC_TABLE;
private _ctrlChargeList = _dialog displayCtrl IDC_CHARGELIST;
private _ctrlElevationHigh = _dialog displayCtrl IDC_BUTTON_ELEV_HIGH;
private _ctrlElevationLow = _dialog displayCtrl IDC_BUTTON_ELEV_LOW;
GVAR(lastElevationMode) = param [0, GVAR(lastElevationMode)];
GVAR(lastCharge) = lbCurSel _ctrlChargeList;
private _listBoxData = _ctrlChargeList lbData GVAR(lastCharge);
if (isNil "_listBoxData" || {_listBoxData == ""}) exitWith {ERROR("lbCurSel out of bounds or no data");};
private _muzzleVelocity = parseNumber _listBoxData;
private _elevMin = _dialog getVariable [QGVAR(elevMin), 0];
private _elevMax = _dialog getVariable [QGVAR(elevMax), 0];
_ctrlElevationHigh ctrlSetTextColor ([[0.25,0.25,0.25,1],[1,1,1,1]] select GVAR(lastElevationMode));
_ctrlElevationLow ctrlSetTextColor ([[1,1,1,1],[0.25,0.25,0.25,1]] select GVAR(lastElevationMode));
private _advCorrection = _dialog getVariable [QGVAR(advCorrection), false];
private _airFriction = if (_advCorrection) then {-0.00005} else {0};
TRACE_4("",_muzzleVelocity,_elevMin,_elevMax,_airFriction);
lnbClear _ctrlRangeTable;
TRACE_5("callExtension:start",_muzzleVelocity,_airFriction,_elevMin,_elevMax,GVAR(lastElevationMode));
private _ret = "ace_artillerytables" callExtension ["start", [_muzzleVelocity,_airFriction,_elevMin,_elevMax,GVAR(lastElevationMode)]];
TRACE_1("",_ret);
// Non-blocking read data out of extension as it becomes availiable
[{
private _dialog = uiNamespace getVariable [QGVAR(rangeTableDialog), displayNull];
private _ctrlRangeTable = _dialog displayCtrl IDC_TABLE;
if (isNull _dialog) exitWith {true};
private _status = 1; // 1 = data on line, 2 - data not ready, 3 - done
while {_status == 1} do {
private _ret = ("ace_artillerytables" callExtension ["getline", []]);
// TRACE_1("callExtension:getline",_ret);
_status = _ret select 1;
if (_status == 1) then { _ctrlRangeTable lnbAddRow parseSimpleArray (_ret select 0) };
};
(_status == 3) // exit loop when all data read
}, {
// put dummy line at end because scrolling is problematic and can't see last line
private _dialog = uiNamespace getVariable [QGVAR(rangeTableDialog), displayNull];
private _ctrlRangeTable = _dialog displayCtrl IDC_TABLE;
if (isNull _dialog) exitWith {TRACE_1("dialog closed",_this);};
_ctrlRangeTable lnbAddRow ["", "", "", "", "", "", "", "", "", "", ""];
TRACE_1("table filled",_ctrlRangeTable);
}, []] call CBA_fnc_waitUntilAndExecute;

View File

@ -0,0 +1,59 @@
#include "script_component.hpp"
/*
* Author: PabstMirror
* Turret changed.
*
* Arguments:
* 0: Player <OBJECT>
* 1: Turret <ARRAY>
*
* Return Value:
* Nothing
*
* Example:
* [player] call ace_artillerytables_fnc_turretChanged
*
* Public: No
*/
params ["_player", "_turret"];
private _vehicle = vehicle _player;
private _typeOf = typeOf _vehicle;
TRACE_3("turretEH",_player,_turret,_typeOf);
private _enabled = (alive _player) && {_turret isEqualTo [0]}
&& { ((_vehicle getVariable [QGVAR(enabled), getNumber (configFile >> "CfgVehicles" >> _typeOf >> "artilleryScanner")]) in [1, true])};
if (GVAR(pfID) >= 0) then {
TRACE_1("removing pfEH and display",GVAR(pfID));
[GVAR(pfID)] call CBA_fnc_removePerFrameHandler;
GVAR(pfID) = -1;
if (!isNull (uiNamespace getVariable [QGVAR(display), displayNull])) then {
([QGVAR(modeDisplay)] call BIS_fnc_rscLayer) cutText ["", "PLAIN"];
};
};
if (_enabled) then {
// Ugh, see FUNC(turretPFEH)
private _useAltElevation = (["Mortar_01_base_F", "rhs_2b14_82mm_Base", "RHS_M252_Base", "CUP_B_M1129_MC_MK19_Desert"] findIf {_typeOf isKindOf _x}) > -1;
private _weaponsTurret = _vehicle weaponsTurret _turret;
if ((count _weaponsTurret) != 1) then { WARNING_1("multiple weapons - %1",typeOf _vehicle); };
private _weapon = _weaponsTurret param [0, ""];
#ifdef DEBUG_MODE_FULL
private _ballisticsComputer = getNumber (configFile >> "CfgWeapons" >> _weapon >> "ballisticsComputer");
private _elevationMode = getNumber (([_typeOf, _turret] call CBA_fnc_getTurret) >> "elevationMode");
private _discreteDistance = getArray (([_typeOf, _turret] call CBA_fnc_getTurret) >> "discreteDistance");
TRACE_4("",_weapon,_ballisticsComputer,_elevationMode,_discreteDistance);
#endif
private _fireModes = getArray (configFile >> "CfgWeapons" >> _weapon >> "modes");
_fireModes = (_fireModes apply {configFile >> "CfgWeapons" >> _weapon >> _x}) select {1 == getNumber (_x >> "showToPlayer")};
_fireModes = _fireModes apply {[getNumber (_x >> "artilleryCharge"), configName _x]};
_fireModes sort true;
_fireModes = _fireModes apply {_x select 1};
GVAR(pfID) = [LINKFUNC(turretPFEH), 0, [_vehicle, _turret, _fireModes, _useAltElevation]] call CBA_fnc_addPerFrameHandler;
TRACE_1("added pfEH",GVAR(pfID));
};

View File

@ -0,0 +1,91 @@
#include "script_component.hpp"
/*
* Author: PabstMirror
* Shows real azimuth and elevation on hud
*
* Arguments:
* 0: PFEH Args <ARRAY>
*
* Return Value:
* Nothing
*
* Example:
* [[]] call ace_artillerytables_fnc_turretPFEH
*
* Public: No
*/
(_this select 0) params ["_mortarVeh", "_turret", "_fireModes", "_useAltElevation"];
if (shownArtilleryComputer && {GVAR(disableArtilleryComputer)}) then {
// Still Don't like this solution, but it works
closeDialog 0;
[parseText "Computer Disabled"] call EFUNC(common,displayTextStructured);
};
// Restart display if null (not just at start, this will happen periodicly)
if (isNull (uiNamespace getVariable [QGVAR(display), displayNull])) then {
TRACE_1("creating display",_this);
([QGVAR(modeDisplay)] call BIS_fnc_rscLayer) cutRsc [QGVAR(modeDisplay), "PLAIN", 1, false];
};
private _ctrlGroup = (uiNamespace getVariable [QGVAR(display), displayNull]) displayCtrl 1000;
if (cameraView != "GUNNER") exitWith { // need to be in gunner mode, so we can check where the optics are aiming at
_ctrlGroup ctrlShow false;
};
_ctrlGroup ctrlShow true;
BEGIN_COUNTER(pfeh);
private _currentFireMode = (weaponState [_mortarVeh, _turret]) select 2;
private _currentChargeMode = _fireModes find _currentFireMode;
private _lookVector = (AGLtoASL (positionCameraToWorld [0,0,0])) vectorFromTo (AGLtoASL (positionCameraToWorld [0,0,1]));
private _weaponDir = _mortarVeh weaponDirection (currentWeapon _mortarVeh);
// Calc real azimuth/elevation
// (looking at the sky VS looking at ground will radicaly change fire direction because BIS)
private _display = uiNamespace getVariable ["ACE_dlgArtillery", displayNull];
private _useRealWeaponDir = if ((isNull (_display displayCtrl 173)) || {(_mortarVeh ammo (currentWeapon _mortarVeh)) == 0}) then {
// With no ammo, distance display will be empty, but gun will still fire at wonky angle if aimed at ground
private _testSeekerPosASL = AGLtoASL (positionCameraToWorld [0,0,0]);
private _testPoint = _testSeekerPosASL vectorAdd (_lookVector vectorMultiply viewDistance);
!((terrainIntersectASL [_testSeekerPosASL, _testPoint]) || {lineIntersects [_testSeekerPosASL, _testPoint, _mortarVeh]});
} else {
((ctrlText (_display displayCtrl 173)) == "--")
};
private _realElevation = asin (_weaponDir select 2);
private _realAzimuth = if (_useRealWeaponDir) then {
// No range (looking at sky), it will follow weaponDir:
(_weaponDir select 0) atan2 (_weaponDir select 1)
} else {
// Valid range, will fire at camera dir
// Azimuth will still be look dir EVEN IF elevation has flipped over 90! (on steep hills)
if (_useAltElevation) then {
// Some mortars have odd launch angles... still not sure why
// This gets very close, (should be less than 0.5deg deviation on a 20deg slope)
private _turretRot = [vectorDir _mortarVeh, vectorUp _mortarVeh, (180 / PI) * (_mortarVeh animationPhase "mainTurret")] call FUNC(rotateVector3d);
_realElevation = acos (_turretRot vectorDotProduct _weaponDir) + ((_turretRot call CBA_fnc_vect2polar) select 2);
if (_realElevation > 90) then { _realElevation = 180 - _realElevation; }; // does not flip azimuth!
};
((_lookVector select 0) atan2 (_lookVector select 1))
};
if (_realAzimuth < 0) then { _realAzimuth = _realAzimuth + 360; };
private _ctrlCharge = (uiNamespace getVariable [QGVAR(display), displayNull]) displayCtrl IDC_CHARGE;
private _ctrlAzimuth = (uiNamespace getVariable [QGVAR(display), displayNull]) displayCtrl IDC_AZIMUTH;
private _ctrlElevation = (uiNamespace getVariable [QGVAR(display), displayNull]) displayCtrl IDC_ELEVATION;
_ctrlAzimuth ctrlSetText Format ["AZ: %1", [DEGTOMILS * _realAzimuth, 4, 0] call CBA_fnc_formatNumber];
_ctrlElevation ctrlSetText Format ["EL: %1", [DEGTOMILS * _realElevation, 4, 0] call CBA_fnc_formatNumber];
_ctrlCharge ctrlSetText format ["CH: %1", _currentChargeMode];
END_COUNTER(pfeh);
#ifdef DEBUG_MODE_FULL
// private _lookVector = (AGLtoASL (positionCameraToWorld [0,0,0])) vectorFromTo (AGLtoASL (positionCameraToWorld [0,0,10]));
// systemChat format ["AZ: %1 EL: %2", _realAzimuth toFixed 1, _realElevation toFixed 1];
// systemChat format ["Slope: %1 - Look: %2 [%3]", (acos ((vectorUp _mortarVeh) select 2)) toFixed 1, ((_lookVector select 0) atan2 (_lookVector select 1)), ["GND","SKY"] select _useRealWeaponDir];
GVAR(prediction) = _realElevation;
#endif

View File

@ -0,0 +1 @@
#include "\z\ace\addons\artillerytables\script_component.hpp"

View File

@ -0,0 +1,21 @@
// CBA Settings [ADDON: ace_artillerytables]:
[
QGVAR(advancedCorrections), "CHECKBOX",
[LSTRING(advancedCorrections_displayName), LSTRING(advancedCorrections_description)],
"ACE Artillery",
false, // default value
true, // isGlobal
{[QGVAR(advancedCorrections), _this] call EFUNC(common,cbaSettings_settingChanged)},
true // Needs mission restart
] call CBA_settings_fnc_init;
[
QGVAR(disableArtilleryComputer), "CHECKBOX",
[LSTRING(disableArtilleryComputer_displayName), LSTRING(disableArtilleryComputer_description)],
"ACE Artillery",
false, // default value
true, // isGlobal
{[QGVAR(disableArtilleryComputer), _this] call EFUNC(common,cbaSettings_settingChanged)},
false // Needs mission restart
] call CBA_settings_fnc_init;

View File

@ -0,0 +1,21 @@
#define COMPONENT artillerytables
#define COMPONENT_BEAUTIFIED ArtilleryTables
#include "\z\ace\addons\main\script_mod.hpp"
#define DEBUG_MODE_FULL
#define DISABLE_COMPILE_CACHE
#define ENABLE_PERFORMANCE_COUNTERS
#include "\z\ace\addons\main\script_macros.hpp"
#define DEGTOMILS 17.7777778
#define IDC_MODECONTROLGROUP 1000
#define IDC_CHARGE 1001
#define IDC_AZIMUTH 1002
#define IDC_ELEVATION 1003
#define IDC_TABLE 2001
#define IDC_CHARGELIST 2002
#define IDC_BUTTON_ELEV_HIGH 2003
#define IDC_BUTTON_ELEV_LOW 2004

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<Project name="ACE">
<Package name="Mk6Mortar">
<Key ID="STR_ACE_artillerytables_rangetable_displayName">
<English>Artillery Rangetable</English>
</Key>
<Key ID="STR_ACE_artillerytables_rangetable_descriptionn">
<English>Universal Artillery Rangetable</English>
</Key>
<Key ID="STR_ACE_artillerytables_advancedCorrections_displayName">
<English>Air Resistance</English>
<Polish>Opór powietrza</Polish>
<Spanish>Resistencia al aire</Spanish>
<German>Luftwiderstand</German>
<Czech>Odpor vzduchu</Czech>
<Portuguese>Resistência do Ar</Portuguese>
<French>Résistance de l'air</French>
<Hungarian>Légellenállás</Hungarian>
<Russian>Сопротивление воздуха</Russian>
<Italian>Resistenza dell'Aria</Italian>
<Japanese>空気抵抗</Japanese>
<Korean>공기저항</Korean>
<Chinesesimp>空气阻力</Chinesesimp>
<Chinese>空氣阻力</Chinese>
</Key>
<Key ID="STR_ACE_artillerytables_advancedCorrections_description">
<English>For Player Shots, Model Air Resistance and Wind Effects</English>
<Polish>Modeluj opór powietrza oraz wpływ wiatru na tor lotu pocisku dla strzałów z moździerza Mk6 przez graczy</Polish>
<Spanish>Para disparos del jugador, modelo de resistencia al aire y efectos de viento</Spanish>
<German>Für Spielerschüsse, Luftwiderstand und Windeffekte</German>
<Czech>Pro hráčovu střelbu, Model odporu vzduchu a povětrných podmínek</Czech>
<Portuguese>Para disparos do jogador, modelo de resistência de ar e efeitos de vento</Portuguese>
<French>Pour les tirs de joueurs, modèle de résistance à l'air et d'effet du vent</French>
<Hungarian>Játékos általi lövésekhez, legyen-e számított légellenállás és szélhatás</Hungarian>
<Russian>Для выстрелов игрока. Моделирует сопротивление воздуха и эффект ветра</Russian>
<Italian>Per Proiettili dei Giocatori, simula la Resistenza dell'Aria e gli Effetti del Vento</Italian>
<Japanese>プレイヤが射撃すると、空気抵抗モデルと風による影響をあたえます。</Japanese>
<Korean>플레이어 사격시 공기저항과 바람에 영향을 받습니다</Korean>
<Chinesesimp>设定由玩家射击的迫击炮,将会受到空气阻力与风力的影响</Chinesesimp>
<Chinese>設定由玩家射擊的迫擊砲,將會受到空氣阻力與風力的影響</Chinese>
</Key>
<Key ID="STR_ACE_artillerytables_disableArtilleryComputer_displayName">
<English>Disable Artillery Computer</English>
</Key>
<Key ID="STR_ACE_artillerytables_disableArtilleryComputer_description">
<English>Disable the vanilla artillery computers</English>
</Key>
</Package>
</Project>

View File

@ -89,8 +89,8 @@ endif()
string(TIMESTAMP ACE_BUILDSTAMP "%Y-%m-%dT%H:%M:%SZ")
set(ACE_VERSION_MAJOR 3)
set(ACE_VERSION_MINOR 12)
set(ACE_VERSION_REVISION 3)
set(ACE_VERSION_MINOR 15)
set(ACE_VERSION_REVISION 6)
EXECUTE_PROCESS(COMMAND git rev-parse --verify HEAD
OUTPUT_VARIABLE T_ACE_VERSION_BUILD
OUTPUT_STRIP_TRAILING_WHITESPACE
@ -127,6 +127,7 @@ add_subdirectory(clipboard)
add_subdirectory(advanced_ballistics)
add_subdirectory(medical)
add_subdirectory(parse_imagepath)
add_subdirectory(artillerytables)
# Test Extension for dynamically loading/unloading built extensions; does not build in release
if (DEVEL)

View File

@ -0,0 +1,12 @@
set(ACE_EXTENSION_NAME "ace_artillerytables")
file(GLOB SOURCES *.h *.hpp *.c *.cpp)
add_library( ${ACE_EXTENSION_NAME} SHARED ${SOURCES} ${GLOBAL_SOURCES})
target_link_libraries(${ACE_EXTENSION_NAME} ace_common)
set_target_properties(${ACE_EXTENSION_NAME} PROPERTIES PREFIX "")
set_target_properties(${ACE_EXTENSION_NAME} PROPERTIES FOLDER Extensions)
if(CMAKE_COMPILER_IS_GNUCXX)
set_target_properties(${ACE_EXTENSION_NAME} PROPERTIES LINK_SEARCH_START_STATIC 1)
set_target_properties(${ACE_EXTENSION_NAME} PROPERTIES LINK_SEARCH_END_STATIC 1)
endif()

View File

@ -0,0 +1,360 @@
/*
* ace_artillerytables.cpp
* Author: PabstMirror
*/
//#define TEST_EXE
#define _USE_MATH_DEFINES
#include <cmath>
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <tuple>
#include <algorithm>
#include <chrono>
#include <future>
// ace libs:
#include "vector.hpp"
#ifndef TEST_EXE
extern "C" {
__declspec(dllexport) void __stdcall RVExtension(char* output, int outputSize, const char* function);
__declspec(dllexport) int __stdcall RVExtensionArgs(char* output, int outputSize, const char* function, const char** argv, int argc);
__declspec(dllexport) void __stdcall RVExtensionVersion(char* output, int outputSize);
}
#endif
// Constants
const double timeStep = 1.0 / 100;
const double rangeSearchErrorMax = 0.001; // ratio * distance
const double rangeSearchAngleConvergance = 0.00001;
const double gravityABS = 9.8066;
const ace::vector3<double> gravityAccl(0, 0, -1 * gravityABS);
// Globals:
std::vector<std::future<std::string>> fWorkers;
unsigned int getLineIndex = 0;
std::tuple<double, double, double> simulateShot(const double& _fireAngleRad, const double& _muzzleVelocity, const double& _heightOfTarget, const double& _crossWind, const double& _tailWind, const double& _temperature, const double& _airDensity, double _airFriction) {
double kCoefficient = -1.0 * _airDensity * _airFriction;
double powderEffects = (_airFriction) ? ((_temperature + 273.13) / 288.13 - 1) / 40 + 1 : 1.0;
double currentTime = 0;
ace::vector3<double> currentPosition(0, 0, 0);
ace::vector3<double> lastPosition(currentPosition);
ace::vector3<double> currentVelocity(0, powderEffects * _muzzleVelocity * cos(_fireAngleRad), powderEffects * _muzzleVelocity * sin(_fireAngleRad));
ace::vector3<double> wind(_crossWind, _tailWind, 0);
while ((currentVelocity.z() > 0) || (currentPosition.z() >= _heightOfTarget)) {
lastPosition = currentPosition;
ace::vector3<double> apparentWind = wind - currentVelocity;
ace::vector3<double> changeInVelocity = gravityAccl + apparentWind * (kCoefficient * apparentWind.magnitude());
currentVelocity += changeInVelocity * timeStep;
currentPosition += currentVelocity * timeStep;
currentTime += timeStep;
}
double lastCurrentRatio((_heightOfTarget - currentPosition.z()) / (lastPosition.z() - currentPosition.z()));
ace::vector3<double> finalPos = lastPosition.lerp(currentPosition, lastCurrentRatio);
return { finalPos.x(), finalPos.y(), currentTime };
}
std::tuple<double, double> findMaxAngle(const double& _muzzleVelocity, const double& _airFriction) {
if (_airFriction == 0) {
return { M_PI_4, _muzzleVelocity * _muzzleVelocity / gravityABS };
}
double bestAngle = M_PI_4;
double bestDistance = -1;
double testResultDist;
for (double testAngle = M_PI_4; testAngle > 0; testAngle -= (M_PI_4 / 100.0)) {
std::tie(std::ignore, testResultDist, std::ignore) = simulateShot(testAngle, _muzzleVelocity, 0, 0, 0, 15, 1, _airFriction);
if (testResultDist > bestDistance) {
bestAngle = testAngle;
bestDistance = testResultDist;
}
}
return { bestAngle, bestDistance };
}
std::tuple<double, double, double> simulateFindSolution(const double& _rangeToHit, const double& _heightToHit, const double& _muzzleVelocity, const double& _airFriction, const double& _minElev, const double& _maxElev, const bool& _highArc) {
// returns: actual distance traveled, elevation, time of flight
double searchMin = _minElev;
double searchMax = _maxElev;
if (!_airFriction) {
// can do trivial ballistics physics to get angle, could compute tof as well, but run through sim once to ensure consistancy (uses positive value of g)
double radicand = pow(_muzzleVelocity, 4) - gravityABS * (gravityABS * pow(_rangeToHit, 2) + 2 * _heightToHit * pow(_muzzleVelocity, 2));
if ((!_rangeToHit) || (radicand < 0)) { // radican't
return { -1, -1, -1 };
}
radicand = sqrt(radicand);
double angleRoot = atan((pow(_muzzleVelocity, 2) + radicand) / (gravityABS * _rangeToHit));
if ((angleRoot > _maxElev) || (angleRoot < _minElev)) {
angleRoot = atan((pow(_muzzleVelocity, 2) - radicand) / (gravityABS * _rangeToHit));
}
if ((angleRoot > _maxElev) || (angleRoot < _minElev)) {
return { -1, -1, -1 };
};
double tof = _rangeToHit / (_muzzleVelocity * cos(angleRoot));
return { _rangeToHit, angleRoot, tof };
}
int numberOfAttempts = 0;
double resultDistance = -1;
double resultTime = -1;
double currentError = 9999;
double currentElevation = -1;
do {
if (numberOfAttempts++ > 50) break; // for safetey, min/max should converge long before
currentElevation = (searchMin + searchMax) / 2.0;
std::tie(std::ignore, resultDistance, resultTime) = simulateShot(currentElevation, _muzzleVelocity, _heightToHit, 0, 0, 15, 1, _airFriction);
currentError = _rangeToHit - resultDistance;
// printf("elev %f [%f, %f]range%f\n goes %f [%f]\n", currentElevation, searchMin, searchMax, (searchMax - searchMin), resultDistance, currentError);
if ((currentError > 0) ^ (!_highArc)) {
searchMax = currentElevation;
}
else {
searchMin = currentElevation;
}
} while ((searchMax - searchMin) > rangeSearchAngleConvergance);
// printf("[%f, %f] Actuall [%f] err [%f of %f]\n", _rangeToHit, _heightToHit, resultDistance, currentError, (_rangeToHit * rangeSearchErrorMax * (_highArc ? 1.0 : 2.0)));
// On some low angle shots, it will really struggle to converge because of precision issues
if ((abs(currentError) > (_rangeToHit * rangeSearchErrorMax * (_highArc ? 1.0 : 2.0)))) {
return { -1, -1, -1 };
}
return { resultDistance, currentElevation, resultTime };
}
void writeNumber(std::stringstream & ss, double _num, const int& _widthInt, const int& _widthDec) {
if ((_num < 0) && (_num > -0.05)) { // hard coded fix -0.0 rounding errors
_num = 0;
}
if (_widthInt > 1) {
ss << std::setfill('0');
}
ss << std::fixed << std::setw(_widthInt) << std::setprecision(_widthDec) << _num;
}
std::string simulateCalcRangeTableLine(const double& _rangeToHit, const double& _muzzleVelocity, const double& _airFriction, const double& _minElev, const double& _maxElev, const bool& _highArc) {
auto [actualDistance, lineElevation, lineTimeOfFlight] = simulateFindSolution(_rangeToHit, 0, _muzzleVelocity, _airFriction, _minElev, _maxElev, _highArc);
if (lineTimeOfFlight < 0) {
return "";
}
auto [actualDistanceHeight, lineHeightElevation, lineHeightTimeOfFlight] = simulateFindSolution(_rangeToHit, -100, _muzzleVelocity, _airFriction, _minElev, _maxElev, _highArc);
std::stringstream returnSS;
returnSS << "[\"";
writeNumber(returnSS, _rangeToHit, 0, 0);
returnSS << "\",\"";
writeNumber(returnSS, lineElevation * 3200.0 / M_PI, 0, 0);
returnSS << "\",\"";
if (lineHeightElevation > 0) {
double drElevAdjust = lineHeightElevation - lineElevation;
double drTofAdjust = lineHeightTimeOfFlight - lineTimeOfFlight;
writeNumber(returnSS, drElevAdjust * 3200.0 / M_PI, 0, 0);
returnSS << "\",\"";
writeNumber(returnSS, drTofAdjust, 0, 1);
}
else {
// low angle shots won't be able to adjust down further
returnSS << "-\",\"-";
}
returnSS << "\",\"";
writeNumber(returnSS, lineTimeOfFlight, 0, ((lineTimeOfFlight < 99.945) ? 1 : 0)); // round TOF when high
returnSS << "\",\"";
if (_airFriction) {
// Calc corrections:
double xOffset, yOffset;
// Crosswind
std::tie(xOffset, std::ignore, std::ignore) = simulateShot(lineElevation, _muzzleVelocity, 0, 10, 0, 15, 1, _airFriction);
double crosswindOffsetRad = atan2(xOffset, actualDistance) / 10;
// Headwind
std::tie(std::ignore, yOffset, std::ignore) = simulateShot(lineElevation, _muzzleVelocity, 0, 0, -10, 15, 1, _airFriction);
double headwindOffset = (actualDistance - yOffset) / 10;
// Tailwind
std::tie(std::ignore, yOffset, std::ignore) = simulateShot(lineElevation, _muzzleVelocity, 0, 0, 10, 15, 1, _airFriction);
double tailwindOffset = (actualDistance - yOffset) / 10;
// Air Temp Dec
std::tie(std::ignore, yOffset, std::ignore) = simulateShot(lineElevation, _muzzleVelocity, 0, 0, 0, 5, 1, _airFriction);
double tempDecOffset = (actualDistance - yOffset) / 10;
// Air Temp Inc
std::tie(std::ignore, yOffset, std::ignore) = simulateShot(lineElevation, _muzzleVelocity, 0, 0, 0, 25, 1, _airFriction);
double tempIncOffset = (actualDistance - yOffset) / 10;
// Air Density Dec
std::tie(std::ignore, yOffset, std::ignore) = simulateShot(lineElevation, _muzzleVelocity, 0, 0, 0, 15, 0.9, _airFriction);
double airDensityDecOffset = (actualDistance - yOffset) / 10;
// Air Density Inc
std::tie(std::ignore, yOffset, std::ignore) = simulateShot(lineElevation, _muzzleVelocity, 0, 0, 0, 15, 1.1, _airFriction);
double airDensityIncOffset = (actualDistance - yOffset) / 10;
writeNumber(returnSS, crosswindOffsetRad * 3200.0 / M_PI, 1, 1);
returnSS << "\",\"";
writeNumber(returnSS, headwindOffset, 1, (abs(headwindOffset) > 9.949) ? 0 : 1);
returnSS << "\",\"";
writeNumber(returnSS, tailwindOffset, 1, (abs(tailwindOffset) > 9.949) ? 0 : 1);
returnSS << "\",\"";
writeNumber(returnSS, tempDecOffset, 1, (abs(tempDecOffset) > 9.949) ? 0 : 1);
returnSS << "\",\"";
writeNumber(returnSS, tempIncOffset, 1, (abs(tempIncOffset) > 9.949) ? 0 : 1);
returnSS << "\",\"";
writeNumber(returnSS, airDensityDecOffset, 1, (abs(airDensityDecOffset) > 9.949) ? 0 : 1);
returnSS << "\",\"";
writeNumber(returnSS, airDensityIncOffset, 1, (abs(airDensityIncOffset) > 9.949) ? 0 : 1);
returnSS << "\"]";
}
else {
returnSS << "-\",\"-\",\"-\",\"-\",\"-\",\"-\",\"-\"]"; // 7 dashes
}
return (returnSS.str());
}
#ifndef ACE_FULL_VERSION_STR
#define ACE_FULL_VERSION_STR "not defined"
#endif
void RVExtensionVersion(char* output, int outputSize) {
strncpy_s(output, outputSize, ACE_FULL_VERSION_STR, _TRUNCATE);
}
void RVExtension(char* output, int outputSize, const char* function) {
if (!strcmp(function, "version")) {
RVExtensionVersion(output, outputSize);
return;
}
strncpy_s(output, outputSize, "error", _TRUNCATE);
}
int RVExtensionArgs(char* output, int outputSize, const char* function, const char** args, int argsCnt) {
if (!strcmp(function, "version")) {
RVExtensionVersion(output, outputSize);
return 0;
}
if (!strcmp(function, "start")) {
if (argsCnt != 5) { return -2; } // Error: not enough args
double muzzleVelocity = strtod(args[0], NULL);
double airFriction = strtod(args[1], NULL);
double minElev = (M_PI / 180.0) * strtod(args[2], NULL);
double maxElev = (M_PI / 180.0) * strtod(args[3], NULL);
bool highArc = !strcmp(args[4], "true");
// Reset workers:
fWorkers.clear();
getLineIndex = 0;
auto [bestAngle, bestDistance] = findMaxAngle(muzzleVelocity, airFriction);
minElev = std::max(minElev, 2 * (M_PI / 180.0)); // cap min to 2 degrees (negative elev might get messy)
maxElev = std::min(maxElev, 88 * (M_PI / 180.0)); // cap max to 88 degrees (mk6)
if (highArc) {
minElev = std::max(minElev, bestAngle);
}
else {
maxElev = std::min(maxElev, bestAngle);
}
double loopStart = (bestDistance < 4000) ? 50 : 100;
double loopInc = (bestDistance < 5000) ? 50 : 100; // simplify when range gets high
double loopMaxRange = std::min(bestDistance, 25000.0); // with no air resistance, max range could go higher than 60km
if (maxElev > minElev) { // don't bother if we can't hit anything (e.g. mortar in low mode)
for (double range = loopStart; range < loopMaxRange; range += loopInc) {
fWorkers.emplace_back(std::async(&simulateCalcRangeTableLine, range, muzzleVelocity, airFriction, minElev, maxElev, highArc));
}
}
std::stringstream outputStr; // debug max distance and thead count
outputStr << "[" << bestDistance << "," << fWorkers.size() << "]";
strncpy_s(output, outputSize, outputStr.str().c_str(), _TRUNCATE);
return 0;
}
if (!strcmp(function, "getline")) {
// 1 = data on line, 2 - data not ready, 3 - done
std::string result = "";
std::future_status workerStatus;
while (result.empty()) {
if (getLineIndex >= fWorkers.size()) {
return 3;
}
workerStatus = fWorkers[getLineIndex].wait_for(std::chrono::seconds(0));
if (workerStatus != std::future_status::ready) {
return 2;
}
result = fWorkers[getLineIndex].get();
getLineIndex++;
}
strncpy_s(output, outputSize, result.c_str(), _TRUNCATE);
return 1;
}
return -1; // Error: function not valid
}
#ifdef TEST_EXE
int main() {
//double a, b;
//std::tie(a, b) = simulateFindSolution(200,50, 100, 0, 0, 45 * (M_PI / 180.0), false);
//printf("sim: %f, %f\n",a,b);
//std::string r = simulateCalcRangeTableLine(4000, 810, );
//printf("result: [%s]\n", r.c_str());
//auto [lineElevation, lineTimeOfFlight] = simulateFindSolution(4000, 0, 810, -0.00005, 5 * (M_PI / 180.0), 80 * (M_PI / 180.0), false);
//printf("result: [%f, %f]\n", lineElevation, lineTimeOfFlight);
// Determine realistic air firiction values
/*
double mv = 241;
printf(" %f m/s\n", mv);
double range;
for (double ar = 0; ar > -0.00015; ar -= 0.00001) {
std::tie(std::ignore, range) = findMaxAngle(mv, ar);
printf("[%f] = %f\n", ar, range);
}
*/
/*
// test callExtension
char output[256];
char function1[] = "start";
//const char* args1[] = { "200", "0", "-5", "80", "false" };
//const char* args1[] = { "153.9", "-0.00005", "-5", "80", "false" };
const char* args1[] = { "810", "-0.00005", "-5", "80", "false" };
//const char* args1[] = { "810", "0", "-5", "80", "true" };
auto t1 = std::chrono::high_resolution_clock::now();
int ret = RVExtensionArgs(output, 256, function1, args1, 5);
auto t2 = std::chrono::high_resolution_clock::now();
std::printf("ret: %d - %s\n", ret, output);
std::printf("func %s: %1.1f ms\n", function1, std::chrono::duration<double, std::milli>(t2 - t1).count());
int lines = 0;
auto t3 = std::chrono::high_resolution_clock::now();
char function2[] = "getline";
int ret2 = 0;
while (ret2 != 3) { // dumb spin
ret2 = RVExtensionArgs(output, 256, function2, NULL, 0);
if (ret2 == 1) {
lines++;
//std::printf("ret: %d - %s\n", ret2, output);
}
}
auto t4 = std::chrono::high_resolution_clock::now();
std::printf("func %s: %1.1f ms with %d lines\n", function2, std::chrono::duration<double, std::milli>(t4 - t3).count(), lines);
std::printf("callExtensions finished in %1.1f ms\n", std::chrono::duration<double, std::milli>(t4 - t1).count());
*/
}
#endif