Skip to content
RustPluginGenerator

OnEntityReskinned

OnEntityReskinned is called after a player successfully applies a skin with a spray can, once SprayCan.ChangeItemSkin has already set the entity's skin and sent the reskin result. It runs after the change, not before it.

Signature
void OnEntityReskinned(BaseEntity entity, ulong skinId, BasePlayer player)

Parameters

Hook parameters
ParameterTypeDescription
entityBaseEntityThe BaseEntity that was just reskinned. This is the item entity the spray can targeted, so read its ShortPrefabName or cast it to a known type when you need specific data.
skinIdulongThe new skin id (ulong) that was applied to the entity. Use it to look up the skin in your own data or to validate what players are spraying.
playerBasePlayerThe BasePlayer who applied the skin with the spray can. Useful for permission checks, logging, and per-player quotas; may be null in edge cases, so null-check it.

Return value

The injection uses a continue return behavior, so whatever your hook method returns is ignored. The original method proceeds normally: the condition on the spray can is still consumed and the network result is still sent. You cannot cancel a reskin through this hook.

How it works

The hook fires inside SprayCan.ChangeItemSkin, after the game has assigned the new skin to the target entity, notified any IReskinCallback implementation, and sent the Client_ReskinResult RPC back to the client. Because it fires post-change, you observe the result rather than approve it: the entity already carries the new skin when entity reaches your code.

Use entity to inspect what was reskinned (check ShortPrefabName, cast to BaseEntity subtypes like signs or deployables), skinId to know which skin was applied, and player to attribute the action. A common pattern is tracking how many reskins a player performed in a time window, logging skin usage for moderation, or tagging specific skins.

Declaring player as BasePlayer is exact here since the injection passes a BasePlayer. There is no narrower type worth declaring for entity beyond BaseEntity; instead cast inside the method. Since reskinning is a player-driven action rather than a high-frequency event, light bookkeeping in the hook is fine, but keep any chat output gated behind permission checks.

Example plugin: Track reskin usage per player

ReskinTracker.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;

namespace Oxide.Plugins
{
    [Info("Reskin Tracker", "RustPluginGenerator", "1.0.0")]
    [Description("Counts spray can reskins per player and reports totals to permission holders.")]
    public class ReskinTracker : RustPlugin
    {
        private const string PermTrack = "reskintracker.track";
        private const string DataFile = "ReskinTracker/counts";

        private PluginConfig config;
        private Dictionary<string, int> counts = new Dictionary<string, int>();

        private class PluginConfig
        {
            [JsonProperty("Show totals to permission holders")]
            public bool ShowTotals { 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>
            {
                ["ReskinTotal"] = "You have reskinned {0} entities in total.",
                ["ReskinWarn"] = "That reskin brings you to {0} total reskins."
            }, this);
        }

        private void Init()
        {
            permission.RegisterPermission(PermTrack, this);
            counts = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, int>>(DataFile) ?? new Dictionary<string, int>();
        }

        private void Unload()
        {
            Interface.Oxide.DataFileSystem.WriteObject(DataFile, counts);
        }

        private void OnEntityReskinned(BaseEntity entity, ulong skinId, BasePlayer player)
        {
            if (player == null || entity == null || player.IsNpc) return;

            string id = player.UserIDString;
            int total;
            counts.TryGetValue(id, out total);
            total++;
            counts[id] = total;

            if (!config.ShowTotals) return;
            if (!permission.UserHasPermission(id, PermTrack)) return;

            player.ChatMessage(string.Format(lang.GetMessage("ReskinWarn", this, id), total));
        }

        [ChatCommand("reskins")]
        private void CmdReskins(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            int total;
            counts.TryGetValue(player.UserIDString, out total);
            player.ChatMessage(string.Format(lang.GetMessage("ReskinTotal", this, player.UserIDString), total));
        }
    }
}

The plugin records how many times each player reskins an entity and prints the running total to chat only for players with the tracker permission. LoadDefaultConfig seeds a config with a show-totals toggle. In OnEntityReskinned, the hook null-checks player and entity, increments a per-player counter stored in oxide/data, and sends a localized message when the permission check passes. Unload writes the counters back to disk. Because the hook fires after the skin is already applied, the plugin never interferes with the reskin itself; it only observes.

Common use cases

  • Skin usage audit trail

    Log which player applied which skin id to which entity type so admins can roll back grief-related skins or investigate reported signs.

  • Per-player reskin quotas

    Count reskins within a rolling window and warn or notify staff when a player exceeds a configured limit, discouraging skin spam.

  • Reward cosmetic activity

    Award a small in-game currency through a referenced Economics plugin each time a player reskins an entity, with a cooldown to avoid farming.

Pitfalls

  • Hook fires after the change

    You cannot block or revert a reskin here because the skin is already applied and the client result already sent. Use this hook to observe, not to approve.

  • player may be null

    The injection passes msg.player; keep a null guard before touching player.UserIDString or permission checks to avoid throwing inside the hook.

  • Spray can condition loss is unconditional

    The method still calls LoseCondition after the hook, so any logic that also consumes items duplicates condition loss unless you account for it.

Build a plugin with OnEntityReskinned

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

Prompt: Skin usage audit trail using the OnEntityReskinned 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 OnEntityReskinned

Generate