using Newtonsoft.Json; 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", "1.3.0")] [Description("Allows automatic team auth on codelocks, turrets, and tool cupboards via the /share command.")] public class TeamShare : RustPlugin { [PluginReference] private Plugin WelcomeController; [PluginReference] private Plugin NoEscape; 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 Messages = new Dictionary { ["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" }; [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; } public class CommandsSettings { [JsonProperty("Open UI Command")] public string OpenUI = "teamui"; [JsonProperty("Share Command")] public string Share = "share"; } public class UISettings { [JsonProperty("_Comment_Colors")] public string CommentColors { get; set; } = "All colors use format: R G B A (0-1 per channel). UIBackgroundColor = main panel background. UIPrimaryColor = primary buttons (e.g. Set Code). UIDangerButtonColor = close/danger buttons (e.g. X). UIToggleOnColor = toggle ON state (e.g. green). UIToggleOffColor = toggle OFF state (e.g. red)."; 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 AutoTurrets = new HashSet(); public readonly HashSet BuildingPrivlidges = new HashSet(); public readonly HashSet CodeLocks = new HashSet(); } 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 DynamicConfigFile _playerCodesData; private DynamicConfigFile _playerGuestCodesData; private DynamicConfigFile _playerDataFile; private static Configuration _pluginConfig; private Dictionary _playerCodes = new Dictionary(); private Dictionary _playerGuestCodes = new Dictionary(); private Dictionary _playerData = new Dictionary(); private Dictionary playerUIs = new Dictionary(); private Dictionary playerTempCodes = new Dictionary(); private Dictionary playerTempGuestCodes = new Dictionary(); private Dictionary playerCodeRevealed = new Dictionary(); private Dictionary playerGuestCodeRevealed = new Dictionary(); private Dictionary codeChangedTimers = new Dictionary(); private Dictionary guestCodeChangedTimers = new Dictionary(); private Dictionary animationTimers = new Dictionary(); private UISettings uiSettings; private readonly Dictionary _playerEntities = new Dictionary(); private class ToggleOption { public string Label; public string Command; public bool IsOn; public string Permission; } #region Hooks and Initialization void Init() { LoadConfigData(); 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); lang.RegisterMessages(_pluginConfig.Messages, this); uiSettings = _pluginConfig.UISettings; HandleCommands(); Interface.CallHook("OnWCRequestColors", _pluginConfig.AddonName); if (_pluginConfig.DebugMode) { Puts("TeamShare plugin initialized."); } } private void LoadConfigData() { try { _pluginConfig = Config.ReadObject(); 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(); } } 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>(); if (_pluginConfig.DebugMode) { Puts("Player codes loaded."); } } catch { _playerCodes = new Dictionary(); PrintError("Failed to load PlayerCodes data. Initialized empty dictionary."); } try { _playerGuestCodes = _playerGuestCodesData.ReadObject>(); if (_pluginConfig.DebugMode) { Puts("Player guest codes loaded."); } } catch { _playerGuestCodes = new Dictionary(); PrintError("Failed to load PlayerGuestCodes data. Initialized empty dictionary."); } try { _playerData = _playerDataFile.ReadObject>(); if (_pluginConfig.DebugMode) { Puts("Player data loaded."); } } catch { _playerData = new Dictionary(); PrintError("Failed to load PlayerData. Initialized empty dictionary."); } } private void SavePlayerData() { _playerCodesData.WriteObject(_playerCodes); _playerGuestCodesData.WriteObject(_playerGuestCodes); _playerDataFile.WriteObject(_playerData); if (_pluginConfig.DebugMode) { Puts("Player data saved."); } } 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 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 pluginNames, Dictionary 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 ConvertToDictionary(UISettings uiSettings) { var dictionary = new Dictionary(); 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 (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.SetFlag(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 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 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 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 GetTeamMembers(BasePlayer player) { if (player == null) return new List(); List members = new List { 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 GetAuthorizedNames(List memberIds) { List names = new List(); 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 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 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 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 Mode 2 entity cache and backward auth private EntityCache GetOrCreateEntityCache(ulong ownerId) { if (!_playerEntities.TryGetValue(ownerId, out EntityCache cache)) { cache = new EntityCache(); _playerEntities.Add(ownerId, cache); } return cache; } private void AddEntityToCache(ulong ownerId, CodeLock codeLock) { if (codeLock == null || codeLock.IsDestroyed) return; GetOrCreateEntityCache(ownerId).CodeLocks.Add(codeLock); if (_pluginConfig.DebugMode) Puts($"Mode 2: CodeLock added to cache for owner {ownerId}."); } private void AddEntityToCache(ulong ownerId, BuildingPrivlidge cupboard) { if (cupboard == null || cupboard.IsDestroyed) return; GetOrCreateEntityCache(ownerId).BuildingPrivlidges.Add(cupboard); if (_pluginConfig.DebugMode) Puts($"Mode 2: Cupboard added to cache for owner {ownerId}."); } private void AddEntityToCache(ulong ownerId, AutoTurret turret) { if (turret == null || turret.IsDestroyed) return; GetOrCreateEntityCache(ownerId).AutoTurrets.Add(turret); if (_pluginConfig.DebugMode) Puts($"Mode 2: Turret added to cache for owner {ownerId}."); } private void RemoveEntityFromCache(CodeLock codeLock) { if (codeLock == null) return; ulong ownerId = codeLock.OwnerID != 0 ? codeLock.OwnerID : codeLock.GetParentEntity()?.OwnerID ?? 0; if (ownerId == 0) return; if (_playerEntities.TryGetValue(ownerId, out EntityCache cache)) { cache.CodeLocks.Remove(codeLock); if (_pluginConfig.DebugMode) Puts($"Mode 2: CodeLock removed from cache for owner {ownerId}."); } } private void RemoveEntityFromCache(BuildingPrivlidge cupboard) { if (cupboard == null) return; if (cupboard.OwnerID == 0) return; if (_playerEntities.TryGetValue(cupboard.OwnerID, out EntityCache cache)) { cache.BuildingPrivlidges.Remove(cupboard); if (_pluginConfig.DebugMode) Puts($"Mode 2: Cupboard removed from cache for owner {cupboard.OwnerID}."); } } private void RemoveEntityFromCache(AutoTurret turret) { if (turret == null) return; if (turret.OwnerID == 0) return; if (_playerEntities.TryGetValue(turret.OwnerID, out EntityCache cache)) { cache.AutoTurrets.Remove(turret); if (_pluginConfig.DebugMode) Puts($"Mode 2: Turret removed from cache for owner {turret.OwnerID}."); } } private List GetTeamMembers(ulong playerId) { var team = RelationshipManager.ServerInstance?.FindPlayersTeam(playerId); if (team == null) return new List { playerId }; return new List(team.members); } private void RefreshOwnerEntityAuth(ulong ownerId, ulong newTeammateId = 0) { if (!_playerEntities.TryGetValue(ownerId, out EntityCache cache)) return; List teamMembers = GetTeamMembers(ownerId); int codelocksUpdated = 0, turretsUpdated = 0, cupboardsUpdated = 0; foreach (var turret in cache.AutoTurrets.ToList()) { if (turret == null || turret.IsDestroyed) continue; turret.authorizedPlayers.Clear(); foreach (ulong id in teamMembers) turret.authorizedPlayers.Add(id); turret.SendNetworkUpdate(); turretsUpdated++; } foreach (var cupboard in cache.BuildingPrivlidges.ToList()) { if (cupboard == null || cupboard.IsDestroyed) continue; cupboard.authorizedPlayers.Clear(); foreach (ulong id in teamMembers) cupboard.authorizedPlayers.Add(id); cupboard.SendNetworkUpdate(); cupboardsUpdated++; } foreach (var codeLock in cache.CodeLocks.ToList()) { if (codeLock == null || codeLock.IsDestroyed) continue; codeLock.whitelistPlayers.Clear(); foreach (ulong id in teamMembers) codeLock.whitelistPlayers.Add(id); codeLock.guestPlayers.Clear(); foreach (ulong id in teamMembers) { if (id != ownerId) codeLock.guestPlayers.Add(id); } codeLock.SendNetworkUpdate(); codelocksUpdated++; } if (newTeammateId != 0 && ownerId != newTeammateId && (codelocksUpdated + turretsUpdated + cupboardsUpdated) > 0) { BasePlayer owner = BasePlayer.FindByID(ownerId); if (owner != null && owner.IsConnected && !GetPlayerData(ownerId).enabledQuiet) { string newTeammateName = covalence.Players.FindPlayerById(newTeammateId.ToString())?.Name ?? "Unknown"; string msg = Lang("AuthedBackward", owner.UserIDString); SendChatMessage(owner, msg, newTeammateName, codelocksUpdated, turretsUpdated, cupboardsUpdated); } } if (_pluginConfig.DebugMode) Puts($"Mode 2: Refreshed auth for owner {ownerId} ({teamMembers.Count} members)."); } private void UpdateTeamAuthList(IList affectedMemberIds, ulong newTeammateId = 0) { if (affectedMemberIds == null || affectedMemberIds.Count == 0) return; foreach (ulong memberId in affectedMemberIds) { if (_pluginConfig.UsePermissions && !permission.UserHasPermission(memberId.ToString(), PERMISSION_USE)) continue; RefreshOwnerEntityAuth(memberId, newTeammateId); } } void OnEntityKill(AutoTurret turret) { if (_pluginConfig.OperatingMode == OPERATING_MODE_BACKWARD_AUTH) RemoveEntityFromCache(turret); } void OnEntityKill(BuildingPrivlidge cupboard) { if (_pluginConfig.OperatingMode == OPERATING_MODE_BACKWARD_AUTH) RemoveEntityFromCache(cupboard); } void OnEntityKill(CodeLock codeLock) { if (_pluginConfig.OperatingMode == OPERATING_MODE_BACKWARD_AUTH) RemoveEntityFromCache(codeLock); } void OnTeamAcceptInvite(RelationshipManager.PlayerTeam playerTeam, BasePlayer player) { if (_pluginConfig.OperatingMode != OPERATING_MODE_BACKWARD_AUTH) return; if (playerTeam == null || player == null) return; ulong newTeammateId = player.userID; NextTick(() => { if (playerTeam.members.Contains(newTeammateId)) { UpdateTeamAuthList(playerTeam.members, newTeammateId); } }); } void OnTeamLeave(RelationshipManager.PlayerTeam playerTeam, BasePlayer player) { if (_pluginConfig.OperatingMode != OPERATING_MODE_BACKWARD_AUTH) return; if (playerTeam == null || player == null) return; NextTick(() => { if (!playerTeam.members.Contains(player.userID)) { var affected = new List(playerTeam.members) { player.userID }; UpdateTeamAuthList(affected); } }); } void OnTeamKick(RelationshipManager.PlayerTeam playerTeam, BasePlayer leader, ulong target) { if (_pluginConfig.OperatingMode != OPERATING_MODE_BACKWARD_AUTH) return; if (playerTeam == null) return; NextTick(() => { if (!playerTeam.members.Contains(target)) { var affected = new List(playerTeam.members) { target }; UpdateTeamAuthList(affected); } }); } void OnTeamDisbanded(RelationshipManager.PlayerTeam playerTeam) { if (_pluginConfig.OperatingMode != OPERATING_MODE_BACKWARD_AUTH) return; if (playerTeam == null) return; UpdateTeamAuthList(playerTeam.members); } #endregion Mode 2 entity cache and backward auth #endregion #region UI Methods [ChatCommand("teamui")] private void OpenTeamShareUI(BasePlayer player, string command, string[] args) { if (!IsPermitted(player)) { SendChatMessage(player, Lang("NoPermission", player.UserIDString)); return; } CreateUI(player); } private void CreateUI(BasePlayer player, string parentPanel = "Overlay") { DestroyUI(player); CuiElementContainer container = new CuiElementContainer(); string panelName = CuiHelper.GetGuid(); playerUIs[player.userID] = panelName; string anchorMin = !_pluginConfig.UsingWelcomeController ? "0.2 0.2" : "0 0"; string anchorMax = !_pluginConfig.UsingWelcomeController ? "0.8 0.8" : "1 1"; int fontSize = !_pluginConfig.UsingWelcomeController ? 16 : 20; int buttonFontSize = !_pluginConfig.UsingWelcomeController ? 14 : 18; string fieldBackgroundColor = !_pluginConfig.UsingWelcomeController ? "0 0 0 0.7" : uiSettings.UIBackgroundColor; CreatePanel(ref container, anchorMin, anchorMax, uiSettings.UIBackgroundColor, parentPanel, panelName, isMainPanel: true, blur: true); #region Top Panel string topPanel = CreatePanel(ref container, _pluginConfig.UsingWelcomeController ? "0.1 0.65" : "0.1 0.652", _pluginConfig.UsingWelcomeController ? "0.9 0.9" : "0.9 0.9", uiSettings.UIBackgroundColor, panelName, "TopPanel"); CreateLabel(ref container, "0 0.75", "1 1", "1 1 1 1", "0 0 0 0", "Team Auth Options", fontSize, TextAnchor.MiddleCenter, topPanel); if (!_pluginConfig.UsingWelcomeController) { CreateButton(ref container, "0.92 0.92", "0.98 0.98", uiSettings.UIDangerButtonColor, "1 1 1 1", "×", fontSize, "teamshare_close_ui", panelName, TextAnchor.MiddleCenter); } var sharingToggles = new List(); if (HasFeaturePermission(player, PERMISSION_CODELOCK)) sharingToggles.Add(new ToggleOption { Label = "Codelock Sharing", Command = "toggle_codelock", IsOn = GetPlayerData(player.userID).enabledCodelock, Permission = PERMISSION_CODELOCK }); if (HasFeaturePermission(player, PERMISSION_CUPBOARD)) sharingToggles.Add(new ToggleOption { Label = "Cupboard Sharing", Command = "toggle_cupboard", IsOn = GetPlayerData(player.userID).enabledCupboard, Permission = PERMISSION_CUPBOARD }); if (HasFeaturePermission(player, PERMISSION_TURRET)) sharingToggles.Add(new ToggleOption { Label = "Turret Sharing", Command = "toggle_turret", IsOn = GetPlayerData(player.userID).enabledTurret, Permission = PERMISSION_TURRET }); var accessControlToggles = new List(); if (HasFeaturePermission(player, PERMISSION_GUEST)) accessControlToggles.Add(new ToggleOption { Label = "Enable Guest Code", Command = "toggle_guestcode", IsOn = GetPlayerData(player.userID).enabledGuestCode, Permission = PERMISSION_GUEST }); accessControlToggles.Add(new ToggleOption { Label = "Quiet Mode", Command = "toggle_quiet", IsOn = GetPlayerData(player.userID).enabledQuiet, Permission = null }); float startY = _pluginConfig.UsingWelcomeController ? 0.5f : 0.5f; float rowHeight = _pluginConfig.UsingWelcomeController ? 0.2f : 0.23f; float rowSpacing = _pluginConfig.UsingWelcomeController ? 0.25f : 0.26f; foreach (var toggle in sharingToggles) { int index = sharingToggles.IndexOf(toggle); float yMin = startY - index * rowSpacing; float yMax = yMin + rowHeight; string toggleName = toggle.Command.Substring(7); string toggleGroup = CreatePanel(ref container, $"0.05 {yMin}", $"0.95 {yMax}", fieldBackgroundColor, topPanel, $"ToggleGroup_{toggleName}"); CreateLabel(ref container, "0.05 0", "0.6 1", "1 1 1 1", "0 0 0 0", toggle.Label, fontSize, TextAnchor.MiddleLeft, toggleGroup); CreateToggleSwitch(ref container, "0.75 0.2", "0.95 0.8", toggle.IsOn, $"teamshare_{toggle.Command}", toggleGroup, $"ToggleButton_{toggleName}", buttonFontSize); } #endregion #region Middle Panel string middlePanel = CreatePanel(ref container, _pluginConfig.UsingWelcomeController ? "0.1 0.4" : "0.1 0.4", _pluginConfig.UsingWelcomeController ? "0.9 0.648" : "0.9 0.65", uiSettings.UIBackgroundColor, panelName, "MiddlePanel"); CreateLabel(ref container, "0 0.7", "1 1", "1 1 1 1", "0 0 0 0", "Access Control", fontSize, TextAnchor.MiddleCenter, middlePanel); foreach (var toggle in accessControlToggles) { int index = accessControlToggles.IndexOf(toggle); float yMin = startY - index * rowSpacing; float yMax = yMin + rowHeight; string toggleName = toggle.Command.Substring(7); string toggleGroup = CreatePanel(ref container, $"0.05 {yMin}", $"0.95 {yMax}", fieldBackgroundColor, middlePanel, $"ToggleGroup_{toggleName}"); CreateLabel(ref container, "0.05 0", "0.6 1", "1 1 1 1", "0 0 0 0", toggle.Label, fontSize, TextAnchor.MiddleLeft, toggleGroup); CreateToggleSwitch(ref container, "0.75 0.2", "0.95 0.8", toggle.IsOn, $"teamshare_{toggle.Command}", toggleGroup, $"ToggleButton_{toggleName}", buttonFontSize); } #endregion #region Code Panels string leftPanel = CreatePanel(ref container, "0.1 0.1", _pluginConfig.UsingWelcomeController ? "0.4 0.35" : "0.4 0.35", uiSettings.UIBackgroundColor, panelName, "LeftPanel"); CreateLabel(ref container, "0.05 0.75", "0.95 0.95", "1 1 1 1", "0 0 0 0", "Main Code:", fontSize, TextAnchor.MiddleCenter, leftPanel); string mainCodeDisplay = GetPlayerCode(player.userID) ?? ""; if (!playerCodeRevealed.ContainsKey(player.userID)) { playerCodeRevealed[player.userID] = false; } bool isMainRevealed = playerCodeRevealed[player.userID]; CreatePanel(ref container, "0.1 0.5", "0.9 0.7", fieldBackgroundColor, leftPanel, "InputFieldBackground_MainCode"); string inputFieldText = ""; if (playerTempCodes.ContainsKey(player.userID)) { inputFieldText = playerTempCodes[player.userID]; } else if (isMainRevealed && !string.IsNullOrEmpty(mainCodeDisplay)) { inputFieldText = mainCodeDisplay; } container.Add(new CuiElement { Name = "MainCodeInputField", Parent = "InputFieldBackground_MainCode", Components = { new CuiInputFieldComponent { Command = "teamshare_set_temp_code", FontSize = fontSize, Align = TextAnchor.MiddleCenter, Text = inputFieldText, Color = "1 1 1 1", NeedsKeyboard = true }, new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1" } } }); if (!string.IsNullOrEmpty(mainCodeDisplay) && !isMainRevealed) { CreateButton(ref container, "0.1 0.5", "0.9 0.7", "0 0 0 0", "1 1 1 1", "****", fontSize, "teamshare_toggle_reveal_main", leftPanel, TextAnchor.MiddleCenter, "MainCodeDisplay"); } CreateButton(ref container, "0.1 0.15", "0.9 0.3", uiSettings.UIPrimaryColor, "1 1 1 1", "Set Code", buttonFontSize, "teamshare_confirm_code", leftPanel); if (HasFeaturePermission(player, PERMISSION_GUEST)) { string rightPanel = CreatePanel(ref container, "0.6 0.1", _pluginConfig.UsingWelcomeController ? "0.9 0.35" : "0.9 0.35", uiSettings.UIBackgroundColor, panelName, "RightPanel"); CreateLabel(ref container, "0.05 0.75", "0.95 0.95", "1 1 1 1", "0 0 0 0", "Guest Code:", fontSize, TextAnchor.MiddleCenter, rightPanel); string guestCodeDisplay = GetPlayerGuestCode(player.userID) ?? ""; if (!playerGuestCodeRevealed.ContainsKey(player.userID)) { playerGuestCodeRevealed[player.userID] = false; } bool isGuestRevealed = playerGuestCodeRevealed[player.userID]; CreatePanel(ref container, "0.1 0.5", "0.9 0.7", fieldBackgroundColor, rightPanel, "InputFieldBackground_GuestCode"); string guestInputFieldText = ""; if (playerTempGuestCodes.ContainsKey(player.userID)) { guestInputFieldText = playerTempGuestCodes[player.userID]; } else if (isGuestRevealed && !string.IsNullOrEmpty(guestCodeDisplay)) { guestInputFieldText = guestCodeDisplay; } container.Add(new CuiElement { Name = "GuestCodeInputField", Parent = "InputFieldBackground_GuestCode", Components = { new CuiInputFieldComponent { Command = "teamshare_set_temp_guest_code", FontSize = fontSize, Align = TextAnchor.MiddleCenter, Text = guestInputFieldText, Color = "1 1 1 1", NeedsKeyboard = true }, new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1" } } }); if (!string.IsNullOrEmpty(guestCodeDisplay) && !isGuestRevealed) { CreateButton(ref container, "0.1 0.5", "0.9 0.7", "0 0 0 0", "1 1 1 1", "****", fontSize, "teamshare_toggle_reveal_guest", rightPanel, TextAnchor.MiddleCenter, "GuestCodeDisplay"); } CreateButton(ref container, "0.1 0.15", "0.9 0.3", uiSettings.UIPrimaryColor, "1 1 1 1", "Set Guest Code", buttonFontSize, "teamshare_confirm_guest_code", rightPanel); } #endregion CuiHelper.AddUi(player, container); if (_pluginConfig.DebugMode) { Puts($"UI created for player {player.displayName}."); } } #endregion #region UI Management private void DestroyUI(BasePlayer player) { if (playerUIs.TryGetValue(player.userID, out string panelName)) { CuiHelper.DestroyUi(player, panelName); playerUIs.Remove(player.userID); playerTempCodes.Remove(player.userID); playerTempGuestCodes.Remove(player.userID); playerCodeRevealed.Remove(player.userID); playerGuestCodeRevealed.Remove(player.userID); if (codeChangedTimers.TryGetValue(player.userID, out var codeTimer)) { codeTimer.Destroy(); codeChangedTimers.Remove(player.userID); } if (guestCodeChangedTimers.TryGetValue(player.userID, out var guestCodeTimer)) { guestCodeTimer.Destroy(); guestCodeChangedTimers.Remove(player.userID); } var animationKeysToRemove = new List(); string playerPrefix = $"{player.userID}_"; foreach (var kvp in animationTimers) { if (kvp.Key.StartsWith(playerPrefix)) { kvp.Value.Destroy(); animationKeysToRemove.Add(kvp.Key); } } foreach (var key in animationKeysToRemove) { animationTimers.Remove(key); } if (WelcomeController != null) { WelcomeController.Call("DestroyUI", player); } if (_pluginConfig.DebugMode) { Puts($"UI destroyed for player {player.displayName}."); } } } private void UpdateToggleButton(BasePlayer player, string toggleName, bool isOn) { string toggleButtonName = $"ToggleButton_{toggleName}"; string toggleGroupName = $"ToggleGroup_{toggleName}"; string buttonCommand = $"teamshare_toggle_{toggleName}"; int buttonFontSize = !_pluginConfig.UsingWelcomeController ? 14 : 18; if (_pluginConfig.UISettings.AnimatedButtons) { AnimateToggleSwitch(player, toggleGroupName, toggleButtonName, buttonCommand, !isOn, isOn, buttonFontSize); } else { CuiHelper.DestroyUi(player, $"{toggleButtonName}_Container"); var container = new CuiElementContainer(); CreateToggleSwitch(ref container, "0.75 0.2", "0.95 0.8", isOn, buttonCommand, toggleGroupName, toggleButtonName, buttonFontSize); CuiHelper.AddUi(player, container); } if (_pluginConfig.DebugMode) { Puts($"Toggle '{toggleName}' updated to {(isOn ? "On" : "Off")} for player {player.displayName}."); } } private void AnimateToggleSwitch(BasePlayer player, string parent, string buttonName, string buttonCommand, bool fromState, bool toState, int fontSize) { string animationKey = $"{player.userID}_{buttonName}"; if (animationTimers.TryGetValue(animationKey, out var existingTimer)) { existingTimer.Destroy(); animationTimers.Remove(animationKey); CuiHelper.DestroyUi(player, $"{buttonName}_Container"); var instantContainer = new CuiElementContainer(); CreateToggleSwitch(ref instantContainer, "0.75 0.2", "0.95 0.8", toState, buttonCommand, parent, buttonName, fontSize); CuiHelper.AddUi(player, instantContainer); return; } int steps = 24; float interval = 0.25f / steps; Timer animTimer = null; void RunStep(int step) { if (step > steps) { animTimer?.Destroy(); animationTimers.Remove(animationKey); var finalContainer = new CuiElementContainer(); CreateToggleSwitch(ref finalContainer, "0.75 0.2", "0.95 0.8", toState, buttonCommand, parent, buttonName, fontSize); CuiHelper.DestroyUi(player, $"{buttonName}_Container"); CuiHelper.AddUi(player, finalContainer); return; } float progress = (float)step / steps; float easedProgress = progress * progress * (3f - 2f * progress); var container = new CuiElementContainer(); string toggleContainer = $"{buttonName}_Container"; float onPanelMaxStart = fromState ? 1f : 0f; float onPanelMaxEnd = toState ? 1f : 0f; float onPanelMax = onPanelMaxStart + (onPanelMaxEnd - onPanelMaxStart) * easedProgress; float offPanelMinStart = fromState ? 1f : 0f; float offPanelMinEnd = toState ? 1f : 0f; float offPanelMin = offPanelMinStart + (offPanelMinEnd - offPanelMinStart) * easedProgress; float offPanelMax = 1f; if (onPanelMax > 0.001f) { CreatePanel(ref container, "0 0", $"{onPanelMax} 1", uiSettings.UIToggleOnColor, toggleContainer, $"{buttonName}_OnPanel"); CreateLabel(ref container, "0 0", "1 1", "1 1 1 1", "0 0 0 0", "ON", fontSize, TextAnchor.MiddleCenter, $"{buttonName}_OnPanel"); } if (offPanelMin < offPanelMax - 0.001f) { CreatePanel(ref container, $"{offPanelMin} 0", $"{offPanelMax} 1", uiSettings.UIToggleOffColor, toggleContainer, $"{buttonName}_OffPanel"); CreateLabel(ref container, "0 0", "1 1", "1 1 1 1", "0 0 0 0", "OFF", fontSize, TextAnchor.MiddleCenter, $"{buttonName}_OffPanel"); } CuiHelper.AddUi(player, container); if (step < steps) { animTimer = timer.Once(interval, () => RunStep(step + 1)); animationTimers[animationKey] = animTimer; } else { RunStep(step + 1); } } animTimer = timer.Once(interval, () => RunStep(1)); animationTimers[animationKey] = animTimer; } #endregion #region Command Handlers [ConsoleCommand("teamshare_close_ui")] private void CloseUI(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; DestroyUI(player); } [ConsoleCommand("teamshare_toggle_codelock")] private void ToggleCodelock(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; PlayerData pData = GetPlayerData(player.userID); pData.enabledCodelock = !pData.enabledCodelock; SavePlayerData(); UpdateToggleButton(player, "codelock", pData.enabledCodelock); } [ConsoleCommand("teamshare_toggle_cupboard")] private void ToggleCupboard(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; PlayerData pData = GetPlayerData(player.userID); pData.enabledCupboard = !pData.enabledCupboard; SavePlayerData(); UpdateToggleButton(player, "cupboard", pData.enabledCupboard); } [ConsoleCommand("teamshare_toggle_turret")] private void ToggleTurret(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; PlayerData pData = GetPlayerData(player.userID); pData.enabledTurret = !pData.enabledTurret; SavePlayerData(); UpdateToggleButton(player, "turret", pData.enabledTurret); } [ConsoleCommand("teamshare_toggle_quiet")] private void ToggleQuiet(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; PlayerData pData = GetPlayerData(player.userID); pData.enabledQuiet = !pData.enabledQuiet; SavePlayerData(); UpdateToggleButton(player, "quiet", pData.enabledQuiet); } [ConsoleCommand("teamshare_toggle_guestcode")] private void ToggleGuestCode(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; if (!HasFeaturePermission(player, PERMISSION_GUEST)) return; PlayerData pData = GetPlayerData(player.userID); pData.enabledGuestCode = !pData.enabledGuestCode; SavePlayerData(); UpdateToggleButton(player, "guestcode", pData.enabledGuestCode); } [ConsoleCommand("teamshare_set_temp_code")] private void SetTempCode(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; string code = arg.GetString(0, ""); playerTempCodes[player.userID] = code; if (_pluginConfig.DebugMode) { Puts($"Temporary main code set to '{code}' for player {player.displayName}."); } } [ConsoleCommand("teamshare_set_temp_guest_code")] private void SetTempGuestCode(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; if (!HasFeaturePermission(player, PERMISSION_GUEST)) return; string code = arg.GetString(0, ""); playerTempGuestCodes[player.userID] = code; if (_pluginConfig.DebugMode) { Puts($"Temporary guest code set to '{code}' for player {player.displayName}."); } } [ConsoleCommand("teamshare_toggle_reveal_main")] private void ToggleRevealMainCode(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; if (!playerCodeRevealed.ContainsKey(player.userID) || !playerCodeRevealed[player.userID]) { playerCodeRevealed[player.userID] = true; UpdateCodeDisplay(player, isGuestCode: false); } } [ConsoleCommand("teamshare_toggle_reveal_guest")] private void ToggleRevealGuestCode(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; if (!HasFeaturePermission(player, PERMISSION_GUEST)) return; if (!playerGuestCodeRevealed.ContainsKey(player.userID) || !playerGuestCodeRevealed[player.userID]) { playerGuestCodeRevealed[player.userID] = true; UpdateCodeDisplay(player, isGuestCode: true); } } private void UpdateCodeDisplay(BasePlayer player, bool isGuestCode = false) { var container = new CuiElementContainer(); int fontSize = !_pluginConfig.UsingWelcomeController ? 16 : 20; if (isGuestCode) { string guestCodeDisplay = GetPlayerGuestCode(player.userID) ?? ""; bool isRevealed = playerGuestCodeRevealed.ContainsKey(player.userID) && playerGuestCodeRevealed[player.userID]; if (!string.IsNullOrEmpty(guestCodeDisplay) && !isRevealed) { CreateButton(ref container, "0.1 0.5", "0.9 0.7", "0 0 0 0", "1 1 1 1", "****", fontSize, "teamshare_toggle_reveal_guest", "RightPanel", TextAnchor.MiddleCenter, "GuestCodeDisplay"); } if (isRevealed && !string.IsNullOrEmpty(guestCodeDisplay)) { CuiHelper.DestroyUi(player, "GuestCodeInputField"); container.Add(new CuiElement { Name = "GuestCodeInputField", Parent = "InputFieldBackground_GuestCode", Components = { new CuiInputFieldComponent { Command = "teamshare_set_temp_guest_code", FontSize = fontSize, Align = TextAnchor.MiddleCenter, Text = playerTempGuestCodes.ContainsKey(player.userID) ? playerTempGuestCodes[player.userID] : guestCodeDisplay, Color = "1 1 1 1", NeedsKeyboard = true }, new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1" } } }); } } else { string mainCodeDisplay = GetPlayerCode(player.userID) ?? ""; bool isRevealed = playerCodeRevealed.ContainsKey(player.userID) && playerCodeRevealed[player.userID]; if (!string.IsNullOrEmpty(mainCodeDisplay) && !isRevealed) { CreateButton(ref container, "0.1 0.5", "0.9 0.7", "0 0 0 0", "1 1 1 1", "****", fontSize, "teamshare_toggle_reveal_main", "LeftPanel", TextAnchor.MiddleCenter, "MainCodeDisplay"); } if (isRevealed && !string.IsNullOrEmpty(mainCodeDisplay)) { CuiHelper.DestroyUi(player, "MainCodeInputField"); container.Add(new CuiElement { Name = "MainCodeInputField", Parent = "InputFieldBackground_MainCode", Components = { new CuiInputFieldComponent { Command = "teamshare_set_temp_code", FontSize = fontSize, Align = TextAnchor.MiddleCenter, Text = playerTempCodes.ContainsKey(player.userID) ? playerTempCodes[player.userID] : mainCodeDisplay, Color = "1 1 1 1", NeedsKeyboard = true }, new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1" } } }); } } CuiHelper.DestroyUi(player, isGuestCode ? "GuestCodeDisplay" : "MainCodeDisplay"); CuiHelper.AddUi(player, container); } [ConsoleCommand("teamshare_confirm_code")] private void ConfirmCode(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; string code = ""; if (playerTempCodes.TryGetValue(player.userID, out string tempCode) && !string.IsNullOrEmpty(tempCode) && tempCode != "****") { code = tempCode; } else { string currentCode = GetPlayerCode(player.userID); if (!string.IsNullOrEmpty(currentCode)) { code = currentCode; } else { SendChatMessage(player, Lang("NoCodeSet", player.UserIDString)); return; } } code = new string(code.Where(char.IsDigit).ToArray()); if (string.IsNullOrEmpty(code)) { SendChatMessage(player, Lang("InvalidCode", player.UserIDString)); ShowCodeChangedMessage(player, "LeftPanel", "CodeChangedLabel_MainCode", isGuestCode: false, isValid: false); return; } code = code.PadLeft(4, '0'); if (code.Length > 4) { code = code.Substring(0, 4); } // Validate 4-digit code (0000-9999) if (!IsValidCode(code)) { SendChatMessage(player, Lang("InvalidCode", player.UserIDString)); ShowCodeChangedMessage(player, "LeftPanel", "CodeChangedLabel_MainCode", isGuestCode: false, isValid: false); return; } SetPlayerCode(player.userID, code); SendChatMessage(player, string.Format(Lang("CodeSet", player.UserIDString), code)); ShowCodeChangedMessage(player, "LeftPanel", "CodeChangedLabel_MainCode", isGuestCode: false, isValid: true); playerTempCodes.Remove(player.userID); playerCodeRevealed[player.userID] = false; CreateUI(player); } [ConsoleCommand("teamshare_confirm_guest_code")] private void ConfirmGuestCode(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) return; if (!HasFeaturePermission(player, PERMISSION_GUEST)) return; string code = ""; if (playerTempGuestCodes.TryGetValue(player.userID, out string tempCode) && !string.IsNullOrEmpty(tempCode) && tempCode != "****") { code = tempCode; } else { string currentCode = GetPlayerGuestCode(player.userID); if (!string.IsNullOrEmpty(currentCode)) { code = currentCode; } else { SendChatMessage(player, Lang("NoGuestCodeSet", player.UserIDString)); return; } } code = new string(code.Where(char.IsDigit).ToArray()); if (string.IsNullOrEmpty(code)) { SendChatMessage(player, Lang("InvalidCode", player.UserIDString)); ShowCodeChangedMessage(player, "GuestCodeSection", "CodeChangedLabel_GuestCode", isGuestCode: true, isValid: false); return; } code = code.PadLeft(4, '0'); if (code.Length > 4) { code = code.Substring(0, 4); } // Validate 4-digit code (0000-9999) if (!IsValidCode(code)) { SendChatMessage(player, Lang("InvalidCode", player.UserIDString)); ShowCodeChangedMessage(player, "RightPanel", "CodeChangedLabel_GuestCode", isGuestCode: true, isValid: false); return; } SetPlayerGuestCode(player.userID, code); SendChatMessage(player, string.Format(Lang("GuestCodeSet", player.UserIDString), code)); ShowCodeChangedMessage(player, "RightPanel", "CodeChangedLabel_GuestCode", isGuestCode: true, isValid: true); playerTempGuestCodes.Remove(player.userID); playerGuestCodeRevealed[player.userID] = false; CreateUI(player); } #endregion #region Helper Methods private void SendChatMessage(BasePlayer player, string message, params object[] args) { if (GetPlayerData(player.userID).enabledQuiet) { if (_pluginConfig.DebugMode) { Puts($"Quiet mode enabled. Message '{message}' not sent to player {player.displayName}."); } return; } string formattedMessage = args.Length > 0 ? string.Format(message, args) : message; player.ChatMessage(formattedMessage); } private string Lang(string key, string userId = null) => lang.GetMessage(key, this, userId); private string CreatePanel(ref CuiElementContainer container, string anchorMin, string anchorMax, string panelColor, string parent = "Overlay", string panelName = null, bool isMainPanel = false, bool blur = false) { CuiPanel panel = new CuiPanel { RectTransform = { AnchorMin = anchorMin, AnchorMax = anchorMax }, Image = { Color = panelColor }, CursorEnabled = isMainPanel }; if (blur) { panel.Image.Material = "assets/content/ui/uibackgroundblur.mat"; } string name = container.Add(panel, parent, panelName); return name; } private void CreateLabel(ref CuiElementContainer container, string anchorMin, string anchorMax, string textColor, string backgroundColor, string text, int fontSize, TextAnchor alignment, string parent = "Overlay") { string panel = CreatePanel(ref container, anchorMin, anchorMax, backgroundColor, parent); container.Add(new CuiLabel { Text = { Color = textColor, Text = text, Align = alignment, FontSize = fontSize, Font = "robotocondensed-bold.ttf" }, RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" } }, panel); } private void CreateButton(ref CuiElementContainer container, string anchorMin, string anchorMax, string buttonColor, string textColor, string buttonText, int fontSize, string buttonCommand, string parent = "Overlay", TextAnchor labelAnchor = TextAnchor.MiddleCenter, string buttonName = null) { container.Add(new CuiButton { Button = { Color = buttonColor, Command = $"{buttonCommand}", Close = "" }, RectTransform = { AnchorMin = anchorMin, AnchorMax = anchorMax }, Text = { Align = labelAnchor, Color = textColor, FontSize = fontSize, Text = buttonText, Font = "robotocondensed-bold.ttf" } }, parent, buttonName); } private void CreateToggleSwitch(ref CuiElementContainer container, string anchorMin, string anchorMax, bool isOn, string buttonCommand, string parent, string buttonName, int fontSize) { string toggleContainer = CreatePanel(ref container, anchorMin, anchorMax, "0 0 0 0", parent, $"{buttonName}_Container"); float onPanelMin = isOn ? 0f : 0f; float onPanelMax = isOn ? 1f : 0f; float offPanelMin = isOn ? 1f : 0f; float offPanelMax = isOn ? 1f : 1f; if (onPanelMax > onPanelMin + 0.001f) { CreatePanel(ref container, $"{onPanelMin} 0", $"{onPanelMax} 1", uiSettings.UIToggleOnColor, toggleContainer, $"{buttonName}_OnPanel"); CreateLabel(ref container, "0 0", "1 1", "1 1 1 1", "0 0 0 0", "ON", fontSize, TextAnchor.MiddleCenter, $"{buttonName}_OnPanel"); } if (offPanelMax > offPanelMin + 0.001f) { CreatePanel(ref container, $"{offPanelMin} 0", $"{offPanelMax} 1", uiSettings.UIToggleOffColor, toggleContainer, $"{buttonName}_OffPanel"); CreateLabel(ref container, "0 0", "1 1", "1 1 1 1", "0 0 0 0", "OFF", fontSize, TextAnchor.MiddleCenter, $"{buttonName}_OffPanel"); } CreateButton(ref container, "0 0", "1 1", "0 0 0 0", "0 0 0 0", "", 1, buttonCommand, toggleContainer, TextAnchor.MiddleCenter, buttonName); } private void ShowCodeChangedMessage(BasePlayer player, string parentPanel, string labelName, bool isGuestCode = false, bool isValid = true) { string messageText = isValid ? "Code Changed" : "Invalid Code"; int fontSize = 14; float fadeDuration = 1.0f; int steps = 10; float interval = fadeDuration / steps; float alphaIncrement = 1f / steps; float alphaDecrement = 1f / steps; float currentAlpha = 1f; Timer fadeOutTimer = null; fadeOutTimer = timer.Repeat(interval, steps, () => { try { if (player == null || !player.IsConnected) { if (fadeOutTimer != null) fadeOutTimer.Destroy(); return; } currentAlpha -= alphaDecrement; if (currentAlpha < 0f) currentAlpha = 0f; CuiHelper.DestroyUi(player, labelName); var container = new CuiElementContainer(); container.Add(CreateLabelElement(player, parentPanel, labelName, $"1 1 1 {currentAlpha}", fontSize, "")); CuiHelper.AddUi(player, container); if (currentAlpha <= 0) { if (fadeOutTimer != null) fadeOutTimer.Destroy(); float newAlpha = 0f; Timer fadeInTimer = null; fadeInTimer = timer.Repeat(interval, steps, () => { try { if (player == null || !player.IsConnected) { if (fadeInTimer != null) fadeInTimer.Destroy(); return; } newAlpha += alphaIncrement; if (newAlpha > 1f) newAlpha = 1f; CuiHelper.DestroyUi(player, labelName); var containerFadeIn = new CuiElementContainer(); containerFadeIn.Add(CreateLabelElement(player, parentPanel, labelName, $"1 1 1 {newAlpha}", fontSize, messageText)); CuiHelper.AddUi(player, containerFadeIn); if (newAlpha >= 1f) { if (fadeInTimer != null) fadeInTimer.Destroy(); timer.Once(7f, () => { try { if (player == null || !player.IsConnected) return; Timer fadeOutAfterTimer = null; currentAlpha = 1f; fadeOutAfterTimer = timer.Repeat(interval, steps, () => { try { if (player == null || !player.IsConnected) { if (fadeOutAfterTimer != null) fadeOutAfterTimer.Destroy(); return; } currentAlpha -= alphaDecrement; if (currentAlpha < 0f) currentAlpha = 0f; CuiHelper.DestroyUi(player, labelName); var containerFadeOut = new CuiElementContainer(); containerFadeOut.Add(CreateLabelElement(player, parentPanel, labelName, $"1 1 1 {currentAlpha}", fontSize, messageText)); CuiHelper.AddUi(player, containerFadeOut); if (currentAlpha <= 0) { if (fadeOutAfterTimer != null) fadeOutAfterTimer.Destroy(); } } catch (Exception ex) { PrintError($"Error in fade out after timer: {ex.Message}"); if (fadeOutAfterTimer != null) fadeOutAfterTimer.Destroy(); } }); } catch (Exception ex) { PrintError($"Error setting up fade out after timer: {ex.Message}"); } }); } } catch (Exception ex) { PrintError($"Error in fade in timer: {ex.Message}"); if (fadeInTimer != null) fadeInTimer.Destroy(); } }); } } catch (Exception ex) { PrintError($"Error in fade out timer: {ex.Message}"); if (fadeOutTimer != null) fadeOutTimer.Destroy(); } }); if (_pluginConfig.DebugMode) { Puts($"Displayed '{messageText}' message for player {player.displayName}."); } } private CuiElement CreateLabelElement(BasePlayer player, string parentPanel, string labelName, string color, int fontSize, string messageText) { var labelElement = new CuiElement { Name = labelName, Parent = parentPanel, Components = { new CuiRectTransformComponent { AnchorMin = "0.1 0.01", AnchorMax = "0.9 0.13" }, new CuiTextComponent { Text = messageText, FontSize = fontSize, Align = TextAnchor.MiddleCenter, Color = color, Font = "robotocondensed-bold.ttf" } } }; return labelElement; } #endregion } }