Initial rollback to old frag simulation and spalling simulation. Updated spall to function and helpter functions as well

This commit is contained in:
lambdatiger 2024-07-17 01:53:40 -05:00
parent 15de0d9a3a
commit b656ef6467
23 changed files with 345 additions and 1040 deletions

View File

@ -87,6 +87,5 @@ class CfgAmmo {
};
#include "CfgAmmoSpall.hpp"
#include "CfgAmmoFragSpawner.hpp"
#include "CfgAmmoFragParameters.hpp"
};

View File

@ -372,7 +372,7 @@ class APERSMineDispenser_Mine_Ammo: APERSMine_Range_Ammo {
GVAR(skip) = 1;
};
class UXO_deploy_base_f: SubmunitionBase;
class UXO_deploy_base_f: SubmunitionBase {};
class ClaymoreDirectionalMine_Remote_Ammo: DirectionalBombBase {
GVAR(skip) = 1;

View File

@ -1,81 +0,0 @@
#define TARGETED_SPAWNER_PROTOTYPE(size) class GVAR(DOUBLES(size,spawner_2_short)): GVAR(spawnbase_targeted) {\
submunitionAmmo = QGVAR(size);\
};\
class GVAR(DOUBLES(size,spawner_2_mid)): GVAR(DOUBLES(size,spawner_2_short)) {\
submunitionConeAngle = 2;\
};\
class GVAR(DOUBLES(size,spawner_2_far)): GVAR(DOUBLES(size,spawner_2_short)) {\
submunitionConeAngle = 0.9;\
};\
class GVAR(DOUBLES(size,spawner_3_short)): GVAR(DOUBLES(size,spawner_2_short)) {\
submunitionConeType[] = {"random", 3};\
};\
class GVAR(DOUBLES(size,spawner_3_mid)): GVAR(DOUBLES(size,spawner_3_short)) {\
submunitionConeAngle = 2;\
};\
class GVAR(DOUBLES(size,spawner_3_far)): GVAR(DOUBLES(size,spawner_3_short)) {\
submunitionConeAngle = 0.9;\
}
#define RANDOM_SPAWNER_PROTOTYPE(size,count) class GVAR(DOUBLES(TRIPLES(random,size,count),mid)): GVAR(spawnbase) {\
submunitionConeType[] = {"random", count};\
submunitionAmmo = QGVAR(size);\
submunitionConeAngle = 85;\
triggerSpeedCoef[] = {-1.5, 1.5};\
};\
class GVAR(DOUBLES(TRIPLES(random,size,count),high)): GVAR(spawnbase) {\
submunitionConeType[] = {"random", count};\
submunitionAmmo = QGVAR(size);\
submunitionConeAngle = 80;\
triggerSpeedCoef[] = {0.75, 1.5};\
};\
class GVAR(DOUBLES(TRIPLES(random,size,count),top)): GVAR(spawnbase) {\
submunitionConeType[] = {"random", count};\
submunitionAmmo = QGVAR(size);\
submunitionConeAngle = 60;\
triggerSpeedCoef[] = {0.75, 1.5};\
}
class GVAR(spawnbase): B_65x39_Caseless {
GVAR(skip) = 1;
deleteParentWhenTriggered = 1;
explosionEffects = "";
submunitionConeType[] = {"random", 25};
submunitionAmmo[] = {QGVAR(tiny), 3, QGVAR(tiny_HD), 3, QGVAR(small), 4, QGVAR(small_HD), 4, QGVAR(medium_HD), 5};
submunitionDirectionType = "SubmunitionModelDirection";
submunitionConeAngleHorizontal = 15;
submunitionConeAngle = 87;
submunitionInitialOffset[] = {0,0,0};
submunitionInitSpeed = 0;
submunitionParentSpeedCoef = 1;
triggerSpeedCoef[] = {0.75, 1.5};
triggerTime = 0;
};
RANDOM_SPAWNER_PROTOTYPE(tiny,15);
RANDOM_SPAWNER_PROTOTYPE(tiny,10);
RANDOM_SPAWNER_PROTOTYPE(tiny,5);
RANDOM_SPAWNER_PROTOTYPE(small,15);
RANDOM_SPAWNER_PROTOTYPE(small,10);
RANDOM_SPAWNER_PROTOTYPE(small,5);
/*
* Targeted fragment spawner, for when multiple fragments are spawned (1-3)
*/
class GVAR(spawnbase_targeted): GVAR(spawnbase) {
submunitionConeType[] = {"random", 2};
submunitionConeAngle = 4.5;
triggerSpeedCoef[] = {0.5, 1};
};
TARGETED_SPAWNER_PROTOTYPE(tiny);
TARGETED_SPAWNER_PROTOTYPE(tiny_HD);
TARGETED_SPAWNER_PROTOTYPE(small);
TARGETED_SPAWNER_PROTOTYPE(small_HD);
TARGETED_SPAWNER_PROTOTYPE(medium);
TARGETED_SPAWNER_PROTOTYPE(medium_HD);
TARGETED_SPAWNER_PROTOTYPE(large);
TARGETED_SPAWNER_PROTOTYPE(large_HD);
TARGETED_SPAWNER_PROTOTYPE(huge);
TARGETED_SPAWNER_PROTOTYPE(huge_HD);

View File

@ -1,158 +1,15 @@
class GVAR(spallBase): B_65x39_Caseless {
GVAR(skip) = 1;
submunitionAmmo[] = {QGVAR(small),4,QGVAR(medium),3,QGVAR(large),2,QGVAR(huge),1};
submunitionConeType[] = {"random", 20};
submunitionConeAngle = 40;
submunitionDirectionType = "SubmunitionModelDirection";
triggerTime = 0;
submunitionInitialOffset[] = {0,0,0};
submunitionInitSpeed = 0;
triggerSpeedCoef[] = {0.75,1.25};
deleteParentWhenTriggered = 1;
submunitionParentSpeedCoef = 1;
class GVAR(spall_small): GVAR(small) {
timeToLive = 0.1;
};
/*
* ground
*/
class GVAR(ground_spall_tiny): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 9, QGVAR(small), 1};
submunitionConeType[] = {"poissondisccenter", 4};
class GVAR(spall_medium): GVAR(medium) {
timeToLive = 0.15;
};
class GVAR(ground_spall_small): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(small), 4,QGVAR(medium), 1};
submunitionConeType[] = {"poissondisccenter", 6};
class GVAR(spall_large): GVAR(large) {
timeToLive = 0.25;
};
class GVAR(ground_spall_medium): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(small), 2, QGVAR(small_HD), 1,QGVAR(medium), 3, QGVAR(medium_HD), 1, QGVAR(large), 2};
submunitionConeType[] = {"poissondisccenter", 15};
};
class GVAR(ground_spall_large): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 3, QGVAR(tiny_HD), 3, QGVAR(small), 4, QGVAR(small_HD), 4, QGVAR(medium_HD), 5, QGVAR(large), 1, QGVAR(large_HD), 2};
submunitionConeType[] = {"poissondisccenter", 15};
};
class GVAR(ground_spall_huge): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 3, QGVAR(tiny_HD), 3, QGVAR(small), 4, QGVAR(small_HD), 4, QGVAR(medium), 5, QGVAR(large), 1, QGVAR(large_HD), 2};
submunitionConeType[] = {"poissondisccenter", 20};
};
/*
* rock
*/
class GVAR(rock_spall_tiny): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(small), 4, QGVAR(medium_HD), 1};
submunitionConeType[] = {"poissondisccenter", 4};
};
class GVAR(rock_spall_small): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(small), 4, QGVAR(medium), 1};
submunitionConeType[] = {"poissondisccenter", 5};
};
class GVAR(rock_spall_medium): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(small), 4, QGVAR(medium), 1, QGVAR(medium_HD), 2, QGVAR(large_HD), 1};
submunitionConeType[] = {"poissondisccenter", 7};
};
class GVAR(rock_spall_large): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(small), 5, QGVAR(medium), 2, QGVAR(large), 1};
submunitionConeType[] = {"poissondisccenter", 10};
};
class GVAR(rock_spall_huge): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(small), 5, QGVAR(medium), 2, QGVAR(large), 1, QGVAR(huge_HD), 1};
submunitionConeType[] = {"poissondisccenter", 13};
};
/*
* wood
*/
class GVAR(wood_spall_tiny): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 2, QGVAR(small), 4};
submunitionConeType[] = {"poissondisccenter", 4};
};
class GVAR(wood_spall_small): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 2, QGVAR(small), 4, QGVAR(medium), 1};
submunitionConeType[] = {"poissondisccenter", 6};
};
class GVAR(wood_spall_medium): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 2, QGVAR(small), 2, QGVAR(medium), 2, QGVAR(medium_HD), 1, QGVAR(large_HD), 1};
submunitionConeType[] = {"poissondisccenter", 8};
};
class GVAR(wood_spall_large): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 1, QGVAR(small), 3, QGVAR(medium), 2, QGVAR(large_HD), 2, QGVAR(huge_HD), 1};
submunitionConeType[] = {"poissondisccenter", 10};
};
class GVAR(wood_spall_huge): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 1, QGVAR(small), 3, QGVAR(medium), 2, QGVAR(large_HD), 2, QGVAR(huge_HD), 1};
submunitionConeType[] = {"poissondisccenter", 12};
};
/*
* concrete
*/
class GVAR(concrete_spall_tiny): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 6, QGVAR(tiny_HD), 3, QGVAR(small), 1};
submunitionConeType[] = {"poissondisccenter", 4};
};
class GVAR(concrete_spall_small): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 4, QGVAR(tiny_HD), 2, QGVAR(small), 2, QGVAR(medium), 1};
submunitionConeType[] = {"poissondisccenter", 6};
};
class GVAR(concrete_spall_medium): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 6, QGVAR(tiny_HD), 1, QGVAR(small), 4, QGVAR(medium), 5, QGVAR(large_HD), 1};
submunitionConeType[] = {"poissondisccenter", 9};
};
class GVAR(concrete_spall_large): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 6, QGVAR(small), 4, QGVAR(medium), 3, QGVAR(large), 1, QGVAR(large_HD), 2};
submunitionConeType[] = {"poissondisccenter", 12};
};
class GVAR(concrete_spall_huge): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 8, QGVAR(small), 4, QGVAR(medium), 3, QGVAR(large), 1, QGVAR(large_HD), 1, QGVAR(huge_HD), 1};
submunitionConeType[] = {"poissondisccenter", 18};
};
/*
* metal
*/
class GVAR(metal_spall_tiny): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 9, QGVAR(small), 1};
submunitionConeType[] = {"poissondisccenter", 4};
};
class GVAR(metal_spall_small): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 4, QGVAR(small), 2, QGVAR(medium), 1};
submunitionConeType[] = {"poissondisccenter", 6};
};
class GVAR(metal_spall_medium): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 5, QGVAR(small), 4, QGVAR(medium), 2, QGVAR(large), 1};
submunitionConeType[] = {"poissondisccenter", 8};
};
class GVAR(metal_spall_large): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 4, QGVAR(small), 3, QGVAR(medium), 2, QGVAR(large), 1, QGVAR(huge), 1};
submunitionConeType[] = {"poissondisccenter", 12};
};
class GVAR(metal_spall_huge): GVAR(spallBase) {
submunitionAmmo[] = {QGVAR(tiny), 8, QGVAR(small), 3, QGVAR(medium), 2, QGVAR(large), 3, QGVAR(huge), 1};
submunitionConeType[] = {"poissondisccenter", 18};
};
class GVAR(spall_huge): GVAR(huge) {
timeToLive = 0.3;
};

