Teraz jest Wt 16 kwi, 2024 23:41


Jednistki w JIP

Dział poświęcony edytorowi ArmA II i Operation Arrowhead, czyli tworzeniu misji.
  • Autor
  • Wiadomość
Offline
Avatar użytkownika

tsuki

Porucznik

Porucznik

  • Posty: 491
  • Dołączył(a): Cz 21 sty, 2010 22:33
  • Lokalizacja: z Nienacka

Jednistki w JIP

PostŚr 12 maja, 2010 17:34

Witam Panów i być może Panie.

Przejdę może od razu do rzeczy, bo sprawa zbyt prosta nie jest.

Robię pewien mały projekcik i potrzebuję pomocy przy wykrywaniu jednostek, które dołączyły jako JIP. Ogólny sschemat jest nastepujący:

Misja dynamicznie wypełnia wszelkie miasta, w okolicy których znaleźli się gracze odpowiednią ilością jednostek wojskowych i cywili zalezną od wielkości lokacji w obrębie całej wyspy. Jeżeli gracze oddalą się na odpowiednią odległość, to miasta są czyszczone, a jednostki sprzed wyczyszczenia są zapamiętywane aby później miasto wrócić do stanu sprzed "uśpienia". Wszystko to dzieje się praktycznie bez żadnego grzebania w edytorze, za pomocą samych skryptów. To wszystko na razie działa i ten system nazwałem sobie GCE.

W czym problem:
Misja jest przystosowane do gry z respawnami i jednostki, które mogą aktywować miasta nazwane są (w edytorze) jako GCE_p(liczba). Skrypt na początku przelatuje wszystkie cyferki od 0-64 i sprawdza, czy zmienna GCE_p(_i) jest zainicjalizowana, i jeżeli tak, to dodaje grupę tej jednostki, do puli sprawdzanych na odległość od miast. Problem tkwi w graczach, którzy dołoączyli w trakcie gry do grup, w których nie było żadnych jednostek (zablokowane AI) i nie zostały dodane na starcie. Szybkie testy wykazały, że owi gracze nie widzą siebie jako GCE_p(cyfra), ale jako <obj-null> (to wykazuje hint format["%1", player] w init.sqf). Potrzebuję jakiegoś sposobu, który pozwoliłby na przesłanie informacji na serwer, że dana jednostka pojawiła się na mapie i że musi zostać dodana do tablicy rozpoznawanych grup.

Czego nie chcę:

Coś takiego w polu odpalanym tylko u serwera w init.sqf:

Kod: Zaznacz cały
"GCE_Call" addPublicVariableEventHandler
{
if  !((group GCE_Call) in GCE_Groups) then
{
    GCE_Groups = GCE_Groups + [group GCE_Call];
};
};


I czegoś takiego w polu odpalanym tylko u klienta w init.sqf:
Kod: Zaznacz cały
GCE_Call = player;
publicVariable "GCE_Call";


Ponieważ istnieje szansa, że 2 graczy w tym samym czasie zrobi publicVariable "GCE_Call"; to skrypt może się posypać, lub jeden z graczy może nie byc rozpoznawany. Potrzebuję czegoś bardziej niezawodnego.

Jezeli potrzeba dalszych wyjaśnień - chętnie ich udzielę.
Obrazek
Offline
Avatar użytkownika

Kadryl

Major

Major

  • Posty: 883
  • Dołączył(a): Cz 14 wrz, 2006 14:04
  • Lokalizacja: Wa-wa

PostŚr 12 maja, 2010 19:50

A może wystarczy zabezpieczyć skrypt przed wywołaniem z dwóch rożnych źródeł kolejkując proces:

Kod: Zaznacz cały
@(blok)
blok = false
.
{wykonanie skryptu}
.
blok = true


p.s. jak inicjujesz AI w miastach ?
Offline
Avatar użytkownika

tsuki

Porucznik

Porucznik

  • Posty: 491
  • Dołączył(a): Cz 21 sty, 2010 22:33
  • Lokalizacja: z Nienacka

PostŚr 12 maja, 2010 20:05

Kadryl napisał(a):A może wystarczy zabezpieczyć skrypt przed wywołaniem z dwóch rożnych źródeł kolejkując proces:

Kod: Zaznacz cały
@(blok)
blok = false
.
{wykonanie skryptu}
.
blok = true


