jwllorens 43 Posted July 10, 2016 So I am pretty new to ArmA scripting. I want to make a multiplayer PVP mission that incorporates AI on both teams. I have written a script, basically a function, that spawns an AI soldier with a customized loudout. I intend to run this function on a dedicated server only, or headless client if it exists. For units that are under player control, it would run on the client only. If anyone could take a look and point out any obvious mistakes I am making in terms of optimization, it would be greatly appreciated. It is a bit long, though. Questions: Do I have to return "true" when using count to prevent rpt spam? Would it be worthwhile to build in the loadout definitions, currently in separate files, into the main function inside of a switch statement? Here is the function. _grp is the group to add the unit to. _cls is the "class" to use, which much match the name of an sqf that defines the class (example included after). _pos is the position to spawn the unit at. _kitUp defines whether to use higher caliber rifle and night vision. [false, true] would spawn soldier with lower caliber rifle and nv goggles. _sklUp defines skill upgrades. 1.5 would increment all skills by 1.5, for example. JFNC_SpawnUnit = { comment "------------------------------------------------------------------------------------------------------------- Set up parameters and variables. ---------------------------------------------------------------------------------------------------------------------"; params [ ["_grp", grpNull, [grpNull], 1], ["_cls", "EMPTY", [""], 1], ["_pos", [0,0,0], [[]], 3], ["_kitUp", [false,false], [[]], 2], ["_sklUp", 0, [0], 1] ]; comment "------------------------------------------------------------------------------------------------------------- Retrieve an array defined in AILoadouts\SoldierClassName.sqf. This array contains the base soldier classname, an array of custom equipment, and an array of individual skills. ---------------------------------------------------------------------------------------------------------------------"; _info = [(side _grp),(_kitUp select 0),(_kitUp select 1)] call (compile loadFile (format ["AILoadouts\%1.sqf", (toLower _cls)])); _ldt = _info select 1; comment "------------------------------------------------------------------------------------------------------------- Create the unit, turn off damage. Sleep ensures that remainder of script runs after default engine randomization has completed to prevent conflicts or overriding intended behavior (mostly ensures that headgear is spawned properly). ---------------------------------------------------------------------------------------------------------------------"; _unit = _grp createUnit [(_info select 0),_pos, [], 0, "CAN_COLLIDE"]; _unit allowDamage false; sleep 0.5; comment "------------------------------------------------------------------------------------------------------------- Remove all default equipment and items from the unit. ---------------------------------------------------------------------------------------------------------------------"; removeAllWeapons _unit; removeAllItems _unit; removeAllAssignedItems _unit; removeUniform _unit; removeVest _unit; removeBackpack _unit; removeHeadgear _unit; removeGoggles _unit; comment "------------------------------------------------------------------------------------------------------------- Add equipment and items from _classInfo. ---------------------------------------------------------------------------------------------------------------------"; if ((_ldt select 0)!= "EMPTY") then {_unit addHeadgear (_ldt select 0);}; if ((_ldt select 1)!= "EMPTY") then {_unit addGoggles (_ldt select 1);}; if ((_ldt select 2)!= "EMPTY") then {_unit forceAddUniform (_ldt select 2);}; {for "_i" from 1 to (_x select 1) do { if ((_x select 0)!= "EMPTY") then { _unit addItemToUniform (_x select 0);}; }; true } count (_ldt select 3); if ((_ldt select 4)!= "EMPTY") then {_unit addVest (_ldt select 4);}; {for "_i" from 1 to (_x select 1) do { if ((_x select 0)!= "EMPTY") then { _unit addItemToVest (_x select 0);}; }; true } count (_ldt select 5); if ((_ldt select 6)!= "EMPTY") then {_unit addBackpack (_ldt select 6);}; {for "_i" from 1 to (_x select 1) do { if ((_x select 0)!= "EMPTY") then { _unit addItemToBackpack (_x select 0);}; }; true } count (_ldt select 7); {if (_x != "EMPTY") then { _unit addWeapon _x;}; true } count ([_ldt select 8] + (_ldt select 10)); {if (_x != "EMPTY") then { _unit addPrimaryWeaponItem _x;}; true } count (_ldt select 9); {if (_x != "EMPTY") then { _unit linkItem _x;}; true } count (_ldt select 11); comment "------------------------------------------------------------------------------------------------------------- Set individual skills of unit. Skills are retrieved from _info, then incremented by _sklUp. ---------------------------------------------------------------------------------------------------------------------"; {_unit setSkill [_x, (((_info select 2) select _forEachIndex) +_sklUp)];} forEach ["aimingAccuracy", "aimingShake", "aimingSpeed", "commanding", "courage", "reloadSpeed", "spotDistance", "spotTime"]; comment "------------------------------------------------------------------------------------------------------------- Add a killed event handler to unit for cleanup, turn damage back on now that setup is complete, and return the unit object from the function. ---------------------------------------------------------------------------------------------------------------------"; _unit addEventHandler ["killed", {_this spawn JFNC_Cleanup;}]; _unit allowDamage true; _unit }; Here is an example of a script file, in this case it is called "medic.sqf" located in the AILoadouts folder in the mission. This file would define a "medic" loadout, for example, for both east and west factions and using one of two weapon options and with or without night vision equipment. _side difines whether to use the opfor or blufor loadout. _wUp defines whether to use higher caliber weapon or not. _nv defines whether to use night vision goggles and lasers or flashlight only. params [["_side", west, [west], 1], ["_wUp", false, [true], 1], ["_nv", false, [true], 1]]; _info = switch (_side) do { case (west): { _baseSoldier = "B_T_Medic_F"; _helmet = selectRandom ["H_HelmetB_Light_tna_F"]; _facewear = selectRandom ["G_Tactical_Clear"]; _uniform = selectRandom ["U_B_T_Soldier_F"]; _uniformItems = ([[["EMPTY",0]],[["EMPTY",0]]] select _wUp) + [["FirstAidKit", 1], ["16Rnd_9x21_Mag",3]]; _vest = selectRandom ["V_PlateCarrier1_rgr"]; _vestItems = ([[["30Rnd_556x45_Stanag",6]],[["30Rnd_65x39_caseless_mag",6]]] select _wUp) + [["HandGrenade", 2], ["SmokeShell", 2]]; _backpack = selectRandom ["B_AssaultPack_rgr"]; _backpackItems = ([[["EMPTY",0]],[["EMPTY",0]]] select _wUp) + [["Medikit",1],["FirstAidKit",10]]; _rifle = ["arifle_SPAR_01_khk_F","arifle_MXC_khk_F"] select _wUp; _attachments = ([["acc_flashlight"],["acc_pointer_IR"]] select _nv) + ["optic_Holosight_khk_F"]; _secondaryWeapons = ["hgun_P07_khk_F"]; _gear = [["ItemMap", "ItemCompass", "ItemWatch", "ItemRadio"], ["ItemMap", "ItemCompass", "ItemWatch", "ItemRadio", "NVGoggles_tna_F"]] select _nv; [_baseSoldier,[_helmet, _facewear, _uniform, _uniformitems, _vest, _vestItems, _backpack, _backpackItems, _rifle, _attachments, _secondaryWeapons, _gear]]; }; case (east): { _baseSoldier = "O_T_Medic_F"; _helmet = selectRandom ["H_HelmetSpecO_ghex_F"]; _facewear = selectRandom ["G_Balaclava_oli"]; _uniform = selectRandom ["U_O_T_Soldier_F"]; _uniformItems = ([[["EMPTY",0]],[["EMPTY",0]]] select _wUp) + [["FirstAidKit", 1], ["16Rnd_9x21_Mag",3]]; _vest = selectRandom ["V_HarnessO_ghex_F"]; _vestItems = ([[["30Rnd_580x42_Mag_F",6]],[["30Rnd_65x39_caseless_green",6]]] select _wUp) + [["HandGrenade", 2], ["SmokeShell", 2]]; _backpack = selectRandom ["B_FieldPack_ghex_F"]; _backpackItems = ([[["EMPTY",0]],[["EMPTY",0]]] select _wUp) + [["Medikit",1],["FirstAidKit",10]]; _rifle = ["arifle_CTAR_blk_F","arifle_Katiba_C_F"] select _wUp; _attachments = ([["acc_flashlight"],["acc_pointer_IR"]] select _nv) + ["optic_ACO_grn"]; _secondaryWeapons = ["hgun_Rook40_F"]; _gear = [["ItemMap", "ItemCompass", "ItemWatch", "ItemRadio"], ["ItemMap", "ItemCompass", "ItemWatch", "ItemRadio", "O_NVGoggles_ghex_F"]] select _nv; [_baseSoldier,[_helmet, _facewear, _uniform, _uniformitems, _vest, _vestItems, _backpack, _backpackItems, _rifle, _attachments, _secondaryWeapons, _gear]]; }; }; comment "------------------------------------------------------------------------------------------------------------- _skill array format: [aimingAccuracy, aimingShake, aimingSpeed, commanding, courage, reloadSpeed, spotDistance, spotTime] ---------------------------------------------------------------------------------------------------------------------"; _skill = [0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5]; _info pushBack _skill; _info Share this post Link to post Share on other sites
R3vo 2654 Posted July 10, 2016 Nothing to optimise on first glance. For being new to Arma 3 scripting, it looks incredibly well written. Well done! Share this post Link to post Share on other sites
Grumpy Old Man 3548 Posted July 10, 2016 You could replace all the weird " _item = selectRandom ["OnlyOneItemInArray"]". It's redundant if there's only one item in the array. Cheers Share this post Link to post Share on other sites
kylania 568 Posted July 10, 2016 You could replace all the weird " _item = selectRandom ["OnlyOneItemInArray"]". It's redundant if there's only one item in the array. Cheers Yeah, _item = "item"; is 10x faster, but it's still super fast and he's probably leaving it as selectRandom for code maintenance so he just adjusts the array instead of rewrites the line if he wants to add another possible item. My only pet peeve about it is using comment instead of /* */ :) Never been a fan of ---- lines in code since fixed width screens are rare these days. Share this post Link to post Share on other sites
fn_Quiksilver 1636 Posted July 11, 2016 you dont have to use TRUE in your count loops. If you're calling a function which has a return, the return has to be a number. for instance {_array pushBack _element} count _list; ^ will produce an error IIRC {0 = _array pushBack _element} count _list; ^ no error alternatively: {_array pushBack _element} forEach _list; ^ forEach does not expect a return ------------------- For optimization, get rid of the entire script and just use these ;) https://community.bistudio.com/wiki/getUnitLoadout https://community.bistudio.com/wiki/setUnitLoadout Dump the array generated with getunitloadout to a script copytoclipboard str (getunitloadout player) then work with that array in your script, inserting elements with randomized selections, and then use setUnitLoadout to apply the new loadout. 1 Share this post Link to post Share on other sites
fn_Quiksilver 1636 Posted July 11, 2016 Yeah, _item = "item"; is 10x faster, but it's still super fast and he's probably leaving it as selectRandom for code maintenance so he just adjusts the array instead of rewrites the line if he wants to add another possible item. My only pet peeve about it is using comment instead of /* */ :) Never been a fan of ---- lines in code since fixed width screens are rare these days. Using comment command is generally better than /* */ comments during development, since you can paste the former into debug console and execute without error, while the latter will cause errors 1 Share this post Link to post Share on other sites
jwllorens 43 Posted July 11, 2016 Using comment command is generally better than /* */ comments during development, since you can paste the former into debug console and execute without error, while the latter will cause errors So THATS why /* */ wasn't working when I was testing in the debug console. As for get and setUnitLoadout, I didn't know that existed. Re-writing my script to use that will take days, haha. Maybe I can come up with a script that will take a single unit iterate through the loadouts that I have made in the array format that I have, using getUnitLoadout and adding it to a string. So I can get all the loadouts converted in one go. I'll have to figure a way to replace certain array elements with an algorithm. Damn, I wish I knew about those commands earlier. Share this post Link to post Share on other sites
kylania 568 Posted July 11, 2016 If you're using get/setUnitLoadout you don't even need scripts. Just put down units and edit their loadout in Eden to match what you want. Set their init to: missileGuyLoadout = getUnitLoadout this;deleteVehicle this; They won't show up in game but you'll have a global variable, missileGuyLoadout, you can use to apply whatever they had to another unit with a quick: player setUnitLoadout missileGuyLoadout; 1 Share this post Link to post Share on other sites
Grumpy Old Man 3548 Posted July 11, 2016 I admit I didn't know about these commands either. This changes everything. *mad laughter commences* Cheers Share this post Link to post Share on other sites
sarogahtyp 1109 Posted July 11, 2016 (edited) you dont have to use TRUE in your count loops. If you're calling a function which has a return, the return has to be a number. for instance {_array pushBack _element} count _list; ^ will produce an error IIRC {0 = _array pushBack _element} count _list; ^ no error alternatively: {_array pushBack _element} forEach _list; ^ forEach does not expect a return ------------------- For optimization, get rid of the entire script and just use these ;) https://community.bistudio.com/wiki/getUnitLoadout https://community.bistudio.com/wiki/setUnitLoadout Dump the array generated with getunitloadout to a script copytoclipboard str (getunitloadout player) then work with that array in your script, inserting elements with randomized selections, and then use setUnitLoadout to apply the new loadout. I would not use the array returned by getUnitLoadout for hardcoding in a script. The reason is that the command is not final and the array structure may change with future updates. This could cause an error with setUnitLoadout in future.Until these commands r final i recommand to use setUnitLoadout only with the array returned by getUnitLoadout while mission runtime. Edit: kylania showed the correct way above until these commands r final. Edited July 11, 2016 by sarogahtyp 1 Share this post Link to post Share on other sites
jwllorens 43 Posted July 11, 2016 If you're using get/setUnitLoadout you don't even need scripts. Just put down units and edit their loadout in Eden to match what you want. Set their init to: missileGuyLoadout = getUnitLoadout this;deleteVehicle this; They won't show up in game but you'll have a global variable, missileGuyLoadout, you can use to apply whatever they had to another unit with a quick: player setUnitLoadout missileGuyLoadout; That is tricky. Additional scripting would still be needed to maintain the functionality of quickly swapping NV goggles in or out or swapping between two rifles and ammo. Also, it is a pain to get units to have some things in their loadout when just using the in-game arsenal. I can't make an ammo bearer, for example, that has the right ammo for his squad because the only items I can add in the arsenal is ammo for guns that he has equipped. My script solves that problem. I do think I will just change the format of the arrays so I can ditch all the loops that equip the unit and just use setUnitLoadout. Looking at the array that it takes, it looks pretty standardized in terms of array size, so it shouldn't be difficult to create this array on the fly inside the script and maintain that functionality. Edit: Now that I think about it, I can't really maintain the "dynamic" functionality of my loadout scripts by using editor-placed units. getUnitLoadout will return a static array from these units. To then insert dynamic objects (such as random eyewear) would require known information about the array structure that would be hardcoded into the script AFTER getUnitLoadout is called. I would have to replace elements in the array returned by getUnitLoadout with (selectRandom ["one","two","ect"]) or (["one","two"] select wUp) using this fixed information about the array structure. As a result, any change to the structure of the array as getUnitLoadout and setUnitLoadout are finalized would break the script. So using editor-placed units to call getUnitLoadout ultimately doesn't circumvent the problem of the array structure potentially changing in the future and breaking the script, assuming I want to maintain my own loadout functionality of including randomization and night vision/weapon caliber upgrades on the fly. For now, I will stick with my own custom loadout array structure and the required individual loops for iterating through it and setting the equipped items. In the future when setUnitLoadout and getUnitLoadout are finalized, I'll switch to the new array structure and replace all the equipment setting loops with a simple setUnitLoadout. Share this post Link to post Share on other sites