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

FREE CelestialBarrage : 1.0.25 1.0.25

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using Oxide.Core.Plugins;
using Oxide.Core;
using UnityEngine;
using Rust;
using System.Collections;
using Oxide.Core.Libraries.Covalence;
using Newtonsoft.Json;
using System.Text;
using System.Linq;
namespace Oxide.Plugins {
    [Info("Celestial Barrage", "Ftuoil Xelrash", "1.0.25")]
    [Description("Create a Celestial Barrage falling from the sky")]
    class CelestialBarrage : RustPlugin {
#region Fields
        private Timer EventTimer = null;
        private List<Timer> RocketTimers = new List<Timer>();
        private HashSet<BaseEntity> meteorRockets = new HashSet<BaseEntity>();
        // Track our meteor rockets
        private List<(Vector3 position, float time)> recentExplosions = new List<(Vector3, float)>();
        // Track recent explosion positions for impact detection
        private List<MapMarkerGenericRadius> activeMarkers = new List<MapMarkerGenericRadius>();
        // Track map markers
        private List<VendingMachineMapMarker> activeVendingMarkers = new List<VendingMachineMapMarker>();
        // Track vending markers for hover text
        private Dictionary<string, float> discordRateLimiter = new Dictionary<string, float>();
        // Rate limit Discord messages
        private Dictionary<string, int> discordMessageCount = new Dictionary<string, int>();
        // Track message count per minute
        private Queue<QueuedDiscordMessage> discordMessageQueue = new Queue<QueuedDiscordMessage>();
        // Queue for rate limited messages
        private Timer discordQueueTimer = null;
        private bool wasGrenadeLauncherOverride = false;
        // Track if grenade launcher override occurred
#endregion
#region Oxide Hooks
        private void OnServerInitialized() {
            try {
                lang.RegisterMessages(Messages, this);
                LoadVariables();
                StartEventTimer();
                StartDiscordQueueProcessor();
            }
            catch (System.Exception ex) {
                PrintError($"Error during plugin initialization: {ex.Message}");
            }
        }
        private void Unload() {
            StopTimer();
            discordQueueTimer?.Destroy();
            foreach (var t in RocketTimers) t.Destroy();
            var objects = UnityEngine.Object.FindObjectsOfType<ItemCarrier>();
            if (objects != null) foreach (var gameObj in objects) UnityEngine.Object.Destroy(gameObj);
            // Clean up any remaining map markers
            CleanupAllMapMarkers();
        }
        private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
            float now = Time.realtimeSinceStartup;
            // Purge explosion records older than 0.5 seconds
            recentExplosions.RemoveAll(e => now - e.time > 0.5f);
            // Check live rockets and recent explosion positions
            bool nearMeteor = false;
            float closestDistance = float.MaxValue;
            foreach (var rocket in meteorRockets) {
                if (rocket != null && !rocket.IsDestroyed) {
                    float d = Vector3.Distance(rocket.transform.position, entity.transform.position);
                    if (d < 50f) {
                        nearMeteor = true;
                        if (d < closestDistance) closestDistance = d;
                    }
                }
            }
            if (!nearMeteor) {
                foreach (var explosion in recentExplosions) {
                    float d = Vector3.Distance(explosion.position, entity.transform.position);
                    if (d < 50f) {
                        nearMeteor = true;
                        if (d < closestDistance) closestDistance = d;
                        break;
                    }
                }
            }
            if (configData?.Logging?.LogDebugToConsole == true && (meteorRockets.Count > 0 || recentExplosions.Count > 0)) {
                string weaponInfo = info?.WeaponPrefab?.ShortPrefabName ?? "Unknown";
                string attackerInfo = info?.Initiator?.ShortPrefabName ?? "Unknown";
                Puts($"[DEBUG] Damage event - Weapon: {weaponInfo}, Attacker: {attackerInfo}, Damage: {info?.damageTypes?.Total():F1}, Entity: {entity?.ShortPrefabName}, NearMeteor: {nearMeteor}");
            }
            if (nearMeteor) {
                if (configData?.Logging?.LogDebugToConsole == true) Puts($"[DEBUG] Meteor impact detected - Distance: {closestDistance:F1}m");
                LogMeteorImpact(entity, info);
            }
        }
        private void OnEntityDeath(BaseCombatEntity entity, HitInfo info) {
            // Clean up tracking when our rockets explode/die
            if (entity != null && meteorRockets.Contains(entity)) {
                if (configData?.Logging?.LogDebugToConsole == true) {
                    Puts($"[DEBUG] Removing tracked meteor projectile: {entity.ShortPrefabName} (Remaining: {meteorRockets.Count - 1})");
                }
                // Record explosion position so OnEntityTakeDamage can still match impacts after the rocket is gone
                recentExplosions.Add((entity.transform.position, Time.realtimeSinceStartup));
                meteorRockets.Remove(entity);
            }
        }
#endregion
#region Functions
        private void StartDiscordQueueProcessor() {
            // Process Discord queue every 5 seconds
            discordQueueTimer = timer.Repeat(5f, 0, ProcessDiscordQueue);
        }
        private void ProcessDiscordQueue() {
            if (!configData.Logging.DiscordRateLimit.EnableRateLimit) {
                // If rate limiting is disabled, send all queued messages immediately
                while (discordMessageQueue.Count > 0) {
                    var queuedMessage = discordMessageQueue.Dequeue();
                    SendDiscordMessageImmediately(queuedMessage);
                }
                return;
            }
            // Reset message counts every minute
            float currentTime = UnityEngine.Time.realtimeSinceStartup;
            var keysToRemove = new List<string>();
            foreach (var kvp in discordRateLimiter) {
                if (currentTime - kvp.Value > 60f)  // Reset after 1 minute
                {
                    keysToRemove.Add(kvp.Key);
                }
            }
            foreach (var key in keysToRemove) {
                discordRateLimiter.Remove(key);
                discordMessageCount.Remove(key);
            }
            // Process queued messages within rate limits
            var messagesToProcess = new List<QueuedDiscordMessage>();
            while (discordMessageQueue.Count > 0 && messagesToProcess.Count < 3)  // Process max 3 at a time
            {
                messagesToProcess.Add(discordMessageQueue.Dequeue());
            }
            foreach (var queuedMessage in messagesToProcess) {
                if (CanSendDiscordMessage(queuedMessage.MessageType)) {
                    SendDiscordMessageImmediately(queuedMessage);
                }
                else {
                    // Put it back in queue if still rate limited
                    discordMessageQueue.Enqueue(queuedMessage);
                    break;
                    // Stop processing to avoid infinite loop
                }
            }
        }
        private bool CanSendDiscordMessage(string messageType) {
            if (!configData.Logging.DiscordRateLimit.EnableRateLimit) return true;
            float currentTime = UnityEngine.Time.realtimeSinceStartup;
            string rateLimitKey = messageType;
            // Check if we've hit the per-minute limit
            if (discordMessageCount.ContainsKey(rateLimitKey)) {
                if (discordMessageCount[rateLimitKey] >= configData.Logging.DiscordRateLimit.MaxImpactsPerMinute) {
                    return false;
                    // Hit per-minute limit
                }
            }
            // Check cooldown between messages
            if (discordRateLimiter.ContainsKey(rateLimitKey)) {
                float timeSinceLastMessage = currentTime - discordRateLimiter[rateLimitKey];
                if (timeSinceLastMessage < configData.Logging.DiscordRateLimit.ImpactMessageCooldown) {
                    return false;
                    // Still in cooldown
                }
            }
            return true;
        }
        private void QueueDiscordMessage(string webhookUrl, string message, bool isAdmin, string messageType) {
            try {
                var queuedMessage = new QueuedDiscordMessage {
                    WebhookUrl = webhookUrl, Message = message, IsAdmin = isAdmin, MessageType = messageType, QueuedTime = UnityEngine.Time.realtimeSinceStartup
                }
                ;
                if (CanSendDiscordMessage(messageType)) {
                    // Send immediately if not rate limited
                    SendDiscordMessageImmediately(queuedMessage);
                }
                else {
                    // Queue the message
                    discordMessageQueue.Enqueue(queuedMessage);
                    // Enhanced logging for queue status
                    if (configData.Logging.LogDebugToConsole) {
                        Puts($"[DISCORD QUEUE] Message queued due to rate limiting. Queue size: {discordMessageQueue.Count}");
                    }
                }
            }
            catch (System.Exception ex) {
                PrintError($"Error queuing Discord message: {ex.Message}");
            }
        }
        private void SendDiscordMessageImmediately(QueuedDiscordMessage queuedMessage) {
            // Update rate limiting tracking
            if (configData.Logging.DiscordRateLimit.EnableRateLimit) {
                float currentTime = UnityEngine.Time.realtimeSinceStartup;
                string rateLimitKey = queuedMessage.MessageType;
                discordRateLimiter[rateLimitKey] = currentTime;
                if (!discordMessageCount.ContainsKey(rateLimitKey)) discordMessageCount[rateLimitKey] = 0;
                discordMessageCount[rateLimitKey]++;
            }
            // Calculate queue delay for display
            float queueDelay = UnityEngine.Time.realtimeSinceStartup - queuedMessage.QueuedTime;
            // Send the message with queue info passed separately for proper embed formatting
            if (queueDelay > 5f) {
                SendDiscordEmbedWithQueueInfo(queuedMessage.WebhookUrl, queuedMessage.Message, queuedMessage.IsAdmin, queueDelay, discordMessageQueue.Count);
            }
            else {
                SendDiscordEmbedDirect(queuedMessage.WebhookUrl, queuedMessage.Message, queuedMessage.IsAdmin);
            }
            // Log when processing queued messages
            if (queueDelay > 5f && configData.Logging.LogDebugToConsole) {
                Puts($"[DISCORD QUEUE] Sent queued message (delayed {queueDelay:F0}s). {discordMessageQueue.Count} messages remaining in queue.");
            }
        }
        private void StartEventTimer() {
            try {
                if (configData.Options.EnableAutomaticEvents) {
                    EventTimer = timer.Repeat(configData.Options.EventTimers.EventIntervalMinutes * 60, 0, () => StartRandomOnMap());
                }
            }
            catch (System.Exception ex) {
                PrintError($"Error starting event timer: {ex.Message}");
            }
        }
        private void StopTimer() {
            if (EventTimer != null) EventTimer.Destroy();
        }
        private bool CheckPerformanceAndPlayerCount(string eventType = "Event") {
            // Check minimum player requirement
            if (configData?.Options?.MinimumPlayerCount != null && BasePlayer.activePlayerList.Count < configData.Options.MinimumPlayerCount) {
                string reason = "Not enough players online";
                string additionalInfo = $"Current Players: {BasePlayer.activePlayerList.Count} / {configData.Options.MinimumPlayerCount} minimum required";
                SendSkippedEventDiscord(reason, eventType, additionalInfo);
                // Keep console logging
                if (configData.Logging.LogDebugToConsole) {
                    Puts($"CELESTIAL BARRAGE SKIPPED - Not enough players online ({BasePlayer.activePlayerList.Count} < {configData.Options.MinimumPlayerCount}) - {eventType}");
                }
                return false;
            }
            // Check performance monitoring
            if (configData?.Options?.PerformanceMonitoring?.EnableFPSCheck == true) {
                float currentFPS = 1f / UnityEngine.Time.unscaledDeltaTime;
                if (currentFPS < configData.Options.PerformanceMonitoring.MinimumFPS) {
                    string reason = "Server performance below threshold";
                    string additionalInfo = $"Current FPS: {currentFPS:F1} / {configData.Options.PerformanceMonitoring.MinimumFPS} minimum required";
                    SendSkippedEventDiscord(reason, eventType, additionalInfo);
                    // Keep console logging
                    if (configData.Logging.LogDebugToConsole) {
                        Puts($"CELESTIAL BARRAGE CANCELLED - Low FPS detected ({currentFPS:F1} < {configData.Options.PerformanceMonitoring.MinimumFPS}) - {eventType}");
                    }
                    return false;
                }
            }
            return true;
        }
        private void StartRandomOnMap(bool skipPlayerCount = false, bool skipFpsCheck = false) {
            // Player count check only applies to automatic events, not admin-triggered ones
            if (!skipPlayerCount && !CheckPerformanceAndPlayerCount("Automatic Event")) return;
            float mapsize = (TerrainMeta.Size.x / 2) - 600f;
            float randomX = UnityEngine.Random.Range(-mapsize, mapsize);
            float randomY = UnityEngine.Random.Range(-mapsize, mapsize);
            Vector3 callAt = new Vector3(randomX, 0f, randomY);
            var selectedSetting = GetRandomIntensitySetting();
            StartCelestialEvent(callAt, selectedSetting.setting, "Automatic Event", skipFpsCheck);
        }
        private (ConfigData.Settings setting, string intensity) GetRandomIntensitySetting() {
            var w = configData.IntensitySettings.SpawnWeights;
            float total = w.MildWeight + w.MediumWeight + w.ExtremeWeight;
            if (total <= 0f) return (configData.IntensitySettings.Mild, "Mild");
            float roll = UnityEngine.Random.Range(0f, total);
            if (roll < w.MildWeight) return (configData.IntensitySettings.Mild, "Mild");
            roll -= w.MildWeight;
            if (roll < w.MediumWeight) return (configData.IntensitySettings.Medium, "Medium");
            return (configData.IntensitySettings.Extreme, "Extreme");
        }
        private bool StartOnPlayer(string playerName, ConfigData.Settings setting, string eventType, BasePlayer caller = null, bool skipFpsCheck = false) {
            BasePlayer player = GetPlayerByName(playerName, caller);
            if (player == null) return false;
            StartCelestialEvent(player.transform.position, setting, eventType, skipFpsCheck);
            return true;
        }
        private bool StartRandomOnPlayer(string playerName, string eventType, BasePlayer caller = null, bool skipFpsCheck = false) {
            BasePlayer player = GetPlayerByName(playerName, caller);
            if (player == null) return false;
            var randomSetting = GetRandomIntensitySetting();
            StartCelestialEvent(player.transform.position, randomSetting.setting, eventType, skipFpsCheck);
            return true;
        }
        private void StartRandomOnPosition(Vector3 position, string eventType, bool skipFpsCheck = false) {
            var randomSetting = GetRandomIntensitySetting();
            StartCelestialEvent(position, randomSetting.setting, eventType, skipFpsCheck);
        }
        private void StartBarrage(Vector3 origin, Vector3 direction) => timer.Repeat(configData.BarrageSettings.RocketDelay, configData.BarrageSettings.NumberOfRockets, () => SpreadRocket(origin, direction));
        private void StartCelestialEvent(Vector3 origin, ConfigData.Settings setting, string eventType = "Manual", bool skipFpsCheck = false) {
            // FPS check applies to automatic events only; admin-triggered events bypass it
            if (!skipFpsCheck && configData?.Options?.PerformanceMonitoring?.EnableFPSCheck == true) {
                float currentFPS = 1f / UnityEngine.Time.unscaledDeltaTime;
                if (currentFPS < configData.Options.PerformanceMonitoring.MinimumFPS) {
                    LogMessage($"CELESTIAL BARRAGE BLOCKED\nLow FPS detected ({currentFPS:F1} < {configData.Options.PerformanceMonitoring.MinimumFPS})\n{eventType}", "admin");
                    return;
                }
            }
            float radius = setting.Radius;
            int numberOfRockets = configData.Options.EventTimers.UseRandomTimers ? UnityEngine.Random.Range(setting.RocketAmountMin, setting.RocketAmountMax + 1) : setting.RocketAmountMin;
            float duration = configData.Options.EventTimers.UseRandomTimers ? UnityEngine.Random.Range(setting.DurationSecondsMin, setting.DurationSecondsMax) : setting.DurationSecondsMin;
            bool dropsItems = setting.ItemDropControl.EnableItemDrop;
            ItemDrop[] itemDrops = setting.ItemDropControl.ItemsToDrop;
            float intervals = duration / numberOfRockets;
            // Determine intensity level by comparing settings objects
            string intensity = "Unknown";
            if (ReferenceEquals(setting, configData.IntensitySettings.Mild)) intensity = "Mild";
            else if (ReferenceEquals(setting, configData.IntensitySettings.Medium)) intensity = "Medium";
            else if (ReferenceEquals(setting, configData.IntensitySettings.Extreme)) intensity = "Extreme";
            // Log celestial barrage start to console
            string gridRef = GetGridReference(origin);
            Vector3 groundPos = GetGroundPosition(origin);
            string teleportCmd = $"teleportpos {groundPos.x:F1} {groundPos.y:F1} {groundPos.z:F1}";
            // Start meteor shower event
            StartMeteorShowerWithEffects(origin, setting, eventType, intensity, gridRef, teleportCmd, intervals, numberOfRockets, duration, radius);
        }
        private void StartMeteorShowerWithEffects(Vector3 origin, ConfigData.Settings setting, string eventType, string intensity, string gridRef, string teleportCmd, float intervals, int numberOfRockets, float duration, float radius) {
            string startMessage = $"========== METEOR SHOWER STARTED ==========\n" + $"Type: {eventType} ({intensity})\n" + $"Location: ({origin.x:F0}, {origin.z:F0}) Grid: {gridRef}\n" + $"Stats: {numberOfRockets} rockets ({setting.RocketAmountMin}-{setting.RocketAmountMax} range), {FormatDuration(duration)} duration ({FormatDuration(setting.DurationSecondsMin)}-{FormatDuration(setting.DurationSecondsMax)} range), {radius}m radius\n" + $"Teleport: {teleportCmd}\n" + $"==========================================";
            // Send enhanced Discord embeds to admin
            SendEnhancedDiscordMessage(true, eventType, intensity, gridRef, numberOfRockets, duration, radius, origin, teleportCmd);
            // Send public Discord message if enabled
            if (configData.Logging.PublicChannel.Enabled && IsValidWebhookUrl(configData.Logging.PublicChannel.PublicWebhookURL)) {
                SendPublicDiscordEmbed($"Celestial barrage started at {gridRef}!", intensity, gridRef, true);
            }
            // Send in-game notifications (independent of Discord)
            if (configData.Options.InGamePlayerEventNotifications) {
                // Broadcast location info to all players
                foreach (var player in BasePlayer.activePlayerList) {
                    switch (intensity.ToLower()) {
                        case "mild" : player.ChatMessage($"<color=#32CD32>CELESTIAL BARRAGE (Mild)</color> started at grid <color=#FFFFFF>{gridRef}</color>");
                        break;
                        case "extreme" : player.ChatMessage($"<color=#DC143C>CELESTIAL BARRAGE (Extreme)</color> started at grid <color=#FFFFFF>{gridRef}</color>");
                        break;
                        case "medium" : default : player.ChatMessage($"<color=#FFD700>CELESTIAL BARRAGE (Medium)</color> started at grid <color=#FFFFFF>{gridRef}</color>");
                        break;
                    }
                }
            }
            // Create map marker if enabled
            if (configData.Options.VisualEffects.ShowEventMapMarkers) {
                CreateMapMarker(origin, intensity, duration);
            }
            timer.Repeat(intervals, numberOfRockets, () => RandomRocket(origin, radius, setting));
            // Schedule the end event hook to fire after all rockets have been spawned + 15 second buffer
            timer.Once(duration + 15f, () => {
                string endMessage = $"========== METEOR SHOWER ENDED ===========\n" + $"Type: {eventType} ({intensity})\n" + $"Location: ({origin.x:F0}, {origin.z:F0}) Grid: {gridRef}\n" + $"Teleport: {teleportCmd}\n" + $"==========================================";
                // Send enhanced Discord end embed to admin
                SendEnhancedDiscordMessage(false, eventType, intensity, gridRef, numberOfRockets, duration, radius, origin, teleportCmd);
                // Send public Discord end message if enabled
                if (configData.Logging.PublicChannel.Enabled && IsValidWebhookUrl(configData.Logging.PublicChannel.PublicWebhookURL)) {
                    SendPublicDiscordEmbed($"Celestial barrage ended at {gridRef}", intensity, gridRef, false);
                }
                // Send in-game notifications (independent of Discord)
                if (configData.Options.InGamePlayerEventNotifications) {
                    // Broadcast end to all players
                    foreach (var player in BasePlayer.activePlayerList) {
                        player.ChatMessage($"<color=#20B2AA>Celestial barrage ended at grid {gridRef}</color>");
                    }
                }
                // Remove map marker if it exists
                // Markers auto-remove via timer, no manual cleanup needed
            }
            );
        }
        private void SendEnhancedDiscordMessage(bool isStart, string eventType, string intensity, string gridRef, int numberOfRockets, float duration, float radius, Vector3 origin, string teleportCmd) {
            // Only send if admin Discord is enabled and event messages are included
            if (!configData.Logging.AdminChannel.Enabled || !configData.Logging.AdminChannel.IncludeEventMessages || !IsValidWebhookUrl(configData.Logging.AdminChannel.PrivateAdminWebhookURL)) return;
            float currentFPS = 1f / UnityEngine.Time.unscaledDeltaTime;
            int playersOnline = BasePlayer.activePlayerList.Count;
            var embed = new object();
            if (isStart) {
                // Enhanced Started Message - WITH PROPER SECTION SPACING
                embed = new {
                    title = GetIntensityIcon(intensity) + " Celestial Barrage Event", description = "🟢 **STARTED**", color = GetIntensityColor(intensity, true), fields = new[] {
                        new {
                            name = "⚡ **Event Information**", value = "", inline = false
                        }
                        , new {
                            name = "", value = $"🎯 **Event Type:** `{eventType} ({intensity})`", inline = false
                        }
                        , new {
                            name = "", value = $"📍 **Grid Location:** `{gridRef}`", inline = false
                        }
                        ,  // SPACING BETWEEN SECTIONS
                        new {
                            name = "", value = "", inline = false
                        }
                        , new {
                            name = "📊 **Event Statistics**", value = "", inline = false
                        }
                        , new {
                            name = "", value = $"🚀 **Rocket Count:** `{numberOfRockets}`", inline = false
                        }
                        , new {
                            name = "", value = $"⏱️ **Duration:** `{FormatDuration(duration)}`", inline = false
                        }
                        , new {
                            name = "", value = $"📏 **Impact Radius:** `{radius}m`", inline = false
                        }
                        ,  // SPACING BETWEEN SECTIONS
                        new {
                            name = "", value = "", inline = false
                        }
                        , new {
                            name = "⚙️ **Server Status**", value = "", inline = false
                        }
                        , new {
                            name = "", value = $"🖥️ **Server FPS:** `{currentFPS:F1}`", inline = false
                        }
                        , new {
                            name = "", value = $"👥 **Players Online:** `{playersOnline}`", inline = false
                        }
                        ,  // SPACING BETWEEN SECTIONS
                        new {
                            name = "", value = "", inline = false
                        }
                        , new {
                            name = "🗺️ **Location Data**", value = "", inline = false
                        }
                        , new {
                            name = "", value = $"🔗 **Teleport Command:** `{teleportCmd}`", inline = false
                        }
                    }
                    , timestamp = System.DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), footer = new {
                        text = $"Celestial Barrage v{Version}"
                    }
                }
                ;
            }
            else {
                // Enhanced Ended Message - WITH PROPER SECTION SPACING
                embed = new {
                    title = "🌟 Celestial Barrage Event", description = "🔴 **COMPLETED**", color = 0x27AE60,  // Success green
                    fields = new[] {
                        new {
                            name = "📋 **Event Summary**", value = "", inline = false
                        }
                        , new {
                            name = "", value = $"🎯 **Event Type:** `{eventType} ({intensity})`", inline = false
                        }
                        , new {
                            name = "", value = $"📍 **Grid Location:** `{gridRef}`", inline = false
                        }
                        ,  // SPACING BETWEEN SECTIONS
                        new {
                            name = "", value = "", inline = false
                        }
                        , new {
                            name = "🗺️ **Location Data**", value = "", inline = false
                        }
                        , new {
                            name = "", value = $"🔗 **Teleport Command:** `{teleportCmd}`", inline = false
                        }
                    }
                    , timestamp = System.DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), footer = new {
                        text = $"Celestial Barrage v{Version}"
                    }
                }
                ;
            }
            var payload = new {
                username = "Celestial Barrage Monitor", avatar_url = "https://cdn.discordapp.com/emojis/1234567890123456789.png", embeds = new[] {
                    embed
                }
            }
            ;
            string json = JsonConvert.SerializeObject(payload);
            var headers = new Dictionary<string, string> {
                {
                    "Content-Type", "application/json"
                }
            }
            ;
            webrequest.Enqueue(configData.Logging.AdminChannel.PrivateAdminWebhookURL, json, (code, response) => {
                if (code != 200 && code != 204) {
                    PrintError($"Failed to send enhanced Discord embed to Admin webhook. Response code: {code}");
                    if (configData.Logging.LogDebugToConsole) {
                        Puts($"Admin Discord webhook error response: {response}");
                    }
                }
            }
            , this, Core.Libraries.RequestMethod.POST, headers);
        }
        private string GetIntensityIcon(string intensity) {
            switch (intensity.ToLower()) {
                case "mild" : return "💫";
                // Changed from 🌿 to 💫
                case "medium" : return "⚡";
                // Electric energy
                case "extreme" : return "🔥";
                // Intense fire
                default : return "☄️";
                // Default meteor
            }
        }
        private int GetIntensityColor(string intensity, bool isStart) {
            if (isStart) {
                switch (intensity.ToLower()) {
                    case "mild" : return 0x2ECC71;
                    // Emerald green
                    case "medium" : return 0xF39C12;
                    // Orange
                    case "extreme" : return 0xE74C3C;
                    // Crimson red
                    default : return 0x3498DB;
                    // Blue
                }
            }
            else {
                return 0x27AE60;
                // Success green for completed events
            }
        }
        private void RandomRocket(Vector3 origin, float radius, ConfigData.Settings setting) {
            try {
                bool isFireRocket = false;
                Vector2 rand = UnityEngine.Random.insideUnitCircle;
                Vector3 offset = new Vector3(rand.x * radius, 0, rand.y * radius);
                Vector3 direction = (Vector3.up * -2.0f + Vector3.right).normalized;
                Vector3 launchPos = origin + offset - direction * 200;
                if (RandomRange(1, setting.FireRocketChance) == 1) isFireRocket = true;
                BaseEntity rocket = CreateRocket(launchPos, direction, isFireRocket, setting);
                // Check if rocket creation failed
                if (rocket == null) {
                    PrintWarning("Failed to create meteor rocket - skipping this rocket spawn");
                    return;
                }
                if (setting.ItemDropControl.EnableItemDrop) {
                    var comp = rocket.gameObject.AddComponent<ItemCarrier>();
                    comp.SetCarriedItems(setting.ItemDropControl.ItemsToDrop);
                    comp.SetDropMultiplier(configData.IntensitySettings.ItemDropMultiplier);
                }
                // If grenade launcher override occurred, spawn bonus original projectile
                if (wasGrenadeLauncherOverride) {
                    timer.Once(UnityEngine.Random.Range(1f, 3f), () => {
                        // Create random offset position
                        Vector2 bonusRand = UnityEngine.Random.insideUnitCircle;
                        Vector3 bonusOffset = new Vector3(bonusRand.x * radius, 0, bonusRand.y * radius);
                        Vector3 bonusLaunchPos = origin + bonusOffset - direction * 200;
                        // Spawn another random projectile (easiest approach)
                        RandomRocket(origin, radius, setting);
                    }
                    );
                }
            }
            catch (System.Exception ex) {
                PrintError($"Error creating rocket: {ex.Message}");
            }
        }
        private void SpreadRocket(Vector3 origin, Vector3 direction) {
            var barrageSpread = configData.BarrageSettings.RocketSpread;
            direction = Quaternion.Euler(UnityEngine.Random.Range((float)(-(double)barrageSpread * 0.5), barrageSpread * 0.5f), UnityEngine.Random.Range((float)(-(double)barrageSpread * 0.5), barrageSpread * 0.5f), UnityEngine.Random.Range((float)(-(double)barrageSpread * 0.5), barrageSpread * 0.5f)) * direction;
            BaseEntity rocket = CreateRocket(origin, direction, false, configData.IntensitySettings.Medium);
            // Check if rocket creation failed
            if (rocket == null) {
                PrintWarning("Failed to create barrage rocket - skipping this rocket spawn");
                return;
            }
        }
        private BaseEntity CreateRocket(Vector3 startPoint, Vector3 direction, bool isFireRocket, ConfigData.Settings setting) {
            ItemDefinition projectileItem = null;
            wasGrenadeLauncherOverride = false;
            // Reset flag
            if (isFireRocket) {
                // 1% MLRS, 40% fire rocket, 59% catapult incendiary boulder
                float roll = UnityEngine.Random.Range(0f, 1f);
                if (roll < 0.01f) projectileItem = ItemManager.FindItemDefinition("ammo.rocket.mlrs");
                else if (roll < 0.41f)  // 0.01 + 0.40 = 0.41
                projectileItem = ItemManager.FindItemDefinition("ammo.rocket.fire");
                else projectileItem = ItemManager.FindItemDefinition("catapult.ammo.incendiary");
            }
            else {
                // 20% basic, 20% hv, 20% smoke rocket, 40% catapult boulder
                float roll = UnityEngine.Random.Range(0f, 1f);
                if (roll < 0.2f) projectileItem = ItemManager.FindItemDefinition("ammo.rocket.basic");
                else if (roll < 0.4f) projectileItem = ItemManager.FindItemDefinition("ammo.rocket.hv");
                else if (roll < 0.6f) projectileItem = ItemManager.FindItemDefinition("ammo.rocket.smoke");
                else projectileItem = ItemManager.FindItemDefinition("catapult.ammo.boulder");
            }
            // 5% chance to override with grenade launcher (50/50 smoke or HE)
            if (UnityEngine.Random.Range(0f, 1f) < 0.05f) {
                wasGrenadeLauncherOverride = true;
                // Set flag
                if (UnityEngine.Random.Range(0f, 1f) < 0.5f) projectileItem = ItemManager.FindItemDefinition("ammo.grenadelauncher.smoke");
                else projectileItem = ItemManager.FindItemDefinition("ammo.grenadelauncher.he");
            }
            // VALIDATION: Fallback to basic rocket if item not found
            if (projectileItem == null) {
                PrintWarning("Failed to find projectile item, falling back to basic rocket");
                projectileItem = ItemManager.FindItemDefinition("ammo.rocket.basic");
                // If even basic rocket fails, try one more fallback
                if (projectileItem == null) {
                    string reason = "Critical error: Missing rocket ammo items";
                    string additionalInfo = "Unable to find any rocket projectiles in item definitions";
                    SendSkippedEventDiscord(reason, "System Check", additionalInfo);
                    PrintError("Critical: Cannot find any rocket ammo items! Meteor event cancelled.");
                    return null;
                    // Return null to indicate failure
                }
            }
            // Debug logging for projectile creation
            if (configData?.Logging?.LogDebugToConsole == true) {
                Puts($"[DEBUG] Creating projectile: {projectileItem.shortname}");
            }
            // Validate component exists
            ItemModProjectile component = projectileItem.GetComponent<ItemModProjectile>();
            if (component == null) {
                PrintError($"Critical: {projectileItem.shortname} has no ItemModProjectile component!");
                return null;
            }
            BaseEntity entity = GameManager.server.CreateEntity(component.projectileObject.resourcePath, startPoint, new Quaternion(), true);
            TimedExplosive timedExplosive = entity.GetComponent<TimedExplosive>();
            ServerProjectile serverProjectile = entity.GetComponent<ServerProjectile>();
            serverProjectile.gravityModifier = 0;
            serverProjectile.speed = 25;
            timedExplosive.timerAmountMin = 300;
            timedExplosive.timerAmountMax = 300;
            ScaleAllDamage(timedExplosive.damageTypes, setting.DamageMultiplier);
            // Add visual effects if enabled (simplified approach)
            if (configData.Options.VisualEffects.EnableParticleTrails) {
                AddVisualTrail(entity, projectileItem.shortname);
            }
            serverProjectile.InitializeVelocity(direction.normalized * 25);
            entity.Spawn();
            // Track this rocket as one of ours
            meteorRockets.Add(entity);
            // Debug logging for tracking
            if (configData?.Logging?.LogDebugToConsole == true) {
                Puts($"[DEBUG] Tracking meteor projectile: {entity.ShortPrefabName} (Total tracked: {meteorRockets.Count})");
            }
            return entity;
        }
        private void AddVisualTrail(BaseEntity rocket, string projectileType) {
            string effectPath = GetTrailEffectForProjectile(projectileType);
            // Skip if no effect specified
            if (string.IsNullOrEmpty(effectPath)) return;
            float baseFrequency = GetTrailFrequencyForProjectile(projectileType);
            int iterations = GetTrailDurationForProjectile(projectileType);
            // Track spawn count for this rocket's trail
            int spawnCount = 0;
            // Create recursive function that generates new random delay for EACH effect spawn
            Action spawnNextEffect = null;
            spawnNextEffect = () => {
                // Check if rocket is still valid and we haven't reached max iterations
                if (rocket != null && !rocket.IsDestroyed && spawnCount < iterations) {
                    // Spawn the effect at current rocket position
                    Effect.server.Run(effectPath, rocket.transform.position, Vector3.up, null, false);
                    spawnCount++;
                    // Generate NEW random delay for THIS iteration (unique every time)
                    // Increased randomization range (±0.5) for more organic, less mechanical effect patterns
                    float randomizedFrequency = Mathf.Max(0f, baseFrequency + UnityEngine.Random.Range(-0.5f, 0.5f));
                    // Schedule the next effect spawn with the random delay
                    timer.Once(randomizedFrequency, spawnNextEffect);
                }
            }
            ;
            // Start the trail effect sequence
            spawnNextEffect();
        }
        private string GetTrailEffectForProjectile(string projectileType) {
            switch (projectileType) {
                case "ammo.rocket.fire" : return "assets/bundled/prefabs/fx/gas_explosion_small.prefab";
                // Bigger explosion for fire
                case "ammo.rocket.hv" : return "assets/content/vehicles/mlrs/effects/pfx_mlrs_backfire.prefab";
                case "ammo.rocket.mlrs" : return "assets/content/vehicles/mlrs/effects/pfx_mlrs_backfire.prefab";
                case "ammo.rocket.basic" : return "assets/bundled/prefabs/fx/build/promote_toptier.prefab";
                case "catapult.ammo.incendiary" : return "assets/bundled/prefabs/fx/fire/fire_v2.prefab";
                case "ammo.rocket.smoke" : return "assets/bundled/prefabs/fx/fire/fire_v3.prefab";
                case "catapult.ammo.boulder" : return "assets/bundled/prefabs/fx/weapons/landmine/landmine_explosion.prefab";
                case "ammo.grenadelauncher.smoke" : return "assets/content/effects/weather/pfx_lightning_medium.prefab";
                case "ammo.grenadelauncher.he" : return "assets/content/effects/weather/pfx_lightning_strong.prefab";
                default : return "assets/bundled/prefabs/fx/build/promote_toptier.prefab";
                // Standard fallback
            }
        }
        private float GetTrailFrequencyForProjectile(string projectileType) {
            switch (projectileType) {
                case "ammo.rocket.hv" : return 0.95f;
                case "ammo.rocket.mlrs" : return 0.95f;
                case "ammo.rocket.fire" : return 0.95f;
                case "ammo.rocket.basic" : return 0.45f;
                case "ammo.grenadelauncher.he" : return 0.95f;
                case "catapult.ammo.incendiary" : return 0.25f;
                case "ammo.rocket.smoke" : return 0.15f;
                case "ammo.grenadelauncher.smoke" : return 0.95f;
                case "catapult.ammo.boulder" : return 2.5f;
                default : return 0.45f;
                // Standard fallback
            }
        }
        private int GetTrailDurationForProjectile(string projectileType) {
            switch (projectileType) {
                case "ammo.rocket.hv" : return 5;
                case "ammo.rocket.mlrs" : return 5;
                case "ammo.rocket.fire" : return 5;
                case "ammo.rocket.smoke" : return 45;
                case "ammo.grenadelauncher.smoke" : return 10;
                case "catapult.ammo.boulder" : return 6;
                case "catapult.ammo.incendiary" : return 30;
                case "ammo.rocket.basic" : return 30;
                case "ammo.grenadelauncher.he" : return 10;
                default : return 15;
            }
        }
        private void ScaleAllDamage(List<DamageTypeEntry> damageTypes, float scale) {
            for (int i = 0; i < damageTypes.Count; i++) {
                damageTypes[i].amount *= scale;
            }
        }
        private void LogMeteorImpact(BaseCombatEntity entity, HitInfo info) {
            string entityType = "Unknown";
            string ownerInfo = "";
            bool isPlayerStructure = false;
            bool isPlayer = false;
            string damageSource = "";
            // Determine what weapon/projectile caused the damage
            if (info?.WeaponPrefab != null) {
                damageSource = info.WeaponPrefab.ShortPrefabName;
            }
            float totalDamage = info?.damageTypes?.Total() ?? 0f;
            bool isCatapultImpact = damageSource.Contains("boulder") || damageSource.Contains("catapult");
            bool isGrenadeImpact = damageSource.Contains("40mm") || damageSource.Contains("grenade");
            // Enhanced NPC filtering - check for known NPC types first
            if (IsNPCEntity(entity)) {
                // This is an NPC - don't log it
                return;
            }
            // Determine what was hit
            if (entity is BasePlayer) {
                var player = entity as BasePlayer;
                // Additional check to filter out NPC players (scientists, etc.)
                if (player.IsNpc || player.userID < 76561197960265728L) {
                    // This is an NPC player - don't log it
                    return;
                }
                entityType = "Player";
                ownerInfo = player.displayName;
                isPlayer = true;
                // Screen shake effect for the hit player if enabled
                if (configData.Options.VisualEffects.EnableScreenShake) {
                    ApplyScreenShake(player, 1.0f);
                }
            }
            else if (entity.OwnerID != 0) {
                // Player-owned structure
                isPlayerStructure = true;
                var ownerPlayer = BasePlayer.FindByID(entity.OwnerID);
                entityType = entity.ShortPrefabName;
                ownerInfo = ownerPlayer != null ? ownerPlayer.displayName : $"OwnerID: {entity.OwnerID}";
                // Screen shake for nearby players if enabled
                if (configData.Options.VisualEffects.EnableScreenShake) {
                    ApplyScreenShakeToNearbyPlayers(entity.transform.position, 50f, 0.5f);
                }
            }
            else {
                // Natural/unowned entity - don't log these
                return;
            }
            // Apply damage threshold — players always pass, structures/deployables must meet minimum
            if (!isPlayer && totalDamage < configData.Logging.AdminChannel.ImpactFiltering.MinimumDamageThreshold) return;
            // Log and fire hook for players or player structures only
            if (isPlayer || isPlayerStructure) {
                string damageInfo = $"Damage: {totalDamage:F1}";
                Vector3 pos = entity.transform.position;
                string teleportCmd = $"teleportpos {pos.x:F1} {pos.y:F1} {pos.z:F1}";
                // Enhanced debug logging
                if (configData?.Logging?.LogDebugToConsole == true) {
                    string projectileType = "Unknown";
                    if (damageSource.Contains("rocket")) projectileType = "Rocket";
                    else if (isCatapultImpact) projectileType = "Catapult";
                    else if (isGrenadeImpact) projectileType = "Grenade";
                    Puts($"[DEBUG] LOGGING IMPACT - Type: {projectileType}, Weapon: {damageSource}, Damage: {totalDamage:F1}");
                }
                // Console logging for admin monitoring
                if (configData.Logging.LogDebugToConsole) {
                    string consoleMessage = isPlayer ? $"METEOR IMPACT: {entityType} - Player: {ownerInfo} - Weapon: {damageSource} - {damageInfo}" : $"METEOR IMPACT: {entityType} - Owner: {ownerInfo} - Weapon: {damageSource} - {damageInfo}";
                    Puts(consoleMessage);
                }
                // Send beautiful Discord embed message
                SendMeteorImpactDiscord(entityType, ownerInfo, damageSource, damageInfo, teleportCmd, isPlayer, isPlayerStructure);
                // Fire the hook for other plugins
                Interface.CallHook("OnCelestialBarrageImpact", entity, info, entityType, ownerInfo);
            }
        }
        private bool IsNPCEntity(BaseCombatEntity entity) {
            if (entity == null) return false;
            // Check if it's an NPC player first
            if (entity is BasePlayer player) {
                return player.IsNpc || player.userID < 76561197960265728L;
            }
            // Check for common NPC entity types by prefab name
            string prefabName = entity.ShortPrefabName.ToLower();
            // List of known NPC entities that should be filtered
            string[] npcPrefabs = {
                "scientist",  // Scientists
                "murderer",  // Murderer NPCs
                "bandit",  // Bandit NPCs
                "dweller",  // Tunnel dwellers
                "scarecrow",  // Scarecrows
                "zombie",  // Zombies (if any)
                "bear",  // Bears
                "wolf",  // Wolves
                "chicken",  // Chickens
                "stag",  // Deer/Stags
                "boar",  // Boars
                "horse",  // Horses
       
// ... [Code preview truncated due to length. Full source in attached file] ...

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