Jump to content
Sign in to follow this  
Rydygier

SQS to SQF "translation" request - field of view scanner

Recommended Posts

Recently wrote a script for own use, using, as usual, SQS syntax. However, I would like learn at practical example also SQF syntax, that is, they say, better (BTW, actually why is it better?). Would someone "translate" for me this script to SQF, so I (and also maybe some other folks) can compare and use them? There is used fourth-level looping and numerous goto's, so simply getting lost in unfamiliar to me syntax when I try to convert this script.

Purpose of this script is to create a global variable (an array) containing specified number of positions ensuring field of view in desired direction(s) at chosen distance. For testing purposes also indicates them using markers. I use it mostly for automatic (setpos) deployment units such static weapons, snipers, etc. In certain circumstances it may also indicate rooftops of buildings. It's based on some "(getposASL select 2) comparing" routines.

Maybe even someone would have an idea how to improve it? Particularly valuable would be reduction of working time. Now, depending on settings, scan can take long.

Here it is:

; if done, scanready become 1
Scanready = 0
; desired directions are "1" (using other values may give interesting effects perhaps), other are "0". With more than two at once rather not found any good position
_N = 1
_S = 0
_E = 1
_W = 0
; desired hight difference factor (not difference itself) for adjacent steps (required steepness)
_level0 = 60
; maximum number of good spots to find
_Nplaces = 50
; desired distance of not blocked by terrain view from scanned point
_LOS = 320
; loop: if not enough spots found (less than _Nplaces value), script repeats scan with reduced steepness factor within defined limits 
#again
;initial scan-step (in meters) between scanned point and LOS-testing point
_step2 = 2.5
; steepness factor reduction, now it becomes currently considered hight difference between scanned and testing spots in meters
_level0 = _level0/2

hintsilent "Scanning..."
;half of lenght and width of scanned sector (rectangular)
_L = 300
_Wd = 300
;defining of scanned point coordinates variables
_X = 0
_Y = 0
; distance between scanned spots. The larger area to scan, the greater distances between scanned points, and therefore less thorough scanning
_step = (_L + _Wd)/40
; definig global array with "good posts"
goodpos1 = []
#start
_a = -_step
; loop for horizontal line change (from south to north)
#startY
_a = _a + _step
_y = ((getpos player) select 1) - _Wd +_a
? _Y >= ((getpos player) select 1) + _Wd : goto "exit"
_b = -_step
; loop for given horizontal (west - east) line points scan
#startX
_b = _b + _step;
_step2 = 5
_X = ((getpos player) select 0) - _L +_b;
? _X >= ((getpos player) select 0) + _L : goto "startY";
_mark = [_X,_Y];
; loop for a single point scan for growing distances (subsequent _step2 value is doubling of preceding _step2 value, so it's not linear growth)
#step2
_level = _Level0/(1 + (10/_step2))
_step2 = 2*_step2
_marker1 = createTrigger["EmptyDetector",_mark]; 
_marker2 = createTrigger["EmptyDetector",[_X-_step2,_Y]];
_marker3 = createTrigger["EmptyDetector",[_X+_step2,_Y]];
_marker4 = createTrigger["EmptyDetector",[_X,_Y-_step2]];
_marker5 = createTrigger["EmptyDetector",[_X,_Y+_step2]];

? (((getposASL _marker1) select 2) <= (((getposASL _marker2) select 2)+_level)*_W) or (((getposASL _marker1) select 2) <= (((getposASL _marker3) select 2)+_level)*_E) or (((getposASL _marker1) select 2) <= (((getposASL _marker4) select 2)+_level)*_S) or (((getposASL _marker1) select 2) <= (((getposASL _marker5) select 2)+_level)*_N) : deletevehicle _marker1;deletevehicle _marker2;deletevehicle _marker3;deletevehicle _marker4;deletevehicle _marker5;goto "startX" 
? (_step2 >= _LOS) : goto "found"
deletevehicle _marker1
deletevehicle _marker2
deletevehicle _marker3
deletevehicle _marker4
deletevehicle _marker5
goto "step2"
; if good spot found, it is added to goodpos1 array (3D format position array, which is nested inside goodpos1 array) and some marking on the map fo testing purposes
#found
goodpos1 = goodpos1 + [position _marker1]
_good = createMarker[(str _a)+(str _b),_marker1];
_good setMarkerShape "ICON";
((str _a)+(str _b)) setMarkerType "DOT";
deletevehicle _marker1
deletevehicle _marker2
deletevehicle _marker3
deletevehicle _marker4
deletevehicle _marker5
? (count goodpos1 >= _Nplaces) : goto "exit"
goto "startX"

#exit
; final check, if it is enough chosen spots. If not, and if steepness is still large enough, script repeats scan with halfed steepness)
? (count goodpos1 < _Nplaces) and (_level0 >= 6) : goto "again"
player sidechat format ["Scan complete: %1 places found",count goodpos1]
Scanready = 1
exit

