Jump to content
T. Miller

Need help fixing a bug!

Recommended Posts

I'm busy creating a custom Arma 3 gamemode that only takes place on the map and does not involve any actual units walking around. The gamemode is an RTS, and instead of moving actual units around, you move markers around on the map. I've been busy trying to fix a bug I've had for a while now, which really bothers me, mainly because I have tried a lot of things to fix it. I will explain the bug using images and code:

 

Moving units is fairly easy in the gamemode: You select the unit (using leftclick on the map), and you give it a waypoint (using alt-leftclick on the map). The specific unit, the waypoint position, and the speed of the unit will be added to a array, which for every second checks if the units position matches the waypoint position, and if not, moves the unit slightly towards it (with the given speed). Besides this, the waypoint position is also saved in the massive "A3A_units" variable, so I can use that position later. TL;DR: You can give a unit a moveorder, and the waypoint position gets saved.

 

Upon selecting a unit (if the unit has a waypoint), or giving a unit a waypoint, for a short period of time, a marker appears that shows the movement of that unit. Upon selecting a unit without a waypoint, nothing happens.

 

XmOU2er.jpg

 

Now here's the problem:

 

When I give a unit a waypoint, not only does it save the waypoint position for that unit, it also saves it for every other unit of the same type.

 

What's a type? When I spawn a new unit, this unit has certain stats, such as 'range', 'firepower', 'manpower', 'protection'. There is various different types of units, which each have different stats.

A3A_fnc_unitListCreate

private ["_unitList"];
_unitList =
[
	[
		"UT#001",
		"UT#002",
		"UT#003",
		"UT#004",
		"UT#005",
		"UT#006",
		"UT#007",
		"UT#008",
		"UT#009",
		"UT#010"
	],
	[
		[[[8,8],[0.5,0.5],[10,10],800,500,0.8,4,0,200,0,0],[5],[],[[],"b_inf", "Assault Squad"]],
		[[[4,4],[0.5,0.5],[10,10],1200,600,0.8,2,0,300,0,0],[5],[],[[],"b_recon", "Recon Squad"]],
		[[[1,1],[0.5,0.5],[10,10],800,500,0.8,2,0,800,0,0],[5],[],[[],"b_motor_inf", "HMMWV HMG"]],
		[[[1,1],[0.5,0.5],[10,10],800,800,0.8,2,0,400,0,0],[5],[],[[],"b_mech_inf", "M1 Abrams"]],
		[[[1,1],[0.5,0.5],[10,10],1500,0,0,2,0,800,0,0],[5],[],[[],"b_uav", "UAV"]],
		[[[8,8],[0.5,0.5],[10,10],800,500,0.8,2,0,200,0,0],[],[],[[],"o_inf", "Assault Squad"]],
		[[[4,4],[0.5,0.5],[10,10],1200,600,0.8,2,0,300,0,0],[],[],[[],"o_recon", "Recon Squad"]],
		[[[1,1],[0.5,0.5],[10,10],800,500,0.8,2,0,800,0,0],[],[],[[],"o_motor_inf", "HMMWV HMG"]],
		[[[1,1],[0.5,0.5],[10,10],800,800,0.8,2,0,400,0,0],[],[],[[],"o_mech_inf", "M1 Abrams"]],
		[[[1,1],[0.5,0.5],[10,10],1500,0,0,2,0,800,0,0],[],[],[[],"o_uav", "UAV"]]
	]
];

missionNameSpace setVariable ["A3A_unitList", _unitList, true];

This is how I spawn a new unit:

A3A_fnc_unitCreate

private ["_unitType","_unitLoc","_unitList","_unitName","_unitStats","_unit"];
_unitType = (_this select 0) select 0;
_unitLoc = (_this select 0) select 1;
_unitList = missionNameSpace getVariable "A3A_unitList";

_unitName = [] call A3A_fnc_unitAssignName;
_unitStats = _unitList select 1 select (_unitList select 0 find _unitType);
_unit = [_unitName, _unitLoc];

{
	_unit pushback _x;
} foreach _unitStats;

_unit;

This new unit will then be added to my giant variable "A3A_units", like so:

A3A_fnc_unitArrayUpdate

private ["_req","_input","_units"];
_req = _this select 0;
_input = _this select 1;
_units = missionNameSpace getVariable "A3A_units";

