Visa ämne
Anrop » ArmA 3 » Editing
 Skriv ut ämne
Multiplayer scripting 101
Det här är en liten crash course i arma multiplayer scripting.

Jag kommer att gå igenom lokalitet tillsammans med script exempel, hur man kollar upp saker på bikin samt hur man kan använda verktyg så som CBA.
Den här guiden förutsätter att du har en åtminstonde extremt grundläggande förståelse för hur programmering fungerar så du kan läsa SQF syntax både här och på bikin.

Bikin eller Bohemia Interactives Community Wiki som den annars heter är en ovärderlig resurs när du ska scripta för arma då du hittar exakt hur ett kommando ska användas och sådan viktig information, Länken till bikin är:
https://community.bistudio.com/

Lokalitet
I arma så finns det två typer av kommandon, lokala och globala.
Ett lokalt kommando kommer bara att utföras hos den dator(spelare) som kommandot körs från och kommer inte märkas av någon annan spelare
Ett globalt kommando syncas med alla andra på servern så att alla spelare berörs

Varför gör man inte alla kommandon till globala?
Du behöver inte alltid att ett kommando ska vara globalt och i vissa tillfällen kan det t.om. vara bättre om det är lokalt, Tänk dig att du vill skapa en ammolåda med olika uppsättning vapen beroende på vem som tittar i den så för att uppnå den effekten kan du helt enkelt lägga till vapnena som lokala i lådan för varje spelare.
Men den absolut stora anledningen är att det är väldigt kostsamt att synca mellan datorer i arma och kan leda till desync om man gör det för ofta.


Lokalitet i praktiken
Nu vet du skillnad på globala och lokala kommandon men hur sätter man den informationen till praktiken?
Ett klassiskt exempel är att sätta ut ett fordon av någon slag och sedan skriva
Kod Källa  

this addItemCargo ["NVGoggles", 10];


addItemCargo är ett lokalt kommando då biki sidan för det kommandot inte har en liten "effect global" bild samt att om du scrollar ner så ser du addItemCargoGlobal under see also vilket indikerar att det finns en global variant av samma kommando.

Vad som har hänt nu är att om ni spelar 10 personer på en server så bör man tycka att det finns ett nvg för varje spelare i lådan och det är fel.
Initraden för alla enheter eller objekt som man placerar ut på kartan kommer att köras av alla klienter i ett uppdrag (alla spelare + servern om den är dedi) så varje spelare har skapat 10 lokala NVGs i fordonet oberoende av varandra så om en spelare går och plockar 2 NVG så kommer alla andra fortfarande se 10 NVG kvar i fordonet förutsatt att de inte har tagit något.

Då kanske du försöker med
Kod Källa  

this addItemCargoGlobal ["NVGoggles", 10];
i fordonets init istället men vad som händer istället är att varje spelare kommer att skapa 10 NVGs globalt dvs de syncas ut till alla andra spelare och då får du helt plötsligt 10 NVG per spelare dvs 100 NVG vilket inte heller är rätt, Hur löser man det här?

3 sätt.
Antingen så sätter du this addItemCargoGlobal ["NVGoggles", 1]; så kommer 1 NVG per spelare att skapas men det kommer att bli 30 anrop som ska syncas mellan varje spelare om man spelar ett op på 30 spelare så det är definitivt inte en lösning att föredra, se nästa.

Eller så använder du dig av init.sqf
Init.sqf är en fil som alltid körs när uppdraget har startat (vid kartvyn) som man helt enkelt skapar i samma katalog som uppdraget, Ett bra tillvägagångsätt är att se till att endast servern hanterar utplacering av objekt eller items vilket vi kan uppnå igenom att skriva följande i init.sqf:
Kod Källa  

if (isServer) then {
    stabtgb addItemCargoGlobal ["NVGoggles", 30];
    stabtgb addItemCargoGlobal ["sfp_ak5c", 30];
};

Den koden kommer i det här fallet att kolla om klienten som kör init.sqf är servern och i så fall skapa 30 NVGs och 30 AK5C i ett fordon som heter stabtgb, Om spelaren är en icke-server klient så ignoreras koden och därmed så är det endast servern som kör sina två kommandon och reducerar mängden sync trafik som det skapar.

Det tredje sättet är en blandning av de två ovanstående sätten i det att du kanske har många olika objekt som du vill sätta saker i och vill undvika att namnge objektet (t.ex. stabtgb som i exemplet ovan) där vi vill kunna använda objektets init men ändå se till att endast servern utför kommandot.
Detta kan vi åstakomma igenom att skapa en fil i våran uppdragskatalog som heter addCargo.sqf och skriva följande:
Kod Källa  

_object = _this select 0;
_item = _this select 1;
_amount = _this select 2;

