Иконка ресурса

FREE VanillaLootBoost : 1.1.0 1.1.0

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using UnityEngine;
namespace Oxide.Plugins {
    [Info("Vanilla Loot Boost", "Kirar / Codex", "1.1.0")]
    [Description("A small loot boost using each container's own vanilla loot definitions.")]
    public class VanillaLootBoost : RustPlugin {
        private Configuration config;
        private const int BonusSlotLimit = 36;
        private readonly Dictionary<LootContainer, ItemContainer> pending = new Dictionary<LootContainer, ItemContainer>();
        private readonly Dictionary<string, LootContainer> vanillaSources = new Dictionary<string, LootContainer>();
        private readonly HashSet<string> warnedPrefabs = new HashSet<string>();
        private int boostedContainers;
        private int addedStacks;
        private int componentBonuses;
        private int otherBonuses;
        private class Configuration {
            [JsonProperty("Enabled")]
            public bool Enabled = true;
            [JsonProperty("Minimum extra item rolls per container")]
            public int MinimumExtras = 1;
            [JsonProperty("Maximum extra item rolls per container")]
            public int MaximumExtras = 2;
            [JsonProperty("Bonus component category chance (percent)")]
            public double ComponentChance = 95.0;
            [JsonProperty("Existing scrap amount multiplier")]
            public double ScrapMultiplier = 2.0;
            [JsonProperty("Other existing stackable item amount multiplier")]
            public double StackMultiplier = 1.5;
            [JsonProperty("Excluded container short prefab names")]
            public List<string> Excluded = new List<string>();
        }
        protected override void LoadDefaultConfig() => config = new Configuration();
        protected override void LoadConfig() {
            base.LoadConfig();
            try {
                config = Config.ReadObject<Configuration>() ?? new Configuration();
            }
            catch (Exception ex) {
                throw new Exception("VanillaLootBoost config is invalid. Repair its JSON; the file has not been overwritten.", ex);
            }
            config.MinimumExtras = Math.Max(0, Math.Min(5, config.MinimumExtras));
            config.MaximumExtras = Math.Max(config.MinimumExtras, Math.Min(5, config.MaximumExtras));
            config.ComponentChance = double.IsNaN(config.ComponentChance) || double.IsInfinity(config.ComponentChance) ? 95 : Math.Max(0, Math.Min(100, config.ComponentChance));
            config.ScrapMultiplier = ValidateMultiplier(config.ScrapMultiplier, 2);
            config.StackMultiplier = ValidateMultiplier(config.StackMultiplier, 1.5);
            config.Excluded = (config.Excluded ?? new List<string>()).Where(s => !string.IsNullOrWhiteSpace(s)) .Select(s => s.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
            SaveConfig();
        }
        protected override void SaveConfig() => Config.WriteObject(config, true);
        private static double ValidateMultiplier(double value, double fallback) {
            return double.IsNaN(value) || double.IsInfinity(value) ? fallback : Math.Max(1, Math.Min(5, value));
        }
        private void OnServerInitialized() {
            Puts(config.Enabled ? $"Enabled: {config.MinimumExtras}-{config.MaximumExtras} bonus rolls ({config.ComponentChance}% components), scrap x{config.ScrapMultiplier}, other stacks x{config.StackMultiplier}. Applies to newly generated loot; existing loot waits for its normal refresh." : "Disabled in configuration.");
        }
        // This hook runs before PopulateLoot. Do not return a value or replace the vanilla population.
        private void OnLootSpawn(LootContainer container) {
            if (!Eligible(container) || pending.ContainsKey(container)) return;
            pending[container] = container.inventory;
            NextTick(() => FlushPending(container));
        }
        // Flush queued generation before a player can remove an item or break a freshly spawned barrel.
        private void OnLootEntity(BasePlayer player, BaseEntity entity) => FlushPending(entity as LootContainer);
        private void OnEntityTakeDamage(LootContainer container, HitInfo info) => FlushPending(container);
        private bool Eligible(LootContainer container) {
            return config.Enabled && container != null && !container.IsDestroyed && container.OwnerID == 0 && container.inventory != null && !config.Excluded.Contains(container.ShortPrefabName, StringComparer.OrdinalIgnoreCase);
        }
        private void FlushPending(LootContainer container) {
            if (ReferenceEquals(container, null)) return;
            ItemContainer inventory;
            if (!pending.TryGetValue(container, out inventory)) return;
            // Claim before generation/moves: callbacks cannot boost the same inventory twice.
            pending.Remove(container);
            if (!Eligible(container) || container.inventory != inventory || inventory.itemList.Count == 0) return;
            try {
                Boost(container, inventory);
            }
            catch (Exception ex) {
                PrintError($"Could not finish boosting {container.ShortPrefabName}: {ex.Message}. Original loot was not cleared; no retry for this generation.");
            }
        }
        private LootContainer VanillaSource(LootContainer container) {
            LootContainer source;
            if (vanillaSources.TryGetValue(container.PrefabName, out source)) return source;
            var prefab = GameManager.server.FindPrefab(container.PrefabName);
            source = prefab == null ? null : prefab.GetComponent<LootContainer>();
            vanillaSources[container.PrefabName] = source;
            return source;
        }
        private static bool HasLootDefinition(LootContainer source) {
            return source != null && (source.lootDefinition != null || (source.LootSpawnSlots != null && source.LootSpawnSlots.Any(slot => slot.definition != null)));
        }
        private void Boost(LootContainer container, ItemContainer inventory) {
            var source = VanillaSource(container);
            if (!HasLootDefinition(source)) {
                if (warnedPrefabs.Add(container.PrefabName)) PrintWarning($"Skipping {container.ShortPrefabName}: no vanilla prefab loot definition. No generic replacement table will be used.");
                return;
            }
            // Only the original stacks receive a quantity boost. Bonus items retain their rolled amounts.
            foreach (var item in inventory.itemList.ToArray()) {
                if (item == null || item.info == null || item.IsBlueprint() || item.info.stackable <= 1) continue;
                double multiplier = item.info.shortname == "scrap" ? config.ScrapMultiplier : config.StackMultiplier;
                int amount = BoostedAmount(item.amount, multiplier, item.MaxStackable());
                if (amount == item.amount) continue;
                item.amount = amount;
                item.MarkDirty();
            }
            int requested = UnityEngine.Random.Range(config.MinimumExtras, config.MaximumExtras + 1);
            if (requested > 0) AddVanillaExtras(source, inventory, requested);
            inventory.MarkDirty();
            container.SendNetworkUpdate();
            boostedContainers++;
        }
        private static int BoostedAmount(int original, double multiplier, int stackLimit) {
            if (original <= 0 || stackLimit <= original) return original;
            double target = Math.Ceiling(original * multiplier);
            return (int)Math.Max(original, Math.Min(stackLimit, target));
        }
        private void AddVanillaExtras(LootContainer source, ItemContainer destination, int requested) {
            // Generate a scratch roll using Rust's own FillLoot (including slot probabilities,
            // nested weights, blueprint handling and current era restrictions). Never mutate the prefab.
            var scratch = new ItemContainer();
            scratch.ServerInitialize(null, BonusSlotLimit);
            scratch.GiveUID();
            try {
                for (int bonus = 0; bonus < requested; bonus++) {
                    // Pick the category ONCE per requested bonus. Rerolling this choice after a
                    // miss would make rare weapon draws much more common than the configured 5%.
                    bool wantComponent = IsComponentRoll(UnityEngine.Random.Range(0, 10000), config.ComponentChance);
                    bool inserted = false;
                    // Bound the work, including on food/medical-only tables. A missed component
                    // roll is skipped; it never falls back to a weapon or a foreign component.
                    for (int attempt = 0; attempt < 8 && !inserted; attempt++) {
                        scratch.Clear();
                        LootContainer.FillLoot(scratch, source.lootDefinition, source.maxDefinitionsToSpawn, source.LootSpawnSlots ?? new LootContainer.LootSpawnSlot[0]);
                        var candidates = scratch.itemList.Where(item => item != null && item.info != null && item.amount > 0 && IsComponent(item) == wantComponent).ToList();
                        while (candidates.Count > 0 && !inserted) {
                            int index = UnityEngine.Random.Range(0, candidates.Count);
                            Item candidate = candidates[index];
                            candidates.RemoveAt(index);
                            bool fitsExistingStack = destination.itemList.Any(item => item != null && item.CanStack(candidate) && candidate.amount <= item.MaxStackable() - item.amount);
                            if (!fitsExistingStack && destination.itemList.Count >= destination.capacity && destination.capacity < BonusSlotLimit) destination.capacity++;
                            if (!candidate.MoveToContainer(destination)) continue;
                            inserted = true;
                            addedStacks++;
                            if (wantComponent) componentBonuses++;
                            else otherBonuses++;
                        }
                    }
                }
            }
            finally {
                // Moved items belong to the destination. Destroy only unused scratch loot.
                scratch.Clear();
                scratch.Kill();
            }
        }
        private static bool IsComponentRoll(int roll, double chance) => roll < chance * 100;
        private static bool IsComponent(Item item) => !item.IsBlueprint() && item.info.category == ItemCategory.Component;
        private void OnEntityKill(LootContainer container) => pending.Remove(container);
        private void Unload() {
            pending.Clear();
            vanillaSources.Clear();
            warnedPrefabs.Clear();
        }
        [ConsoleCommand("vanillalootboost.status")]
        private void StatusCommand(ConsoleSystem.Arg arg) {
            if (arg.Connection != null && (arg.Player() == null || !arg.Player().IsAdmin)) return;
            arg.ReplyWith($"VanillaLootBoost: enabled={config.Enabled}, component chance={config.ComponentChance}%, containers boosted={boostedContainers}, bonus rolls inserted={addedStacks} (components={componentBonuses}, other={otherBonuses}), pending={pending.Count}. Counts reset on reload; boosts apply on loot generation, not opening.");
        }
    }
}

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху