Jump to content
Sign in to follow this  
Fuzzy Bandit

Getting the Position of a Laser Target

Recommended Posts

Conclusion of thread:

Below is the conclusion of this thread, a working Laser-Guided AI Artillery System. It's all in a spoiler, 'cause it's quite big! :D

Ok, just posting final code and files to the end of this thread in case anybody else wanted to have a bash.

In this scenario, we have a unit named 'man1' being able to call in the artillery, and 'man5' is the designated... designator. :D

There are a total of two laser-guided rounds available to the squad. After that, the action will be removed and artillery support will no longer be available.

Upon first calling the Artillery, HQ will tell you whether they can see the target or not. If they can, then the next time you call them they'll fire the first round. The third time you call they will once again scan for Laser Targets, and if found the fourth time you call they will fire the second round and become unavailable.

If at any point HQ can not find the Laser Target, they will let you know.

I'm happy to interpret these scripts for anybody that wants slightly different setups or the like.

arty.sqf

(The first line of this script highlighted in blue is where it defines which unit will be designating. For a specific unit, do as I have done, using '[man5]' or '[delta4]'. If you just want the player, leave it as '[]'. The line at the bottom, highlighted in red, removes the action from a specific unit after two rounds have been fired. If you want one specific unit to have the action to call in the artillery, specify him here. Mine is 'man1', the Team Leader. If you have set the top line to '[]', then use 'player'.)

[color="Blue"]_myLaserTarget = [man5] call getMyLaserTarget;[/color]
if(not isNull _myLaserTarget) then {
   artilleryuse = artilleryuse + 1; 
   if (artilleryuse == 1) then {
	[west,"HQ"] sideChat "Confirm Laser. Target acquired and cranking round the gun. Call us again when you're ready to fire."}; 
   if (artilleryuse == 2) then {
	[artbatt, getposASL _myLaserTarget, ["IMMEDIATE", "LASER", 0, 1]] call BIS_ARTY_F_ExecuteTemplateMission; 
	sleep 1;
	[west,"HQ"] sideChat "Round one away! Splash ETA 1 minute. Stand by and keep the target designated. You've got one more shot at your disposal."};
   if (artilleryuse == 3) then {
	[west,"HQ"] sideChat "Confirm Laser. Target acquired and adjusting aim. Call us again when you're ready to fire."};
   if (artilleryuse == 4) then {
	[artbatt, getposASL _myLaserTarget, ["IMMEDIATE", "LASER", 0, 1]] call BIS_ARTY_F_ExecuteTemplateMission; 
	sleep 1; 
	[west,"HQ"] sideChat "Round two away! Splash ETA 1 minute. Keep target designated and stand by. Bingo on Laser-Guided Ammunitions. Stay safe.";
	[color="Red"]man1 removeAction 0};[/color]
} else {[west,"HQ"] sideChat "Negative on that request. No Laser Target found - check your batteries are loaded!"};

func_getMyLaserTarget.sqf

(The line highlighted red tells the script to use the man you specified in 'arty.sqf'. If you set 'arty.sqf' to '[]', delete the red line.)

(All words highlighted blue are also if you specified a specific unit in 'arty.sqf'. If you did not, find and replace all '_unit' occurances to 'player'.)

//by Bon_Inf*
/** returns the laser target the player is lasering at, or ObjNull if it does not exist **/

private ["_xpos","_ypos","_myLaserTarget","_lasertargets","_dir","_distance","_min","_target","_targetpos","_targetdistance","_aimpos"];

[color="Red"]_unit = _this select 0;    // _this = [man5][/color]

_lasertargets = nearestObjects[[color="Blue"]_unit[/color],["LaserTarget"],2000];
if(count _lasertargets == 0) exitWith{ObjNull};

_xpos = getPos [color="Blue"]_unit[/color] select 0;
_ypos = getPos [color="blue"]_unit[/color] select 1;

_myLaserTarget = ObjNull;

_dir = [color="blue"]_unit[/color] weaponDirection "Laserdesignator";
_dir = (_dir select 0) atan2 (_dir select 1);		// get direction of the lasermarker aiming at

_distance = 99;
_min =99;

