Skip to content
RustPluginGenerator

OnHorseUnhitch

OnHorseUnhitch is called from HitchTrough.UnHitch just before the game clears a hitch spot and detaches the animal from the trough. Returning a non-null value cancels the unhitch and leaves the animal tied.

Signature
object OnHorseUnhitch(HitchTrough.IHitchable hitchable, HitchTrough.HitchSpot hitchSpot)

Parameters

Hook parameters
ParameterTypeDescription
hitchableHitchTrough.IHitchableThe HitchTrough.IHitchable being released from its hitch spot. In practice this is the ridable animal (e.g. a horse) that is currently tied to the trough; use it to identify the animal and cast it to a BaseEntity when you need prefab, position or owner data.
hitchSpotHitchTrough.HitchSpotThe HitchTrough.HitchSpot the animal currently occupies. It is the specific slot being cleared, and it exposes the trough context and its own occupied state so you can tell exactly which spot is being released.

Return value

Returning null lets the game continue: the spot is cleared and the animal is detached. Returning any non-null value stops the original unhitch for that spot, so the animal stays tied and the loop breaks; the hook is cancelled for the rest of that call. The original method returns void, so the return value is only used as a cancel signal.

How it works

The hook fires inside HitchTrough.UnHitch for each hitch spot on the trough, when a spot's occupant matches the animal being released. This happens when a player unhitches an animal from a hitch trough through the game's normal interaction, and the method is also the common path used whenever the game programmatically detaches an animal from a trough. It fires once per matching spot, before the game calls SetOccupiedBy(null) and SetHitch(null, null).

hitchable is the animal being released; cast it to BaseEntity (or a concrete type such as RidableAnimal) to read its position, owner and network identity. hitchSpot identifies which slot is being freed, which is useful if a trough has several spots and you care about the specific one.

Because the animal argument is an interface type, declaring a more specific parameter type narrows when Oxide calls your method. Use that only if you want to ignore other hitchable entities. For most admin features, keep the interface signature and inspect the object yourself. The hook fires on the server only and does not run for players, so keep per-call work cheap.

Example plugin: Protect horses from unhitching during a raid cooldown

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

namespace Oxide.Plugins
{
    [Info("Horse Hitch Protection", "RustPluginGenerator", "1.0.0")]
    [Description("Blocks unhitching of horses while their owner is in a protection window.")]
    public class HorseHitchProtection : RustPlugin
    {
        private const string PermBypass = "horsehitchprotection.bypass";

        private PluginConfig config;
        private StoredData data;

        private class PluginConfig
        {
            [JsonProperty("Protection duration (seconds)")]
            public double ProtectionSeconds = 600;
        }

        private class StoredData
        {
            [JsonProperty("Protected player ids")]
            public Dictionary<string, double> Protected = new Dictionary<string, double>();
        }

        private class PluginData
        {
            public StoredData Value;
        }

        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);

        private void Init()
        {
            permission.RegisterPermission(PermBypass, this);
            LoadData();
        }

        private void Unload()
        {
            SaveData();
        }

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

        private void SaveData()
        {
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }

        private bool IsProtected(BaseEntity entity)
        {
            if (entity == null) return false;
            string id = entity.OwnerID.ToString();
            if (!data.Protected.TryGetValue(id, out double until)) return false;
            if (UnityEngine.Time.realtimeSinceStartup > until)
            {
                data.Protected.Remove(id);
                return false;
            }
            return true;
        }

        private object OnHorseUnhitch(HitchTrough.IHitchable hitchable, HitchTrough.HitchSpot hitchSpot)
        {
            if (hitchable == null || hitchSpot == null) return null;

            BaseEntity entity = hitchable as BaseEntity;
            if (entity == null) return null;

            string ownerId = entity.OwnerID.ToString();
            if (permission.UserHasPermission(ownerId, PermBypass)) return null;

            if (IsProtected(entity))
            {
                return true;
            }

            return null;
        }

        [ChatCommand("hitchprotect")]
        private void CmdProtect(BasePlayer player, string command, string[] args)
        {
            if (player == null || !player.IsAdmin) return;

            double seconds = config.ProtectionSeconds;
            if (args != null && args.Length > 0 && double.TryParse(args[0], out double parsed) && parsed > 0)
            {
                seconds = parsed;
            }

            string targetId = player.UserIDString;
            if (args != null && args.Length > 1)
            {
                BasePlayer target = BasePlayer.FindAwakeOrSleeping(args[1]);
                if (target != null) targetId = target.UserIDString;
            }

            data.Protected[targetId] = UnityEngine.Time.realtimeSinceStartup + seconds;
            SaveData();
            player.ChatMessage($"Hitch protection enabled for {targetId} for {seconds:0} seconds.");
        }
    }
}

The plugin blocks unhitching of horses while their owner is in an active raid window, which is useful on servers that want raiders to commit stolen mounts to a trough instead of instantly running off. It registers a permission so admins can bypass the lock, and a config entry controls how long the protection lasts after a player is flagged.

On OnHorseUnhitch, the plugin casts the hitchable to BaseEntity and reads its OwnerID. If the owner lacks the bypass permission and the protection window is active, it returns a non-null value, which cancels the unhitch and keeps the animal tied. Otherwise it returns null and the game detaches the horse normally. A chat command lets admins start the protection window for a given player.

Common use cases

  • Raid-scenario horse locking

    Prevent players from unhitching horses during a raid or event window so mounts stay committed to the trough. Return a non-null value for owners in the locked state.

  • Logging mount transfers

    Record which player's horse was unhitched, from which trough position, to audit suspicious movement of animals between teams. Return null and write to a data file.

  • Owner-only unhitch rules

    Allow only the animal's owner or their teammates to release it, returning non-null for everyone else. Combine with team lookups via RelationshipManager for shared mounts.

Pitfalls

  • Fires for every matching spot

    The hook is inside a loop over the trough's hitch spots and only fires for the spot whose occupant matches the animal. Avoid assuming it fires for every spot on the trough.

  • No built-in player context

    The signature gives you no player. If you need the acting player, resolve it from the hitchable's owner or from your own command or input hooks; do not expect a BasePlayer parameter to be populated by Oxide.

  • Over-broad interface type

    Declaring the hitchable parameter as a narrower type such as RidableAnimal changes when Oxide calls your method, silently skipping other hitchable entities. Only narrow if that filtering is exactly what you want.

Build a plugin with OnHorseUnhitch

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

Prompt: Raid-scenario horse locking using the OnHorseUnhitch 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 OnHorseUnhitch

Generate