OnHorseHitch
OnHorseHitch is called from HitchTrough.AttemptToHitch right before the chosen hitch spot is marked occupied, giving you a chance to allow or deny hitching the animal to that trough.
object OnHorseHitch(HitchTrough.IHitchable hitchable, HitchTrough.HitchSpot spot)Parameters
| Parameter | Type | Description |
|---|---|---|
hitchable | HitchTrough.IHitchable | The hitchable entity being placed at the trough, exposed as HitchTrough.IHitchable. In practice this is the horse or ridable animal; cast it to BaseEntity to read OwnerID, net.ID or transform position. |
spot | HitchTrough.HitchSpot | The HitchTrough.HitchSpot the game chose (either requested by the caller or the closest free spot). You cannot occupy it yourself from the hook, so treat it as read-only context for the decision. |
Return value
Returning null (or anything that is not a bool) lets the original code proceed, so the spot is marked occupied and AttemptToHitch continues normally. Returning a bool replaces the method's result: true reports a successful hitch without the game occupying the spot, while false cancels the hitch and the spot stays free.
How it works
The hook fires inside HitchTrough.AttemptToHitch, after the game has resolved which HitchSpot to use, and immediately before spot.SetOccupiedBy(hitchable) runs. It runs both when a player uses a trough (the game passes the entity that is trying to be hitched) and when no spot was supplied and the closest free spot was selected automatically.
hitchable is the animal being attached to the trough. The useful work is to cast it to BaseEntity (or BaseRidableAnimal) and check OwnerID, IsDestroyed, distance to the trough, or team membership. spot identifies the target position; its owner trough is not passed separately, so use hitchable.transform.position or your own tracked trough list when you need location context.
Because the game calls the hook for any hitchable, you can narrow it by declaring BasePlayer - never appropriate here - or by declaring a more specific type than IHitchable. Most plugins simply keep IHitchable and filter with as casts, since the interface is the documented contract.
Example plugin: Block hitching animals you do not own to someone else's trough
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Trough Owner Guard", "RustPluginGenerator", "1.0.0")]
[Description("Restricts hitching animals to troughs so only the owner or their team can do it")]
public class TroughOwnerGuard : RustPlugin
{
private const string PermBypass = "troughownerguard.bypass";
private PluginConfig config;
public class PluginConfig
{
[JsonProperty("Max trough search distance")]
public float MaxTroughDistance = 4f;
[JsonProperty("Require same team as animal owner")]
public bool RequireSameTeam = 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>
{
["NoPermission"] = "You are not allowed to hitch an animal you do not own to this trough.",
["NotYourAnimal"] = "This animal belongs to someone else.",
["TroughTooFar"] = "No usable hitch trough is close enough."
}, 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 object OnHorseHitch(HitchTrough.IHitchable hitchable, HitchTrough.HitchSpot spot)
{
if (hitchable == null || spot == null) return null;
var animal = hitchable as BaseEntity;
if (animal == null || animal.IsDestroyed) return null;
var trough = FindTrough(animal.transform.position);
if (trough == null) return null;
var animalOwner = animal.OwnerID;
if (animalOwner == 0 || trough.OwnerID == animalOwner) return null;
var player = BasePlayer.FindByID(animalOwner);
if (config.RequireSameTeam && player != null && player.currentTeam != 0)
{
var team = RelationshipManager.ServerInstance?.FindTeam(player.currentTeam);
if (team != null && team.members.Contains(trough.OwnerID))
return null;
}
if (player != null)
{
if (permission.UserHasPermission(player.UserIDString, PermBypass)) return null;
player.ChatMessage(Msg("NotYourAnimal", player.UserIDString));
}
return false;
}
private HitchTrough FindTrough(Vector3 position)
{
foreach (var entity in BaseNetworkable.serverEntities)
{
var trough = entity as HitchTrough;
if (trough == null || trough.IsDestroyed) continue;
if (Vector3.Distance(trough.transform.position, position) <= config.MaxTroughDistance) return trough;
}
return null;
}
}
}The plugin denies hitching when the animal's owner differs from the trough's owner and the animal owner is not on the same team as the trough owner. It locates a nearby HitchTrough by scanning BaseNetworkable.serverEntities for one within the configured MaxTroughDistance (default 4) of the animal, then checks ownership via OwnerID and the animal owner's team through RelationshipManager. A permission (troughownerguard.bypass) lets the animal's owner bypass the restriction. Every denial returns false, which prevents the hitch; every allowed hitch returns null so normal game behavior continues. The animal owner receives the NotYourAnimal chat message when denied. No extra data files are needed; settings live in the plugin config.
Common use cases
Whitelist hitching by team
Only allow a horse to be hitched by players on the same team as the animal's current owner, preventing raiders from parking stolen horses at their own troughs.
Owner-only troughs
Compare
BaseEntity.OwnerIDof the trough (found via proximity) against the hitchable's owner and deny hitching from anyone else, giving players personal stables.Maintenance mode
During a server event or while restructuring a base, temporarily return
falseto freeze hitching without removing troughs or touching prefabs.
Pitfalls
The trough is not passed
The hook only gives the hitchable and the spot. To check the trough you need the hitchable's position or your own mapping; do not assume
spothas a public owner field.Returning true skips SetOccupiedBy
Returning
trueshort-circuits the original method before the spot is marked occupied, so the animal is considered hitched by the caller while the trough stays free. Usenullfor default behaviour andfalseto cancel.Do not destroy entities here
Killing the hitchable or the trough inside this hook runs while the original method still holds state. Defer destructive actions with
NextTickif you must perform them.
Build a plugin with OnHorseHitch
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 OnHorseHitch
Generate