Jump to content

Recommended Posts

Hey there! I've begun learning SQF scripting around a week ago, so still quite the newbie. I've been reading through the Fockers tutorial series (which I have yet to finish), and decided to try and get some practical experience making something, since that's the best way I learn code. My friend, who makes missions and does zeus stuff, suggested I make a patrol script. I liked the idea, and went along with trying to make a patrol script callable by execVM, accepting the calling unit and an array of Markers as parameters. The script works by sending the entire group to those markers assuming the group still exists and there's at least one unit alive in it. If the marker they are about to head to does not exist, the marker is deleted from the array and the loop starts from the beginning until a valid marker exists.

 

 

 

Irrelevant at this point as I can't test to make sure that actually works, since I can't seem to get an object param passed to the file. It looks like it only accepts arrays, which I am having trouble understanding. So I need to solve this one of two ways:

 

  1. I need to find a way to pass an object as a parameter and keep it intact when it reaches the function.
  2. Or, I need to find a way to take the array sent as the unit, and turn it back in to the object, which I don't know how to do.

 

 

This script is meant to be multiplayer friendly (despite me not having the slightest clue of how to code it for multiplayer) so please keep that in mind with the solution. My goal is not to just come to a solution to fix my issue, but to understand why it works, since I'm trying to learn SQF and understanding what the code is doing and why will help me advance in that goal. I appreciate the help and can try to give as much information as I can when asked.

 

 

 

This is in the team leader's init box:

 

patrolArray = [patrolMarker1, patrolMarker2, patrolMarker3];
PatrolScriptHandle = [this, patrolArray] execVM "Patrol.sqf";

 

 

This is the Patrol.sqf:

 

// Patrol.sqf
// Parameters:
// 0: (Object) Unit
// 1: (Array) Markers




params ["_unitObject", "_markerArray"];


debugPatrolRunning = True; // Use this only for debug purposes to see if the script is still running.
_unitMarkerPatrolArray = _this select 1;
_patrolArrayCount = (count _unitMarkerPatrolArray) - 1;
_unitGroup = group _this select 0;
_shouldRun = True;
while (_shouldRun) do
{
	scopeName "patrolCheck";
	sleep 0.1;
	for "_i" from 0 to _patrolArrayCount do
	{
		_unitsAlive = {alive _x} count units _unitGroup;
		_curMarkerSelection = _unitMarkerPatrolArray select _i;
		
		if ( isNull _curMarkerSelection ) then
		{
			_tempA = [_curMarkerSelection];
			_unitMarkerPatrolArray = _unitMarkerPatrolArray - _tempA;
			_tempA = nil;
			breakTo "patrolCheck";
		};
		
		if ( _unitsAlive >= 1 && {!isNull _unitGroup} ) then
		{
			// I don't think I can use the Marker object itself in getMarkerPos, so let's convert it to string first.
			_curMarker = format ["%1", _curMarkerSelection];
			_unitGroup move getMarkerPos _curMarker;
			sleep random [3, 5, 10];
		}
		else
		{
			_shouldRun = False;
			debugPatrolRunning = False;
			breakOut "patrolCheck";
		};
	};
};

 

Share this post


Link to post
Share on other sites

1. you don't need to pass global variables such your array of markers. They are existing for all scripts and the mission. But they are running locally (on each PC running the code), so sometimes you have to public them (make them common and synchronized on all PCs). Not your problem here;

2. an array of markers should be an array of strings: patrolArray = ["patrolMarker1", "patrolMarker2", "patrolMarker3"];

As patrolArray is a global variable, and you don't need to change it, you just have to place it anywhere. In init field, it's ok. In init.sqf (to be created at mission root) is also OK.

3. <this> is a special variable referring to the object with the current init field. So:

this execVM "patrol.sqf" should work. (no need to place parameter in an array if there is just one of it).

 

4. So, here, disregard params, _this select 0, _this select 1. Your code is OK but as you're in an init field, <this> does the trick. Just pay attention that, as you pass inside the sqf, now you need to refer to the local variable <_this>, and no more <this>. Same as:

this spawn { hint format ["this is my object: %1", _this] };

 

5. never let some //comments in an init field (or in a trigger).

 

6. a step further: here, you want to make a patrol for the group. In editor, the group icon has also an init field. So, don't script in a unit if you can directly script for the group!

place the code in the init field of the group. Now <this> refers to the group!

 

7. to make this work in MP, there is no real problem here. Just run the working code (not yours!) on server only if you don't want to "reset" (rerun) it on each Join In Player (JIP).

 

My contrib, in init field of the group (no sqf needed for so little code):

 

