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

FREE BlueprintShare : 1.4.8 1.4.8

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Blueprint Share", "c_creep, modified by SeesAll", "1.4.8")]
    [Description("Allows players to share researched blueprints with their friends, clan or team")]
    class BlueprintShare : RustPlugin
    {
        #region Fields

        [PluginReference]
        private Plugin Clans, Friends;

        private StoredData storedData;

        private Dictionary<string, Action<BasePlayer, string[]>> chatCommandHandlers;
        private Dictionary<ulong, List<ulong>> shareTargetCache = new Dictionary<ulong, List<ulong>>();
        private Dictionary<ulong, Timer> shareTargetCacheTimers = new Dictionary<ulong, Timer>();
        private readonly HashSet<string> normalTechTreeUnlocks = new HashSet<string>();

        private const float shareTargetCacheDuration = 10f; // Seconds

        private enum ShareType
        {
            Teams,
            Friends,
            Clans
        }

        private enum DebugLevel
        {
            Info,
            Warning,
            Error
        }

        private const string usePermission = "blueprintshare.use";
        private const string togglePermission = "blueprintshare.toggle";
        private const string sharePermission = "blueprintshare.share";
        private const string showPermission = "blueprintshare.show";
        private const string bypassPermission = "blueprintshare.bypass";

        private bool isTeamsEnabled;
        private bool isClansEnabled;
        private bool isFriendsEnabled;
        private bool dataDirty = false;
        private Timer pendingDataSaveTimer;

        private const float dataSaveDebounceDelay = 30f; // Seconds

        #endregion

        #region Oxide Hooks

        private void Init()
        {
            LoadData();

            permission.RegisterPermission(usePermission, this);
            permission.RegisterPermission(togglePermission, this);
            permission.RegisterPermission(sharePermission, this);
            permission.RegisterPermission(showPermission, this);
            permission.RegisterPermission(bypassPermission, this);

            isTeamsEnabled = config.TeamsEnabled;
            isClansEnabled = config.ClansEnabled;
            isFriendsEnabled = config.FriendsEnabled;

            chatCommandHandlers = new Dictionary<string, Action<BasePlayer, string[]>>
            {
                ["help"] = HelpCommand,
                ["toggle"] = ToggleCommand,
                ["share"] = ShareCommand,
                ["show"] = ShowCommand
            };

            if (!isTeamsEnabled)
            {
                Unsubscribe(nameof(OnTeamAcceptInvite));
                Unsubscribe(nameof(OnTeamKick));
                Unsubscribe(nameof(OnTeamLeave));
            }

            if (!isClansEnabled)
            {
                Unsubscribe(nameof(OnClanMemberJoined));
                Unsubscribe(nameof(OnClanMemberGone));
                Unsubscribe(nameof(OnClanDisbanded));
            }

            if (!isFriendsEnabled)
            {
                Unsubscribe(nameof(OnFriendAdded));
                Unsubscribe(nameof(OnFriendRemoved));
            }
        }

        private void OnNewSave(string filename)
        {
            if (!config.ClearDataOnWipe) return;

            CreateData();
        }

        private void OnServerShutdown()
        {
            FlushDataSave();
            CancelScheduledDataSave();
        }

        private void OnServerSave()
        {
            FlushDataSave();
        }

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (dataDirty)
            {
                ScheduleDataSave();
            }
        }

        private void OnPlayerConnected(BasePlayer player)
        {
            GetPlayerData(player.UserIDString);

            if (config.ReceiveMessagesEnabled)
            {
                timer.Once(1f, () =>
                {
                    if (player != null && player.IsConnected)
                    {
                        DisplayPendingNotifications(player);
                    }
                });
            }
        }

        private void OnItemAction(Item item, string action, BasePlayer player)
        {
            if (!config.ItemSharingEnabled) return;
            if (player == null || item == null) return;
            if (action != "study") return;

            DebugLog($"{player.displayName} Started Blueprint Item Unlock For {item.blueprintTargetDef.displayName.translated}");

            if (TryShareBlueprint(item.blueprintTargetDef, player))
            {
                item.Remove();
            }
        }

        private void OnTechTreeNodeUnlock(Workbench workbench, TechTreeData.NodeInstance node, BasePlayer player)
        {
            if (!config.TechTreeSharingEnabled || workbench == null || node?.itemDef == null || player == null) return;
            if (!workbench.HasTechTreeBypassUpgrade()) return;

            var key = GetTechTreeUnlockKey(player.userID, node.itemDef.itemid);

            timer.Once(0.1f, () =>
            {
                if (player == null || !player.IsConnected || node?.itemDef == null) return;
                if (!player.blueprints.HasUnlocked(node.itemDef)) return;
                if (normalTechTreeUnlocks.Contains(key)) return;

                HandleTechTreeUnlock(player, new[] { node.itemDef });
            });
        }

        private void OnTechTreeNodeUnlocked(Workbench workbench, TechTreeData.NodeInstance node, BasePlayer player, PooledList<ItemDefinition> items)
        {
            if (!config.TechTreeSharingEnabled || player == null || node?.itemDef == null || items == null) return;

            var key = GetTechTreeUnlockKey(player.userID, node.itemDef.itemid);
            normalTechTreeUnlocks.Add(key);

            HandleTechTreeUnlock(player, items);

            timer.Once(1f, () => normalTechTreeUnlocks.Remove(key));
        }

        private void HandleTechTreeUnlock(BasePlayer player, IEnumerable<ItemDefinition> items)
        {
            if (player == null || items == null) return;

            foreach (var item in items)
            {
                if (item == null) continue;

                DebugLog($"{player.displayName} Started TechTree Unlock For {item.displayName.translated}");

                TryShareBlueprint(item, player);
            }
        }

        private static string GetTechTreeUnlockKey(ulong playerId, int itemId) => $"{playerId}:{itemId}";

        private void OnTeamAcceptInvite(RelationshipManager.PlayerTeam team, BasePlayer joiningPlayer)
        {
            if (!config.ShareToExistingMembers && !config.ShareToNewMembers) return;
            if (team == null || joiningPlayer == null) return;

            DebugLog($"{joiningPlayer.displayName} joined a team");

            timer.Once(1f, () =>
            {
                var teamMemberIds = team.members;

                if (teamMemberIds == null || teamMemberIds.Count == 0) return;

                var teamMembers = FindPlayersByIds(teamMemberIds, joiningPlayer.userID);

                if (teamMembers.Count == 0) return;

                // Share FROM joining player TO each team member
                if (config.ShareToExistingMembers)
                {
                    foreach (var member in teamMembers)
                    {
                        if (member == null || member == joiningPlayer) continue;
                        ShareWithPlayer(joiningPlayer, member); // member receives from joiner
                    }
                }

                // Share FROM each team member TO joining player
                if (config.ShareToNewMembers)
                {
                    foreach (var member in teamMembers)
                    {
                        if (member == null || member == joiningPlayer) continue;
                        ShareWithPlayer(member, joiningPlayer); // joiner receives from member
                    }
                }
            });
        }

        private void OnTeamLeave(RelationshipManager.PlayerTeam team, BasePlayer player)
        {
            DebugLog($"{player.displayName} left their team");

            ClearShareTargetCache(player.userID);
            PlayerLeftTeam(player.userID);
        }

        private void OnTeamKick(RelationshipManager.PlayerTeam team, BasePlayer player, ulong targetId)
        {
            DebugLog($"{targetId} was kicked from their team by {player.displayName}");

            ClearShareTargetCaches(player.userID, targetId);
            PlayerLeftTeam(targetId);
        }

        #endregion

        #region External Plugins

        #region Friends

        private bool HasFriends(ulong playerId)
        {
            if (Friends == null || !Friends.IsLoaded) return false;

            var friendsList = Friends.Call<ulong[]>("GetFriends", playerId);

            return friendsList != null && friendsList.Length != 0;
        }

        private List<ulong> GetFriends(ulong playerId)
        {
            if (Friends == null || !Friends.IsLoaded) return new List<ulong>();

            var friends = Friends.Call<ulong[]>("GetFriends", playerId);

            return friends.ToList();
        }

        private bool IsFriend(ulong playerId, ulong targetId)
        {
            return Friends != null && Friends.IsLoaded && Friends.Call<bool>("HasFriend", playerId, targetId);
        }

        private bool AreMutualFriends(ulong playerId, ulong targetId)
        {
            return Friends != null && Friends.IsLoaded && Friends.Call<bool>("AreFriends", playerId, targetId);
        }

        #endregion

        #region Clan

        private bool InClan(ulong playerId)
        {
            if (Clans == null || !Clans.IsLoaded) return false;

            var clanName = Clans.Call<string>("GetClanOf", playerId);

            return !string.IsNullOrEmpty(clanName);
        }

        private List<ulong> GetClanMembers(ulong playerId)
        {
            var memberIds = new HashSet<ulong>();

            if (Clans == null || !Clans.IsLoaded) return memberIds.ToList();

            var currentApiMembers = Clans.Call("GetClanMembers", playerId);

            if (currentApiMembers is IEnumerable<ulong> ulongMembers)
            {
                memberIds.UnionWith(ulongMembers);
            }
            else if (currentApiMembers is IEnumerable<string> stringMembers)
            {
                foreach (var memberId in stringMembers)
                {
                    if (ulong.TryParse(memberId, out var parsedMemberId))
                    {
                        memberIds.Add(parsedMemberId);
                    }
                }
            }

            if (memberIds.Count > 0) return memberIds.ToList();

            // Compatibility fallback for older Clans implementations.
            var clanTag = Clans.Call<string>("GetClanOf", playerId);

            if (string.IsNullOrEmpty(clanTag)) return memberIds.ToList();

            var clanInfo = Clans.Call<JObject>("GetClan", clanTag);

            if (clanInfo?["members"] == null) return memberIds.ToList();

            foreach (var memberToken in clanInfo["members"])
            {
                if (ulong.TryParse(memberToken.ToString(), out var parsedMemberId))
                {
                    memberIds.Add(parsedMemberId);
                }
            }

            return memberIds.ToList();
        }

        private bool SameClan(ulong playerId, ulong targetId)
        {
            if (Clans == null || !Clans.IsLoaded) return false;

            var currentApiResult = Clans.Call("IsClanMember", playerId, targetId);

            if (currentApiResult is bool isClanMember && isClanMember) return true;

            // Compatibility fallback for older Clans implementations.
            var playerClanTag = Clans.Call<string>("GetClanOf", playerId);

            if (string.IsNullOrEmpty(playerClanTag)) return false;

            var targetClanTag = Clans.Call<string>("GetClanOf", targetId);

            if (string.IsNullOrEmpty(targetClanTag)) return false;

            return playerClanTag == targetClanTag;
        }

        #endregion

        #region Team

        private bool InTeam(ulong playerId)
        {
            if (!RelationshipManager.TeamsEnabled()) return false;

            var playersTeam = RelationshipManager.ServerInstance.FindPlayersTeam(playerId);

            return playersTeam != null && playersTeam.members.Count > 1;
        }

        private List<ulong> GetTeamMembers(ulong playerId)
        {
            if (!RelationshipManager.TeamsEnabled()) return new List<ulong>();

            var playersTeam = RelationshipManager.ServerInstance.FindPlayersTeam(playerId);

            return playersTeam?.members;
        }

        private bool SameTeam(ulong playerId, ulong targetId)
        {
            if (!RelationshipManager.TeamsEnabled()) return false;

            var playerTeam = RelationshipManager.ServerInstance.FindPlayersTeam(playerId);

            if (playerTeam == null) return false;

            var targetTeam = RelationshipManager.ServerInstance.FindPlayersTeam(targetId);

            if (targetTeam == null) return false;

            return playerTeam == targetTeam;
        }

        #endregion

        #endregion

        #region External Plugin Hooks

        private void OnFriendAdded(string playerIdStr, string friendIdStr)
        {
            if (!ulong.TryParse(playerIdStr, out var playerId) || !ulong.TryParse(friendIdStr, out var friendId)) return;

            ClearShareTargetCaches(playerId, friendId);

            if (!config.ShareToExistingMembers && !config.ShareToNewMembers) return;

            // Only proceed if both players consider each other friends
            if (!AreMutualFriends(playerId, friendId))
            {
                DebugLog($"Not sharing blueprints between {GetPlayerName(playerId)} and {GetPlayerName(friendId)} — friendship is not mutual.");
                return;
            }

            var player = RustCore.FindPlayerById(playerId);
            var friend = RustCore.FindPlayerById(friendId);

            if (player == null || friend == null) return;

            DebugLog($"{player.displayName} added {friend.displayName} as a friend");

            // Share FROM new friend TO player
            if (config.ShareToExistingMembers)
            {
                ShareWithPlayer(friend, player); // player receives blueprints from friend
            }

            // Share FROM player TO new friend
            if (config.ShareToNewMembers)
            {
                ShareWithPlayer(player, friend); // friend receives blueprints from player
            }
        }

        private void OnFriendRemoved(string playerIdStr, string friendIdStr)
        {
            if (!ulong.TryParse(playerIdStr, out var playerId) || !ulong.TryParse(friendIdStr, out var friendId)) return;

            ClearShareTargetCaches(playerId, friendId);

            if (!config.LoseBlueprintsOnLeave) return;

            DebugLog($"{GetPlayerName(playerId)} has removed {GetPlayerName(friendId)} as a friend");

            var stillFriends = IsFriend(friendId, playerId);

            if (stillFriends)
            {
                DebugLog($"{GetPlayerName(playerId)} removed {GetPlayerName(friendId)}, but they are still mutual friends. No blueprints removed.");
                return;
            }

            // If not mutual anymore, remove shared blueprints in both directions
            var playerToFriend = GetSharedBlueprints(playerIdStr, ShareType.Friends, friendIdStr);
            var friendToPlayer = GetSharedBlueprints(friendIdStr, ShareType.Friends, playerIdStr);

            if (playerToFriend.Count > 0)
            {
                RemoveBlueprints(playerId, playerToFriend, ShareType.Friends, friendIdStr);
            }

            if (friendToPlayer.Count > 0)
            {
                RemoveBlueprints(friendId, friendToPlayer, ShareType.Friends, playerIdStr);
            }
        }

        private void OnClanMemberJoined(string tag, ulong joining, List<ulong> members)
        {
            HandleClanMemberJoined(tag, joining, members);
        }

        // Compatibility with older Clans hook signatures.
        private void OnClanMemberJoined(ulong playerId, string clanName)
        {
            HandleClanMemberJoined(clanName, playerId, null);
        }

        private void OnClanMemberJoined(string tag, string joining, List<string> members)
        {
            if (!ulong.TryParse(joining, out var joiningId)) return;

            HandleClanMemberJoined(tag, joiningId, members?.Select(member =>
            {
                ulong.TryParse(member, out var memberId);
                return memberId;
            }).Where(memberId => memberId != 0UL));
        }

        private void HandleClanMemberJoined(string clanName, ulong playerId, IEnumerable<ulong> memberIds)
        {
            var clanMemberIds = memberIds?.Where(memberId => memberId != 0UL).Distinct().ToList() ?? GetClanMembers(playerId);

            ClearShareTargetCache(playerId);
            ClearShareTargetCaches(clanMemberIds.ToArray());

            if (!config.ShareToExistingMembers && !config.ShareToNewMembers) return;

            var player = RustCore.FindPlayerById(playerId);

            if (player == null) return;

            DebugLog($"{player.displayName} joined the {clanName} clan");

            if (clanMemberIds == null || clanMemberIds.Count == 0) return;

            var clanMembers = FindPlayersByIds(clanMemberIds, playerId);

            if (clanMembers.Count == 0) return;

            foreach (var clanMember in clanMembers)
            {
                if (clanMember == null || clanMember == player) continue;

                // Share FROM joining player TO existing member
                if (config.ShareToExistingMembers)
                {
                    ShareWithPlayer(player, clanMember); // clan member receives from new player
                }

                // Share FROM existing member TO joining player
                if (config.ShareToNewMembers)
                {
                    ShareWithPlayer(clanMember, player); // new player receives from clan member
                }
            }
        }

        private void OnClanMemberGone(string tag, ulong playerId, List<ulong> members)
        {
            HandleClanMemberGone(tag, playerId, members);
        }

        // Compatibility with older Clans hook signatures.
        private void OnClanMemberGone(ulong playerId, string tag)
        {
            HandleClanMemberGone(tag, playerId, null);
        }

        private void OnClanMemberGone(string tag, string playerId, List<string> members)
        {
            if (!ulong.TryParse(playerId, out var parsedPlayerId)) return;

            HandleClanMemberGone(tag, parsedPlayerId, members?.Select(member =>
            {
                ulong.TryParse(member, out var memberId);
                return memberId;
            }).Where(memberId => memberId != 0UL));
        }

        private void HandleClanMemberGone(string tag, ulong playerId, IEnumerable<ulong> memberIds)
        {
            ClearShareTargetCache(playerId);
            ClearShareTargetCaches(memberIds?.ToArray() ?? Array.Empty<ulong>());

            if (!config.LoseBlueprintsOnLeave) return;

            var learntBlueprints = GetSharedBlueprints(playerId.ToString(), ShareType.Clans);

            if (learntBlueprints.Count == 0) return;

            DebugLog($"{GetPlayerName(playerId)} left the {tag} clan");

            RemoveBlueprints(playerId, learntBlueprints, ShareType.Clans);
        }

        private void OnClanDisbanded(string tag, List<ulong> memberIds)
        {
            HandleClanDisbanded(memberIds?.Select(memberId => memberId.ToString()));
        }

        // Compatibility with older Clans hook signatures.
        private void OnClanDisbanded(List<string> memberIds)
        {
            HandleClanDisbanded(memberIds);
        }

        private void OnClanDisbanded(string tag, List<string> memberIds)
        {
            HandleClanDisbanded(memberIds);
        }

        private void HandleClanDisbanded(IEnumerable<string> memberIds)
        {
            if (memberIds == null) return;

            var members = memberIds.ToList();
            ClearShareTargetCaches(members
                .Select(memberId => ulong.TryParse(memberId, out var parsedMemberId) ? parsedMemberId : 0UL)
                .Where(memberId => memberId != 0UL)
                .ToArray());

            if (!config.LoseBlueprintsOnLeave) return;

            int processedPlayers = 0;

            foreach (var memberId in members)
            {
                var sharedBlueprints = GetSharedBlueprints(memberId, ShareType.Clans);
                if (sharedBlueprints == null || sharedBlueprints.Count == 0)
                {
                    continue;
                }

                if (ulong.TryParse(memberId, out var playerId))
                {
                    RemoveBlueprints(playerId, sharedBlueprints, ShareType.Clans);
                    processedPlayers++;
                }
                else
                {
                    // Fallback: if we somehow can't parse the player id, at least clear our tracking data
                    RemoveBlueprintsFromDatabase(ShareType.Clans, memberId, string.Empty);
                }
            }

            DebugLog($"Removed clan blueprints for {processedPlayers} player(s) on clan disband");
        }

        #endregion

        #region Core

        private bool TryShareBlueprint(ItemDefinition item, BasePlayer player)
        {
            if (item == null || player == null) return false;

            if (!permission.UserHasPermission(player.UserIDString, usePermission)) return false;

            if (BlueprintBlocked(item))
            {
                SendMessage("BlueprintBlocked", player, true, item.displayName.translated);

                return false;
            }

            var playerId = player.userID;

            if (SharingEnabled(player.UserIDString) && HasSocialConnections(playerId) && SomeoneWillLearnBlueprint(playerId, item))
            {
                var targetIds = GetCachedShareTargets(playerId);
                
                ShareBlueprint(player, targetIds, item);

                return true;
            }

            return false;
        }

        private static class ListPool<T>
        {
            private static readonly Stack<List<T>> pool = new Stack<List<T>>();

            public static List<T> Get()
            {
                return pool.Count > 0 ? pool.Pop() : new List<T>();
            }

            public static void Free(List<T> list)
            {
                list.Clear();
                pool.Push(list);
            }
        }

        private class UnlockTask
        {
            public ulong TargetId;
            public ulong SharerId;
            public List<int> Blueprints = ListPool<int>.Get();
        }

        private bool QueueBlueprintUnlock(ulong playerId, ulong sharerId, int blueprintId, List<int> unlockQueue)
        {
            if (unlockQueue == null || unlockQueue.Contains(blueprintId)) return false;

            var playerInfo = ServerMgr.Instance.persistance.GetPlayerInfo(playerId);

            if (playerInfo == null) return false;

            var unlockedItems = playerInfo.unlockedItems;

            if (unlockedItems == null) return false;
            if (unlockedItems.Contains(blueprintId)) return false;

            unlockQueue.Add(blueprintId);

            DebugLog($"Added {GetItemNameById(blueprintId)} to unlock queue for {GetPlayerName(playerId)}");

            if (config.LoseBlueprintsOnLeave)
            {
                AddBlueprintToDatabase(playerId, sharerId, blueprintId);
            }

            return true;
        }

        private int ProcessQueuedBlueprintUnlocks(ulong playerId, ulong sharerId, List<int> unlockQueue)
        {
            if (unlockQueue.Count == 0) return 0;

            var persistance = ServerMgr.Instance.persistance;

            if (persistance == null) return 0;

            var playerInfo = persistance.GetPlayerInfo(playerId);

            if (playerInfo == null) return 0;

            playerInfo.unlockedItems.AddRange(unlockQueue);

            persistance.SetPlayerInfo(playerId, playerInfo);

            var player = RustCore.FindPlayerById(playerId);

            var isOnline = player != null && player.net?.connection != null && player.net.connection.connected;

            if (isOnline)
            {
                foreach (var blueprint in unlockQueue)
                {
                    if (!player.PersistantPlayerInfo.unlockedItems.Contains(blueprint))
                    {
                        player.PersistantPlayerInfo.unlockedItems.Add(blueprint);
                    }
                }

                player.ClientRPC(RpcTarget.Player("UnlockedBlueprint", player), 0);
                player.stats.Add("blueprint_studied", unlockQueue.Count);
                player.SendNetworkUpdateImmediate();

                PlaySoundEffect(player);
            }
            else
            {
                AddPendingNotification(playerId, sharerId, unlockQueue);
            }

            DebugLog($"Unlocked {unlockQueue.Count} blueprint(s) for {GetPlayerName(playerId)}");

            return unlockQueue.Count;
        }

        private void ShareBlueprint(BasePlayer sharer, List<ulong> targetIds, ItemDefinition item)
        {
            var blueprintId = item.itemid;
            var sharedCount = 0;
            var tasks = new List<UnlockTask>();

            DebugLog($"{sharer.displayName} is Sharing {GetItemNameById(blueprintId)} with {targetIds.Count()} player(s)");

            foreach (var targetId in targetIds)
            {
                if (targetId == sharer.userID) continue;

                var task = new UnlockTask
                {
                    TargetId = targetId,
                    SharerId = sharer.userID
                };

                var additionalUnlocks = item.Blueprint?.additionalUnlocks;
                if (additionalUnlocks != null)
                {
                    foreach (var blueprint in additionalUnlocks)
                    {
                        if (blueprint == null) continue;
                        QueueBlueprintUnlock(targetId, sharer.userID, blueprint.itemid, task.Blueprints);
                    }
                }

                QueueBlueprintUnlock(targetId, sharer.userID, blueprintId, task.Blueprints);

                sharedCount++;

                if (task.Blueprints.Count > 0)
                {
                    tasks.Add(task);
                }
                else
                {
                    ListPool<int>.Free(task.Blueprints);
                }
            }

            foreach (var task in tasks)
            {
                ProcessQueuedBlueprintUnlocks(task.TargetId, task.SharerId, task.Blueprints);

                var target = RustCore.FindPlayerById(task.TargetId);

                if (target != null && config.ReceiveMessagesEnabled)
                {
                    SendMessage("TargetLearntBlueprint", target, true, sharer.displayName, item.displayName.translated);
                }

                ListPool<int>.Free(task.Blueprints);
            }

            if (sharedCount > 0)
            {
                if (config.ShareMessagesEnabled)
                {
                    SendMessage("PlayerSharedBlueprint", sharer, true, item.displayName.translated, sharedCount);
                }

                if (config.LoseBlueprintsOnLeave)
                {
                    MarkDataDirty();
                }
            }
        }

        private void ShareWithPlayer(BasePlayer sharer, BasePlayer target)
        {
            if (sharer == null || target == null) return;

            DebugLog($"{sharer.displayName} started sharing their blueprints with {target.displayName}");

            var playerId = sharer.userID;
            var targetId = target.userID;

            if (!SharingEnabled(target.UserIDString))
            {
                SendMessage("TargetSharingDisabled", sharer, true, target.displayName);

                return;
            }

            if (!SameTeam(playerId, targetId) && !SameClan(playerId, targetId) && !AreMutualFriends(playerId, targetId) && !permission.UserHasPermission(sharer.UserIDString, bypassPermission))
            {
                SendMessage("CannotShare", sharer, true);

                return;
            }

            var filteredBlueprints = RemoveBlockedBlueprints(sharer.PersistantPlayerInfo.unlockedItems);

            if (filteredBlueprints.Count == 0)
            {
                SendMessage("NoBlueprintsToShare", sharer, true, target.displayName);

                return;
            }

            var queue = ListPool<int>.Get();

            foreach (var blueprintId in filteredBlueprints)
            {
                QueueBlueprintUnlock(targetId, playerId, blueprintId, queue);
            }

            var unlocked = ProcessQueuedBlueprintUnlocks(targetId, playerId, queue);

            ListPool<int>.Free(queue);

            if (unlocked > 0)
            {
                if (config.LoseBlueprintsOnLeave)
                {
                    MarkDataDirty();
                }

                if (config.ShareMessagesEnabled)
                {
                    SendMessage("PlayerSharedBlueprints", sharer, true, unlocked, target.displayName);
                }

                if (config.ReceiveMessagesEnabled)
                {
                    SendMessage("TargetLearntBlueprints", target, true, sharer.displayName, unlocked);
                }
            }
            else
            {
                SendMessage("NoBlueprintsToShare", sharer, true, target.displayName);
            }
        }

        private bool HasSocialConnections(ulong playerId)
        {
            return InTeam(playerId) || InClan(playerId) || HasFriends(playerId);
        }

        private List<int> RemoveBlockedBlueprints(List<int> blueprints)
        {
            return blueprints
                .Select(ItemManager.FindItemDefinition)
                .Where(item => !BlueprintBlocked(item))
                .Select(item => item.itemid)
                .ToList();
        }

        private bool SomeoneWillLearnBlueprint(ulong playerId, ItemDefinition item)
        {
            if (item == null) return false;

            var targetIds = GetCachedShareTargets(playerId);

            if (targetIds.Count == 0) return false;

            var additionalUnlocks = item.Blueprint?.additionalUnlocks;

            foreach (var targetId in targetIds)
            {
                if (PlayerWouldLearnBlueprint(targetId, item.itemid))
                {
                    return true;
                }

                if (additionalUnlocks == null) continue;

                foreach (var blueprintItem in additionalUnlocks)
                {
                    if (blueprintItem == null) continue;

                    if (PlayerWouldLearnBlueprint(targetId, blueprintItem.itemid))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private bool PlayerWouldLearnBlueprint(ulong playerId, int blueprintId)
        {
            var playerInfo = ServerMgr.Instance.persistance.GetPlayerInfo(playerId);

            if (playerInfo == null) return false;

            var unlockedItems = playerInfo.unlockedItems;

            if (unlockedItems.Contains(blueprintId)) return false;

            return true;
        }

        private List<ulong> GetPlayerIdsToShareWith(ulong playerId) 
        {
            var ids = new HashSet<ulong>();

            if (isClansEnabled && InClan(playerId))
            {
                var clanMembers = GetClanMembers(playerId);

                if (clanMembers != null)
                {
                    ids.UnionWith(clanMembers);
                }
            }

            if (isFriendsEnabled && HasFriends(playerId))
            {
                var mutualFriends = GetFriends(playerId).Where(friendId => AreMutualFriends(playerId, friendId));

                ids.UnionWith(mutualFriends);
            }

            if (isTeamsEnabled && InTeam(playerId))
            {
                var teamMembers = GetTeamMembers(playerId);

                if (teamMembers != null)
                {
                    ids.UnionWith(teamMembers);
                }
            }

            ids.Remove(playerId);

            return ids.ToList();
        }

        private List<ulong> GetCachedShareTargets(ulong playerId)
        {
            if (shareTargetCache == null)
            {
                shareTargetCache = new Dictionary<ulong, List<ulong>>();
            }

            if (shareTargetCache.TryGetValue(playerId, out var cached))
            {
                return cached;
            }

            var targetIds = GetPlayerIdsToShareWith(playerId);

            if (targetIds == null)
            {
                targetIds = Enumerable.Empty<ulong>().ToList();
            }

            var freshTargets = new List<ulong>();

            foreach (var targetId in targetIds)
            {
                if (SharingEnabled(targetId.ToString()))
                {
                    freshTargets.Add(targetId);
                }
            }

            shareTargetCache[playerId] = freshTargets;

            if (shareTargetCacheTimers.TryGetValue(playerId, out var oldTimer))
            {
                oldTimer.Destroy();
                shareTargetCacheTimers.Remove(playerId);
            }

            shareTargetCacheTimers[playerId] = timer.Once(shareTargetCacheDuration, () =>
            {
                shareTargetCache.Remove(playerId);
                shareTargetCacheTimers.Remove(playerId);
            });

            return freshTargets;
        }

        private void ClearShareTargetCache(ulong playerId)
        {
            if (shareTargetCache == null) return;

            shareTargetCache.Remove(playerId);

            if (shareTargetCacheTimers.TryGetValue(playerId, out var existingTimer))
            {
                existingTimer.Destroy();
                shareTargetCacheTimers.Remove(playerId);
            }
        }

        private void ClearShareTargetCaches(params ulong[] playerIds)
        {
            if (playerIds == null || playerIds.Length == 0) return;

            foreach (var playerId in playerIds)
            {
                if (playerId == 0) continue;
                ClearShareTargetCache(playerId);
            }
        }

        private void AddBlueprintToDatabase(ulong playerId, ulong sharerId, int blueprint)
        {
            var playerIdStr = playerId.ToString();
            var sharerIdStr = sharerId.ToString();
            var changed = false;

            if (isTeamsEnabled && SameTeam(playerId, sharerId))
            {
                var list = GetSharedBlueprints(playerIdStr, ShareType.Teams);

                if (!list.Contains(blueprint))
                {
                    list.Add(blueprint);
                    changed = true;

                    DebugLog($"Added {GetItemNameById(blueprint)} to team blueprints for {GetPlayerName(playerId)}");
                }
            }

            if (isClansEnabled && SameClan(playerId, sharerId))
            {
                var list = GetSharedBlueprints(playerIdStr, ShareType.Clans);

                if (!list.Contains(blueprint))
                {
                    list.Add(blueprint);
                    changed = true;

                    DebugLog($"Added {GetItemNameById(blueprint)} to clan blueprints for {GetPlayerName(playerId)}");
                }
            }

            if (isFriendsEnabled && IsFriend(sharerId, playerId))
            {
                var list = GetSharedBlueprints(playerIdStr, ShareType.Friends, sharerIdStr);

                if (!list.Contains(blueprint))
                {
                    list.Add(blueprint);
                    changed = true;

                    DebugLog($"Added {GetItemNameById(blueprint)} to friend {GetPlayerName(sharerId)} blueprints for {GetPlayerName(playerId)}");
                }
            }

            if (changed)
            {
                MarkDataDirty();
            }
        }

        private void RemoveBlueprintsFromDatabase(ShareType type, string playerId, string friendId)
        {
            var list = GetSharedBlueprints(playerId, type, friendId);

            list.Clear();

            MarkDataDirty();

            DebugLog($"Removed {type} blueprints for {GetPlayerName(ulong.Parse(playerId))} from database");
        }

        private void RemoveBlueprints(ulong playerId, HashSet<int> blueprintIds, ShareType type, string friendId = "")
        {
            if (blueprintIds == null || blueprintIds.Count == 0) return;

            var playerInfo = ServerMgr.Instance.persistance.GetPlayerInfo(playerId);
            
            if (playerInfo == null) return;

            var player = RustCore.FindPlayerById(playerId);
            int blueprintsRemoved = 0;

            foreach (var blueprintId in blueprintIds)
            {
                if (!playerInfo.unlockedItems.Contains(blueprintId)) continue;

                playerInfo.unlockedItems.Remove(blueprintId);
                blueprintsRemoved++;

                DebugLog($"Removed blueprint {GetItemNameById(blueprintId)} from {GetPlayerName(playerId)}");

                // If player is online, remove from their in-memory list too
                if (player != null && player.PersistantPlayerInfo.unlockedItems.Contains(blueprintId))
                {
                    player.PersistantPlayerInfo.unlockedItems.Remove(blueprintId);
                }
            }

            if (blueprintsRemoved == 0) return;

            ServerMgr.Instance.persistance.SetPlayerInfo(playerId, playerInfo);

            RemoveBlueprintsFromDatabase(type, playerId.ToString(), friendId);

            if (player != null)
            {
                player.SendNetworkUpdateImmediate();
                SendMessage("BlueprintsRemoved", player, true, blueprintsRemoved);
            }
        }

        private void PlayerLeftTeam(ulong playerId)
        {
            if (!config.LoseBlueprintsOnLeave) return;

            var sharedBlueprints = GetSharedBlueprints(playerId.ToString(), ShareType.Teams);

            if (sharedBlueprints.Count == 0) return;

            RemoveBlueprints(playerId, sharedBlueprints, ShareType.Teams);
        }

        private void AddPendingNotification(ulong playerId, ulong sharerId, List<int> blueprints)
        {
            if (!config.ReceiveMessagesEnabled || blueprints == null || blueprints.Count == 0) return;

            if (isTeamsEnabled && SameTeam(playerId, sharerId))
            {
                AddPendingNotificationType(playerId, blueprints, ShareType.Teams);
            }
            else if (isClansEnabled && SameClan(playerId, sharerId))
            {
                AddPendingNotificationType(playerId, blueprints, ShareType.Clans);
            }
            else if (isFriendsEnabled && IsFriend(sharerId, playerId))
            {
                AddPendingNotificationType(playerId, blueprints, ShareType.Friends);
            }
        }

        private void AddPendingNotificationType(ulong playerId, IEnumerable<int> blueprintIds, ShareType shareType)
        {
            var playerData = GetPlayerData(playerId.ToString());

            if (playerData.PendingNotifications == null)
            {
                playerData.PendingNotifications = new PendingNotificationsData();
            }

            var target = shareType switch
            {
                ShareType.Teams => playerData.PendingNotifications.Team,
                ShareType.Clans => playerData.PendingNotifications.Clan,
                ShareType.Friends => playerData.PendingNotifications.Friend,
                _ => null
            };

            if (target == null) return;

            var added = false;

            foreach (var blueprintId in blueprintIds)
            {
                if (target.Contains(blueprintId)) continue;

                target.Add(blueprintId);
                added = true;
            }

            if (added)
            {
                MarkDataDirty();
            }
        }

        private void DisplayPendingNotifications(BasePlayer player)
        {
            if (!config.ReceiveMessagesEnabled || player == null || !player.IsConnected) return;

            var pending = GetPlayerData(player.UserIDString).PendingNotifications;

            if (pending == null) return;

            pending.InitializeAfterLoad();

            var notifications = new Dictionary<ShareType, List<int>>
            {
                [ShareType.Teams] = pending.Team,
                [ShareType.Clans] = pending.Clan,
                [ShareType.Friends] = pending.Friend
            };
            var totalBlueprints = notifications.Sum(entry => entry.Value.Count);

            // Stay quiet when nothing was shared while the player was offline.
            if (totalBlueprints == 0) return;

            SendMessage("PendingNotifications", player, true, totalBlueprints);

            foreach (var entry in notifications)
            {
                if (entry.Value.Count == 0) continue;

                var blueprintNames = entry.Value
                    .Select(GetItemNameById)
                    .Where(name => !string.IsNullOrEmpty(name))
                    .ToList();

                if (blueprintNames.Count > 0)
                {
                    SendMessage("ShowOfflineBlueprints", player, false, GetShareTypeLabel(entry.Key), string.Join(", ", blueprintNames));
                }

                entry.Value.Clear();
            }

            MarkDataDirty();
        }

        private static string GetShareTypeLabel(ShareType shareType)
        {
            return shareType switch
            {
                ShareType.Teams => "Team",
                ShareType.Clans => "Clan",
                ShareType.Friends => "Friend",
                _ => shareType.ToString()
            };
        }

        #endregion

        #region Utility

        private void PlaySoundEffect(BasePlayer player)
        {
            if (player == null) return;

            var soundEffect = new Effect("assets/prefabs/deployable/research table/effects/research-success.prefab", player.transform.position, Vector3.zero);

            if (soundEffect == null) return;

            EffectNetwork.Send(soundEffect, player.net.connection);
        }

        private List<BasePlayer> FindPlayersByIds(IEnumerable<ulong> ids, ulong excludeId = 0)
        {
            return ids
                .Where(id => id != excludeId)
                .Select(RustCore.FindPlayerById)
                .Where(player => player != null)
                .Distinct()
                .ToList();
        }

        private bool TryGetOtherPlayer(BasePlayer sender, string name, out BasePlayer target)
        {
            target = RustCore.FindPlayerByName(name);

            if (target == null)
            {
                SendMessage("PlayerNotFound", sender, true);

                return false;
            }

            if (target == sender)
            {
                SendMessage("TargetEqualsPlayer", sender, true);

                return false;
            }

            return true;
        }

        private bool BlueprintBlocked(ItemDefinition item) => item == null || config.BlockedItems.Contains(item.shortname);

        private Dictionary<int, List<string>> GroupBlueprintsByWorkbenchLevel(HashSet<int> sharedBlueprints)
        {
            var groupedBlueprints = new Dictionary<int, List<string>>();

            foreach (var item in sharedBlueprints.Select(ItemManager.FindItemDefinition).Where(i => i != null))
            {
                var tier = item.Blueprint.workbenchLevelRequired;

                if (!groupedBlueprints.TryGetValue(tier, out var list))
                {
                    list = Pool.Get<List<string>>();

                    groupedBlueprints[tier] = list;
                }

                list.Add(item.displayName.translated);
            }

            return groupedBlueprints;
        }

        private void DisplayLearntBlueprints(BasePlayer player, ShareType type, string friendId = "")
        {
            if (player == null) return;

            var sharedBlueprints = GetSharedBlueprints(player.UserIDString, type, friendId);

            if (sharedBlueprints == null || sharedBlueprints.Count == 0)
            {
                SendMessage("NoSharedBlueprints", player, true);

                return;
            }

            var groupedBlueprints = GroupBlueprintsByWorkbenchLevel(sharedBlueprints);

            SendMessage("SharedBlueprintsTitle", player, true);

            foreach (var kvp in groupedBlueprints.OrderBy(k => k.Key))
            {
                var tier = kvp.Key;
                var names = kvp.Value;

                var msg = GetLangValue($"ShowSharedBlueprints", player.UserIDString, tier.ToString(), string.Join(", ", names));

                player.ChatMessage(msg);                
            }

            FreeGroupedBlueprints(groupedBlueprints);
        }

        private void FreeGroupedBlueprints(Dictionary<int, List<string>> grouped)
        {
            foreach (var key in grouped.Keys.ToList())
            {
                var list = grouped[key];

                Pool.FreeUnmanaged(ref list);

                grouped[key] = null;
            }
        }

        private void DebugLog(string message, DebugLevel level = DebugLevel.Info)
        {
            if (!config.Debug) return;

            string prefixColor = level switch
            {
                DebugLevel.Warning => "#f1c40f",// yellow
                DebugLevel.Error => "#e74c3c",// red
                _ => "#3498db",// blue
            };

            PrintToConsole($"<color={prefixColor}>[BlueprintShare Debug]</color> {message}");
        }

        private string GetItemNameById(int itemId)
        {
            return ItemManager.FindItemDefinition(itemId)?.displayName?.translated ?? string.Empty;
        }

        private string GetPlayerName(ulong playerId)
        {
            return RustCore.FindPlayerById(playerId)?.displayName
                ?? covalence.Players.FindPlayerById(playerId.ToString())?.Name
                ?? "Unknown";
        }

        #endregion

        #region Chat Commands

        [ChatCommand("bs")]
        private void ChatCommands(BasePlayer player, string command, string[] args)
        {
            if (args.Length == 0)
            {
                HelpCommand(player, args);

                return;
            }

            var key = args[0].ToLower();

            if (chatCommandHandlers.TryGetValue(key, out var handler))
            {
                handler(player, args);
            }
            else
            {
                SendMessage("ArgumentsError", player, true);
            }
        }

        private void ToggleCommand(BasePlayer player, string[] args)
        {
            if (!permission.UserHasPermission(player.UserIDString, togglePermi
// ... [Code preview truncated due to length. Full source in attached file] ...

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