C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Facepunch;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using UnityEngine;
using System;
namespace Oxide.Plugins
{
[Info("Restore Upon Death", "k1lly0u", "0.6.3")]
[Description("Restores player inventories on death and removes the items from their corpse")]
class RestoreUponDeath : RustPlugin
{
#region Fields
private const int SmallBackpackItemId = 2068884361;
private const int LargeBackpackItemId = -907422733;
private RestoreData restoreData;
private DynamicConfigFile restorationData;
private bool wipeData = false;
#endregion
#region Oxide Hooks
private void OnServerInitialized()
{
LoadData();
if (Configuration.DropActiveItem)
Unsubscribe(nameof(CanDropActiveItem));
foreach (string perm in Configuration.Permissions.Keys)
permission.RegisterPermission(!perm.StartsWith("restoreupondeath.") ? $"restoreupondeath.{perm}" : perm, this);
EnableBackpackDropOnDeath(SmallBackpackItemId, Configuration.DropBackpack);
EnableBackpackDropOnDeath(LargeBackpackItemId, Configuration.DropBackpack);
}
private void Unload()
{
EnableBackpackDropOnDeath(SmallBackpackItemId, false);
EnableBackpackDropOnDeath(LargeBackpackItemId, false);
OnServerSave();
}
private void OnNewSave(string fileName)
{
if (Configuration.WipeOnNewSave)
wipeData = true;
}
private void OnServerSave() => SaveData();
private object CanDropActiveItem(BasePlayer player) => ShouldBlockRestore(player) ? null : (object)(!HasAnyPermission(player.UserIDString));
private void OnEntityDeath(BasePlayer player, HitInfo info)
{
if (!player || !player.userID.IsSteamId() || restoreData.HasRestoreData(player.userID))
return;
if (ShouldBlockRestore(player))
return;
if (Configuration.NoSuicideRestore && info != null && info.damageTypes.GetMajorityDamageType() == Rust.DamageType.Suicide)
return;
if (HasAnyPermission(player.UserIDString, out ConfigData.LossAmounts lossAmounts))
{
double expires = CurrentTime() + (Configuration.PurgeDays * 24 * 60 * 60);
restoreData.AddData(player, lossAmounts, expires);
}
}
// Handle player death while sleeping in a safe zone.private void OnEntityKill(BasePlayer player)
{
OnEntityDeath(player, null);
}
private void OnPlayerRespawned(BasePlayer player)
{
if (!player || !restoreData.HasRestoreData(player.userID))
return;
if (ShouldBlockRestore(player))
return;
TryRestorePlayer(player);
}
#endregion
#region Functions
private bool ShouldBlockRestore(BasePlayer player) => Interface.Call("isEventPlayer", player) != null || Interface.Call("OnRestoreUponDeath", player) != null;
private void TryRestorePlayer(BasePlayer player)
{
if (!player || player.IsDead())
return;
if (!player.IsSleeping())
{
if (!Configuration.DefaultItems)
{
StripContainer(player.inventory.containerWear);
StripContainer(player.inventory.containerBelt);
}
restoreData.RestorePlayer(player);
}
else player.Invoke(() => TryRestorePlayer(player), 0.25f);
}
private bool HasAnyPermission(string playerId)
{
foreach (string key in Configuration.Permissions.Keys)
{
if (permission.UserHasPermission(playerId, key))
return true;
}
return false;
}
private bool HasAnyPermission(string playerId, out ConfigData.LossAmounts lossAmounts)
{
foreach (KeyValuePair<string, ConfigData.LossAmounts> kvp in Configuration.Permissions)
{
if (permission.UserHasPermission(playerId, kvp.Key))
{
lossAmounts = kvp.Value;
return true;
}
}
lossAmounts = null;
return false;
}
private void StripContainer(ItemContainer container)
{
for (int i = container.itemList.Count - 1; i >= 0; i--)
{
Item item = container.itemList[i];
item.RemoveFromContainer();
item.Remove();
}
}
private void EnableBackpackDropOnDeath(int itemId, bool enabled)
{
ItemModBackpack itemModBackpack = ItemManager.FindItemDefinition(itemId)?.GetComponent<ItemModBackpack>();
if (!itemModBackpack)
return;
itemModBackpack.DropWhenDowned = enabled;
}
#endregion
#region Config
private ConfigData Configuration;
private class ConfigData
{
[JsonProperty("Give default items upon respawn if the players is having items restored")]
public bool DefaultItems { get; set; }
[JsonProperty("Can drop active item on death")]
public bool DropActiveItem { get; set; }
[JsonProperty("Can drop backpack on death")]
public bool DropBackpack { get; set; } = true;
[JsonProperty("Don't restore items if player commited suicide")]
public bool NoSuicideRestore { get; set; }
[JsonProperty("Wipe stored data when the map wipes")]
public bool WipeOnNewSave { get; set; }
[JsonProperty("Purge expired user data after X amount of days")]
public int PurgeDays { get; set; } = 7;
[JsonProperty("Percentage of total items lost (Permission Name | Percentage (0 - 100))")]
public Dictionary<string, LossAmounts> Permissions { get; set; }
public class LossAmounts
{
public int Belt { get; set; }
public int Wear { get; set; }
public int Main { get; set; }
}
public VersionNumber Version { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
Configuration = Config.ReadObject<ConfigData>();
if (Configuration.Version < Version)
UpdateConfigValues();
Config.WriteObject(Configuration, true);
}
protected override void LoadDefaultConfig() => Configuration = GetBaseConfig();
private ConfigData GetBaseConfig()
{
return new ConfigData
{
DefaultItems = false,
DropActiveItem = false,
DropBackpack = true,
Permissions = new Dictionary<string, ConfigData.LossAmounts>
{
["restoreupondeath.default"] = new ConfigData.LossAmounts()
{
Belt = 75,
Main = 75,
Wear = 75
},
["restoreupondeath.beltonly"] = new ConfigData.LossAmounts()
{
Belt = 100,
Main = 0,
Wear = 0
},
["restoreupondeath.admin"] = new ConfigData.LossAmounts()
{
Belt = 0,
Main = 0,
Wear = 0
}
},
Version = Version
};
}
protected override void SaveConfig() => Config.WriteObject(Configuration, true);
private void UpdateConfigValues()
{
PrintWarning("Config update detected! Updating config values...");
ConfigData baseConfig = GetBaseConfig();
if (Configuration.Version < new VersionNumber(0, 3, 0))
Configuration.Permissions = baseConfig.Permissions;
Configuration.Version = Version;
PrintWarning("Config update completed!");
}
#endregion
#region Data Management
private double CurrentTime() => DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
public enum ContainerType
{
Main,
Wear,
Belt
}
private void SaveData()
{
restorationData.WriteObject(restoreData);
}
private void LoadData()
{
restorationData = Interface.Oxide.DataFileSystem.GetFile("restoreupondeath_data");
restoreData = restorationData.ReadObject<RestoreData>();
if (restoreData?.restoreData == null || wipeData)
restoreData = new RestoreData();
double currentTime = CurrentTime();
List<ulong> list = Pool.Get<List<ulong>>();
foreach (KeyValuePair<ulong, RestoreData.PlayerData> kvp in restoreData.restoreData)
{
if (kvp.Value.HasExpired(currentTime))
list.Add(kvp.Key);
}
foreach (ulong id in list)
restoreData.RemoveData(id);
Debug.Log($"[RestoreUponDeath] Purged {list.Count} expired user data entries");
Pool.FreeUnmanaged(ref list);
}
private class RestoreData
{
public Hash<ulong, PlayerData> restoreData = new Hash<ulong, PlayerData>();
public void AddData(BasePlayer player, ConfigData.LossAmounts lossAmounts, double expires)
{
restoreData[player.userID] = new PlayerData(player, lossAmounts, expires);
}
public void RemoveData(ulong playerId)
{
if (HasRestoreData(playerId))
restoreData.Remove(playerId);
}
public bool HasRestoreData(ulong playerId) => restoreData.ContainsKey(playerId);
public void RestorePlayer(BasePlayer player)
{
if (restoreData.TryGetValue(player.userID, out PlayerData playerData))
{
if (playerData == null || !player.IsConnected)
return;
RestoreAllItems(player, playerData);
}
}
private void RestoreAllItems(BasePlayer player, PlayerData playerData)
{
if (RestoreItems(player, playerData.containerBelt, ContainerType.Belt) && RestoreItems(player, playerData.containerWear, ContainerType.Wear) && RestoreItems(player, playerData.containerMain, ContainerType.Main))
{
RemoveData(player.userID);
player.ChatMessage("Your inventory has been restored");
}
}
private static bool RestoreItems(BasePlayer player, ItemData[] itemData, ContainerType containerType)
{
ItemContainer container = containerType switch
{
ContainerType.Belt => player.inventory.containerBelt,
ContainerType.Wear => player.inventory.containerWear,
_ => player.inventory.containerMain
};
for (int i = 0; i < itemData.Length; i++)
{
Item item = CreateItem(itemData[i]);
if (item == null)
continue;
if (!item.MoveToContainer(container, itemData[i].position) && !item.MoveToContainer(container) && !player.inventory.GiveItem(item))
{
// If allowing default items, restored items might not fit, so drop them as a last resort.item.Drop(player.inventory.containerMain.dropPosition, player.inventory.containerMain.dropVelocity);
}
}
return true;
}
public class PlayerData
{
public ItemData[] containerMain;
public ItemData[] containerWear;
public ItemData[] containerBelt;
public double expires = 0;
public PlayerData()
{
}
public PlayerData(BasePlayer player, ConfigData.LossAmounts lossAmounts, double expires)
{
containerBelt = GetItems(player.inventory.containerBelt, Mathf.Clamp(lossAmounts.Belt, 0, 100));
containerMain = GetItems(player.inventory.containerMain, Mathf.Clamp(lossAmounts.Main, 0, 100));
containerWear = GetItems(player.inventory.containerWear, Mathf.Clamp(lossAmounts.Wear, 0, 100));
this.expires = expires;
}
public bool HasExpired(double currentTime) => expires > 0 && expires < currentTime;
private static ItemData[] GetItems(ItemContainer container, int lossPercentage)
{
int keepPercentage = 100 - lossPercentage;
int itemCount = keepPercentage == 100 ? container.itemList.Count : Mathf.CeilToInt((float)container.itemList.Count * (float)(keepPercentage / 100f));
if (itemCount == 0)
return Array.Empty<ItemData>();
ItemData[] itemData = new ItemData[itemCount];
for (int i = 0; i < itemCount; i++)
{
Item item = container.itemList.GetRandom();
itemData[i] = new ItemData(item);
item.RemoveFromContainer();
item.Remove();
}
// Sort items by position to ensure they are restored in original order if desired slot is taken by pre-existing items.Array.Sort(itemData, (a, b) => a.position.CompareTo(b.position));
return itemData;
}
}
}
public class ItemData
{
public int itemid;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public ulong skin;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public string displayName;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int amount;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public float condition;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public float maxCondition;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int ammo;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public string ammotype;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int position;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int frequency;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public InstanceData instanceData;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public string text;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public Item.Flag flags;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public ItemData[] contents;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public ItemContainer container;
public ItemData()
{
}
public ItemData(Item item)
{
BaseEntity heldEntity = item.GetHeldEntity();
BaseProjectile baseProjectile = heldEntity as BaseProjectile;
itemid = item.info.itemid;
amount = item.amount;
displayName = item.name;
ammo = baseProjectile ? baseProjectile.primaryMagazine.contents : heldEntity is FlameThrower flameThrower ? flameThrower.ammo : 0;
ammotype = baseProjectile ? baseProjectile.primaryMagazine.ammoType.shortname : null;
position = item.position;
skin = item.skin;
condition = item.condition;
maxCondition = item.maxCondition;
frequency = ItemModAssociatedEntity<PagerEntity>.GetAssociatedEntity(item)?.GetFrequency() ?? -1;
instanceData = new InstanceData(item);
text = item.text;
flags = item.flags;
container = item.contents != null ? new ItemContainer(item.contents) : null;
}
public class InstanceData
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int dataInt;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int blueprintTarget;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int blueprintAmount;
public InstanceData()
{
}
public InstanceData(Item item)
{
if (item.instanceData == null)
return;
dataInt = item.instanceData.dataInt;
blueprintAmount = item.instanceData.blueprintAmount;
blueprintTarget = item.instanceData.blueprintTarget;
}
public void Restore(Item item)
{
if (item.instanceData == null)
item.instanceData = new ProtoBuf.Item.InstanceData();
item.instanceData.ShouldPool = false;
item.instanceData.blueprintAmount = blueprintAmount;
item.instanceData.blueprintTarget = blueprintTarget;
item.instanceData.dataInt = dataInt;
item.MarkDirty();
}
public bool IsValid()
{
return dataInt != 0 || blueprintAmount != 0 || blueprintTarget != 0;
}
}
public class ItemContainer
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int slots;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public float temperature;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int flags;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int allowedContents;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int maxStackSize;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public List<int> allowedItems;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public List<int> availableSlots;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public int volume;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore)]
public List<ItemData> contents;
public ItemContainer()
{
}
public ItemContainer(global::ItemContainer container)
{
contents = new List<ItemData>();
slots = container.capacity;
temperature = container.temperature;
allowedContents = (int)container.allowedContents;
if (container.HasLimitedAllowedItems)
{
allowedItems = new List<int>();
for (int i = 0; i < container.onlyAllowedItems.Length; i++)
{
if (container.onlyAllowedItems[i])
{
allowedItems.Add(container.onlyAllowedItems[i].itemid);
}
}
}
flags = (int)container.flags;
maxStackSize = container.maxStackSize;
volume = container.containerVolume;
if (container.availableSlots is { Count: > 0 })
{
availableSlots = new List<int>();
for (int j = 0; j < container.availableSlots.Count; j++)
{
availableSlots.Add((int)container.availableSlots[j]);
}
}
for (int k = 0; k < container.itemList.Count; k++)
{
Item item = container.itemList[k];
if (item.IsValid())
{
contents.Add(new ItemData(item));
}
}
}
public void Load(global::ItemContainer itemContainer)
{
itemContainer.capacity = slots;
itemContainer.itemList = Pool.Get<List<Item>>();
itemContainer.temperature = temperature;
itemContainer.flags = (global::ItemContainer.Flag)flags;
itemContainer.allowedContents = (global::ItemContainer.ContentsType)((allowedContents == 0) ? 1 : allowedContents);
if (allowedItems is { Count: > 0 })
{
itemContainer.onlyAllowedItems = new ItemDefinition[allowedItems.Count];
for (int i = 0; i < allowedItems.Count; i++)
{
itemContainer.onlyAllowedItems[i] = ItemManager.FindItemDefinition(allowedItems[i]);
}
}
else
{
itemContainer.onlyAllowedItems = null;
}
itemContainer.maxStackSize = maxStackSize;
itemContainer.containerVolume = volume;
itemContainer.availableSlots.Clear();
if (availableSlots != null)
{
for (int j = 0; j < availableSlots.Count; j++)
{
itemContainer.availableSlots.Add((ItemSlot)availableSlots[j]);
}
}
foreach (ItemData itemData in contents)
{
Item item = CreateItem(itemData);
if (item == null)
continue;
if (!item.MoveToContainer(itemContainer, itemData.position) && !item.MoveToContainer(itemContainer))
item.Remove();
}
itemContainer.MarkDirty();
}
}
}
public static Item CreateItem(ItemData itemData)
{
Item item = ItemManager.CreateByItemID(itemData.itemid, Mathf.Max(1, itemData.amount), itemData.skin);
if (item == null)
return null;
item.condition = itemData.condition;
item.maxCondition = itemData.maxCondition;
if (itemData.displayName != null)
{
item.name = itemData.displayName;
}
if (itemData.text != null)
{
item.text = itemData.text;
}
item.flags |= itemData.flags;
if (itemData.frequency > 0)
{
ItemModRFListener rfListener = item.info.GetComponentInChildren<ItemModRFListener>();
if (rfListener)
{
PagerEntity pagerEntity = BaseNetworkable.serverEntities.Find(item.instanceData.subEntity) as PagerEntity;
if (pagerEntity)
{
pagerEntity.ChangeFrequency(itemData.frequency);
item.MarkDirty();
}
}
}
if (itemData.instanceData != null && itemData.instanceData.IsValid())
itemData.instanceData.Restore(item);
FlameThrower flameThrower = item.GetHeldEntity() as FlameThrower;
if (flameThrower)
flameThrower.ammo = itemData.ammo;
if (itemData.contents != null && item.contents != null)
{
foreach (ItemData contentData in itemData.contents)
{
Item childItem = CreateItem(contentData);
if (childItem == null)
continue;
if (!childItem.MoveToContainer(item.contents, contentData.position) && !childItem.MoveToContainer(item.contents))
item.Remove();
item.MarkDirty();
}
item.contents.MarkDirty();
}
if (itemData.container != null)
{
if (item.contents == null)
{
ItemModContainerArmorSlot armorSlot = FindItemMod<ItemModContainerArmorSlot>(item);
if (armorSlot)
armorSlot.CreateAtCapacity(itemData.container.slots, item);
else
{
item.contents = Pool.Get<ItemContainer>();
item.contents.ServerInitialize(item, itemData.container.slots);
item.contents.GiveUID();
}
}
itemData.container.Load(item.contents);
}
// Process weapon attachments/capacity after child items have been added.BaseProjectile weapon = item.GetHeldEntity() as BaseProjectile;
if (weapon)
{
weapon.DelayedModsChanged();
if (!string.IsNullOrEmpty(itemData.ammotype))
weapon.primaryMagazine.ammoType = ItemManager.FindItemDefinition(itemData.ammotype);
weapon.primaryMagazine.contents = itemData.ammo;
}
return item;
}
private static T FindItemMod<T>(Item item) where T : ItemMod
{
foreach (ItemMod itemMod in item.info.itemMods)
{
if (itemMod is T mod)
return mod;
}
return null;
}
#endregion
}
}