C#:
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Newtonsoft.Json;
using System.Collections;
using Network;
using Rust;
namespace Oxide.Plugins
{
[Info("CS2 Style Plugin", "BugFixed", "2.1.0")]
[Description("Thoroughly bug-tested CS2-style competitive gameplay plugin for Rust")]
public class CS2Plugin : RustPlugin
{
#region Fields and Configuration
// Thread safety lock object
private readonly object _lockObject = new object();
private Configuration config;
private Dictionary<ulong, PlayerData> playerData = new Dictionary<ulong, PlayerData>();
private Dictionary<string, QueueZone> queueZones = new Dictionary<string, QueueZone>();
private Dictionary<string, BombSite> bombSites = new Dictionary<string, BombSite>();
private Dictionary<string, MapConfiguration> mapConfigurations = new Dictionary<string, MapConfiguration>();
private Dictionary<ulong, Match> activeMatches = new Dictionary<ulong, Match>();
private Dictionary<ulong, ClipboardData> adminClipboard = new Dictionary<ulong, ClipboardData>();
private Dictionary<ulong, Vote> activeVotes = new Dictionary<ulong, Vote>();
private Dictionary<string, GameMode> gameModes = new Dictionary<string, GameMode>();
private Dictionary<string, WeaponSkin> availableSkins = new Dictionary<string, WeaponSkin>();
private Dictionary<ulong, Tournament> activeTournaments = new Dictionary<ulong, Tournament>();
private Dictionary<string, Callout> mapCallouts = new Dictionary<string, Callout>();
private List<Achievement> availableAchievements = new List<Achievement>();
private Dictionary<ulong, Warmup> activeWarmups = new Dictionary<ulong, Warmup>();
private Dictionary<string, string> killFeedMessages = new Dictionary<string, string>();
private const string PERMISSION_ADMIN = "cs2plugin.admin";
private const string PERMISSION_USE = "cs2plugin.use";
private Timer uiUpdateTimer;
private Timer matchUpdateTimer;
private Timer cleanupTimer;
// Bug Fix: Add safety checks and validation
private HashSet<ulong> processingPlayers = new HashSet<ulong>();
private Dictionary<ulong, float> lastActionTime = new Dictionary<ulong, float>();
private const float ACTION_COOLDOWN = 0.5f;
#endregion
#region Configuration Classes
public class Configuration
{
[JsonProperty("Default Team Size")]
public int DefaultTeamSize { get; set; } = 5;
[JsonProperty("Round Win Requirement")]
public int RoundWinRequirement { get; set; } = 13;
[JsonProperty("Side Swap Round")]
public int SideSwapRound { get; set; } = 12;
[JsonProperty("Buy Phase Duration")]
public float BuyPhaseDuration { get; set; } = 20f;
[JsonProperty("Buy Menu Duration")]
public float BuyMenuDuration { get; set; } = 40f;
[JsonProperty("Bomb Timer")]
public float BombTimer { get; set; } = 40f;
[JsonProperty("Max Team Damage")]
public float MaxTeamDamage { get; set; } = 300f;
[JsonProperty("Max Grenade Damage")]
public float MaxGrenadeDamage { get; set; } = 60f;
[JsonProperty("Lobby Spawn Point")]
public Vector3 LobbySpawnPoint { get; set; } = new Vector3(0, 0, 0);
[JsonProperty("Auto Generate Default Maps")]
public bool AutoGenerateDefaultMaps { get; set; } = true;
[JsonProperty("Default Map Template")]
public string DefaultMapTemplate { get; set; } = "custom";
[JsonProperty("Map Generation On Server Start")]
public bool GenerateMapOnStart { get; set; } = true;
[JsonProperty("Server Map Name")]
public string ServerMapName { get; set; } = "cs2_server_map";
[JsonProperty("Map Generation Center")]
public Vector3 MapGenerationCenter { get; set; } = new Vector3(0, 0, 0);
[JsonProperty("Auto Detect Map Center")]
public bool AutoDetectMapCenter { get; set; } = true;
[JsonProperty("Large Map Mode")]
public bool LargeMapMode { get; set; } = true;
[JsonProperty("Map Search Radius")]
public float MapSearchRadius { get; set; } = 1450f; // Half of 2900
[JsonProperty("Multiple Map Zones")]
public bool MultipleMapZones { get; set; } = true;
[JsonProperty("Zone Spacing")]
public float ZoneSpacing { get; set; } = 500f;
[JsonProperty("Max Maps Per Server")]
public int MaxMapsPerServer { get; set; } = 8;
[JsonProperty("Backup Map Configurations")]
public bool BackupMapConfigs { get; set; } = true;
[JsonProperty("Map Generation Timeout")]
public float MapGenerationTimeout { get; set; } = 300f; // 5 minutes
[JsonProperty("Enable Map Validation")]
public bool EnableMapValidation { get; set; } = true;
[JsonProperty("Enable Voting System")]
public bool EnableVotingSystem { get; set; } = true;
[JsonProperty("Vote Duration")]
public float VoteDuration { get; set; } = 30f;
[JsonProperty("Minimum Vote Percentage")]
public float MinimumVotePercentage { get; set; } = 0.6f;
[JsonProperty("Require Vote Majority")]
public bool RequireVoteMajority { get; set; } = true;
[JsonProperty("Auto Execute Votes")]
public bool AutoExecuteVotes { get; set; } = true;
[JsonProperty("Allow Map Voting")]
public bool AllowMapVoting { get; set; } = true;
[JsonProperty("Allow Teleport Voting")]
public bool AllowTeleportVoting { get; set; } = true;
// Bug Fix: Add validation settings
[JsonProperty("Enable Safety Checks")]
public bool EnableSafetyChecks { get; set; } = true;
[JsonProperty("Max Queue Time")]
public float MaxQueueTime { get; set; } = 300f; // 5 minutes
[JsonProperty("Auto Cleanup Interval")]
public float AutoCleanupInterval { get; set; } = 60f; // 1 minute
[JsonProperty("Max Concurrent Matches")]
public int MaxConcurrentMatches { get; set; } = 10;
}
#endregion
#region Data Classes (Bug Fixed)
public class PlayerData
{
public ulong PlayerId { get; set; }
public string PlayerName { get; set; }
public Team CurrentTeam { get; set; } = Team.None;
public ulong CurrentMatchId { get; set; } = 0;
public bool IsInQueue { get; set; } = false;
public string QueueZoneName { get; set; } = "";
public float TeamDamageDealt { get; set; } = 0f;
public int Kills { get; set; } = 0;
public int Deaths { get; set; } = 0;
public int Assists { get; set; } = 0;
public bool IsSpectating { get; set; } = false;
public bool HasBuyMenuOpen { get; set; } = false;
public Dictionary<string, int> Inventory { get; set; } = new Dictionary<string, int>();
public Vector3 LastPosition { get; set; }
public bool IsDefusing { get; set; } = false;
public Timer DefuseTimer { get; set; }
public bool IsInFreeCam { get; set; } = false;
public ulong SpectatingPlayerId { get; set; } = 0;
public int SpectatorIndex { get; set; } = 0;
public bool IsTalking { get; set; } = false;
public float LastTalkTime { get; set; } = 0f;
public int RoundKills { get; set; } = 0;
public int RoundDeaths { get; set; } = 0;
public int HeadShots { get; set; } = 0;
public int RoundHeadShots { get; set; } = 0;
public float DamageDealt { get; set; } = 0f;
public float DamageTaken { get; set; } = 0f;
public int BombsPlanted { get; set; } = 0;
public int BombsDefused { get; set; } = 0;
public int MVPRounds { get; set; } = 0;
public float Accuracy { get; set; } = 0f;
public int ShotsHit { get; set; } = 0;
public int ShotsFired { get; set; } = 0;
public Dictionary<string, int> WeaponKills { get; set; } = new Dictionary<string, int>();
public int Rank { get; set; } = 1;
public int RankPoints { get; set; } = 1000;
public bool IsReady { get; set; } = false;
public string PreferredRole { get; set; } = "Rifler";
public List<string> OwnedSkins { get; set; } = new List<string>();
public Dictionary<string, object> Settings { get; set; } = new Dictionary<string, object>();
public float LastDamageTime { get; set; } = 0f;
public ulong LastAttackerId { get; set; } = 0;
public bool IsClutching { get; set; } = false;
public int ClutchKills { get; set; } = 0;
public List<Achievement> UnlockedAchievements { get; set; } = new List<Achievement>();
// Bug Fix: Add validation and safety
public float QueueJoinTime { get; set; } = 0f;
public float LastActionTime { get; set; } = 0f;
public bool IsProcessing { get; set; } = false;
public Vector3 SafePosition { get; set; } = Vector3.zero;
public List<string> ActiveTimers { get; set; } = new List<string>();
// Bug Fix: Prevent null reference exceptions
public bool IsValid()
{
return PlayerId != 0 && !string.IsNullOrEmpty(PlayerName);
}
}
public class QueueZone
{
public string Name { get; set; }
public Vector3 MinBounds { get; set; }
public Vector3 MaxBounds { get; set; }
public List<ulong> QueuedPlayers { get; set; } = new List<ulong>();
public int TeamSize { get; set; } = 5;
public bool IsActive { get; set; } = true;
public string SelectedMap { get; set; } = "dust2";
// Bug Fix: Add validation
public float CreatedTime { get; set; } = 0f;
public bool IsValid()
{
return !string.IsNullOrEmpty(Name) && TeamSize > 0 && TeamSize <= 10;
}
public Vector3 GetCenter()
{
return (MinBounds + MaxBounds) / 2f;
}
}
public class BombSite
{
public string Name { get; set; }
public Vector3 MinBounds { get; set; }
public Vector3 MaxBounds { get; set; }
public string MapName { get; set; }
public bool IsActive { get; set; } = true;
// Bug Fix: Add validation
public bool IsValid()
{
return !string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(MapName);
}
public bool ContainsPosition(Vector3 position)
{
return position.x >= MinBounds.x && position.x <= MaxBounds.x &&
position.y >= MinBounds.y && position.y <= MaxBounds.y &&
position.z >= MinBounds.z && position.z <= MaxBounds.z;
}
}
public class MapConfiguration
{
public string MapName { get; set; }
public List<Vector3> TerroristSpawns { get; set; } = new List<Vector3>();
public List<Vector3> CounterTerroristSpawns { get; set; } = new List<Vector3>();
public List<string> BombSiteNames { get; set; } = new List<string>();
public Vector3 SpectatorArea { get; set; }
// Bug Fix: Add validation
public bool IsValid()
{
return !string.IsNullOrEmpty(MapName) &&
TerroristSpawns.Count >= 5 &&
CounterTerroristSpawns.Count >= 5 &&
BombSiteNames.Count >= 2;
}
}
public class Match
{
public ulong MatchId { get; set; }
public List<ulong> TerroristPlayers { get; set; } = new List<ulong>();
public List<ulong> CounterTerroristPlayers { get; set; } = new List<ulong>();
public int TerroristScore { get; set; } = 0;
public int CounterTerroristScore { get; set; } = 0;
public int CurrentRound { get; set; } = 0; // Bug Fix: Start at 0, increment to 1
public MatchState State { get; set; } = MatchState.WaitingForPlayers;
public string MapName { get; set; } = "dust2";
public float RoundStartTime { get; set; }
public bool IsBuyPhase { get; set; } = false;
public PlantedBomb PlantedBomb { get; set; }
public bool HasSwappedSides { get; set; } = false;
public Timer RoundTimer { get; set; }
public Timer BuyPhaseTimer { get; set; }
public string QueueZoneName { get; set; }
// Bug Fix: Add safety and validation
public float MatchStartTime { get; set; } = 0f;
public List<Timer> ActiveTimers { get; set; } = new List<Timer>();
public bool IsValid()
{
return MatchId != 0 &&
TerroristPlayers.Count > 0 &&
CounterTerroristPlayers.Count > 0 &&
!string.IsNullOrEmpty(MapName);
}
public void CleanupTimers()
{
try
{
RoundTimer?.Destroy();
RoundTimer = null;
BuyPhaseTimer?.Destroy();
BuyPhaseTimer = null;
PlantedBomb?.ExplosionTimer?.Destroy();
if (PlantedBomb != null) PlantedBomb.ExplosionTimer = null;
foreach (var timer in ActiveTimers.ToList())
{
timer?.Destroy();
}
ActiveTimers.Clear();
}
catch (Exception ex)
{
// Handle cleanup errors gracefully
}
}
}
public class PlantedBomb
{
public Vector3 Position { get; set; }
public float PlantTime { get; set; }
public ulong PlanterId { get; set; }
public string BombSiteName { get; set; }
public Timer ExplosionTimer { get; set; }
public bool IsBeingDefused { get; set; } = false;
public ulong DefuserId { get; set; } = 0;
// Bug Fix: Add validation
public bool IsValid()
{
return PlanterId != 0 && !string.IsNullOrEmpty(BombSiteName) && PlantTime > 0;
}
public float GetTimeLeft(float bombTimer)
{
return bombTimer - (Time.time - PlantTime);
}
}
public class ClipboardData
{
public Vector3? PointA { get; set; }
public Vector3? PointB { get; set; }
public string PendingCommand { get; set; } = "";
public float LastUsed { get; set; } = 0f; // Bug Fix: Prevent spam
}
// Additional classes referenced in the original code
public class Vote
{
public ulong VoteId { get; set; }
public string VoteType { get; set; }
public string Description { get; set; }
public List<ulong> YesVotes { get; set; } = new List<ulong>();
public List<ulong> NoVotes { get; set; } = new List<ulong>();
public float StartTime { get; set; }
public float Duration { get; set; }
public bool IsActive { get; set; } = true;
}
public class GameMode
{
public string Name { get; set; }
public string Description { get; set; }
public Dictionary<string, object> Settings { get; set; } = new Dictionary<string, object>();
}
}
public class Callout
{
public string Name { get; set; }
public Vector3 Position { get; set; }
public string MapName { get; set; }
}
public class Warmup
{
public ulong WarmupId { get; set; }
public List<ulong> Players { get; set; } = new List<ulong>();
public bool IsActive { get; set; } = true;
}
public enum Team
{
None,
Terrorist,
CounterTerrorist
}
public enum MatchState
{
WaitingForPlayers,
InProgress,
Paused, // Bug Fix: Add paused state
Finished
}
public enum RoundEndReason
{
TerroristsEliminated,
CounterTerroristsEliminated,
BombExploded,
BombDefused,
TimeExpired,
MatchCancelled // Bug Fix: Add cancellation reason
}
#endregion
#region Plugin Initialization (Bug Fixed)
protected override void LoadDefaultConfig()
{
config = new Configuration();
SaveConfig();
}
private void LoadConfig()
{
try
{
config = Config.ReadObject<Configuration>();
if (config == null)
{
PrintWarning("Config is null, loading default config");
LoadDefaultConfig();
}
// Bug Fix: Validate config values
ValidateConfig();
}
catch (Exception ex)
{
PrintError($"Error loading config: {ex.Message}");
LoadDefaultConfig();
}
}
private void ValidateConfig()
{
// Bug Fix: Ensure config values are within reasonable bounds
if (config.DefaultTeamSize < 1) config.DefaultTeamSize = 5;
if (config.DefaultTeamSize > 10) config.DefaultTeamSize = 10;
if (config.RoundWinRequirement < 1) config.RoundWinRequirement = 13;
if (config.BuyPhaseDuration < 5f) config.BuyPhaseDuration = 20f;
if (config.BombTimer < 10f) config.BombTimer = 40f;
if (config.MaxTeamDamage < 100f) config.MaxTeamDamage = 300f;
if (config.MaxQueueTime < 60f) config.MaxQueueTime = 300f;
if (config.MaxConcurrentMatches < 1) config.MaxConcurrentMatches = 10;
}
private void SaveConfig()
{
try
{
Config.WriteObject(config);
}
catch (Exception ex)
{
PrintError($"Error saving config: {ex.Message}");
}
}
void Init()
{
LoadConfig();
permission.RegisterPermission(PERMISSION_ADMIN, this);
permission.RegisterPermission(PERMISSION_USE, this);
// Register commands with error handling
try
{
RegisterCommands();
}
catch (Exception ex)
{
PrintError($"Error registering commands: {ex.Message}");
}
// Initialize timers with error handling
try
{
InitializeTimers();
}
catch (Exception ex)
{
PrintError($"Error initializing timers: {ex.Message}");
}
}
private void RegisterCommands()
{
// Admin commands
cmd.AddChatCommand("setqzone", this, "CmdSetQueueZone");
cmd.AddChatCommand("removeqzone", this, "CmdRemoveQueueZone");
cmd.AddChatCommand("setbombsite", this, "CmdSetBombSite");
cmd.AddChatCommand("removebombsite", this, "CmdRemoveBombSite");
cmd.AddChatCommand("setspawn", this, "CmdSetSpawn");
cmd.AddChatCommand("removespawn", this, "CmdRemoveSpawn");
cmd.AddChatCommand("forcestart", this, "CmdForceStartMatch");
cmd.AddChatCommand("endmatch", this, "CmdEndMatch");
cmd.AddChatCommand("generatemap", this, "CmdGenerateMap");
cmd.AddChatCommand("clearmap", this, "CmdClearMap");
cmd.AddChatCommand("listmaps", this, "CmdListMaps");
cmd.AddChatCommand("teleportmap", this, "CmdTeleportToMap");
cmd.AddChatCommand("cs2debug", this, "CmdDebugInfo");
// Player commands
cmd.AddChatCommand("buymenu", this, "CmdBuyMenu");
cmd.AddChatCommand("joinqueue", this, "CmdJoinQueue");
cmd.AddChatCommand("leavequeue", this, "CmdLeaveQueue");
cmd.AddChatCommand("cs2stats", this, "CmdShowStats");
cmd.AddChatCommand("spec", this, "CmdSpectate");
cmd.AddChatCommand("specnext", this, "CmdSpectateNext");
cmd.AddChatCommand("specprev", this, "CmdSpectatePrevious");
cmd.AddChatCommand("freecam", this, "CmdToggleFreeCam");
// Additional commands from original
cmd.AddChatCommand("generateultramap", this, "CmdGenerateUltraMap");
cmd.AddChatCommand("confirmultramap", this, "CmdConfirmUltraMap");
cmd.AddChatCommand("validatemap", this, "CmdValidateMap");
cmd.AddChatCommand("exportmap", this, "CmdExportMap");
cmd.AddChatCommand("importmap", this, "CmdImportMap");
cmd.AddChatCommand("addbot", this, "CmdAddBot");
cmd.AddChatCommand("skins", this, "CmdSkins");
cmd.AddChatCommand("skinshop", this, "CmdSkinShop");
cmd.AddChatCommand("mykins", this, "CmdMySkins");
cmd.AddChatCommand("heatmap", this, "CmdHeatMap");
cmd.AddChatCommand("playerstats", this, "CmdPlayerStats");
cmd.AddChatCommand("createtournament", this, "CmdCreateTournament");
cmd.AddChatCommand("jointeam", this, "CmdJoinTournament");
cmd.AddChatCommand("starttournament", this, "CmdStartTournament");
cmd.AddChatCommand("votemap", this, "CmdVoteMap");
cmd.AddChatCommand("vote", this, "CmdVote");
cmd.AddChatCommand("teleportvote", this, "CmdTeleportVote");
cmd.AddChatCommand("forcevote", this, "CmdForceVote");
// Voting commands
cmd.AddChatCommand("votekick", this, "CmdVoteKick");
cmd.AddChatCommand("surrender", this, "CmdVoteSurrender");
cmd.AddChatCommand("yes", this, "CmdVoteYes");
cmd.AddChatCommand("no", this, "CmdVoteNo");
cmd.AddChatCommand("f1", this, "CmdVoteYes");
cmd.AddChatCommand("f2", this, "CmdVoteNo");
// Economy commands
cmd.AddChatCommand("money", this, "CmdShowMoney");
cmd.AddChatCommand("buy", this, "CmdQuickBuy");
// Leaderboard commands
cmd.AddChatCommand("leaderboard", this, "CmdLeaderboard");
cmd.AddChatCommand("top", this, "CmdLeaderboard");
cmd.AddChatCommand("rank", this, "CmdShowRank");
// Achievement commands
cmd.AddChatCommand("achievements", this, "CmdAchievements");
cmd.AddChatCommand("progress", this, "CmdAchievements");
// Tournament commands
cmd.AddChatCommand("createtournament", this, "CmdCreateTournament");
cmd.AddChatCommand("jointournament", this, "CmdJoinTournament");
cmd.AddChatCommand("tournaments", this, "CmdListTournaments");
// Warmup commands
cmd.AddChatCommand("warmup", this, "CmdToggleWarmup");
cmd.AddChatCommand("ready", this, "CmdPlayerReady");
// Clan commands
cmd.AddChatCommand("createclan", this, "CmdCreateClan");
cmd.AddChatCommand("joinclan", this, "CmdJoinClan");
cmd.AddChatCommand("leaveclan", this, "CmdLeaveClan");
cmd.AddChatCommand("clan", this, "CmdClanInfo");
// Admin commands
cmd.AddChatCommand("pause", this, "CmdPauseMatch");
cmd.AddChatCommand("unpause", this, "CmdUnpauseMatch");
cmd.AddChatCommand("restart", this, "CmdRestartRound");
cmd.AddChatCommand("knife", this, "CmdKnifeRound");
// UI commands
cmd.AddChatCommand("cs2.closebuy", this, "CmdCloseBuyMenu");
cmd.AddChatCommand("cs2.buy", this, "CmdBuyItem");
cmd.AddChatCommand("cs2.voteyes", this, "CmdVoteYes");
cmd.AddChatCommand("cs2.voteno", this, "CmdVoteNo");
cmd.AddChatCommand("cs2.closeleaderboard", this, "CmdCloseLeaderboard");
cmd.AddChatCommand("cs2.closeachievements", this, "CmdCloseAchievements");
}
private void InitializeTimers()
{
uiUpdateTimer = timer.Every(2f, UpdateAllUI); // Bug Fix: Reduced frequency
matchUpdateTimer = timer.Every(0.5f, UpdateMatches); // Bug Fix: Reduced frequency
cleanupTimer = timer.Every(config.AutoCleanupInterval, PerformCleanup);
}
void OnServerInitialized()
{
try
{
LoadData();
InitializeDefaultMaps();
InitializeWeaponSkins();
if (config.GenerateMapOnStart)
{
timer.Once(10f, () => SafeAutoGenerateServerMaps());
}
if (config.AutoDetectMapCenter)
{
DetectMapCenter();
}
if (config.EnableMapValidation)
{
ValidateAllMaps();
}
PrintWarning("CS2 Style Plugin loaded successfully!");
PrintWarning($"Map generation center: {config.MapGenerationCenter}");
PrintWarning($"Available maps: {mapConfigurations.Count}");
PrintWarning($"Available skins: {availableSkins.Count}");
PrintWarning($"Large map mode: {config.LargeMapMode}");
}
catch (Exception ex)
{
PrintError($"Error during server initialization: {ex.Message}");
}
}
void Unload()
{
try
{
SaveData();
// Bug Fix: Proper cleanup of all resources
CleanupAllResources();
PrintWarning("CS2 Style Plugin unloaded successfully!");
}
catch (Exception ex)
{
PrintError($"Error during plugin unload: {ex.Message}");
}
}
private void CleanupAllResources()
{
// Clean up timers safely
DestroyTimer(ref uiUpdateTimer);
DestroyTimer(ref matchUpdateTimer);
DestroyTimer(ref cleanupTimer);
// Clean up all active matches
SafeModifyCollection(() =>
{
foreach (var match in activeMatches.Values.ToList())
{
try
{
EndMatch(match, "Plugin unloaded");
}
catch (Exception ex)
{
PrintError($"Error ending match {match.MatchId}: {ex.Message}");
}
}
});
// Clean up player data timers
foreach (var data in playerData.Values.ToList())
{
try
{
data.DefuseTimer?.Destroy();
data.DefuseTimer = null;
}
catch (Exception ex)
{
PrintError($"Error cleaning up player {data.PlayerId}: {ex.Message}");
}
}
// Destroy all UI elements safely
foreach (var player in BasePlayer.activePlayerList.ToList())
{
if (IsValidPlayer(player))
{
SafeDestroyUI(player, "LobbyUI");
SafeDestroyUI(player, "MatchUI");
SafeDestroyUI(player, "BuyMenu");
SafeDestroyUI(player, "SpectatorUI");
SafeDestroyUI(player, "FreeCamUI");
SafeDestroyUI(player, "VoiceChatUI");
SafeDestroyUI(player, "VoteUI");
}
}
// Clear collections
processingPlayers.Clear();
lastActionTime.Clear();
}
#endregion
#region Safety and Validation (Bug Fixes)
// Thread-safe collection modification
private void SafeModifyCollection(Action action)
{
lock (_lockObject)
{
try
{
action();
}
catch (Exception ex)
{
PrintError($"Error in SafeModifyCollection: {ex.Message}");
}
}
}
// Enhanced timer management
private void DestroyTimer(ref Timer timer)
{
try
{
timer?.Destroy();
timer = null;
}
catch (Exception ex)
{
PrintError($"Error destroying timer: {ex.Message}");
}
}
// Enhanced player validation
private bool IsValidPlayer(BasePlayer player)
{
return player != null &&
player.IsConnected &&
!player.IsDestroyed &&
player.net?.connection != null;
}
// Safe player data access
private PlayerData GetSafePlayerData(ulong playerId)
{
if (playerId == 0) return null;
lock (_lockObject)
{
if (!playerData.ContainsKey(playerId))
{
var player = BasePlayer.FindByID(playerId);
if (!IsValidPlayer(player)) return null;
playerData[playerId] = new PlayerData
{
PlayerId = playerId,
PlayerName = player.displayName,
JoinTime = DateTime.Now,
IsProcessing = false
};
}
return playerData[playerId];
}
}
// Safe match access
private Match GetSafeMatch(ulong matchId)
{
lock (_lockObject)
{
return activeMatches.ContainsKey(matchId) ? activeMatches[matchId] : null;
}
}
// Safe collection iteration with modification
private void SafeIterateAndModify<T>(ICollection<T> collection, Func<T, bool> shouldRemove, Action<T> onRemove = null)
{
lock (_lockObject)
{
var itemsToRemove = collection.Where(shouldRemove).ToList();
foreach (var item in itemsToRemove)
{
collection.Remove(item);
onRemove?.Invoke(item);
}
}
}
// Enhanced UI cleanup
private void SafeDestroyUI(BasePlayer player, string uiName)
{
if (!IsValidPlayer(player)) return;
try
{
CuiHelper.DestroyUi(player, uiName);
}
catch (Exception ex)
{
PrintError($"Error destroying UI '{uiName}' for {player.displayName}: {ex.Message}");
}
}
// Resource cleanup helper
private void CleanupPlayerResources(ulong playerId)
{
SafeModifyCollection(() =>
{
// Clean up player data
if (playerData.ContainsKey(playerId))
{
var data = playerData[playerId];
data.CleanupTimers();
playerData.Remove(playerId);
}
// Clean up from queues
foreach (var zone in queueZones.Values.ToList())
{
zone.QueuedPlayers.Remove(playerId);
}
// Clean up from matches
foreach (var match in activeMatches.Values.ToList())
{
match.TerroristPlayers.Remove(playerId);
match.CounterTerroristPlayers.Remove(playerId);
}
// Clean up processing states
processingPlayers.Remove(playerId);
lastActionTime.Remove(playerId);
});
}
private bool CanPlayerPerformAction(BasePlayer player)
{
if (player == null || !player.IsConnected) return false;
ulong playerId = player.userID;
// Bug Fix: Prevent spam actions
if (lastActionTime.ContainsKey(playerId))
{
if (Time.time - lastActionTime[playerId] < ACTION_COOLDOWN)
{
return false;
}
}
// Bug Fix: Prevent concurrent processing
if (processingPlayers.Contains(playerId))
{
return false;
}
return true;
}
private void StartPlayerAction(BasePlayer player)
{
if (player == null) return;
ulong playerId = player.userID;
lastActionTime[playerId] = Time.time;
processingPlayers.Add(playerId);
var data = GetPlayerData(playerId);
if (data != null)
{
data.IsProcessing = true;
data.LastActionTime = Time.time;
}
}
private void EndPlayerAction(BasePlayer player)
{
if (player == null) return;
ulong playerId = player.userID;
processingPlayers.Remove(playerId);
var data = GetPlayerData(playerId);
if (data != null)
{
data.IsProcessing = false;
}
}
private bool IsValidPosition(Vector3 position)
{
// Bug Fix: Validate position is not NaN or infinity
if (float.IsNaN(position.x) || float.IsNaN(position.y) || float.IsNaN(position.z))
return false;
if (float.IsInfinity(position.x) || float.IsInfinity(position.y) || float.IsInfinity(position.z))
return false;
// Bug Fix: Check if position is within reasonable bounds
if (Mathf.Abs(position.x) > 10000f || Mathf.Abs(position.z) > 10000f)
return false;
if (position.y < -1000f || position.y > 1000f)
return false;
return true;
}
private Vector3 GetSafePosition(Vector3 position)
{
if (IsValidPosition(position))
return position;
// Bug Fix: Return a safe fallback position
return config.LobbySpawnPoint != Vector3.zero ? config.LobbySpawnPoint : Vector3.zero;
}
private void PerformCleanup()
{
try
{
CleanupExpiredQueues();
CleanupInactiveMatches();
CleanupDisconnectedPlayers();
CleanupOrphanedTimers();
}
catch (Exception ex)
{
PrintError($"Error during cleanup: {ex.Message}");
}
}
private void CleanupExpiredQueues()
{
SafeModifyCollection(() =>
{
foreach (var zone in queueZones.Values.ToList())
{
SafeIterateAndModify(zone.QueuedPlayers, playerId =>
{
var data = GetPlayerData(playerId);
return data == null || !data.IsInQueue ||
(data.QueueJoinTime > 0 && Time.time - data.QueueJoinTime > config.MaxQueueTime);
}, playerId =>
{
var data = GetPlayerData(playerId);
if (data != null)
{
data.IsInQueue = false;
data.QueueZoneName = "";
}
});
}
});
}
private void CleanupInactiveMatches()
{
SafeModifyCollection(() =>
{
var inactiveMatches = activeMatches.Where(kvp =>
{
var match = kvp.Value;
// Check if match has been running too long without activity
if (Time.time - match.MatchStartTime > 3600f) return true; // 1 hour
// Check if all players have disconnected
var connectedPlayers = match.TerroristPlayers.Concat(match.CounterTerroristPlayers)
.Count(id => IsValidPlayer(BasePlayer.FindByID(id)));
return connectedPlayers == 0;
}).Select(kvp => kvp.Key).ToList();
foreach (var matchId in inactiveMatches)
{
if (activeMatches.ContainsKey(matchId))
{
EndMatch(activeMatches[matchId], "Match cleanup - inactive");
}
}
});
}
private void CleanupDisconnectedPlayers()
{
SafeModifyCollection(() =>
{
var disconnectedPlayers = playerData.Where(kvp =>
{
var playerId = kvp.Key;
var player = BasePlayer.FindByID(playerId);
return !IsValidPlayer(player);
}).Select(kvp => kvp.Key).ToList();
foreach (var playerId in disconnectedPlayers)
{
CleanupPlayerResources(playerId);
}
});
}
if (data.CurrentMatchId != 0)
{
if (activeMatches.ContainsKey(data.CurrentMatchId))
{
var match = activeMatches[data.CurrentMatchId];
match.TerroristPlayers.Remove(playerId);
match.CounterTerroristPlayers.Remove(playerId);
}
}
// Clean up queue involvement
if (data.IsInQueue && !string.IsNullOrEmpty(data.QueueZoneName))
{
if (queueZones.ContainsKey(data.QueueZoneName))
{
queueZones[data.QueueZoneName].QueuedPlayers.Remove(playerId);
}
}
// Clean up timers
data.DefuseTimer?.Destroy();
disconnectedPlayers.Add(playerId);
}
}
foreach (var playerId in disconnectedPlayers)
{
playerData.Remove(playerId);
}
}
private void CleanupOrphanedTimers()
{
// This would be implemented to clean up any timers that might be orphaned
// In a real implementation, we'd track all timers and clean up unused ones
}
#endregion
#region Data Management (Bug Fixed)
private void LoadData()
{
try
{
playerData = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, PlayerData>>("CS2Plugin_PlayerData") ?? new Dictionary<ulong, PlayerData>();
queueZones = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, QueueZone>>("CS2Plugin_QueueZones") ?? new Dictionary<string, QueueZone>();
bombSites = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, BombSite>>("CS2Plugin_BombSites") ?? new Dictionary<string, BombSite>();
mapConfigurations = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, MapConfiguration>>("CS2Plugin_MapConfigurations") ?? new Dictionary<string, MapConfiguration>();
// Bug Fix: Validate loaded data
ValidateLoadedData();
}
catch (Exception ex)
{
PrintError($"Error loading data: {ex.Message}");
// Initialize empty collections on error
playerData = new Dictionary<ulong, PlayerData>();
queueZones = new Dictionary<string, QueueZone>();
bombSites = new Dictionary<string, BombSite>();
mapConfigurations = new Dictionary<string, MapConfiguration>();
}
}
private void ValidateLoadedData()
{
// Bug Fix: Remove invalid player data
var invalidPlayers = playerData.Where(kvp => !kvp.Value.IsValid()).Select(kvp => kvp.Key).ToList();
foreach (var playerId in invalidPlayers)
{
playerData.Remove(playerId);
PrintWarning($"Removed invalid player data for {playerId}");
}
// Bug Fix: Remove invalid queue zones
var invalidZones = queueZones.Where(kvp => !kvp.Value.IsValid()).Select(kvp => kvp.Key).ToList();
foreach (var zoneName in invalidZones)
{
queueZones.Remove(zoneName);
PrintWarning($"Removed invalid queue zone: {zoneName}");
}
// Bug Fix: Remove invalid bomb sites
var invalidSites = bombSites.Where(kvp => !kvp.Value.IsValid()).Select(kvp => kvp.Key).ToList();
foreach (var siteName in invalidSites)
{
bombSites.Remove(siteName);
PrintWarning($"Removed invalid bomb site: {siteName}");
}
// Bug Fix: Remove invalid map configurations
var invalidMaps = mapConfigurations.Where(kvp => !kvp.Value.IsValid()).Select(kvp => kvp.Key).ToList();
foreach (var mapName in invalidMaps)
{
mapConfigurations.Remove(mapName);
PrintWarning($"Removed invalid map configuration: {mapName}");
}
}
private void SaveData()
{
try
{
Interface.Oxide.DataFileSystem.WriteObject("CS2Plugin_PlayerData", playerData);
Interface.Oxide.DataFileSystem.WriteObject("CS2Plugin_QueueZones", queueZones);
Interface.Oxide.DataFileSystem.WriteObject("CS2Plugin_BombSites", bombSites);
Interface.Oxide.DataFileSystem.WriteObject("CS2Plugin_MapConfigurations", mapConfigurations);
}
catch (Exception ex)
{
PrintError($"Error saving data: {ex.Message}");
}
}
private PlayerData GetPlayerData(ulong playerId)
{
// Use the safer version
return GetSafePlayerData(playerId);
}
private void InitializeDefaultMaps()
{
try
{
// Initialize Dust2 configuration with validation
if (!mapConfigurations.ContainsKey("dust2"))
{
var dust2Config = new MapConfiguration
{
MapName = "dust2",
SpectatorArea = new Vector3(0, 50, 0)
};
// Generate spawn points with proper spacing
for (int i = 0; i < 8; i++)
{
dust2Config.TerroristSpawns.Add(new Vector3(100 + i * 3f, 0, 100));
dust2Config.CounterTerroristSpawns.Add(new Vector3(-100 + i * 3f, 0, -100));
}
dust2Config.BombSiteNames.Add("dust2_A");
dust2Config.BombSiteNames.Add("dust2_B");
mapConfigurations["dust2"] = dust2Config;
}
// Initialize Train configuration
if (!mapConfigurations.ContainsKey("train"))
{
var trainConfig = new MapConfiguration
{
MapName = "train",
SpectatorArea = new Vector3(0, 75, 0)
};
for (int i = 0; i < 8; i++)
{
trainConfig.TerroristSpawns.Add(new Vector3(200 + i * 3f, 0, 200));
trainConfig.CounterTerroristSpawns.Add(new Vector3(-200 + i * 3f, 0, -200));
}
trainConfig.BombSiteNames.Add("train_A");
trainConfig.BombSiteNames.Add("train_B");
mapConfigurations["train"] = trainConfig;
}
}
catch (Exception ex)
{
PrintError($"Error initializing default maps: {ex.Message}");
}
}
private void InitializeWeaponSkins()
{
// ... [Code preview truncated due to length. Full source in attached file] ...