Jump to content
s.rodge

Rifle Qualification Range with Scoring System Help

Recommended Posts

Hey guys,  before I posted this I have looked at everything I can find on the internet.  I have tried using the existing scripts to make it work or understand how it works.  This is all I have right now but I am stuck on finding a solution.

 

Layout:

 

Laptop controls starting range and resetting targets.  There are 5 targets in line at 50/175/300 meter spacing. 

Round 1 starts with an audible sound and 20 random targets from target lines 1 (50) and 2 (175).  Round 1 ends with sound and allows for reload and stance change.

Round 2 starts with an audible sound and 20 random targets from target lines 2 (175) and 3 (300).  Round 2 ends with sound and allows for reload and stance change.

Round 3 starts with an audible sound and 10 random targets from target lines 3 (300).  Round 3 ends with sound and allows for reload and stance change.

It then shows score of player.

 

I will need to do this for 4 - 6 lanes.

 

 

Quote

_count = 0;
_line1 = [l1t1, l1t2, l1t3, l1t4, l1t5];
_line2 = [l1t6, l1t7, l1t8, l1t9, l1t10];
_line3 = [l1t11, l1t12, l1t13, l1t14, l1t15];
_allLines = (count _line1) + (count _line2) + (count _line3);

{_x  animate["terc",1]} forEach _line1;
{_x  animate["terc",1]} forEach _line2;
{_x  animate["terc",1]} forEach _line3;

//Round 1


//Round 2


//Round 3


hint "Completed"

 

Share this post


Link to post
Share on other sites
On 12/30/2017 at 8:27 PM, s.rodge said:

Hey guys,  before I posted this I have looked at everything I can find on the internet.  I have tried using the existing scripts to make it work or understand how it works.  This is all I have right now but I am stuck on finding a solution.

 

Layout:

 

Laptop controls starting range and resetting targets.  There are 5 targets in line at 50/175/300 meter spacing. 

Round 1 starts with an audible sound and 20 random targets from target lines 1 (50) and 2 (175).  Round 1 ends with sound and allows for reload and stance change.

Round 2 starts with an audible sound and 20 random targets from target lines 2 (175) and 3 (300).  Round 2 ends with sound and allows for reload and stance change.

Round 3 starts with an audible sound and 10 random targets from target lines 3 (300).  Round 3 ends with sound and allows for reload and stance change.

It then shows score of player.

 

I will need to do this for 4 - 6 lanes.

 

 

 

 

Here's something I threw together that might be what you're looking for. A quick disclaimer: It's only designed to work with Popup targets. Other targets might work too, I've not checked.

Now you weren't super clear on what the ruleset is and what scoring is based on so I took some creative liberties, let me know if you need any changes and I'll gladly help.

 

When placing the targets they do not have to be placed at the exact distance you provide, the script includes targets up to 25% of the way to the next line. This means you can vary the placement of your targets quite a bit and, if you so desire, put them on buildings and/or behind cover. Just keep in mind:
All recognised targets will be lying down until the range is activated.

Any targets that are not recognised, i.e. placed to for away from any line, will be standing up at the start of the mission.

 

**** Newer version available ****

A new version is available here: 

 

 

==============================

Everything in this post below this line belongs to the older version and is kept only for reference.

==============================

 

The script: 

Spoiler

//initRqrLane.sqf
#define SOUND_POPUP "a3\missions_f_beta\data\sounds\firing_drills\target_pop-up_large.wss"
#define SOUND_POPDOWN "a3\missions_f_beta\data\sounds\firing_drills\target_pop-down_large.wss"
#define SOUND_ACTIVE "a3\missions_f_beta\data\sounds\firing_drills\course_active.wss"
#define SOUND_CLEAR "a3\missions_f_beta\data\sounds\firing_drills\checkpoint_clear.wss"
#define SOUND_MISS "a3\missions_f_beta\data\sounds\firing_drills\drill_start.wss"

private _roundsDef = [
	[5, [1], [3,5], [3,7]],
	[5, [2], [3,5], [3,7]],
	[5, [3], [3,5], [3,7]]
];

params [
	["_laneController", objNull, [objNull]],
	["_laneTrigger", objNull, [objNull]],
	["_lineSpacing", [50, 100, 150], ["",[]]],
	["_roundTemplate", _roundsDef, [[]]],
	["_shootingPosition", objNull, [objNull]]
];

//Param check
if(isNull _laneController) exitWith { ["Invalid LaneController provided, must be a valid OBJECT, _this = %1", _this] call BIS_fnc_error; };
if(isNull _laneTrigger) exitWith { ["Invalid LaneTrigger provided, must be a valid TRIGGER, _this = %1", _this] call BIS_fnc_error; };
if(isNull _shootingPosition) then { _shootingPosition = _laneController; };

//Find targets
private _targets = allMissionObjects "TargetBase" select {_x inArea _laneTrigger};
//Sort found targets into the appropriate line
private _lines = []; 
{
	_lines pushBack []; 
} forEach _lineSpacing;

private _tolerance = 0.25;
{
	private _distance = _x distance2D _shootingPosition;
	private _lineIndex = -1;
	private _numLines = count _lineSpacing;
	{ 
		private _actualTolerance = _x * _tolerance;
		
		private _lowerIndex = _forEachIndex - 1;
		private _lowerBound = if(_lowerIndex < 0) then { _x - _actualTolerance } else { _x - _tolerance * (_x - (_lineSpacing select _lowerIndex)) };
		private _upperIndex = _forEachIndex + 1;
		private _upperBound = if(_upperIndex >= _numLines) then { _x + _actualTolerance } else { _x + _tolerance * ((_lineSpacing select _upperIndex) - _x) };
		if(_lowerBound <= _distance && _distance <= _upperBound) exitWith {
			_lineIndex = _forEachIndex;
		}; 
	} forEach _lineSpacing;
	
	if(_lineIndex >= 0) then {
		//Target is in line, initalize it
		_x setVariable ["RQR_controller", _laneController];
		_x animateSource ["terc", 1];
		_x addEventHandler [
			"HandleDamage", 
			{ 
				params ["_target", "_selection", "_damage", "_source", "_projectile", "_hitPartIndex", "_instigator"];
				if(_target animationPhase "terc" == 0) then {				
					if(_projectile isKindOf ["BulletBase", configFile >> "CfgAmmo"]) then {				
						playSound3D [SOUND_POPDOWN, _target, false, getPosASL _target, 1, 1, 300];
						_target spawn {
							waitUntil {(_this animationPhase "terc" == 1)};
							waitUntil {(_this animationPhase "terc" < 1)};
							_this animateSource ["terc", 1];
						};
					} else {
						_target animateSource ["terc", 0];
					};
				};
				0
			}
		];
		_x addEventHandler [
			"HitPart",
			{
				{
					_x params ["_target", "_shooter", "_bullet"];
					private _controller = _target getVariable "RQR_controller";
					if(_shooter == _controller getVariable ["RQR_currentShooter", objNull]) then {
						if(_target animationPhase "terc" == 0) then {				
							if(typeOf _bullet isKindOf ["BulletBase", configFile >> "CfgAmmo"]) then {											
								private _stats = _controller getVariable ["RQR_stats", []];
								_stats set [3, (_stats select 3) + 1];
							};
						};
					};
				} forEach _this;
			}
		];
		(_lines select _lineIndex) pushBack _x;
	};
} forEach _targets;

_laneController setVariable ["RQR_lineSpacing", _lineSpacing];
_laneController setVariable ["RQR_targets", _lines];
_laneController setVariable ["RQR_rounds", _roundTemplate];
_laneController setVariable ["RQR_shootPosition", _shootingPosition];


