C#:
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
/* Changelog
1.1.6
- Fixed: Horse will now rear when unauthorized players attempt to lead or mount
- Added: Config option to prevent horses losing health when in owners/teams building priv
1.1.5
- Fixed: Updated for May 1st Rust update
1.1.4:
- Fixed: Players being able to claim horses without buying in some cases
- Added: Config option to set minimum time before warning player about unlock time (reduce chat notification spam)
1.1.3:
- Fixed: Players being able to claim horses at Ranch without using a saddle
1.1.2:
- Fixed: Unauthorised players are now unable to loot horse inventory
1.1.1:
- Fixed: Use Friends, Clans and Teams options now work as intended when set to false
*/
namespace Oxide.Plugins
{
[Info("Horse Lock", "ZEODE", "1.1.6")]
[Description("Lock ridable horses to players to prevent unauthorised use.")]
public class HorseLock: RustPlugin
{
#region Plugin References
[PluginReference] Plugin Friends, Clans, VehicleLicence;
#endregion Plugin References
#region Consts
private const string horsePrefab = "assets/content/vehicles/horse/ridablehorse.prefab";
private const string saddlePrefab = "assets/prefabs/vehicle/seats/horsesaddle.prefab";
#endregion Consts
#region Oxide Hooks
private void Init()
{
try
{
storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
if (storedData == null)
{
Puts("Data is null. Creating blank data file...");
storedData = new StoredData();
SaveData();
}
}
catch (Exception ex)
{
if (ex is JsonSerializationException || ex is NullReferenceException || ex is JsonReaderException || ex is KeyNotFoundException)
{
Puts("Data file invalid. Creating blank data file...");
storedData = new StoredData();
SaveData();
return;
}
throw;
}
}
private void Unload()
{
SaveData();
}
private void OnServerSave()
{
SaveData();
}
private void OnNewSave()
{
PrintWarning("Server wipe detected, clearing all player horse data.");
storedData = new StoredData();
SaveData();
}
private object CanMountEntity(BasePlayer player, BaseMountable mount)
{
if (player == null || mount == null)
return null;
if (mount.name.Equals(saddlePrefab) && player.userID.IsSteamId())
{
RidableHorse horse = mount?.GetParentEntity() as RidableHorse;
if (horse == null)
return null;
return CanMountOrLead(horse, player, false);
}
return null;
}
private object OnHorseLead(RidableHorse horse, BasePlayer player)
{
if (player == null || horse == null)
return null;
if (horse.name.Equals(horsePrefab) && player.userID.IsSteamId())
return CanMountOrLead(horse, player, true);
return null;
}
private object CanLootEntity(BasePlayer player, RidableHorse horse)
{
if (player == null || horse == null)
return null;
if (horse.name.Equals(horsePrefab) && player.userID.IsSteamId())
{
if (horse.IsForSale)
return null;
return CanMountOrLead(horse, player, false);
}
return null;
}
private object OnEntityDismounted(BaseMountable entity, BasePlayer player)
{
if (player == null || entity == null)
return null;
if (entity.name.Equals(saddlePrefab) && player.userID.IsSteamId())
{
RidableHorse horse = entity?.GetParentEntity() as RidableHorse;
if (horse == null || player == null)
return null;
if (VehicleLicence && (bool)VehicleLicence?.CallHook("IsLicensedVehicle", horse))
return null;
ulong ownerId = horse.OwnerID;
ulong horseNetId = horse.net.ID.Value;
float dismountTime;
if (!storedData.playerData[ownerId].Horses.TryGetValue(horseNetId, out dismountTime))
return null;
BasePlayer owner = FindPlayerByPartialNameOrId(ownerId.ToString());
if (owner == null)
return null;
if (Time.realtimeSinceStartup - storedData.playerData[ownerId].UnlockWarningTime >= config.options.warningTimeout)
{
storedData.playerData[ownerId].UnlockWarningTime = Time.realtimeSinceStartup;
var horsePriv = horse?.GetBuildingPrivilege();
if (!horsePriv || !horsePriv.IsAuthed(ownerId))
{
TimeSpan time = TimeSpan.FromSeconds(config.options.lockTime);
Message(player, "DismountTimeSplit", time.ToString("hh"), time.ToString("mm"), time.ToString("ss"));
}
}
AddHorseData(owner.userID, owner.displayName, horseNetId);
}
return null;
}
private object OnEntityTakeDamage(RidableHorse horse, HitInfo info)
{
if (horse == null || info == null)
return null;
var damageType = info.damageTypes.GetMajorityDamageType();
var damageAmount = info.damageTypes.Total();
if (damageType == DamageType.Decay && config.options.privProtected)
{
BasePlayer owner = FindPlayerByPartialNameOrId(horse.OwnerID.ToString());
if (owner == null)
return null;
if (horse.GetBuildingPrivilege()?.IsAuthed(owner) == true)
{
info.damageTypes.Clear();
return true;
}
}
return null;
}
#endregion Oxide Hooks
#region Helpers
private object CanMountOrLead(RidableHorse horse, BasePlayer player, bool lead = false)
{
if (player == null || horse == null)
return null;
ulong playerId = player.userID;
ulong ownerId = horse.OwnerID;
string ownerName = GetOwnerDisplayName(horse);
ulong horseNetId = horse.net.ID.Value;
if (horse.name.Equals(horsePrefab) && player.userID.IsSteamId())
{
if (ownerId == 0)
{
horse.OwnerID = playerId;
AddHorseData(playerId, player.displayName, horseNetId);
Message(player, "Claimed");
return null;
}
else if (VehicleLicence && (bool)VehicleLicence.CallHook("IsLicensedVehicle", horse))
{
return null;
}
if(!storedData.playerData.ContainsKey(ownerId))
AddHorseData(ownerId, ownerName, horseNetId);
float dismountTime;
if (!storedData.playerData[ownerId].Horses.TryGetValue(horseNetId, out dismountTime))
{
if (!IsFriend(ownerId, playerId))
{
if (config.options.doRear)
HorseRear(horse, player);
Message(player, lead ? "NoLead" : "NoMountOrLoot", ownerName);
return true;
}
AddHorseData(ownerId, ownerName, horseNetId);
}
else if (storedData.playerData[ownerId].Horses.TryGetValue(horseNetId, out dismountTime))
{
var horsePriv = horse?.GetBuildingPrivilege();
if (horsePriv && horsePriv.IsAuthed(ownerId))
{
if (!IsFriend(ownerId, playerId))
{
if (config.options.doRear)
HorseRear(horse, player);
Message(player, lead ? "NoLead" : "NoMountOrLoot", ownerName);
return true;
}
}
else
{
float timeSinceDismount = Time.realtimeSinceStartup - storedData.playerData[ownerId].Horses[horseNetId];
if (timeSinceDismount > config.options.lockTime)
{
RemoveHorseData(ownerId, horseNetId);
horse.OwnerID = playerId;
AddHorseData(playerId, player.displayName, horseNetId);
Message(player, "TimedOut");
return null;
}
else if (!IsFriend(ownerId, playerId))
{
if (config.options.doRear)
HorseRear(horse, player);
Message(player, lead ? "NoLead" : "NoMountOrLoot", ownerName);
return true;
}
}
}
}
return null;
}
private void HorseRear(RidableHorse horse, BasePlayer player)
{
horse.duckDoubleTapped = true;
horse.lastDuckTapTime = Time.time;
horse.MovementsUpdate();
}
private bool IsFriend(ulong playerId, ulong targetId)
{
if (playerId == 0 || targetId == 0)
return false;
if (playerId == targetId)
return true;
if (config.options.useClans && Clans)
{
var result = Clans?.Call("IsMemberOrAlly", playerId, targetId);
if (result != null && Convert.ToBoolean(result))
return true;
}
if (config.options.useFriends && Friends)
{
var result = Friends?.Call("AreFriends", playerId, targetId);
if (result != null && Convert.ToBoolean(result))
return true;
}
if (config.options.useTeams)
{
RelationshipManager.PlayerTeam team;
RelationshipManager.ServerInstance.playerToTeam.TryGetValue(playerId, out team);
if (team == null)
return false;
if (team.members.Contains(targetId))
return true;
}
return false;
}
BasePlayer FindPlayerByPartialNameOrId(string nameOrId)
{
if (string.IsNullOrEmpty(nameOrId))
return null;
IPlayer player = covalence.Players.FindPlayer(nameOrId);
if (player != null)
return (BasePlayer)player.Object;
return null;
}
private string GetOwnerDisplayName(BaseEntity entity)
{
var playerID = entity.OwnerID;
if (playerID.IsSteamId())
{
var player = FindPlayerByPartialNameOrId(playerID.ToString());
if (player)
return player.displayName;
var p = covalence.Players.FindPlayerById(playerID.ToString());
if (p != null)
return p.Name;
}
return $"Unknown: {playerID}";
}
#endregion Helpers
#region Language
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["NoLead"] = "You <color=red>cannot</color> lead this horse, it belongs to <color=green>{0}</color>.",
["NoMountOrLoot"] = "You <color=red>cannot</color> mount or loot this horse, it belongs to <color=green>{0}</color>.",
["Claimed"] = "This horse was unclaimed, you now <color=green>own</color> it!",
["DismountTimeSplit"] = "Horse will be unlocked in <color=red>{0} h : {1} m : {2} s</color> unless you mount again in that time or store within your building privilege.",
["TimedOut"] = "This horse was abandoned by it's previous owner, you now <color=green>own</color> it!"
}, this);
}
private string Lang(string messageKey, string playerId, params object[] args) {
return string.Format(lang.GetMessage(messageKey, this, playerId), args);
}
private void Message(BasePlayer player, string messageKey, params object[] args)
{
if (player == null || !player.userID.IsSteamId())
return;
var message = Lang(messageKey, player.UserIDString, args);
Player.Message(player, message, config.options.usePrefix ? config.options.chatPrefix : null, config.options.chatIcon);
}
#endregion Language
#region Stored Data
private static StoredData storedData;
private class StoredData
{
public Dictionary<ulong, PlayerData> playerData = new Dictionary<ulong, PlayerData>();
}
private class PlayerData
{
public string PlayerName;
public ulong UserID;
public float UnlockWarningTime;
public Dictionary<ulong, float> Horses = new Dictionary<ulong, float>();
}
private void AddHorseData(ulong playerId, string playerName, ulong horseId)
{
if(!storedData.playerData.ContainsKey(playerId))
{
storedData.playerData.Add(playerId, new PlayerData());
storedData.playerData[playerId].PlayerName = playerName;
storedData.playerData[playerId].UserID = playerId;
storedData.playerData[playerId].Horses.Add(horseId, Time.realtimeSinceStartup);
}
else
{
if (storedData.playerData[playerId].Horses.ContainsKey(horseId))
storedData.playerData[playerId].Horses[horseId] = Time.realtimeSinceStartup;
else
storedData.playerData[playerId].Horses.Add(horseId, Time.realtimeSinceStartup);
}
}
private void RemoveHorseData(ulong playerId, ulong horseId)
{
if (storedData.playerData.ContainsKey(playerId))
{
if (storedData.playerData[playerId].Horses.ContainsKey(horseId))
storedData.playerData[playerId].Horses.Remove(horseId);
}
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, storedData);
}
#endregion Stored Data
#region Config
private static ConfigData config;
private class ConfigData
{
[JsonProperty(PropertyName = "Options")]
public Options options;
public class Options
{
[JsonProperty(PropertyName = "Use Chat Prefix")]
public bool usePrefix { get; set; }
[JsonProperty(PropertyName = "Chat Prefix")]
public string chatPrefix { get; set; }
[JsonProperty(PropertyName = "Use Friends Plugin (Friends Can Mount)")]
public bool useFriends { get; set; }
[JsonProperty(PropertyName = "Use Clans Plugin (Clan Can Mount)")]
public bool useClans { get; set; }
[JsonProperty(PropertyName = "Use Teams (Team Can Mount)")]
public bool useTeams { get; set; }
[JsonProperty(PropertyName = "Custom Chat Icon (Default = 0)")]
public ulong chatIcon { get; set; }
[JsonProperty(PropertyName = "Time Horse Locked After Dismount (Seconds)")]
public float lockTime { get; set; }
[JsonProperty(PropertyName = "Minimum Time Between Dismount Unlock Warnings (Seconds)")]
public float warningTimeout { get; set; }
[JsonProperty(PropertyName = "Make Horse Rear When Unauthorised Player Attempts to Mount")]
public bool doRear { get; set; }
[JsonProperty(PropertyName = "Horses Within Building Privilege Do Not Lose Health")]
public bool privProtected { get; set; }
}
[JsonProperty(PropertyName = "Plugin Version")]
public VersionNumber pluginVersion { get; set; }
}
private ConfigData GetDefaultConfig()
{
return new ConfigData
{
options = new ConfigData.Options
{
usePrefix = true,
chatPrefix = "[Horse Lock]: ",
useFriends = false,
useClans = false,
useTeams = false,
chatIcon = 0,
lockTime = 900f,
warningTimeout = 120f,
doRear = true,
privProtected = false
},
pluginVersion = Version
};
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<ConfigData>();
if (config == null)
LoadDefaultConfig();
else if (config.pluginVersion < Version)
UpdateConfigValues();
}
catch (Exception ex)
{
if (ex is JsonSerializationException || ex is NullReferenceException || ex is JsonReaderException)
{
Puts($"ERROR: {ex}");
return;
}
throw;
}
}
protected override void LoadDefaultConfig()
{
Puts("Configuration file missing or corrupt, creating default config file.");
config = GetDefaultConfig();
}
protected override void SaveConfig()
{
Config.WriteObject(config);
}
private void UpdateConfigValues()
{
ConfigData defaultConfig = GetDefaultConfig();
Puts("Config update detected! Updating config file...");
if (config.pluginVersion < new VersionNumber(1, 0, 1))
{
config = defaultConfig;
}
if (config.pluginVersion < new VersionNumber(1, 1, 4))
{
config.options.warningTimeout = defaultConfig.options.warningTimeout;
}
if (config.pluginVersion < new VersionNumber(1, 1, 6))
{
config.options.privProtected = defaultConfig.options.privProtected;
}
Puts("Config update completed!");
config.pluginVersion = Version;
SaveConfig();
}
#endregion Config
}
}