Skip to content
RustPluginGenerator

OnSignContentCopied

OnSignContentCopied is called after SignContent.CopyInfoToSign copies a player's sign artwork and edit history onto a sign entity.

Signature
void OnSignContentCopied(SignContent signContent, ISignage signage, IUGCBrowserEntity browserEntity)

Parameters

Hook parameters
ParameterTypeDescription
signContentSignContentThe SignContent instance holding the texture data and edit history that is being copied onto the signage.
signageISignageThe target ISignage entity (sign, photo frame) receiving the content; cast to BaseEntity or BaseNetworkable when you need its position or ID.
browserEntityIUGCBrowserEntityThe IUGCBrowserEntity view of the same entity, exposing the editing history; usually only needed when you inspect the copy source.

Return value

The hook is injected with a continue behavior and CopyInfoToSign returns void, so any return value you produce is ignored and the original copy always completes.

How it works

The hook fires at the end of SignContent.CopyInfoToSign, after the target signage's EditingHistory has been cleared and refilled with the entries from the source SignContent. That method is the transfer step in Rust's sign workflow: a player paints in the sign editor and the server writes the resulting texture and history onto the world entity, so this hook runs once per completed copy. Because it fires after the data is already written, it is a notification hook, not a gate.

signContent gives you the source data (texture, edit history). s is the target signage, and b is the same entity seen through IUGCBrowserEntity. Both interface parameters can be cast to BaseEntity or BaseNetworkable; the interface itself has few usable members. Declaring a more specific type on the interface parameters is not practical, but declaring SignContent exactly is required for Oxide to dispatch the call.

Use it to record who painted which sign, audit vandalism on public signs, or trigger a reward when a player completes artwork. Keep the handler cheap: it can fire on every save of a sign editor session, so avoid chat spam or heavy entity scans.

Example plugin: Grant a reward when a player paints a sign

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

namespace Oxide.Plugins
{
    [Info("Sign Paint Reward", "RustPluginGenerator", "1.0.0")]
    [Description("Gives players a scrap reward the first time they finish painting a sign, with a cooldown.")]
    public class SignPaintReward : RustPlugin
    {
        private const string PermReward = "signpaintreward.use";

        private PluginConfig config;
        private readonly Dictionary<string, double> lastReward = new Dictionary<string, double>();

        private class PluginConfig
        {
            [JsonProperty("Reward amount")]
            public int RewardAmount = 25;

            [JsonProperty("Reward item shortname")]
            public string RewardItem = "scrap";

            [JsonProperty("Cooldown seconds")]
            public double CooldownSeconds = 600.0;
        }

        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>
            {
                ["Reward"] = "Nice artwork! You received {0} x {1}.",
                ["Cooldown"] = "Sign reward is on cooldown."
            }, 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(PermReward, this);
        }

        private void OnSignContentCopied(SignContent signContent, ISignage signage, IUGCBrowserEntity browserEntity)
        {
            if (signContent == null || signage == null) return;

            var entity = signage as BaseEntity;
            if (entity == null || entity.OwnerID == 0) return;

            var player = BasePlayer.FindByID(entity.OwnerID);
            if (player == null || !player.IsConnected || player.IsAdmin) return;
            if (!permission.UserHasPermission(player.UserIDString, PermReward)) return;

            double now = UnityEngine.Time.realtimeSinceStartup;
            double last;
            if (lastReward.TryGetValue(player.UserIDString, out last) && now - last < config.CooldownSeconds)
            {
                return;
            }

            var item = ItemManager.CreateByName(config.RewardItem, config.RewardAmount);
            if (item == null) return;

            if (!player.inventory.GiveItem(item))
            {
                item.Drop(player.transform.position, Vector3.zero);
            }

            lastReward[player.UserIDString] = now;
            player.ChatMessage(Msg("Reward", player.UserIDString, config.RewardAmount, item.info.displayName.english));
        }
    }
}

The plugin hooks OnSignContentCopied, casts the signage to a BaseEntity and looks up the owner. If the owner is a connected, non-admin player and their cooldown has expired, it awards scrap through ItemManager.CreateByName and GiveItem, updates the cooldown, and sends a localized chat message. A dictionary keyed by UserIDString tracks per-player cooldowns, and the reward amount comes from a config file so admins can tune it. All player-facing text goes through the language file, and the handler never prints to console.

Common use cases

  • Reward creative players

    Hand out scrap or a currency item the first time a player finishes painting a sign, with a per-player cooldown to prevent farming.

  • Audit public artwork

    Record the owner ID, position, and timestamp of every sign content copy so staff can trace who painted an offensive or rule-breaking image.

  • Flag suspicious sign edits

    Compare the sign owner with the editing history entries and warn staff when a sign is being overwritten by a different player.

Pitfalls

  • Fires after the write

    The texture and history are already on the entity when the hook runs, so you cannot reject or modify a copy here. Blocking has to happen earlier in the sign editor flow.

  • Interface parameters are thin

    ISignage and IUGCBrowserEntity expose little, so cast s or b to BaseEntity/BaseNetworkable before reading OwnerID or transform.

  • Can fire repeatedly

    Each copy onto the entity triggers the hook, so cooldowns and cheap handlers keep rewards and logs from spamming.

Build a plugin with OnSignContentCopied

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

Prompt: Reward creative players using the OnSignContentCopied 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 OnSignContentCopied

Generate