//Timed mode
private _actionStart = _laneController addAction [
	"Start timed test", 
	{
		params ["_controller", "_caller", "_id", "_args"];
		_controller setVariable ["RQR_currentShooter", _caller, true];
		_controller setVariable ["RQR_cancel", false, true];
		_caller setVariable ["RQR_currentController", _controller];
		// ============================
		// ======== Functions =========
		// ============================
			// ==== Break handler ====
			private _breakFunction = {
				params ["_time", "_hintReload"];
				private _t = 5 min ceil (_time/2);
				sleep _t;
				if(_hintReload) then {
					private _tLeft = _time - _t;
					while {_tLeft > 0 && !(_controller getVariable ["RQR_cancel", false])} do {
						hintSilent parseText (
							[
								format ["Next round in %1 seconds", _tLeft],
								"Reload"
							] joinString "<br/>"
						);
						sleep 1;
						_tLeft = _tLeft - 1;
					};
				} else {
					if(!(_controller getVariable ["RQR_cancel", false])) then {
						sleep (_time - _t);
					};
				};
			};
			
			// ==== Round handler ====
			private _roundFunction = {
				params ["_roundNum","_maxRounds", "_numTargets", "_lineIndexes", "_paramTimeUp", "_paramTimeDown"];
				_paramTimeUp params ["_timeUpMin", "_timeUpMax"];
				_paramTimeDown params ["_timeDownMin", "_timeDownMax"];
				
				private _timeUpDelta = _timeUpMax - _timeUpMin;	
				private _timeDownDelta = _timeDownMax - _timeDownMin;	
				
				//Select possible targets
				private _roundTargetList = [];
				private _ranges = [];
				{
					private _index = _x - 1;
					if(0 <= _index && _index < count _targets) then {
						_roundTargetList append (_targets select _index);
						_ranges pushBack format ["%1 m", _spacing select _index];
					};
				} forEach _lineIndexes;
				
				hint parseText (
					[
						format ["<t size='1.5'>Round %1 of %2</t>", _roundNum, _maxRounds],
						format ["<t align='left'>Targets: </t><t align='right'>%1</t>", _numTargets],
						format ["<t align='left'>Ranges: </t><t align='right'>%1</t>", _ranges joinString ", "],
						"",
						"<t size='1.5'>Get ready...</t>"
					] joinString "<br/>"
				);
				
				sleep 5;
				hintSilent parseText "<t size='1.5'>Begin!</t>";
				playSound3D [SOUND_ACTIVE, _controller, false, getPosASL _controller, 1, 1, 50];
				private _firedEH = _caller addEventHandler [
					"FiredMan", 
					{
						params ["_unit", "_weapon"];
						private _ctrl = _unit getVariable "RQR_currentController";
						_stats = _ctrl getVariable "RQR_stats";
						_stats set [2, (_stats select 2)+1];
					}
				];
				sleep 2;
				
				private _i = 0;
				private _misses = 0;
				private _hits = 0;
				while {_i < _numTargets && !(_controller getVariable ["RQR_cancel", false])} do {
					private _popup = selectRandom _roundTargetList;
					private _timeUp = _timeUpMin + random _timeUpDelta;
					
					_popup animateSource ["terc", 0];
					playSound3D [SOUND_POPUP, _popup, false, getPosASL _popup, 1, 1, 300];
					sleep 0.25;
					
					private _timeout = time + _timeUp;
					waitUntil {time > _timeout OR (_popup animationPhase "terc") == 1};
					
					_popup animateSource ["terc", 1];
					playSound3D [SOUND_POPDOWN, _popup, false, getPosASL _popup, 1, 1, 300];
					
					if(time > _timeout) then {
						//Missed
						playSound3D [SOUND_MISS, _controller, false, getPosASL _controller, 2, 1, 50];
						_misses = _misses + 1;
					} else {
						//Hit
						_hits = _hits + 1;
					};
					
					_i = _i + 1;
					if(_i < _numTargets) then {
						sleep (_timeDownMin + random _timeDownDelta);
					};
				};
				
				_stats set [0, (_stats select 0) + _misses];
				_stats set [1, (_stats select 1) + _hits];
				//Play end sound
				_caller removeEventHandler ["FiredMan",_firedEH];
				playSound3D [SOUND_CLEAR, _controller, false, getPosASL _controller, 2, 1, 50];
				hintSilent parseText (
					[
						"<t size='1.5'>Round complete!</t>",
						format ["<t align='left'># targets: </t><t align='right'>%1", _numTargets],
						format ["<t align='left'>Targets missed: </t><t align='right'>%1", _misses],
						format ["<t align='left'>Targets hit: </t><t align='right'>%1", _hits]
					] joinString "<br/>"
				);
			};
		// ==================================
		// =========== Action Main ==========
		// ==================================
		private _spacing = _controller getVariable "RQR_lineSpacing";
		private _targets = _controller getVariable "RQR_targets";
		private _rounds = _controller getVariable "RQR_rounds";
		private _stats = [0,0,0,0]; //[Targets missed, Targets hit, ShotsFired, ShotsHit]
		_controller setVariable ["RQR_stats", _stats];
		
		//Thread for checking if still in shooting position
		private _checkShooter_thread = [_controller, _caller, _controller getVariable "RQR_shootPosition"] spawn {
			params ["_ctrl", "_shooter", "_shootFrom"];
			waitUntil {
				sleep 0.5;
				isNull (_ctrl getVariable ["RQR_currentShooter", objNull]) 
				OR 
				_shooter distance _shootFrom > 10
			};
			
			if( !isNull (_ctrl getVariable ["RQR_currentShooter", objNull]) ) then {
				_ctrl setVariable ["RQR_cancel", true];
			};
		};
		
		private _roundsCount = count _rounds;
		{
			if(_controller getVariable ["RQR_cancel", false]) exitWith {};
			
			_x params [
				["_targetNum", 10, [0]],
				["_lineList", [_forEachIndex % count _targets], [[]]],
				["_upTime", [3,5], [[]]],
				["_downTime", [3,7], [[]]]
			];
			private _roundNum = _forEachIndex + 1;
			[_roundNum, _roundsCount, _targetNum, _lineList, _upTime, _downTime] call _roundFunction;
			[10, _roundNum < _roundsCount] call _breakFunction;
		} forEach _rounds;
		
		{
			{ 
				_x animateSource ["terc", 1];
			} forEach _x;
		} forEach _targets;
		
		if(_controller getVariable ["RQR_cancel", false]) then {
			hintSilent parseText "<t size='1.5'>Test cancelled!</t>";
		} else {		
			//Show scores
			_stats params ["_targetsMissed", "_targetsHit", "_shotsFired", "_shotsHit"];
			private _totalTargets = _targetsHit + _targetsMissed;
			hintSilent parseText (
				[
					"<t size='1.5'>RESULTS</t>",
					format ["<t align='left'>Targets hit: </t><t align='right'>%1 / %2", _targetsHit, _totalTargets],
					format ["<t align='left'>Targets missed: </t><t align='right'>%1 / %2", _targetsMissed, _totalTargets],
					format ["<t align='left'>Success rate: </t><t align='right'>%1%2", 0.1*round (1000*_targetsHit/_totalTargets), "%"],
					"",
					format ["<t align='left'>Shots landed: </t><t align='right'>%1 / %2", _shotsHit, _shotsFired],
					format ["<t align='left'>Accuracy: </t><t align='right'>%1%2", 0.1*round (1000*_shotsHit/_shotsFired), "%"]
				] joinString "<br/>"
			);
		};
		
		_controller setVariable ["RQR_currentShooter", objNull, true];
	}, 
	[_lines], 
	6969, 
	true, 
	true, 
	"", 
	"isNull (_target getVariable [""RQR_currentShooter"", objNull])"
];

private _actionReset = _laneController addAction [
	"Cancel", 
	{
		params ["_controller", "_caller", "_id", "_targets"];
		_controller setVariable ["RQR_cancel", true];
	}, 
	_targets, 
	6967, 
	false, 
	true, 
	"", 
	"(_target getVariable [""RQR_currentShooter"", objNull]) isEqualTo _this"
];

 


Execution and parameters:

Spoiler

 


//==== Execution ====//
//Put this in init field of laptop
0 = [  
	this, 
	nameOfTrigger, 
	[50, 100, 150],
	[	
		[20, [1,2], [3,5], [3,7]], //Round 1
		[20, [2,3], [3,5], [3,7]], //Round 2
		[10, [3], [3,5], [3,7]]    //Round 3 etc.
	],  
	nameOfShootingPosition //(optional) 
] execVM "initRqrLane.sqf";

//==== Parameters ====//
/*
	Param 1: OBJECT - Controller: Actions are attached to this object.
	Param 2: TRIGGER - Lane trigger: Trigger should cover the area that is the firing lane and all targets that are to be used. Do not overlap with other lanes.
	Param 3: ARRAY of NUMBERS - Line spacing: Each elemnents represent a line of targets, the number is the approximate distance from the target to the shooting position.
	Param 4: ARRAY of ARRAYS - Rounds template: Array of arrays where each sub array represents 1 round and the individual settings for it. For more/fewer rounds add/remove as elements as seen below.
		[ 
			Number of targets in the round, 
			Line numbers to use (1 = first line, 2 = second line, etc.),
			[Min time targets are up, Max time targets are up],
			[Min time between targets, Max time between targest]
		]
	Param 5: OBJECT - Shooting position (OPTIONAL, Default: Same as Param 1): Object in location that player should shoot from (recommend shooting positions or shooting mats).
*/

 

 

 

Example Mission: 

 

Known Issues:

  • When targets are hit by a client in multiplayer they pop back up slightly before going back down. If anyone find a workaround for this please let me know.
Edited by mrcurry
Newer version

Share this post


Link to post
Share on other sites

Wow...this is more than I could of asked for.  Thank you for your assistance!  I will let you know if I come up with any snags.

Share this post


Link to post
Share on other sites

So I have manage to make everything work as far as the range goes. I changed the distances and removed one hint. The only two things that I can think of adding is randomizing from 2 to 3 targets at once and a notice board to track live targets hit. I will be adding your name near the shooting range also because you have done a lot. I will post pictures once completed if that is ok.

Share this post


Link to post
Share on other sites

So... something like this?
Only code changes (No editor changes)
 

Spoiler



//initRqrLane.sqf
#define SOUND_POPUP "a3\missions_f_beta\data\sounds\firing_drills\target_pop-up_large.wss"
#define SOUND_POPDOWN "a3\missions_f_beta\data\sounds\firing_drills\target_pop-down_large.wss"
#define SOUND_ACTIVE "a3\missions_f_beta\data\sounds\firing_drills\course_active.wss"
#define SOUND_CLEAR "a3\missions_f_beta\data\sounds\firing_drills\checkpoint_clear.wss"
#define SOUND_MISS "a3\missions_f_beta\data\sounds\firing_drills\drill_start.wss"
#define IMAGE_POPUP "a3\Ui_f\data\GUI\Rsc\RscDisplayMain\profile_player_ca.paa"
private _roundsDef = [
	[5, [1], [3,5], [3,7]],
	[5, [2], [3,5], [3,7]],
	[5, [3], [3,5], [3,7]]
];