p.s. jak inicjujesz AI w miastach ?

Może omówie co się stanie jak odpalę cos takiego jak pisałem wyżej.

Strona serwera
"jeżeli zostanie wykryte wysyłanie do wszystkich zmiannek GCE_Call - sprawdź, czy grupa w niej zawarta była już zainicjalizowana. Jeżeli nie - dodaj ją do grup, których pozycje obserwujesz".

Strona klienta:
"Przy wejściu prześlij do wszystkich zmienną GCE_Call zawierającą odwołanie do twojej jednostki".

I teraz:
Na samym starcie gry każdy będzie się przedstawiał i będzie się to działo asynchronicznie (bez ładu i składu) i isnieje możliwość, że 2 graczy "przedstawi się" w tym samym czasie i serwer nie załapie jednej z nich co doprowadzi do tego, że główny skrypt będzie ignorował tę jednostkę.


p.s. jak inicjujesz AI w miastach ?

Skrypt "manageTown.sqf", który, jak nazwa wskazuje zarzadza poszczególnymi miastami ma 322 linie, więc jak widzisz to nie jest pytanie, na które jest krótka odpowiedź ;). Jak skończę GCE, to będziesz mógł sobie pogrzebać do woli.
Obrazek
Offline
Avatar użytkownika

Kadryl

Major

Major

  • Posty: 883
  • Dołączył(a): Cz 14 wrz, 2006 14:04
  • Lokalizacja: Wa-wa

PostŚr 12 maja, 2010 23:49

Metoda kolejowania była z sukcesem używana w crCTI przez Cleanrocka. Tworzenia jednostek w hangarach (obsługiwał serwer) musiało być uporządkowane bo mogło się zdażyć, iż kilku klientów naraz wysyłało żądanie budowy pojazdu.
Istotą tej metody jest to, że serwer nie odrzuca wywołania chociaż realizuje proces dla innego kompa, natomiast wstrzymuje działanie do moment spełnienia warunku @.
Może problem tutaj jest w wywołaniu execVM ?

A może wykonanie przenieść na lokalny komp ?
Kod: Zaznacz cały
if  !((group player) in GCE_Groups) then
{GCE_Groups = GCE_Groups + [group player];};
publicVariable "GCE_Groups";


Skrypt "manageTown.sqf",
- nie znam ale brzmi obiecująco :grin:
Offline
Avatar użytkownika

tsuki

Porucznik

Porucznik

  • Posty: 491
  • Dołączył(a): Cz 21 sty, 2010 22:33
  • Lokalizacja: z Nienacka

PostCz 13 maja, 2010 08:53

Kadryl napisał(a):Metoda kolejowania była z sukcesem używana w crCTI przez Cleanrocka. Tworzenia jednostek w hangarach (obsługiwał serwer) musiało być uporządkowane bo mogło się zdażyć, iż kilku klientów naraz wysyłało żądanie budowy pojazdu.
Istotą tej metody jest to, że serwer nie odrzuca wywołania chociaż realizuje proces dla innego kompa, natomiast wstrzymuje działanie do moment spełnienia warunku @.
Może problem tutaj jest w wywołaniu execVM ?

A może wykonanie przenieść na lokalny komp ?
Kod: Zaznacz cały
if  !((group player) in GCE_Groups) then
{GCE_Groups = GCE_Groups + [group player];};
publicVariable "GCE_Groups";


Wykonanie na lokalnym kompie nie zda egzaminu, bo GCE_Groups może być nieaktualne na kompie klienta - jezeli w tym samym czasie 2 wywoła tę komendę to późniejszy nadpisze wcześniejszego i będzie on ignorowany.

wymyśliłem coś takiego.PIsane z pamięci, więc mogą być błędy, ale chodzi o zasadę:
Przed inicjalizacją skryptów serwera:

Kod: Zaznacz cały
GCE_Init = false;

w polu odpalanym tylko u serwera:

Kod: Zaznacz cały
"GCE_Call" addPublicVariableEventHandler
{
   if  !((this select 0) in GCE_Groups) then
   {GCE_Groups = GCE_Groups + [this select 0];};
};
_a = execVM "JIPhandle.sqf";


