Vytvářet a programovat mise pro singleplayer (SP) Arma 3 je ve své podstatě jednoduché, protože veškeré vyhodnocování probíhá na jednom stroji. Vyjma několika příkazů (např. netId) je naprostá většina v SP dostupná a využitelná. Ta pravá zábava a problémy přichází až s multiplayerem (MP).

Pro vytváření misí hratelných v multiplayeru je nutné si ujasnit několik základních pojmů. Jak asi tušíte, tak hraní probíhá tak, že se na serveru zvolí mise, který ji načte a umožní ostatním hráčům (klientům) si zvolit jednu z hratelných pozic. Posléze dojde k samotnému spuštění mise. Arma disponuje režimem, kdy jeden z hráčů může hru vytvořit, ostatní se k němu mohou připojit a on s nimi pak hraje, přičemž jeho stroj má specifickou pozici, protože vystupuje zároveň jako server i jako hráč.

Pokud tvoříme SP misi a chceme v něm spouštět nějaké příkazy, pak si většinou vystačíme s postupem, kdy si v adresáři mise vytvoříme soubor init.sqf a do něj vložíme požadovaný kód, jenž se následně při spuštění mise začne vyhodnocovat. V multiplayeru to funguje téměř stejně, akorát se v tomto případě (využití souboru init.sqf) příkazy provádějí na každém stroji zvlášť (a to včetně serveru). Pokud tak vytvoříme v init souboru proměnnou, tak si ji každý stroj nastaví u sebe a i když všichni budou mít pravděpodobně stejnou hodnotu, tak ji nemůže považovat za společnou proměnnou. Pokud bychom například do proměnné vkládali náhodnou číslici (příkaz random), tak s největší pravděpodobností bude na každém stroji obsahovat rozdílné číslo. Pokud bychom tak chtěli zajistit, že vytvoříme jednu proměnnou s náhodnou hodnotou, která bude na všech strojích stejná, pak by měl tuto hodnotu určit nejlépe server a přes příkaz publicVariable ji předat všem klientům. Pokud by tuto proměnnou takto publikoval někdo jiný, pak stejně dojde k předání serveru, který si ji sám uloží a automaticky předá všem připojeným klientům a navíc i těm, kteří se připojí dodatečně.

Pro programování MP misí je tak důležité vyjasnit si pojem lokality a vlastnictví. V SP je vše lokální vůči hráčově počítači a hráčův počítač je vlastníkem všech objektů a může s nimi tak manipulovat. V MP jsou ale některé objekty lokální vůči specifickému stroji. Co to přesně znamená? Jeden ze zásadních důsledků je to, že o chování daných objektů se stará právě jejich majitel vůči němuž jsou lokální. Příkladem budiž jednotka AI, jejímž velitelem je určen některý z hráčů. V ten moment jsou všechna AI v jeho jednotce lokální vůči jeho stroji a jak pro ostatní hráče, tak i pro server jsou tyto jednotky označované jako vzdálené (remote či nelokální). Ignorujme stav, kdy je hráč zároveň serverem.

Důsledkem je, že u mnoha příkazů je vyžadováno, aby předávaný objekt v parametru příkazu byl vůči stroji, který příkaz provádí, lokální a tedy v jeho vlastnictví. Aby to nebylo až tak prosté, tak je k tomu důležité navíc rozlišovat, zda efekt příkazu je označen též jako lokální či globální. Pokud je lokální, pak se změny projeví jen na stroji, kde byl příkaz proveden. Pokud by ale byl takový příkaz vyvolán na jiném klientovi či serveru, ke kterému není objekt lokální, pak se jeho efekt neprojeví. Obráceně ale platí, že pokud je efekt příkazu globální, pak se změna přenese na všechny stroje (skrz server) a projeví se na nich a je na místě si tak zajistit, aby se prováděl jen na jednom stroji (třeba na serveru).
Pokud mrknete na oficiální dokumentaci, pak efekt a požadavek lokálnosti rozpoznáte dle ikon uvedených většinou pod názvem příkazu (EL – lokální efekt, EG – globální efekt, AG – globální argument, LG – lokální argument).

