// Advanced "InstantBarrelFB" Plugin for Rust // Copyright (c) 2024 Extended Studios // // This plugin is licensed under the MIT License. // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using Newtonsoft.Json; using Oxide.Core; using UnityEngine; namespace Oxide.Plugins { [Info("Instant Barrel FB", "Abbas", "6.7.5")] [Description("A high-performance plugin for instant barrel farming. Features: instant barrel destruction, automatic loot collection, configurable weapon farming, distance-based restrictions, one-shot kills, anti-cheat measures, and scrap multiplier support. Perfect for both PvE and PvP servers.")] public class InstantBarrelFB : RustPlugin { private const string OnPermission = "instantbarrelfb.use"; private const int CLEANUP_INTERVAL = 300; private const int CLEANUP_MINUTES = 5; private const int SCRAP_ITEM_ID = -932201673; private Configuration _config; private static readonly string[] LootBarrelNames = { "loot_barrel_1", "loot_barrel_2", "loot-barrel-1", "loot-barrel-2", "oil_barrel", "roadsign1", "roadsign2", "roadsign3", "roadsign4", "roadsign5", "roadsign6", "roadsign7", "roadsign8", "roadsign9" }; private readonly Dictionary _lastInteractionTimes = new Dictionary(50); private readonly Dictionary _scrapYieldMap = new Dictionary { { 1, 4 }, { 2, 6 }, { 3, 9 } }; #region Hooks private void OnServerInitialized() { LoadConfig(); permission.RegisterPermission(OnPermission, this); timer.Every(CLEANUP_INTERVAL, CleanupInteractionTimes); } private void Unload() { _lastInteractionTimes.Clear(); _config = null; } private void OnEntityTakeDamage(LootContainer lootContainer, HitInfo hitInfo) { if (lootContainer == null || hitInfo?.InitiatorPlayer == null || !_config.EnableOneShot) return; var player = hitInfo.InitiatorPlayer; if (!permission.UserHasPermission(player.UserIDString, OnPermission)) return; if (!IsValidHit(lootContainer, hitInfo, player)) return; ProcessLoot(player, lootContainer, hitInfo); hitInfo.damageTypes.ScaleAll(0); } #endregion #region Core Logic private bool IsValidHit(LootContainer lootContainer, HitInfo hitInfo, BasePlayer player) { if (player == null || lootContainer == null) return false; var damage = hitInfo.damageTypes.Total(); if (damage < _config.MinimumDamageThreshold) return false; if (!ValidateBarrelName(lootContainer.ShortPrefabName)) return false; if (!CanInteract(player)) return false; return !_config.EnableAntiCheat || (player.IsOnGround() || player.IsSwimming()); } private bool ValidateBarrelName(string name) { if (string.IsNullOrEmpty(name)) return false; for (int i = 0; i < LootBarrelNames.Length; i++) if (LootBarrelNames[i] == name) return true; return false; } private void ProcessLoot(BasePlayer player, LootContainer lootContainer, HitInfo hitInfo) { if (player == null || lootContainer == null) return; var playerPos = player.transform.position; var containerPos = lootContainer.transform.position; if (!IsWithinDistance(playerPos, containerPos)) return; if (!_config.EnableWeaponFarming && hitInfo.IsProjectile()) return; try { var inventory = lootContainer.inventory; if (inventory?.itemList == null || inventory.itemList.Count == 0) return; int scrapAmount = GetScrapAmount(player); TransferItemsToPlayer(player, inventory, scrapAmount); if (inventory.itemList.Count == 0) { NextTick(() => DestroyLootContainer(lootContainer, hitInfo)); } } catch (Exception ex) { PrintError($"Error in ProcessLoot: {ex.Message} - Player: {player.displayName}"); } } private void CleanupInteractionTimes() { if (_lastInteractionTimes.Count == 0) return; var now = DateTime.UtcNow; var keys = new List(_lastInteractionTimes.Count); foreach (var kvp in _lastInteractionTimes) { if ((now - kvp.Value).TotalMinutes > CLEANUP_MINUTES) keys.Add(kvp.Key); } if (keys.Count > 0) { for (int i = 0; i < keys.Count; i++) _lastInteractionTimes.Remove(keys[i]); } } private bool IsWithinDistance(Vector3 playerPos, Vector3 containerPos) => Vector3.Distance(playerPos, containerPos) <= _config.MaxDistance; private int GetScrapAmount(BasePlayer player) { if (player.modifiers == null) return 0; int yield = (int)player.modifiers.GetValue(Modifier.ModifierType.Scrap_Yield); return _scrapYieldMap.TryGetValue(yield, out int amount) ? amount : 0; } private void TransferItemsToPlayer(BasePlayer player, ItemContainer inventory, int scrapAmount) { var items = inventory.itemList.ToArray(); for (int i = 0; i < items.Length; i++) { var item = items[i]; if (item == null) continue; if (scrapAmount > 0 && item.info.itemid == SCRAP_ITEM_ID) item.amount = scrapAmount; player.GiveItem(item, BaseEntity.GiveItemReason.PickedUp); } } private void DestroyLootContainer(LootContainer lootContainer, HitInfo hitInfo) { Interface.CallHook("OnEntityDeath", lootContainer, hitInfo); lootContainer.Kill(_config.EnableGibs ? BaseNetworkable.DestroyMode.Gib : BaseNetworkable.DestroyMode.None); } private bool CanInteract(BasePlayer player) { if (player == null) return false; var now = DateTime.UtcNow; if (_lastInteractionTimes.TryGetValue(player.userID, out DateTime lastTime) && (now - lastTime).TotalMilliseconds < _config.CooldownMs) return false; _lastInteractionTimes[player.userID] = now; return true; } #endregion #region Config private class Configuration { [JsonProperty("Enable farming with weapons")] public bool EnableWeaponFarming { get; set; } = true; [JsonProperty("Max farming distance")] public float MaxDistance { get; set; } = 3f; [JsonProperty("Make barrels 1 hit to kill")] public bool EnableOneShot { get; set; } = true; [JsonProperty("Enable barrel gibs")] public bool EnableGibs { get; set; } = true; [JsonProperty("Minimum damage threshold")] public float MinimumDamageThreshold { get; set; } = 5f; [JsonProperty("Cooldown duration (ms)")] public int CooldownMs { get; set; } = 100; [JsonProperty("Enable anti-cheat checks")] public bool EnableAntiCheat { get; set; } = true; [JsonProperty("Debug mode")] public bool DebugMode { get; set; } = false; } protected override void LoadConfig() { base.LoadConfig(); try { _config = Config.ReadObject() ?? new Configuration(); SaveConfig(); } catch (Exception e) { Debug.LogError($"Error loading configuration: {e.Message}"); LoadDefaultConfig(); } } protected override void LoadDefaultConfig() => _config = new Configuration(); protected override void SaveConfig() => Config.WriteObject(_config); #endregion } }