po powyższym, w sekcji odpalanych dla wszystkich:
Kod: Zaznacz cały
waitUntil {GCE_Init};
while {!((group player) in GCE_Groups)} do
{
  GCE_Call = group player;
  publicVariable "GCE_Call";
  sleep 5;
};


Skrypt JIPhandle.sqf:

Kod: Zaznacz cały
if(!isServer) exitWith {};
GCE_Init = true;
while {true} do
{
  publicVariable "GCE_Groups";
  publicVariable "GCE_Init";
  sleep 1;
};


Czyli metoda brute force - klient zgłasza do serwera co 5s, że wszedł na serwer, aż otrzyma wiadomość, że został dodany. Problem polega na tym, że wczorajsze testy wykazały, że podczas, gdy kilenta jeszcze nie ma na mapie, to init.sqf jest już odpalany i komenda player zwraca obiekt null. Kiedy coś takiego trafi do GCE_Groups, sypie się skrypt visibleTowns.sqf (czyli główny skrypt GCE odpowiedzialny za obserwacje grup).

Jak wykryć, że gracz jest już fizycznie w grze i gra?

- nie znam ale brzmi obiecująco :D

Nie możesz znać, bo to mój skrypt i nie był jeszcze publikowany ;).
Obrazek
Offline
Avatar użytkownika

Kadryl

Major

Major

  • Posty: 883
  • Dołączył(a): Cz 14 wrz, 2006 14:04
  • Lokalizacja: Wa-wa

PostCz 13 maja, 2010 10:01

Dwie pętle z czego jedna obciąża cały czas serwer co 1s.
Zastanawiam się czy nie wystarczy:

Na początku gry w init serwera:
Kod: Zaznacz cały
if (! isServer) exitWith {};
GCE_Init = false; // blokowanie do zakonczenia aktywacji serwera
publicVariable "GCE_Init"; // wysłyłamy do lokalnych

//zapisujemy formułe aktywacji dodawania grup gdy zmieni się GCE_Call:
"GCE_Call" addPublicVariableEventHandler
{
   GCE_Init = false; // blokownie
   publicVariable "GCE_Init"; // wysłyłamy do lokalnych 
   if  !((this select 0) in GCE_Groups) then
   {GCE_Groups = GCE_Groups + [this select 0];};
   publicVariable "GCE_Groups"; //wysłanie info o stanie grup
  GCE_Init = true; // odblokowanie
  publicVariable "GCE_Init"; // wysyłamy do lokalnych
};
GCE_Init = true; // odblokowanie skryptu u graczy
publicVariable "GCE_Init"; // wysłyłamy do lokalnych


inicjowanie skryptu gdy włącza się gracz do gry i jest gotowy serwer:
Kod: Zaznacz cały
if(isServer) exitWith {};
waitUntil {GCE_Init};
while {!((group player) in GCE_Groups)} do
{
  GCE_Call = group player;
  publicVariable "GCE_Call";
  sleep 5;
};


tsuki napisał(a):Jak wykryć, że gracz jest już fizycznie w grze i gra?

Do tego stosuje się chyba warunek
Kod: Zaznacz cały
player == player
Offline
Avatar użytkownika

tsuki

Porucznik

Porucznik

  • Posty: 491
  • Dołączył(a): Cz 21 sty, 2010 22:33
  • Lokalizacja: z Nienacka

PostCz 13 maja, 2010 10:34

Do tego stosuje się chyba warunek
Kod: Zaznacz cały
player == player

O kurde, to jest genialne. Dzięki!
Masz +1 ;).

Kadryl napisał(a):Dwie pętle z czego jedna obciąża cały czas serwer co 1s.
Zastanawiam się czy nie wystarczy:

Na początku gry w init serwera:
Kod: Zaznacz cały
if (! isServer) exitWith {};
GCE_Init = false; // blokowanie do zakonczenia aktywacji serwera
publicVariable "GCE_Init"; // wysłyłamy do lokalnych

//zapisujemy formułe aktywacji dodawania grup gdy zmieni się GCE_Call:
"GCE_Call" addPublicVariableEventHandler
{
   GCE_Init = false; // blokownie
   publicVariable "GCE_Init"; // wysłyłamy do lokalnych 
   if  !((this select 0) in GCE_Groups) then
   {GCE_Groups = GCE_Groups + [this select 0];};
   publicVariable "GCE_Groups"; //wysłanie info o stanie grup
  GCE_Init = true; // odblokowanie
  publicVariable "GCE_Init"; // wysyłamy do lokalnych
};
GCE_Init = true; // odblokowanie skryptu u graczy
publicVariable "GCE_Init"; // wysłyłamy do lokalnych