params [
	["_laneController", objNull, [objNull]],
	["_laneTrigger", objNull, [objNull]],
	["_lineSpacing", [50, 100, 150], ["",[]]],
	["_roundTemplate", _roundsDef, [[]]],
	["_shootingPosition", objNull, [objNull]]
];

//Param check
if(isNull _laneController) exitWith { ["Invalid LaneController provided, must be a valid OBJECT, _this = %1", _this] call BIS_fnc_error; };
if(isNull _laneTrigger) exitWith { ["Invalid LaneTrigger provided, must be a valid TRIGGER, _this = %1", _this] call BIS_fnc_error; };
if(isNull _shootingPosition) then { _shootingPosition = _laneController; };

//Find targets
private _targets = allMissionObjects "TargetBase" select {_x inArea _laneTrigger};
//Sort found targets into the appropriate line
private _lines = []; 
{
	_lines pushBack []; 
} forEach _lineSpacing;

private _tolerance = 0.25;
{
	private _distance = _x distance2D _shootingPosition;
	private _lineIndex = -1;
	private _numLines = count _lineSpacing;
	{ 
		private _actualTolerance = _x * _tolerance;
		
		private _lowerIndex = _forEachIndex - 1;
		private _lowerBound = if(_lowerIndex < 0) then { _x - _actualTolerance } else { _x - _tolerance * (_x - (_lineSpacing select _lowerIndex)) };
		private _upperIndex = _forEachIndex + 1;
		private _upperBound = if(_upperIndex >= _numLines) then { _x + _actualTolerance } else { _x + _tolerance * ((_lineSpacing select _upperIndex) - _x) };
		if(_lowerBound <= _distance && _distance <= _upperBound) exitWith {
			_lineIndex = _forEachIndex;
		}; 
	} forEach _lineSpacing;
	
	if(_lineIndex >= 0) then {
		//Target is in line, initalize it
		_x setVariable ["RQR_controller", _laneController];
		_x animateSource ["terc", 1];
		_x addEventHandler [
			"HandleDamage", 
			{ 
				params ["_target", "_selection", "_damage", "_source", "_projectile", "_hitPartIndex", "_instigator"];
				if(_target animationPhase "terc" == 0) then {				
					if(_projectile isKindOf ["BulletBase", configFile >> "CfgAmmo"]) then {				
						playSound3D [SOUND_POPDOWN, _target, false, getPosASL _target, 1, 1, 300];
						_target spawn {
							waitUntil {(_this animationPhase "terc" == 1)};
							waitUntil {(_this animationPhase "terc" < 1)};
							_this animateSource ["terc", 1];
						};
					} else {
						_target animateSource ["terc", 0];
					};
				};
				0
			}
		];
		_x addEventHandler [
			"HitPart",
			{
				{
					_x params ["_target", "_shooter", "_bullet"];
					private _controller = _target getVariable "RQR_controller";
					if(_shooter == _controller getVariable ["RQR_currentShooter", objNull]) then {
						if(_target animationPhase "terc" == 0) then {				
							if(typeOf _bullet isKindOf ["BulletBase", configFile >> "CfgAmmo"]) then {											
								private _stats = _controller getVariable ["RQR_stats", []];
								_stats set [3, (_stats select 3) + 1];
							};
						};
					};
				} forEach _this;
			}
		];
		(_lines select _lineIndex) pushBack _x;
	};
} forEach _targets;

_laneController setVariable ["RQR_lineSpacing", _lineSpacing];
_laneController setVariable ["RQR_targets", _lines];
_laneController setVariable ["RQR_rounds", _roundTemplate];
_laneController setVariable ["RQR_shootPosition", _shootingPosition];


//Timed mode
private _actionStart = _laneController addAction [
	"Start timed test", 
	{
		params ["_controller", "_caller", "_id", "_args"];
		_controller setVariable ["RQR_currentShooter", _caller, true];
		_controller setVariable ["RQR_cancel", false, true];
		_caller setVariable ["RQR_currentController", _controller];
		// ============================
		// ======== Functions =========
		// ============================
			// ==== Break handler ====
			private _breakFunction = {
				params ["_time", "_hintReload"];
				private _t = 5 min ceil (_time/2);
				sleep _t;
				if(_hintReload) then {
					private _tLeft = _time - _t;
					while {_tLeft > 0 && !(_controller getVariable ["RQR_cancel", false])} do {
						hintSilent parseText (
							[
								format ["Next round in %1 seconds", _tLeft],
								"Reload"
							] joinString "<br/>"
						);
						sleep 1;
						_tLeft = _tLeft - 1;
					};
				} else {
					if(!(_controller getVariable ["RQR_cancel", false])) then {
						sleep (_time - _t);
					};
				};
			};
			
			// ==== Round handler ====
			private _roundFunction = {
				params ["_roundNum","_maxRounds", "_numTargets", "_lineIndexes", "_paramTimeUp", "_paramTimeDown"];
				_paramTimeUp params ["_timeUpMin", "_timeUpMax"];
				_paramTimeDown params ["_timeDownMin", "_timeDownMax"];
				
				private _timeUpDelta = _timeUpMax - _timeUpMin;	
				private _timeDownDelta = _timeDownMax - _timeDownMin;	
				
				//Select possible targets
				private _roundTargetList = [];
				private _ranges = [];
				{
					private _index = _x - 1;
					if(0 <= _index && _index < count _targets) then {
						_roundTargetList append (_targets select _index);
						_ranges pushBack format ["%1 m", _spacing select _index];
					};
				} forEach _lineIndexes;
				
				hint parseText (
					[
						format ["<t size='1.5'>Round %1 of %2</t>", _roundNum, _maxRounds],
						format ["<t align='left'>Targets: </t><t align='right'>%1</t>", _numTargets],
						format ["<t align='left'>Ranges: </t><t align='right'>%1</t>", _ranges joinString ", "],
						"",
						"<t size='1.5'>Get ready...</t>"
					] joinString "<br/>"
				);
				
				sleep 5;
				hintSilent parseText "<t size='1.5'>Begin!</t>";
				playSound3D [SOUND_ACTIVE, _controller, false, getPosASL _controller, 1, 1, 50];
				private _firedEH = _caller addEventHandler [
					"FiredMan", 
					{
						params ["_unit", "_weapon"];
						private _ctrl = _unit getVariable "RQR_currentController";
						_stats = _ctrl getVariable "RQR_stats";
						_stats set [2, (_stats select 2)+1];
					}
				];
				sleep 2;
				
				private _i = 0;
				private _misses = 0;
				private _hits = 0;
				while {_i < _numTargets && !(_controller getVariable ["RQR_cancel", false])} do {
					//Pick 1-3 random targets
					private _numPopups = (1 + floor random 3) min (_numTargets - _i);
					private _availablePopups = +_roundTargetList;
					private _popups = [];
					
					while {count _popups < _numPopups && count _availablePopups > 0} do {
						_popups pushBack (_availablePopups deleteAt floor random count _availablePopups);
					};
					private _timeUp = _timeUpMin + random _timeUpDelta + 0.5 * _numPopups;
					
					//Pop up
					{	
						_x animateSource ["terc", 0];
						playSound3D [SOUND_POPUP, _x, false, getPosASL _x, 1, 1, 300]; 
					} forEach _popups;
					sleep 0.25;
					
					//Wait for hits
					private _timeout = time + _timeUp;
					waitUntil {
						private _str = (
							_popups apply {
								private _c = if((_x animationPhase "terc") == 1) then {"#00FF00"} else {"#FFFFFF"};
								format ["<img size='2' color='%1' image='%2'/>", _c, IMAGE_POPUP]
							}
						) joinString "";
						hintSilent parseText (" " + _str + " " );
						time > _timeout 
						OR 
						{(_x animationPhase "terc") == 1} count _popups == _numPopups
					};
					
					private _str = (
							_popups apply {
								private _c = if((_x animationPhase "terc") == 1) then {"#00FF00"} else {"#FF0000"};
								format ["<img size='2' color='%1' image='%2'/>", _c, IMAGE_POPUP]
							}
						) joinString "";
						hintSilent parseText (" " + _str + " " );
					
					private _hit = {(_x animationPhase "terc") == 1} count _popups;
					private _missed = _numPopups - _hit;
					_hits = _hits + _hit;
					_misses = _misses + _missed;
					
					//Pop down
					{
						_x animateSource ["terc", 1];
						playSound3D [SOUND_POPDOWN, _x, false, getPosASL _x, 1, 1, 300];
					} forEach _popups;
					
					if(time > _timeout) then {
						playSound3D [SOUND_MISS, _controller, false, getPosASL _controller, 2, 1, 50];
					};
					
					_i = _i + _numPopups;
					if(_i < _numTargets) then {
						sleep _timeDownMin; 
						hintSilent "";
						sleep random _timeDownDelta;
					} else {
						hintSilent "";
					};
				};
				
				_stats set [0, (_stats select 0) + _misses];
				_stats set [1, (_stats select 1) + _hits];
				//Play end sound
				_caller removeEventHandler ["FiredMan",_firedEH];
				playSound3D [SOUND_CLEAR, _controller, false, getPosASL _controller, 2, 1, 50];
				hintSilent parseText (
					[
						"<t size='1.5'>Round complete!</t>",
						format ["<t align='left'># targets: </t><t align='right'>%1", _numTargets],
						format ["<t align='left'>Targets missed: </t><t align='right'>%1", _misses],
						format ["<t align='left'>Targets hit: </t><t align='right'>%1", _hits]
					] joinString "<br/>"
				);
			};
		// ==================================
		// =========== Action Main ==========
		// ==================================
		private _spacing = _controller getVariable "RQR_lineSpacing";
		private _targets = _controller getVariable "RQR_targets";
		private _rounds = _controller getVariable "RQR_rounds";
		private _stats = [0,0,0,0]; //[Targets missed, Targets hit, ShotsFired, ShotsHit]
		_controller setVariable ["RQR_stats", _stats];
		
		//Thread for checking if still in shooting position
		private _checkShooter_thread = [_controller, _caller, _controller getVariable "RQR_shootPosition"] spawn {
			params ["_ctrl", "_shooter", "_shootFrom"];
			waitUntil {
				sleep 0.5;
				isNull (_ctrl getVariable ["RQR_currentShooter", objNull]) 
				OR 
				_shooter distance _shootFrom > 10
			};
			
			if( !isNull (_ctrl getVariable ["RQR_currentShooter", objNull]) ) then {
				_ctrl setVariable ["RQR_cancel", true];
			};
		};
		
		private _roundsCount = count _rounds;
		{
			if(_controller getVariable ["RQR_cancel", false]) exitWith {};
			
			_x params [
				["_targetNum", 10, [0]],
				["_lineList", [_forEachIndex % count _targets], [[]]],
				["_upTime", [3,5], [[]]],
				["_downTime", [3,7], [[]]]
			];
			private _roundNum = _forEachIndex + 1;
			[_roundNum, _roundsCount, _targetNum, _lineList, _upTime, _downTime] call _roundFunction;
			[10, _roundNum < _roundsCount] call _breakFunction;
		} forEach _rounds;
		
		{
			{ 
				_x animateSource ["terc", 1];
			} forEach _x;
		} forEach _targets;
		
		if(_controller getVariable ["RQR_cancel", false]) then {
			hintSilent parseText "<t size='1.5'>Test cancelled!</t>";
		} else {		
			//Show scores
			_stats params ["_targetsMissed", "_targetsHit", "_shotsFired", "_shotsHit"];
			private _totalTargets = _targetsHit + _targetsMissed;
			hintSilent parseText (
				[
					"<t size='1.5'>RESULTS</t>",
					format ["<t align='left'>Targets hit: </t><t align='right'>%1 / %2", _targetsHit, _totalTargets],
					format ["<t align='left'>Targets missed: </t><t align='right'>%1 / %2", _targetsMissed, _totalTargets],
					format ["<t align='left'>Success rate: </t><t align='right'>%1%2", 0.1*round (1000*_targetsHit/_totalTargets), "%"],
					"",
					format ["<t align='left'>Shots landed: </t><t align='right'>%1 / %2", _shotsHit, _shotsFired],
					format ["<t align='left'>Accuracy: </t><t align='right'>%1%2", 0.1*round (1000*_shotsHit/_shotsFired), "%"]
				] joinString "<br/>"
			);
		};
		
		_controller setVariable ["RQR_currentShooter", objNull, true];
	}, 
	[_lines], 
	6969, 
	true, 
	true, 
	"", 
	"isNull (_target getVariable [""RQR_currentShooter"", objNull])"
];

