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

FREE MlrsMapMarker : 2.0.3 2.0.3

Нет прав для скачивания
C#:
using Facepunch;
using HarmonyLib;
using Oxide.Core;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace Oxide.Plugins;
[Info("MlrsMapMarker", "Magic Services // crash", "2.0.3")]
[Description("Shows MLRS target locations on the map with accurate impact tracking")]
public class MlrsMapMarker : RustPlugin {
    private static MlrsMapMarker _instance;
    private readonly Dictionary<ulong, List<MapNote>> _playerMarkers = new();
    private readonly List<StrikeData> _activeStrikes = new();
    private readonly Dictionary<MLRS, TargetData> _mlrsTargets = new();
    private readonly Dictionary<MLRSRocket, StrikeData> _rocketToStrike = new();
    private Timer _cleanupTimer;
    private Configuration _config;
    private class TargetData {
        public Vector3 position;
    }
    private class StrikeData {
        public Vector3 position;
        public string playerName;
        public float expireTime;
        public string grid;
        public float firstImpactTime;
        public bool hasImpacted;
    }
    private class Configuration {
        public float MarkerDuration = 45f;
        public int MarkerIcon = 4;
        public int MarkerColor = 3;
        public string MarkerLabel = "MLRS Strike!";
        public bool ShowChatNotification = true;
        public bool ShowPlayerName = true;
        public float CleanupInterval = 5f;
        public bool ShowGridReference = true;
        public bool AutoShowToAllPlayers = true;
    }
    protected override void LoadDefaultConfig() => _config = new Configuration();
    protected override void LoadConfig() {
        base.LoadConfig();
        try {
            _config = Config.ReadObject<Configuration>();
            if (_config == null) throw new Exception();
        }
        catch {
            LoadDefaultConfig();
            PrintWarning("Creating new config file");
            SaveConfig();
        }
    }
    protected override void SaveConfig() => Config.WriteObject(_config);
    private void Init() {
        _instance = this;
        PatchNestedClasses();
    }
    private void OnServerInitialized() {
        _cleanupTimer = timer.Every(_config.CleanupInterval, CleanupExpired);
    }
    private void Unload() {
        _cleanupTimer?.Destroy();
        foreach (var kvp in _playerMarkers) {
            var markers = kvp.Value;
            for (var i = 0; i < markers.Count; i++) markers[i].Dispose();
            markers.Clear();
            var connectedPlayer = BasePlayer.FindByID(kvp.Key);
            if (connectedPlayer is null || !connectedPlayer.IsConnected) continue;
            connectedPlayer.State.pings ??= new List<MapNote>();
            connectedPlayer.SendPingsToClient();
        }
        _playerMarkers.Clear();
        _activeStrikes.Clear();
        _mlrsTargets.Clear();
        _rocketToStrike.Clear();
        _instance = null;
    }
    private void OnPlayerDisconnected(BasePlayer player) {
        if (!_playerMarkers.TryGetValue(player.userID, out var markers)) return;
        for (var i = 0; i < markers.Count; i++) markers[i].Dispose();
        markers.Clear();
        _playerMarkers.Remove(player.userID);
    }
    private void OnPlayerPingsSend(BasePlayer player, MapNoteList mapNoteList) {
        if (_playerMarkers.TryGetValue(player.userID, out var markers)) mapNoteList.notes.AddRange(markers);
    }
    private object OnMlrsTargetSet(MLRS mlrs, Vector3 targetPos, BasePlayer player) {
        if (mlrs is null) return null;
        if (!_mlrsTargets.TryGetValue(mlrs, out var data)) {
            data = new TargetData();
            _mlrsTargets[mlrs] = data;
        }
        data.position = targetPos;
        return null;
    }
    private void OnMlrsFired(MLRS mlrs, BasePlayer player) {
        if (mlrs is null || !_mlrsTargets.TryGetValue(mlrs, out var targetData)) return;
        var impactTime = CalculateImpactTime(mlrs, targetData.position);
        var strike = new StrikeData {
            position = targetData.position, playerName = _config.ShowPlayerName && player != null ? player.displayName : "Unknown", expireTime = Time.time + _config.MarkerDuration, grid = GetGrid(targetData.position), firstImpactTime = Time.time + impactTime, hasImpacted = false
        }
        ;
        _activeStrikes.Add(strike);
        if (_config.ShowChatNotification) {
            var message = _config.ShowGridReference ? GetMessage("IncomingStrikeWithGrid", null, strike.grid, strike.playerName, impactTime.ToString("F1")) : GetMessage("IncomingStrikeWithCoords", null, FormatPosition(strike.position), strike.playerName, impactTime.ToString("F1"));
            Server.Broadcast(message);
        }
        if (!_config.AutoShowToAllPlayers) return;
        for (var i = 0; i < BasePlayer.activePlayerList.Count; i++) UpdateMarkersForPlayer(BasePlayer.activePlayerList[i]);
    }
    private void OnEntitySpawned(MLRSRocket rocket) {
        if (rocket is null || _activeStrikes.Count == 0) return;
        StrikeData bestMatch = null;
        var closestDist = float.MaxValue;
        for (var i = 0; i < _activeStrikes.Count; i++) {
            var strike = _activeStrikes[i];
            if (strike.hasImpacted) continue;
            var dist = (strike.position - rocket.transform.position).sqrMagnitude;
            if (dist >= closestDist) continue;
            closestDist = dist;
            bestMatch = strike;
        }
        if (bestMatch != null) _rocketToStrike[rocket] = bestMatch;
    }
    private void OnExplosiveThrown(BasePlayer player, MLRSRocket rocket) {
        if (rocket is null || !_rocketToStrike.TryGetValue(rocket, out var strike)) return;
        strike.hasImpacted = true;
    }
    private object OnEntityKill(MLRSRocket rocket) {
        if (rocket is null || !_rocketToStrike.TryGetValue(rocket, out var strike)) return null;
        strike.hasImpacted = true;
        _rocketToStrike.Remove(rocket);
        return null;
    }
    private void PatchNestedClasses() {
        foreach (var type in typeof(MlrsMapMarker).GetNestedTypes(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public)) {
            var harmonyPatchAttributes = type.GetCustomAttributes(typeof(HarmonyPatch), true);
            if (harmonyPatchAttributes is not {
                Length : > 0
            }
            ) continue;
            try {
                HarmonyInstance.CreateClassProcessor(type).Patch();
            }
            catch (Exception ex) {
                PrintError($"Failed to apply Harmony patch for {type.Name}: {ex}");
            }
        }
    }
    [HarmonyPatch(typeof(MLRS), "SetUserTargetHitPos")]
    internal static class MlrsTargetPatch {
        [HarmonyPostfix]
        private static void Postfix(MLRS __instance) {
            if (_instance == null) return;
            var player = __instance.GetMounted();
            var truePos = __instance.TrueHitPos;
            if (truePos != Vector3.zero) Interface.CallHook("OnMlrsTargetSet", __instance, truePos, player);
        }
    }
    private float CalculateImpactTime(MLRS mlrs, Vector3 targetPos) {
        var startPos = mlrs.firingPoint.position;
        var speed = mlrs.rocketSpeed;
        var aimDirection = Ballistics.GetAimToTarget(startPos, targetPos, speed, mlrs.vRotMax, mlrs.rocketBaseGravity, mlrs.minRange, out var requiredGravity );
        var horizontalDir = new Vector3(aimDirection.x, 0, aimDirection.z).magnitude;
        var launchAngle = Mathf.Atan2(aimDirection.y, horizontalDir);
        var heightDiff = targetPos.y - startPos.y;
        var vSinTheta = speed * Mathf.Sin(launchAngle);
        var discriminant = vSinTheta * vSinTheta - 2f * requiredGravity * heightDiff;
        if (discriminant < 0) return 10f;
        var time = (vSinTheta + Mathf.Sqrt(discriminant)) / requiredGravity;
        return Mathf.Max(0.1f, time);
    }
    private static string GetGrid(Vector3 pos) => MapHelper.PositionToString(pos);
    private void UpdateMarkersForPlayer(BasePlayer player) {
        if (player is null || !player.IsConnected) return;
        ClearMarkersForPlayer(player);
        if (!_playerMarkers.TryGetValue(player.userID, out var markers)) {
            markers = new List<MapNote>();
            _playerMarkers[player.userID] = markers;
        }
        var currentTime = Time.time;
        for (var i = 0; i < _activeStrikes.Count; i++) {
            var strike = _activeStrikes[i];
            if (strike.expireTime > currentTime) markers.Add(CreateMarker(strike));
        }
        player.SendPingsToClient();
    }
    private MapNote CreateMarker(StrikeData strike) {
        var note = Pool.Get<MapNote>();
        note.isPing = true;
        note.worldPosition = strike.position;
        note.icon = _config.MarkerIcon;
        note.colourIndex = _config.MarkerColor;
        var label = _config.MarkerLabel;
        if (_config.ShowPlayerName) label += $" (sent by {strike.playerName})";
        if (_config.ShowGridReference) label += $"\nGrid: {strike.grid}";
        note.label = label;
        return note;
    }
    private void ClearMarkersForPlayer(BasePlayer player) {
        if (!_playerMarkers.TryGetValue(player.userID, out var markers)) return;
        for (var i = 0; i < markers.Count; i++) markers[i].Dispose();
        markers.Clear();
    }
    private void CleanupExpired() {
        var currentTime = Time.time;
        for (var i = _activeStrikes.Count - 1; i >= 0; i--) {
            if (_activeStrikes[i].expireTime <= currentTime) _activeStrikes.RemoveAt(i);
        }
        var toRemoveMlrs = new List<MLRS>();
        foreach (var kvp in _mlrsTargets) {
            if (kvp.Key is null || kvp.Key.IsDestroyed) toRemoveMlrs.Add(kvp.Key);
        }
        for (var i = 0; i < toRemoveMlrs.Count; i++) _mlrsTargets.Remove(toRemoveMlrs[i]);
        var toRemoveRockets = new List<MLRSRocket>();
        foreach (var kvp in _rocketToStrike) {
            if (kvp.Key is null || kvp.Key.IsDestroyed) toRemoveRockets.Add(kvp.Key);
        }
        for (var i = 0; i < toRemoveRockets.Count; i++) _rocketToStrike.Remove(toRemoveRockets[i]);
        for (var i = 0; i < BasePlayer.activePlayerList.Count; i++) {
            var player = BasePlayer.activePlayerList[i];
            if (_playerMarkers.ContainsKey(player.userID)) UpdateMarkersForPlayer(player);
        }
    }
    private static string FormatPosition(Vector3 pos) => $"{pos.x:F0}, {pos.z:F0}";
    [ChatCommand("mlrs")]
    private void CmdMlrsInfo(BasePlayer player, string command, string[] args) {
        var currentTime = Time.time;
        var activeCount = 0;
        for (var i = 0; i < _activeStrikes.Count; i++) {
            if (_activeStrikes[i].expireTime > currentTime) activeCount++;
        }
        if (activeCount == 0) {
            player.ChatMessage(GetMessage("NoActiveStrikes", player.UserIDString));
            return;
        }
        player.ChatMessage(GetMessage("ActiveStrikesHeader", player.UserIDString, activeCount));
        for (var i = 0; i < _activeStrikes.Count; i++) {
            var strike = _activeStrikes[i];
            if (strike.expireTime <= currentTime) continue;
            var timeToImpact = strike.firstImpactTime > 0 ? Mathf.Max(0, strike.firstImpactTime - currentTime) : 0f;
            string timeDisplay;
            if (timeToImpact <= 0.5f) timeDisplay = "NOW";
            else timeDisplay = timeToImpact.ToString("F1") + "s";
            if (_config.ShowPlayerName) {
                player.ChatMessage(GetMessage("StrikeInfo", player.UserIDString, strike.grid, strike.playerName, timeDisplay));
            }
            else {
                player.ChatMessage(GetMessage("StrikeInfoNoPlayer", player.UserIDString, strike.grid, timeDisplay));
            }
        }
    }
    protected override void LoadDefaultMessages() {
        lang.RegisterMessages(new Dictionary<string, string> {
            ["NoActiveStrikes"]
            = "<size=14>:yellowpin: <color=#2C3E50>NO ACTIVE MLRS STRIKES</color></size>\n<size=12><color=#ECF0F1>The battlefield is currently clear</color></size>", ["ActiveStrikesHeader"] = "<size=16>:exclamation: <color=#E74C3C>MLRS STRIKE ALERT</color></size>\n<size=14><color=#F39C12>ACTIVE STRIKES </color><color=#ECF0F1>({0}) INCOMING</color></size>", ["StrikeInfo"] = "<size=12>· <color=#3498DB>Grid: </color><color=#2ECC71>{0}</color> <color=#7F8C8D>| </color><color=#3498DB>Operator: </color><color=#ECF0F1>{1}</color>\n· <color=#3498DB>Impact: </color><color=#E74C3C>{2}</color> <color=#7F8C8D>| </color><color=#3498DB>Status: </color><color=#F39C12>INCOMING</color></size>", ["StrikeInfoNoPlayer"] = "<size=12>· <color=#3498DB>Grid: </color><color=#2ECC71>{0}</color> <color=#7F8C8D>| </color><color=#3498DB>Impact: </color><color=#E74C3C>{1}</color>\n· <color=#3498DB>Status: </color><color=#F39C12>INCOMING</color> <color=#7F8C8D>| </color><color=#3498DB>Threat: </color><color=#E74C3C>HIGH</color></size>", ["IncomingStrikeWithGrid"] = "<size=16>:skull: <color=#E74C3C>MLRS STRIKE INCOMING</color></size>\n<size=12>· <color=#3498DB>Grid: </color><color=#2ECC71>{0}</color> <color=#7F8C8D>| </color><color=#3498DB>Operator: </color><color=#ECF0F1>{1}</color>\n· <color=#3498DB>Impact: </color><color=#E74C3C>{2}</color></size>\n<size=11><color=#E74C3C>:exclamation: SEEK IMMEDIATE SHELTER :exclamation:</color></size>", ["IncomingStrikeWithCoords"] = "<size=16>:skull: <color=#E74C3C>MLRS STRIKE INCOMING</color></size>\n<size=12>· <color=#3498DB>Position: </color><color=#2ECC71>{0}</color> <color=#7F8C8D>| </color><color=#3498DB>Operator: </color><color=#ECF0F1>{1}</color>\n· <color=#3498DB>Impact: </color><color=#E74C3C>{2}</color></size>\n<size=11><color=#E74C3C>:exclamation: SEEK IMMEDIATE SHELTER :exclamation:</color></size>", ["MarkersCleared"] = "<size=14>:coffeecan: <color=#2ECC71>MARKERS CLEARED</color></size>\n<size=12><color=#ECF0F1>All MLRS markers removed</color></size>", ["PlayerMarkersCleared"] = "<size=14>:coffeecan: <color=#2ECC71>YOUR MARKERS CLEARED</color></size>\n<size=12><color=#ECF0F1>All your MLRS markers have been removed</color></size>"
        }
        , this);
    }
    private string GetMessage(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);
}

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