View File

@ -8,18 +8,13 @@ PREP(dev_trackHitBox);
PREP(dev_trackObj);
PREP(dev_sphereDraw);
PREP(doExplosions);
PREP(doFrag);
PREP(doFragRandom);
PREP(doFragTargeted);
PREP(doReflections);
PREP(doSpallLocal);
PREP(doSpallServer);
PREP(findReflections);
PREP(fired);
PREP(getMaterialInfo);
PREP(getSpallInfo);
PREP(frago);
PREP(getFragInfo);
PREP(initMaterialCache);
PREP(roundInitFrag);
PREP(getSpallInfo);
PREP(shouldFrag);
PREP(shouldSpall);

View File

@ -28,14 +28,7 @@ GVAR(dev_drawPFEH) = -1;
#endif
if (isServer) then {
[
QGVAR(explosionEvent),
{
params ["_posASL", "_velocity", "_ammo", "_shotParents"];
[_posASL, _velocity, _ammo, _shotParents] call FUNC(doFrag);
}
] call CBA_fnc_addEventHandler;
[QGVAR(frag_eh), LINKFUNC(frago)] call CBA_fnc_addEventHandler;
[QGVAR(spallEvent), LINKFUNC(doSpallServer)] call CBA_fnc_addEventHandler;
};

View File

@ -8,7 +8,7 @@ class CfgPatches {
requiredVersion = REQUIRED_VERSION;
requiredAddons[] = {"ace_common"};
author = ECSTRING(common,ACETeam);
authors[] = {"Nou"};
authors[] = {"Nou", "johnb43" "Lambda.Tiger"};
url = ECSTRING(main,URL);
VERSION_CONFIG;
};

View File

@ -1,65 +0,0 @@
#include "..\script_component.hpp"
/*
* Author: Jaynus, NouberNou, Lambda.Tiger
* This function handles creating both random and targeted fragments as well as handling some of the performance optimizations.
*
* Arguments:
* 0: Position (posASL) of projectile <ARRAY>
* 1: Velocity of projectile <ARRAY>
* 2: Projectile CfgAmmo classname <STRING>
* 3: getShotParents of projectile at EH <ARRAY>
*
* Return Value:
* None
*
* Example:
* [getPosASL _projectile, velocity _projectile, typeOf _projectile, getShotParents _projectile] call ace_frag_fnc_doFrag
*
* Public: No
*/
#define ACE_FRAG_MIN_FRAG_BUDGET_FOR_RANDOM 3
#define ACE_FRAG_NEGATIVE_AGL_OFFSET 0.1
params ["_posASL", "_projectileVelocity", "_ammo", "_shotParents"];
TRACE_4("doFrag",_posASL,_projectileVelocity,_ammo,_shotParents);
// Don't let a single object cause all fragmentation events
_shotParents params ["_shotParentVehicle"];
if (_shotParentVehicle getVariable [QGVAR(obj_nextFragTime), -1] > CBA_missionTime) exitWith {
TRACE_1("vehicleTimeExit",_shotParentVehicle);
};
// Check normal round timeout and adjust _max frags
private _timeSinceLastFrag = CBA_missionTime - GVAR(lastFragTime);
if (_timeSinceLastFrag < ACE_FRAG_HOLDOFF || {_posASL isEqualTo [0, 0, 0]} || {_ammo isEqualTo ""}) exitWith {
TRACE_3("timeExit",_timeSinceLastFrag,CBA_missionTime,GVAR(lastFragTime));
};
TRACE_3("willFrag",_timeSinceLastFrag,CBA_missionTime,_maxFragCount);
_shotParentVehicle setVariable [QGVAR(obj_nextFragTime), CBA_missionTime + ACE_FRAG_HOLDOFF_VEHICLE];
private _maxFragCount = round linearConversion [ACE_FRAG_COUNT_MIN_TIME, ACE_FRAG_COUNT_MAX_TIME, _timeSinceLastFrag, ACE_FRAG_COUNT_MIN, ACE_FRAG_COUNT_MAX, true];
_ammo call FUNC(getFragInfo) params ["_fragRange", "_fragVelocity", "_fragTypes", "_modFragCount"];
// For low frag rounds limit the # of frags created
if (_modFragCount < ACE_FRAG_LOW_FRAG_MOD_COUNT) then {
_maxFragCount = _modFragCount * ACE_FRAG_LOW_FRAG_COEFF;
GVAR(lastFragTime) = CBA_missionTime - ACE_FRAG_LOW_FRAG_HOLDOFF_REDUCTION;
} else {
GVAR(lastFragTime) = CBA_missionTime;
};
// Double check if round is below, or too close to terrain for
private _posAGL = ASLToAGL _posASL;
if (_posAGL#2 < ACE_FRAG_NEGATIVE_AGL_OFFSET) then {
TRACE_1("below 0 AGL",_posAGL);
_posAGL set [2, ACE_FRAG_NEGATIVE_AGL_OFFSET];
};
TRACE_3("doFrag choices",_maxFragCount,_fragRange,GVAR(fragSimComplexity));
if (GVAR(fragSimComplexity) != 1 && _fragRange > 3) then {
_maxFragCount = _maxFragCount - ([_posAGL, _fragVelocity, _fragRange, _maxFragCount, _fragTypes, _modFragCount, _shotParents] call FUNC(doFragTargeted));
};
if (GVAR(fragSimComplexity) > 0 && _maxFragCount >= ACE_FRAG_MIN_FRAG_BUDGET_FOR_RANDOM) then {
[_posAGL, _fragVelocity, _projectileVelocity, _fragTypes, _maxFragCount, _shotParents] call FUNC(doFragRandom);
};

View File

