Skip to content
RustPluginGenerator

OnAnimalDungProduced

OnAnimalDungProduced is called by RidableHorse.DoDung right after the horse's dung item has been created and dropped into the world. It fires for each pile a horse produces during its dung timer.

Signature
void OnAnimalDungProduced(RidableHorse ridableHorse, Item item)

Parameters

Hook parameters
ParameterTypeDescription
ridableHorseRidableHorseThe horse (or other ridable animal running RidableHorse.DoDung) that produced the dung. Use it to read owner, breed, position and network identity.
itemItemThe dropped dung item created by the game for this event. It is already dropped into the world, so you can read its owner, skin or position, or remove it to suppress the drop.

Return value

The return value is ignored at this injection site. Returning null, true or any other value has no effect on whether the dung is produced or dropped; the game continues regardless. To change the outcome, act on the item parameter itself (for example item.Remove()).

How it works

The hook fires inside RidableHorse.DoDung, after the game has created the dung Item, set its ownership phrase (Pooped) and called Item.Drop with a random position offset and velocity. That means by the time your plugin sees ridableHorse and item, the item is already a DroppedItem in the world, not something waiting to be spawned.

Use ridableHorse to identify the producer: OwnerID, net.ID.Value, ShortPrefabName, IsNpc, the animal's transform position, or its breed data if you track horses elsewhere. Use item for the pile itself: item.info.shortname, amount, skin, parent (the dropped item's container) or GetOwnerPlayer() if you want to know who owns the pile.

Because the item already exists and is dropped, suppression requires calling item.Remove() on it rather than returning a value. There is no narrower parameter type to declare: the only overload the game calls is (RidableHorse, Item), so declaring BaseEntity for the first argument would simply never run.

The hook fires often on servers with many horses, so keep the body cheap and never log inside it.

Example plugin: Notify owners when their horse leaves dung

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

namespace Oxide.Plugins
{
    [Info("HorseDungNotifier", "RustPluginGenerator", "1.0.0")]
    [Description("Sends horse owners a throttled chat message when their horse produces dung.")]
    public class HorseDungNotifier : RustPlugin
    {
        private const string PermUse = "horsedungnotifier.use";

        private PluginConfig config;
        private StoredData data;
        private readonly Dictionary<ulong, float> lastNotice = new Dictionary<ulong, float>();

        private class PluginConfig
        {
            [JsonProperty("Cooldown seconds between notices per player")]
            public float CooldownSeconds = 60f;
        }

        private class StoredData
        {
            [JsonProperty("Players who disabled dung notices")]
            public HashSet<string> Disabled = new HashSet<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>
            {
                ["Notice"] = "Your {0} just left dung near {1}. Use /dungnotice to toggle these alerts.",
                ["Disabled"] = "Dung notices are now disabled.",
                ["Enabled"] = "Dung notices are now enabled.",
                ["NoPermission"] = "You do not have permission to use this command."
            }, 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);
            LoadData();
        }

        private void LoadData()
        {
            try
            {
                data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
            }
            catch
            {
                data = null;
            }
            if (data == null || data.Disabled == null) data = new StoredData();
        }

        private void OnAnimalDungProduced(RidableHorse ridableHorse, Item item)
        {
            if (ridableHorse == null || ridableHorse.IsNpc || ridableHorse.OwnerID == 0) return;

            var owner = BasePlayer.FindByID(ridableHorse.OwnerID);
            if (owner == null || !owner.IsConnected) return;
            if (data.Disabled.Contains(owner.UserIDString)) return;

            float now = UnityEngine.Time.realtimeSinceStartup;
            float last;
            if (lastNotice.TryGetValue(owner.userID, out last) && now - last < config.CooldownSeconds) return;
            lastNotice[owner.userID] = now;

            Vector3 pos = ridableHorse.transform.position;
            owner.ChatMessage(Msg("Notice", owner.UserIDString,
                ridableHorse.ShortPrefabName ?? "horse",
                string.Format("{0:F0}, {1:F0}", pos.x, pos.z)));
        }

        [ChatCommand("dungnotice")]
        private void CmdDungNotice(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }

            if (data.Disabled.Contains(player.UserIDString))
            {
                data.Disabled.Remove(player.UserIDString);
                player.ChatMessage(Msg("Enabled", player.UserIDString));
            }
            else
            {
                data.Disabled.Add(player.UserIDString);
                player.ChatMessage(Msg("Disabled", player.UserIDString));
            }
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }

        private void Unload()
        {
            lastNotice.Clear();
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }
    }
}

The plugin registers a /dungnotice chat command that toggles a per-player preference stored in a data file under oxide/data. When OnAnimalDungProduced fires, it checks ridableHorse.IsNpc (NPC horses are skipped), resolves the owner with BasePlayer.FindByID(ridableHorse.OwnerID), verifies the player is connected and has the preference enabled, then sends a chat message through the language file including the horse's short prefab name and the pile count. It also has an optional cooldown config value so a large herd does not flood the owner's chat; the cooldown is tracked in memory per player ID. All player-facing strings live in LoadDefaultMessages, and the cooldown dictionary is cleared in Unload.

Common use cases

  • Remove dung on tidy servers

    Call item.Remove() inside the hook to delete every pile the instant it is created, which keeps stables and pens clean without a periodic cleanup timer. Combine it with a permission check so only opted-in players get clean stables.

  • Reward horse ownership

    Read ridableHorse.OwnerID and credit the owner through a referenced plugin (Economics, ServerRewards) for each pile, or increment a counter that a leaderboard command later reads. Guard against NPC horses with ridableHorse.IsNpc.

  • Alert the owner with position

    Send the owner a chat or CUI hint including ridableHorse.transform.position so they can find the pile before it despawns, throttled by a per-player cooldown to avoid spam.

Pitfalls

  • The item is already dropped

    Item.Drop runs before the hook, so item.parent is already the dropped container and moving the item elsewhere requires care. If you want the item in the owner's inventory, remove it from the world and create a fresh item with ItemManager.Create, then call GiveItem.

  • Fires very often

    On busy servers with many horses this hook can fire dozens of times per second. Any logging, data saving or chat message inside it must be throttled or it will hurt performance; never call Puts or PrintWarning here.

  • OwnerID is not always a player

    Horses spawned by plugins or server events can have a OwnerID of 0 or an NPC ID. Check ridableHorse.IsNpc and use BasePlayer.FindByID which returns null for missing players before sending messages.

Build a plugin with OnAnimalDungProduced

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

Prompt: Remove dung on tidy servers using the OnAnimalDungProduced 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 OnAnimalDungProduced

Generate