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.
void OnAnimalDungProduced(RidableHorse ridableHorse, Item item)Parameters
| Parameter | Type | Description |
|---|---|---|
ridableHorse | RidableHorse | The horse (or other ridable animal running RidableHorse.DoDung) that produced the dung. Use it to read owner, breed, position and network identity. |
item | Item | The 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
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.OwnerIDand 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 withridableHorse.IsNpc.Alert the owner with position
Send the owner a chat or CUI hint including
ridableHorse.transform.positionso they can find the pile before it despawns, throttled by a per-player cooldown to avoid spam.
Pitfalls
The item is already dropped
Item.Dropruns before the hook, soitem.parentis 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 withItemManager.Create, then callGiveItem.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
PutsorPrintWarninghere.OwnerID is not always a player
Horses spawned by plugins or server events can have a
OwnerIDof 0 or an NPC ID. CheckridableHorse.IsNpcand useBasePlayer.FindByIDwhich 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.
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