I will be grateful for any help.

Of course, feel free to experiment with variables and to use code for your own purposes, if needed.

Share this post


Link to post
Share on other sites

Disclaimer: Has not used SQS

I think most people think SQF is better because:

You don't have to "interpret" symbols like '~' and '?' all the time. Sure you learn what they do but in sqf you just read 'sleep' and 'if' and your mind spends less time analysing and less chance of mistake.

Also seems to me that SQS uses gotos often. They can make it harder to follow the flow of the logic. You have to jump back and forth in the logic all the time. In SQF you typically use keywords like 'while', 'for' and functions which is easier to comprehend. Eg. normally the purpose of a function can be derived from its name and you don't have to look at how it is made to understand the logic; although you can.

About your script. Looks like you are trying to do some LOS checking. I have made such a function in SQF which does this (by using the terrain):

/*
Terrain based LOS - Does NOT check for vegetation or buildings
Returns nil on bad positions/objects.

FROM_ASL - OBJECT or POS in ASL
TO_ASL - OBJECT or POS in ASL
RESOLUTION - Distance between each check in meters.
EXTRA_ALLOWED - Defaults to 0.06 for extremely small stretches of terrain that technically obstructs LOS (eg. Tarmac somehow).
   "Eye"-height to add to both, or if [NUM, NUM] to add to both viewer and target respectively.
*/
haveTerrainLOS = {  
   private ["_result","_currentPos","_actualHeight","_fromASL","_toASL","_resolution","_maxChecks","_stepVector","_extra"];
   _fromASL = _this select 0;
   _toASL = _this select 1;
   //Object inputs?
   if (typeName _fromASL == "OBJECT") then {_fromASL = getPosASL _fromASL};
   if (typeName _toASL == "OBJECT") then {_toASL = getPosASL _toASL};
   //Don't return true on bad input!!!
   if (isNil "_fromASL" || isNil "_toASL") exitWith {nil};
   _resolution = _this select 2;
   _extra = 0.08;
   //Add extra
   if (count _this > 3) then {_extra = _this select 3;};
   if (typeName _extra == typeName []) then {
       _fromASL set [2, (_fromASL select 2) + (_extra select 0)];
       _toASL set [2, (_toASL select 2) + (_extra select 1)];
   } else {
       _fromASL set [2, (_fromASL select 2) + _extra];
       _toASL set [2, (_toASL select 2) + _extra];
   };
   //Save one multiplication per step for only a tiny loss in accuracy hopefully.
   _stepVector = [_fromASL, _toASL] call BIS_fnc_vectorFromXToY;
   _stepVector = [_stepVector, _resolution] call BIS_fnc_vectorMultiply;
   //Assert there is LOS
   _result = true;
   _currentPos = _fromASL;
   _maxChecks = floor ((_fromASL distance _toASL) / _resolution);
   for "_i" from 1 to _maxChecks do {
       _currentPos = [_currentPos, _stepVector] call BIS_fnc_vectorAdd;
       _actualHeight = getTerrainHeightASL _currentPos;
       if (_actualHeight > _currentPos select 2) exitWith {_result = false;};
   };
   _result
};

If I want to know whether two objects or positions have LOS by looking every 20m of the way I just do:

_haveLos = [man_a, man_b, 20] call haveTerrainLOS;
if (_haveLos) then {
   hint "Can see eachother";
} else {
   hint "Can't see each other";
};

Edited by Muzzleflash

Share this post


Link to post
Share on other sites

@PVPScene Thanks, but I read it. Despite this, "translate" a more complex code seems to outgrow my current possibilities. Of course I intend to increase my knowledge and practice in this and this is what I could use an extensive comparative example.

@Muzzleflash Thanks a lot, I'll look at this. So in fact there is no special qualitative difference (eg, efficiency) between the SQS and SQF? Good to know. An interesting thing... To me is much more understandable SQS syntax. Probably because it reminds me a more programming languages, which I have been taught in school, long ago. :)