Trošku více specifický význam použití lokálnosti nabízí Arma navíc v tom, že za pomocí některých příkazů jako například createVehicleLocal či createMarkerLocal lze vytvořit objekt, který je viditelný a dostupný jen na daném stroji a jenž se nepřenese na ostatní klienty.

Jak s tím pracovat? Od verze 1.50 je v Arma 3 dostupný příkaz remoteExec, který umožňuje spouštět či volat patřičné funkce na vzdálených strojích, které lze parametrem určit. Je možné tak kód (s omezeními stanovenými serverem) z jakéhokoliv stroje vyvolat buď na jiném specifickém klientovi, na všech připojených strojích, na serveru, na všech strojích mimo specifického klienta či na všech strojích mimo serveru (tedy na všech připojených klientech).

Při provádění kódu má má pak remoteExec, mimo jiné, k dispozici i příkaz remoteExecutedOwner, díky kterému je možné vyvolat z klienta kód, který se odešle k provedení na server, jenž v sobě obsahuje další volání remoteExec, které se postará o zaslání odpovědi přímo vyvolavateli kódu.
Příklad, který ukazuje, jak si klient zažádá o vyvolání vzdáleného volání jenž vrátí lokální čas na serveru a přes “hint” ho klientovi spustí (jen) na jeho počítači (jde o příkaz s lokálním efektem).

 {
 	(format ["Server time: %1", time]) remoteExec ["hint", remoteExecutedOwner]  // send hint command to execute to client as response
 } remoteExec ["call", 2]; // send code to execute to server as request

Lokalita má svá základní pravidla, ale může se během mise měnit, takže například prázdné vozidlo vložené v editoru je lokální vůči serveru. Pokud do něj ale nastoupí hráč jako řidič, pak se stane lokální vůči hráčově počítači. Pokud hráč přestane být velitelem skupiny a stane se jím AI, pak přestanou být AI v jednotce lokální vůči hráčově počítači a stanou se lokální vůči serveru. S tím souvisí i změna vlastnictví, takže server převezme kontrolu nad jejich chováním, kterou do té doby řídil počítač hráče, jenž velel.
S tím ale nemůžete automaticky počítat. Pomoci příkazu setGroupOwner je možné změnit ke kterému stroji je jednotka velená AI lokální (a stroj se tak stane i jejich vlastníkem). Obdobně pak server přes setOwner může měnit vlastníky dalších objektů.

Dříve bylo zmíněno, že je vhodné zajistit u příkazů s globálním dopadem, aby se prováděl jen jednou. Ukázkovým problémovým příkladem může být obyčejný spínač vložený v editoru, který se po spuštění mise stane lokálním na každému stroji (pokud tedy u něj není zaškrtnuta volba “pouze na serveru”). O jeho uplatnění a vyhodnocení se tak stará každý stroj sám. Pokud bychom do pole po aktivaci vložili příkaz, který má globální efekt, pak dosáhneme nechtěného stavu, kdy se daný příkaz provede tolikrát, kolik je připojených hráčů (a server). U mnoha příkazů by se zase nic tak zásadního asi nestalo, ale pokud bychom použili například příkaz addMagazineGlobal, tak by se volanému cíli přidalo tolik zásobníků, kolik je hráčů plus případný dedikovaný server, protože ten si pochopitelně vyhodnocuje spínače taktéž.

Za upozornění stojí zajímavost, že například příkaz addMagazine, který je dostupný už od první verze Flashpointu má v základní syntaxi (_unit addMagazine “16Rnd_9x21_Mag”) efekt lokální. Při použití rozšířeného zápisu umožňujícího zadat přímo počet přidávaných nábojů v zásobníku: (_unit addMagazine [“16Rnd_9x21_Mag”,6]) je jeho efekt oproti tomu globální. Proto je vhodné si zkontrolovat i alternativní syntaxi, protože se, jak je vidět, může výsledný efekt lišit.