patrolArray = ["patrolMarker1","patrolMarker2","patrolMarker3"];
if (isServer) then {
  this spawn {
    _unitGroup =  _this;
    _patrolArray = +patrolArray;
    while {count units _unitGroup > 0} do {
      sleep 1;
      for "_i" from 0 to count patrolArray - 1 do {
        _curMarker = _patrolArray deleteAt floor random count _patrolArray;
        _wp = _unitGroup addWaypoint [getMarkerPos _curMarker,0];
        _wp setWaypointType "MOVE";
        _wp setWaypointTimeout [3,5,10];
      };
      waitUntil {sleep 1; currentWaypoint _unitGroup == count patrolArray};
      while {(count (waypoints _unitGroup)) > 0} do {
        deleteWaypoint ((waypoints _unitGroup) select 0)
      };
      _patrolArray = +patrolArray;
    };
  };
};

 

The randomized path is more fun with 4 markers or more. With 3, you just get a rotation clock or anticlockwise.

If you don't understand a command just have a look here: https://community.bistudio.com/wiki/Category:Scripting_Commands_Arma_3

 

Have fun!

 

Share this post


Link to post
Share on other sites
3 hours ago, pierremgi said:

1. you don't need to pass global variables such your array of markers. They are existing for all scripts and the mission. But they are running locally (on each PC running the code), so sometimes you have to public them (make them common and synchronized on all PCs). Not your problem here;

 

How would I make them public? Is there an article or specific resource I can read in to that will tell me more about the nuances and specifics of multiplayer scripting and how to synchronize things, what to synchronize, when to synchronize and such? It feels like one of the single most important things I should really learn, and I just haven't found as specific of information about it on the wiki so far.

 

 

3 hours ago, pierremgi said:

2. an array of markers should be an array of strings: patrolArray = ["patrolMarker1", "patrolMarker2", "patrolMarker3"];

As patrolArray is a global variable, and you don't need to change it, you just have to place it anywhere. In init field, it's ok. In init.sqf (to be created at mission root) is also OK.

 

I can't believe I didn't think of just making the array quotes instead of references to the actual marker objects. That would have saved me the process of converting, I wasn't thinking right on that one. Thank you for pointing that out! So basically a global variable is fully accessible in the entire mission by any script, and any machine including the server can also access it?

 

 

3 hours ago, pierremgi said:

3. <this> is a special variable referring to the object with the current init field. So:

this execVM "patrol.sqf" should work. (no need to place parameter in an array if there is just one of it).

 

Will that allow me to pass an object to the SQF file? I insist on doing this through an SQF file instead of a pure Init line, even if it will be harder. My reasoning is that I want to make an SQF file that any mission maker could just stick in the mission layout folder and call with only a few parameters in order to use its functionality (encapsulation). I want to try avoiding doing it on an Init line and making the mission maker have to do more work and assurances, but maybe I'm just not that good yet. I did play around with the idea of trying to create a logic module through Eden and maybe finding a way to use it as storage for objects, and then have the SQF file reference the object through the module instead? I make things more complicated than they should be, but it's because I'm always trying to figure out how to make my code more efficient, better, and robust, even if I'm nowhere near the level of proficiency in the language to be even trying to do that.

 

 

3 hours ago, pierremgi said:

4. So, here, disregard params, _this select 0, _this select 1. Your code is OK but as you're in an init field, <this> does the trick. Just pay attention that, as you pass inside the sqf, now you need to refer to the local variable <_this>, and no more <this>. Same as:

this spawn { hint format ["this is my object: %1", _this] };

 

So since it would be just one param, there's no reason to treat it as an array right? That is why select is no longer needed, because it's only one param being sent. I thought I read on the wiki for the param command that it counts single elements as a single element array though?

 

 

3 hours ago, pierremgi said:

 

5. never let some //comments in an init field (or in a trigger).

 

 

Do Eden Init boxes not properly parse line comments? Will the "comment" command still work fine as it's an actual command to signify a comment?

 

3 hours ago, pierremgi said:

 

6. a step further: here, you want to make a patrol for the group. In editor, the group icon has also an init field. So, don't script in a unit if you can directly script for the group!

place the code in the init field of the group. Now <this> refers to the group!

 

 

That is so much more efficient, I didn't even know a group counted as an Eden object to edit. I've learned something new, thank you! Do you think this object could somehow be stored in a logic module for reference by an SQF file?

 

3 hours ago, pierremgi said:

 

7. to make this work in MP, there is no real problem here. Just run the working code (not yours!) on server only if you don't want to "reset" (rerun) it on each Join In Player (JIP).

 

Using an 'if' condition to check if isServer is true hm? Will this solution work to multiplayer-proof pretty much any code? Does it get more complicated than that as far as writing multiplayer-ready code goes?

 

 

3 hours ago, pierremgi said:

 

My contrib, in init field of the group (no sqf needed for so little code):

 


patrolArray = ["patrolMarker1","patrolMarker2","patrolMarker3"];
if (isServer) then {
  this spawn {
    _unitGroup =  _this;
    _patrolArray = +patrolArray;
    while {count units _unitGroup > 0} do {
      sleep 1;
      for "_i" from 0 to count patrolArray - 1 do {
        _curMarker = _patrolArray deleteAt floor random count _patrolArray;
        _wp = _unitGroup addWaypoint [getMarkerPos _curMarker,0];
        _wp setWaypointType "MOVE";
        _wp setWaypointTimeout [3,5,10];
        waitUntil {sleep 1; currentWaypoint _unitGroup == count patrolArray};
        while {(count (waypoints _unitGroup)) > 0} do {
          deleteWaypoint ((waypoints _unitGroup) select 0)
        };
        _patrolArray = +patrolArray;
      };
    };
  };
};

 

 