Share this post


Link to post
Share on other sites
@Muzzleflash Thanks a lot, I'll look at this. So in fact there is no special qualitative difference (eg, efficiency) between the SQS and SQF? Good to know. An interesting thing... To me is much more understandable SQS syntax. Probably because it reminds me a more programming languages, which I have been taught in school, long ago. :)

Regarding effiency I don't there is a difference. You can do the same in both, however, regarding understandability I think there is a rather significant difference. I don't think SQS resembles a programming language more than SQF, well except regarding assembler perhaps.

Share this post


Link to post
Share on other sites

I suggest just jumping into it. It's really not that hard to grasp the concept of, a concept which is far superior. I see from the original script (which I'm not gonna bother trying to read and understand, because I just don't like SQS anymore) that you also suffer from mixing up the two, by putting ; at the end of lines when you don't need to in SQS.

Also I suggest you do a search on the subject. We've had a few heated discussions about SQS vs SQF in the past you might find interesting.

Muzzleflash code has calls to BIS_fnc_*, which means you need to have a functions module present in your mission, and wait for it to be initialized (waiting may not be needed in this case, but is considered good practice).

Share this post


Link to post
Share on other sites
Muzzleflash code has calls to BIS_fnc_*, which means you need to have a functions module present in your mission, and wait for it to be initialized (waiting may not be needed in this case, but is considered good practice).

I primarily made it just for something I needed. I always use CBA so the functions module isn't an issue for me. I always insert a functions module though. It it was to be very userfriendly I could insert a wait. However, instead of waits for the function module in every function I use some BIS function, I would rather just have the caller ensure it is ready a single time before using the function. A good compromise would be to include a "requires functions module init" in the description of the function.

EDIT:

Somehow misread it as you wanted me to insert waits in the function. If that isn't the case then please disregard the above.

Edited by Muzzleflash

Share this post


Link to post
Share on other sites
I don't think SQS resembles a programming language more than SQF, well except regarding assembler perhaps.

It's probably mostly a question of "goto", which preserves a certain way of thinking about code and looping. I am firmly rooted in it, because I was taught what is called "Turbo Pascal". I remember, that I liked there to use similar commands. So this is a matter of shifting to a different way of thinking, what comes to me with difficulty. Maybe I'm too old for such revolutions? :) Still, I will try. Moreover, not always I know, how to use SQF to achieve something, that can easily be done in the SQS. I think that without SQS, I never have learned to write scripts for this game.

Share this post


Link to post
Share on other sites

SQS was a gateway drug to programming for me when I learned it a decade ago. It was simple to grasp due to its line-by-line, BASIC type nature. I have since learned other programming languages. SQF is more akin to what you will find in the real world. Another thing: anything complex becomes impossible with SQS, as you are finding out with your script. With SQS, you have to keep everything simple. If you want to create advanced scripts, learning SQF is a must.

Share this post


Link to post
Share on other sites

@benreeper Surely SQS is, or at least was in my case, a great introduction to writing scripts.

So, my own attempt. I will not say it was easy to force this code to work. Sweat and tears. Syntax full of brackets and semicolons is one thing, but longest I was looking for error, which consisted in fact, that variable "isgood" was local, which for some reason made ​​it invisible outside the loop. It works now, but perhaps it's far from optimum. Something could be done better? Simpler? Something is missing or is unnecessary?

