OnTeamUpdated
OnTeamUpdated is called from BasePlayer.TeamUpdate just before the server sends the CLIENT_ReceiveTeamInfo RPC to a player, so it fires once per player each time their team state is refreshed.
object OnTeamUpdated(ulong teamId, ProtoBuf.PlayerTeam teamData, BasePlayer player)Parameters
| Parameter | Type | Description |
|---|---|---|
teamId | ulong | The numeric ID of the team being updated. Use it to look up the team later with RelationshipManager.ServerInstance.FindTeam or to key your own per-team data. |
teamData | ProtoBuf.PlayerTeam | The ProtoBuf.PlayerTeam payload about to be sent to the player: members, teamLeader, teamID, and map notes. Treat it as read-only unless you know exactly what the client expects. |
player | BasePlayer | The BasePlayer receiving the team update. This is the player whose client would get the CLIENT_ReceiveTeamInfo RPC, not necessarily every member of the team. |
Return value
Returning null (or nothing) lets the server send the team update RPC as normal. Returning any non-null value stops the CLIENT_ReceiveTeamInfo RPC and the map-note cleanup that follows, so that player's client is not refreshed with the new team state.
How it works
The hook fires at the end of BasePlayer.TeamUpdate, after the server has assembled the ProtoBuf.PlayerTeam payload (members, leader, map notes and all). TeamUpdate runs whenever the game needs to push fresh team state to one player: joining or leaving a team, a member connecting or disconnecting, leader changes, and other membership edits all trigger a refresh for affected players. Because the call site sits inside a per-player method, one team event can produce several hook calls, one for each player whose state was refreshed.
teamId is the value the server is updating for, teamData is the protobuf that will be serialized into the RPC, and player is the recipient. Declaring player as BasePlayer is the only sensible narrowing; a more specific subclass would skip almost every call. The hook is a good place to observe team composition without polling, and to react in your own systems (notifications, statistics, custom UI) using the same data the client is about to receive.
Example plugin: Notify teammates when a member joins the team
using System.Linq;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Team Join Notifier", "RustPluginGenerator", "1.0.0")]
[Description("Announces in team chat when a member joins a team")]
public class TeamJoinNotifier : RustPlugin
{
private const string PermUse = "teamjoinnotifier.use";
private PluginConfig config;
private readonly Dictionary<ulong, HashSet<ulong>> membersByTeam = new Dictionary<ulong, HashSet<ulong>>();
private readonly Dictionary<ulong, Timer> debounceTimers = new Dictionary<ulong, Timer>();
private class PluginConfig
{
[JsonProperty("Notify team in chat")]
public bool NotifyTeam { get; set; } = true;
[JsonProperty("Log joins to admins")]
public bool LogToAdmins { get; set; } = true;
}
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Team.Joined"] = "{0} joined the team",
["Admin.Joined"] = "{0} joined team {1}"
}, this);
}
private string Msg(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);
private void Init()
{
permission.RegisterPermission(PermUse, this);
}
private List<ulong> GetMemberIds(ProtoBuf.PlayerTeam teamData)
{
var ids = new List<ulong>();
foreach (var member in teamData.members)
{
if (member != null) ids.Add(member.userID);
}
return ids;
}
private object OnTeamUpdated(ulong teamId, ProtoBuf.PlayerTeam teamData, BasePlayer player)
{
if (teamData?.members == null || player == null || teamId == 0) return null;
List<ulong> currentMembers = GetMemberIds(teamData);
HashSet<ulong> previous;
if (!membersByTeam.TryGetValue(teamId, out previous))
{
previous = new HashSet<ulong>();
foreach (ulong member in currentMembers) previous.Add(member);
membersByTeam[teamId] = previous;
return null;
}
ulong joined = 0;
foreach (ulong member in currentMembers)
{
if (!previous.Contains(member)) joined = member;
}
if (joined == 0) return null;
previous.Clear();
foreach (ulong member in currentMembers) previous.Add(member);
if (config.NotifyTeam)
{
var newMember = BasePlayer.FindByID(joined);
string name = newMember != null ? newMember.displayName : joined.ToString();
foreach (ulong member in currentMembers)
{
BasePlayer memberPlayer = BasePlayer.FindByID(member);
if (memberPlayer != null && memberPlayer.IsConnected)
{
memberPlayer.ChatMessage(Msg("Team.Joined", memberPlayer.UserIDString, name));
}
}
}
if (config.LogToAdmins)
{
var newMember = BasePlayer.FindByID(joined);
string name = newMember != null ? newMember.displayName : joined.ToString();
foreach (BasePlayer admin in BasePlayer.activePlayerList)
{
if (admin.IsAdmin && permission.UserHasPermission(admin.UserIDString, PermUse))
{
admin.ChatMessage(Msg("Admin.Joined", admin.UserIDString, name, teamId));
}
}
}
return null;
}
private void Unload()
{
foreach (Timer timer in debounceTimers.Values) timer?.Destroy();
debounceTimers.Clear();
membersByTeam.Clear();
foreach (BasePlayer player in BasePlayer.activePlayerList)
{
if (player != null && !player.IsDestroyed) CuiHelper.DestroyUi(player, "TeamJoinNotifierPanel");
}
}
}
}The plugin keeps a per-team snapshot of member Steam IDs and compares it to teamData.members every time OnTeamUpdated runs. When a new member is detected, the snapshot is updated immediately and a chat message is sent to all connected team members if NotifyTeam is enabled in the config. Admins who have the teamjoinnotifier.use permission also receive a separate notification if LogToAdmins is enabled. Messages are defined in the language file. Data is stored in memory only and is cleared on unload.
Common use cases
Team change announcements
Detect that a member joined or left and post a chat message to the affected players, without needing OnTeamAcceptInvite or OnTeamLeave wiring for every path.
Custom team panel refresh
Use the hook as a signal that a player's team state changed and redraw your own CUI team panel from
teamData.membersandteamData.teamLeader.Team activity statistics
Track team size over time or count refresh events per team to spot large groups or churn on a busy server.
Pitfalls
Fires once per receiving player
One membership change produces a call for each player whose team state is refreshed. Deduplicate with a per-team timer or sequence check before sending messages or writing data.
Returning non-null blocks the update
A non-null return prevents
CLIENT_ReceiveTeamInfofrom being sent, leaving that client with stale team data. Only return a value if you intentionally want to suppress the update.Do not mutate teamData casually
The protobuf is serialized straight into the RPC. Editing members or map notes can desync the client from the server's authoritative team.
Build a plugin with OnTeamUpdated
Describe what should happen and get a compiled plugin that uses this hook.
Edit the prompt on the next screen. 2 free plugins a month, no card.
Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.
Build a plugin with OnTeamUpdated
Generate