C#:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins {
[Info("TeamShare", "Uzumi & NordicRust", "1.4.4")]
[Description("Allows automatic team auth on codelocks, turrets, and tool cupboards via the /share command. Team blueprint sharing follows Operating Mode and can be disabled in config; when Use Permissions is on, sharing requires teamshare.use and teamshare.blueprint.")]
public class TeamShare : RustPlugin {
[PluginReference]
private Plugin WelcomeController;
[PluginReference]
private Plugin NoEscape;
[PluginReference]
private Plugin Clans;
// Some servers run Clans Reborn under this plugin name.
[PluginReference]
private Plugin ClansReborn;
private class Configuration {
[JsonProperty("Use Permissions")]
public bool UsePermissions = true;
[JsonProperty("Using WelcomeController")]
public bool UsingWelcomeController = false;
[JsonProperty("Debug Mode")]
public bool DebugMode = false;
[JsonProperty("Messages")]
public Dictionary<string, string> Messages = new Dictionary<string, string> {
["NoPermission"]
= "You don't have permission to use this command.", ["InvalidCode"] = "Invalid code. Please enter a 4-digit numerical code.", ["CodeSet"] = "Code set to: {0}", ["GuestCodeSet"] = "Guest code set to: {0}", ["NoCodeSet"] = "You have not set any code. Use the UI to set a code.", ["CodeAutoLocked"] = "Code lock placed with code {0}.", ["CodeAutoLockedWithGuest"] = "Code lock placed with code {0} and guest code {1}.", ["CupboardAuthorized"] = "{0} authorized on the Tool Cupboard", ["NoEscape.RaidBlocked"] = "Cannot set codes during raid block.", ["NoEscape.CombatBlocked"] = "Cannot set codes during combat block.", ["NoGuestCodeSet"] = "Please enter a guest code first.", ["AuthedBackward"] = "Authed \"{0}\" to {1} codelocks, {2} turrets and {3} TC's", ["BlueprintSharedSharer"] = "You shared \"{0}\" with {1} teammate(s).", ["BlueprintReceivedReceiver"] = "{0} shared \"{1}\" with the team.", ["BlueprintBulkReceived"] = "Team share: you received {0} blueprint(s) from {1}.", ["BlueprintBulkFromYourLibrary"] = "Team share: {0} received {1} blueprint(s) from your library."
}
;
[JsonProperty("UI Settings")]
public UISettings UISettings = new UISettings();
[JsonProperty("Commands Settings")]
public CommandsSettings CommandsSettings = new CommandsSettings();
[JsonProperty("AddonName for WelcomeController")]
public string AddonName = "TeamShare";
[JsonProperty("NoEscape Integration")]
public NoEscapeIntegrationSettings NoEscapeIntegration = new NoEscapeIntegrationSettings();
[JsonProperty("Operating Mode")]
public int OperatingMode = 1;
[JsonProperty("Blueprint team sharing enabled")]
public bool BlueprintTeamSharingEnabled = true;
[JsonProperty("Blueprint clan sharing enabled (Clans Reborn)")]
public bool BlueprintClanSharingEnabled = true;
[JsonProperty("Blueprint share from research items (study action)")]
public bool BlueprintShareFromItems = true;
[JsonProperty("Blueprint share from tech tree unlocks")]
public bool BlueprintShareFromTechTree = true;
[JsonProperty("Blueprint share blocked item short names")]
public List<string> BlueprintShareBlockedShortNames = new List<string>();
[JsonProperty("Blueprint library resync delay seconds after learn")]
public float BlueprintLibraryResyncDelaySeconds = 3f;
}
public class CommandsSettings {
[JsonProperty("Open UI Command")]
public string OpenUI = "teamui";
[JsonProperty("Share Command")]
public string Share = "share";
}
public class UISettings {
public string UIBackgroundColor {
get;
set;
}
= "0 0 0 0.6";
public string UIPrimaryColor {
get;
set;
}
= "1 0.4 0 0.5";
public string UIDangerButtonColor {
get;
set;
}
= "1 0 0 1";
[JsonProperty("UIToggleOnColor")]
public string UIToggleOnColor {
get;
set;
}
= "0.15 0.6 0.15 1";
[JsonProperty("UIToggleOffColor")]
public string UIToggleOffColor {
get;
set;
}
= "0.6 0.15 0.15 1";
[JsonProperty("Animated Buttons")]
public bool AnimatedButtons = true;
}
public class NoEscapeIntegrationSettings {
[JsonProperty("Block During Raid Block")]
public bool BlockDuringRaidBlock = true;
[JsonProperty("Block During Combat Block")]
public bool BlockDuringCombatBlock = true;
}
private const int OPERATING_MODE_PER_PLACEMENT = 1;
private const int OPERATING_MODE_BACKWARD_AUTH = 2;
private class EntityCache {
public readonly HashSet<AutoTurret> AutoTurrets = new HashSet<AutoTurret>();
public readonly HashSet<BuildingPrivlidge> BuildingPrivlidges = new HashSet<BuildingPrivlidge>();
public readonly HashSet<CodeLock> CodeLocks = new HashSet<CodeLock>();
}
private class PlayerData {
public bool enabledCodelock {
get;
set;
}
= true;
public bool enabledCupboard {
get;
set;
}
= true;
public bool enabledTurret {
get;
set;
}
= true;
public bool enabledQuiet {
get;
set;
}
= false;
public bool enabledGuestCode {
get;
set;
}
= false;
}
private const string PERMISSION_USE = "teamshare.use";
private const string PERMISSION_CODELOCK = "teamshare.codelock";
private const string PERMISSION_CUPBOARD = "teamshare.cupboard";
private const string PERMISSION_TURRET = "teamshare.turret";
private const string PERMISSION_GUEST = "teamshare.guest";
private const string PERMISSION_BLUEPRINT = "teamshare.blueprint";
private DynamicConfigFile _playerCodesData;
private DynamicConfigFile _playerGuestCodesData;
private DynamicConfigFile _playerDataFile;
private static Configuration _pluginConfig;
private Dictionary<ulong, string> _playerCodes = new Dictionary<ulong, string>();
private Dictionary<ulong, string> _playerGuestCodes = new Dictionary<ulong, string>();
private Dictionary<ulong, PlayerData> _playerData = new Dictionary<ulong, PlayerData>();
private Dictionary<ulong, string> playerUIs = new Dictionary<ulong, string>();
private Dictionary<ulong, string> playerTempCodes = new Dictionary<ulong, string>();
private Dictionary<ulong, string> playerTempGuestCodes = new Dictionary<ulong, string>();
private Dictionary<ulong, bool> playerCodeRevealed = new Dictionary<ulong, bool>();
private Dictionary<ulong, bool> playerGuestCodeRevealed = new Dictionary<ulong, bool>();
private Dictionary<ulong, Timer> codeChangedTimers = new Dictionary<ulong, Timer>();
private Dictionary<ulong, Timer> guestCodeChangedTimers = new Dictionary<ulong, Timer>();
private Dictionary<string, Timer> animationTimers = new Dictionary<string, Timer>();
private UISettings uiSettings;
private readonly Dictionary<ulong, EntityCache> _playerEntities = new Dictionary<ulong, EntityCache>();
private DynamicConfigFile _blueprintTrackingFile;
// receiverId -> (sharerId -> blueprintIds)
private Dictionary<string, Dictionary<string, HashSet<int>>> _blueprintTeamSharedByPlayer = new Dictionary<string, Dictionary<string, HashSet<int>>>();
private bool _blueprintTrackingDirty;
private readonly Dictionary<ulong, Timer> _blueprintLibraryResyncTimers = new Dictionary<ulong, Timer>();
// receiverId -> (sharerId -> blueprintIds)
private Dictionary<string, Dictionary<string, HashSet<int>>> _blueprintClanSharedByPlayer = new Dictionary<string, Dictionary<string, HashSet<int>>>();
private DynamicConfigFile _blueprintClanTrackingFile;
private bool _blueprintClanTrackingDirty;
private readonly Dictionary<string, HashSet<ulong>> _clanMembersByTag = new Dictionary<string, HashSet<ulong>>();
private class TeamBlueprintTrackingSerialized {
public Dictionary<string, Dictionary<string, List<int>>> TeamSharedBlueprintIdsByReceiver = new Dictionary<string, Dictionary<string, List<int>>>();
}
// Backward compatible loader for old tracking format (receiverId -> blueprintIds).
private class TeamBlueprintTrackingSerializedV1 {
public Dictionary<string, List<int>> TeamSharedBlueprintIdsByPlayer = new Dictionary<string, List<int>>();
}
private class ClanBlueprintTrackingSerialized {
public Dictionary<string, Dictionary<string, List<int>>> ClanSharedBlueprintIdsByReceiver = new Dictionary<string, Dictionary<string, List<int>>>();
}
private class ToggleOption {
public string Label;
public string Command;
public bool IsOn;
public string Permission;
}
#region Hooks and Initialization
void Init() {
LoadConfigData();
EnsureConfigurationDefaults();
LoadPlayerData();
permission.RegisterPermission(PERMISSION_USE, this);
permission.RegisterPermission(PERMISSION_CODELOCK, this);
permission.RegisterPermission(PERMISSION_CUPBOARD, this);
permission.RegisterPermission(PERMISSION_TURRET, this);
permission.RegisterPermission(PERMISSION_GUEST, this);
permission.RegisterPermission(PERMISSION_BLUEPRINT, this);
lang.RegisterMessages(_pluginConfig.Messages, this);
uiSettings = _pluginConfig.UISettings;
HandleCommands();
LoadClanBlueprintTracking();
RebuildClanMemberCache();
if (!BlueprintSharingHooksEnabled()) {
Unsubscribe(nameof(OnItemAction));
Unsubscribe(nameof(OnBlueprintLearned));
Unsubscribe(nameof(OnTechTreeNodeUnlock));
Unsubscribe(nameof(OnTechTreeNodeUnlocked));
Unsubscribe(nameof(OnPlayerStudyBlueprint));
}
else {
if (!_pluginConfig.BlueprintShareFromItems) {
Unsubscribe(nameof(OnItemAction));
Unsubscribe(nameof(OnPlayerStudyBlueprint));
}
if (!_pluginConfig.BlueprintShareFromTechTree) {
Unsubscribe(nameof(OnBlueprintLearned));
Unsubscribe(nameof(OnTechTreeNodeUnlock));
Unsubscribe(nameof(OnTechTreeNodeUnlocked));
}
}
Interface.CallHook("OnWCRequestColors", _pluginConfig.AddonName);
if (_pluginConfig.DebugMode) {
Puts("TeamShare plugin initialized.");
}
}
private void LoadConfigData() {
try {
_pluginConfig = Config.ReadObject<Configuration>();
if (_pluginConfig == null) throw new Exception();
if (_pluginConfig.DebugMode) {
Puts("Configuration loaded successfully.");
}
}
catch {
PrintError("Configuration file is corrupt or missing. Creating new configuration.");
LoadDefaultConfig();
}
}
private void EnsureConfigurationDefaults() {
if (_pluginConfig.Messages == null) _pluginConfig.Messages = new Dictionary<string, string>();
foreach (var kv in new Configuration().Messages) {
if (!_pluginConfig.Messages.ContainsKey(kv.Key)) _pluginConfig.Messages[kv.Key] = kv.Value;
}
}
protected override void LoadDefaultConfig() {
_pluginConfig = new Configuration();
SaveConfig();
if (_pluginConfig.DebugMode) {
Puts("Default configuration loaded.");
}
}
protected override void SaveConfig() {
Config.WriteObject(_pluginConfig, true);
if (_pluginConfig.DebugMode) {
Puts("Configuration saved.");
}
}
private void LoadPlayerData() {
_playerCodesData = Interface.Oxide.DataFileSystem.GetDatafile("TeamShare/PlayerCodes");
_playerGuestCodesData = Interface.Oxide.DataFileSystem.GetDatafile("TeamShare/PlayerGuestCodes");
_playerDataFile = Interface.Oxide.DataFileSystem.GetDatafile("TeamShare/PlayerData");
try {
_playerCodes = _playerCodesData.ReadObject<Dictionary<ulong, string>>();
if (_pluginConfig.DebugMode) {
Puts("Player codes loaded.");
}
}
catch {
_playerCodes = new Dictionary<ulong, string>();
PrintError("Failed to load PlayerCodes data. Initialized empty dictionary.");
}
try {
_playerGuestCodes = _playerGuestCodesData.ReadObject<Dictionary<ulong, string>>();
if (_pluginConfig.DebugMode) {
Puts("Player guest codes loaded.");
}
}
catch {
_playerGuestCodes = new Dictionary<ulong, string>();
PrintError("Failed to load PlayerGuestCodes data. Initialized empty dictionary.");
}
try {
_playerData = _playerDataFile.ReadObject<Dictionary<ulong, PlayerData>>();
if (_pluginConfig.DebugMode) {
Puts("Player data loaded.");
}
}
catch {
_playerData = new Dictionary<ulong, PlayerData>();
PrintError("Failed to load PlayerData. Initialized empty dictionary.");
}
_blueprintTrackingFile = Interface.Oxide.DataFileSystem.GetDatafile("TeamShare/BlueprintTracking");
try {
_blueprintTeamSharedByPlayer = new Dictionary<string, Dictionary<string, HashSet<int>>>();
// Try v2 format first.
var raw = _blueprintTrackingFile.ReadObject<TeamBlueprintTrackingSerialized>() ?? new TeamBlueprintTrackingSerialized();
if (raw.TeamSharedBlueprintIdsByReceiver != null && raw.TeamSharedBlueprintIdsByReceiver.Count > 0) {
foreach (var receiverKvp in raw.TeamSharedBlueprintIdsByReceiver) {
var perSharer = new Dictionary<string, HashSet<int>>();
if (receiverKvp.Value != null) {
foreach (var sharerKvp in receiverKvp.Value) {
perSharer[sharerKvp.Key] = new HashSet<int>(sharerKvp.Value ?? new List<int>());
}
}
_blueprintTeamSharedByPlayer[receiverKvp.Key] = perSharer;
}
}
else {
// Fallback to v1 format. We store under sharerId "*" since we do not know the actual sharer.
var rawV1 = _blueprintTrackingFile.ReadObject<TeamBlueprintTrackingSerializedV1>() ?? new TeamBlueprintTrackingSerializedV1();
if (rawV1.TeamSharedBlueprintIdsByPlayer != null) {
foreach (var kv in rawV1.TeamSharedBlueprintIdsByPlayer) {
_blueprintTeamSharedByPlayer[kv.Key] = new Dictionary<string, HashSet<int>> {
["*"]
= new HashSet<int>(kv.Value ?? new List<int>())
}
;
}
}
}
if (_pluginConfig.DebugMode) {
Puts("Blueprint tracking data loaded.");
}
}
catch {
_blueprintTeamSharedByPlayer = new Dictionary<string, Dictionary<string, HashSet<int>>>();
PrintError("Failed to load TeamShare blueprint tracking. Initialized empty.");
}
}
private void SavePlayerData() {
_playerCodesData.WriteObject(_playerCodes);
_playerGuestCodesData.WriteObject(_playerGuestCodes);
_playerDataFile.WriteObject(_playerData);
SaveBlueprintTracking();
SaveClanBlueprintTracking();
if (_pluginConfig.DebugMode) {
Puts("Player data saved.");
}
}
private void LoadClanBlueprintTracking() {
_blueprintClanTrackingFile = Interface.Oxide.DataFileSystem.GetDatafile("TeamShare/ClanBlueprintTracking");
try {
_blueprintClanSharedByPlayer = new Dictionary<string, Dictionary<string, HashSet<int>>>();
var raw = _blueprintClanTrackingFile.ReadObject<ClanBlueprintTrackingSerialized>() ?? new ClanBlueprintTrackingSerialized();
if (raw.ClanSharedBlueprintIdsByReceiver != null && raw.ClanSharedBlueprintIdsByReceiver.Count > 0) {
foreach (var receiverKvp in raw.ClanSharedBlueprintIdsByReceiver) {
var perSharer = new Dictionary<string, HashSet<int>>();
if (receiverKvp.Value != null) {
foreach (var sharerKvp in receiverKvp.Value) {
perSharer[sharerKvp.Key] = new HashSet<int>(sharerKvp.Value ?? new List<int>());
}
}
_blueprintClanSharedByPlayer[receiverKvp.Key] = perSharer;
}
}
if (_pluginConfig.DebugMode) Puts("Clan blueprint tracking data loaded.");
}
catch {
_blueprintClanSharedByPlayer = new Dictionary<string, Dictionary<string, HashSet<int>>>();
PrintError("Failed to load Clan blueprint tracking. Initialized empty.");
}
}
private HashSet<ulong> FetchClanMembersByTag(string tag) {
var clansPlugin = GetClansPlugin();
if (clansPlugin == null) return new HashSet<ulong>();
var clan = clansPlugin.Call<JObject>("GetClan", tag);
if (clan == null) return new HashSet<ulong>();
var membersToken = clan["members"];
if (membersToken == null) return new HashSet<ulong>();
var set = new HashSet<ulong>();
if (membersToken is JArray arr) {
foreach (var t in arr) {
if (t == null) continue;
if (ulong.TryParse(t.ToString(), out var id)) set.Add(id);
}
}
return set;
}
private void RebuildClanMemberCache() {
_clanMembersByTag.Clear();
var clansPlugin = GetClansPlugin();
if (clansPlugin == null) return;
var tags = clansPlugin.Call<JArray>("GetAllClans");
if (tags == null) return;
foreach (var t in tags) {
var tag = t?.ToString();
if (string.IsNullOrEmpty(tag)) continue;
_clanMembersByTag[tag] = FetchClanMembersByTag(tag);
}
}
private PlayerData GetPlayerData(ulong userID) {
if (!_playerData.ContainsKey(userID)) {
_playerData.Add(userID, new PlayerData());
SavePlayerData();
if (_pluginConfig.DebugMode) {
Puts($"Created new PlayerData for UserID: {userID}");
}
}
return _playerData[userID];
}
private bool IsPermitted(BasePlayer player) {
if (_pluginConfig.UsePermissions && !permission.UserHasPermission(player.UserIDString, PERMISSION_USE)) {
if (_pluginConfig.DebugMode) {
Puts($"Player {player.displayName} does not have permission to use TeamShare.");
}
return false;
}
return true;
}
private bool HasBlueprintSharerPermission(ulong sharerUserId) {
if (!_pluginConfig.UsePermissions) return true;
string id = sharerUserId.ToString();
if (!permission.UserHasPermission(id, PERMISSION_USE)) return false;
return permission.UserHasPermission(id, PERMISSION_BLUEPRINT);
}
private bool CanPlayerInitiateBlueprintSharing(BasePlayer player) {
if (player == null) return false;
if (!IsPermitted(player)) return false;
return HasFeaturePermission(player, PERMISSION_BLUEPRINT);
}
private bool HasFeaturePermission(BasePlayer player, string perm) {
if (!_pluginConfig.UsePermissions) return true;
return permission.UserHasPermission(player.UserIDString, perm);
}
private bool IsValidCode(string code) {
bool isValid = code.Length == 4 && int.TryParse(code, out _);
if (_pluginConfig.DebugMode) {
Puts($"Code validation for '{code}': {isValid}");
}
return isValid;
}
private void SetPlayerCode(ulong playerId, string code) {
_playerCodes[playerId] = code;
SavePlayerData();
if (_pluginConfig.DebugMode) {
Puts($"Set main code for UserID {playerId} to {code}");
}
}
private string GetPlayerCode(ulong playerId) {
string code = _playerCodes.TryGetValue(playerId, out string value) ? value : null;
if (_pluginConfig.DebugMode) {
Puts($"Retrieved main code for UserID {playerId}: {code}");
}
return code;
}
private void SetPlayerGuestCode(ulong playerId, string code) {
_playerGuestCodes[playerId] = code;
SavePlayerData();
if (_pluginConfig.DebugMode) {
Puts($"Set guest code for UserID {playerId} to {code}");
}
}
private string GetPlayerGuestCode(ulong playerId) {
string code = _playerGuestCodes.TryGetValue(playerId, out string value) ? value : null;
if (_pluginConfig.DebugMode) {
Puts($"Retrieved guest code for UserID {playerId}: {code}");
}
return code;
}
[HookMethod("IsUsingPlugin")]
private bool IsUsingPlugin(string pluginName) {
bool isUsing = pluginName.Equals(_pluginConfig.AddonName, StringComparison.OrdinalIgnoreCase);
if (_pluginConfig.DebugMode) {
Puts($"IsUsingPlugin check for '{pluginName}': {isUsing}");
}
return isUsing;
}
[HookMethod("OnWCRequestedUIPanel")]
private void OnWCRequestedUIPanel(BasePlayer player, string panelName, string neededPlugin) {
if (!neededPlugin.Equals(_pluginConfig.AddonName, StringComparison.OrdinalIgnoreCase)) return;
if (_pluginConfig.DebugMode) {
Puts($"Received UI panel request '{panelName}' from player {player.displayName}.");
}
CreateUI(player, panelName);
}
void OnWCRequestColors(string pluginName) {
if (!pluginName.Equals(_pluginConfig.AddonName, StringComparison.OrdinalIgnoreCase)) return;
if (_pluginConfig.DebugMode) {
Puts($"Requesting theme colors for plugin '{pluginName}'.");
}
Interface.CallHook("WCSendColors", ConvertToDictionary(uiSettings), pluginName);
}
void OnWCSentThemeColors(List<string> pluginNames, Dictionary<string, string> themeColors) {
if (!pluginNames.Contains(_pluginConfig.AddonName)) return;
if (_pluginConfig.DebugMode) {
Puts($"Received theme colors for plugin '{_pluginConfig.AddonName}'.");
}
uiSettings.UIBackgroundColor = themeColors.ContainsKey("BackgroundColor") ? themeColors["BackgroundColor"] : uiSettings.UIBackgroundColor;
uiSettings.UIPrimaryColor = themeColors.ContainsKey("PrimaryButtonColor") ? themeColors["PrimaryButtonColor"] : uiSettings.UIPrimaryColor;
uiSettings.UIDangerButtonColor = themeColors.ContainsKey("SecondaryButtonColor") ? themeColors["SecondaryButtonColor"] : uiSettings.UIDangerButtonColor;
SaveConfig();
}
private static Dictionary<string, string> ConvertToDictionary(UISettings uiSettings) {
var dictionary = new Dictionary<string, string>();
foreach (var property in typeof(UISettings).GetProperties()) {
string key = property.Name;
string value = property.GetValue(uiSettings)?.ToString();
dictionary[key] = value;
}
return dictionary;
}
private void HandleCommands() {
cmd.AddChatCommand(_pluginConfig.CommandsSettings.OpenUI, this, OpenTeamShareUI);
cmd.AddChatCommand(_pluginConfig.CommandsSettings.Share, this, OpenTeamShareUI);
if (_pluginConfig.DebugMode) {
Puts($"Chat commands registered: /{_pluginConfig.CommandsSettings.OpenUI}, /{_pluginConfig.CommandsSettings.Share}");
}
}
private void OnServerSave() {
SavePlayerData();
}
private void Unload() {
foreach (var kvp in codeChangedTimers.ToList()) {
kvp.Value?.Destroy();
}
codeChangedTimers.Clear();
foreach (var kvp in guestCodeChangedTimers.ToList()) {
kvp.Value?.Destroy();
}
guestCodeChangedTimers.Clear();
foreach (var kvp in animationTimers.ToList()) {
kvp.Value?.Destroy();
}
animationTimers.Clear();
foreach (var kvp in _blueprintLibraryResyncTimers.ToList()) {
kvp.Value?.Destroy();
}
_blueprintLibraryResyncTimers.Clear();
foreach (BasePlayer player in BasePlayer.activePlayerList) {
DestroyUI(player);
}
SavePlayerData();
if (_pluginConfig?.DebugMode == true) {
Puts("TeamShare plugin unloaded. All UIs and timers destroyed, data saved.");
}
}
private void OnServerInitialized() {
if (_pluginConfig.OperatingMode != OPERATING_MODE_BACKWARD_AUTH) return;
foreach (var serverEntity in BaseNetworkable.serverEntities) {
if (serverEntity == null || serverEntity.IsDestroyed) continue;
if (serverEntity is AutoTurret autoTurret) {
if (autoTurret.OwnerID == 0) continue;
if (!_pluginConfig.UsePermissions || permission.UserHasPermission(autoTurret.OwnerID.ToString(), PERMISSION_USE)) AddEntityToCache(autoTurret.OwnerID, autoTurret);
continue;
}
if (serverEntity is BuildingPrivlidge buildingPrivlidge) {
if (buildingPrivlidge.OwnerID == 0) continue;
if (!_pluginConfig.UsePermissions || permission.UserHasPermission(buildingPrivlidge.OwnerID.ToString(), PERMISSION_USE)) AddEntityToCache(buildingPrivlidge.OwnerID, buildingPrivlidge);
continue;
}
if (serverEntity is CodeLock codeLock) {
var parent = codeLock.GetParentEntity();
ulong ownerId = codeLock.OwnerID != 0 ? codeLock.OwnerID : parent?.OwnerID ?? 0;
if (ownerId == 0) continue;
if (!_pluginConfig.UsePermissions || permission.UserHasPermission(ownerId.ToString(), PERMISSION_USE)) AddEntityToCache(ownerId, codeLock);
}
}
if (_pluginConfig.DebugMode) Puts("Mode 2: Entity cache rebuilt from server entities.");
}
#endregion
#region Entity Deployment Hooks
void OnEntitySpawned(BaseNetworkable entity) {
if (entity == null) return;
if (entity is CodeLock codelock) {
HandleCodeLockDeployment(codelock);
}
else if (entity is BuildingPrivlidge cupboard) {
HandleCupboardDeployment(cupboard);
}
else if (entity is AutoTurret turret) {
HandleAutoTurretDeployment(turret);
}
}
private void HandleCodeLockDeployment(CodeLock codelock) {
BasePlayer player = GetPermittedOwner(codelock);
if (player == null || !IsPermitted(player)) return;
if (!HasFeaturePermission(player, PERMISSION_CODELOCK)) return;
if (_pluginConfig.DebugMode) {
Puts($"Handling CodeLock deployment by {player.displayName} (UserID: {player.UserIDString})");
}
if (NoEscape != null) {
object raidBlockedResult = NoEscape.Call("IsRaidBlocked", player);
if (_pluginConfig.NoEscapeIntegration.BlockDuringRaidBlock && raidBlockedResult is bool isRaidBlocked && isRaidBlocked) {
player.ChatMessage(Lang("NoEscape.RaidBlocked", player.UserIDString));
if (_pluginConfig.DebugMode) {
Puts($"Raid block active. Deployment of CodeLock by {player.displayName} blocked.");
}
return;
}
object combatBlockedResult = NoEscape.Call("IsCombatBlocked", player);
if (_pluginConfig.NoEscapeIntegration.BlockDuringCombatBlock && combatBlockedResult is bool isCombatBlocked && isCombatBlocked) {
player.ChatMessage(Lang("NoEscape.CombatBlocked", player.UserIDString));
if (_pluginConfig.DebugMode) {
Puts($"Combat block active. Deployment of CodeLock by {player.displayName} blocked.");
}
return;
}
}
PlayerData pData = GetPlayerData(player.userID);
if (pData.enabledCodelock) {
string code = GetPlayerCode(player.userID);
if (!string.IsNullOrEmpty(code)) {
codelock.code = code;
codelock.hasCode = true;
codelock.SetFlagLocal(BaseEntity.Flags.Locked, true);
codelock.whitelistPlayers.Clear();
SetEntityWhitelist(player, codelock);
if (pData.enabledGuestCode && HasFeaturePermission(player, PERMISSION_GUEST)) {
string guestCode = GetPlayerGuestCode(player.userID);
if (!string.IsNullOrEmpty(guestCode)) {
codelock.guestCode = guestCode;
codelock.hasGuestCode = true;
codelock.guestPlayers.Clear();
}
else {
codelock.hasGuestCode = false;
}
}
else {
codelock.hasGuestCode = false;
}
codelock.SendNetworkUpdateImmediate();
if (!pData.enabledQuiet) {
List<string> authorizedNames = GetAuthorizedNames(GetTeamMembers(player));
string message = pData.enabledGuestCode && codelock.hasGuestCode ? $"Code lock placed with code \"{code}\" and guest code \"{codelock.guestCode}\" authed {string.Join(", ", authorizedNames)}" : $"Code lock placed with code \"{code}\" authed {string.Join(", ", authorizedNames)}";
SendChatMessage(player, message);
}
}
else {
if (!pData.enabledQuiet) SendChatMessage(player, Lang("NoCodeSet", player.UserIDString));
}
}
if (_pluginConfig.OperatingMode == OPERATING_MODE_BACKWARD_AUTH) {
AddEntityToCache(player.userID, codelock);
}
if (_pluginConfig.DebugMode) {
Puts($"CodeLock deployment handled for {player.displayName}.");
}
}
private void HandleCupboardDeployment(BuildingPrivlidge cupboard) {
BasePlayer player = GetPermittedOwner(cupboard);
if (player == null || !IsPermitted(player)) return;
if (!HasFeaturePermission(player, PERMISSION_CUPBOARD)) return;
if (_pluginConfig.DebugMode) {
Puts($"Handling Tool Cupboard deployment by {player.displayName} (UserID: {player.UserIDString})");
}
PlayerData pData = GetPlayerData(player.userID);
if (pData.enabledCupboard) {
SetEntityWhitelist(player, cupboard);
cupboard.SendNetworkUpdateImmediate();
if (!pData.enabledQuiet) {
List<string> authorizedNames = GetAuthorizedNames(GetTeamMembers(player));
string authedMessage = $"Authorized {string.Join(", ", authorizedNames)} on the Tool Cupboard";
SendChatMessage(player, authedMessage);
}
}
if (_pluginConfig.OperatingMode == OPERATING_MODE_BACKWARD_AUTH) {
AddEntityToCache(player.userID, cupboard);
}
if (_pluginConfig.DebugMode) {
Puts($"Tool Cupboard deployment handled for {player.displayName}.");
}
}
private void HandleAutoTurretDeployment(AutoTurret turret) {
BasePlayer player = GetPermittedOwner(turret);
if (player == null || !IsPermitted(player)) return;
if (!HasFeaturePermission(player, PERMISSION_TURRET)) return;
if (_pluginConfig.DebugMode) {
Puts($"Handling Auto Turret deployment by {player.displayName} (UserID: {player.UserIDString})");
}
PlayerData pData = GetPlayerData(player.userID);
if (pData.enabledTurret) {
SetEntityWhitelist(player, turret);
turret.SendNetworkUpdateImmediate();
if (!pData.enabledQuiet) {
List<string> authorizedNames = GetAuthorizedNames(GetTeamMembers(player));
string authedMessage = $"Authorized {string.Join(", ", authorizedNames)} on the Auto Turret";
SendChatMessage(player, authedMessage);
}
}
if (_pluginConfig.OperatingMode == OPERATING_MODE_BACKWARD_AUTH) {
AddEntityToCache(player.userID, turret);
}
if (_pluginConfig.DebugMode) {
Puts($"Auto Turret deployment handled for {player.displayName}.");
}
}
private BasePlayer GetPermittedOwner(BaseEntity entity) {
if (entity == null) return null;
BasePlayer player = BasePlayer.FindByID(entity.OwnerID);
if (player == null || !IsPermitted(player)) {
if (_pluginConfig.DebugMode) {
Puts($"Entity owner not found or lacks permission.");
}
return null;
}
if (_pluginConfig.DebugMode) {
Puts($"Permitted owner found: {player.displayName} (UserID: {player.UserIDString})");
}
return player;
}
private List<ulong> GetTeamMembers(BasePlayer player) {
if (player == null) return new List<ulong>();
List<ulong> members = new List<ulong> {
player.userID
}
;
if (player.currentTeam != 0) {
RelationshipManager.PlayerTeam team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
if (team != null) {
foreach (ulong memberId in team.members) {
if (memberId != player.userID) members.Add(memberId);
}
}
}
if (_pluginConfig.DebugMode) {
Puts($"Team members for {player.displayName}: {string.Join(", ", members)}");
}
return members;
}
private List<string> GetAuthorizedNames(List<ulong> memberIds) {
List<string> names = new List<string>();
foreach (ulong id in memberIds) {
string name = covalence.Players.FindPlayerById(id.ToString())?.Name ?? "Unknown";
names.Add($"\"{name}\"");
}
return names;
}
private void SetEntityWhitelist(BasePlayer player, CodeLock codelock) {
List<ulong> teamMembers = GetTeamMembers(player);
foreach (ulong memberId in teamMembers) {
codelock.whitelistPlayers.Add(memberId);
}
if (_pluginConfig.DebugMode) {
foreach (ulong memberId in teamMembers) {
Puts($"UserID {memberId} authorized on CodeLock by {player.displayName}.");
}
}
}
private void SetEntityWhitelist(BasePlayer player, BuildingPrivlidge cupboard) {
List<ulong> teamMembers = GetTeamMembers(player);
foreach (ulong memberId in teamMembers) {
cupboard.authorizedPlayers.Add(memberId);
}
if (_pluginConfig.DebugMode) {
foreach (ulong memberId in teamMembers) {
Puts($"UserID {memberId} authorized on Tool Cupboard by {player.displayName}.");
}
}
}
private void SetEntityWhitelist(BasePlayer player, AutoTurret turret) {
List<ulong> teamMembers = GetTeamMembers(player);
foreach (ulong memberId in teamMembers) {
turret.authorizedPlayers.Add(memberId);
}
if (_pluginConfig.DebugMode) {
foreach (ulong memberId in teamMembers) {
Puts($"UserID {memberId} authorized on Auto Turret by {player.displayName}.");
}
}
}
#region Team blueprint sharing
private bool BlueprintSharingHooksEnabled() {
if (!_pluginConfig.BlueprintTeamSharingEnabled) return false;
return _pluginConfig.BlueprintShareFromItems || _pluginConfig.BlueprintShareFromTechTree;
}
private bool ClanBlueprintSharingEnabled() {
if (!_pluginConfig.BlueprintClanSharingEnabled) return false;
var clansPlugin = GetClansPlugin();
return clansPlugin != null && clansPlugin.IsLoaded;
}
private Plugin GetClansPlugin() {
if (ClansReborn != null && ClansReborn.IsLoaded) return ClansReborn;
if (Clans != null && Clans.IsLoaded) return Clans;
return null;
}
private bool IsClanMember(ulong playerId, ulong otherId) {
var clansPlugin = GetClansPlugin();
return clansPlugin != null && clansPlugin.Call<bool>("IsClanMember", playerId, otherId);
}
private List<ulong> GetClanMembers(ulong playerId) {
var clansPlugin = GetClansPlugin();
if (clansPlugin == null) return new List<ulong>();
var members = clansPlugin.Call<List<string>>("GetClanMembers", playerId);
if (members == null) return new List<ulong>();
var result = new List<ulong>();
foreach (var idStr in members) {
if (ulong.TryParse(idStr, out var id)) result.Add(id);
}
return result;
}
private bool PlayerHasClanmatesForBlueprintShare(ulong playerId) {
var members = GetClanMembers(playerId);
return members.Count > 1;
}
private List<ulong> GetClanmateIdsForBlueprintShare(ulong sharerId) {
return GetClanMembers(sharerId).Where(id => id != sharerId).ToList();
}
private void HandleBlueprintTeamInviteAccepted(RelationshipManager.PlayerTeam playerTeam, BasePlayer joiningPlayer) {
if (!BlueprintSharingHooksEnabled() || playerTeam == null || joiningPlayer == null) return;
ulong joinerId = joiningPlayer.userID;
timer.Once(1f, () => {
if (playerTeam.members == null || !playerTeam.members.Contains(joinerId)) return;
foreach (ulong otherId in playerTeam.members) {
if (otherId == joinerId) continue;
ShareAllBlueprintsFromTo(otherId, joinerId);
ShareAllBlueprintsFromTo(joinerId, otherId);
}
FlushBlueprintTrackingIfDirty();
}
);
}
private void HandleClanMemberJoined(ulong joiningId, IEnumerable<ulong> existingMemberIds) {
if (!ClanBlueprintSharingEnabled()) return;
if (existingMemberIds == null) return;
foreach (ulong otherId in existingMemberIds) {
if (otherId == joiningId) continue;
ShareAllBlueprintsFromToClan(otherId, joiningId);
ShareAllBlueprintsFromToClan(joiningId, otherId);
}
FlushClanBlueprintTrackingIfDirty();
}
private void HandleClanMemberGone(ulong leavingId, IEnumerable<ulong> remainingMemberIds) {
if (!ClanBlueprintSharingEnabled()) return;
// Remove blueprints the leaving player received from clanmates.
RemoveTrackedClanBlueprintsFromPlayer(leavingId);
// Remove blueprints remaining members received from the leaving player.
if (remainingMemberIds != null) {
foreach (ulong receiverId in remainingMemberIds) {
if (receiverId == leavingId) continue;
RemoveTrackedClanBlueprintsFromPlayer(receiverId, leavingId);
}
}
FlushClanBlueprintTrackingIfDirty();
}
private void ScheduleBlueprintLibrarySyncToTeammates(ulong learnerId) {
if (!BlueprintSharingHooksEnabled()) return;
var learner = BasePlayer.FindByID(learnerId);
if (learner != null && !CanPlayerInitiateBlueprintSharing(learner)) return;
if (!PlayerHasTeammatesForBlueprintShare(learnerId)) return;
float delay = _pluginConfig.BlueprintLibraryResyncDelaySeconds;
if (delay < 0f) delay = 0f;
if (_blueprintLibraryResyncTimers.TryGetValue(learnerId, out var existing)) {
existing?.Destroy();
_blueprintLibraryResyncTimers.Remove(learnerId);
}
ulong idCopy = learnerId;
_blueprintLibraryResyncTimers[learnerId] = timer.Once(delay, () => {
_blueprintLibraryResyncTimers.Remove(idCopy);
NextTick(() => RunBlueprintLibrarySyncFromPlayerToAllTeammates(idCopy));
}
);
}
private void RunBlueprintLibrarySyncFromPlayerToAllTeammates(ulong sharerId) {
if (!BlueprintSharingHooksEnabled()) return;
if (!HasBlueprintSharerPermission(sharerId)) return;
var team = RelationshipManager.ServerInstance?.FindPlayersTeam(sharerId);
if (team?.members == null || team.members.Count < 2) return;
foreach (ulong teammateId in team.members) {
if (teammateId == sharerId) continue;
ShareAllBlueprintsFromTo(sharerId, teammateId);
}
FlushBlueprintTrackingIfDirty();
}
private void CancelBlueprintLibraryResyncTimer(ulong playerId) {
if (!_blueprintLibraryResyncTimers.TryGetValue(playerId, out var t)) return;
t?.Destroy();
_blueprintLibraryResyncTimers.Remove(playerId);
}
private void HandleBlueprintPlayerLeftTeam(ulong leavingPlayerId, IList<ulong> lastKnownTeamMembers = null) {
// Remove blueprints the leaving player received from the team.
RemoveTrackedTeamBlueprintsFromPlayer(leavingPlayerId);
// Also remove blueprints other players received from the leaving player.
if (lastKnownTeamMembers == null || lastKnownTeamMembers.Count == 0) return;
foreach (ulong receiverId in lastKnownTeamMembers) {
if (receiverId == leavingPlayerId) continue;
RemoveTrackedTeamBlueprintsFromPlayer(receiverId, leavingPlayerId);
}
}
private void ShareAllBlueprintsFromTo(ulong sharerId, ulong receiverId) {
if (sharerId == receiverId) return;
if (!HasBlueprintSharerPermission(sharerId)) return;
var filtered = GetFilteredUnlockedBlueprintIdsForPlayer(sharerId);
if (filtered.Count == 0) return;
var queue = new List<int>();
bool track = _pluginConfig.OperatingMode == OPERATING_MODE_BACKWARD_AUTH;
foreach (int blueprintId in filtered) {
QueueBlueprintUnlockForTeam(sharerId, receiverId, blueprintId, queue, track);
}
if (queue.Count > 0) {
int n = ProcessQueuedBlueprintUnlocks(receiverId, queue);
if (n > 0) {
string sharerName = ResolvePlayerDisplayName(sharerId);
string receiverName = ResolvePlayerDisplayName(receiverId);
var receiver = BasePlayer.FindByID(receiverId);
if (receiver != null && receiver.IsConnected) SendChatMessage(receiver, Lang("BlueprintBulkReceived", receiver.UserIDString), n, sharerName);
var sharer = BasePlayer.FindByID(sharerId);
if (sharer != null && sharer.IsConnected) SendChatMessage(sharer, Lang("BlueprintBulkFromYourLibrary", sharer.UserIDString), receiverName, n);
// ... [Code preview truncated due to length. Full source in attached file] ...