/*
Terrain scanner for spots with good field of view in chosen direction(s)

init: 

null = [Number of needed spots,first direction ("N" "S" "E" "W"),second direction for "N" or "S" only ("E" or "W", optional),free LOS distance in meters, halfed lenght of scanned for spots area (in meters, rectangular), halfed area width] execVM "Spotscan.sqf";
The larger area to scan, the greater distances between scanned points, and therefore less thorough scanning. 
eg.:

null = [50,"N","W",640,300,300] execVM "Spotscan.sqf"

or:

null = [50,"S","",100,500,200] execVM "Spotscan.sqf"

*/
_spotsneeded = _this select 0;
_windroseOne = _this select 1;
_windroseTwo = _this select 2;
_scanRange = _this select 3;
_sectorLenght = _this select 4;
_sectorWidth = _this select 5;
_step = (_sectorLenght + _sectorWidth)/40;
_isdone = false;
_true = true;
goodspots = [];
hintsilent "Scanning...";
for [{_steepnessInitial = 9},{(_steepnessInitial >= 1) and not (_isdone)},{_steepnessInitial = _steepnessInitial/1.5}] do
{
for [{_Y = ((getpos markscan) select 1) - _sectorWidth},{_Y <= ((getpos markscan) select 1) + _sectorWidth},{_Y = _Y + _step}] do
	{
	for [{_X = ((getpos markscan) select 0) - _sectorLenght},{_X <= ((getpos markscan) select 0) + _sectorLenght},{_X = _X + _step}] do
		{
		_scannedSpot = [_X,_Y];

			for [{_probeStep = 2.5;isgood = true},{(_probeStep <= _scanRange) and isgood},{_probeStep = 2*_probeStep}] do
				{
				_steepnessActual = _steepnessInitial/(1 + (10/_probeStep));
				_centralSpot = createTrigger["EmptyDetector",_scannedSpot]; 
				_probeNorth = createTrigger["EmptyDetector",[_X,_Y+_probeStep]];
				_probeSouth = createTrigger["EmptyDetector",[_X,_Y-_probeStep]];
				_probeEast = createTrigger["EmptyDetector",[_X+_probeStep,_Y]];
				_probeWest = createTrigger["EmptyDetector",[_X-_probeStep,_Y]];

					switch (_windroseOne) do
					{
					  case "N" : {
						  	if (((getposASL _centralSpot) select 2) <= (((getposASL _probeNorth) select 2)+_steepnessActual))
							  	exitwith {isgood = false};

									switch (_windroseTwo) do 
									  	{
									  	case "E":   {
										  		if (((getposASL _centralSpot) select 2) <= (((getposASL _probeEast) select 2)+_steepnessActual)) 
													exitwith {isgood = false}; if _true exitwith {isgood = true}
												};

								  		case "W":   {
									  			if (((getposASL _centralSpot) select 2) <= (((getposASL _probeWest) select 2)+_steepnessActual)) 
								  					exitwith {isgood = false}; if _true exitwith {isgood = true}
								  				};
								  		default {if _true exitwith {isgood = true}}
								  		}
							 };

					  case "S": {
						  	if (((getposASL _centralSpot) select 2) <= (((getposASL _probeSouth) select 2)+_steepnessActual))
							  	exitwith {isgood = false}; 

									switch (_windroseTwo) do 
										{
									  	case "E":   {
										  		if (((getposASL _centralSpot) select 2) <= (((getposASL _probeEast) select 2)+_steepnessActual)) 
								  					exitwith {isgood = false}; if _true exitwith{isgood = true}
								  				};

								  		case "W":   {
									  			if (((getposASL _centralSpot) select 2) <= (((getposASL _probeWest) select 2)+_steepnessActual)) 
								  					exitwith {isgood = false}; if _true exitwith {isgood = true}
								  				};
								  		default {if _true exitwith {isgood = true}}
								  		}
							 };

					  case "E": {
						  	if (((getposASL _centralSpot) select 2) <= (((getposASL _probeEast) select 2)+_steepnessActual))
							  	exitwith {isgood = false}; if _true exitwith {isgood = true}
							};

					  case "W": {
						  	if (((getposASL _centralSpot) select 2) <= (((getposASL _probeWest) select 2)+_steepnessActual))
							  	exitwith {isgood = false}; if _true exitwith {isgood = true}
							};

					  default {if _true exitwith {isgood = false}}


					};

				deletevehicle _centralSpot;
				deletevehicle _probeNorth;
				deletevehicle _probeSouth;
				deletevehicle _probeEast;
				deletevehicle _probeWest


				};



		if isgood then 
		  	{
			 goodspots = goodspots + [_scannedSpot];
			_goodmark = createMarker[(str _X)+(str _Y),_scannedSpot];
			_goodmark setMarkerShape "ICON";
			((str _X)+(str _Y)) setMarkerType "DOT"
			};
		if ((count goodspots) >= _Spotsneeded) exitwith {_isdone = true}

		}
	}
};
player sidechat format ["Scan complete: %1 places found",count goodspots];
isgood = nil;

Edited by Rydygier

Share this post


Link to post
Share on other sites

You did it, congrats! It looks like good code. This makes it easier to see the program flow and spot errors and such. Don't worry; if it works, the job is done. Later, as your skills increase, you will re-visit this and other older scripts and say "Oh, why did I do it that way?" You will then be able to optimize them. You are on your way.

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
Sign in to follow this  

×