if (isServer) then {
    _object addItemCargoGlobal [_item, _amount];
};


och sedan för varje fordon/låda/whatever init så skriver vi:
Kod Källa  

Nul = [this, "NVGoggles", 10] execVM "addCargo.sqf"

Vad som händer är att man helt enkelt passar vilket objekt vi ska lägga till sakerna till, klassnamnet (i det här fallet "NVGoggles") och hur många till en generisk funktion vi just skapat som kollar om klienten som kör scriptet är server och i så fall utför det.

Alla spelare på servern kommer att anropa addCargo.sqf men eftersom alla utom en inte är server så kommer alla att direkt avsluta det anropet så det är helt OK.

Triggers
Triggers är lite speciella och man måste vara försiktig med dessa, Låt säga att du skapar en trigger med Anybody present som condition och skriver hint "hej" i dess onAct fält, hint är ett lokalt kommando. Vad händer om någon går in i triggern?
Hej kommer att visas för alla.

Det är för att trots att när du sätter ut en trigger i kartan så skapas den för alla som kör uppdraget och trots att triggern i sig är lokal så finns den hos alla och om någon går in i triggern så kravet uppfylls så kommer hint därmed att köras för alla och därför visas hinten för alla trots att det är ett lokalt kommando.

Vill man att en trigger bara ska gå igång om spelaren går in i den kan man skriva
Kod Källa  

player in thisList
som condition på triggern, Det här kan vara smidigt om du vill t.ex. visa en teleport action på en flaggstång.

Så var försiktig med att utföra globala kommandon i triggers då det är lätt hänt att triggern i sig kan orsaka kommandot att uppföra sig som globalt trots att det är lokalt, Hade samma trigger i exemplet ovan använt ett globalt kommando t.ex. "sfp_su25" createVehicle (getMarkerPos "flygbasen");
I det exemplet så hade 30x su-25 skapats på en marker som heter "flygbasen" om det är 30 spelare inne på servern och någon går in i triggern. OBSERVERA att det är en liten "global" bild i början av artikeln vilket indikerar att kommandot är globalt.

[TODO: Lägg till information om triggers med radio aktivering här / måste kolla upp så jag är säker först]

Synca data och custom events
Utöver globala funktioner som BIS tillhandahåller så kan man synca egen information över nätverket t.ex. om du vill hålla en scoreboard eller civilian kill counter etc. Man kan även skapa egna events t.ex. att man kör en funktion hos allas datorer när något hände, Ett exempel på detta är Giants träningsuppdrag där han har en "power point" ruta som han kontrollerar med en action meny som triggar ett event hos allas datorer som visar en custom textur på "power pointen".

Det finns två sätt att synca data över nätverket (egentligen finns det fler men jag fokuserar på dessa två nu):
publicVariable
- Pros: Vanilla, funkar utan addons
- Cons: lite mer pill att hålla på med själv

CBA
- Pros: Enklare att sätta upp egna events och så
- Cons: gör att uppdraget kräver CBA men de flesta har CBA ändå :)

Vi börjar med publicVariable eller PVs som kort, du kan läsa om det här: https://community.bistudio.com/wiki/p...icVariable
Kort och gott så kan du skapa en variabel och köra publicVariable "variabel" så kommer den variabeln att finnas hos alla datorer på nätverket med det värdet som variabeln innehöll när du exekverade publicVariable.

Man kan konstruera ett event system med PVs med hjälp av en public variable event handler, se: https://community.bistudio.com/wiki/a...entHandler

Här är ett funktionellt exempel på ett PV event handler system:
Kod Källa  

/* PV EH för att avsluta uppdraget */
fnc_eh_win {
   hint "Mission Success";
   sleep 5;
   endMission "END1";   
};

/* PV EH för att visa ett meddelande */
fnc_eh_message {
   [format["%1", _this], 0, -0.1, 5, 0.5, -0.2] call bis_fnc_dynamicText;
};

/* Initialisera variablerna */
eh_win = [];
eh_message = "";