inicjowanie skryptu gdy włącza się gracz do gry i jest gotowy serwer:
Kod: Zaznacz cały
if(isServer) exitWith {};
waitUntil {GCE_Init};
while {!((group player) in GCE_Groups)} do
{
  GCE_Call = group player;
  publicVariable "GCE_Call";
  sleep 5;
};



Czyli tak na dobrą sprawę wyszło, że od początku dostałem od Ciebie prawidłowe rozwiązanie, a najzwyczajniej w świecie go nie zrozumiałem. To jest oczywiście dobry pomysł i sorry, że nie załapałem od razu ;).

Jedyny moim zdaniem błąd jest tu:

Kod: Zaznacz cały
if(isServer) exitWith {};
waitUntil {GCE_Init};
while {!((group player) in GCE_Groups)} do
{
  GCE_Call = group player;
  publicVariable "GCE_Call";
  sleep 5;
};


A powinno być tak:

Kod: Zaznacz cały
while {!((group player) in GCE_Groups)} do
{
  waitUntil {GCE_Init};
  GCE_Call = group player;
  publicVariable "GCE_Call";
  sleep 5;
};


a) serwer nie musi być dedykowany i może być jako player
b) skrypt czekałby tylko raz aż pojawi się GCE_Init

Peace.



EDIT:
Coś sobie przypomniałem:
JIPhandle.sqf działa cały czas, bo gracze JIP po podłączeniu nie mają informacji o zmiennych, które wcześniej synchronizowałem z graczami, czyli GCE_Groups i GCE_Init nie będą u niego nawet zainicjalizowane. Połącze mój stary sposób z kolejkowaniem twojego i będzie git.

EDIT2:
Problem rozwiązany. W ramach rewanżu za pomoc, oto plik init.sqf:

Kod: Zaznacz cały
"publicHint" addPublicVariableEventHandler {hint format["%1", _this select 1]};
GCE_Groups = [];

_eh = execVM "scripts\actionMgt.sqf";
GCE_Init = false;
if(isServer) then
{
   GCE_MarkersWest = [];
   GCE_MarkersEast = [];
   GCE_Querry = [];
   for "_i" from 1 to 64 do
   {
      _potentialPlayer = format ["GCE_p%1", _i];
      if !(isNil _potentialPlayer) then
      {   
         call compile format["
         if !((group GCE_p%1) IN GCE_Groups) then
         {
            GCE_groups = GCE_groups + [group GCE_p%1];
         };
         ", _i];
      };
   };
   "GCE_Call" addPublicVariableEventHandler
   {
      GCE_Init = false; // blokownie
      publicVariable "GCE_Init"; // wysłyłamy do lokalnych
      if !((_this select 1) in GCE_Groups) then
      {
         GCE_Groups = GCE_Groups + [(_this select 1)];
      };      
      GCE_Init = true; // odblokowanie
      publicVariable "GCE_Init"; // wysłyłamy do lokalnych
   };
   _a = execVM "scripts\JIPManagement.sqf";
   _a = execVM "scripts\visibleTowns.sqf";
};

_str = "Inicjalizacja skryptow serwera zakonczona\n";
_str =_str + "Oczekiwanie na koniec ładowania\n";
waitUntil {!(isNil {player}) AND (player == player)};
_str = _str + "Ladowanie zakonczone\n";

   
while {!((group player) IN GCE_Groups)} do
{
   waitUntil {GCE_Init};
   GCE_Call = group player;
   publicVariable "GCE_Call";
   _str =_str  + "Wysylanie danych grupy...\n";   
   hintSilent _str;
   sleep 3;
};
_str = _str + "Inicjalizacja grupy zakonczona pomyslnie!";
hint _str;

if(true) exitWith {};
Obrazek
Offline
Avatar użytkownika

kondor

Major

Major

  • Posty: 880
  • Dołączył(a): Pt 11 lut, 2005 04:00
  • Lokalizacja: Berlin (wschodni)

PostN 16 maja, 2010 12:28

1. A jeśli gracz z wyłączonego dla AI slotu wyjdzie? Zmienna GCE_pNUMER będzie nullem!
2. Sprawdzałeś komendy: http://community.bistudio.com/wiki/onPlayerConnected oraz http://community.bistudio.com/wiki/onPlayerDisconnected - jeśli w nich da się pobrać obiekt dołączanego gracza, wówczas cała ta synchronizacja, w której być może chowa się kilka bugów, nie będzie potrzebna
3.
Kod: Zaznacz cały
waitUntil {!(isNil {player}) AND (player == player)};
player nigdy nie jest nilem, lecz (gdy JIP) nullem, czyli powinno być ! isNull player. Ale wystarczy sprawdzać 1 warunek, czyli player == player wystarczy
Kod: Zaznacz cały
if(true) exitWith {};
na końcu skryptu to jest niepotrzebne, chyba, że ten skrypt jest includowany poprzez "#include"
Offline
Avatar użytkownika

tsuki

Porucznik

Porucznik

  • Posty: 491
  • Dołączył(a): Cz 21 sty, 2010 22:33
  • Lokalizacja: z Nienacka

PostN 16 maja, 2010 19:10

Oglądałem te komendy, ale z tego, co tam jest napisane przekazywane jest _name i _id, z czego pierwsze, to jak rozumiem imię gracza, a więc niezbyt przydatne, ale być może jestem przyślepy ;).