Arma 3 obsahuje několik příkazů, díky kterým je možné zjistit, zda právě spuštěný skript je prováděn na připojeném počítači hráče (isPlayer), na serveru (isServer) či zda jde o server dedikovaný (isDedicated). Kód pro multiplayer tak můžeme klidně vložit do init.sqf souboru a podmínkami určit, kdo jej může spustit.

Mimo právě zmíněného souboru init.sqf dojde při spuštění mise i k možnému spuštění dalších specifických souborů (pokud existují) a tak je možné využít soubor initServer.sqf, který se provede jen na  serveru, initPlayerLocal.sqf, který se provede zase jen na počítači klientů, kteří si ve výběru pozice zvolí postavu a připojí do hry a obdobně skript initPlayerServer.sqf se spustí na serveru po takovém připojení hráče. Oba dva poslední zmíněné soubory mají výhodu, že jako první parametr obdrží objekt právě připojeného hráče a je tak možné s ním pracovat. Skripty se spustí i v případě, že se hráč k misi připojuje dodatečně později (join in progress), což skripty dostanou potvrzeno jako další parametr.

U pozdějšího připojení je dobré zmínit, že v Arma 3 se již víceméně většina nastavení synchronizuje a to včetně proměnných nastavených přes publicVariable a příkazů s globálním efektem.

Některé příkazy (právě třeba remoteExec) mají v parametrech uvedenou možnost, zda se jejich efekt má přenášet i na později připojené hráče, takže pokud bychom měli vytvořený spínač, jenž se spouští jen na serveru a při aktivaci (dejme tomu úspěšné splnění nepovinného úkolu) provede tyto příkazy:

{player addWeapon "NVGoggles"} remoteExec ["call",-2,true]; 
{systemChat "NVGoggles added"} remoteExec ["call",-2]; 

pak výsledkem bude, že při aktivaci u všech klientů mimo serveru (druhý parametr s hodnotou -2) bude proveden lokální příkaz s lokální působností, který jejich aktuální hráčské postavě přidá do výbavy noční vidění a zároveň díky třetímu parametru s hodnotou nastavenou na true se zajistí, že se příkaz provede i u klientů, kteří se k misi následně připojí dodatečně. Druhý příkaz obdobně vypíše v systemchatu všem klientům informaci o přidaném nočním vidění, ale později připojení hráči již tuto hlášku neobdrží. Takový kód by se měl použít jen v případě, že máme jistotu, že se kód nespustí v SP a nebo u hráče, který je zároveň serverem, protože by k jeho provedení vůbec nedošlo.

Příkaz jenž v multiplayeru má asi nejvíce specifické chování je player a to proto, že vrací, stejně jako v SP, objekt, který hráč ovládá, ale protože jsme v MP, tak na každém počítači logicky vrací objekt odlišný. Na dedikovaném serveru by pak měl vracet ObjNull.

Na závěr si uveďme názorný příklad na kterém si ukážeme některé dříve zmíněné záludnosti. Neberte jej jako návod, jak postupovat, ale jen jako prezentační ukázku.

Mějme misi, kde je v editoru vložena značka se jménem mainBase a v adresáři mise bude soubor initServer.sqf s obsahem

mainBox = "Box_IND_Wps_F" createVehicle markerPos "mainBase";
clearWeaponCargoGlobal mainBox;
clearMagazineCargoGlobal mainBox;
mainBox addWeaponCargoGlobal ["launch_NLAW_F",5]; // for all
mainBox addMagazineCargo ["16Rnd_9x21_Mag",1]; // only on server
publicVariable "mainBox";

Podstatou je, že server si vytvoří bednu s municí a protože je využit createVehicle a nikoliv createVehicleLocal, pak je tato bedna dostupná na všech strojích, ale zároveň pro ně zůstává nelokální (je remote). Tudíž všichni klienti v bedně uvidí jen globální obsah a tedy pět PCML odpalovačů. Pokud by byl hráč zároveň serverem, pak by viděl i zásobník do pistole. Na konci pro pochopitelnost uvádím publicVariable, abychom s bednou mohli pracovat v dalších souborech, které se volají dle definice postupně (nejdříve initServer.sqf na serveru, pak initPlayerLocal.sqf u hráče a nakonec initPlayerServer.sqf znovu na serveru).