for "_i" from 0 to (count _lasertargets - 1) do{
_target = _lasertargets select _i;
_targetpos = [getPos _target select 0,getPos _target select 1,0];

_targetdistance = _targetpos distance [getPos [color="blue"]_unit[/color] select 0, getPos [color="blue"]_unit[/color] select 1, 0];

_aimpos = [_xpos + _targetdistance*sin(_dir), _ypos + _targetdistance*cos(_dir),0];

if((_targetpos distance _aimpos)<_min) then{
	_myLaserTarget=_target;
	_min = _targetpos distance _aimpos;
};
};

if(_min>2) then{_myLaserTarget = ObjNull};

_myLaserTarget

init.sqf

(The line highlighted in red adds an action (in the Scroll-Menu) to the player 'man1', which you can change to specify only one person who can call in the Artillery. You can also, however, put the code on a trigger. Set the trigger to BLUFOR, Present, Once, and in the 'On Act.' box, type: 'man1 addAction ["Call Artillery on Laser", "arty.sqf"]', man1 being the interchangable part. If you do add that code to a trigger, then remove all lines highlighted in red and blue.)

getMyLaserTarget = compile (preprocessFileLineNumbers "func_getMyLaserTarget.sqf");
sleep 0.1; // give precompiling a break ^^
[color="Blue"]if(not isDedicated) then{ //prevent a ded. server to try to assign an action to a (then) non-existent player unit[/color]
   [color="Red"]if(player == man1) then{player addAction ["Call Artillery on Laser", "arty.sqf"]};[/color]
[color="Blue"]};[/color]

In your Mission you will also need:

  1. A static Artillery unit (Such as a D30 for OPFOR or M119 for BLUFOR).
  2. An Artillery Module (NOT VIRTUAL!) named 'artbatt' placed near to the Artillery Unit and synchronised with it.
  3. A Game Logic containing the simple code: 'artilleryuse = 0'.

That should be everything! This has worked out pretty well for me at least! Pretty much all the credit goes to Bon for his fantastic scripting work finding the Laser Designator. I pretty much just called in the artillery and made it limited! :)

Hope anyone that has any use for this has fun with it, and I'm happy to make slightly different versions of the script(s) if you need them, or I'm sure Bon would be happy to help out!

Cheers.

Original Post:

And here is the original first post if you want to follow the whole thing chronologically. :)

Hey hey!

In my mission, I want to incorporate a laser-guided artillery system. At the moment it all works fine. I'm using an M119 artillery piece synced up to an Artillery Module, and me as a Rifleman (for testing purposes) pointing my laser at things and calling in the Artillery using a trigger assigned to Radio Alpha.

The code in the trigger is:

[artbatt, getposASL arttarg, ["IMMEDIATE", "LASER", 0, 1]] call BIS_ARTY_F_ExecuteTemplateMission;

Now that's all fine and dandy, and works like a charm, but there's one problem. At the moment the artillery is firing at an invisible helipad on the ground named "arttarg", and the laser is guiding it in to specific points around that inital target. The issue is that the laser can't really make the artillery hit more than ~500m from the invisible helipad "arttarg". Ideally, I'd like to produce a small artillery system where the Artillery would fire at where my laser is pointing, but I don't see a way to get the position of my laser.

I had a look at a thread which named some sort of laser targets and came across something named "LaserTargetWBase", but that just seems to make him fire off in some crazy direction, as opposed to where my laser is pointing? So anybody got any ideas?

Wish is was just as simple as:

[artbatt, getposASL mylasertarget, ["IMMEDIATE", "LASER", 0, 1]] call BIS_ARTY_F_ExecuteTemplateMission;

:P

Thanks in advance.

Edited by Fuzzy Bandit
Adding the Conclusion to the Topic

Share this post


Link to post
Share on other sites

Quick solution for Singleplayer (not suited for multiplayer).

This solution isn't optimal since whenever you use "screenToWorld[0.5,0.5]" (Which return the position of where the player is looking at), only the landscape position is returned (i.e, if you look at a house, the position returned will be the landscape behind the house).

nullReturn = [] Spawn {
_lasers = (screenToWorld[0.5,0.5]) nearEntities ["LaserTarget",4000];
_laserPos = if (count _lasers > 0) then {_lasers select 0} else {objNull};
if (!isNull _laserPos) then {[artbatt, _laserPos, ["IMMEDIATE", "LASER", 0, 1]] call BIS_ARTY_F_ExecuteTemplateMission};
};