Edit:

Co do drugiej sprawy - pusty GCE_pNUMBER nie boli mnie w żaden sposób, bo dodawana jest grupa i wszystkie jednostki w niej s a sorawdzane na odległość poprzez units(group). Po wyjściu jednostki bez włączonego AIka i jeżeli jest ostatnią w niej jednostką, to w GCE_Groups zostaje <grpnull>, które jest usuwane przez JIPmanagement.sqf.

Ale dzięki za uwagę :).
Obrazek
Offline
Avatar użytkownika

kondor

Major

Major

  • Posty: 880
  • Dołączył(a): Pt 11 lut, 2005 04:00
  • Lokalizacja: Berlin (wschodni)

PostPn 17 maja, 2010 17:18

Oglądałem te komendy, ale z tego, co tam jest napisane przekazywane jest _name i _id, z czego pierwsze, to jak rozumiem imię gracza, a więc niezbyt przydatne, ale być może jestem przyślepy ;) .

Możesz pobrać jednostkę poprzez badanie w pętli, czy któreś z (name GCE_pNUMBER) nie równa się _name. Czyli coś takiego:
Kod: Zaznacz cały
getUnitByName =
{
  private ["_str", "_unit", "_i", "_name", "_res"];
  _name = _this select 0;
  for "_i" from 0 to (MAX_GROUP - 1) do
  {
    _str = format ["GCE_p%1", _i];
    if (not isNil _str) then
    {
       if (_name == (name call compile _str)) then
       {
          //przydalby sie jeszcze jakis break
          _res = call compile _str;
       };
    };
  };
  _res
};

addGroupToGCE =
{
  GCE_Groups = GCE_Groups + [_this select 0];
};

onPlayerConnected = "[group ([_name] call getUnitByName)] call addGroupToGCE;";
Offline
Avatar użytkownika

tsuki

Porucznik

Porucznik

  • Posty: 491
  • Dołączył(a): Cz 21 sty, 2010 22:33
  • Lokalizacja: z Nienacka

PostWt 18 maja, 2010 07:26

kondor napisał(a):
Kod: Zaznacz cały
getUnitByName =
{
  private ["_str", "_unit", "_i", "_name", "_res"];
  _name = _this select 0;
  for "_i" from 0 to (MAX_GROUP - 1) do
  {
    _str = format ["GCE_p%1", _i];
    if (not isNil _str) then
    {
       if (_name == (name call compile _str)) then
       {
          //przydalby sie jeszcze jakis break
          _res = call compile _str;
       };
    };
  };
  _res
};

addGroupToGCE =
{
  GCE_Groups = GCE_Groups + [_this select 0];
};

onPlayerConnected "[group ([_name] call getUnitByName)] call addGroupToGCE;";

