C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Facepunch;
using Network;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Libraries;
using Rust;
using Rust.Workshop;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Oxide.Plugins {
[Info("Automated Stash Traps", "Dana", "1.5.4")]
[Description("Spawns fully automated stash traps across the map to catch ESP cheaters.")]
public class AutomatedStashTraps : RustPlugin {
#region Dependencies
[PluginReference]
private readonly Plugin Clans;
#endregion Dependencies
#region Fields
private static AutomatedStashTraps _instance;
private static Configuration _config;
private static Data _data;
private SpawnPointManager _spawnPointManager;
private DiscordWebhook _webhook;
private SkinManager _skinManager;
private Coroutine _spawnCoroutine;
private List<BasePlayer> _manualTrapDeployers = new List<BasePlayer>();
private HashSet<ulong> _revealedOwnedStashes = new HashSet<ulong>();
private Dictionary<BasePlayer, StorageContainer> _activeLootEditors = new Dictionary<BasePlayer, StorageContainer>();
private Timer _reportScheduler;
private Queue<DiscordWebhook.Message> _queuedDiscordReports = new Queue<DiscordWebhook.Message>();
private const string BLUEPRINT_TEMPLATE = "blueprintbase";
private const string STASH_PREFAB = "assets/prefabs/deployable/small stash/small_stash_deployed.prefab";
private const string STORAGE_PREFAB = "assets/prefabs/deployable/large wood storage/box.wooden.large.prefab";
private const string SLEEPING_BAG_PREFAB = "assets/prefabs/deployable/sleeping bag/sleepingbag_leather_deployed.prefab";
private Vector3 _lastRevealedStashPosition;
#endregion Fields
#region Configuration
private class Configuration {
[JsonProperty("Version")]
public string Version {
get;
set;
}
[JsonProperty("Spawn Point")]
public SpawnPointConfig SpawnPoint {
get;
set;
}
[JsonProperty("Automated Trap")]
public AutomatedTrapConfig AutomatedTrap {
get;
set;
}
[JsonProperty("Violation")]
public ViolationConfig Violation {
get;
set;
}
[JsonProperty("Moderation")]
public ModerationConfig Moderation {
get;
set;
}
[JsonProperty("Notification")]
public NotificationConfig Notification {
get;
set;
}
[JsonProperty("Discord")]
public DiscordConfig Discord {
get;
set;
}
[JsonProperty("Stash Loot")]
public StashLootConfig StashLoot {
get;
set;
}
}
private class SpawnPointConfig {
[JsonProperty("Maximum Attempts To Find Spawn Points")]
public int MaximumAttemptsToFindSpawnPoints {
get;
set;
}
[JsonProperty("Safe Area Radius")]
public float SafeAreaRadius {
get;
set;
}
[JsonProperty("Entity Detection Radius")]
public float EntityDetectionRadius {
get;
set;
}
[JsonProperty("Player Detection Radius")]
public float PlayerDetectionRadius {
get;
set;
}
}
private class AutomatedTrapConfig {
[JsonProperty("Maximum Traps To Spawn")]
public int MaximumTrapsToSpawn {
get;
set;
}
[JsonProperty("Destroy Revealed Trap After Minutes")]
public int DestroyRevealedTrapAfterMinutes {
get;
set;
}
[JsonProperty("Replace Revealed Trap")]
public bool ReplaceRevealedTrap {
get;
set;
}
[JsonProperty("Dummy Sleeping Bag")]
public DummySleepingBagConfig DummySleepingBag {
get;
set;
}
}
private class DummySleepingBagConfig {
[JsonProperty("Spawn Along")]
public bool SpawnAlong {
get;
set;
}
[JsonProperty("Spawn Proximity To Stash")]
public float SpawnProximityToStash {
get;
set;
}
[JsonProperty("Spawn Chance")]
public int SpawnChance {
get;
set;
}
[JsonProperty("Randomized Skin Chance")]
public int RandomizedSkinChance {
get;
set;
}
[JsonProperty("Randomized Nice Name Chance")]
public int RandomizedNiceNameChance {
get;
set;
}
}
private class ViolationConfig {
[JsonProperty("Reset On Wipe")]
public bool ResetOnWipe {
get;
set;
}
[JsonProperty("Can Teammate Ignore")]
public bool CanTeammateIgnore {
get;
set;
}
[JsonProperty("Can Clanmate Ignore")]
public bool CanClanmateIgnore {
get;
set;
}
}
private class ModerationConfig {
[JsonProperty("Automatic Ban")]
public bool AutomaticBan {
get;
set;
}
[JsonProperty("Violations Tolerance")]
public int ViolationsTolerance {
get;
set;
}
[JsonProperty("Ban Delay Seconds")]
public int BanDelaySeconds {
get;
set;
}
[JsonProperty("Ban Reason")]
public string BanReason {
get;
set;
}
}
private class NotificationConfig {
[JsonProperty("Prefix")]
public string Prefix {
get;
set;
}
[JsonProperty("Enable Console Report")]
public bool EnableConsoleReport {
get;
set;
}
[JsonProperty("Stash Report Filter")]
public int StashReportFilter {
get;
set;
}
}
private class DiscordConfig {
[JsonProperty("Post Into Discord")]
public bool PostIntoDiscord {
get;
set;
}
[JsonProperty("Webhook Url")]
public string WebhookUrl {
get;
set;
}
[JsonProperty("Report Interval")]
public float ReportInterval {
get;
set;
}
[JsonProperty("Message")]
public string Message {
get;
set;
}
[JsonProperty("Embed Color")]
public string EmbedColor {
get;
set;
}
[JsonProperty("Embed Title")]
public string EmbedTitle {
get;
set;
}
[JsonProperty("Embed Footer")]
public string EmbedFooter {
get;
set;
}
[JsonProperty("Embed Fields")]
public List<DiscordWebhook.EmbedField> EmbedFields {
get;
set;
}
[JsonIgnore]
private int color;
[JsonIgnore]
private bool colorIsValidated;
public int GetColor() {
if (!colorIsValidated) {
if (!int.TryParse(EmbedColor.TrimStart('#'), NumberStyles.HexNumber, null, out color)) color = 16777215;
colorIsValidated = true;
}
return color;
}
}
private class StashLootConfig {
[JsonProperty("Minimum Loot Spawn Slots")]
public int MinimumLootSpawnSlots {
get;
set;
}
[JsonProperty("Maximum Loot Spawn Slots")]
public int MaximumLootSpawnSlots {
get;
set;
}
[JsonProperty("Spawn Chance As Blueprint")]
public int SpawnChanceAsBlueprint {
get;
set;
}
[JsonProperty("Spawn Chance With Skin")]
public int SpawnChanceWithSkin {
get;
set;
}
[JsonProperty("Spawn Chance As Damaged")]
public int SpawnChanceAsDamaged {
get;
set;
}
[JsonProperty("Minimum Condition Loss")]
public float MinimumConditionLoss {
get;
set;
}
[JsonProperty("Maximum Condition Loss")]
public float MaximumConditionLoss {
get;
set;
}
[JsonProperty("Spawn Chance As Repaired")]
public int SpawnChanceAsRepaired {
get;
set;
}
[JsonProperty("Spawn Chance As Broken")]
public int SpawnChanceAsBroken {
get;
set;
}
[JsonProperty("Loot Table")]
public List<ItemInfo> LootTable {
get;
set;
}
}
private class ItemInfo {
[JsonProperty("Short Name")]
public string ShortName {
get;
set;
}
[JsonProperty("Minimum Spawn Amount")]
public int MinimumSpawnAmount {
get;
set;
}
[JsonProperty("Maximum Spawn Amount")]
public int MaximumSpawnAmount {
get;
set;
}
[JsonIgnore]
private ItemDefinition itemDefinition;
[JsonIgnore]
private bool itemIsValidated;
// Inspired by WhiteThunder./// <summary>
/// Returns the item definition associated with this item./// </summary>
/// <returns> The item definition, or null if the item is not valid.</returns>
public ItemDefinition GetItemDefinition() {
if (!itemIsValidated) {
ItemDefinition lookupResult = ItemManager.FindItemDefinition(ShortName);
if (lookupResult != null) itemDefinition = lookupResult;
else return null;
itemIsValidated = true;
}
return itemDefinition;
}
/// <summary>
/// Determines whether the item can be researched./// </summary>
/// <returns> True if the item can be researched, false otherwise.</returns>
public bool CanBeResearched() {
return itemDefinition.Blueprint == null || !itemDefinition.Blueprint.isResearchable ? false : true;
}
/// <summary>
/// Determines whether the item has skins./// </summary>
/// <returns> True if the item can be skinned, false otherwise.</returns>
public bool CanBeSkinned() {
return !itemDefinition.HasSkins ? false : true;
}
/// <summary>
/// Determines whether the item can be repaired./// </summary>
/// <returns> True if the item can be repaired, false otherwise.</returns>
public bool CanBeRepaired() {
return !itemDefinition.condition.repairable ? false : true;
// !item.hasCondition
}
}
private Configuration GetDefaultConfig() {
return new Configuration {
Version = Version.ToString(), SpawnPoint = new SpawnPointConfig {
MaximumAttemptsToFindSpawnPoints = 1500, SafeAreaRadius = 3f, EntityDetectionRadius = 25f, PlayerDetectionRadius = 25f
}
, AutomatedTrap = new AutomatedTrapConfig {
MaximumTrapsToSpawn = 50, DestroyRevealedTrapAfterMinutes = 2, ReplaceRevealedTrap = true, DummySleepingBag = new DummySleepingBagConfig {
SpawnAlong = false, SpawnProximityToStash = 0.90f, SpawnChance = 50, RandomizedSkinChance = 40, RandomizedNiceNameChance = 60
}
}
, Violation = new ViolationConfig {
ResetOnWipe = true, CanTeammateIgnore = false, CanClanmateIgnore = false
}
, Moderation = new ModerationConfig {
AutomaticBan = false, ViolationsTolerance = 3, BanDelaySeconds = 60, BanReason = "Cheat Detected!"
}
, Notification = new NotificationConfig {
Prefix = "<color=#F2C94C>Automated Stash Trap</color>:", EnableConsoleReport = true, StashReportFilter = 2
}
, Discord = new DiscordConfig {
PostIntoDiscord = false, WebhookUrl = string.Empty, ReportInterval = 60f, Message = "Cheater, cheater, pumpkin eater! Looks like someone's been caught breaking the rules!", EmbedTitle = "A cheater has been spotted", EmbedColor = "#FFFFFF", EmbedFooter = string.Empty, EmbedFields = new List<DiscordWebhook.EmbedField>() {
new DiscordWebhook.EmbedField {
Name = "Player Name", Value = "$Player.Name", Inline = true
}
, new DiscordWebhook.EmbedField {
Name = "Id", Value = "$Player.Id", Inline = true
}
, new DiscordWebhook.EmbedField {
Name = "Violations Count", Value = "$Player.Violations", Inline = true
}
, new DiscordWebhook.EmbedField {
Name = "Revealed Stash Type", Value = "$Stash.Type", Inline = true
}
, new DiscordWebhook.EmbedField {
Name = "Stash Id", Value = "$Stash.Id", Inline = true
}
, new DiscordWebhook.EmbedField {
Name = "Grid", Value = "$Stash.Position.Grid", Inline = true
}
, new DiscordWebhook.EmbedField {
Name = "Reveal Method", Value = "$Stash.Reveal.Method", Inline = false
}
, new DiscordWebhook.EmbedField {
Name = "Stash Owner Name", Value = "$Stash.Owner.Name", Inline = true
}
, new DiscordWebhook.EmbedField {
Name = "Stash Owner Id", Value = "$Stash.Owner.Id", Inline = true
}
, new DiscordWebhook.EmbedField {
Name = "Team Info", Value = "$Player.Team", Inline = false
}
, new DiscordWebhook.EmbedField {
Name = "Player Connection Time", Value = "$Player.Connection.Time", Inline = false
}
, new DiscordWebhook.EmbedField {
Name = "Server", Value = "$Server.Name $Server.Address", Inline = false
}
,
}
}
, StashLoot = new StashLootConfig {
MinimumLootSpawnSlots = 1, MaximumLootSpawnSlots = 6, SpawnChanceAsBlueprint = 10, SpawnChanceWithSkin = 50, SpawnChanceAsDamaged = 30, MinimumConditionLoss = 5f, MaximumConditionLoss = 95f, SpawnChanceAsRepaired = 15, SpawnChanceAsBroken = 5, LootTable = new List<ItemInfo>() {
new ItemInfo {
ShortName = "scrap", MinimumSpawnAmount = 25, MaximumSpawnAmount = 125,
}
, new ItemInfo {
ShortName = "metal.refined", MinimumSpawnAmount = 15, MaximumSpawnAmount = 40,
}
, new ItemInfo {
ShortName = "cloth", MinimumSpawnAmount = 60, MaximumSpawnAmount = 200,
}
, new ItemInfo {
ShortName = "cctv.camera", MinimumSpawnAmount = 1, MaximumSpawnAmount = 2,
}
, new ItemInfo {
ShortName = "riflebody", MinimumSpawnAmount = 1, MaximumSpawnAmount = 3,
}
, new ItemInfo {
ShortName = "techparts", MinimumSpawnAmount = 1, MaximumSpawnAmount = 6,
}
}
}
}
;
}
protected override void LoadConfig() {
base.LoadConfig();
_config = Config.ReadObject<Configuration>();
if (string.Compare(_config.Version, Version.ToString()) < 0) UpdateConfig();
ValidateConfigValues();
SaveConfig();
}
protected override void LoadDefaultConfig() {
_config = GetDefaultConfig();
}
protected override void SaveConfig() {
Config.WriteObject(_config, true);
}
private void UpdateConfig() {
PrintWarning("Detected changes in configuration! Updating...");
Configuration defaultConfig = GetDefaultConfig();
if (string.Compare(_config.Version, "1.0.0") < 0) _config = defaultConfig;
if (string.Compare(_config.Version, "1.1.0") < 0) {
_config.Violation = defaultConfig.Violation;
_config.Moderation = defaultConfig.Moderation;
}
if (string.Compare(_config.Version, "1.3.0") < 0) {
_config.Discord.ReportInterval = defaultConfig.Discord.ReportInterval;
}
if (string.Compare(_config.Version, "1.4.0") < 0) {
_config.Notification = defaultConfig.Notification;
_config.AutomatedTrap.DummySleepingBag.SpawnProximityToStash = defaultConfig.AutomatedTrap.DummySleepingBag.SpawnProximityToStash;
}
if (string.Compare(_config.Version, "1.5.0") < 0) {
_config.Notification.StashReportFilter = defaultConfig.Notification.StashReportFilter;
}
PrintWarning("Configuration update complete! Updated from version " + _config.Version + " to " + Version.ToString());
_config.Version = Version.ToString();
}
private void ValidateConfigValues() {
PrintWarning("Validating configuration values...");
if (_config.AutomatedTrap.DestroyRevealedTrapAfterMinutes <= 0) {
PrintError("Invalid trap removal time value.To avoid potential entity leaks, this value must be greater than 0.Default value of 5 will be applied.");
_config.AutomatedTrap.DestroyRevealedTrapAfterMinutes = 5;
}
if (_config.AutomatedTrap.DummySleepingBag.SpawnProximityToStash <= 0.0f || _config.AutomatedTrap.DummySleepingBag.SpawnProximityToStash > 1.0f) {
PrintError("Invalid sleeping bag spawn proximity.The value must be greater than 0.0 and less than or equal to 1.0.Default value of 0.9 will be applied.");
_config.AutomatedTrap.DummySleepingBag.SpawnProximityToStash = 0.90f;
}
if (_config.Notification.StashReportFilter < 0 || _config.Notification.StashReportFilter > 2) {
PrintError("Invalid stash report filter value.The value must be 0, 1, or 2.Default value of 2 will be applied.");
_config.Notification.StashReportFilter = 2;
}
if (_config.Discord.PostIntoDiscord) {
if (string.IsNullOrWhiteSpace(_config.Discord.WebhookUrl)) {
PrintError("Invalid webhook url provided.Please provide a valid webhook url to post into Discord.");
_config.Discord.PostIntoDiscord = false;
}
if (string.IsNullOrWhiteSpace(_config.Discord.EmbedColor) || !_config.Discord.EmbedColor.StartsWith("#")) {
PrintError("Invalid color provided.The color must be a valid hex color code.Default color of #FFFFFF will be applied.");
_config.Discord.EmbedColor = "#FFFFFF";
}
}
if (_config.StashLoot.MinimumLootSpawnSlots < 1) {
PrintError("Invalid minimum loot spawn slots value.Default value of 1 will be applied.");
_config.StashLoot.MinimumLootSpawnSlots = 1;
}
if (_config.StashLoot.MaximumLootSpawnSlots > 6) {
PrintError("Invalid maximum loot spawn slots value.Default value of 6 will be applied.");
_config.StashLoot.MaximumLootSpawnSlots = 6;
}
List<ItemInfo> invalidItems = _config.StashLoot.LootTable.Where(item => item.GetItemDefinition() == null).ToList();
foreach (ItemInfo invalidItem in invalidItems) {
_config.StashLoot.LootTable.Remove(invalidItem);
PrintError("Invalid item '" + invalidItem.ShortName + "' removed from the loot table.");
}
foreach (ItemInfo item in _config.StashLoot.LootTable) {
if (item.MinimumSpawnAmount <= 0) {
PrintError("Invalid minimum spawn amount for item '" + item.ShortName + "'.Default value of 1 will be applied.");
item.MinimumSpawnAmount = 1;
}
if (item.MaximumSpawnAmount < item.MinimumSpawnAmount) {
PrintError("Invalid maximum spawn amount for item '" + item.ShortName + "'.Default value of " + item.MinimumSpawnAmount + " will be applied.");
item.MaximumSpawnAmount = item.MinimumSpawnAmount;
}
}
PrintWarning("Configuration validation complete!");
}
#endregion
#region Data
private class Data {
[JsonProperty("Violations")]
public Dictionary<ulong, int> Violations {
get;
set;
}
= new Dictionary<ulong, int>();
[JsonProperty("Automated Traps")]
public Dictionary<ulong, AutomatedTrapData> AutomatedTraps {
get;
set;
}
= new Dictionary<ulong, AutomatedTrapData>();
public static Data Load() {
return Interface.Oxide.DataFileSystem.ReadObject<Data>(_instance.Name) ?? new Data();
}
public Data Save() {
Interface.Oxide.DataFileSystem.WriteObject(_instance.Name, this);
return this;
}
public static Data Clear() {
return new Data().Save();
}
public void RemovePlayerData(BasePlayer player) {
if (Violations.ContainsKey(player.userID)) Violations.Remove(player.userID);
}
public void CreateOrUpdatePlayerData(BasePlayer player) {
int revealedTraps;
Violations.TryGetValue(player.userID, out revealedTraps);
Violations[player.userID] = revealedTraps + 1;
}
public int GetPlayerRevealedTrapsCount(BasePlayer player) {
int revealedTraps;
return Violations.TryGetValue(player.userID, out revealedTraps) ? revealedTraps : 0;
}
public void CreateTrapData(StashContainer stash, SleepingBag sleepingBag = null) {
AutomatedTraps[stash.net.ID.Value] = new AutomatedTrapData {
DummyStash = new DummyStashData {
Hidden = true, Id = stash.net.ID.Value, Position = stash.ServerPosition
}
}
;
if (sleepingBag != null) AutomatedTraps[stash.net.ID.Value].DummySleepingBag = new DummySleepingBagData {
Id = sleepingBag.net.ID.Value, NiceName = sleepingBag.niceName, SkinId = sleepingBag.skinID, Position = sleepingBag.ServerPosition
}
;
}
public AutomatedTrapData GetTrapData(ulong trapId) {
AutomatedTrapData trapData;
return AutomatedTraps.TryGetValue(trapId, out trapData) ? trapData : null;
}
public void UpdateTrapData(AutomatedTrapData trap) {
trap.DummyStash.Hidden = false;
}
}
private class AutomatedTrapData {
[JsonProperty("Dummy Stash")]
public DummyStashData DummyStash {
get;
set;
}
[JsonProperty("Dummy Sleeping Bag")]
public DummySleepingBagData DummySleepingBag {
get;
set;
}
}
private class DummyStashData {
[JsonProperty("Hidden")]
public bool Hidden {
get;
set;
}
[JsonProperty("Id")]
public ulong Id {
get;
set;
}
[JsonProperty("Position")]
public Vector3 Position {
get;
set;
}
}
private class DummySleepingBagData {
[JsonProperty("Id")]
public ulong Id {
get;
set;
}
[JsonProperty("Nice Name")]
public string NiceName {
get;
set;
}
[JsonProperty("Skin Id")]
public ulong SkinId {
get;
set;
}
[JsonProperty("Position")]
public Vector3 Position {
get;
set;
}
}
#endregion
#region Oxide Hooks
/// <summary>
/// Hook: Called when a plugin is being initialized./// </summary>
private void Init() {
_instance = this;
_skinManager = new SkinManager();
_webhook = new DiscordWebhook();
_spawnPointManager = new SpawnPointManager();
_data = Data.Load();
PermissionUtils.Register();
}
/// <summary>
/// Hook: Called after server startup is complete and awaits connections or when a plugin is hotloaded while the server is running./// </summary>
private void OnServerInitialized() {
StartSpawnCoroutine();
}
/// <summary>
/// Hook: Called when a plugin is being unloaded./// </summary>
private void Unload() {
CleanupTraps();
StopSpawnCoroutine();
_spawnPointManager.ClearAvailableSpawnPoints();
_lastRevealedStashPosition = Vector3.zero;
_reportScheduler?.Destroy();
_instance = null;
_config = null;
_data = null;
}
/// <summary>
/// Hook: Called when a new save file is created./// </summary>
private void OnNewSave() {
if (_config.Violation.ResetOnWipe) Data.Clear();
}
/// <summary>
/// Hook: Called when any entity is built or deployed./// </summary>
/// <param name="planner"> The building planner held by the player.</param>
/// <param name="gameObject"> Contains information about the built entity.</param>
private void OnEntityBuilt(Planner planner, GameObject gameObject) {
StashContainer stash = gameObject?.ToBaseEntity() as StashContainer;
if (!stash) return;
BasePlayer deployingPlayer = planner?.GetOwnerPlayer();
if (!deployingPlayer) return;
if (!_manualTrapDeployers.Contains(deployingPlayer)) return;
PopulateLoot(stash);
stash.SetHidden(true);
_manualTrapDeployers.Remove(deployingPlayer);
ReplyToPlayer(deployingPlayer, GetLang(Lang.TRAP_SETUP, deployingPlayer.UserIDString));
Draw.Sphere(deployingPlayer, 60f, ParseColor("#BDBDBD", Color.white), stash.transform.position, 0.5f);
Draw.Text(deployingPlayer, 60f, ParseColor("#F2C94C", Color.white), stash.transform.position + new Vector3(0, 0.7f, 0), $"<size=30>{stash.net.ID}</size>");
}
/// <summary>
/// Hook: Called when the player stops looting./// </summary>
/// <param name="inventory"> The inventory that the player was looting.</param>
private void OnPlayerLootEnd(PlayerLoot inventory) {
CloseLootEditor(inventory);
}
/// <summary>
/// Hook: Called when an entity is destroyed./// </summary>
/// <param name="stash"> The stash container that has been destroyed.</param>
private void OnEntityKill(StashContainer stash) {
if (stash.IsValid()) HandleDestroyedStash(stash);
}
/// <summary>
/// Hook: Called when a player reveals a hidden stash./// </summary>
/// <param name="stash"> The stash that was revealed.</param>
/// <param name="player"> The player who revealed the stash.</param>
private void OnStashExposed(StashContainer stash, BasePlayer player) {
if (PermissionUtils.Verify(player, PermissionUtils.IGNORE)) return;
OnStashTriggered(stash, player, false);
}
/// <summary>
/// Hook: Called when a player hides a stash./// </summary>
/// <param name="stash"> The stash that was hidden.</param>
/// <param name="player"> The player who buried the stash.</param>
private void OnStashHidden(StashContainer stash, BasePlayer player) {
if (PlayerIsStashOwner(stash, player)) return;
else if (!PlayerExistsInOwnerTeam(stash.OwnerID, player)) _revealedOwnedStashes.Add(stash.net.ID.Value);
}
#endregion Oxide Hooks
#region Spawn Coroutine
/// <summary>
/// Starts a coroutine that gradually spawns automated traps over time./// </summary>
private void StartSpawnCoroutine() {
// Hold a reference to the coroutine that is currently running._spawnCoroutine = ServerMgr.Instance.StartCoroutine(SpawnTraps());
}
/// <summary>
/// Stops the periodic spawning of automated traps if it is currently running./// </summary>
private void StopSpawnCoroutine() {
// Proceed if the coroutine is currently running.if (!_spawnCoroutine.IsUnityNull())
{
// Stop the execution of the coroutine and release it to be garbage collected.ServerMgr.Instance.StopCoroutine(_spawnCoroutine);
_spawnCoroutine = null;
}
}
#endregion Spawn Coroutine
#region Trap Creation
/// <summary>
/// Spawns a specified number of automated traps, consisting of a stash and, optionally, a dummy sleeping bag./// </summary>
/// <returns> The number of traps that were spawned.</returns>
private IEnumerator SpawnTraps() {
// Keep track of the number of traps that have been spawned.int spawnedTraps = 0;
// Calculate the number of traps that need to be spawned.int trapsToSpawn = _config.AutomatedTrap.MaximumTrapsToSpawn - _data.AutomatedTraps.Where(trapData => trapData.Value.DummyStash.Hidden).Count();
// If there are no traps to spawn, exit early.if (trapsToSpawn <= 0)
yield break;
// Determine the wait duration for the coroutine based on the current frame rate limit.WaitForSeconds waitDuration = ConVar.FPS.limit > 80 ? CoroutineEx.waitForSeconds(0.01f) : null;
// If there are not enough available spawn points, generate more until there are enough.if (_spawnPointManager.AvailableSpawnPointsCount < trapsToSpawn)
{
int spawnPointsToGenerate = trapsToSpawn - _spawnPointManager.AvailableSpawnPointsCount;
yield return ServerMgr.Instance.StartCoroutine(_spawnPointManager.GenerateSpawnPoints(spawnPointsToGenerate));
}
// Begin spawning traps until the required number has been reached.for (int i = 0; i < trapsToSpawn; i++)
{
// Get a random spawn point.Tuple<Vector3, Quaternion> spawnPoint = _spawnPointManager.GetRandomSpawnPoint();
// Create a stash container entity at the spawn point and populate it with loot.StashContainer stash = CreateStashEntity(STASH_PREFAB, spawnPoint.Item1, spawnPoint.Item2);
PopulateLoot(stash);
// Initialize a sleeping bag entity, which may be spawned if the configuration allows it.SleepingBag sleepingBag = null;
if (_config.AutomatedTrap.DummySleepingBag.SpawnAlong && ChanceSucceeded(_config.AutomatedTrap.DummySleepingBag.SpawnChance)) {
// Find a nearby spawn point and create a sleeping bag at it.Tuple<Vector3, Quaternion> nearbySpawnPoint = _spawnPointManager.FindChildSpawnPoint(spawnPoint.Item1);
sleepingBag = CreateSleepingBagEntity(SLEEPING_BAG_PREFAB, nearbySpawnPoint.Item1, nearbySpawnPoint.Item2);
}
_data.CreateTrapData(stash, sleepingBag);
spawnedTraps++;
// Wait for a set duration to prevent overloading the server with spawning actions.yield return waitDuration;
}
// Output the total number of spawned traps to the console.Puts("Spawned " + spawnedTraps + " traps.");
// Save the trap _data and set the coroutine to null to be garbage collected._data.Save();
}
/// <summary>
/// Creates a stash entity from the specified prefab at the given position and rotation./// </summary>
/// <param name="prefabPath"> The path to the prefab to use for the stash entity.</param>
/// <param name="position"> The position to spawn the stash entity at.</param>
/// <param name="rotation"> The rotation to spawn the stash entity with.</param>
/// <returns> The created stash entity, or null if the entity could not be created.</returns>
private StashContainer CreateStashEntity(string prefabPath, Vector3 position, Quaternion rotation) {
// Create the entity from the specified prefab.BaseEntity entity = GameManager.server.CreateEntity(prefabPath, position, rotation);
// Don't proceed if the entity could not be created.if (entity == null)
return null;
// Convert the entity to a StashContainer.StashContainer stash = entity as StashContainer;
if (stash == null) {
// Destroy the entity if it could not be converted.UnityEngine.Object.Destroy(entity);
return null;
}
// Initialize the stash by spawning and hiding it underground.stash.Spawn();
stash.SetHidden(true);
// Cancel the decay invoke, so the stash does not decay over time.stash.CancelInvoke(stash.Decay);
return stash;
}
/// <summary>
/// Creates a sleeping bag entity from the specified prefab at the given position and rotation./// </summary>
/// <param name="prefabPath"> The path to the prefab to use for the sleeping bag entity.</param>
/// <param name="position"> The position to spawn the sleeping bag entity at.</param>
/// <param name="rotation"> The rotation to spawn the sleeping bag entity with.</param>
/// <returns> The created sleeping bag entity, or null if the entity could not be created.</returns>
private SleepingBag CreateSleepingBagEntity(string prefabPath, Vector3 position, Quaternion rotation) {
// Create the entity from the specified prefab.BaseEntity entity = GameManager.server.CreateEntity(prefabPath, position, rotation);
// Don't proceed if the entity could not be created.if (entity == null)
return null;
// Convert the entity to a SleepingBag.SleepingBag sleepingBag = entity as SleepingBag;
if (sleepingBag == null) {
// Destroy the entity if it could not be converted.UnityEngine.Object.Destroy(entity);
return null;
}
UnityEngine.Object.DestroyImmediate(sleepingBag.GetComponent<DestroyOnGroundMissing>());
UnityEngine.Object.DestroyImmediate(sleepingBag.GetComponent<GroundWatch>());
// Set a random skin for the sleeping bag.if (_config.AutomatedTrap.DummySleepingBag.RandomizedSkinChance > 0 && ChanceSucceeded(_config.AutomatedTrap.DummySleepingBag.RandomizedSkinChance))
sleepingBag.skinID = _skinManager.GetSkinsForItem(ItemManager.FindItemDefinition("sleepingbag")).GetRandom();
// Set a random nice name for the sleeping bag.if (_config.AutomatedTrap.DummySleepingBag.RandomizedNiceNameChance > 0 && ChanceSucceeded(_config.AutomatedTrap.DummySleepingBag.RandomizedNiceNameChance))
sleepingBag.niceName = RandomUsernames.Get(Random.Range(0, 5000));
// Spawn the sleeping bag.sleepingBag.Spawn();
return sleepingBag;
}
private void PopulateLoot(StashContainer stash) {
List<ItemInfo> itemsToSpawn = new List<ItemInfo>(_config.StashLoot.LootTable);
int lootSpawnSlots = Random.Range(_config.StashLoot.MinimumLootSpawnSlots, _config.StashLoot.MaximumLootSpawnSlots);
if (lootSpawnSlots > itemsToSpawn.Count) lootSpawnSlots = itemsToSpawn.Count;
stash.inventory.Clear();
for (int i = 0; i < lootSpawnSlots; i++) {
Item item;
ItemInfo randomItem = itemsToSpawn.GetRandom();
ItemDefinition itemDefinition = randomItem.GetItemDefinition();
if (itemDefinition == null) continue;
if (_config.StashLoot.SpawnChanceAsBlueprint > 0 && randomItem.CanBeResearched() && ChanceSucceeded(_config.StashLoot.SpawnChanceAsBlueprint)) {
item = ItemManager.CreateByName(BLUEPRINT_TEMPLATE);
item.blueprintTarget = itemDefinition.itemid;
}
else {
int spawnAmount = Random.Range(randomItem.MinimumSpawnAmount, randomItem.MaximumSpawnAmount + 1);
ulong skin = 0;
if (_config.StashLoot.SpawnChanceWithSkin > 0 && randomItem.CanBeSkinned() && ChanceSucceeded(_config.StashLoot.SpawnChanceWithSkin)) skin = _instance._skinManager.GetSkinsForItem(itemDefinition).GetRandom();
item = ItemManager.CreateByName(randomItem.ShortName, spawnAmount, skin);
if (_config.StashLoot.SpawnChanceAsDamaged > 0 && randomItem.CanBeRepaired()) RandomizeItemCondition(item);
}
// Remove the item if it wasn't added successfully to avoid any potential entity leak.if (!item.MoveToContainer(stash.inventory))
item.Remove();
item.MarkDirty();
itemsToSpawn.Remove(randomItem);
}
Pool.FreeList(ref itemsToSpawn);
}
private void RandomizeItemCondition(Item item) {
if (ChanceSucceeded(_config.StashLoot.SpawnChanceAsDamaged)) {
float conditionLoss = Random.Range(_config.StashLoot.MinimumConditionLoss, _config.StashLoot.MaximumConditionLoss);
item.conditionNormalized = conditionLoss / 100;
}
if (ChanceSucceeded(_config.StashLoot.SpawnChanceAsRepaired)) {
float repairAmount = Random.Range(1f, 0.8f);
item.DoRepair(repairAmount);
}
else if (ChanceSucceeded(_config.StashLoot.SpawnChanceAsBroken)) {
item.condition = 0f;
}
}
#endregion Trap Creation
#region Trap Removal
/// <summary>
/// Removes all automated traps from the world and their associated entities./// </summary>
/// <returns> The number of removed traps.</returns>
private void CleanupTraps() {
// Keep track of the number of removed traps.int removedTraps = 0;
// Process all traps one by one.foreach (ulong trapId in _data.AutomatedTraps.Keys)
{
// Retrieve the _data for the current trap.AutomatedTrapData trap = _data.GetTrapData(trapId);
// Skip the trap if its _data cannot be found and move on to the next one.if (trap == null)
continue;
// Find the stash for the current trap and kill it if found.StashContainer stash = FindEntityById(trap.DummyStash.Id) as StashContainer;
stash?.Kill();
// Find the dummy sleeping bag associated with the trap and kill it if found.if (trap.DummySleepingBag != null)
{
SleepingBag sleepingBag = FindEntityById(trap.DummySleepingBag.Id) as SleepingBag;
sleepingBag?.Kill();
}
// Increment the number of successfully removed traps.removedTraps++;
}
Puts("Cleaned up " + removedTraps + " traps.");
_data.AutomatedTraps.Clear();
_data.Save();
}
/// <summary>
/// Schedules the destruction of an automated trap and, optionally, replaces it with a new one./// </summary>
/// <param name="trap"> The AutomatedTrapData object containing information about the trap to be destroyed and replaced.</param>
private void TryDestroyAndReplaceTrap(AutomatedTrapData trap) {
// Schedule the trap for destruction after the specified time interval.timer.Once(_config.AutomatedTrap.DestroyRevealedTrapAfterMinutes * 60, () =>
{
// Find the dummy stash associated with the trap and destroy it if found.StashContainer stash = FindEntityById(trap.DummyStash.Id) as StashContainer;
stash?.Kill();
// Find the dummy sleeping bag associated with the trap and destroy it if found.if (trap.DummySleepingBag != null)
{
SleepingBag sleepingBag = FindEntityById(trap.DummySleepingBag.Id) as SleepingBag;
sleepingBag?.Kill();
}
// Remove the trap from the AutomatedTraps list._data.AutomatedTraps.Remove(trap.DummyStash.Id);
// If specified in the _config, spawn a new automated trap after the old one has been destroyed.if (_config.AutomatedTrap.ReplaceRevealedTrap)
StartSpawnCoroutine();
}
);
}
#endregion Trap Removal
#region Trap Trigger
private void OnStashTriggered(StashContainer stash, BasePlayer player, bool stashWasDestroyed) {
AutomatedTrapData trap = _data.GetTrapData(stash.net.ID.Value);
if (trap != null) {
if (_config.Notification.StashReportFilter != 0 && _config.Notification.StashReportFilter != 2) return;
if (!trap.DummyStash.Hidden) return;
_data.UpdateTrapData(trap);
TryDestroyAndReplaceTrap(trap);
}
else if (StashIsOwned(stash)) {
if (_config.Notification.StashReportFilter != 1 && _config.Notification.StashReportFilter != 2) return;
if (_revealedOwnedStashes.Contains(stash.net.ID.Value)) return;
if (PlayerIsStashOwner(stash, player)) return;
if (_config.Violation.CanTeammateIgnore && PlayerExistsInOwnerTeam(stash.OwnerID, player)) return;
if (_config.Violation.CanClanmateIgnore && PlayerExistsInOwnerClan(stash.OwnerID, player)) return;
if (!stashWasDestroyed) _revealedOwnedStashes.Add(stash.net.ID.Value);
}
_lastRevealedStashPosition = stash.ServerPosition;
_data.CreateOrUpdatePlayerData(player);
_data.Save();
int violations = _data.GetPlayerRevealedTrapsCount(player);
if (_config.Moderation.AutomaticBan && violations >= _config.Moderation.ViolationsTolerance) IssueBan(player);
if (_config.Notification.EnableConsoleReport) {
string report = ConstructConsoleReport(stash, player);
Puts(report);
}
if (_config.Discord.PostIntoDiscord) {
DiscordWebhook.Message message = ConstructDiscordReport(stash, player, stashWasDestroyed);
_queuedDiscordReports.Enqueue(message);
if (_reportScheduler == null) {
_reportScheduler = timer.Once(_config.Discord.ReportInterval, () => {
PushQueuedDiscordReports();
_reportScheduler = null;
}
);
}
}
foreach (BasePlayer admin in BasePlayer.activePlayerList.Where(p => PermissionUtils.Verify(p))) {
ReplyToPlayer(admin, GetLang(Lang.TRAP_REVEAL, admin.UserIDString), player.displayName, GetGrid(stash.ServerPosition));
Draw.Sphere(admin, 60f, Color.black, _lastRevealedStashPosition, 0.5f);
Draw.Arrow(admin, 60f, Color.black, _lastRevealedStashPosition + new Vector3(0, 390f, 0), _lastRevealedStashPosition, 0.50f);
Draw.Text(admin, 60f, ParseColor("#F2C94C", Color.white), _lastRevealedStashPosition + new Vector3(0, 390.1f, 0), $"<size=25>{player.displayName}</size>");
}
}
private string ConstructConsoleReport(StashContainer stash, BasePlayer player) {
StringBuilder reportBuilder = new StringBuilder();
// ... [Code preview truncated due to length. Full source in attached file] ...