Skip to content

Capitulo 6.9: Redes y RPC

Inicio | << Anterior: File I/O & JSON | Redes y RPC | Siguiente: Central Economy >>


Introduccion

DayZ es un juego cliente-servidor. Toda la logica autoritativa se ejecuta en el servidor, y los clientes se comunican con el a traves de Llamadas a Procedimiento Remoto (RPCs). El mecanismo principal de RPC es ScriptRPC, que te permite escribir datos arbitrarios en un lado y leerlos en el otro. Este capitulo cubre la API de redes: enviar y recibir RPCs, las clases de contexto de serializacion, el metodo legacy CGame.RPC(), y ScriptInputUserData para mensajes verificados de cliente a servidor.


Arquitectura Cliente-Servidor

┌────────────┐                    ┌────────────┐
│   Cliente  │  ──── RPC ────►   │  Servidor  │
│            │  ◄──── RPC ────   │            │
│ GetGame()  │                    │ GetGame()  │
│ .IsClient()│                    │ .IsServer()│
└────────────┘                    └────────────┘

Verificaciones de Entorno

c
proto native bool GetGame().IsServer();          // true en servidor y host de listen-server
proto native bool GetGame().IsClient();          // true en cliente
proto native bool GetGame().IsMultiplayer();      // true en multijugador
proto native bool GetGame().IsDedicatedServer();  // true solo en servidor dedicado

Patron tipico de guarda:

c
if (GetGame().IsServer())
{
    // Logica solo del servidor
}

if (!GetGame().IsServer())
{
    // Logica solo del cliente
}

ScriptRPC

Archivo: 3_Game/gameplay.c:104

La clase de RPC principal para enviar datos personalizados entre cliente y servidor. ScriptRPC extiende ParamsWriteContext, asi que llamas a .Write() directamente en el para serializar datos.

Definicion de Clase

c
class ScriptRPC : ParamsWriteContext
{
    void ScriptRPC();
    void ~ScriptRPC();
    proto native void Reset();
    proto native void Send(Object target, int rpc_type, bool guaranteed,
                           PlayerIdentity recipient = NULL);
}

Parametros de Send

ParametroDescripcion
targetEl objeto con el que este RPC esta asociado (puede ser null para RPCs globales)
rpc_typeID entero de RPC (debe coincidir entre emisor y receptor)
guaranteedtrue = entrega confiable tipo TCP; false = no confiable tipo UDP
recipientPlayerIdentity del cliente objetivo; null = broadcast a todos los clientes (solo servidor)

Escribir Datos

ParamsWriteContext (que ScriptRPC extiende) provee:

c
proto bool Write(void value_out);

Soporta todos los tipos primitivos, arrays y objetos serializables:

c
ScriptRPC rpc = new ScriptRPC();
rpc.Write(42);                          // int
rpc.Write(3.14);                        // float
rpc.Write(true);                        // bool
rpc.Write("hello");                     // string
rpc.Write(Vector(100, 0, 200));         // vector

array<string> names = {"Alice", "Bob"};
rpc.Write(names);                       // array<string>

Enviar: Servidor a Cliente

c
// Enviar a un jugador especifico
void SendDataToPlayer(PlayerBase player, int value, string message)
{
    if (!GetGame().IsServer())
        return;

    ScriptRPC rpc = new ScriptRPC();
    rpc.Write(value);
    rpc.Write(message);
    rpc.Send(player, MY_RPC_ID, true, player.GetIdentity());
}

// Broadcast a todos los jugadores
void BroadcastData(string message)
{
    if (!GetGame().IsServer())
        return;

    ScriptRPC rpc = new ScriptRPC();
    rpc.Write(message);
    rpc.Send(null, MY_RPC_ID, true, null);  // recipient null = todos los clientes
}

Enviar: Cliente a Servidor

c
void SendRequestToServer(int requestType)
{
    if (!GetGame().IsClient())
        return;

    PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());
    if (!player)
        return;

    ScriptRPC rpc = new ScriptRPC();
    rpc.Write(requestType);
    rpc.Send(player, MY_REQUEST_RPC, true, null);
    // Cuando se envia desde cliente, recipient se ignora — siempre va al servidor
}

Recibir RPCs

Los RPCs se reciben sobreescribiendo OnRPC en el objeto objetivo (o cualquier clase padre en la jerarquia).

Firma de OnRPC