Świetny pomysł, warty potestowania.
Rzecz w tym, że jezeli onPlayerConnected jest wykonywany w czasie, gdy gracz ma jeszcze ekran ładowania, to nie zostanie on dodany, bo jeszcze nie ma jednostki w grze.
Kod: Zaznacz cały
hint format["%1", player];

w pliku init.sqf wyrzuca <null>, a serwer też może nie widzieć jednostki GCE_pNUMBER.
Obrazek
Offline
Avatar użytkownika

kondor

Major

Major

  • Posty: 880
  • Dołączył(a): Pt 11 lut, 2005 04:00
  • Lokalizacja: Berlin (wschodni)

PostWt 18 maja, 2010 12:08

Kod: Zaznacz cały
hint format["%1", player];
nie ma wiele wspolnego z onPlayerConnected
http://community.bistudio.com/wiki/6thS ... ialization
onPlayerConnected wykonuje sie tylko na serwerze, a player dziala na kliencie. Z punktu widzenia logiki misji, sa zupelnie niezalezne od siebie i uzywanie komendy player w onPlayerConnected jest zle (choc dziala w przypadku niededykowanych serwerow).
Na pewno dziala (testowalem dzialanie onPlayerConnected, nie to co napisalem powyzej) dla nie JIP. Dla JIP nie testowalem (w pojedynke trudno przetestowac, mimo ustawienia serwera jako persistence)
onPlayerConnected odpali sie, gdy gracz sie podlaczy (czyli, ze chyba obiekt jednostki bedzie jakos zainicjalizowany, przynajmniej na serwerze, co w tym przypadku wystarczy) A to, ze podlaczajacy gracz jeszcze nie jest w pelni zainicjalizowany u siebie, to w niczym nie przeszkadza, o ile logike GCE masz po stronie serwera.

Oczywiscie ten kod powyzszy powinien byc w jakims init_server.sqf lub w bloku if (isServer) then ...
Offline
Avatar użytkownika

tsuki

Porucznik

Porucznik

  • Posty: 491
  • Dołączył(a): Cz 21 sty, 2010 22:33
  • Lokalizacja: z Nienacka

PostWt 18 maja, 2010 13:34

Chyba się nie zrozumieliśmy.

W trakcie, gdy gracza nie ma jeszcze na mapie plik "init.sqf" jest już wykonywany. Natomiast gracz siebie lokalnie widzi jako <null> (co wykazał ten hint) do czasu, kiedy fizycznie pojawi się w grze (jego model będzie widoczny dla innych grczy).

Z tego wnioskuję, że serwer w czasie ładowania, kiedy nie ma jeszcze jednostki też widzi GCE_pNUMBER jako <null>.

Nie ma żadnej gwarancji, że jeżeli gracz wchodzi na slot bez AI, to serwer już w trakcie, gdy gracz jest wczytywany, (a jeszcze nie ma go na mapie) nie wykonuje "onPlayerConnected". Jeżeli tak, to fizycznie jednostki gracza nie ma, a więc "grup GCE_pNUMBER" będzie nil i posypie visibleTowns.sqf.

Oczywiście, nie ma też żadnej gwarancji, że tak będzie, a więc muszę potestować, czego teraz zrobić nie mogę, bo jestem w pracy.
Obrazek
Offline
Avatar użytkownika

kondor

Major

Major

  • Posty: 880
  • Dołączył(a): Pt 11 lut, 2005 04:00
  • Lokalizacja: Berlin (wschodni)

PostWt 18 maja, 2010 13:48

http://forums.bistudio.com/showthread.p ... rConnected
http://community.bistudio.com/wiki/6thSense.eu:EG
onPlayerConnected will process new players after they are connected to their player body. So JIP player connects, world initializes, he gets control of his character, now the onPlayerConnected fires on the server.

That means that the players who JIP during briefing don't fire the onPlayerConnected until it's too late and isJIP is already set to false.

Wg mnie, bez czekania, az player rzeczywiscie sie ucielesni, ta komenda bylaby bardzo malo uzyteczna. Dlatego wierze, ze "czeka".
btw: zastanawiajace jest to, ze dostaje _name, a nie cos wygodniejszego w stylu _newPlayer

Powrót do [ArmA II] Edytor

Kto przegląda forum

Użytkownicy przeglądający ten dział: Brak zidentyfikowanych użytkowników i 8 gości