OnCounterModeToggle
OnCounterModeToggle is called when a player toggles a PowerCounter's display mode, before the server checks build permission and applies the new flag.
object OnCounterModeToggle(PowerCounter powerCounter, BasePlayer player, bool flag)Parameters
| Parameter | Type | Description |
|---|---|---|
powerCounter | PowerCounter | The PowerCounter entity whose display mode the player is trying to toggle. Use it to read the owner, position, current flags or attached IO connections. |
player | BasePlayer | The BasePlayer that sent the toggle RPC. Oxide can deliver a null here if the RPC has no attached connection, so null-check before using it. |
flag | bool | The requested display mode bit read from the RPC payload. For a counter this switches between the counter display and the passthrough/IO display; check the entity's own flags if you need the current state. |
Return value
Returning null lets the original code continue: it then checks player.CanBuild() and, if allowed, writes the new mode flag, marks the counter dirty and sends a network update. Returning any non-null value skips that whole block, so the mode change is silently rejected without a network update โ use that to deny the toggle.
How it works
The hook fires inside PowerCounter.ToggleDisplayMode, which is invoked by an RPC from the client when a player presses the counter's mode button. The game passes the counter entity, the sending player and the requested mode bit flag.
Because the call happens before the CanBuild() check, the hook sees attempts from players who would normally be rejected โ useful if you want to allow or deny mode changes based on your own rules. powerCounter is the entity to inspect (owner, position, building privilege, connected IO); player is the acting player; flag is the mode value the client requested.
Declaring player as a more specific subclass will not help here โ it is always a BasePlayer. You cannot narrow this hook by type in a meaningful way, so filter inside the method (for example by entity owner or zone).
The hook fires only for actual mode-toggle RPCs, not for counter output updates, which use OnCounterTargetChange and OnOutputUpdate. It is not called for other electrical components.
Example plugin: Restrict counter mode toggles to the owner and their team
using System.Linq;
using UnityEngine;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("CounterModeOwnerOnly", "RustPluginGenerator", "1.0.0")]
[Description("Restricts PowerCounter display mode toggles to the entity owner, their team and staff.")]
public class CounterModeOwnerOnly : RustPlugin
{
private const string PermBypass = "countermodeowneronly.bypass";
private PluginConfig config;
private readonly Dictionary<ulong, float> lastNotice = new Dictionary<ulong, float>();
private class PluginConfig
{
[JsonProperty("Allow teammates of the owner")]
public bool AllowTeammates { get; set; } = true;
[JsonProperty("Require building privilege instead of ownership")]
public bool RequireBuildPrivilege { get; set; } = false;
[JsonProperty("Notify the player when denied")]
public bool NotifyDenied { get; set; } = true;
[JsonProperty("Notice cooldown in seconds")]
public float NoticeCooldown { get; set; } = 5f;
}
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>
{
["Denied"] = "You are not allowed to change this counter's display mode."
}, 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(PermBypass, this);
}
private void Unload()
{
lastNotice.Clear();
}
private object OnCounterModeToggle(PowerCounter powerCounter, BasePlayer player, bool flag)
{
if (powerCounter == null || player == null || player.IsAdmin) return null;
if (permission.UserHasPermission(player.UserIDString, PermBypass)) return null;
if (player.userID == powerCounter.OwnerID) return null;
if (config.RequireBuildPrivilege)
{
var privilege = powerCounter.GetBuildingPrivilege();
if (privilege != null && privilege.IsAuthed(player)) return null;
}
else if (config.AllowTeammates && IsTeammateOfOwner(player, powerCounter.OwnerID))
{
return null;
}
Notify(player);
return false;
}
private bool IsTeammateOfOwner(BasePlayer player, ulong ownerId)
{
if (ownerId == 0 || player.currentTeam == 0) return false;
var team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
return team != null && team.members.Contains(ownerId);
}
private void Notify(BasePlayer player)
{
if (!config.NotifyDenied) return;
float now = UnityEngine.Time.realtimeSinceStartup;
float last;
if (lastNotice.TryGetValue(player.userID, out last) && now - last < config.NoticeCooldown) return;
lastNotice[player.userID] = now;
player.ChatMessage(Msg("Denied", player.UserIDString));
}
}
}The plugin denies mode toggles on a PowerCounter unless the player is the entity's owner, the owner's teammate, an admin, or has the countermodeowneronly.bypass permission. On a denied attempt the hook returns false, which makes the game skip the permission check and the network update, so the counter visually stays where it was. Allowed attempts return null so vanilla behavior (including the CanBuild() check) runs untouched. A short chat message tells the player why nothing happened, throttled with a per-player cooldown so repeated clicks do not spam chat.
Common use cases
Entity ownership enforcement
Block strangers from flipping a counter's mode on a shared build so its display stays as the owner configured it.
Raiding and zone rules
Allow mode changes only while the player has building privilege on the counter, or only outside a PvP event zone.
Staff tools
Log or count toggle attempts per player to spot griefers clicking other players' electricals, and grant staff a bypass permission.
Pitfalls
Return null to allow
Any non-null return cancels the change, including
falseor0. Only return a value when you actually want to deny; otherwise returnnull.Player can be null
The player comes from the RPC message and is not guaranteed to be present. Guard against a null player before calling methods on it.
No build check before the hook
The hook runs before the game's
CanBuild()test, so you may see attempts from players who would be rejected anyway. Do not assume every call is a legitimate action.
Build a plugin with OnCounterModeToggle
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 OnCounterModeToggle
Generate