PL/Element data
Każdy Element może posiadać swoje własne dane, nazywane element data. Jest to zbiór danych, składający się z klucza oraz wartości. Element daty mogą być przydatne dla przechowywania i synchronizacji informacji pomiędzy serwerem, a klientami. Przykładowo możesz zapisywać punkty gracza, właśnie na jego elemencie, a następnie po stronie klienta możesz je wyświetlać np. w tabeli graczy. Często element data jest także przydatna dla stricte skryptowych zastosowań, aby sprawdzać czy gracz ma np. konto VIP.
Notatka: Należy jednak pamiętać, że element daty są dość obciążające dla serwera, gdyż są nieustannie synchronizowane pomiędzy wszystkimi klientami. Dlatego warto rozważyć wyłączenie synchronizacji konkretnej element daty jeśli nie jest ona potrzebna lub ograniczyć ją do konkretnych klientów. Duża ilość synchronizowanych element dat ma znaczący wpływ na wydajność serwera! |
Synchronizację elementdaty można wyłączyć ustawiając argument synchronize na false przy użyciu funkcji setElementData.
Wartością elementdaty może być niemal wszystko. Może to być liczba, ciąg znaków, wartość true/false, element czy tabela danych.
Ograniczenia
Jak już wspomniano każdy Element może posiadać dowolną ilość element dat "na sobie". Jednak istnieją pewne ograniczenia jeśli chodzi o to co możemy podać jako wartość elementdaty. Nie mogą to być tzw. non-element userdata (Więcej informacji tutaj), funkcje (typ function) oraz wątki (typ thread). Nie można także przesyłać tabel zawierających co najmniej jeden z w/w typów.
Synchronizacja
Każda element data domyślnie jest synchronizowana. Oznacza to, że każda zmiana jej po stronie serwera jest synchronizowana ze wszystkimi klientami i analogicznie po stronie klienta jest synchronizowana z serwerem, a następnie ze wszystkimi klientami. Im więcej element dat tym więcej pakietów synchronizacji pomiędzy serwerem a klientami. Ma to znaczący wpływ na wydajność serwera w przypadku dużej ilości graczy oraz dużej ilości element dat, w zależności od przepustowości serwera.
Nie zawsze jest potrzeba synchronizowania element dat, więc gdy to możliwe wyłączaj synchronizację podając jako argument synchronize wartość false przy ustawianiu jej setElementData. Wówczas taka element data dostępna jest tylko po tej stronie gdzie została ustawiona. Czyli jeśli ustawiono element datę po stronie serwera, to tylko tam jest ona dostępna. Jeśli ustawiono ją po stronie klienta to jest ona dostępna tylko u tego klienta, u którego została ustawiona.
Możesz także ograniczyć synchronizację element daty do konkretnych osób, dla których ma być synchronizowana. Jest to możliwe dzięki tzw. subscribe-mode. Możesz określić za pomocą funkcji addElementDataSubscriber oraz removeElementDataSubscriber określić z kim ma być synchronizowana dana element data.
Bezpieczeństwo
Pamiętaj, że absolutnie nigdy nie należy ufać stronie klienta. Nigdy nie powinno się zarządzać wrażliwymi danymi po stronie klienta, ponieważ strona klienta może być fałszywa. Wszystko co dzieje się po stronie klienta, dzieje się na komputerze danego klienta, a co za tym idzie wszystko może zostać sfałszowane. Dlatego zawsze należy w miarę możliwości wszelkie ważne dane trzymać po stronie serwera, a jeśli już musisz coś sprawdzać po stronie klienta np. czy gracz ma rangę to pamiętaj, aby sprawdzić to również po stronie serwera, jako dodatkowe zabezpieczenie.
Zapoznaj się z artykułem o bezpieczeństwie skryptów po więcej informacji jak się chronić.
Funkcje elementdat
- setElementData - Do ustawiania elementdaty
- getElementData - Do odczytu elementdaty
- hasElementData - Do sprawdzenia czy element ma daną elementdate. Jest równoważne ze sprawdzeniem getElementData czy nie jest false, zazwyczaj nie ma potrzeby sprawdzania czy elementdata w ogóle istnieje, tylko czy ma jakąś wartość.
- addElementDataSubscriber - Do dodania klienta, z którym ma być synchronizowana elementdata.
- removeElementDataSubscriber - Do usunięcia klienta, z którym nie ma już być synchronizowana elementdata.
- hasElementDataSubscriber - Do sprawdzenia czy konkretny elementdata jest synchronizowana z konkretnym klientem.
Eventy elementdat
Przykłady
Synchronizowana element data ze wszystkimi
Ten przykład ustawia element datę o nazwie kills dla gracza, która określa ilość zabójstw. Komenda kills <nick> pozwala sprawdzić ilość zabójstw danego gracza, a także każdy widzi swoją ilość zabójstw u góry ekranu.
-- Event, aby doliczać zabójstwa addEventHandler('onPlayerWasted', root, function(_, killer) if (killer and getElementType(killer) == 'player') then -- jeśli zabójcą był gracz local kills = getElementData(killer, 'kills') or 0 -- pobieramy ilość zabójstw gracza, jeśli nigdy nikogo nie zabił to domyślnie 0 setElementData(killer, 'kills', kills + 1) -- Zwiększamy ilość zabójstw o 1 end end) -- Komenda do sprawdzania ilości zabójstw danego gracza addCommandHandler('kills', function(plr, cmd, nick) if (not nick) then outputChatBox('Nie podano nicku', plr) return end -- szukamy gracza po nicku local killer = getPlayerFromName(nick) if (killer) then local kills = getElementData(killer, 'kills') or 0 outputChatBox('Ten gracz ma na koncie '..kills..' zabójstw', plr) else outputChatBox('Nie znaleziono gracza o takim nicku', plr) end end)
local screenX, screenY = guiGetScreenSize() local textY = (30/1080) * screenY -- skalujemy pozycje tekstu -- Renderujemy tekst na ekranie addEventHandler('onClientRender', root, function() local kills = getElementData(localPlayer, 'kills') or 0 dxDrawText('Zabójstwa: '..kills, 0, textY, screenX, 0, 0xFFFFFFFF, 1, 'default-bold', 'center') end)
Synchronizowana element data z konkretnymi klientami (subscribe)
Ten przykład imituje wyścig pomiędzy dwoma graczami i pokazuje każdemu z nich ilość punktów przeciwnika z elementdaty race_scores. W tym przypadku synchronizujemy elementdatę tylko pomiędzy członkami wyścigu, a nie wszystkimi klientami.
function getElementsWithinMarker(marker) if (not isElement(marker)) or (getElementType(marker) ~= "marker") then return false end local markerColShape = getElementColShape(marker) local elements = getElementsWithinColShape(markerColShape) return elements end local startMarker = createMarker(0,0,3,'cylinder',1.5, 255, 0, 0) -- Event, że weszliśmy w marker addEventHandler('onMarkerHit', startMarker, function(he,md) if (getElementType(he) == 'player' and md) then -- Sprawdzamy czy wszedł gracz local elems = #getElementsWithinMarker(source, 'player') -- Pobieramy ilość elementów (graczy) w markerze if (elems == 2) then -- Jeśli w markerze jest 2 graczy -- Ustawiamy punkty na 0 dla obydwu graczy i ustawiamy synchronizację na tryb 'subscribe' setElementData(elems[1], 'race_scores', 0, 'subscribe') setElementData(elems[2], 'race_scores', 0, 'subscribe') addElementDataSubscriber(elems[1], 'race_scores', elems[2]) -- ustawiamy synchronizację elementdaty race_scores gracza 1 z graczem 2 addElementDataSubscriber(elems[2], 'race_scores', elems[1]) -- ustawiamy synchronizację elementdaty race_scores gracza 2 z graczem 1 -- Wysyłamy informacje do klientów o rozpoczęciu wyścigu triggerClientEvent({elems[1],elems[2]}, 'startRaceBetweenPlayers', resourceRoot, elems[1], elems[2]) end end end)
local screenX, screenY = guiGetScreenSize() local textY = (30/1080) * screenY -- Skalujemy wysokość tekstu na ekranie local opponent -- Funkcja renderująca punkty na ekranie local function renderScores() if (opponent and isElement(opponent)) then -- Czy gracz wciąż jest na serwerze local scores = getElementData(opponent, 'race_scores') dxDrawText('Punkty przeciwnika: '..scores, 0, textY, screenX, 0, 0xFFFFFFFF, 1, 'default-bold', 'center') end end addEvent('startRaceBetweenPlayers', true) addEventHandler('startRaceBetweenPlayers', resourceRoot, function(player1, player2) -- Ustalamy przeciwnika opponent = localPlayer == player1 and player2 or player1 -- Renderujemy punkty przeciwnika addEventHandler('onClientRender', root, renderScores) end)
Wtedy jeśli zmieni się wartość elementdaty ``race_scores`` dla jednego z graczy, drugi zobaczy, że ilość punktów uległa zmianie.
Element data z wyłączoną synchronizacją
Ten przykład ustawia element datę gdy gracz odebrał już nagrodę. To prosty przykład, więc po ponownym połączeniu się z serwerem gracz znów może odebrać nagrodę! W przykładzie wyłączamy synchronizację elementdaty, ponieważ nie potrzebujemy jej synchronizować ze wszystkimi klientami. Używamy jej tylko na stronie serwera, więc nie ma potrzeby jej synchronizowania z klientami.
-- Tworzymy marker local marker = createMarker(0,0,3, 'cylinder', 1.5, 255, 255, 0) -- Event, że weszliśmy w marker addEventHandler('onMarkerHit', marker, function(he, md) if (getElementType(he) == 'player' and md) then -- Jeśli w marker wszedł gracz, a nie np. wjechał pojazd if (getElementData(he, 'reward')) then -- jeśli gracz ma elementdate, czyli odebrał już nagrodę outputChatBox('Odebrałeś/aś już nagrodę!', he) return end -- Gracz odbiera nagrode local money = math.random(100,1000) -- Losujemy kwote nagrody od 100 do 1000$ givePlayerMoney(he, money) -- dajemy graczowi pieniądze outputChatBox('Odebrano '..money..'$ nagrody', he) setElementData(he, 'reward', true, false) -- Ustawiamy elementdate, że gracz odebrał już nagrodę. Wyłączamy synchronizację, ponieważ jej nie potrzebujemy end end)