c
override void OnRPC(PlayerIdentity sender, int rpc_type, ParamsReadContext ctx)
{
    super.OnRPC(sender, rpc_type, ctx);

    if (rpc_type == MY_RPC_ID)
    {
        // Leer datos en el mismo orden en que fueron escritos
        int value;
        string message;

        if (!ctx.Read(value))
            return;
        if (!ctx.Read(message))
            return;

        // Procesar los datos
        HandleData(value, message);
    }
}

ParamsReadContext

ParamsReadContext es un typedef para Serializer:

c
typedef Serializer ParamsReadContext;
typedef Serializer ParamsWriteContext;

El metodo Read:

c
proto bool Read(void value_in);

Retorna true en exito, false si la lectura falla (tipo incorrecto, datos insuficientes). Siempre verifica el valor de retorno.

Donde Sobreescribir OnRPC

Objeto ObjetivoRecibe RPCs Para
PlayerBaseRPCs enviados con target = player
ItemBaseRPCs enviados con target = item
Cualquier ObjectRPCs enviados con ese objeto como target
MissionGameplay / MissionServerRPCs globales (target = null) via OnRPC en mision

Ejemplo --- intercambio completo cliente-servidor:

c
// Constante compartida (capa 3_Game)
const int RPC_MY_CUSTOM_DATA = 87001;

// Lado del servidor: enviar datos al cliente (4_World o 5_Mission)
class MyServerHandler
{
    void SendScore(PlayerBase player, int score)
    {
        ScriptRPC rpc = new ScriptRPC();
        rpc.Write(score);
        rpc.Send(player, RPC_MY_CUSTOM_DATA, true, player.GetIdentity());
    }
}

// Lado del cliente: recibir datos (modded PlayerBase)
modded class PlayerBase
{
    override void OnRPC(PlayerIdentity sender, int rpc_type, ParamsReadContext ctx)
    {
        super.OnRPC(sender, rpc_type, ctx);

        if (rpc_type == RPC_MY_CUSTOM_DATA)
        {
            int score;
            if (!ctx.Read(score))
                return;

            Print(string.Format("Received score: %1", score));
        }
    }
}

CGame.RPC (API Legacy)

El sistema de RPC antiguo basado en arrays. Aun se usa en codigo vanilla pero ScriptRPC es preferido para mods nuevos.

Firmas

c
// Enviar con array de objetos Param
proto native void GetGame().RPC(Object target, int rpcType,
                                 notnull array<ref Param> params,
                                 bool guaranteed,
                                 PlayerIdentity recipient = null);

// Enviar con un solo Param
proto native void GetGame().RPCSingleParam(Object target, int rpc_type,
                                            Param param, bool guaranteed,
                                            PlayerIdentity recipient = null);

Clases Param

c
class Param1<Class T1> extends Param { T1 param1; };
class Param2<Class T1, Class T2> extends Param { T1 param1; T2 param2; };
// ... hasta Param8

Ejemplo --- RPC legacy:

c
// Enviar
Param1<string> data = new Param1<string>("Hello World");
GetGame().RPCSingleParam(null, MY_RPC_ID, data, true, player.GetIdentity());

// Recibir (en OnRPC)
if (rpc_type == MY_RPC_ID)
{
    Param1<string> data = new Param1<string>("");
    if (ctx.Read(data))
    {
        Print(data.param1);  // "Hello World"
    }
}

ScriptInputUserData

Archivo: 3_Game/gameplay.c

Un contexto de escritura especializado para enviar mensajes de entrada de cliente a servidor que pasan por el pipeline de validacion de entrada del motor. Se usa para acciones que necesitan verificacion anti-cheat.

c
class ScriptInputUserData : ParamsWriteContext
{
    proto native void Reset();
    proto native void Send();
    proto native static bool CanStoreInputUserData();
}

Patron de Uso

c
// Lado del cliente
void SendAction(int actionId)
{
    if (!ScriptInputUserData.CanStoreInputUserData())
    {
        Print("Cannot send input data right now");
        return;
    }

    ScriptInputUserData ctx = new ScriptInputUserData();
    ctx.Write(actionId);
    ctx.Send();  // Automaticamente enrutado al servidor
}

Nota: ScriptInputUserData tiene limitacion de tasa. Siempre verifica CanStoreInputUserData() antes de enviar.


Gestion de IDs de RPC

Elegir IDs de RPC

DayZ vanilla usa el enum ERPCs para RPCs incorporados. Los mods personalizados deben usar IDs que no conflictuen con vanilla.

