Overthrow uses Arma Reforger's replication system for network synchronization between server and clients. Understanding the patterns and limitations is crucial for multiplayer functionality.
The simplest way to replicate data is using the [RplProp]
attribute:
class OVT_ExampleComponent : OVT_Component
{
// Replicated property with callback on change
[RplProp(onRplName: "OnExampleParamChanged")]
protected int m_iExampleParam;
protected void OnExampleParamChanged()
{
// Called on clients when m_iExampleParam changes
// Update UI, refresh state, etc.
}
void SetExampleParam(int exampleParam)
{
m_iExampleParam = exampleParam;
Replication.BumpMe(); // Broadcast change to all clients
}
}
Send a message to all clients:
void NotifyAllClients(float value)
{
RpcDo_NotifyAllClients(value); // Call directly for host
Rpc(RpcDo_NotifyAllClients, value); // RPC to all clients
}
[RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]
void RpcDo_NotifyAllClients(float value)
{
// Executed on all clients
Print("Server says: " + value);
}
Send a message to a specific player:
void NotifyPlayer(string playerId, string message)
{
OVT_OverthrowController controller = OVT_Global.GetPlayers().GetController(playerId);
if (!controller) return; // Player offline
OVT_NotificationComponent notif = OVT_NotificationComponent.Cast(
controller.FindComponent(OVT_NotificationComponent)
);
if (!notif) return;
notif.ShowNotification(message);
}
// In OVT_NotificationComponent:
void ShowNotification(string message)
{
Rpc(RpcDo_ShowNotification, message);
}
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
void RpcDo_ShowNotification(string message)
{
// Shows only on the owning client
// Update UI with notification
}
The new pattern uses components on OVT_OverthrowController
:
// Client code
void RequestPurchase(int itemId)
{
OVT_OverthrowController controller = OVT_Global.GetController();
if (!controller) return; // We're on dedicated server
OVT_ShopComponent shop = OVT_ShopComponent.Cast(
controller.FindComponent(OVT_ShopComponent)
);
if (!shop) return;
shop.PurchaseItem(itemId);
}
// In OVT_ShopComponent:
void PurchaseItem(int itemId)
{
if (Replication.IsServer())
{
RpcAsk_PurchaseItem(itemId); // Direct call on server
}
else
{
Rpc(RpcAsk_PurchaseItem, itemId); // RPC to server
}
}
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
void RpcAsk_PurchaseItem(int itemId)
{
// Validate request
// Process purchase
// Send response back to client
}
For complex data that new players need when joining:
class OVT_TownManagerComponent : OVT_Component
{
ref array<ref OVT_TownData> m_aTowns;
override bool RplSave(ScriptBitWriter writer)
{
writer.WriteInt(m_aTowns.Count());
foreach (OVT_TownData town : m_aTowns)
{
writer.WriteString(town.name);
writer.WriteVector(town.location);
writer.WriteInt(town.population);
}
return true;
}
override bool RplLoad(ScriptBitReader reader)
{
int count;
reader.ReadInt(count);
m_aTowns.Clear();
for (int i = 0; i < count; i++)
{
OVT_TownData town = new OVT_TownData();
reader.ReadString(town.name);
reader.ReadVector(town.location);
reader.ReadInt(town.population);
m_aTowns.Insert(town);
}
return true;
}
}
For client-specific events, add ScriptInvokers to OVT_OverthrowController
:
class OVT_OverthrowController : GenericEntity
{
ref OVT_ProgressEventHandler m_OnProgress = new OVT_ProgressEventHandler();
}
// Usage:
controller.m_OnProgress.GetInvoker().Invoke(0.5, "Loading...");
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
void RpcAsk_ClaimBase(RplId baseId)
{
// Always validate on server
IEntity baseEntity = Replication.FindItem(baseId);
if (!baseEntity) return;
OVT_BaseControllerComponent base = OVT_BaseControllerComponent.Cast(
baseEntity.FindComponent(OVT_BaseControllerComponent)
);
if (!base) return;
// Check if player can claim
if (!CanPlayerClaimBase(GetPlayerId(), base)) return;
// Process claim
base.SetOwner(GetPlayerId());
}
// Store RplId instead of EntityID
RplId m_TargetId;
// Get entity from RplId
IEntity GetTarget()
{
if (!m_TargetId.IsValid()) return null;
return RplComponent.Cast(Replication.FindItem(m_TargetId)).GetEntity();
}
The old OVT_PlayerCommsComponent
system is deprecated. Do not use OVT_Global.GetServer()
anymore. All new client-to-server communication should use components on OVT_OverthrowController
.