Edited by Benny.

Share this post


Link to post
Share on other sites

Ok, it will be used for a short(ish) multiplayer mission, but I won't worry about that too much.

Just tried entering it into my trigger and got a blank error upon trying to click OK - any ideas?

Share this post


Link to post
Share on other sites
Ok, it will be used for a short(ish) multiplayer mission, but I won't worry about that too much.

Just tried entering it into my trigger and got a blank error upon trying to click OK - any ideas?

Made a small mistake ;), retry the code i've posted.

Share this post


Link to post
Share on other sites

Here a solution that also works in MP:

The function follows the following algorithm: Gather all near lasertargets. Draw an imaginary line along the direction you're aiming your laserdesignator at.

Check for each near lasertarget the distance to this line. If the lasertarget with closest distance to this line is closer than 2m, it returns this lasertarget, otherwise ObjNull.

Note: its a function that has to be precompiled.

func_getMyLaserTarget.sqf

//by Bon_Inf*
/** returns the laser target the player is lasering at, or ObjNull if it does not exist **/

private ["_xpos","_ypos","_myLaserTarget","_lasertargets","_dir","_distance","_min","_target","_targetpos","_targetdistance","_aimpos"];


_lasertargets = nearestObjects[player,["LaserTarget"],2000];
if(count _lasertargets == 0) exitWith{ObjNull};

_xpos = getPos player select 0;
_ypos = getPos player select 1;

_myLaserTarget = ObjNull;

_dir = player weaponDirection "Laserdesignator";
_dir = (_dir select 0) atan2 (_dir select 1);		// get direction of the lasermarker aiming at

_distance = 99;
_min =99;

for "_i" from 0 to (count _lasertargets - 1) do{
_target = _lasertargets select _i;
_targetpos = [getPos _target select 0,getPos _target select 1,0];

_targetdistance = _targetpos distance [getPos player select 0, getPos player select 1, 0];

_aimpos = [_xpos + _targetdistance*sin(_dir), _ypos + _targetdistance*cos(_dir),0];

if((_targetpos distance _aimpos)<_min) then{
	_myLaserTarget=_target;
	_min = _targetpos distance _aimpos;
};
};

if(_min>2) then{_myLaserTarget = ObjNull};

_myLaserTarget

Sometimes it doesn't find the lasertarget, e.g. you are aiming at specific walls of houses (other walls work even if they belong to the same house :confused: ), but all in all its working pretty reliable.

Share this post


Link to post
Share on other sites

Bon that answer is terrific! I do have a couple of questions however! ^_^

  1. How do I precompile and then call the script?
  2. What will the name of the Target be? (i.e. for me I would type "getPos player" - for the laser target do I need to type "getPos myLaserTarget" or something to that effect?)

Thanks for all the help guys and apologies for my not-so-good knowledge. I'm trying to understand what's going on, and I've figured out quite a few things, but I've never dealt with scripts before!

Cheers!

P.S. Below is my current code that goes in my trigger's "On Act." box if it were firing at "arttarg". "artilleryuse" is a pre-defined variable that is originally set to 0. I made this so that the artillery can only be used twice before becoming unavailable.

[artbatt, getposASL arttarg, ["IMMEDIATE", "LASER", 0, 1]] call BIS_ARTY_F_ExecuteTemplateMission;
artilleryuse = artilleryuse + 1; 
if (artilleryuse == 1) then {player sideChat "Used one!"}; 
if (artilleryuse == 2) then {player sideChat "Used ALL!"; 1 setRadioMsg "NULL"};

Edited by Fuzzy Bandit
Changing post to include current code used

Share this post


Link to post
Share on other sites

