PHP Code:
/*
Basic bullet detection framework
Version: 0.64
Date: 03/07/2012
Author: Fabrizio_T
Additional code: TPW
File Name: bdetect.sqf
CHANGELOG:
-------------
Version: 0.64
-------------
* Incorporated suggestions from tpw: http://forums.bistudio.com/showthread.php?136304-TPWC-AI-suppression-system&p=2182742&viewfull=1#post2182742
* Minor optimization / fixes
* Introduced variable "bdetect_callback_mode": (String, Default "spawn") Allowed values: "call" or "spawn". It controls synchronous vs.asynchronous bdetect_callback() execution.
-------------
Version: 0.63
-------------
* Temporarily reverted from "diag_tickTime" to "time" for issues to be looked in
-------------
Version: 0.62
-------------
* Fixed bullet position handling
-------------
Version: 0.61
-------------
* Fixed problem with units eventually detecting own shots
------------
Version: 0.6
------------
* Incorporated v0.51 changes/fixes by tpw.
* Function bdetect_fnc_callback() slightly changed. See bottom of this file for example.
* Function bdetect_fnc_init() waits for 5 seconds to pass by after mission start, before activating the framework.
* Introduced function bdetect_fnc_eh_fired_add(): suitable to force -immediate- assignment of a fired EH to any unit. Syntax: [unit] call bdetect_fnc_eh_fired_add();
* Fixed function bdetect_fnc_benchmark(): internally using code spawning, not to block code execution after function call.
* Tweaked internals so that "diag_tickTime" is used instead of "time" (Thanks Ollem).
* Introduced function bdetect_fnc_eh_loop(): iteratively checks ( each "bdetect_eh_assign_cycle_wait" seconds ) for newly spawned units to assign them a 'Fired' event Handler.
* Renamed "bdetect_bullet_min_distance" to "bdetect_bullet_max_proximity"
* Introduced variable "bdetect_init_done": (Boolean). It evaulates true when framework is loaded. Usage: "waitUntil { bdetect_init_done};"
* Introduced variable "bdetect_eh_assign_cycle_wait": (seconds, Default 10). Wait duration foreach cyclic execution of bdetect_fnc_eh_loop()
* Introduced variable "bdetect_bullet_min_distance": (meters, Default 25). Bullets not having travelled this distance are ignored
* Introduced variable "bdetect_bullet_initial_min_speed": (meters / second, Default 360). Bullets initially slower than this are ignored (good for subsonic bullets skipping).
* Introduced variable "bdetect_debug_chat": (Boolean, Default false) Show debug messages also in globalChat.
* Changed Variable "bdetect_debug_min_level" to "bdetect_debug_levels". (Array). Default [0,1,2,3,4,5,6,7,8,9]. 0-9 are reserved levels.
* Much more
------------
Version: 0.5
------------
* Framework is named "bdetect" and contained into a single file: bdetect.sqf
* Renamed variable "bdetect_debug" to "bdetect_debug_enable"
* Introduced variables "bdetect_name" and "bdetect_version": name and version of the framework;
* Introduced function bdetect_fnc_init() to handle the framework bootstrap.
* Introduced variable "bdetect_callback" (string). It defines the name of a custom callback function to be called on a unit when it detects a close bullet (Default: "bdetect_fnc_callback").
* Functions and variable names polishing. All vars named bdetect_<varname>. All functions named bdetect_fnc_<funcname>.
* Added some missing private vars.
------------
Version: 0.4
------------
* Introduced variables "bdetect_enable" (boolean), "bdetect_debug_levels" (Number), "bdetect_skip_mags" (array).
* Variable "bdetect_enable" act as a toggle to enable / disable framework.
* Variable "bdetect_debug_levels" allows for selective display / logging of debug messages based on level.
* Introduced function bdetect_fnc_benchmark() to display performance stats.
* Added "bdetect_skip_mags", which is a blacklist of bullet types which should not trigger detection
------------
Version: 0.3
------------
* Introduced variables "bdetect_bullet_delay" (seconds) , "bdetect_bullet_max_height" (meters).
* Variable "bdetect_bullet_delay" (seconds) changes dinamycally between "bdetect_bullet_min_delay" (lower bound) and 1 (upper bound), depending on actual FPS vs "bdetect_fps_min".
* Variable "bdetect_bullet_max_height" (meters) is the maximum height for bullets, over ground. Bullets higher than this value (Default: 10) and with up vector > 0 are skipped from detection, since they are too high and diverge from ground.
* Some Code optimization.
------------
Version: 0.2
------------
* Position of bullet as SetPosASL.
* Introduced variables "bdetect_fps_min" (fps), "bdetect_bullet_max_distance" (meters), "bdetect_bullet_max_lifespan" (seconds).
* Introduced function bdetect_fnc_diag_min_fps(): in case FPS go under "bdetect_fps_min" value (Default: 25) bdetect_bullet_delay is gradually raised.
* Function bdetect_fnc_bullet_remove() edited and renamed to bdetect_fnc_bullet_tag_remove(), since actual removal is done elsewhere.
* Bullets are removed from bdetect_fired_bullets if distance is over "bdetect_bullet_max_distance" meters or lifespan is over "bdetect_bullet_max_lifespan" seconds.
------------
Version: 0.1
------------
* First draft
*/
//BEGINNING OF FRAMEWORK CODE
// -----------------------------
// Constants
// -----------------------------
bdetect_name = "bDetect - Bullet Detection Framework";
bdetect_short_name = "bDetect";
bdetect_version = "0.64";
bdetect_init_done= false;
// -----------------------------
// Functions
// -----------------------------
bdetect_fnc_init =
{
private [ "_msg", "_x" ];
sleep 5;
// You may override these variables
if(isNil "bdetect_enable") then { bdetect_enable = true; }; // (Boolean, Default true) Toggle to Enable / Disable bdetect altogether.
if(isNil "bdetect_debug_enable") then { bdetect_debug_enable = false; }; // (Boolean, Default false) Toggle to Enable / Disable debug messages.
if(isNil "bdetect_debug_chat") then { bdetect_debug_chat = false; }; // (Boolean, Default false) Show debug messages also in globalChat.
if(isNil "bdetect_debug_levels") then { bdetect_debug_levels = [0,1,2,3,4,5,6,7,8,9]; }; // (Array, Default [0,1,2,3,4,5,6,7,8,9]) Filter debug messages by included levels.
if(isNil "bdetect_callback") then { bdetect_callback = "bdetect_fnc_callback"; }; // (String, Default "bdetect_fnc_callback") Name for your own callback function
if(isNil "bdetect_callback_mode") then { bdetect_callback_mode = "spawn"; }; // (String, Default "spawn") Allowed values: "call" or "spawn"
if(isNil "bdetect_fps_min") then { bdetect_fps_min = 25; }; // (Number, Default 25) The minimum FPS you wish to keep
if(isNil "bdetect_fps_calc_each_x_frames") then { bdetect_fps_calc_each_x_frames = 16; }; // (Number, Default 16) FPS check is done each "bdetect_fps_min" frames. 1 means each frame.
if(isNil "bdetect_eh_assign_cycle_wait") then { bdetect_eh_assign_cycle_wait = 10; }; // (Seconds, Default 10). Wait duration foreach cyclic execution of bdetect_fnc_eh_loop()
if(isNil "bdetect_bullet_min_delay") then { bdetect_bullet_min_delay = 0.1; }; // (Seconds, Default 0.1) Minimum time between 2 consecutive shots fired by an unit for the last bullet to be tracked. Very low values may cause lag.
if(isNil "bdetect_bullet_max_delay") then { bdetect_bullet_max_delay = 2; }; // (Seconds, Default 2)
if(isNil "bdetect_bullet_initial_min_speed") then { bdetect_bullet_initial_min_speed = 360; }; // (Meters/Second, Default 360) Bullets slower than this are ignored.
if(isNil "bdetect_bullet_max_proximity") then { bdetect_bullet_max_proximity = 10; }; // (Meters, Default 10) Maximum proximity to unit for triggering detection
if(isNil "bdetect_bullet_min_distance") then { bdetect_bullet_min_distance = 25; }; // (Meters, Default 25) Bullets having travelled less than this distance are ignored
if(isNil "bdetect_bullet_max_distance") then { bdetect_bullet_max_distance = 400; }; // (Meters, Default 400) Bullets past this distance are ignored
if(isNil "bdetect_bullet_max_lifespan") then { bdetect_bullet_max_lifespan = 0.5; }; // (Seconds, Default 0.5) Bullets living more than this are ignored
if(isNil "bdetect_bullet_max_height") then { bdetect_bullet_max_height = 6; }; // (Meters, Default 6) Bullets higher than this -and- diverging from ground are ignored
if(isNil "bdetect_skip_mags") then { // (Array) Skip these bullet types altogether
bdetect_skip_mags =
[
"30rnd_9x19_MP5",
"30rnd_9x19_MP5SD",
"15Rnd_9x19_M9",
"15Rnd_9x19_M9SD",
"7Rnd_45ACP_1911",
"7Rnd_45ACP_1911",
"8Rnd_9x18_Makarov",
"8Rnd_9x18_MakarovSD",
"64Rnd_9x19_Bizon",
"64Rnd_9x19_SD_Bizon",
"13Rnd_9mm_SLP",
"17Rnd_9x19_glock17",
"6Rnd_45ACP",
"30Rnd_9x19_UZI",
"30Rnd_9x19_UZI_SD"
];
};
// Do not edit the variables below.
if(isNil "bdetect_fired_bullets") then { bdetect_fired_bullets = []; };
if(isNil "bdetect_fps") then { bdetect_fps = bdetect_fps_min; };
if(isNil "bdetect_bullet_delay") then { bdetect_bullet_delay = bdetect_bullet_min_delay; };
if(isNil "bdetect_frame_tstamp") then { bdetect_frame_tstamp = 0; };
if(isNil "bdetect_frame_min_duration") then { bdetect_frame_min_duration = (bdetect_bullet_max_proximity * 2 * .66 / 900) max .01; };
// bullet speed converted to kmh
bdetect_bullet_initial_min_speed = bdetect_bullet_initial_min_speed * 3.6;
// compile callback name into function
bdetect_callback_compiled = call compile format["%1", bdetect_callback];
_msg = format["Starting %1 v%2.", bdetect_name, bdetect_version];
[ _msg, 0 ] call bdetect_fnc_debug;
bdetect_spawned_loop_handler = [] spawn bdetect_fnc_eh_loop;
[bdetect_fnc_detect,0] call cba_fnc_addPerFrameHandler;
bdetect_init_done= true;
hint format["%1 v%2 started.", bdetect_name, bdetect_version];
};
// Keep searching units for newly spawned ones and assign fired EH to them
bdetect_fnc_eh_loop =
{
private [ "_x", "_msg"];
while { true } do
{
{
[_x] call bdetect_fnc_eh_fired_add;
} foreach allUnits;
sleep bdetect_eh_assign_cycle_wait;
};
};
// Assign fired EH to a single unit
bdetect_fnc_eh_fired_add =
{
private ["_unit", "_msg"];
_unit = _this select 0;
if( isNil { _unit getVariable "bdetect_fired_eh" } ) then
{
_unit setVariable ["bdetect_fired_eh", true];
_unit addEventHandler ["Fired", bdetect_fnc_fired];
if ( ( assignedVehicleRole _unit) select 0 == "Turret" && isNil { (vehicle _x) getVariable "bdetect_fired_eh" } ) then
{
(vehicle _x) setVariable ["bdetect_fired_eh", true];
(vehicle _x) addeventhandler ["Fired", bdetect_fnc_fired];
};
_msg = format["[%1] was assigned 'Fired' EH", _unit];
[ _msg, 3 ] call bdetect_fnc_debug;
}
else
{
_msg = format["[%1] already had an assigned 'Fired' EH", _unit];
[ _msg, 3 ] call bdetect_fnc_debug;
};
};
// Fired EH
bdetect_fnc_fired =
{
private ["_unit", "_muzzle", "_magazine", "_bullet", "_speed", "_msg", "_time", "_dt"];
if( bdetect_enable ) then
{
_unit = _this select 0;
_muzzle = _this select 2;
_magazine = _this select 5;
_bullet = _this select 6;
_speed = speed _bullet;
_time = time; //diag_tickTime
_dt = _time - ( _unit getVariable ["bdetect_fired_time", 0] );
if( _dt > bdetect_bullet_delay
&& !( _magazine in bdetect_skip_mags )
&& _speed > bdetect_bullet_initial_min_speed
) then
{
_unit setVariable ["bdetect_fired_time", _time];
// Append info to bullets array
[ _bullet, _unit, _time ] call bdetect_fnc_bullet_add;
_msg = format["[%1] Fired bullet: speed=%2, type=%3, Delay=%4", _unit, _speed, typeOf _bullet, _dt ];
[ _msg, 2 ] call bdetect_fnc_debug;
}
else
{
_msg = format["[%1] Skipped bullet: speed=%2, type=%3, Delay=%4 [%5 - %6]", _unit, _speed, typeOf _bullet, _dt, _time , ( _unit getVariable ["bdetect_fired_time", 0] )];
[ _msg, 2 ] call bdetect_fnc_debug;
};
};
};
// Time-critical detection function, to be executed per-frame
bdetect_fnc_detect =
{
private ["_n", "_tot", "_bullet", "_data", "_side", "_pos", "_time", "_shooter", "_blacklist", "_update_blacklist", "_dist", "_units", "_x", "_data", "_func", "_t", "_k", "_bpos", "_nul"];
_t = time; //diag_tickTime
if( bdetect_enable && (_t - bdetect_frame_tstamp) >= bdetect_frame_min_duration) then
{
_msg = format["Frame duration=%1, min duration:%2", (_t - bdetect_frame_tstamp), bdetect_frame_min_duration ];
[ _msg, 4 ] call bdetect_fnc_debug;
_tot = count bdetect_fired_bullets;
bdetect_frame_tstamp = _t;
if ( _tot > 0 ) then
{
if( diag_frameno % bdetect_fps_calc_each_x_frames == 0) then
{
call bdetect_fnc_diag_min_fps;
};
for "_n" from 0 to _tot - 1 step 2 do
{
_bullet = bdetect_fired_bullets select _n;
_data = bdetect_fired_bullets select (_n + 1);
_shooter = _data select 0;
_pos = _data select 1;
_time = _data select 2;
_blacklist = _data select 3;
_update_blacklist = false;
if( !( isnull _bullet ) ) then
{
_bpos = getPosATL _bullet;
_dist = _bpos distance _pos;
//_msg = format["Following bullet %1. Time: %2. Distance: %3 Speed: %4. Position: %5", _bullet, _t - _time, _dist, (speed _bullet / 3.6), getPosASL _bullet];
//[ _msg, 2 ] call bdetect_fnc_debug;
};
if( isNull _bullet
|| !(alive _bullet)
|| _t - _time > bdetect_bullet_max_lifespan
|| _dist > bdetect_bullet_max_distance
|| speed _bullet < bdetect_bullet_initial_min_speed // funny rebounds handling
|| ( ( _bpos select 2) > bdetect_bullet_max_height && ( ( vectordir _bullet ) select 2 ) > 0 )
) then
{
[_bullet] call bdetect_fnc_bullet_tag_remove;
}
else
{
if( _dist > bdetect_bullet_min_distance ) then
{
_units = _bpos nearEntities [ ["MAN"] , bdetect_bullet_max_proximity];
{
if( alive _x && !(_x in _blacklist) && _x != _shooter ) then
{
if( vehicle _x == _x && lifestate _x == "ALIVE") then
{
_blacklist = _blacklist + [_x];
_update_blacklist = true;
if(bdetect_callback_mode == "spawn") then {
_nul = [_x, _bullet, _x distance _bpos, _data] spawn bdetect_callback_compiled;
} else {
[_x, _bullet, _x distance _bpos, _data] call bdetect_callback_compiled;
};
_msg = format["[%1] close to bullet %2 fired by %3, proximity=%4m, data=%5", _x, _bullet, _shooter, _x distance _bpos, _data];
[ _msg, 9 ] call bdetect_fnc_debug;
};
}
else
{
_msg = format["[%1] Blacklisted, bullet %2 ignored", _x, _bullet];
[ _msg, 5 ] call bdetect_fnc_debug;
};
} foreach _units;
if(_update_blacklist) then
{
// Update blacklist
bdetect_fired_bullets set[ _n + 1, [_shooter, _pos, _time, _blacklist] ];
};
//_msg = format["bdetect_fired_bullets = %1", bdetect_fired_bullets];
//[ _msg, 2 ] call bdetect_fnc_debug;
};
};
};
// remove dead / expired bullets
bdetect_fired_bullets = bdetect_fired_bullets - [-1];
//_msg = format["%1 bullets in array", count ( bdetect_fired_bullets ) / 2];
//[ _msg, 2 ] call bdetect_fnc_debug;
};
};
};
bdetect_fnc_diag_min_fps =
{
private ["_fps", "_msg"];
_fps = diag_fps;
_msg = format["FPS=%1, Min.FPS=%2, Prev. FPS=%3, bdetect_bullet_delay=%4)", _fps, bdetect_fps_min, bdetect_fps, bdetect_bullet_delay ];
[ _msg, 1 ] call bdetect_fnc_debug;
if( _fps < bdetect_fps_min * 1.15) then
{
if( bdetect_bullet_delay + .15 < bdetect_bullet_max_delay && _fps < bdetect_fps_min ) then
{
bdetect_bullet_delay = bdetect_bullet_delay + .15;
_msg = format["FPS down to %1. Augmenting bdetect_bullet_delay to %2", _fps, bdetect_bullet_delay];
[ _msg, 1 ] call bdetect_fnc_debug;
};
}
else
{
if( ( bdetect_bullet_delay - .15 ) >= bdetect_bullet_min_delay ) then
{
bdetect_bullet_delay = bdetect_bullet_delay - .15;
_msg = format["FPS up to %1. Reducing bdetect_bullet_delay to %2", _fps, bdetect_bullet_delay];
[ _msg, 1 ] call bdetect_fnc_debug;
};
};
bdetect_fps = _fps;
};
// Function to add a bullet to bdetect_fired_bullets
bdetect_fnc_bullet_add =
{
private ["_bullet", "_shooter", "_pos", "_time", "_msg", "_n"];
_bullet = _this select 0; // bullet object
_shooter = _this select 1; // shooter
_pos = getPosATL _bullet; // bullet start position
_time = _this select 2; // bullet shoot time
_n = count bdetect_fired_bullets;
bdetect_fired_bullets set [ _n, _bullet ];
bdetect_fired_bullets set [ _n + 1, [ _shooter, _pos, _time, [] ] ];
_msg = format["bullet %1 added", _bullet, _n / 2];
[ _msg, 2] call bdetect_fnc_debug;
};
// Function to remove a bullet from bdetect_fired_bullets
bdetect_fnc_bullet_tag_remove =
{
private ["_bullet", "_n", "_msg" ];
_bullet = _this select 0;
_n = bdetect_fired_bullets find _bullet;
if( _n != -1 ) then
{
bdetect_fired_bullets set[ _n, -1 ];
bdetect_fired_bullets set[ _n + 1, -1 ];
_msg = format["null/expired bullet removed"];
[ _msg, 2 ] call bdetect_fnc_debug;
};
};
// Callback function to be executed from within bdetect_fnc_detect
bdetect_fnc_callback =
{
private [ "_unit", "_bullet", "_proximity", "_data", "_shooter", "_pos", "_time", "_msg" ];
_unit = _this select 0; // unit being under fire
_bullet = _this select 1; // bullet object
_proximity = _this select 2; // distance between _bullet and _unit
_data = _this select 3; // Array containing more data
_shooter = _data select 0; // shooter
_pos = _data select 1; // starting position of bullet
_time = _data select 2; // starting time of bullet
_msg = format["[%1] close to bullet %2 fired by %3, proximity=%4m, data=%5", _unit, _bullet, _shooter, _proximity, _data];
[ _msg, 9 ] call bdetect_fnc_debug;
};
// function to display and log stuff (into .rpt file) level zero is intended only for builtin messages
bdetect_fnc_debug =
{
private [ "_msg", "_level"];
/*
DEBUG LEVELS:
From 0-9 are reserved.
0 = unclassified messages
1 = FPS related messages
2 = "bdetect_fired_bullets" related messages
3 = EH related messages
4 = Frame related messages
5 = Unit blacklist messages
...
9 = Unit detection related messages
*/
_level = _this select 1;
if( bdetect_debug_enable && _level in bdetect_debug_levels) then
{
_msg = _this select 0;
diag_log format["%1 [%2 v%3] Frame:%4 L%5: %6", time, bdetect_short_name, bdetect_version, diag_frameno, _level, _msg ];
if( bdetect_debug_chat ) then
{
player globalchat format["%1 - %2", time, _msg ];
};
};
};
bdetect_fnc_benchmark =
{
private ["_cnt"];
if(isNil "bdetect_stats_max_bullets") then { bdetect_stats_max_bullets = 0;};
if(isNil "bdetect_stats_min_fps") then { bdetect_stats_min_fps = 999;};
if(isNil "bdetect_fired_bullets") then { bdetect_fired_bullets = [];};
_nul = [] spawn
{
sleep 5;
while { true } do
{
_cnt = count ( bdetect_fired_bullets ) / 2;
if( _cnt > bdetect_stats_max_bullets ) then { bdetect_stats_max_bullets = _cnt; };
if( diag_fps < bdetect_stats_min_fps ) then { bdetect_stats_min_fps = diag_fps };
hintsilent format["TIME: %1\nFPS: %2 (min: %3)\nBULLETS: %4 (max: %5)\nS.DELAY: %6 (Min FPS: %7)", time, diag_fps, bdetect_stats_min_fps, _cnt, bdetect_stats_max_bullets, bdetect_bullet_delay, bdetect_fps_min];
sleep .1;
};
};
};
// END OF FRAMEWORK CODE
// -----------------------------------------------------------
// Example for running the script
// -----------------------------------------------------------
// The following commented code is not part of the framework,
// just an advice on how to run it from within another file
// -----------------------------------------------------------
/*
// load framework
call compile preprocessFileLineNumbers "bdetect.sqf";
// First declare any optional variables whose value should be other than Default (see the defined variables in bdetect.sqf, function bdetect_fnc_init()
bdetect_debug_enable = true;
bdetect_debug_levels = [9];
bdetect_debug_chat = true;
// Then name your own unit callback function (the one that should be triggered when a bullet is close to a unit)
bdetect_callback = "my_suppression_function";
// Define your own callback function, named as above
my_suppression_function = {
private [ "_unit", "_bullet", "_proximity", "_data", "_shooter", "_pos", "_time", "_msg" ];
_unit = _this select 0;
_bullet = _this select 1;
_proximity = _this select 2;
_data = _this select 3;
_shooter = _data select 0; // enemy shooter
_pos = _data select 1; // starting position of bullet
_time = _data select 2; // starting time of bullet
_msg = format["my_suppression_function - [%1] close to bullet %2 fired by %3, proximity=%4m, data=%5", _unit, _bullet, _shooter, _proximity, _data];
[ _msg, 9 ] call bdetect_fnc_debug;
};
// Then initialize framework
call bdetect_fnc_init;
// Wait stuff to be loaded
waitUntil { bdetect_init_done};
// finally, activate display of stats if you wish
call bdetect_fnc_benchmark;
// All done, now put your other stuff here ...
*/