/* Sätt upp event handlers */
"eh_win" addPublicVariableEventHandler { [] call fnc_eh_win
"eh_message" addPublicVariableEventHandler { (_this select 1) call fnc_eh_message };

/* Ge server hosten lite actions */
if (isServer) then {
   player addAction ["Avsluta uppdraget", {
      eh_win = true;
      publicvariable "eh_win";   
      [] spawn fnc_eh_win; // Måste köras manuellt för publicVariable triggar inte lokalt      
   }];

   player addAction ["Skicka meddelande", {
      eh_message = "Admin abuse";
      publicVariable "eh_message";
      (eh_message) spawn fnc_eh_message; // Måste köras manuellt för publicVariable triggar inte lokalt
   }];
};


Nu blev det en del kod här jag går igenom det stegvis, Först så sätter jag upp en funktion som heter fnc_eh_win som visar ett meddelande och avslutar uppdraget sedan så sätter jag upp en funktion som heter fnc_eh_message som visar en stor fancy sträng över hela skärmen.

Därefter så initialiserar jag mina event variabler, detta är oftast en bra ide att göra så att man säkerställer att variablerna finns hos alla.
Sen så sätter jag upp mina event handlers som alla ska ha, när eh_win modifieras och syncas så ska funktionen fnc_eh_win köras och likaså när eh_message är modifierad och syncad så ska fnc_eh_message köras med ett argument som om vi tittar på addPublicVariableEventHandler är det nya värdet som eh_message har satts till.

Sist så har vi biten där jag endast vill ge action menyer till spelaren som hostar uppdraget och därför så kollar jag efter isServer.
Spelaren som hostar får 2 action menyer där den ena uppdaterar och syncar eh_win och den andra uppdaterar och syncar eh_message och båda två kommer att trigga funktionsanropen som skrevs högst upp. Så! Där har vi ett enkelt event system med publicVariable.

Samma sak går att göra med CBA fast smidigare / snyggare, Jag kan illustrera samma exempel fast med CBA:
Kod Källa  

/* EH för att avsluta uppdraget */
fnc_eh_win {
   hint "Mission Success";
   sleep 5;
   endMission "END1";   
};

/* EH för att visa ett meddelande */
fnc_eh_message {
   [format["%1", _this], 0, -0.1, 5, 0.5, -0.2] call bis_fnc_dynamicText;
};


/* Sätt upp event handlers */
["mission_win", { () spawn fnc_eh_win}] call CBA_fnc_addEventHandler;
["show_message", { (_this) spawn fnc_eh_message}] call CBA_fnc_addEventHandler;

/* Ge server hosten lite actions */
if (isServer) then {
   player addAction ["Avsluta uppdraget", {
      ["mission_win"] call CBA_fnc_globalEvent;      
   }];

   player addAction ["Skicka meddelande", {
      ["show_message", "Admin abuse"] call CBA_fnc_globalEvent;
   }];
};


Som du ser är första delen med funktionerna identisk men den stora skillnaden är att det är mycket mer läsbart och intiutivt hur man triggar event.
Du hittar CBA dokumentationen här:
https://dev.withsix.com/docs/cba/file...w-txt.html



TL;DR
Tänk på att om ett kommando körs lokalt eller globalt, både om kommandot i sig är globalt eller ej men samtidigt hur kommandot körs t.ex. i en trigger.
Försök att alltid designa din kod för att minimera mängden sync som måste ske då det kan orsaka desyncs eller lagg i uppdraget.
Använd bikin.


Jag tror att jag nöjer mig här tills jag kommit på något mer att ta upp, Ställ gärna frågor eller ge synpunkter så uppdaterar jag inlägget allt eftersom baserat på behov.
Redigerat av xealot den 2014-07-02 15:53
The enemy cannot predict your actions if you have no idea what you're doing.
Topp, ju fler guider som dyker upp ju mer blir intresserade att försöka sig på att göra uppdrag till Arma3. Oftast är det ju "hur man gör" som sätter problem för dom som vill komma igång. Local vs Global är en klassiker ! Så hoppas du fortsätter med flera exempel tex

Topp som sagt, bra läsning och bättre med lite mera utförligt skrivet än bara köra över läsaren Cool
Stay frosty !
Bra skrivet! Hade länge problem med lokala och globala triggers under gisslanuppdraget där lådor innehöll några saker för vissa spelare, och andra saker för andra spelare. Blev ofta lite tokigt.
Ett litet tillägg:
Just lokalitet och multiplayerscripting i allmänhet är svårt eller helt omöjligt att testa själv beroende på vad för script du har satt ihop, Mitt råd är att använda multiplayer editorn (Hosta en LAN server och när man väljer uppdrag så tar du Editor) och där kan du göra dina ändringar i script osv och sedan trycka preview så laddas uppdraget in och du kan spela det som vanligt och behöver du ändra så går du bara tillbaka till där man väljer uppdrag och tar editorn och laddar in ditt uppdrag igen, Mycket effektivt om man t.ex. vill snabbtesta saker med någon annan och behöver göra ändringar.

Ett annat viktigt tips är att du kan köra flera arma instanser samtidigt så du kan testa hur ett script, trigger eller dylikt uppför sig för olika spelare, Se den här länken för mer information om det:
http://feedback.arma3.com/print_bug_p...ug_id=3288
The enemy cannot predict your actions if you have no idea what you're doing.