switch (_req) do {
	case 0: {
		// add input unit
		private ["_unit"];
		_unit = [_input] call A3A_fnc_unitCreate;
		
		_units pushBack _unit;
		missionNameSpace setVariable ["A3A_units", _units, true];
	};

So here's the problem again:

 

When I give a unit a waypoint, not only does it save the waypoint position for that unit, it also saves it for every other unit of the same type.

Here's the script with the problem:

A3A_fnc_unitMovementOrder

scopeName "scope1";

private ["_unitName","_orderPos","_units","_unit","_unitPos","_distance","_speed","_unitPosOffsetX","_unitPosOffsetY","_unitsPosTBU"];
_unitName = _this select 0;
_orderPos = _this select 1;

_units = missionNameSpace getVariable "A3A_units";

{
    if (_x select 0 == _unitName) then {
        _unit = _x;
        breakTo "scope1";
    };
} foreach _units;

(_unit select 5) set [0, [(_orderPos select 0), (_orderPos select 1)]];

missionNameSpace setVariable ["A3A_units", _units, true];

_unitPos = _unit select 1;
_distance = (((_orderPos select 0) - (_unitPos select 0))^2 + ((_orderPos select 1) - (_unitPos select 1))^2)^0.5;
_speed = _unit select 2 select 8;
_unitPosOffsetX = ((_orderPos select 0) - (_unitPos select 0)) /(_distance /(_speed / 1200));
_unitPosOffsetY = ((_orderPos select 1) - (_unitPos select 1)) /(_distance /(_speed / 1200));

_unitsPosTBU = missionNameSpace getVariable "A3A_unitPosTBU";

{
    if (_x select 0 == _unitName) then {
        _unitsPosTBU deleteAt (_unitsPosTBU find _x);
    };
} foreach _unitsPosTBU;

_unitsPosTBU pushback [_unitName, [_unitPosOffsetX, _unitPosOffsetY], _orderPos];
missionNameSpace setVariable ["A3A_unitPosTBU", _unitsPosTBU, true];

[_unitName] spawn A3A_fnc_unitMovementOrderMarker; 

The problem is the line

 

(_unit select 5) set [0, [(_orderPos select 0), (_orderPos select 1)]];

 

And the function that handles the marker:

A3A_fnc_unitMovementOrderMarker

scopeName "scope1";

private ["_unitName","_units","_scale","_unit","_unitPos","_orderPos","_markerPos","_markerLength","_markerDir","_mk1","_mk2","_time"];
_unitName = _this select 0;

_units = missionNameSpace getVariable "A3A_units";
_scale = ctrlMapScale (findDisplay 12 displayCtrl 51);

{
	if (_x select 0 == _unitName) then {
		_unit = _units select (_units find _x);
		breakTo "scope1";
	};
} foreach _units;

_unitPos = _unit select 1;
_orderPos = _unit select 5 select 0;

if (!([_orderPos,[]] call BIS_fnc_areEqual)) then {

	_markerPos = [(((_orderPos select 0) + (_unitPos select 0))/2),(((_orderPos select 1) + (_unitPos select 1))/2)];
	_markerLength = ((((_orderPos select 0) - (_unitPos select 0))^2 + ((_orderPos select 1) - (_unitPos select 1))^2)^0.5) / 2;
	_markerDir = atan ((abs((_orderPos select 1) - (_unitPos select 1)))/(abs((_orderPos select 0) - (_unitPos select 0))));
	if ((((_orderPos select 0) > (_unitPos select 0)) && ((_orderPos select 1) > (_unitPos select 1))) || (((_orderPos select 0) < (_unitPos select 0)) && ((_orderPos select 1) < (_unitPos select 1)))) then {_markerDir = -_markerDir};
	
	if (!(getMarkerColor "UMOM_1" == "")) then {
		deleteMarkerLocal "UMOM_1";
		deleteMarkerLocal "UMOM_2";
	};
	
	_mk1 = createMarkerLocal ["UMOM_1", _markerPos];
	_mk1 setMarkerDirLocal _markerDir;
	_mk1 setMarkerShapeLocal "RECTANGLE";
	_mk1 setMarkerBrushLocal "BORDER";
	_mk1 setMarkerSizeLocal [_markerLength,0];
	
	_mk2 = createMarkerLocal ["UMOM_2", _orderPos];
	_mk2 setMarkerShapeLocal "ICON";
	_mk2 setMarkerTypeLocal "waypoint";
	
	_time = 0;
	
	while {markerAlpha _mk1 > 0 && !(getMarkerColor "UMOM_1" == "")} do {
		if (_time > 1) then {
			_mk1 setMarkerAlpha ((markerAlpha _mk1) - 0.05);
			_mk2 setMarkerAlpha ((markerAlpha _mk2) - 0.05);
		};
		_time = _time + 0.05;
		sleep 0.05;
	};
	
	deleteMarkerLocal _mk1;
	deleteMarkerLocal _mk2;
};

Share this post


Link to post
Share on other sites

Here's the mission to test/debug:

 

Be sure to unpbo it.

To recreate the problem, execute this is the debug console:

[0,["UT#001", [16000,17000]]] call A3A_fnc_unitArrayUpdate; [0,["UT#004", [16200,17000]]] call A3A_fnc_unitArrayUpdate; [0,["UT#004", [16400,17000]]] call A3A_fnc_unitArrayUpdate; [0,["UT#005", [16600,17000]]] call A3A_fnc_unitArrayUpdate;

EDIT: I uploaded a zipfile instead: https://drive.google.com/file/d/0B6JuQdETqP8NX2o3N3lUbGRLclU/view?usp=sharing

Share this post


Link to post
Share on other sites

Be sure to unpbo it.

 

 

Just upload the mission folder as a 7z or zip, not a pbo, please.  This sounds like an interesting idea and seeing the whole code might be helpful with debugging.

Share this post


Link to post
Share on other sites

Just upload the mission folder as a 7z or zip, not a pbo, please.  This sounds like an interesting idea and seeing the whole code might be helpful with debugging.

You don't have a unpbo'er? Forgot to put the pbo in the reply anyway lol. Here's the file:

https://drive.google.com/file/d/0B6JuQdETqP8NX2o3N3lUbGRLclU/view?usp=sharing

 

I could potentially explain the bug or help explaining the gamemode if you join my teamspeak: 185.16.85.48:9990 (my name there is SSG T. Miller)

Share this post


Link to post
Share on other sites

Two possible sources of the problem are:

 

1. The fact that when the units are created, two units of the same type get info from the same array.

 

2. the 'set' command is bugged and has this as a side effect.

Share this post


Link to post
Share on other sites

1. You can use modern variable declaration style:

private _unitList = [ ...

2. You can simply improve your code in A3A_fnc_unitMovementOrder and A3A_fnc_unitMovementOrderMarker  (about GOTO-like commands: scopeName, breakOut, breakTo):

private _unit = {if (_x select 0 == _unitName) exitWith {_x}} forEach _units;
if (isNil {_unit}) then {
	// unit with given name not found
} else {
	// here we have unit record in _unit variable
};

3. Is there any reason to use  _unit = _units select (_units find _x)   instead of simple   _unit = _x ?

Share this post


Link to post
Share on other sites

Okay. Now I am understand who you using (_units select (_units find _unit) select 5) set [0,[]]   instead of (_unit select 5) set [0, []]

Do you understand difference between value and reference data types? Arrays in ARMA are reference type. For example:

private _a = [1,2,3];
private _b = _a;
_a set [0, 100500];
//Result: _a=[100500, 2, 3] and _b=[100500, 2, 3] too because _a and _b references to same array 

So, this code works fine: (_unit select 5) set [0, []]

 

 

 

And problem is... each unit record as element 5 has REFERENCE to array SHARED between all units of same type!

 

 

Quick and dirty solution is CLONE element 5 each time you create new unit. In fn_unitCreate.sqf:

{
	_unit pushback _x;
} foreach _unitStats;


_unit set [5, (_unit select 5) apply {_x}];


_unit;

But better is to split data in unit record to UNIT_TYPE_REFERENCE_IMMUTABLE_DATA and UNIT_PERSONAL_MUTABLE_DATA

 

 

FixedMission -  fn_unitCreate.sqf (added code to clone 5 element)

 

 

UPD: Picture to better understand nature of references ^^

Share this post


Link to post
Share on other sites

1. You can use modern variable declaration style:

private _unitList = [ ...

2. You can simply improve your code in A3A_fnc_unitMovementOrder and A3A_fnc_unitMovementOrderMarker  (about GOTO-like commands: scopeName, breakOut, breakTo):

private _unit = {if (_x select 0 == _unitName) exitWith {_x}} forEach _units;
if (isNil {_unit}) then {
	// unit with given name not found
} else {
	// here we have unit record in _unit variable
};

3. Is there any reason to use  _unit = _units select (_units find _x)   instead of simple   _unit = _x ?

1. Is that quicker/more efficient?

2.

_unit = {if (_x select 0 == _unitName) exitWith {_x}} forEach _units;

Wow, this is a lot more compact than what I wrote.

if (isNil {_unit}) then {
	// unit with given name not found
} else {
	// here we have unit record in _unit variable
};

What is the purpose of this code?

 

 

 

Okay. Now I am understand who you using (_units select (_units find _unit) select 5) set [0,[]]   instead of (_unit select 5) set [0, []]

Do you understand difference between value and reference data types? Arrays in ARMA are reference type. For example:

private _a = [1,2,3];
private _b = _a;
_a set [0, 100500];
//Result: _a=[100500, 2, 3] and _b=[100500, 2, 3] too because _a and _b references to same array 

So, this code works fine: (_unit select 5) set [0, []]

 

 

 

And problem is... each unit record as element 5 has REFERENCE to array SHARED between all units of same type!

 

 

Quick and dirty solution is CLONE element 5 each time you create new unit. In fn_unitCreate.sqf:

{
	_unit pushback _x;
} foreach _unitStats;


_unit set [5, (_unit select 5) apply {_x}];


_unit;

But better is to split data in unit record to UNIT_TYPE_REFERENCE_IMMUTABLE_DATA and UNIT_PERSONAL_MUTABLE_DATA

 

 

FixedMission -  fn_unitCreate.sqf (added code to clone 5 element)

 

 

UPD: Picture to better understand nature of references ^^

So, this code works fine: (_unit select 5) set [0, []]

Yeah, I already used this instead of the other 1 in the other two functions. I actually didn't realize these two lines in this function.

And problem is... each unit record as element 5 has REFERENCE to array SHARED between all units of same type!

Well yeah fuck, rip.

Quick and dirty solution is CLONE element 5 each time you create new unit. In fn_unitCreate.sqf:

If I in future functions have to edit the other values of the unit as well, won't I bump in the same problem?

Share this post


Link to post
Share on other sites
Do you understand difference between value and reference data types? Arrays in ARMA are reference type.

 

No idea what those are.

Share this post


Link to post
Share on other sites

1. Is that quicker/more efficient?

 

2. What is the purpose of this code?

 

1. More compact at least

 

2. Check whether your desired record is found, otherwise, just the variable will contain a nil value, and when you try to use it you will receive an error

 

 

If I in future functions have to edit the other values of the unit as well, won't I bump in the same problem?

 
As i said earlier, good solution is not mix together pesistent static data and unit individual attributes data. Better when each unit record references to common class descriptor. For example:
UnitClasses = [// persistent, static and immutable data describing unit types
	["Tank_X",	TravelSpeed, TravelType, MarkerType, ...],
	["Tank_Y",	TravelSpeed, TravelType, MarkerType, ...],
	["Infantry_X",	TravelSpeed, TravelType, MarkerType, ...],
	["Infantry_Y",	TravelSpeed, TravelType, MarkerType, ...]
];

Units = [ // unit records, containing unit individual states and reference to unit class descriptor
	[ClassID, Position, [Waypoints], ...], // [2, [100, 200], [[300, 400]]]   <-- first element it is index for UnitClasses array record
	[ClassID, Position, [Waypoints], ...],
	[ClassID, Position, [Waypoints], ...]
];

private _unit = Units select 0;				// [ClassID, Position, [Waypoints], ...],
private _class = UnitClasses select (_unit select 0); 	// ["Infantry_X",	TravelSpeed, TravelType, MarkerType, ...],
private _speed = _class select 1;			// TravelSpeed

 

Also, you can simple create lightweight functions. For example:

A3A_fnc_DoSomething = {
	// do something
};

A3A_fnc_DoSomethingElse = {
	// do something else
};

// anywhere in mission scripts
call A3A_fnc_DoSomething;
[argument1, argument2] spawn A3A_fnc_DoSomethingElse;

This technique encourages generalize the same parts of the algorithms:

// in Functions.sqf
A3A_fnc_GetUnitWithName = {
	{if (_x select 0 == _this) exitWith {_x}} forEach A3A_units};

// in init.sqf
call compile preprocessFileLineNumbers "Functions.sqf";

// in A3A_fnc_unitMovementOrder
private _unit = _unitName call A3A_fnc_GetUnitWithName;

// in A3A_fnc_unitMovementOrderMarker
private _unit = _unitName call A3A_fnc_GetUnitWithName;

Share this post


Link to post
Share on other sites

 

As i said earlier, good solution is not mix together pesistent static data and unit individual attributes data. Better when each unit record references to common class descriptor. For example:

UnitClasses = [// persistent, static and immutable data describing unit types
	["Tank_X",	TravelSpeed, TravelType, MarkerType, ...],
	["Tank_Y",	TravelSpeed, TravelType, MarkerType, ...],
	["Infantry_X",	TravelSpeed, TravelType, MarkerType, ...],
	["Infantry_Y",	TravelSpeed, TravelType, MarkerType, ...]
];

Units = [ // unit records, containing unit individual states and reference to unit class descriptor
	[ClassID, Position, [Waypoints], ...], // [2, [100, 200], [[300, 400]]]   <-- first element it is index for UnitClasses array record
	[ClassID, Position, [Waypoints], ...],
	[ClassID, Position, [Waypoints], ...]
];

private _unit = Units select 0;				// [ClassID, Position, [Waypoints], ...],
private _class = UnitClasses select (_unit select 0); 	// ["Infantry_X",	TravelSpeed, TravelType, MarkerType, ...],
private _speed = _class select 1;			// TravelSpeed

Some of the values of the classes (such as subUnits or veterancyLevel) should be able to be changed later on in the game as well. What would the unitCreate look like then (considering I'd have to use the 'apply' command like; alot.)

Or would you reckon it would be easier if I take these values out of the class and apply them in another way?

Share this post


Link to post
Share on other sites

Some of the values of the classes (such as subUnits or veterancyLevel) should be able to be changed later on in the game as well.

 

Right, just copy that values from unit class to unit record. To deep array copy use _resultArray = _sourceArray apply {_x}

 

Also:

private _units = missionNameSpace getVariable "A3A_unitList";
{ ... } forEach _units;

Equal to:

{ ... } forEach A3A_unitList; // A3A_unitList - just global variable

And:

private _units = [ ... ];
missionNameSpace setVariable ["A3A_unitList", _units, true];

Equal to:

A3A_unitList = [ ... ];    // assign/initialize global variable
publicVariable "A3A_unitList";  // command broadcast variable and its value to all computers

Share this post


Link to post
Share on other sites

 

Right, just copy that values from unit class to unit record. To deep array copy use _resultArray = _sourceArray apply {_x}

 

So if I want to make all values of the unit record changeable, I'd call this code:

_unit = _unit apply {_x};

Or is that not sufficient?

Share this post


Link to post
Share on other sites

So if I want to make all values of the unit record changeable, I'd call this code:

_unit = _unit apply {_x};

Or is that not sufficient?

Never mind didnt work. What would be the most efficient way of making every value changeable?

Share this post


Link to post
Share on other sites

Treat it like a Class.  You setup variables in your init.sqf which define the default values for what a unit can be.  When you spawn a unit you copy that to a new variable.  Then you make all your changes to the copied unit rather than your base template.  Need another unit?  Make another copy.  Need a different unit?  Make a copy and adjust it's type or whatever.  So you only ever change values in your copies.  Perhaps setup another array with the variables of your units to keep track of them.

Share this post


Link to post
Share on other sites

So if I want to make all values of the unit record changeable, I'd call this code:

 

This code make full copy of array, instead of _resultArray = _sourceArray that make reference to the same array

Share this post


Link to post
Share on other sites

Treat it like a Class.  You setup variables in your init.sqf which define the default values for what a unit can be.  When you spawn a unit you copy that to a new variable.  Then you make all your changes to the copied unit rather than your base template.  Need another unit?  Make another copy.  Need a different unit?  Make a copy and adjust it's type or whatever.  So you only ever change values in your copies.  Perhaps setup another array with the variables of your units to keep track of them.

This is exactly what I have.

There is a array which contains all the stats for all the classes.

The problem is that when I change a value for a copied unit of one class, all units of that same class get the same value, due to:


_array1 = [1,2,3];
_array2 = _array1;
_array1 set [0,3];

result:
_array1 = [3,2,3];
_array2 = [3,2,3];

desired result:
_array1 = [3,2,3];
_array2 = [1,2,3];

Share this post


Link to post
Share on other sites

This code make full copy of array, instead of _resultArray = _sourceArray that make reference to the same array

So what code works? I tried a few things but it didn't work.

Share this post


Link to post
Share on other sites

Never mind didnt work. What would be the most efficient way of making every value changeable?

Arma scripting language has no constant variables, any variable changeable by default. Change variable value during mission or not is only your decision. In your case, for example, we have infantry squad template with sub-units [squad-leader, grenadier, rifleman]. During mission you create squad instance based on this template. In squad template record you placing FULL COPY of sub-units array, NOT just a link to array from squad template, because you want to change units composition in squad instance, but do not want to this changes affect to sub-unit array in squad template. Right?  

Share this post


Link to post
Share on other sites

0UYcOVD.jpg

 

At least in this super simple version using the + operator to copy was a lot faster than using apply for the initial copy though.

 

+ was 0.0029ms

apply was 0.0054ms

 

With 400+ values it got worse.  apply was 0.35ms while + was 0.054m. So probably want to use:

myUnit = +templateUnits;

instead of apply to just make a copy.

  • Like 1

Share this post


Link to post
Share on other sites
desired result:
_array1 = [3,2,3];
_array2 = [1,2,3];

So you still do not understand difference between two links to same array and two links to different arrays with equal contents?

Share this post


Link to post
Share on other sites

Arma scripting language has no constant variables, any variable changeable by default. Change variable value during mission or not is only your decision. In your case, for example, we have infantry squad template with sub-units [squad-leader, grenadier, rifleman]. During mission you create squad instance based on this template. In squad template record you placing FULL COPY of sub-units array, NOT just a link to array from squad template, because you want to change units composition in squad instance, but do not want to this changes affect to sub-unit array in squad template. Right?  

I guess yeah. When I create a unit of a certain class, it - for instance - has 8 outta 8 subunits (meaning a squad of 8). When during the game, one of these dies, I want to lower the subunit count to 7, without changing it in the class (obviously).

 

In other words: How do I use this line for every value?

_unit set [5, (_unit select 5) apply {_x}]; 

(a.k.a. what would it look like in the following function)

/*
	@file_name: fn_unitCreate.sqf
	@file_edit: 11/06/2016
	@file_description: ...
*/

private ["_unitType","_unitLoc","_unitList","_unitName","_unitStats","_unit"];
_unitType = (_this select 0) select 0;
_unitLoc = (_this select 0) select 1;
_unitList = missionNameSpace getVariable "A3A_unitList";

_unitName = [] call A3A_fnc_unitAssignName;
_unitStats = _unitList select 1 select (_unitList select 0 find _unitType);
_unit = [_unitName, _unitLoc];

{
	_unit pushback _x;
} foreach _unitStats;

_unit set [5, (_unit select 5) apply {_x}]; // only this 1 value is changeable now

_unit;

Share this post


Link to post
Share on other sites
private _ref = [Value_A, Value_B, Array_C, Array_D]; // unit reference,  Value_B and Array_C need to be copied to unit instance;
private _unt = [X, Y, Z, IndexOfReferenceInReferenceArray, (_ref select 1), +(_ref select 2)];

Second line creates unit record with unique to unit X, Y, Z values, index of reference record for this unit in class reference array, copy of Value_B (string\scalar\bool type), copy of Array_C

Share this post


Link to post
Share on other sites

In your current structure element 5 contains set of data: [WaypointArray, MarkerType, SquadDescription, UnknownScalarValue]. You need to redistribute this array. Waypoint array must be removed from unit references, because unit template can not have the route. MarkerType and SquadDescription should not be copied into unit instance array, because we can access them at any time from unit reference. Right?

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

×