private _actionReset = _laneController addAction [
	"Cancel", 
	{
		params ["_controller", "_caller", "_id", "_targets"];
		_controller setVariable ["RQR_cancel", true];
	}, 
	_targets, 
	6967, 
	false, 
	true, 
	"", 
	"(_target getVariable [""RQR_currentShooter"", objNull]) isEqualTo _this"
];


 

 

Share this post


Link to post
Share on other sites

It does. The timing and random of the targets work perfectly. The red and green targets are great but the intent was to display on a notice board or any other blank assest. This was only to aide an drill instructor in tracking multiple people testing due to the hints being local only to those who initiates it. 

Share this post


Link to post
Share on other sites

Just so we're clear do you want the instructor to be able to review the entire round or just the current bunch of targets?

Share this post


Link to post
Share on other sites

Example:

 

Drill Instructor has to test three people.  They could start the test but the results xx/50 would be posted on a sign.

Share this post


Link to post
Share on other sites

Right, I'll see what I can do. Dynamic text on noticeboards, tbh not sure that's possible in Arma but I'll look into it and/or an alternative.   If anyone else got ideas feel free to share. 

Share this post


Link to post
Share on other sites

I could make some  individual pictures like “01”, “02”, “43”, etc and have them load based off the final score.

Share this post


Link to post
Share on other sites

Was there any resolution to this post? I am looking to add the same display feature to my range.

Share this post


Link to post
Share on other sites
7 hours ago, I.Reyes said:

Was there any resolution to this post? I am looking to add the same display feature to my range.

 

No, at least not with just code.

 

Best you can do is make a bunch of jpgs with different numbers (0-9) on them and load the appropriate one. If you want to larger numbers (>9) just use multiple billboards/screens to represent each digit.

Writing a parser for that kind of setup shouldn't be to difficult, if you need one let me know and I'll throw something together.

Share this post


Link to post
Share on other sites
1 hour ago, mrcurry said:

 

No, at least not with just code.

 

Best you can do is make a bunch of jpgs with different numbers (0-9) on them and load the appropriate one. If you want to larger numbers (>9) just use multiple billboards/screens to represent each digit.

Writing a parser for that kind of setup shouldn't be to difficult, if you need one let me know and I'll throw something together.

I more or less into that conclusion earlier today. I set up a billboard and I have the lanes pre-numbered. I also have the jpg for each individual number. How do I get the scoreboard to actually display the image based on the total?

Share this post


Link to post
Share on other sites
12 hours ago, I.Reyes said:

I more or less into that conclusion earlier today. I set up a billboard and I have the lanes pre-numbered. I also have the jpg for each individual number. How do I get the scoreboard to actually display the image based on the total?

 

I threw something together that should do it. 

Spoiler

/*
	File: fn_setDisplayNumber.sqf
	Description: Sets a display or set of displays to show the given positive integer. More details in header.

	Parameters: [_value, _displays]
	_value = Number, value to show on screen.
	_displays = Array of Objects / Array of Arrays. Each element is either the display object or [display object, selection index]
				Make sure the displays are sorted according to how they appear ingame, left-to-right.

	Usage:
		Example 1 - Two different display objects.
			[ 
				10, 
				[board10, board1] 
			] call TAG_fnc_setDisplayNumber;
		Result
			1 on board10, 0 on board1
		
		Example 2 - Same as above but with selection index other than 0.
			[ 
				10, 
				[ [board10, 1], [board1,1] ] 
			] call TAG_fnc_setDisplayNumber;
		Result
			1 on board10 selection 1, 0 on board1 selection 1

		Example 3 - Multiple displays on the same object.
			[ 
				10, 
				[ [dualMonitorStand1, 0], [dualMonitorStand1, 1] ] 
			] call TAG_fnc_setDisplayNumber;
		Result
			1 on board10 selection 1, 0 on board1 selection 1

	Returns: Array of digits set to each screen, order matches displays parameter. 
		Example:
			[666, [d1,d2,d3,d4]] call TAG_fnc_setDisplayNumber; 
		Returns:
			[0,6,6,6]
*/

params [
	["_value", 0],
	["_displays", []]
];

if(_displays isEqualTo []) exitWith { ["No displays passed to function."] call BIS_fnc_error; };

//Each texture should be on the same index as the number it represents: 0.jpg would be index 0, 1.jpg would be index 1, etc.
//TODO, make images with numbers instead of procedural colours.
private _numTex = [
	"#(rgb,8,8,3)color(1,0,0,1)",
	"#(rgb,8,8,3)color(1,0.222222,0,1)",
	"#(rgb,8,8,3)color(1,0.444444,0,1)",
	"#(rgb,8,8,3)color(1,0.666667,0,1)",
	"#(rgb,8,8,3)color(1,0.888889,0,1)",
	"#(rgb,8,8,3)color(0.888889,1,0,1)",
	"#(rgb,8,8,3)color(0.666667,1,0,1)",
	"#(rgb,8,8,3)color(0.444444,1,0,1)",
	"#(rgb,8,8,3)color(0.222222,1,0,1)",
	"#(rgb,8,8,3)color(0,1,0,1)"
];

private _numDigits = count _displays;
private _ceiling = 10^_numDigits;
private _value = round _value min (_ceiling-1);

private ["_base", "_denominator", "_digitValue"];