Mejores practicas:

c
// Definir en la capa 3_Game (compartido entre cliente y servidor)
const int MY_MOD_RPC_BASE = 87000;  // Elegir un numero alto poco probable de conflicto
const int RPC_MY_FEATURE_A = MY_MOD_RPC_BASE + 1;
const int RPC_MY_FEATURE_B = MY_MOD_RPC_BASE + 2;
const int RPC_MY_FEATURE_C = MY_MOD_RPC_BASE + 3;

Patron de ID Unico del Motor (Usado por MyFramework)

Para mods con muchos tipos de RPC, usa un solo ID de RPC del motor y enruta internamente por un identificador de string:

c
// ID unico del motor
const int MyRPC_ENGINE_ID = 83722;

// Enviar con enrutamiento por string
ScriptRPC rpc = new ScriptRPC();
rpc.Write("MyFeature.DoAction");  // Ruta de string
rpc.Write(payload);
rpc.Send(target, MyRPC_ENGINE_ID, true, recipient);

// Recibir y enrutar
override void OnRPC(PlayerIdentity sender, int rpc_type, ParamsReadContext ctx)
{
    if (rpc_type == MyRPC_ENGINE_ID)
    {
        string route;
        if (!ctx.Read(route))
            return;

        // Enrutar al handler basado en string
        HandleRoute(route, sender, ctx);
    }
}

Variables de Sincronizacion de Red (Alternativa a RPC)

Para sincronizacion de estado simple, RegisterNetSyncVariable*() a menudo es mas simple que RPCs. Consulta el Capitulo 6.1 para detalles.

Los RPCs son mejores cuando:

  • Necesitas enviar eventos unicos (no estado continuo)
  • Los datos no pertenecen a una entidad especifica
  • Necesitas enviar datos complejos o de longitud variable
  • Necesitas comunicacion de cliente a servidor

Las variables de sincronizacion de red son mejores cuando:

  • Tienes un numero pequeno de variables en una entidad que cambian periodicamente
  • Quieres interpolacion automatica
  • Los datos naturalmente pertenecen a la entidad

Consideraciones de Seguridad

Validacion del Lado del Servidor

Nunca confies en datos del cliente. Siempre valida datos de RPC en el servidor:

c
override void OnRPC(PlayerIdentity sender, int rpc_type, ParamsReadContext ctx)
{
    super.OnRPC(sender, rpc_type, ctx);

    if (rpc_type == RPC_PLAYER_REQUEST && GetGame().IsServer())
    {
        int requestedAmount;
        if (!ctx.Read(requestedAmount))
            return;

        // VALIDAR: limitar al rango permitido
        requestedAmount = Math.Clamp(requestedAmount, 0, 100);

        // VALIDAR: verificar que la identidad del emisor coincida con el objeto jugador
        PlayerBase senderPlayer = GetPlayerBySender(sender);
        if (!senderPlayer || !senderPlayer.IsAlive())
            return;

        // Ahora procesar la solicitud validada
        ProcessRequest(senderPlayer, requestedAmount);
    }
}

Limitacion de Tasa

El motor tiene limitacion de tasa incorporada para RPCs. Enviar demasiados RPCs por frame puede causar que se descarten. Para datos de alta frecuencia, considera:

  • Usar variables de sincronizacion de red en su lugar
  • Agrupar multiples valores en un solo RPC
  • Limitar la frecuencia de envio con un timer

Resumen

ConceptoPunto Clave
ScriptRPCClase de RPC principal: Write() datos, luego Send(target, id, guaranteed, recipient)
OnRPCSobreescribir en el objeto objetivo para recibir: OnRPC(sender, rpc_type, ctx)
Read/Writectx.Write(value) / ctx.Read(value) --- siempre verificar el valor de retorno de Read
DireccionCliente envia al servidor; servidor envia a cliente especifico o broadcast
Recipientnull = broadcast (servidor), ignorado (cliente)
Guaranteedtrue = entrega confiable, false = no confiable (mas rapido)
LegacyGetGame().RPC() / RPCSingleParam() con objetos Param
Datos de entradaScriptInputUserData para entrada validada del cliente
IDsUsar numeros altos (87000+) para evitar conflictos con vanilla
SeguridadSiempre validar datos del cliente en el servidor

<< Anterior: File I/O & JSON | Redes y RPC | Siguiente: Central Economy >>

Released under CC BY-SA 4.0 | Code examples under MIT License