T. Miller 26 Posted June 8, 2016 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. 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
T. Miller 26 Posted June 8, 2016 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
kylania 568 Posted June 8, 2016 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
T. Miller 26 Posted June 8, 2016 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
T. Miller 26 Posted June 11, 2016 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
serena 151 Posted June 11, 2016 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
serena 151 Posted June 11, 2016 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
T. Miller 26 Posted June 12, 2016 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
T. Miller 26 Posted June 12, 2016 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
T. Miller 26 Posted June 12, 2016 Updated project (fixed loads of stupid things and added some cool new things): https://drive.google.com/open?id=0B6JuQdETqP8Nb0UwNnNjaHhBS2s Share this post Link to post Share on other sites
serena 151 Posted June 12, 2016 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
T. Miller 26 Posted June 12, 2016 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
serena 151 Posted June 12, 2016 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
T. Miller 26 Posted June 12, 2016 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
T. Miller 26 Posted June 12, 2016 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
kylania 568 Posted June 12, 2016 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
serena 151 Posted June 12, 2016 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
T. Miller 26 Posted June 12, 2016 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
T. Miller 26 Posted June 12, 2016 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
serena 151 Posted June 12, 2016 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
kylania 568 Posted June 12, 2016 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. 1 Share this post Link to post Share on other sites
serena 151 Posted June 12, 2016 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
T. Miller 26 Posted June 12, 2016 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
serena 151 Posted June 12, 2016 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
serena 151 Posted June 12, 2016 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