PL/Element data: Difference between revisions

From Multi Theft Auto: Wiki
Jump to navigation Jump to search
mNo edit summary
Line 86: Line 86:
<section name="Server" class="server" show="true">
<section name="Server" class="server" show="true">
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function getElementsWithinMarker(marker)
local function getElementsWithinMarker(marker)
     if (not isElement(marker)) or (getElementType(marker) ~= "marker") then
     if (not isElement(marker)) or (getElementType(marker) ~= "marker") then
         return false
         return false

Revision as of 18:31, 12 September 2024

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.


[[{{{image}}}|link=|]] 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

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.

Click to collapse [-]
Server
-- 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)
Click to collapse [-]
Client
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.

Click to collapse [-]
Server
local 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)
Click to collapse [-]
Client
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.

Click to collapse [-]
Server
-- 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)