i have the suspicion that you can't use precompiled functions from within triggers, maybe because triggers get initialized earlier than the functions get precompiled. Anyway, using radio triggers in MP is generally not a good idea. When you click on the radiobutton, the code in your trigger gets executed on all machines, means, when there are 5 players ingame, there will be a firemission requested 5 times - one for each player. I recommend using an action instead:

  1. In your Init.sqf script (if you don't have one yet, just create it in your mission folder) write the following on top of it:
    getMyLaserTarget = compile (preprocessFileLineNumbers "func_getMyLaserTarget.sqf");
    sleep 0.1;
    player addAction ["Call Arty on Laser", "arty.sqf"];
    


  2. Now create another file func_getMyLaserTarget.sqf and paste in it the code from my last post.
  3. Last but not least create a third file, lets name it arty.sqf and write in it:
    _myLaserTarget = [] call getMyLaserTarget;
    if(not isNull _myLaserTarget) then {
       [artbatt, getposASL _myLaserTarget, ["IMMEDIATE", "LASER", 0, 1]] call BIS_ARTY_F_ExecuteTemplateMission;
       artilleryuse = artilleryuse + 1; 
       if (artilleryuse == 1) then {player sideChat "Used one!"}; 
       if (artilleryuse == 2) then {player sideChat "Used ALL!"; 1 setRadioMsg "NULL"};
    } else {hint "no lasertarget found"};
    


Now start the mission, laze your target and use the mouse wheel menu to start the script.

Can't guarantee this works as I don't have any clue about the artillery module. Maybe you have to change your templatemisison. What I can guarantee is at the line where you call the BIS_ARTY_Function you have either your lasertarget detected and you can easily retrieve its position with getPos or getPosASL, or it tells you that it didn't find your lasertarget.

Share this post


Link to post
Share on other sites

You are a fabulous man Bon, and this is fast becoming one of the best lessons yet! :D

So I understand about adding the actions and quickly looking at the different scripts (also thanks to your comments inside and outside the scripts) I think I've got an understanding of what's going on here.

As a last quick question, (apologies for this but thank you so much for the endlessly good support and lesson-teaching! :P) in the Init.sqf is the line:

player addAction ["Call Arty on Laser", "arty.sqf"];

Is it possible to add that, instead, to a trigger, and substitute 'player' with 'man1' for example (my current Team Leader) to make this in a trigger:

man1 addAction ["Call Arty on Laser", "arty.sqf"];

The plan is for a fellow teammate (a Rifleman named 'man5') to have the Laser Designator, but to make sure that only the Team Leader ('man1') can call in the Artillery. However, are the lines preceding the addAction command essential to the overall working? My theory is that (and please correct me if I'm wrong, as this is more of an assumption) everything contained within Init.sqf is run as soon as the mission begins. So my un-script-educated mind tells me that as long as the func_getMyLaserTarget.sqf is still being pre-compiled at the beginning of the mission, it shouldn't matter when the player receives the action to call in Artillery.

If that all works out then I do believe we've got ourselves a working Laser-Guided Artillery System! :P

Thank you, once again, so much for all of your help and damned fine scripting! After you've quickly clarified the above points I'll get off your back! I hope you're getting paid for doing this! :D

Thanks some more,

Jack

Share this post


Link to post
Share on other sites

Alright, it is likely the condition of your trigger, once it becomes true, will be globally true, i.e. on all machines. Then adding an action to a unit or object leads into the problem that everyone who comes close to this unit/object will have this action popping in her menu (since the code of the trigger executes on all machines) - that's some locality basics, are hardly documented.

You can easily write in your trigger

if(player == man1) then{player addAction [...]};

but why not keeping this in Init.sqf:

getMyLaserTarget = compile (preprocessFileLineNumbers "func_getMyLaserTarget.sqf");
sleep 0.1; // give precompiling a break ^^
if(not isDedicated) then{ //prevent a ded. server to try to assign an action to a (then) non-existent player unit
   if(player == man1) then{player addAction ["Call Arty on Laser", "arty.sqf"]};
};

It indeed doesn't matter if you add the action altough the function is not precompiled at this moment. But it must be precompiled when you hit the action. Btw, this tenth of a second (precompilation needs way less time) is even shorter than the Black-In fading when the mission starts. You won't even notice it. In general if you want some code executed in a script that needs breaks by sleeps, but do not want to block the rest of the script by it, no matter how small the break would be, you use spawn.

Last but not least we have to modify the getMyLaserTarget.sqf a bit, as the player calling arty should differ from the player who is using the laserdesignator. Very simple.

Edit the arty.sqf, specifically, the call of the getMyLaserTarget function:

arty.sqf

...
_myLaserTarget = [man5] call getMyLaserTarget;
...

in words, we pass the script the laserdesignatorman as parameter.

The func_getMyLaserTarget.sqf then has to be modified as follows (highlighted the changes in blue):

func_getMyLaserTarget.sqf

//by Bon_Inf*
/** returns the laser target the player is lasering at, or ObjNull if it does not exist **/

private ["_xpos","_ypos","_myLaserTarget","_lasertargets","_dir","_distance","_min","_target","_targetpos","_targetdistance","_aimpos"];

[color="Blue"]_unit = _this select 0;    // _this = [man5][/color]

_lasertargets = nearestObjects[[color="Blue"]_unit[/color],["LaserTarget"],2000];
if(count _lasertargets == 0) exitWith{ObjNull};

_xpos = getPos [color="Blue"]_unit[/color] select 0;
_ypos = getPos [color="Blue"]_unit[/color] select 1;

_myLaserTarget = ObjNull;

_dir = [color="Blue"]_unit[/color] weaponDirection "Laserdesignator";
_dir = (_dir select 0) atan2 (_dir select 1);		// get direction of the lasermarker aiming at

_distance = 99;
_min =99;

for "_i" from 0 to (count _lasertargets - 1) do{
_target = _lasertargets select _i;
_targetpos = [getPos _target select 0,getPos _target select 1,0];

_targetdistance = _targetpos distance [getPos [color="Blue"]_unit[/color] select 0, getPos player select 1, 0];

_aimpos = [_xpos + _targetdistance*sin(_dir), _ypos + _targetdistance*cos(_dir),0];

if((_targetpos distance _aimpos)<_min) then{
	_myLaserTarget=_target;
	_min = _targetpos distance _aimpos;
};
};

if(_min>2) then{_myLaserTarget = ObjNull};

_myLaserTarget

Edited by Bon

Share this post


Link to post
Share on other sites

You sir, are a star.

Just tested it on my own server with a friend. He was the Team Leader (man1) and I was designating (man5). Worked flawlessly. Changed arty.sqf slighty too to be inkeeping with the "Limited Artillery" idea.

Changed

if (artilleryuse == 2) then {player sideChat "Used ALL!"; 1 setRadioMsg "NULL"};

to

if (artilleryuse == 2) then {player sideChat "Used ALL!"; player removeAction 0};

Thank you so much, AGAIN, for all the help! You've been invaluable!

Share this post


Link to post
Share on other sites

Ok, just posting final code and files to the end of this thread in case anybody else wanted to have a bash.

In this scenario, we have a unit named 'man1' being able to call in the artillery, and 'man5' is the designated... designator. :D

There are a total of two laser-guided rounds available to the squad. After that, the action will be removed and artillery support will no longer be available.

Upon first calling the Artillery, HQ will tell you whether they can see the target or not. If they can, then the next time you call them they'll fire the first round. The third time you call they will once again scan for Laser Targets, and if found the fourth time you call they will fire the second round and become unavailable.

If at any point HQ can not find the Laser Target, they will let you know.

I'm happy to interpret these scripts for anybody that wants slightly different setups or the like.

arty.sqf

(The first line of this script highlighted in blue is where it defines which unit will be designating. For a specific unit, do as I have done, using '[man5]' or '[delta4]'. If you just want the player, leave it as '[]'. The line at the bottom, highlighted in red, removes the action from a specific unit after two rounds have been fired. If you want one specific unit to have the action to call in the artillery, specify him here. Mine is 'man1', the Team Leader. If you have set the top line to '[]', then use 'player'.)

[color="Blue"]_myLaserTarget = [man5] call getMyLaserTarget;[/color]
if(not isNull _myLaserTarget) then {
   artilleryuse = artilleryuse + 1; 
   if (artilleryuse == 1) then {
	[west,"HQ"] sideChat "Confirm Laser. Target acquired and cranking round the gun. Call us again when you're ready to fire."}; 
   if (artilleryuse == 2) then {
	[artbatt, getposASL _myLaserTarget, ["IMMEDIATE", "LASER", 0, 1]] call BIS_ARTY_F_ExecuteTemplateMission; 
	sleep 1;
	[west,"HQ"] sideChat "Round one away! Splash ETA 1 minute. Stand by and keep the target designated. You've got one more shot at your disposal."};
   if (artilleryuse == 3) then {
	[west,"HQ"] sideChat "Confirm Laser. Target acquired and adjusting aim. Call us again when you're ready to fire."};
   if (artilleryuse == 4) then {
	[artbatt, getposASL _myLaserTarget, ["IMMEDIATE", "LASER", 0, 1]] call BIS_ARTY_F_ExecuteTemplateMission; 
	sleep 1; 
	[west,"HQ"] sideChat "Round two away! Splash ETA 1 minute. Keep target designated and stand by. Bingo on Laser-Guided Ammunitions. Stay safe.";
	[color="Red"]man1 removeAction 0};[/color]
} else {[west,"HQ"] sideChat "Negative on that request. No Laser Target found - check your batteries are loaded!"};

func_getMyLaserTarget.sqf

(The line highlighted red tells the script to use the man you specified in 'arty.sqf'. If you set 'arty.sqf' to '[]', delete the red line.)

(All words highlighted blue are also if you specified a specific unit in 'arty.sqf'. If you did not, find and replace all '_unit' occurances to 'player'.)

//by Bon_Inf*
/** returns the laser target the player is lasering at, or ObjNull if it does not exist **/

private ["_xpos","_ypos","_myLaserTarget","_lasertargets","_dir","_distance","_min","_target","_targetpos","_targetdistance","_aimpos"];

[color="Red"]_unit = _this select 0;    // _this = [man5][/color]

_lasertargets = nearestObjects[[color="Blue"]_unit[/color],["LaserTarget"],2000];
if(count _lasertargets == 0) exitWith{ObjNull};

_xpos = getPos [color="Blue"]_unit[/color] select 0;
_ypos = getPos [color="blue"]_unit[/color] select 1;

_myLaserTarget = ObjNull;

_dir = [color="blue"]_unit[/color] weaponDirection "Laserdesignator";
_dir = (_dir select 0) atan2 (_dir select 1);		// get direction of the lasermarker aiming at

_distance = 99;
_min =99;

for "_i" from 0 to (count _lasertargets - 1) do{
_target = _lasertargets select _i;
_targetpos = [getPos _target select 0,getPos _target select 1,0];

_targetdistance = _targetpos distance [getPos [color="blue"]_unit[/color] select 0, getPos [color="blue"]_unit[/color] select 1, 0];

_aimpos = [_xpos + _targetdistance*sin(_dir), _ypos + _targetdistance*cos(_dir),0];

if((_targetpos distance _aimpos)<_min) then{
	_myLaserTarget=_target;
	_min = _targetpos distance _aimpos;
};
};

if(_min>2) then{_myLaserTarget = ObjNull};

_myLaserTarget

init.sqf

(The line highlighted in red adds an action (in the Scroll-Menu) to the player 'man1', which you can change to specify only one person who can call in the Artillery. You can also, however, put the code on a trigger. Set the trigger to BLUFOR, Present, Once, and in the 'On Act.' box, type: 'man1 addAction ["Call Artillery on Laser", "arty.sqf"]', man1 being the interchangable part. If you do add that code to a trigger, then remove all lines highlighted in red and blue.)

getMyLaserTarget = compile (preprocessFileLineNumbers "func_getMyLaserTarget.sqf");
sleep 0.1; // give precompiling a break ^^
[color="Blue"]if(not isDedicated) then{ //prevent a ded. server to try to assign an action to a (then) non-existent player unit[/color]
   [color="Red"]if(player == man1) then{player addAction ["Call Artillery on Laser", "arty.sqf"]};[/color]
[color="Blue"]};[/color]

In your Mission you will also need:

  1. A static Artillery unit (Such as a D30 for OPFOR or M119 for BLUFOR).
  2. An Artillery Module (NOT VIRTUAL!) named 'artbatt' placed near to the Artillery Unit and synchronised with it.
  3. A Game Logic containing the simple code: 'artilleryuse = 0'.

That should be everything! This has worked out pretty well for me at least! Pretty much all the credit goes to Bon for his fantastic scripting work finding the Laser Designator. I pretty much just called in the artillery and made it limited! :)

Hope anyone that has any use for this has fun with it, and I'm happy to make slightly different versions of the script(s) if you need them, or I'm sure Bon would be happy to help out!

Cheers.

Edited by Fuzzy Bandit

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  

×