Skip to content
RustPluginGenerator

OnTeamMemberPromote

OnTeamMemberPromote is called when a team's leader is about to be reassigned, right before RelationshipManager.PlayerTeam.SetTeamLeader applies the change. Returning a non-null value blocks the promotion entirely.

Signature
object OnTeamMemberPromote(RelationshipManager.PlayerTeam playerTeam, ulong newTeamLeader)

Parameters

Hook parameters
ParameterTypeDescription
playerTeamRelationshipManager.PlayerTeamThe RelationshipManager.PlayerTeam instance whose leadership is being changed. Read teamID, members and the current teamLeader from it; it is the same object the game will mutate if the hook allows the call.
newTeamLeaderulongThe Steam ID (ulong) of the member who will become team leader. Compare it against playerTeam.members to confirm the player is actually on the team before you accept the promotion.

Return value

Return null (or fall through without return) to let the game continue: analytics fire, teamLeader is set to newTeamLeader, and the team is marked dirty. Return any non-null value, such as true or a string, and the whole block is skipped, so the old leader stays in charge and no update is sent.

How it works

The hook fires inside RelationshipManager.PlayerTeam.SetTeamLeader, the game method that swaps which member is the team leader. It runs once per promotion, whether the change comes from a client console command (noteamleader/vote) or from server code that calls SetTeamLeader directly. Because the call happens before the assignments, playerTeam.teamLeader still holds the old leader and newTeamLeader is the incoming one.

Use playerTeam to inspect the team (its teamID, its members list) and newTeamLeader to check who is being promoted. Both parameters are exact types and cannot be narrowed further, so every promotion reaches your method. Rejecting the promotion is the only lever: there is no argument you can rewrite.

Promotions are rare, so it is safe to run a permission check or send a chat line here. Verification calls to your database are still better deferred to a timer if you already have slow work in flight. Returning nothing lets the game record the promotion, update teamLeader and mark the team dirty so clients sync.

Example plugin: Restrict who can be promoted to team leader

TeamLeaderGuard.cs
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Team Leader Guard", "RustPluginGenerator", "1.0.0")]
    [Description("Blocks team leadership promotions for blacklisted players or players without a permission")]
    public class TeamLeaderGuard : RustPlugin
    {
        private const string PermAllow = "teamleaderguard.allow";
        private const string PermAdmin = "teamleaderguard.admin";

        private PluginConfig config;

        private class PluginConfig
        {
            [JsonProperty("Require permission to lead a team")]
            public bool RequirePermission { get; set; } = false;

            [JsonProperty("Blacklisted Steam IDs")]
            public List<string> BlacklistedIds { get; set; } = new List<string>();
        }

        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>
            {
                ["Blocked"] = "You are not allowed to become the leader of this team.",
                ["AdminAdded"] = "{0} was added to the leader blacklist.",
                ["AdminRemoved"] = "{0} was removed from the leader blacklist.",
                ["AdminUsage"] = "Usage: /tlguard add|remove <steamid>"
            }, 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(PermAllow, this);
            permission.RegisterPermission(PermAdmin, this);
        }

        private object OnTeamMemberPromote(RelationshipManager.PlayerTeam playerTeam, ulong newTeamLeader)
        {
            if (playerTeam == null || newTeamLeader == 0) return null;
            if (playerTeam.members == null || !playerTeam.members.Contains(newTeamLeader)) return null;

            string userId = newTeamLeader.ToString();

            if (config.BlacklistedIds.Contains(userId))
            {
                Notify(newTeamLeader);
                return true;
            }

            if (config.RequirePermission && !permission.UserHasPermission(userId, PermAllow))
            {
                Notify(newTeamLeader);
                return true;
            }

            return null;
        }

        private void Notify(ulong userId)
        {
            BasePlayer player = BasePlayer.FindByID(userId);
            if (player != null && player.IsConnected)
                player.ChatMessage(Msg("Blocked", player.UserIDString));
        }

        [ChatCommand("tlguard")]
        private void CmdTlGuard(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, PermAdmin))
            {
                player.ChatMessage(Msg("Blocked", player.UserIDString));
                return;
            }

            if (args == null || args.Length < 2)
            {
                player.ChatMessage(Msg("AdminUsage", player.UserIDString));
                return;
            }

            ulong targetId;
            if (!ulong.TryParse(args[1], out targetId) || targetId == 0)
            {
                player.ChatMessage(Msg("AdminUsage", player.UserIDString));
                return;
            }

            string id = targetId.ToString();
            if (args[0].ToLowerInvariant() == "add")
            {
                if (!config.BlacklistedIds.Contains(id)) config.BlacklistedIds.Add(id);
                SaveConfig();
                player.ChatMessage(Msg("AdminAdded", player.UserIDString, id));
            }
            else if (args[0].ToLowerInvariant() == "remove")
            {
                config.BlacklistedIds.Remove(id);
                SaveConfig();
                player.ChatMessage(Msg("AdminRemoved", player.UserIDString, id));
            }
            else
            {
                player.ChatMessage(Msg("AdminUsage", player.UserIDString));
            }
        }
    }
}

The plugin keeps a list of Steam IDs that may never lead a team and a config flag that limits promotions to players holding the teamleaderguard.allow permission.

OnTeamMemberPromote checks the incoming newTeamLeader against the blacklist, verifies the player is still in playerTeam.members, and, when the permission gate is enabled, looks up the BasePlayer and confirms the permission. Any failed check returns true, which stops SetTeamLeader before the leader field is written, so the current leader keeps control. Messages go through the language file, with one key for the blacklist and one for the missing permission. Access to the deny list is exposed through a chat command that requires the teamleaderguard.admin permission.

Common use cases

  • Block banned players from taking over a team

    Keep a persisted list of Steam IDs and return non-null when a promotion targets one of them, so a member who was demoted or punished cannot become leader through a vote.

  • Gate leadership behind a VIP or staff permission

    Check permission.UserHasPermission(newTeamLeader.ToString(), ...) and refuse the promotion if the player lacks the node, letting you sell or hand out leadership rights on a per-player basis.

  • Log leadership transfers for admin review

    Record playerTeam.teamID, the old teamLeader and newTeamLeader to a data file so staff can later see who ran each team and when, without blocking anything.

Pitfalls

  • The team object is mutated after the hook

    playerTeam.teamLeader still contains the previous leader while your hook runs and MarkDirty has not been called yet. Read the old value here if you need it, because afterwards it is gone.

  • Returning false still blocks the promotion

    The check is for null, not for truthiness. Returning false from the hook stops SetTeamLeader just like returning true does, so only use plain return; or return null; when you mean to allow the promotion.

  • Blocked promotions leave clients on the old leader

    Because the whole block is skipped, no network update is sent. The client keeps showing the previous leader, which is correct, but a plugin-driven vote system should tell the players that the change was refused instead of silently doing nothing.

Build a plugin with OnTeamMemberPromote

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Block banned players from taking over a team using the OnTeamMemberPromote hook, with a config file and a permission
Generate this plugin

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 OnTeamMemberPromote

Generate