V souboru initPlayerLocal.sqf mějme tento obsah:

mainBox addMagazineCargo ["16Rnd_9x21_Mag",5];
mainBox addMagazineCargoGlobal ["30Rnd_556x45_STANAG",1];

Jak už možná tušíte, tak při spuštění mise hráčem, který není server (a to i dodatečně připojeném) se pokusíme do bedny přidat zásobníky. První příkaz se ale neprovede, protože nejsme vlastníci bedny, kdežto druhý ano, protože zásobník přidáváme již globálně. Každé připojení nového hráče tak přidá do bedny jeden zásobník 5.56mm a protože jde o globální akci bude tento zásobník dostupný všem.

Pokud bychom chtěli přidat do této globální bedny lokální obsah jen pro zrovna připojeného hráče, pak bychom mu museli zajistit vlastnictví bedny. Jelikož příkaz owner funguje jen na serveru, tak následující obsah vložíme do souboru initPlayerServer.sqf, který se provede právě na něm.

params ["_unit", "_didJIP"];
mainBox setOwner (owner _unit);
{mainBox addMagazineCargo ["30Rnd_65x39_caseless_mag",2]} remoteExec ["call",_unit];

Zajistíme si tím změnu vlastníka bedny a přes remoteExec server vyvolá lokální přidání zásobníků do této bedny na stroji právě připojeného hráče, kterého skript dostane automaticky v parametru. Hráč pak v bedně uvidí své dva lokálně přidané 6.5mm zásobníky, které si může vzít. Bedna mu nadále zůstane ve vlastnictví.

Jaký to má vliv na ostatní?
S lokálním obsahem může manipulovat jen majitel bedny. Hráč, který by byl serverem, sice svůj zásobník do pistole v bedně stále jako jediný uvidí, ale vzít si ho již nebude moci, protože přestal být jejím vlastníkem.
Kdyby se v tento moment do hry připojil další hráč, pak by došlo opět k vyvolání initPlayerServer.sqf a na konci by novému hráči server vlastnictví předal. Nový hráč by si tak mohl vzít své dva úplně nové lokální 6.5 mm zásobníky, které vidí jen on sám, přičemž předchozí hráč by své dva stejně přidané zásobníky sice v bedně stále viděl (za předpokladu, že si je již nestihl vzít), ale již by s nimi, jako nevlastník bedny, nemohl manipulovat.
Pokud by nový vlastník z bedny zásobník vzal a do ní zase zpět vložil, pak by došlo k provedení akce nad globální bednou a vložený obsah by se již stal nelokálním a byl by tak dostupný všem hráčům, kteří by do bedny nahlédli bez ohledu na aktuální vlastnictví bedny.

Zmíněný postup byl čistě ilustrativní pro prezentaci vlastnictví a lokality. Pravděpodobně si v misích vystačíte s jednou globální bednou a globálním obsahem a žádné vlastnictví nebudete řešit. Případně si budete lokálně vytvářet u klientů bednu naplněnou obsahem specifickým pro daného hráče či typ zvolené postavy (jako to svého času dělala mise Evolution viz https://mission-design.epj.cz/individualni-bedna-s-munici-v-arma-multiplayeru/).

Jak je vidět, tak postupů jak psát a udržovat kód pro multiplayer je více a je tedy na každém tvůrci se rozhodnout, jak a kde bude svůj kód provádět. Je ale dobré mít tyhle základní informace na paměti, protože při špatné volbě se může zbytečná spousta kódu provádět na místě, kde nemá žádný efekt a tvořit tak nepatřičnou zátěž či dokonce se může efekt nežádoucím způsobem zbytečně opakovat či kumulovat.

Ať se daří.