@ -1,62 +0,0 @@
#include "..\script_component.hpp"
/*
* Author: Jaynus, NouberNou, Lambda.Tiger
* This function creates fragments randomly spreading out from an explosion.
* This function will spawn 5, 10, or 15 fragments.
*
* Arguments:
* 0: Position (posAGL) of fragmenting projectile <ARRAY>
* 1: Initial fragment velocity from Gurney equation <NUMBER>
* 2: Velocity of the fragmenting projectile <ARRAY>
* 3: Type of fragments to generate <ARRAY>
* 4: Remaining fragment budget <NUMBER>
* 5: Shot parents <ARRAY>
*
* Return Value:
* None
*
* Example:
* [ASLtoAGL (getPosASL _projectile), 800, 50, 50, [], 1, [player, player]] call ace_frag_fnc_doFragRandom
*
* Public: No
*/
params ["_posAGL", "_fragVelocity", "_projectileVelocity", "_fragType", "_maxFragCount", "_shotParents"];
TRACE_6("doFragRandom",_posAGL,_fragVelocity,_projectileVelocity,_fragType,_maxFragCount,_shotParents);
// See CfgAmmoFragSpawner for different frag types
private _heightAGL = _posAGL#2;
private _hMode = switch (true) do {
case (_heightAGL > 10): {"_top"};
case (_heightAGL > 4): {"_high"};
default {"_mid"};
};
private _type = [QGVAR(random_small_), QGVAR(random_tiny_)] select (_fragType isNotEqualTo [] && {"ace_frag_tiny" == (_fragType#0)});
_maxFragCount = switch (true) do {
case (_maxFragCount <= 5): {"5"};
case (_maxFragCount <= 10): {"10"};
default {"15"};
};
// Spawn the fragment spawner
private _fragSpawner = createVehicle [_type + _maxFragCount + _hMode, _posAGL, [], 0, "CAN_COLLIDE"];
private _randDir = random 360;
_fragSpawner setVectorDirandUp [[0,0,-1], [cos _randDir, sin _randDir, 0]];
_fragSpawner setVelocity (_projectileVelocity vectorAdd [0, 0, -_fragVelocity]);
_fragSpawner setShotParents _shotParents;
TRACE_2("spawnedRandomFragmenter",typeOf _fragSpawner,getObjectID _fragSpawner);
#ifdef DEBUG_MODE_DRAW
_fragSpawner addEventHandler [
"SubmunitionCreated",
{
params ["","_subProj"];
[_subProj, "green", true] call FUNC(dev_trackObj);
}
];
if (GVAR(dbgSphere)) then {
[AGLtoASL _posAGL] call FUNC(dev_sphereDraw);
};
#endif

View File

@ -1,184 +0,0 @@
#include "..\script_component.hpp"
/*
* Author: Jaynus, NouberNou, Lambda.Tiger
* This function creates fragments targeted at specific entities, up to _maxFrags.
*
* Arguments:
* 0: Position of fragmenting projectile AGL <ARRAY>
* 1: Velocity of the fragmenting projectile <ARRAY>
* 2: Maximum range of fragments to calculate <NUMBER>
* 3: Maximum number of fragments to produce <NUMBER>
* 4: Types of fragments <ARRAY>
* 5: A modified parameter used to calculate whether a fragment hits <NUMBER>
* 6: Shot parent <ARRAY>
*
* Return Value:
* Number of fragments created <NUMBER>
*
* Example:
* [ASLtoAGL (getPosASL _projectile), velocity _projectile, 50, 50, [], 1, [player, player]] call ace_frag_fnc_doFragTargeted
*
* Public: No
*/
#define ACE_FRAG_DEFAULT_HEIGHT 0.5
#define ACE_FRAG_DEFAULT_CROSS_AREA 0.75
#define ACE_FRAG_MIN_TARGET_AREA 0.5
params [ "_posAGL", "_fragVelocity", "_fragRange", "_maxFrags", "_fragTypes", "_modFragCount", "_shotParents"];
TRACE_5("fnc_doFragTargeted",_posAGL,_fragRange,_maxFrags,_fragTypes,_modFragCount);
if (_fragTypes isEqualTo []) then {
_fragTypes = [
QGVAR(tiny), QGVAR(tiny), QGVAR(tiny),
QGVAR(tiny_HD), QGVAR(tiny_HD), QGVAR(tiny_HD),
QGVAR(small), QGVAR(small), QGVAR(small), QGVAR(small),
QGVAR(small_HD), QGVAR(small_HD), QGVAR(small_HD), QGVAR(small_HD),
QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD)
];
};
// Post 2.18 change - uncomment line 41, and remove lines 43, 50-55, 64-66
// private _targets = [_posAGL, _fragRange, _fragRange, 0, false, _fragRange] nearEntities [["Car", "Motorcycle", "Tank", "StaticWeapon", "CAManBase", "Air", "Ship"], false, true, true];
private _objects = _posAGL nearEntities [["Car", "Motorcycle", "Tank", "StaticWeapon", "CAManBase", "Air", "Ship"], _fragRange];
if (_objects isEqualTo []) exitWith {
TRACE_2("No nearby targets",_posAGL,_fragRange);
0
};
// grab crews and add them in so that targets stay approx. sorted by distance
private _targets = [];
{
private _crew = crew _x;
_crew pushBackUnique _x;
_targets append _crew;
} forEach _objects;
TRACE_3("Targets found",_posAGL,_fragRange,count _targets);
// limit number of fragments per direction (2D) to _fragsPerFragArc using _fragArcs
private _fragArcs = createHashMap;
private _fragsPerFragArc = _modFragCount * ACE_FRAG_FRAGS_PER_ARC_CONSTANT;
private _totalFragCount = 0;
{ // Begin of forEach iterating on _targets
// Ignore dead units, curators and spectators
if (!alive _x || {getNumber ((configOf _x) >> "isPlayableLogic") == 1}) then {
TRACE_1("dead or logic",_x);
continue;
};
private _target = _x;
#ifdef DEBUG_MODE_DRAW
[_target, false] call FUNC(dev_trackHitBox);
#endif
// Estimate volume and height of target
private _height = ACE_FRAG_DEFAULT_HEIGHT;
private _crossSectionArea = ACE_FRAG_DEFAULT_CROSS_AREA;
private _isPerson = _target isKindOf "CAManBase";
if (_isPerson) then {
switch (stance _target) do {
case "STAND": {
_height = 1.9;
_crossSectionArea = 1.5;
};
case "CROUCH": {
_height = 1.2;
_crossSectionArea = 1;
};
};
} else {
private _boxParams = boundingBoxReal [_target, "FireGeometry"];
_boxParams params ["_pointA", "_pointB"];
private _dims = _pointB vectorDiff _pointA;
if (_dims#0 * _dims#1 * _dims#2 <= ACE_FRAG_MIN_TARGET_AREA) then {
continue;
};
_crossSectionArea = _dims#1 * _dims#2;
_height = _dims#2;
};
private _distance = _target distance _posAGL;
// calculate chance to be hit by a fragment
private _fragChance = _crossSectionArea * _modFragCount / _distance^2;
private _fragCount = if (_fragChance > 1) then {
3 min (floor _fragChance);
} else {
parseNumber (GVAR(atLeastOne) || {random 1 < _fragChance});
};
if (_fragCount == 0) then {
TRACE_2("no fragments",_fragChance,_fragCount);
continue;
};
// handle limiting fragments per degree arc
private _dir = floor (_posAGL getDir _target);
private _fragPerArc = _fragArcs getOrDefault [_dir, 0];
if (_fragPerArc > _fragsPerFragArc) then {
continue;
} else {
_fragArcs set [_dir, _fragPerArc + _fragCount];
};
// Approximate offset to hit including speed & gravity
private _locFragVel = _fragVelocity * (1 - random 0.5);
private _timeOfFlight = _distance / _locFragVel;
// target pos for fragment to hit
private _targetPos = (velocity _target vectorMultiply _timeOfFlight) vectorAdd [0, 0, ACE_FRAG_HALF_GRAVITY_APPROX * _timeOfFlight ^ 2];
_targetPos = if (_isPerson) then {
private _hitPoint = selectRandom ACE_FRAG_HITPOINTS;
private _hitPointPos = _target selectionPosition [_hitPoint, "HitPoints", "AveragePoint"];
_target modelToWorld _hitPointPos vectorAdd _targetPos;
} else {
ASLtoAGL (_targetPos vectorAdd getPosASL _target vectorAdd [
-0.5 + random 1,
-0.5 + random 1,
(0.1 + random 0.4) * _height
]);
};
// select a fragment / submunition frag spawner
private _fragSpawner = selectRandom _fragTypes;
if (_fragCount > 1) then {
_fragSpawner = _fragSpawner + "_spawner_" + str _fragCount + (switch (true) do {
case (_distance < 10): {"_short"};
case (_distance < 20): {"_mid"};
default {"_far"};
});
};
TRACE_4("fragments",_fragSpawner,_fragChance,_distance,_locFragVel);
// Create fragment
private _vectorDir = _posAGL vectorFromTo _targetPos;
private _fragObj = createVehicle [_fragSpawner, _posAGL, [], 0, "CAN_COLLIDE"];
_fragObj setVectorDirAndUp [_vectorDir, _vectorDir vectorAdd [0, -1, 0]] ;
_fragObj setVelocity (_vectorDir vectorMultiply _locFragVel);
_fragObj setShotParents _shotParents;
#ifdef DEBUG_MODE_DRAW
[_fragObj, "purple", true] call FUNC(dev_trackObj);
_fragObj addEventHandler [
"SubmunitionCreated",
{
params ["","_subProj"];
[_subProj, "purple", true] call FUNC(dev_trackObj);
}
];
if (GVAR(dbgSphere)) then {
[AGLtoASL _targetPos, "orange"] call FUNC(dev_sphereDraw);
};
#endif
_totalFragCount = _totalFragCount + _fragCount;
if (_totalFragCount >= _maxFrags) then {
TRACE_2("maxFrags",_totalFragCount,_maxFrags);
break;
};
} forEach _targets;
#ifdef DEBUG_MODE_FULL
systemChat ("targeted frag count: " + str _totalFragCount);
TRACE_1("targeted frag count",_totalFragCount);
#endif
_totalFragCount

View File

@ -1,134 +1,79 @@
#include "..\script_component.hpp"
/*
* Author: Jaynus, NouberNou, Lambda.Tiger
* This function runs on every client and determines if a spall event happened.
* It is intended to be called a frame after the projectile has triggered a hit event,
* where information such as the object hit, the projectiles velocity and position on impact
* should be kept and passed to this function.If a spall event should happen, this function
* calculates the spalling parameters and then calls a server event to create the desired spall effect.
* Author: Jaynus, NouberNou, Lambda.Tiger,
* This function check whether a spall event has occured and generates a spall event
* request for the server if one is needed.
*
* Arguments:
* 0: The projectile that may be creating spall <OBJECT>
* 1: The object the projectile hit <OBJECT>
* 2: The 3D position (ASL) of the projectile when it hit the object <ARRAY>
* 3: The velocity of the projectile when it hit the object <ARRAY>
* 4: The normal of the surface of the object hit <ARRAY>
* 5: The class name of the surface, or the bisurf path of the surface hit <STRING>
* 6: The class name of the projectile that may be spalling <STRING>
* 7: The spalling projectile's shot parents <ARRAY>
* 8: The "up" vector of the projectile when it hit the object <ARRAY>
* 0: The object a projectile hit <OBJECT>
* 1: The config name of the projectile <STRING>
* 2: The projectile that should cause spalling <OBJECT>
* 3: The position the projectile hit the object <ARRAY>
* 4: The old velocity of the projectile <ARRAY>
*
* Return Value:
* None
*
* Example:
* [_projectile, _hitObject, _lastPosASLProjectile, _lastVelocityProjectile, _surfaceNorm, "a3\data_f\penetration\armour_plate.bisurf", "Sh_125mm_APFSDS", [0, 0, 1]] call ace_frag_fnc_doSpallLocal
* [[1000, 45, 60], 0.8, getPosASL ace_player] call ace_frag_fnc_doSpallServer
*
* Public: No
*/
params ["_objectHit", "_roundType", "_round", "_oldPos", "_oldVelocity",];
#define GLUE(g1,g2) g1##g2
TRACE_1("",_objectHit);
if ((isNil "_objectHit") || {isNull _objectHit}) exitWith {WARNING_1("Problem with hitPart data - bad object [%1]",_objectHit);};
if (CBA_missionTime < GVAR(nextSpallAllowTime)) exitWith {
TRACE_2("time exit",CBA_missionTime,GVAR(nextSpallAllowTime));
};
private _caliber = getNumber (configFile >> "CfgAmmo" >> _roundType >> "caliber");
private _explosive = getNumber (configFile >> "CfgAmmo" >> _roundType >> "explosive");
private _idh = getNumber (configFile >> "CfgAmmo" >> _roundType >> "indirectHitRange");
params ["_projectile", "_objectHit", "_lastPosASL", "_lastVelocity", "_surfaceNorm", "_surfaceType", "_ammo", "_shotParents", "_vectorUp"];
TRACE_9("doSpall",_projectile,_objectHit,_lastPosASL,_lastVelocity,_surfaceNorm,_surfaceType,_ammo,_shotParents,_vectorUp);
_roundType call FUNC(getSpallInfo) params ["_caliber", "_explosive"];
if (_ammo == "" || {_objectHit isKindOf "CAManBase"}) exitWith {
TRACE_4("invalid round or hit",CBA_missionTime,GVAR(nextSpallAllowTime),_objectHit,_lastPosASL);
};
// ACE_player sideChat format ["BBBB"];
private _exit = false;
private _vm = 1;
private _material = _surfaceType call FUNC(getMaterialInfo);
if (_material == "ground") exitWith {
TRACE_1("ground",_surfaceType);
};
private _curVelocity = velocity _round;
private _oldSpeed = vectorMagnitude _oldVelocity;
private _curSpeed = vectorMagnitude _curVelocity;
// Find spall speed / fragment info
_ammo call FUNC(getSpallInfo) params ["_caliber", "_explosive", "_indirectHit"];
private _vel = if (alive _projectile) then {
_explosive = 0; // didn't explode since it's alive a frame later
velocity _projectile
} else {
[0, 0, 0]
};
private _speedChange = 0 max (vectorMagnitude _lastVelocity - vectorMagnitude _vel);
/*
* This is all fudge factor since real spalling is too complex for calculation.
* There are two terms. The first is from round impact, taking a quasi scale
* of sqrt(2)/50 * round caliber * srqt(change in speed). The second term is
* explosive * indirect hit, for any explosive contribution
*/
private _spallPower = (ACE_FRAG_SPALL_CALIBER_COEF * _caliber * sqrt _speedChange + _explosive * _indirectHit) * GVAR(spallIntensity);
TRACE_3("found speed",_speedChange,_caliber,_spallPower);
if (_spallPower < ACE_FRAG_SPALL_POWER_MIN) exitWith {
TRACE_1("lowImpulse",_ammo);
};
private _lastVelocityNorm = vectorNormalized _lastVelocity;
private _deltaStep = _lastVelocityNorm vectorMultiply 0.05;
if (terrainIntersectASL [_lastPosASL vectorAdd _deltaStep, _lastPosASL]) exitWith {
TRACE_2("terrainIntersect",_lastPosASL,_deltaStep);
};
#ifdef DEBUG_MODE_DRAW
if GVAR(dbgSphere) then {
[_lastPosASL vectorAdd _lastVelocityNorm, "orange"] call FUNC(dev_sphereDraw);
[_lastPosASL, "yellow"] call FUNC(dev_sphereDraw);
};
#endif
/*
* Improve performance of finding otherside of object on shallow angle
* impacts. 120 degrees due to 90 degree offset with _lastVelocityNorm into object.
*/
private _spallPosASL = _lastPosASL vectorAdd _deltaStep;
if (120 > acos (_lastVelocityNorm vectorDotProduct _surfaceNorm)) then {
_spallPosASL = _spallPosASL vectorAdd (_deltaStep vectorMultiply 5);
};
private _insideObject = true;
for "_i" from 2 to 21 do
{
private _nextPos = _spallPosASL vectorAdd _deltaStep;
if (!lineIntersects [_spallPosASL, _nextPos]) then {
_spallPosASL = _nextPos vectorAdd (_deltaStep vectorMultiply 2);
_insideObject = false;
break
if (alive _round) then {
private _diff = _oldVelocity vectorDiff _curVelocity;
private _polar = _diff call CBA_fnc_vect2polar;
// ACE_player sideChat format ["polar: %1", _polar];
if (abs (_polar select 1) > 45 || {abs (_polar select 2) > 45}) then {
if (_caliber < 2.5) then {
// ACE_player sideChat format ["exit!"];
_exit = true;
} else {
SUB(_vm,_curSpeed / _oldSpeed);
};
};
_spallPosASL = _nextPos;
};
if (_exit) exitWith {};
private _unitDir = vectorNormalized _oldVelocity;
private _spallPos = [];
if ((isNil "_oldPos") || {!(_oldPos isEqualTypeArray [0,0,0])}) exitWith {WARNING_1("Problem with hitPart data - bad pos [%1]",_oldPos);};
for "_i" from 0 to 100 do {
private _pos1 = _oldPos vectorAdd (_unitDir vectorMultiply (0.01 * _i));
private _pos2 = _oldPos vectorAdd (_unitDir vectorMultiply (0.01 * (_i + 1)));
// _data = [nil, nil, nil, 1, [[ASLtoATL _pos1, 1], [ASLtoATL _pos2, 1]]];
// NOU_TRACES pushBack _data;
if (!lineIntersects [_pos1, _pos2]) exitWith {
// ACE_player sideChat format ["FOUND!"];
_spallPos = _pos2;
};
};
if (_spallPos isEqualTo []) exitWith {};
private _spallPolar = _oldVelocity call CBA_fnc_vect2polar;
if (_explosive > 0) then {
_shellType call FUNC(getFragInfo) params ["", "_fragVelocity"];
_spallPolar set [0, _fragVelocity * 0.66];
};
if (_insideObject) exitWith {
TRACE_3("insideObj",_lastPosASL,_spallPosASL,alive _projectile);
};
// Passed all exitWiths
GVAR(nextSpallAllowTime) = CBA_missionTime + ACE_FRAG_SPALL_HOLDOFF;
#ifdef DEBUG_MODE_DRAW
if GVAR(dbgSphere) then {
[_spallPosASL, "green"] call FUNC(dev_sphereDraw);
};
#endif
private _spawnSize = switch (true) do
{
case (_spallPower < ACE_FRAG_SPALL_POWER_TINY_MAX): {"_spall_tiny"};
case (_spallPower < ACE_FRAG_SPALL_POWER_SMALL_MAX): {"_spall_small"};
case (_spallPower < ACE_FRAG_SPALL_POWER_MEDIUM_MAX): {"_spall_medium"};
case (_spallPower < ACE_FRAG_SPALL_POWER_LARGE_MAX): {"_spall_large"};
default {"_spall_huge"};
};
#ifdef DEBUG_MODE_FULL
systemChat ("spd: " + str (_speedChange * ACE_FRAG_SPALL_VELOCITY_INHERIT_COEFF) + ", spallPow: " + str _spallPower);
#endif
TRACE_5("Calling event:",QUOTE(GLUE(ADDON,_)) + _material + _spawnSize,_lastVelocityNorm,_vectorUp,_speedChange,_shotParents);
[
QGVAR(spallEvent),
[QUOTE(GLUE(ADDON,_)) + _material + _spawnSize, _lastVelocityNorm, _vectorUp, _speedChange, _shotParents]
] call CBA_fnc_serverEvent;
[QGVAR(spallEvent), [_spallVelocity, _vm, _spallPos]] call CBA_fnc_serverEvent;

View File

@ -1,45 +1,66 @@
#include "..\script_component.hpp"
/*
* Author: Jaynus, NouberNou, Lambda.Tiger,
* This function creates the requested spalling submunition spawner at a speed and direction.
* The function is intended to create the spalling on a server via an event call by
* ace_frag_fnc_doSpallLocal. The "local" version determines whether an event has occured
* and triggers this event if it has.
* This function creates the requested spall on the server given a location, direction, and velocity.
*
* Arguments:
* 0: Class name of the spall spawner <STRING>
* 1: Normalized 3D vector direction of the spall spawner <ARRAY>
* 2: "Up" vector for the projectile, required for the spawner to aim out of the 2D plane <ARRAY>
* 3: The change in velocity that spalling projectile experienced <NUMBER>
* 4: Shot parents array for the projectile that creates spall <ARRAY>
* 0: A spherical coordinate vector of spall velocity <ARRAY>
* 1: You're going to have to ask Nouber what this means <NUMBER>
* 2: The position (ASL) of the spalling point <ARRAY>
*
* Return Value:
* None
*
* Example:
* [QGVAR(rock_spall_tiny), [1,0,0], [0,0,1], 300, [objNull, ace_player]] call ace_frag_fnc_doSpallServer
* [[1000, 45, 60], 0.8, getPosASL ace_player] call ace_frag_fnc_doSpallServer
*
* Public: No
*/
params ["_spallSpawnerName", "_lastVelocityNorm", "_vectorUp", "_speedChange", "_shotParents"];
#define WEIGHTED_SIZE [QGVAR(spall_small), 4, QGVAR(spall_medium), 3, QGVAR(spall_large), 2, QGVAR(spall_huge), 1]
params ["_spallVelocity", "_vm", "_spallPos"];
private _spallSpawner = createVehicle [
_spallSpawnerName,
ASLToATL _spallPosASL,
[],
0,
"CAN_COLLIDE"
];
_spallSpawner setVectorDirandUp [_lastVelocityNorm, _vectorUp];
_spallSpawner setVelocityModelSpace [0, _speedChange * ACE_FRAG_SPALL_VELOCITY_INHERIT_COEFF, 0];
_spallSpawner setShotParents _shotParents;
// diag_log text format ["SPALL POWER: %1", _spallVelocity select 0];
private _spread = 15 + (random 25);
private _spallCount = 5 + (random 10);
TRACE_1("",_spallCount);
for "_i" from 1 to _spallCount do {
private _elev = ((_spallVelocity select 2) - _spread) + (random (_spread * 2));
private _dir = ((_spallVelocity select 1) - _spread) + (random (_spread * 2));
if (abs _elev > 90) then {
ADD(_dir,180);
};
_dir = _dir % 360;
private _vel = (_spallVelocity select 0) * 0.33 * _vm;
_vel = (_vel - (_vel * 0.25)) + (random (_vel * 0.5));
#ifdef DEBUG_MODE_DRAW
_spallSpawner addEventHandler [
"SubmunitionCreated",
{
params ["", "_submunitionProjectile"];
_submunitionProjectile call FUNC(dev_addRound);
}
];
#endif
private _spallFragVect = [_vel, _dir, _elev] call CBA_fnc_polar2vect;
private _fragment = (selectRandomWeighted WEIGHTED_SIZE) createVehicle [0,0,10000];
_fragment setPosASL _spallPos;
_fragment setVelocity _spallFragVect;
#ifdef DRAW_FRAG_INFO
[_fragment, "orange", true] call FUNC(dev_trackObj);
#endif
};
_spread = 5 + (random 5);
_spallCount = 3 + (random 5);
for "_i" from 1 to _spallCount do {
private _elev = ((_spallVelocity select 2) - _spread) + (random (_spread * 2));
private _dir = ((_spallVelocity select 1) - _spread) + (random (_spread * 2));
if (abs _elev > 90) then {
ADD(_dir,180);
};
_dir = _dir % 360;
private _vel = (_spallVelocity select 0) * 0.55 * _vm;
_vel = (_vel - (_vel * 0.25)) + (random (_vel * 0.5));
private _spallFragVect = [_vel, _dir, _elev] call CBA_fnc_polar2vect;
private _fragment = (selectRandomWeighted WEIGHTED_SIZE) createVehicle [0, 0, 10000];
_fragment setPosASL _spallPos;
_fragment setVelocity _spallFragVect;
#ifdef DRAW_FRAG_INFO
[_fragment, "orange", true] call FUNC(dev_trackObj);
#endif
};

View File

@ -23,13 +23,6 @@ if (_ammo isEqualTo "" || {isNull _projectile} ||
TRACE_2("bad ammo or projectile, or blackList",_ammo,_projectile);
};
#ifdef DEBUG_MODE_DRAW
if (GVAR(debugOptions) && {true in (_ammo call FUNC(shouldFrag)) || {_ammo call FUNC(shouldSpall)}}) then {
[_projectile, "red", true] call FUNC(dev_trackObj);
};
#endif
if (GVAR(spallEnabled) && {_ammo call FUNC(shouldSpall)}) then {
_projectile addEventHandler [
"HitPart",
@ -45,6 +38,11 @@ if (GVAR(spallEnabled) && {_ammo call FUNC(shouldSpall)}) then {
private _shotParent = getShotParents _projectile;
private _ammo = typeOf _projectile;
private _vectorUp = vectorUp _projectile;
// only let a unit make a frag event once per second
private _instigator = _shotParents#1;
if (CBA_missionTime < (_instigator getVariable [QGVAR(nextSpallEvent), -1])) exitWith {};
_instigator setVariable [QGVAR(nextSpallEvent), CBA_missionTime + 1];
/*
* Wait a frame to see what happens to the round, may result in
* multiple hits / slowdowns getting shunted to the first hit
@ -56,10 +54,40 @@ if (GVAR(spallEnabled) && {_ammo call FUNC(shouldSpall)}) then {
}
];
};
if !(GVAR(reflectionsEnabled) || GVAR(enabled)) exitWith {
TRACE_1("firedExit No frag/reflections",_ammo);
if (GVAR(reflectionsEnabled) || GVAR(enabled) && _ammo call FUNC(shouldFrag)) then {
_projectile addEventHandler [
"Explode",
{
params ["_projectile", "_posASL", "_velocity"];
if (GVAR(reflectionsEnabled)) then {
[_posASL, _ammo] call FUNC(doReflections);
};
if (_projectile getVariable [QGVAR(blacklisted), false]) exitWith {
TRACE_2("projectile blackisted",typeOf _projectile,_projectile);
};
private _shotParents = getShotParents _projectile;
private _ammo = typeOf _projectile;
// only let a unit make a frag event once per second
private _instigator = _shotParents#1;
if (CBA_missionTime < (_instigator getVariable [QGVAR(nextFragEvent), -1])) exitWith {};
_instigator setVariable [QGVAR(nextFragEvent), CBA_missionTime + 1];
// Wait a frame to make sure it doesn't target the dead
[
{ [QGVAR(frag_eh), _this] call CBA_fnc_serverEvent; },
[_posASL, _velocity, _ammo, _shotParents]
] call CBA_fnc_execNextFrame;
}
];
};
[_ammo, _projectile] call FUNC(roundInitFrag);
#ifdef DEBUG_MODE_DRAW
if (GVAR(debugOptions) && {_ammo call FUNC(shouldFrag) || {_ammo call FUNC(shouldSpall)}}) then {
[_projectile, "red", true] call FUNC(dev_trackObj);
};
#endif
TRACE_1("firedExit",_ammo);

View File

@ -0,0 +1,156 @@
#include "..\script_component.hpp"
/*
* Author: Jaynus, NouberNou
* Server func to create the fragmentation for a round.
*
* Arguments:
* 0: Last Position (ASL) <ARRAY>
* 1: Velocity <ARRAY>
* 2: Ammo Classname <STRING>
* 3: Shot parents <ARRAY>
*
* Return Value:
* None
*
* Example:
* [[], [], "handGrenade"] call ace_frag_fnc_frago
*
* Public: No
*/
#define FRAG_VEC_VAR 0.004
#define MAX_FRAG_COUNT 50
BEGIN_COUNTER(frago);
params ["_lastPos", "_lastVel", "_shellType", "_shotParents"];
TRACE_4("frago",_lastPos,_lastVel,_shellType,_shotParents);
// Limit max frag count if there was a recent frag
private _maxFrags = round (MAX_FRAG_COUNT * linearConversion [0.1, 1.5, (CBA_missionTime - GVAR(lastFragTime)), 0.1, 1, true]);
TRACE_2("",_maxFrags,CBA_missionTime - GVAR(lastFragTime));
GVAR(lastFragTime) = CBA_missionTime;
_shellType call ace_frag_fnc_getFragInfo params ["_fragRange", "_fragVelocity", "_fragTypes"];
private _atlPos = ASLtoATL _lastPos;
private _fragVelocityRandom = _fragVelocity * 0.5;
if ((_atlPos select 2) < 0.5) then {
_lastPos vectorAdd [0, 0, 0.5];
};
private _objects = _atlPos nearEntities [["Car", "Motorcycle", "Tank", "StaticWeapon", "CAManBase", "Air", "Ship"], _fragRange];
// Add unique crews in faster way
{
{
_objects pushBackUnique _x;
} forEach (crew _x);
} forEach _objects;
TRACE_2("",_fragRange,count _objects);
private _fragCount = 0;
private _fragArcs = [];
_fragArcs set [360, 0];
private _doRandom = true;
if (_objects isNotEqualTo []) then {
if (GVAR(reflectionsEnabled)) then {
[_lastPos, _shellType] call FUNC(doReflections);
};
{
private _target = _x;
if (alive _target) then {
(boundingBox _target) params ["_boundingBoxA", "_boundingBoxB"];
private _cubic = ((abs (_boundingBoxA select 0)) + (_boundingBoxB select 0)) * ((abs (_boundingBoxA select 1)) + (_boundingBoxB select 1)) * ((abs (_boundingBoxA select 2)) + (_boundingBoxB select 2));
if (_cubic <= 1) exitWith {};
// _doRandom = true;
private _targetVel = velocity _target;
private _targetPos = getPosASL _target;
private _distance = _targetPos vectorDistance _lastPos;
private _add = ((_boundingBoxB select 2) / 2) + ((((_distance - (_fragVelocity / 8)) max 0) / _fragVelocity) * 10);
_targetPos = _targetPos vectorAdd [
(_targetVel select 0) * (_distance / _fragVelocity),
(_targetVel select 1) * (_distance / _fragVelocity),
_add
];
private _baseVec = _lastPos vectorFromTo _targetPos;
private _dir = floor (_baseVec call CBA_fnc_vectDir);
private _currentCount = RETDEF(_fragArcs select _dir,0);
if (_currentCount < 10) then {
private _count = ceil (random (sqrt (_m / 1000)));
private _vecVar = FRAG_VEC_VAR;
if !(_target isKindOf "Man") then {
ADD(_vecVar,(sqrt _cubic) / 2000);
if ((crew _target) isEqualTo [] && {_count > 0}) then {
_count = 0 max (_count / 2);
};
};
for "_i" from 1 to _count do {
private _vec = _baseVec vectorDiff [
(_vecVar / 2) + (random _vecVar),
(_vecVar / 2) + (random _vecVar),
(_vecVar / 2) + (random _vecVar)
];
private _fp = _fragVelocity - (random (_fragVelocityRandom));
private _vel = _vec vectorMultiply _fp;
private _fragObj = (selectRandom _fragTypes) createVehicleLocal [0,0,10000];
// TRACE_4("targeted",_fp,typeOf _fragObj,_lastPos vectorDistance _targetPos,typeOf _x);
_fragObj setPosASL _lastPos;
_fragObj setVectorDir _vec;
_fragObj setVelocity _vel;
_fragObj setShotParents _shotParents;
#ifdef DRAW_FRAG_INFO
[ACE_player, _fragObj, [1,0,0,1]] call FUNC(dev_addTrack);
#endif
INC(_fragCount);
INC(_currentCount);
};
_fragArcs set [_dir, _currentCount];
};
};
if (_fragCount > _maxFrags) exitWith {};
} forEach _objects;
TRACE_1("targeted",_fragCount);
if (_fragCount > _maxFrags) exitWith {};
private _randomCount = ceil ((_maxFrags - _fragCount) * 0.35);
TRACE_1("",_randomCount);
private _sectorSize = 360 / (_randomCount max 1);
if (_doRandom) then {
for "_i" from 1 to _randomCount do {
// Distribute evenly
private _sectorOffset = 360 * (_i - 1) / (_randomCount max 1);
private _randomDir = random (_sectorSize);
_vec = [cos (_sectorOffset + _randomDir), sin (_sectorOffset + _randomDir), sin (30 - (random 45))];
_fp = (_fragVelocity - (random (_fragVelocityRandom)));
_vel = _vec vectorMultiply _fp;
_fragObj = (selectRandom _fragTypes) createVehicleLocal [0, 0, 10000];
_fragObj setPosASL _lastPos;
_fragObj setVectorDir _vec;
_fragObj setVelocity _vel;
_fragObj setShotParents _shotParents;
#ifdef DRAW_FRAG_INFO
[ACE_player, _fragObj, [1,0.5,0,1]] call FUNC(dev_addTrack);
#endif
INC(_fragCount);
};
};
};
TRACE_1("total created",_fragCount);
END_COUNTER(frago);

View File

@ -11,7 +11,6 @@
* 0: Search range for fragments in meters <NUMBER>
* 1: Gurney equation calculated speed <NUMBER>
* 2: Array of fragment types <ARRAY>
* 3: Modified frag count under assumptions of spherical fragmentation <NUMBER>
*
* Example:
* "B_556x45_Ball" call ace_frag_fnc_getFragInfo
@ -26,7 +25,13 @@ private _ammoInfo = GVAR(fragInfoCache) get _ammo;
if (!isNil "_ammoInfo") exitWith {_ammoInfo};
private _ammoConfig = configFile >> "CfgAmmo" >> _ammo;
private _fragTypes = [];
private _fragTypes = [
QGVAR(tiny), QGVAR(tiny), QGVAR(tiny),
QGVAR(tiny_HD), QGVAR(tiny_HD), QGVAR(tiny_HD),
QGVAR(small), QGVAR(small), QGVAR(small), QGVAR(small),
QGVAR(small_HD), QGVAR(small_HD), QGVAR(small_HD), QGVAR(small_HD),
QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD)
];
private _notifyMissingEntries = false;
if (isArray (_ammoConfig >> QGVAR(classes))) then {
_fragTypes = getArray (_ammoConfig >> QGVAR(classes));
@ -78,29 +83,21 @@ if (_gurneyConstant == 0) then {
_notifyMissingEntries = true;
};
private _fragCount = getNumber (_ammoConfig >> QGVAR(fragCount));
if (_fragCount == 0) then {
private _indirectHitRange = getNumber (_ammoConfig >> "indirectHitRange");
_fragCount = 4 * pi * ACE_FRAG_MIN_FRAG_HIT_CHANCE * (20 *_indirectHitRange)^2;
_fragCount = _fragCount max 250;
_notifyMissingEntries = true;
};
if (_notifyMissingEntries) then {
INFO_1("Ammo class %1 lacks proper explosive properties definitions for frag!",_ammo);
};
private _indirectHitRange = getNumber(configFile >> "CfgAmmo" >> _shellType >> "indirecthitrange");
/********************** _ammoInfo format *************************//*
* 0: _fragRange - search range for fragments, calculated with the minimum chance to hit as defined
* 1: _fragVelocity - gurney equation calculated velocity
* 2: _fragTypes - array of fragment types
* 3: _fragCount - modified frag count used under assumptions of spherical fragmentation
*/
_ammoInfo = [
ACE_FRAG_MAX_FRAG_RANGE min sqrt (_fragCount / (4 * pi * ACE_FRAG_MIN_FRAG_HIT_CHANCE)),
80 * _indirectHitRange,
ACE_FRAG_IMPERIC_VELOCITY_CONSTANT * _gurneyConstant * sqrt (_chargeMass / (_metalMass + _chargeMass * _geometryCoefficient)),
_fragTypes,
_fragCount / 4 / pi
_fragTypes
];
GVAR(fragInfoCache) set [_ammo, _ammoInfo];

View File

@ -1,62 +0,0 @@
#include "..\script_component.hpp"
/*
* Author: Lambda.Tiger
* This function returns a classification of material type based on the surface hit.
*
* Arguments:
* 0: Surface type given as either a CfgSurfaces path or .bisurf filepath, same format as "HitPart" projectile parameter <STRING>
*
* Return Value:
* Material categories as expanded on in line 44 below <STRING>
*
* Example:
* "a3\data_f\penetration\concrete.bisurf" call ace_frag_fnc_getMaterialInfo
*
* Public: No
*/
#define ACE_FRAG_SOUNDENVIRON_STR_LEN 12
#define ACE_FRAG_SOUNDHIT_STR_LEN 8
#define ACE_FRAG_MATERIAL_SEARCH_LEN 10
params ["_surfType"];
private _material = GVAR(spallMaterialCache) get _surfType;
TRACE_2("materialCache",_surfType,_material);
if (!isNil "_material") exitWith {_material};
// Use 'soundEnviron' or 'soundHit' to extract approx material
private _surfaceConfig = configFile >> "CfgSurfaces" >> _surfType;
if (isClass _surfaceConfig) then {
_material = getText (_surfaceConfig >> "soundEnviron");
if (_material == "" || {_material == "empty"}) then {
_material = getText (_surfaceConfig >> "soundhit");
};
} else { // Messy way when a surface isn't added to CfgSurfaces
private _surfFileText = toLowerANSI preprocessFile _surfType;
_surfFileText = _surfFileText regexReplace ["[^a-z0-9]", ""];
private _idx = ACE_FRAG_SOUNDENVIRON_STR_LEN + (_surfFileText find "soundenviron");
if (_surfFileText select [_idx, 5] isEqualTo "empty") then {
_idx = ACE_FRAG_SOUNDHIT_STR_LEN + (_surfFileText find "soundhit");
};
_material = _surfFileText select [_idx, ACE_FRAG_MATERIAL_SEARCH_LEN];
};
TRACE_1("materialSubString",_material);
_material = switch (true) do {
case ("dirt" in _material);
case ("grass" in _material): { "ground" };
case ("gravel" in _material);
case ("rock" in _material): { "rock" };
case ("wood" in _material): { "wood" };
case ("lino" in _material);
case ("building" in _material);
case ("concrete" in _material): { "concrete" };
case ("metal" in _material): { "metal" };
default { "ground" };
};
GVAR(spallMaterialCache) set [_surfType, _material];
TRACE_2("materialCacheSet",_surfType,_material);
_material

View File

@ -23,7 +23,7 @@ params ["_ammo"];
GVAR(spallInfoCache) getOrDefaultCall [_ammo, {
private _ammoConfig = configFile >> "CfgAmmo" >> _ammo;
private _caliber = getNumber (_ammoConfig >> "caliber");
private _explosive = 1 min getNumber (_ammoConfig >> "explosive");
private _explosive = getNumber (_ammoConfig >> "explosive");
private _indirectHit = getNumber (_ammoConfig >> "indirectHitRange");
[_caliber, _explosive, _indirectHit]

View File

@ -1,96 +0,0 @@
#include "..\script_component.hpp"
/*
* Author: Lambda.Tiger
* For performance, we load a bunch of vanilla materials preemptively into the spall material cache.
*
* Arguments:
* None
*
* Return Value:
* None
*
* Example:
* call ace_frag_fnc_initMaterialCache
*
* Public: No
*/
GVAR(spallMaterialCache) = createHashMapFromArray [
["a3\data_f\penetration\armour.bisurf","metal"],
["a3\data_f\penetration\armour_plate.bisurf","metal"],
["a3\data_f\penetration\armour_plate_100mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_12mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_16mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_1mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_20mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_23mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_250mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_30mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_3mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_40mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_5mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_60mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_7mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_80mm.bisurf","metal"],
["a3\data_f\penetration\armour_plate_heavy.bisurf","metal"],
["a3\data_f\penetration\armour_plate_medium.bisurf","metal"],
["a3\data_f\penetration\armour_plate_thin.bisurf","metal"],
["a3\data_f\penetration\bell.bisurf","ground"],
["a3\data_f\penetration\body.bisurf","ground"],
["a3\data_f\penetration\building.bisurf","concrete"],
["a3\data_f\penetration\building_dust_particle.bisurf","concrete"],
["a3\data_f\penetration\building_dust_soft.bisurf","concrete"],
["a3\data_f\penetration\building_plate.bisurf","concrete"],
["a3\data_f\penetration\building_wood_particle.bisurf","wood"],
["a3\data_f\penetration\cactus.bisurf","ground"],
["a3\data_f\penetration\cloth.bisurf","ground"],
["a3\data_f\penetration\cloth_plate.bisurf","ground"],
["a3\data_f\penetration\concrete.bisurf","concrete"],
["a3\data_f\penetration\concrete_plate.bisurf","concrete"],
["a3\data_f\penetration\default.bisurf","ground"],
["a3\data_f\penetration\engine.bisurf","metal"],
["a3\data_f\penetration\foliage.bisurf","ground"],
["a3\data_f\penetration\foliage_dead.bisurf","ground"],
["a3\data_f\penetration\foliage_dead_plate.bisurf","ground"],
["a3\data_f\penetration\foliage_green.bisurf","ground"],
["a3\data_f\penetration\foliage_green_big.bisurf","ground"],
["a3\data_f\penetration\foliage_green_big_plate.bisurf","ground"],
["a3\data_f\penetration\foliage_green_plate.bisurf","ground"],
["a3\data_f\penetration\foliage_palm.bisurf","ground"],
["a3\data_f\penetration\foliage_palm_plate.bisurf","ground"],
["a3\data_f\penetration\foliage_pine.bisurf","ground"],
["a3\data_f\penetration\foliage_pine_plate.bisurf","ground"],
["a3\data_f\penetration\foliage_plate.bisurf","ground"],
["a3\data_f\penetration\fueltank.bisurf","metal"],
["a3\data_f\penetration\glass.bisurf","ground"],
["a3\data_f\penetration\glass_armored.bisurf","ground"],
["a3\data_f\penetration\glass_armored_plate.bisurf","ground"],
["a3\data_f\penetration\glass_plate.bisurf","ground"],
["a3\data_f\penetration\granite.bisurf","ground"],
["a3\data_f\penetration\granite_plate.bisurf","ground"],
["a3\data_f\penetration\hard_ground.bisurf","ground"],
["a3\data_f\penetration\hay.bisurf","ground"],
["a3\data_f\penetration\iron_cast.bisurf","metal"],
["a3\data_f\penetration\iron_cast_plate.bisurf","metal"],
["a3\data_f\penetration\leather.bisurf","ground"],
["a3\data_f\penetration\meat.bisurf","ground"],
["a3\data_f\penetration\meatbones.bisurf","ground"],
["a3\data_f\penetration\medium_ground.bisurf","ground"],
["a3\data_f\penetration\metal.bisurf","metal"],
["a3\data_f\penetration\metal_plate.bisurf","metal"],
["a3\data_f\penetration\metal_plate_thin.bisurf","metal"],
["a3\data_f\penetration\plastic.bisurf","ground"],
["a3\data_f\penetration\plastic_plate.bisurf","ground"],
["a3\data_f\penetration\plexiglass.bisurf","ground"],
["a3\data_f\penetration\plexiglass_plate.bisurf","ground"],
["a3\data_f\penetration\rubber.bisurf","ground"],
["a3\data_f\penetration\soft_ground.bisurf","ground"],
["a3\data_f\penetration\tyre.bisurf","ground"],
["a3\data_f\penetration\tyre_armored.bisurf","ground"],
["a3\data_f\penetration\vehicle_interior.bisurf","metal"],
["a3\data_f\penetration\void.bisurf","ground"],
["a3\data_f\penetration\water.bisurf","ground"],
["a3\data_f\penetration\weapon_plate.bisurf","metal"],
["a3\data_f\penetration\wood.bisurf","wood"],
["a3\data_f\penetration\wood_plate.bisurf","wood"]
];

View File

@ -1,54 +0,0 @@
#include "..\script_component.hpp"
/*
* Author: Lambda.Tiger
* Adds fragmentation event handlers to projectiles as needed.
*
* Arguments:
* 0: class name of projectile being initialized <STRING>
* 1: Projectile being initialized <OBJECT>
*
* Return Value:
* Nothing
*
* Example:
* [clientFiredBIS-XEH] call ace_frag_fnc_fired
*
* Public: No
*/
params ["_ammo", "_projectile"];
_ammo call FUNC(shouldFrag) params ["_shouldFrag", "_submunitionShouldFrag"];
if (_shouldFrag) then {
_projectile addEventHandler [
"Explode",
{
params ["_projectile", "_posASL", "_velocity"];
if (GVAR(reflectionsEnabled)) then {
[_posASL, _ammo] call FUNC(doReflections);
};
if (_projectile getVariable [QGVAR(blacklisted), false]) exitWith {
TRACE_2("projectile blackisted",typeOf _projectile,_projectile);
};
private _shotParents = getShotParents _projectile;
private _ammo = typeOf _projectile;
// Wait a frame to make sure it doesn't
[
{ [QGVAR(explosionEvent), _this] call CBA_fnc_serverEvent; },
[_posASL, _velocity, _ammo, _shotParents]
] call CBA_fnc_execNextFrame;
}
];
};
if (_submunitionShouldFrag) then {
_projectile addEventHandler [
"SubmunitionCreated",
{
params ["", "_submunitionProjectile"];
[typeOf _submunitionProjectile, _submunitionProjectile] call FUNC(roundInitFrag);
}
];
};

View File

@ -23,51 +23,30 @@ private _shouldFrag = GVAR(shouldFragCache) get _ammo;
if (!isNil "_shouldFrag") exitWith {_shouldFrag};
_shouldFrag = [true, false];
_shouldFrag = true;
private _ammoConfig = configFile >> "CfgAmmo" >> _ammo;
private _skip = getNumber (_ammoConfig >> QGVAR(skip));
if (_skip == 1) then {
_shouldFrag set [0, false];
_shouldFrag = false;
TRACE_1("No frag: skip",_skip);
};
private _force = getNumber (_ammoConfig >> QGVAR(force));
if (_shouldFrag#0 && _force == 0) then {
if (_shouldFrag && !_force) then {
private _explosive = getNumber (_ammoConfig >> "explosive");
if (_explosive < 0.4) exitWith {
_shouldFrag set [0, false];
if (_explosive < 0.5) exitWith {
_shouldFrag = false;
TRACE_3("No frag: _explosive",_skip,_force,_explosive);
};
private _indirectHit = getNumber (_ammoConfig >> "indirectHit");
private _indirectRange = getNumber (_ammoConfig >> "indirectHitRange");
if (_indirectHit < 3 ||
{_indirectRange < 5 && _indirectHit < _indirectRange}) then {
_shouldFrag set [0, false];
if (_indirectHit * sqrt(_indirectRange) < 35 || _indirectRange < 4.5) then {
_shouldFrag = false;
TRACE_5("No frag",_ammo,_skip,_explosive,_indirectRange,_indirectHit);
};
};
private _ammoSubmunitionConfigPath = _ammoConfig >> "submunitionAmmo";
if (isText _ammoSubmunitionConfigPath) then {
private _submunitionAmmo = getText _ammoSubmunitionConfigPath;
TRACE_1("Submunition ammo text: ",_submunitionAmmo);
if (_submunitionAmmo isNotEqualTo "") then {
private _shouldSubmunitionFrag = _submunitionAmmo call FUNC(shouldFrag);
_shouldFrag set [1, _shouldSubmunitionFrag#0 || _shouldSubmunitionFrag#1];
};
} else {
private _submunitionArray = getArray _ammoSubmunitionConfigPath;
TRACE_1("Submunition ammo array: ",_submunitionArray);
for "_i" from 0 to count _submunitionArray - 1 step 2 do {
private _submunitionAmmo = _submunitionArray#_i;
if (_submunitionAmmo isNotEqualTo "") then {
private _shouldSubmunitionFrag = _submunitionAmmo call FUNC(shouldFrag);
_shouldFrag set [1, _shouldSubmunitionFrag#0 || _shouldSubmunitionFrag#1];
};
};
};
GVAR(shouldFragCache) set [_ammo, _shouldFrag];
_shouldFrag

View File

@ -20,5 +20,5 @@ params ["_ammo"];
GVAR(shouldSpallCache) getOrDefaultCall [_ammo, {
(_ammo call FUNC(getSpallInfo)) params ["_caliber", "_explosive", "_indirectHit"];
(_caliber * GVAR(spallIntensity) >= 1.25 * ACE_FRAG_SPALL_POWER_MIN) || {_explosive >= 0.5 && {_indirectHit * GVAR(spallIntensity) >= 2 * ACE_FRAG_SPALL_POWER_MIN}}
_caliber >= 2.5 || (_explosive > 0 && _indirectHit >= 1)
}, true]

View File

@ -1,58 +1,24 @@
private _category = format ["ACE %1", LLSTRING(Module_DisplayName)];
private _category = format ["ACE %1", localize LSTRING(Module_DisplayName)];
[
QGVAR(enabled),
"CHECKBOX",
QGVAR(enabled), "CHECKBOX",
[LSTRING(EnableFrag), LSTRING(EnableFrag_Desc)],
[_category, LSTRING(Frag)],
_category,
true,
1
] call CBA_fnc_addSetting;
[
QGVAR(spallEnabled),
"CHECKBOX",
QGVAR(spallEnabled), "CHECKBOX",
[LSTRING(EnableSpall), LSTRING(EnableSpall_Desc)],
[_category, LSTRING(Spall)],
_category,
false,
1
] call CBA_fnc_addSetting;
[
QGVAR(reflectionsEnabled),
"CHECKBOX",
QGVAR(reflectionsEnabled), "CHECKBOX",
[LSTRING(EnableReflections), LSTRING(EnableReflections_Desc)],
[_category, LSTRING(Reflections)],
_category,
false,
1
] call CBA_fnc_addSetting;
[
QGVAR(fragSimComplexity),
"LIST",
[LSTRING(FragMode), LSTRING(FragMode_Desc)],
[_category, LSTRING(Frag)],
[[2, 1, 0], [LSTRING(FragMode_Opt2),LSTRING(FragMode_Opt1),LSTRING(FragMode_Opt0)], 2],
1
] call CBA_fnc_addSetting;
[
QGVAR(atLeastOne),
"CHECKBOX",
[LSTRING(MinFrag), LSTRING(MinFrag_Desc)],
[_category, LSTRING(Frag)],
false,
1
] call CBA_fnc_addSetting;
[
QGVAR(spallIntensity),
"SLIDER",
[LSTRING(SpallIntensity), LSTRING(SpallIntensity_Desc)],
[_category, LSTRING(Spall)],
[0.1, 2, 1, 1],
1,
{
GVAR(shouldSpallCache) = createHashMap;
}
] call CBA_fnc_addSetting;

View File

@ -123,33 +123,6 @@
<Chinese>啟用ACE模擬爆炸反射</Chinese>
<Czech>Povolit ACE simulaci odrazu exploze</Czech>
</Key>
<Key ID="STR_ACE_Frag_FragMode">
<English>Simulation Mode</English>
</Key>
<Key ID="STR_ACE_Frag_FragMode_Desc">
<English>Sets the fragmentation simulation mode, which can be targeted, random or both.</English>
</Key>
<Key ID="STR_ACE_Frag_FragMode_Opt0">
<English>Unit targeted fragmentation</English>
</Key>
<Key ID="STR_ACE_Frag_FragMode_Opt1">
<English>Random fragmentation</English>
</Key>
<Key ID="STR_ACE_Frag_FragMode_Opt2">
<English>Random and targeted fragmentation</English>
</Key>
<Key ID="STR_ACE_Frag_MinFrag">
<English>Disable Fragment Misses</English>
</Key>
<Key ID="STR_ACE_Frag_MinFrag_Desc">
<English>Create a fragment for each unit that a fragment could be shot towards. This setting has no effect when "Random fragmentation" is selected.</English>
</Key>
<Key ID="STR_ACE_Frag_SpallIntensity">
<English>Spalling Intensity</English>
</Key>
<Key ID="STR_ACE_Frag_SpallIntensity_Desc">
<English>Modifier to increase or decrease the number and intensity of spalling events. Increasing this value may cause performance degradation.</English>
</Key>
<Key ID="STR_ACE_Frag_EnableDebugTrace">
<English>Frag/Spall Debug Tracing</English>
<German>Splitter-/Explosions-Debug-Verfolgung</German>