Half of that code is a bit over my head I have to admit. I never thought you could add waypoints to a group and use that to make them travel to an area. I really want to try and avoid doing it all in an init box because I want to encapsulate my code to be used in other missions with relative ease as long as mission creators would have the knowledge necessary to know how to use the script (necessary markers and parameters). For my particular code I don't want it to delete the markers as it goes along it (perhaps an option I could put in later somehow), only check if the marker still exists just in case. I'll have to take some time to look at this code and understand it, definitely the waypoints portion. I should probably also continue in the Focker's scripting series as it might even answer some of my other questions above. Thank you for contributing this code, it's given me a whole new insight on how to approach my problem.

 

 

3 hours ago, pierremgi said:

The randomized path is more fun with 4 markers or more. With 3, you just get a rotation clock or anticlockwise.

If you don't understand a command just have a look here: https://community.bistudio.com/wiki/Category:Scripting_Commands_Arma_3

 

Have fun!

 

 

 

 

I only ask so many questions because I want to understand this. I hope you are not annoyed by my questions, it is my eagerness for knowing and understanding this language, in an attempt to make better scripts. I appreciate you answering this and I hope to press for more answers so that I can understand. Thank you for your patience and your help, a lot of times I feel like I'm alone in trying to figure out these problems and why they are happening with my code. It's good to be able to put this code up for a sort of peer review and gather even more information about how to improve it and get it to work.

Share this post


Link to post
Share on other sites

1. Variables : https://community.bistudio.com/wiki/Variables

2. yes

3. this spawn {some code} and this execVM "yourCode.sqf" are same. your sqf must contain the code.

4. params, param (see in commands) are useful for checking validity of the parameters. Here , no surprise with <this> referring to the group (or unit)

5. comment command works. // not

6. <this> refers only on where you write it. logic stays logic, in group icon  for a group, in unit  init field for a unit...

7. run on server. You don't need to addwaypoints on each PC! See the command Argument local/global effect local/global

8. I corrected my code for a bad placed }; copy/paste or run it in an sqf.

I don't delete the markers. I'm working with arrays. See arrays if you want to understand the +. https://community.bistudio.com/wiki/Array

 

Share this post


Link to post
Share on other sites
On 7/12/2017 at 11:09 PM, pierremgi said:

1. Variables : https://community.bistudio.com/wiki/Variables

2. yes

3. this spawn {some code} and this execVM "yourCode.sqf" are same. your sqf must contain the code.

4. params, param (see in commands) are useful for checking validity of the parameters. Here , no surprise with <this> referring to the group (or unit)

5. comment command works. // not

6. <this> refers only on where you write it. logic stays logic, in group icon  for a group, in unit  init field for a unit...

7. run on server. You don't need to addwaypoints on each PC! See the command Argument local/global effect local/global

8. I corrected my code for a bad placed }; copy/paste or run it in an sqf.

I don't delete the markers. I'm working with arrays. See arrays if you want to understand the +. https://community.bistudio.com/wiki/Array

 

 

 

I don't know if you'll still get this message, but I have looked through the code you have contributed and I have learned quite a bit. I did have one more question having to do with your code... looking in to how Waypoints worked on the wiki to understand your code (especially the Waypoint datatype itself), a default waypoint at the group's starting position is always created at index 0. When you go through the waypoints at the end to delete them, does it delete this default waypoint too? If it does, does it ever return? Is it even deletable?

 

 

The reason I ask is because of this line: waitUntil {sleep 1; currentWaypoint _unitGroup == count patrolArray};

 

 

If the deleteWaypoint loop were to delete the default waypoint, the line would need to be changed to count patrolArray - 1 to make sure there is no OOB error if I did my homework on this properly. The script still seemed to be working on the next iterations, but I want to make sure I have a straight answer to this question.

 

 

I've learned a good amount about waypoints thanks to your code and following up with the wiki on it. I was not able to find an answer to the questions I have just asked however. Thank you once again and I hope I can get an answer on this!

Edited by MrGamer100
Clarification on why I want to know the answer

Share this post


Link to post
Share on other sites

Yes, a waypoint 0 is automatically added (leader's or vehicle's position) but don't care with it's deletion. You'll find a slight difference between the first patrol track and the following ones, (last waypoint not reached at first) but nothing requiring to make the script more complex.

There is no generated error.

You can have a good idea to see how it works, placing a group of units with this patrol and follow it as player in the group BUT not leader. You can see the waypoints on markers and the tracks of the patrol. Stay on map. (don't place the waypoints too far and use time acceleration). Perhaps there are some game settings to enable if you want to see that on map. I didn't check mine.

 

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

×