private _digits = [];
{
	_base = 10^(_numDigits - _forEachIndex);
	_denominator = _base/10;
	_digitValue = (floor (_value/_denominator)) % _base;
	
	_digits pushBack _digitValue;

	_x params [["_o", objNull], ["_sel", 0]];
	_o setObjectTextureGlobal [_sel, _numTex#_digitValue];
} forEach _displays;

_digits;

 

 

Note: In the above version I'm using colour-coded procedural textures to represent the numbers. Simply update _numTex with the paths to the appropriate images. Make sure the paths are relative to the mission root and that the images are sorted in ascending order e.g. if your images are stored in root\img then it should look like this:

private _numTex = [
	"img\0.jpg",
	"img\1.jpg",
	"img\2.jpg",
	"img\3.jpg",
	"img\4.jpg",
	"img\5.jpg",
	"img\6.jpg",
	"img\7.jpg",
	"img\8.jpg",
	"img\9.jpg"
];

By the way, if you don't mind please share your pictures. This has more applications than just range scores and it would be nice to have both the code and a set of nice working pictures in the same place. (If you don't wanna that's ok too ^^)

Edited by mrcurry
Edit: Clarification and cleaned up redundant comments
  • Like 1

Share this post


Link to post
Share on other sites
17 hours ago, mrcurry said:

 

I threw something together that should do it. 

  Hide contents


/*
	File: fn_setDisplayNumber.sqf
	Description: Sets a display or set of displays to show the given positive integer. More details in header.

	Parameters: [_value, _displays]
	_value = Number, value to show on screen.
	_displays = Array of Objects / Array of Arrays. Each element is either the display object or [display object, selection index]
				Make sure the displays are sorted according to how they appear ingame, left-to-right.

	Usage:
		Example 1 - Two different display objects.
			[ 
				10, 
				[board10, board1] 
			] call TAG_fnc_setDisplayNumber;
		Result
			1 on board10, 0 on board1
		
		Example 2 - Same as above but with selection index other than 0.
			[ 
				10, 
				[ [board10, 1], [board1,1] ] 
			] call TAG_fnc_setDisplayNumber;
		Result
			1 on board10 selection 1, 0 on board1 selection 1

		Example 3 - Multiple displays on the same object.
			[ 
				10, 
				[ [dualMonitorStand1, 0], [dualMonitorStand1, 1] ] 
			] call TAG_fnc_setDisplayNumber;
		Result
			1 on board10 selection 1, 0 on board1 selection 1

	Returns: Array of digits set to each screen, order matches displays parameter. 
		Example:
			[666, [d1,d2,d3,d4]] call TAG_fnc_setDisplayNumber; 
		Returns:
			[0,6,6,6]
*/

params [
	["_value", 0],
	["_displays", []]
];

if(_displays isEqualTo []) exitWith { ["No displays passed to function."] call BIS_fnc_error; };

//Each texture should be on the same index as the number it represents: 0.jpg would be index 0, 1.jpg would be index 1, etc.
//TODO, make images with numbers instead of procedural colours.
private _numTex = [
	"#(rgb,8,8,3)color(1,0,0,1)",
	"#(rgb,8,8,3)color(1,0.222222,0,1)",
	"#(rgb,8,8,3)color(1,0.444444,0,1)",
	"#(rgb,8,8,3)color(1,0.666667,0,1)",
	"#(rgb,8,8,3)color(1,0.888889,0,1)",
	"#(rgb,8,8,3)color(0.888889,1,0,1)",
	"#(rgb,8,8,3)color(0.666667,1,0,1)",
	"#(rgb,8,8,3)color(0.444444,1,0,1)",
	"#(rgb,8,8,3)color(0.222222,1,0,1)",
	"#(rgb,8,8,3)color(0,1,0,1)"
];

private _numDigits = count _displays;
private _ceiling = 10^_numDigits;
private _value = round _value min (_ceiling-1);

private ["_base", "_denominator", "_digitValue"];

private _digits = [];
{
	_base = 10^(_numDigits - _forEachIndex);
	_denominator = _base/10;
	_digitValue = (floor (_value/_denominator)) % _base;
	
	_digits pushBack _digitValue;

	_x params [["_o", objNull], ["_sel", 0]];
	_o setObjectTextureGlobal [_sel, _numTex#_digitValue];
} forEach _displays;

_digits;

 

 

Note: In the above version I'm using colour-coded procedural textures to represent the numbers. Simply update _numTex with the paths to the appropriate images. Make sure the paths are relative to the mission root and that the images are sorted in ascending order e.g. if your images are stored in root\img then it should look like this:


private _numTex = [
	"img\0.jpg",
	"img\1.jpg",
	"img\2.jpg",
	"img\3.jpg",
	"img\4.jpg",
	"img\5.jpg",
	"img\6.jpg",
	"img\7.jpg",
	"img\8.jpg",
	"img\9.jpg"
];

By the way, if you don't mind please share your pictures. This has more applications than just range scores and it would be nice to have both the code and a set of nice working pictures in the same place. (If you don't wanna that's ok too ^^)

 

 

I placed the sqf file in with the rest of my scripts. I also set the images in place as per the file structure you provided. This is the board where I would like to display the scores. ( http://prntscr.com/mbv4a8 ) based on the explanation I would use example3 to make it this board work correctly:

 

Example 3 - Multiple displays on the same object. [ 10, [ [dualMonitorStand1, 0], [dualMonitorStand1, 1] ] ] call TAG_fnc_setDisplayNumber; Result 1 on board10 selection 1, 0 on board1 selection 1

 

could you please explain where I would place the call to action.

Share this post


Link to post
Share on other sites

Being a scripting newb but wanting to put down a similar range for my Unit is there a way to have a master control computer that resets the course and also then shows the score so that the trainer can control the fire lanes and gather scoring.

 

Many thanks in advance

Share this post


Link to post
Share on other sites

I know this topic is getting old, so let me know if I should start a new one instead. 

I tried to see if I could implement the scripting from here to a range I have made.

It has 6 lanes with targets for each 100 meters, with the closest at 100 meters and the farthest at 1200 meters.

I was hoping I could get it to randomize between ALL targets in all 3 rounds, but I guess I am too much of a noob to understand the script.

I did this:

["_lineSpacing", [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200], ["",[]]],

but I guess it takes more than that.

I also noticed that it pops up more than multiple targets at the same time, where I want only one at a time.

Another thing is that it apparently pops up with the closest target (at 100m) even when it should only show those at 200 and 300m...

 

Any help will be appreciated 😉

Share this post


Link to post
Share on other sites

@SnowmanDK

Well it's been awhile since I worked on this... Time sure flies.

 

I've update the script to support parameters for number of targets per pop up and I've added a more free form exercise mode as well (right now not very configurable).

See the new version at the bottom of this post.
 

Before we jump into the issues you're having a quick disclaimer. I strongly recommend using the execution method in the parameters. Only edit the script code if you know what you're doing. Any changes to the script is on you.

If you do not know what you're doing stick to using and editing the execution example(s). No hard feelings just a good thumb rule for working with other people's work.

 

Now with that out of the way let's get down to business.

The third parameter is only used to define the different target groups (or lines). You have modified it appropriately for your range but this parameter has nothing to do with the configuration of the rounds.

The fourth parameter is what defines the rules for each round. The fourth parameter is an array of arrays that looks like this:

Spoiler

[
	//Round 1
	[
		20, 	//Number of targets to popup in this round
		[1,2],	//Which target lines to use
		[3,5], 	//Min and max time for target to be up
		[3,7],  //Min and max time between a new set of targets
		[1,3]	//Min and max number of target to pop up at the same time (new with the latest version)
	],
	//Round 2
	[
		20, 
		[2,3], 
		[3,5],	 
		[3,7], 
		[1]		//In the fifth param only: If min and max is the same we can also use just one number: [1] and [1,1] has the same effect.
	] 
	//Round 3 ... etc etc
]

 

 

So to achieve 3 rounds with all targets on a range with 12 lines at 100 m intervals with only 1 target being used at once the whole execution would look something like this:

Spoiler

0 = [  
	this, 
	nameOfTrigger, 
	[ 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200 ],
	[	
		[
			10, 
			[1,2,3,4,5,6,7,8,9,10,11,12],
			[3,5], 
			[3,7], 
			[1]
		], 
		[
			10, 
			[1,2,3,4,5,6,7,8,9,10,11,12],
			[3,5], 
			[3,7], 
			[1]
		], 
		[
			10, 
			[1,2,3,4,5,6,7,8,9,10,11,12],
			[3,5], 
			[3,7], 
			[1]
		] 
	],  
	nameOfShootingPosition
] execVM "initRqrLane.sqf";

 

 

==========================================================================

NEW VERSION BELOW

==========================================================================

Updated version: *Insert arbitrary version number here*

 

What's new:

  • Added "Free exercise" mode
  • Added ability to configure number of targets to pop up each time
  • Added ability to review the results of the 10 last trials (during the given session). (Useful for Rangemasters and competition organisers.)

Known Issues:

  • When targets are hit by a client in multiplayer they will after a short while pop back up slightly before going back down. If anyone find a workaround for this please let me know.

Example mission: 

https://www.dropbox.com/s/uivwyl8z9bfdv8k/firingRangeTest.Malden.7z?dl=0

 

Full script:

Spoiler

/*
//==== Execution ====//
//Put this in init field of laptop
0 = [  
	this, 
	nameOfTrigger, 
	[
		50, 	//Average range of target group 1 or "Line 1"
		100, 	//Average range of target group 2 or "Line 2"
		150 	//Average range of target group 3 or "Line 3"
				//As many as you want may be added, sort the ranges incrementally
	],
	[	
		//Round 1
		[
			20, 	//Number of targets 
			[1,2],	//Target line(s) to use
			[3,5], 	//[Min time targets are up, Max time targets are up]
			[3,7],  //[Min time between targets, Max time between targest],
			[1,3]	//[Min numbers of targets to pop at once time, Max numbers of targets to pop up at once] //Default [1,3]
		], 
		//Round 2
		[
			20, 
			[2,3], 
			[3,5], 
			[3,7], 
			[1,2]
		], 
		//Round 3
		[
			10, 
			[3], 
			[3,5], 
			[3,7], 
			[1]		//In the fifth param only: If min and max is the same we can also use just one number: [1] and [1,1] has the same effect.
		]   
		//Etc
	],  
	nameOfShootingPosition //(optional) 
] execVM "initRqrLane.sqf";

//==== Parameters ====//

	Param 1: OBJECT - Controller: Actions are attached to this object.
	Param 2: TRIGGER - Lane trigger: Trigger should cover the area that is the firing lane and all targets that are to be used. Do not overlap with other lanes.
	Param 3: ARRAY of NUMBERS - Line spacing: Each element represent a line of targets, the number is the approximate distance from the target to the shooting position.
	Param 4: ARRAY of ARRAYS - Rounds template: Array of arrays where each sub array represents 1 round and the individual settings for it. For more/fewer rounds add/remove as elements as seen below.
		[ 
			Number of targets in the round, 
			Line numbers to use (1 = first line, 2 = second line, etc.),
			[Min time targets are up, Max time targets are up],
			[Min time between targets, Max time between targest],
			[Min numbers of targets to pop at once time, Max numbers of targets to pop up at once] //Default [1,3]
		]
	Param 5: OBJECT - Shooting position (OPTIONAL, Default: Same as Param 1): Object in location that player should shoot from (recommend shooting positions or shooting mats).
*/

#define SOUND_POPUP "a3\missions_f_beta\data\sounds\firing_drills\target_pop-up_large.wss"
#define SOUND_POPDOWN "a3\missions_f_beta\data\sounds\firing_drills\target_pop-down_large.wss"
#define SOUND_ACTIVE "a3\missions_f_beta\data\sounds\firing_drills\course_active.wss"
#define SOUND_CLEAR "a3\missions_f_beta\data\sounds\firing_drills\checkpoint_clear.wss"
#define SOUND_MISS "a3\missions_f_beta\data\sounds\firing_drills\drill_start.wss"
#define IMAGE_POPUP "a3\Ui_f\data\GUI\Rsc\RscDisplayMain\profile_player_ca.paa"
private _roundsDef = [
	[5, [1], [3,5], [3,7]],
	[5, [2], [3,5], [3,7]],
	[5, [3], [3,5], [3,7]]
];

params [
	["_laneController", objNull, [objNull]],
	["_laneTrigger", objNull, [objNull]],
	["_lineSpacing", [50, 100, 150], ["",[]]],
	["_roundTemplate", _roundsDef, [[]]],
	["_shootingPosition", objNull, [objNull]]
];

//Param check
if(isNull _laneController) exitWith { ["Invalid LaneController provided, must be a valid OBJECT, _this = %1", _this] call BIS_fnc_error; };
if(isNull _laneTrigger) exitWith { ["Invalid LaneTrigger provided, must be a valid TRIGGER, _this = %1", _this] call BIS_fnc_error; };
if(isNull _shootingPosition) then { _shootingPosition = _laneController; };

//Find targets
private _targets = allMissionObjects "TargetBase" select {_x inArea _laneTrigger};
//Sort found targets into the appropriate line
private _lines = []; 
{
	_lines pushBack []; 
} forEach _lineSpacing;

private _tolerance = 0.25;
{
	private _distance = _x distance2D _shootingPosition;
	private _lineIndex = -1;
	private _numLines = count _lineSpacing;
	{ 
		private _actualTolerance = _x * _tolerance;
		
		private _lowerIndex = _forEachIndex - 1;
		private _lowerBound = if(_lowerIndex < 0) then { _x - _actualTolerance } else { _x - _tolerance * (_x - (_lineSpacing select _lowerIndex)) };
		private _upperIndex = _forEachIndex + 1;
		private _upperBound = if(_upperIndex >= _numLines) then { _x + _actualTolerance } else { _x + _tolerance * ((_lineSpacing select _upperIndex) - _x) };
		if(_lowerBound <= _distance && _distance <= _upperBound) exitWith {
			_lineIndex = _forEachIndex;
		}; 
	} forEach _lineSpacing;
	
	if(_lineIndex >= 0) then {
		//Target is in line, initalize it
		_x setVariable ["RQR_controller", _laneController];
		_x animateSource ["terc", 1];
		_x addEventHandler [
			"HandleDamage", 
			{ 
				params ["_target", "_selection", "_damage", "_source", "_projectile", "_hitPartIndex", "_instigator"];
				if(_target animationPhase "terc" == 0) then {				
					if(_projectile isKindOf ["BulletBase", configFile >> "CfgAmmo"]) then {				
						playSound3D [SOUND_POPDOWN, _target, false, getPosASL _target, 1, 1, 300];
						_target spawn {
							waitUntil {(_this animationPhase "terc" == 1)};
							waitUntil {(_this animationPhase "terc" < 1)};
							_this animateSource ["terc", 1];
						};
					} else {
						_target animateSource ["terc", 0];
					};
				};
				0
			}
		];
		_x addEventHandler [
			"HitPart",
			{
				{
					_x params ["_target", "_shooter", "_bullet"];
					private _controller = _target getVariable "RQR_controller";
					if(_shooter == _controller getVariable ["RQR_currentShooter", objNull]) then {
						if(_target animationPhase "terc" == 0) then {				
							if(typeOf _bullet isKindOf ["BulletBase", configFile >> "CfgAmmo"]) then {											
								private _stats = _controller getVariable ["RQR_stats", []];
								_stats set [3, (_stats select 3) + 1];
							};
						};
					};
				} forEach _this;
			}
		];
		(_lines select _lineIndex) pushBack _x;
	};
} forEach _targets;

_laneController setVariable ["RQR_lineSpacing", _lineSpacing];
_laneController setVariable ["RQR_targets", _lines];
_laneController setVariable ["RQR_rounds", _roundTemplate];
_laneController setVariable ["RQR_shootPosition", _shootingPosition];


//Timed mode
private _actionTimedTargets = _laneController addAction [
	"Timed Target Trial", 
	{
		params ["_controller", "_caller", "_id", "_args"];
		_controller setVariable ["RQR_currentShooter", _caller, true];
		_controller setVariable ["RQR_cancel", false, true];
		_caller setVariable ["RQR_currentController", _controller];
		// ============================
		// ======== Functions =========
		// ============================
			// ==== Score storage handler ==== //
			private _storeFunction = {
				/*
				params [
					"_name", 
					"_totalTargets", 
					"_targetsHit", 
					"_targetsMissed", 
					"_targethitRate", 
					"_shotsHit", 
					"_shotsFired", 
					"_accuracy"
				];
				*/

				private _recentTests = _controller getVariable ["RQR_data_recentTests", []];

				_recentTests pushBack _this;

				if(count _recentTests > 10) then {
					_recentTests deleteAt 0;
				};

				_controller setVariable ["RQR_data_recentTests", _recentTests, true];
			};

			// ==== Break handler ====
			private _breakFunction = {
				params ["_time", "_hintReload"];
				private _t = 5 min ceil (_time/2);
				sleep _t;
				if(_hintReload) then {
					private _tLeft = _time - _t;
					while {_tLeft > 0 && !(_controller getVariable ["RQR_cancel", false])} do {
						hintSilent parseText (
							[
								"<t size='1.5'>Reload</t>",
								"",
								format ["Next round in %1 seconds", _tLeft]
							] joinString "<br/>"
						);
						sleep 1;
						_tLeft = _tLeft - 1;
					};
				} else {
					if(!(_controller getVariable ["RQR_cancel", false])) then {
						sleep (_time - _t);
					};
				};
			};
			
			// ==== Round handler ====
			private _roundFunction = {
				params ["_roundNum","_maxRounds", "_numTargets", "_lineIndexes", "_paramTimeUp", "_paramTimeDown", "_paramNumTargetPerPop"];
				_paramTimeUp params ["_timeUpMin", "_timeUpMax"];
				_paramTimeDown params ["_timeDownMin", "_timeDownMax"];
				
				_targetsPerPopMin = _paramNumTargetPerPop param [0, 1];
				_targetsPerPopMin = _targetsPerPopMin max 1;
				_targetsPerPopMax = _paramNumTargetPerPop param [1, _targetsPerPopMin];

				private _timeUpDelta = _timeUpMax - _timeUpMin;	
				private _timeDownDelta = _timeDownMax - _timeDownMin;
				private _targetsPerPopDelta = _targetsPerPopMax - _targetsPerPopMin + 1;
				
				//Select possible targets
				private _roundTargetList = [];
				private _ranges = [];
				{
					private _index = _x - 1;
					if(0 <= _index && _index < count _targets) then {
						_roundTargetList append (_targets select _index);
						_ranges pushBack format ["%1 m", _spacing select _index];
					};
				} forEach _lineIndexes;
				
				hint parseText (
					[
						format ["<t size='1.5'>Round %1 of %2</t>", _roundNum, _maxRounds],
						format ["<t align='left'>Targets: </t><t align='right'>%1</t>", _numTargets],
						format ["<t align='left'>Ranges: </t><t align='right'>%1</t>", _ranges joinString ", "],
						"",
						"<t size='1.5'>Get ready...</t>"
					] joinString "<br/>"
				);
				
				sleep 5;
				hintSilent parseText "<t size='1.5'>Begin!</t>";
				playSound3D [SOUND_ACTIVE, _controller, false, getPosASL _controller, 1, 1, 50];
				private _firedEH = _caller addEventHandler [
					"FiredMan", 
					{
						params ["_unit", "_weapon"];
						private _ctrl = _unit getVariable "RQR_currentController";
						_stats = _ctrl getVariable "RQR_stats";
						_stats set [2, (_stats select 2)+1];
					}
				];
				sleep 2;
				
				private _i = 0;
				private _misses = 0;
				private _hits = 0;
				while {_i < _numTargets && !(_controller getVariable ["RQR_cancel", false])} do {
					//Pick 1-3 random targets
					private _numPopups = (_targetsPerPopMin + floor random _targetsPerPopDelta) min (_numTargets - _i);
					private _availablePopups = +_roundTargetList;
					private _popups = [];
					
					while {count _popups < _numPopups && count _availablePopups > 0} do {
						_popups pushBack (_availablePopups deleteAt floor random count _availablePopups);
					};
					private _timeUp = _timeUpMin + random _timeUpDelta + 0.5 * _numPopups;

					//Pop up
					{	
						_x animateSource ["terc", 0];
						playSound3D [SOUND_POPUP, _x, false, getPosASL _x, 1, 1, 300]; 
					} forEach _popups;
					waitUntil {
						{(_x animationPhase "terc") > 0} count _popups == 0
						OR
						(_controller getVariable ["RQR_cancel", false])
					};
					
					//Wait for hits
					private _timeout = time + _timeUp;
					waitUntil {
						private _str = (
							_popups apply {
								private _c = if((_x animationPhase "terc") == 1) then {"#00FF00"} else {"#FFFFFF"};
								format ["<img size='2' color='%1' image='%2'/>", _c, IMAGE_POPUP]
							}
						) joinString "";
						hintSilent parseText (" " + _str + " ");
						time > _timeout
						OR 
						{(_x animationPhase "terc") > 0} count _popups == _numPopups
						OR
						(_controller getVariable ["RQR_cancel", false])
					};
					
					private _str = (
						_popups apply {
							private _c = if((_x animationPhase "terc") > 0) then {"#00FF00"} else {"#FF0000"};
							format ["<img size='2' color='%1' image='%2'/>", _c, IMAGE_POPUP]
						}
					) joinString "";
					hintSilent parseText (" " + _str + " " );
					
					private _hit = {(_x animationPhase "terc") > 0} count _popups;
					private _missed = _numPopups - _hit;
					_hits = _hits + _hit;
					_misses = _misses + _missed;
					
					//Pop down
					{
						_x animateSource ["terc", 1];
						playSound3D [SOUND_POPDOWN, _x, false, getPosASL _x, 1, 1, 300];
					} forEach _popups;

					waitUntil {
						{(_x animationPhase "terc") < 1} count _popups == 0
						OR
						(_controller getVariable ["RQR_cancel", false])
					};

					if(time > _timeout) then {
						playSound3D [SOUND_MISS, _controller, false, getPosASL _controller, 2, 1, 50];
					};
					
					_i = _i + _numPopups;
					if(_i < _numTargets) then {
						sleep _timeDownMin; 
						hintSilent "";
						sleep random _timeDownDelta;
					} else {
						hintSilent "";
					};
				};
				
				_stats set [0, (_stats select 0) + _misses];
				_stats set [1, (_stats select 1) + _hits];
				//Play end sound
				_caller removeEventHandler ["FiredMan",_firedEH];
				playSound3D [SOUND_CLEAR, _controller, false, getPosASL _controller, 2, 1, 50];
				hintSilent parseText (
					[
						"<t size='1.5'>Round complete!</t>",
						format ["<t align='left'># targets: </t><t align='right'>%1", _numTargets],
						format ["<t align='left'>Targets missed: </t><t align='right'>%1", _misses],
						format ["<t align='left'>Targets hit: </t><t align='right'>%1", _hits]
					] joinString "<br/>"
				);
			};
		// ==================================
		// =========== Action Main ==========
		// ==================================
		private _spacing = _controller getVariable "RQR_lineSpacing";
		private _targets = _controller getVariable "RQR_targets";
		private _rounds = _controller getVariable "RQR_rounds";
		private _stats = [0,0,0,0]; //[Targets missed, Targets hit, ShotsFired, ShotsHit]
		_controller setVariable ["RQR_stats", _stats];
		
		//Thread for checking if shooter still is in shooting position
		private _checkShooter_thread = [_controller, _caller, _controller getVariable "RQR_shootPosition"] spawn {
			params ["_ctrl", "_shooter", "_shootFrom"];
			waitUntil {
				sleep 0.5;
				isNull (_ctrl getVariable ["RQR_currentShooter", objNull]) 
				OR 
				_shooter distance _shootFrom > 10
			};
			
			if( !isNull (_ctrl getVariable ["RQR_currentShooter", objNull]) ) then {
				_ctrl setVariable ["RQR_cancel", true];
			};
		};
		
		private _roundsCount = count _rounds;
		{
			if(_controller getVariable ["RQR_cancel", false]) exitWith {};
			
			_x params [
				["_targetNum", 10, [0]],
				["_lineList", [_forEachIndex % count _targets], [[]]],
				["_upTime", [3,5], [[]]],
				["_downTime", [3,7], [[]]],
				["_targetsPerPop", [1,3], [[],0]]
			];
			private _roundNum = _forEachIndex + 1;
			[_roundNum, _roundsCount, _targetNum, _lineList, _upTime, _downTime, _targetsPerPop] call _roundFunction;
			[10, _roundNum < _roundsCount] call _breakFunction;
		} forEach _rounds;
		
		{
			{ 
				_x animateSource ["terc", 1];
			} forEach _x;
		} forEach _targets;
		
		if(_controller getVariable ["RQR_cancel", false]) then {
			hintSilent parseText "<t size='1.5'>Test cancelled!</t>";
		} else {		
			//Show scores
			_stats params ["_targetsMissed", "_targetsHit", "_shotsFired", "_shotsHit"];
			
			private _totalTargets = _targetsHit + _targetsMissed;
			private _targethitRate = 0.1*round (1000*_targetsHit/_totalTargets);
			private _accuracy = 0.1*round (1000*_shotsHit/_shotsFired);

			[name _caller, _totalTargets, _targetsHit, _targetsMissed, _targethitRate, _shotsHit, _shotsFired, _accuracy] call _storeFunction;

			hintSilent parseText (
				[
					"<t size='1.5'>RESULTS</t>",
					format ["<t align='left'>Targets hit: </t><t align='right'>%1 / %2", _targetsHit, _totalTargets],
					format ["<t align='left'>Targets missed: </t><t align='right'>%1 / %2", _targetsMissed, _totalTargets],
					format ["<t align='left'>Target hit rate: </t><t align='right'>%1%2", _targethitRate, "%"],
					"",
					format ["<t align='left'>Shots landed: </t><t align='right'>%1 / %2", _shotsHit, _shotsFired],
					format ["<t align='left'>Accuracy: </t><t align='right'>%1%2", _accuracy, "%"]
				] joinString "<br/>"
			);
		};
		
		_controller setVariable ["RQR_currentShooter", objNull, true];
	}, 
	[_lines], 
	6969, 
	true, 
	true, 
	"", 
	"isNull (_target getVariable [""RQR_currentShooter"", objNull])"
];

private _actionFree = _laneController addAction [
	"Free Exercise", 
	{
		params ["_controller", "_caller", "_id", "_args"];
		_controller setVariable ["RQR_currentShooter", _caller, true];
		_controller setVariable ["RQR_cancel", false, true];
		_caller setVariable ["RQR_currentController", _controller];

		private _spacing = _controller getVariable "RQR_lineSpacing";
		private _targets = _controller getVariable "RQR_targets";
		private _rounds = _controller getVariable "RQR_rounds";
		private _stats = [0,0,0,0]; //[Targets missed, Targets hit, ShotsFired, ShotsHit]
		_controller setVariable ["RQR_stats", _stats];
		
		//Thread for checking if shooter still is in shooting position
		private _checkShooter_thread = [_controller, _caller, _controller getVariable "RQR_shootPosition"] spawn {
			params ["_ctrl", "_shooter", "_shootFrom"];
			waitUntil {
				sleep 0.5;
				isNull (_ctrl getVariable ["RQR_currentShooter", objNull]) 
				OR 
				_shooter distance _shootFrom > 10
			};
			
			if( !isNull (_ctrl getVariable ["RQR_currentShooter", objNull]) ) then {
				_ctrl setVariable ["RQR_cancel", true];
			};
		};

		hint parseText (
			[
				"<t size='1.5'>Free exercise</t>",
				"",
				"<t align='left'>Up to 3 targets will pop up randomly spread across the range and you have 3 seconds per target to take them down.",
				"",
				"This will continue until you choose to cancel the exercise.",
				"",
				"Your results will be displayed at the end of the exercise.</t>",
				"",
				"<t size='1.5'>Get ready...</t>"
			] joinString "<br/>"
		);
		
		sleep 10;
		hintSilent parseText "<t size='1.5'>Begin!</t>";
		playSound3D [SOUND_ACTIVE, _controller, false, getPosASL _controller, 1, 1, 50];
		private _firedEH = _caller addEventHandler [
			"FiredMan", 
			{
				params ["_unit", "_weapon"];
				private _ctrl = _unit getVariable "RQR_currentController";
				_stats = _ctrl getVariable "RQR_stats";
				_stats set [2, (_stats select 2)+1];
			}
		];
		sleep 2;
		
		//=================================//
		// === Free exercise main loop === //
		//=================================//

		//Select possible targets
		private _targetList = [];
		{
			if(random 1 < 0.25) then {
				_targetList append _x;
			};
		} forEach _targets;

		if(count _targetList == 0) then {
			_targetList = +(selectRandom _targets);
		};

		private _i = 0;
		private _misses = 0;
		private _hits = 0;
		
		while {!(_controller getVariable ["RQR_cancel", false])} do {
			//Pick 1-3 random targets
			private _numPopups = [1,2,3] selectRandomWeighted [3,2,1];
			
			private _popups = [];
			private _availablePopups = +_targetList;
			while {count _popups < _numPopups && count _availablePopups > 0} do {
				_popups pushBack (_availablePopups deleteAt floor random count _availablePopups);
			};

			private _timeUp = 3 * count _popups;

			//Pop up
			{	
				_x animateSource ["terc", 0];
				playSound3D [SOUND_POPUP, _x, false, getPosASL _x, 1, 1, 300]; 
			} forEach _popups;
			waitUntil {
				{(_x animationPhase "terc") > 0} count _popups == 0
				OR
				(_controller getVariable ["RQR_cancel", false])
			};
			
			//Wait for hits
			private _timeout = time + _timeUp;
			waitUntil {
				private _str = (
					_popups apply {
						private _c = if((_x animationPhase "terc") == 1) then {"#00FF00"} else {"#FFFFFF"};
						format ["<img size='2' color='%1' image='%2'/>", _c, IMAGE_POPUP]
					}
				) joinString "";
				hintSilent parseText (" " + _str + " ");
				time > _timeout
				OR 
				{(_x animationPhase "terc") > 0} count _popups == _numPopups
				OR
				(_controller getVariable ["RQR_cancel", false])
			};
			
			private _str = (
				_popups apply {
					private _c = if((_x animationPhase "terc") > 0) then {"#00FF00"} else {"#FF0000"};
					format ["<img size='2' color='%1' image='%2'/>", _c, IMAGE_POPUP]
				}
			) joinString "";
			hintSilent parseText (" " + _str + " " );
			
			private _hit = {(_x animationPhase "terc") > 0} count _popups;
			private _missed = _numPopups - _hit;
			_hits = _hits + _hit;
			_misses = _misses + _missed;
			
			//Pop down
			{
				_x animateSource ["terc", 1];
				playSound3D [SOUND_POPDOWN, _x, false, getPosASL _x, 1, 1, 300];
			} forEach _popups;

			waitUntil {
				{(_x animationPhase "terc") < 1} count _popups == 0
				OR
				(_controller getVariable ["RQR_cancel", false])
			};

			if(time > _timeout) then {
				playSound3D [SOUND_MISS, _controller, false, getPosASL _controller, 2, 1, 50];
			};
			
			_i = _i + 1;
			if(_i > 6) then {
				_i = 0;

				//Select new possible targets
				_targetList = [];
				{
					if(random 1 < 0.25) then {
						_targetList append _x;
					};
				} forEach _targets;

				if(count _targetList == 0) then {
					_targetList = +(selectRandom _targets);
				};

				sleep 2;
				hintSilent "";
				for "_i" from 0 to 14 do {
					hintSilent format ["Reload break... %1 s", 15-_i];
					sleep 1;
				};
				hintSilent "";
			} else {
				sleep 2;
				hintSilent "";
				sleep (2+floor random 3);
			};
		};
		
		//===============================//
		// === Free exercise cleanup === //
		//===============================//

		_stats set [0, (_stats select 0) + _misses];
		_stats set [1, (_stats select 1) + _hits];

		//Play end sound
		_caller removeEventHandler ["FiredMan",_firedEH];
		playSound3D [SOUND_CLEAR, _controller, false, getPosASL _controller, 2, 1, 50];

		{
			{ 
				_x animateSource ["terc", 1];
			} forEach _x;
		} forEach _targets;
				
		//Show scores
		_stats params ["_targetsMissed", "_targetsHit", "_shotsFired", "_shotsHit"];
		
		private _totalTargets = _targetsHit + _targetsMissed;
		private _targethitRate = 0.1*round (1000*_targetsHit/_totalTargets);
		private _accuracy = 0.1*round (1000*_shotsHit/_shotsFired);

		hintSilent parseText (
			[
				"<t size='1.5'>RESULTS</t>",
				format ["<t align='left'>Targets hit: </t><t align='right'>%1 / %2", _targetsHit, _totalTargets],
				format ["<t align='left'>Targets missed: </t><t align='right'>%1 / %2", _targetsMissed, _totalTargets],
				format ["<t align='left'>Target hit rate: </t><t align='right'>%1%2", _targethitRate, "%"],
				"",
				format ["<t align='left'>Shots landed: </t><t align='right'>%1 / %2", _shotsHit, _shotsFired],
				format ["<t align='left'>Accuracy: </t><t align='right'>%1%2", _accuracy, "%"]
			] joinString "<br/>"
		);
		
		_controller setVariable ["RQR_currentShooter", objNull, true];
	}, 
	[_lines], 
	6968, 
	true, 
	true, 
	"", 
	"isNull (_target getVariable [""RQR_currentShooter"", objNull])"
];
//Cancel course action 
private _actionReset = _laneController addAction [
	"Cancel exercise", 
	{
		params ["_controller"];
		_controller setVariable ["RQR_cancel", true];
	}, 
	0, 
	6967, 
	false, 
	true, 
	"", 
	"(_target getVariable [""RQR_currentShooter"", objNull]) isEqualTo _this"
];

//Check stats
private _actionStats = _laneController addAction [
	"Recent results", 
	{
		private _handle = missionNamespace getVariable ["RQR_REV_handle", scriptNull];
		if(scriptDone _handle) then {
			RQR_REV_handle = _this spawn {	
				params ["_controller", "_caller", "_id", "_targets"];
				
				private _recentTests = _controller getVariable ["RQR_data_recentTests", []];
				private _numResults = count _recentTests;
				if(_numResults > 0) then {
					RQR_REV_curIndex = _numResults - 1;
					RQR_REV_show = true;
					RQR_REV_viewChangeDelay = 0.5;
					private _nextViewChange = -1;
					
					while {RQR_REV_show} do {
						//Read inputs
						private _back = inputAction "moveBack" > 0;
						private _next = inputAction "leanRight" > 0;
						private _prev = inputAction "leanLeft" > 0;

						//Update logic
						switch(true) do {
							case (_back OR (_caller distance _controller > 5)): {
								RQR_REV_show = false;
							};
							case _next: {
								if(time > _nextViewChange) then {
									RQR_REV_curIndex = (RQR_REV_curIndex - 1 + _numResults) % _numResults;
									_nextViewChange = time + RQR_REV_viewChangeDelay;
								};
							};
							case _prev: {
								if(time > _nextViewChange) then {
									RQR_REV_curIndex = (RQR_REV_curIndex + 1) % _numResults;
									_nextViewChange = time + RQR_REV_viewChangeDelay;
								};
							};	
						};					

						//Update hint
						private _viewedResult = _recentTests#RQR_REV_curIndex;
						_viewedResult params [ "_name", "_totalTargets", "_targetsHit", "_targetsMissed", "_targethitRate", "_shotsHit", "_shotsFired", "_accuracy" ];
						hintSilent parseText (
							[
								format ["<t align='left'>&lt;- Q</t><t align='center' size='1.5'>Result #%1</t><t align='right'>E -&gt;</t>", _numResults - RQR_REV_curIndex],
								"",
								format ["<t align='left'>Name: </t><t align='right'>%1", _name],
								format ["<t align='left'>Targets hit: </t><t align='right'>%1 / %2", _targetsHit, _totalTargets],
								format ["<t align='left'>Targets missed: </t><t align='right'>%1 / %2", _targetsMissed, _totalTargets],
								format ["<t align='left'>Target hit rate: </t><t align='right'>%1%2", _targethitRate, "%"],
								"",
								format ["<t align='left'>Shots landed: </t><t align='right'>%1 / %2", _shotsHit, _shotsFired],
								format ["<t align='left'>Accuracy: </t><t align='right'>%1%2", _accuracy, "%"],
								"",
								"<t align='center'>Press S to exit</t>"
							] joinString "<br/>"
						);
					};
					hintSilent "";
				} else {
					hintSilent "No tests completed since session start.";
				};
			};
		};
	}, 
	_targets, 
	6966, 
	false, 
	true, 
	"", 
	"!( (_target getVariable [""RQR_currentShooter"", objNull]) isEqualTo _this)"
];

 

 

  • Thanks 1

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×