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", "ModernCS2", "2.0.0")]
[Description("Modern CS2-style competitive gameplay plugin for Rust")]
public class CS2Plugin : RustPlugin
{
#region Fields and Configuration
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 const string PERMISSION_ADMIN = "cs2plugin.admin";
private const string PERMISSION_USE = "cs2plugin.use";
private Timer uiUpdateTimer;
private Timer matchUpdateTimer;
#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;
[JsonProperty("Multiple Map Zones")]
public bool MultipleMapZones { get; set; } = true;
}
#endregion
#region Data Classes
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 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 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";
}
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;
}
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; }
}
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; } = 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; }
}
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;
}
public class ClipboardData
{
public Vector3? PointA { get; set; }
public Vector3? PointB { get; set; }
public string PendingCommand { get; set; } = "";
}
public enum Team
{
None,
Terrorist,
CounterTerrorist
}
public enum MatchState
{
WaitingForPlayers,
InProgress,
Finished
}
public enum RoundEndReason
{
TerroristsEliminated,
CounterTerroristsEliminated,
BombExploded,
BombDefused,
TimeExpired
}
#endregion
#region Plugin Initialization
protected override void LoadDefaultConfig()
{
config = new Configuration();
SaveConfig();
}
private void LoadConfig()
{
try
{
config = Config.ReadObject<Configuration>();
if (config == null) LoadDefaultConfig();
}
catch
{
LoadDefaultConfig();
}
}
private void SaveConfig() => Config.WriteObject(config);
void Init()
{
LoadConfig();
permission.RegisterPermission(PERMISSION_ADMIN, this);
permission.RegisterPermission(PERMISSION_USE, this);
// Register 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("buymenu", this, "CmdBuyMenu");
cmd.AddChatCommand("joinqueue", this, "CmdJoinQueue");
cmd.AddChatCommand("leavequeue", this, "CmdLeaveQueue");
cmd.AddChatCommand("forcestart", this, "CmdForceStartMatch");
cmd.AddChatCommand("endmatch", this, "CmdEndMatch");
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");
cmd.AddChatCommand("generatemap", this, "CmdGenerateMap");
cmd.AddChatCommand("clearmap", this, "CmdClearMap");
cmd.AddChatCommand("listmaps", this, "CmdListMaps");
cmd.AddChatCommand("teleportmap", this, "CmdTeleportToMap");
// Initialize timers
uiUpdateTimer = timer.Every(1f, UpdateAllUI);
matchUpdateTimer = timer.Every(0.1f, UpdateMatches);
}
void OnServerInitialized()
{
LoadData();
// Initialize default map configurations
InitializeDefaultMaps();
// Auto-generate maps if enabled
if (config.GenerateMapOnStart)
{
timer.Once(10f, () => AutoGenerateServerMaps());
}
// Auto-detect map center if enabled
if (config.AutoDetectMapCenter)
{
DetectMapCenter();
}
PrintWarning("CS2 Style Plugin loaded successfully!");
PrintWarning($"Map generation center: {config.MapGenerationCenter}");
PrintWarning($"Available maps: {mapConfigurations.Count}");
PrintWarning($"Large map mode: {config.LargeMapMode}");
}
void Unload()
{
SaveData();
// Clean up timers
uiUpdateTimer?.Destroy();
matchUpdateTimer?.Destroy();
// Clean up all active matches
foreach (var match in activeMatches.Values)
{
EndMatch(match, "Plugin unloaded");
}
// Destroy all UI elements
foreach (var player in BasePlayer.activePlayerList)
{
DestroyUI(player);
}
}
#endregion
#region Data Management
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>();
}
catch (Exception ex)
{
PrintError($"Error loading data: {ex.Message}");
}
}
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)
{
if (!playerData.ContainsKey(playerId))
{
var player = BasePlayer.FindByID(playerId);
playerData[playerId] = new PlayerData
{
PlayerId = playerId,
PlayerName = player?.displayName ?? "Unknown"
};
}
return playerData[playerId];
}
private void InitializeDefaultMaps()
{
// Initialize Dust2 configuration
if (!mapConfigurations.ContainsKey("dust2"))
{
mapConfigurations["dust2"] = new MapConfiguration
{
MapName = "dust2",
TerroristSpawns = new List<Vector3>
{
new Vector3(100, 0, 100),
new Vector3(105, 0, 100),
new Vector3(110, 0, 100),
new Vector3(115, 0, 100),
new Vector3(120, 0, 100)
},
CounterTerroristSpawns = new List<Vector3>
{
new Vector3(-100, 0, -100),
new Vector3(-105, 0, -100),
new Vector3(-110, 0, -100),
new Vector3(-115, 0, -100),
new Vector3(-120, 0, -100)
},
SpectatorArea = new Vector3(0, 50, 0)
};
}
// Initialize Train configuration
if (!mapConfigurations.ContainsKey("train"))
{
mapConfigurations["train"] = new MapConfiguration
{
MapName = "train",
TerroristSpawns = new List<Vector3>
{
new Vector3(200, 0, 200),
new Vector3(205, 0, 200),
new Vector3(210, 0, 200),
new Vector3(215, 0, 200),
new Vector3(220, 0, 200)
},
CounterTerroristSpawns = new List<Vector3>
{
new Vector3(-200, 0, -200),
new Vector3(-205, 0, -200),
new Vector3(-210, 0, -200),
new Vector3(-215, 0, -200),
new Vector3(-220, 0, -200)
},
SpectatorArea = new Vector3(0, 75, 0)
};
}
}
#endregion
#region Modern Map Detection System
private void AutoGenerateServerMaps()
{
PrintWarning("Starting automatic map generation for large Rust map...");
// Check if we already have maps configured
if (mapConfigurations.Count > 2)
{
PrintWarning("Maps already exist, skipping auto-generation.");
return;
}
// Detect map size and adjust accordingly
float mapSize = GetMapSize();
PrintWarning($"Detected map size: {mapSize}");
if (mapSize >= 2000f) // Large map
{
GenerateMultipleCompetitiveZones();
}
else
{
GenerateSingleCompetitiveZone();
}
PrintWarning("Automatic map generation completed!");
}
private float GetMapSize()
{
try
{
if (TerrainMeta.Size != null)
{
return TerrainMeta.Size.x;
}
}
catch
{
// Fallback for older versions
}
// Default assumption for large maps
return 2900f;
}
private void GenerateMultipleCompetitiveZones()
{
PrintWarning("Generating multiple competitive zones for large map...");
float mapSize = GetMapSize();
float radius = mapSize * 0.3f; // 30% of map size
// Generate 4 competitive zones in different quadrants
var zones = new List<Vector3>
{
new Vector3(radius * 0.5f, 0, radius * 0.5f), // NE
new Vector3(-radius * 0.5f, 0, radius * 0.5f), // NW
new Vector3(radius * 0.5f, 0, -radius * 0.5f), // SE
new Vector3(-radius * 0.5f, 0, -radius * 0.5f) // SW
};
for (int i = 0; i < zones.Count; i++)
{
Vector3 center = FindSafeLocation(zones[i]);
GenerateCompetitiveZone($"zone_{i + 1}", center, i);
}
}
private void GenerateSingleCompetitiveZone()
{
PrintWarning("Generating single competitive zone...");
Vector3 center = config.MapGenerationCenter;
if (center == Vector3.zero)
{
center = FindSafeLocation(Vector3.zero);
}
GenerateCompetitiveZone("main_zone", center, 0);
}
private Vector3 FindSafeLocation(Vector3 preferredLocation)
{
Vector3 bestLocation = preferredLocation;
try
{
// Try to find a location that's not in water and has reasonable terrain
for (int attempts = 0; attempts < 10; attempts++)
{
Vector3 testLocation = preferredLocation + new Vector3(
UnityEngine.Random.Range(-200f, 200f),
0,
UnityEngine.Random.Range(-200f, 200f)
);
// Get terrain height
float height = GetTerrainHeight(testLocation);
testLocation.y = height;
// Check if location is suitable
if (IsLocationSuitable(testLocation))
{
bestLocation = testLocation;
break;
}
}
}
catch (Exception ex)
{
PrintWarning($"Error finding safe location: {ex.Message}");
}
return bestLocation;
}
private float GetTerrainHeight(Vector3 position)
{
try
{
if (TerrainMeta.HeightMap != null)
{
return TerrainMeta.HeightMap.GetHeight(position);
}
}
catch
{
// Fallback for older versions
}
// Use raycast as fallback
RaycastHit hit;
if (Physics.Raycast(position + Vector3.up * 200f, Vector3.down, out hit, 400f, LayerMask.GetMask("Terrain")))
{
return hit.point.y;
}
return 0f;
}
private bool IsLocationSuitable(Vector3 location)
{
try
{
// Check if in water
if (WaterLevel.Test(location))
{
return false;
}
// Check for existing entities
var entities = new List<BaseEntity>();
Vis.Entities(location, 50f, entities);
// Too many existing structures
if (entities.Count > 20)
{
return false;
}
return true;
}
catch
{
return true; // Assume suitable if checks fail
}
}
private void GenerateCompetitiveZone(string zoneName, Vector3 center, int zoneIndex)
{
PrintWarning($"Generating competitive zone: {zoneName} at {center}");
// Create map configuration
var mapConfig = new MapConfiguration
{
MapName = zoneName,
SpectatorArea = center + Vector3.up * 20f
};
// Generate spawn points
GenerateSpawnPoints(mapConfig, center);
// Generate bomb sites
GenerateBombSites(mapConfig, center);
// Generate structures
GenerateMapStructures(center, zoneName);
// Save configuration
mapConfigurations[zoneName] = mapConfig;
// Create queue zone
CreateQueueZone(zoneName, center);
PrintWarning($"Zone {zoneName} generated successfully!");
}
private void GenerateSpawnPoints(MapConfiguration mapConfig, Vector3 center)
{
// Terrorist spawns (one side)
for (int i = 0; i < 8; i++)
{
Vector3 spawnPos = center + new Vector3(-60f + i * 3f, 0, -80f);
spawnPos.y = GetTerrainHeight(spawnPos) + 1f;
mapConfig.TerroristSpawns.Add(spawnPos);
}
// Counter-Terrorist spawns (opposite side)
for (int i = 0; i < 8; i++)
{
Vector3 spawnPos = center + new Vector3(-60f + i * 3f, 0, 80f);
spawnPos.y = GetTerrainHeight(spawnPos) + 1f;
mapConfig.CounterTerroristSpawns.Add(spawnPos);
}
}
private void GenerateBombSites(MapConfiguration mapConfig, Vector3 center)
{
// Bomb site A
Vector3 siteA = center + new Vector3(-40f, 0, 40f);
siteA.y = GetTerrainHeight(siteA);
string siteAName = $"{mapConfig.MapName}_A";
bombSites[siteAName] = new BombSite
{
Name = siteAName,
MapName = mapConfig.MapName,
MinBounds = siteA - Vector3.one * 8f,
MaxBounds = siteA + Vector3.one * 8f,
IsActive = true
};
mapConfig.BombSiteNames.Add(siteAName);
// Bomb site B
Vector3 siteB = center + new Vector3(40f, 0, -40f);
siteB.y = GetTerrainHeight(siteB);
string siteBName = $"{mapConfig.MapName}_B";
bombSites[siteBName] = new BombSite
{
Name = siteBName,
MapName = mapConfig.MapName,
MinBounds = siteB - Vector3.one * 8f,
MaxBounds = siteB + Vector3.one * 8f,
IsActive = true
};
mapConfig.BombSiteNames.Add(siteBName);
}
private void GenerateMapStructures(Vector3 center, string mapName)
{
try
{
// Generate basic structures using modern Rust entities
CreateWalls(center);
CreateCover(center);
CreateLighting(center);
}
catch (Exception ex)
{
PrintWarning($"Error generating structures for {mapName}: {ex.Message}");
}
}
private void CreateWalls(Vector3 center)
{
// Create walls using modern building blocks
var wallPositions = new List<Vector3>
{
center + new Vector3(-50f, 0, 0),
center + new Vector3(50f, 0, 0),
center + new Vector3(0, 0, -50f),
center + new Vector3(0, 0, 50f)
};
foreach (var pos in wallPositions)
{
Vector3 groundPos = pos;
groundPos.y = GetTerrainHeight(pos);
// Create wall entity
var wall = GameManager.server.CreateEntity("assets/prefabs/building core/wall/wall.prefab", groundPos);
if (wall != null)
{
wall.Spawn();
}
}
}
private void CreateCover(Vector3 center)
{
// Create cover objects
var coverPositions = new List<Vector3>
{
center + new Vector3(-20f, 0, 20f),
center + new Vector3(20f, 0, -20f),
center + new Vector3(-20f, 0, -20f),
center + new Vector3(20f, 0, 20f)
};
foreach (var pos in coverPositions)
{
Vector3 groundPos = pos;
groundPos.y = GetTerrainHeight(pos);
// Create cover entity
var cover = GameManager.server.CreateEntity("assets/prefabs/deployable/large wood storage/box.large.wood.prefab", groundPos);
if (cover != null)
{
cover.Spawn();
}
}
}
private void CreateLighting(Vector3 center)
{
// Create lighting for the area
var lightPositions = new List<Vector3>
{
center + new Vector3(0, 6f, 0),
center + new Vector3(-30f, 6f, 30f),
center + new Vector3(30f, 6f, -30f)
};
foreach (var pos in lightPositions)
{
Vector3 lightPos = pos;
lightPos.y = GetTerrainHeight(new Vector3(pos.x, 0, pos.z)) + 6f;
// Create light entity
var light = GameManager.server.CreateEntity("assets/prefabs/deployable/lantern/lantern.deployed.prefab", lightPos);
if (light != null)
{
light.Spawn();
}
}
}
private void CreateQueueZone(string mapName, Vector3 center)
{
Vector3 queueCenter = center + new Vector3(0, 0, -120f);
queueCenter.y = GetTerrainHeight(queueCenter);
queueZones[mapName + "_queue"] = new QueueZone
{
Name = mapName + "_queue",
MinBounds = queueCenter - new Vector3(10f, 5f, 10f),
MaxBounds = queueCenter + new Vector3(10f, 5f, 10f),
TeamSize = config.DefaultTeamSize,
IsActive = true,
SelectedMap = mapName
};
PrintWarning($"Queue zone created for {mapName} at {queueCenter}");
}
private void DetectMapCenter()
{
if (config.MapGenerationCenter != Vector3.zero) return;
try
{
// Try to get map bounds
if (TerrainMeta.Bounds.size != Vector3.zero)
{
config.MapGenerationCenter = TerrainMeta.Bounds.center;
}
else
{
config.MapGenerationCenter = Vector3.zero;
}
}
catch
{
config.MapGenerationCenter = Vector3.zero;
}
SaveConfig();
PrintWarning($"Map center detected: {config.MapGenerationCenter}");
}
#endregion
#region Player Events (Modern)
void OnPlayerConnected(BasePlayer player)
{
if (player == null) return;
var data = GetPlayerData(player.userID);
data.PlayerName = player.displayName;
// If player was in a match, handle reconnection
if (data.CurrentMatchId != 0 && activeMatches.ContainsKey(data.CurrentMatchId))
{
var match = activeMatches[data.CurrentMatchId];
if (match.State == MatchState.InProgress)
{
timer.Once(2f, () => HandlePlayerReconnection(player, match));
}
}
timer.Once(1f, () => CreateUI(player));
}
void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (player == null) return;
var data = GetPlayerData(player.userID);
// Remove from queue if in one
if (data.IsInQueue)
{
RemovePlayerFromQueue(player.userID);
}
// Handle match disconnection
if (data.CurrentMatchId != 0 && activeMatches.ContainsKey(data.CurrentMatchId))
{
var match = activeMatches[data.CurrentMatchId];
PrintToChat($"{player.displayName} disconnected from match {match.MatchId}");
}
DestroyUI(player);
}
void OnPlayerDeath(BasePlayer victim, HitInfo info)
{
if (victim == null) return;
var victimData = GetPlayerData(victim.userID);
if (victimData.CurrentMatchId == 0) return;
if (!activeMatches.ContainsKey(victimData.CurrentMatchId)) return;
var match = activeMatches[victimData.CurrentMatchId];
if (match.State != MatchState.InProgress) return;
victimData.Deaths++;
victimData.RoundDeaths++;
BasePlayer attacker = info?.InitiatorPlayer;
if (attacker != null && attacker != victim)
{
var attackerData = GetPlayerData(attacker.userID);
// Check for team kill
if (victimData.CurrentTeam == attackerData.CurrentTeam)
{
HandleTeamKill(attacker, victim, info);
}
else
{
attackerData.Kills++;
attackerData.RoundKills++;
PrintToChat($"{attacker.displayName} eliminated {victim.displayName}");
}
}
// Move player to spectator area
timer.Once(2f, () => MoveToSpectatorArea(victim, match));
// Check if round should end
CheckRoundEnd(match);
}
void OnPlayerRespawned(BasePlayer player)
{
if (player == null) return;
var data = GetPlayerData(player.userID);
if (data.CurrentMatchId != 0 && activeMatches.ContainsKey(data.CurrentMatchId))
{
var match = activeMatches[data.CurrentMatchId];
if (match.State == MatchState.InProgress && !data.IsSpectating)
{
timer.Once(0.5f, () => RestorePlayerForMatch(player, match));
}
}
}
#endregion
#region Queue System (Modern)
void OnPlayerInput(BasePlayer player, InputState input)
{
if (player == null || input == null) return;
var data = GetPlayerData(player.userID);
// Check if player is in a queue zone
CheckPlayerInQueueZone(player);
// Handle spectator controls
if (data.IsSpectating && !data.IsInFreeCam)
{
if (input.WasJustPressed(BUTTON.FIRE_PRIMARY))
{
SpectateNextPlayer(player);
}
else if (input.WasJustPressed(BUTTON.FIRE_SECONDARY))
{
SpectatePreviousPlayer(player);
}
else if (input.WasJustPressed(BUTTON.USE))
{
ToggleFreeCam(player);
}
}
// Handle defuse mechanics
if (data.CurrentMatchId != 0 && activeMatches.ContainsKey(data.CurrentMatchId))
{
var match = activeMatches[data.CurrentMatchId];
if (match.State == MatchState.InProgress && match.PlantedBomb != null)
{
if (input.IsDown(BUTTON.USE) && data.CurrentTeam == Team.CounterTerrorist && !data.IsSpectating)
{
float distance = Vector3.Distance(player.transform.position, match.PlantedBomb.Position);
if (distance <= 2f)
{
StartDefuse(player, match);
}
}
else if (!input.IsDown(BUTTON.USE) && data.IsDefusing)
{
StopDefuse(player, match);
}
}
}
}
private void CheckPlayerInQueueZone(BasePlayer player)
{
var data = GetPlayerData(player.userID);
var playerPos = player.transform.position;
foreach (var zone in queueZones.Values)
{
if (!zone.IsActive) continue;
bool inZone = IsPlayerInZone(playerPos, zone.MinBounds, zone.MaxBounds);
if (inZone && !data.IsInQueue)
{
AddPlayerToQueue(player, zone);
}
else if (!inZone && data.IsInQueue && data.QueueZoneName == zone.Name)
{
RemovePlayerFromQueue(player.userID);
}
}
}
private bool IsPlayerInZone(Vector3 position, Vector3 minBounds, Vector3 maxBounds)
{
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;
}
private void AddPlayerToQueue(BasePlayer player, QueueZone zone)
{
var data = GetPlayerData(player.userID);
if (data.CurrentMatchId != 0)
{
SendReply(player, "You are already in a match!");
return;
}
data.IsInQueue = true;
data.QueueZoneName = zone.Name;
zone.QueuedPlayers.Add(player.userID);
SendReply(player, $"Added to queue ({zone.QueuedPlayers.Count}/{zone.TeamSize * 2})");
if (zone.QueuedPlayers.Count >= zone.TeamSize * 2)
{
StartMatch(zone);
}
}
private void RemovePlayerFromQueue(ulong playerId)
{
var data = GetPlayerData(playerId);
if (!data.IsInQueue) return;
if (queueZones.ContainsKey(data.QueueZoneName))
{
var zone = queueZones[data.QueueZoneName];
zone.QueuedPlayers.Remove(playerId);
}
data.IsInQueue = false;
data.QueueZoneName = "";
var player = BasePlayer.FindByID(playerId);
if (player != null)
{
SendReply(player, "Removed from queue");
}
}
private void StartMatch(QueueZone zone)
{
var queuedPlayers = zone.QueuedPlayers.ToList();
zone.QueuedPlayers.Clear();
var matchId = (ulong)UnityEngine.Random.Range(100000, 999999);
var match = new Match
{
MatchId = matchId,
MapName = zone.SelectedMap,
QueueZoneName = zone.Name,
State = MatchState.InProgress
};
// Assign teams randomly
var shuffledPlayers = queuedPlayers.OrderBy(x => UnityEngine.Random.value).ToList();
for (int i = 0; i < shuffledPlayers.Count; i++)
{
var playerId = shuffledPlayers[i];
var data = GetPlayerData(playerId);
data.CurrentMatchId = matchId;
data.IsInQueue = false;
data.QueueZoneName = "";
if (i < zone.TeamSize)
{
match.TerroristPlayers.Add(playerId);
data.CurrentTeam = Team.Terrorist;
}
else
{
match.CounterTerroristPlayers.Add(playerId);
data.CurrentTeam = Team.CounterTerrorist;
}
}
activeMatches[matchId] = match;
NotifyMatchStart(match);
StartRound(match);
}
#endregion
#region Match Management (Modern)
private void NotifyMatchStart(Match match)
{
var allPlayers = match.TerroristPlayers.Concat(match.CounterTerroristPlayers);
foreach (var playerId in allPlayers)
{
var player = BasePlayer.FindByID(playerId);
if (player != null)
{
SendReply(player, $"Match {match.MatchId} started! Map: {match.MapName}");
SendReply(player, $"First to {config.RoundWinRequirement} rounds wins!");
}
}
}
private void StartRound(Match match)
{
match.CurrentRound++;
match.RoundStartTime = Time.time;
match.IsBuyPhase = true;
match.PlantedBomb = null;
// Reset round stats for all players
var allPlayers = match.TerroristPlayers.Concat(match.CounterTerroristPlayers);
foreach (var playerId in allPlayers)
{
var data = GetPlayerData(playerId);
data.RoundKills = 0;
data.RoundDeaths = 0;
}
// Check for side swap
if (match.CurrentRound == config.SideSwapRound + 1 && !match.HasSwappedSides)
{
SwapTeams(match);
}
// Teleport players to spawns
TeleportPlayersToSpawns(match);
// Give satchel to terrorists
GiveSatchelToTerrorists(match);
// Start buy phase timer
match.BuyPhaseTimer = timer.Once(config.BuyPhaseDuration, () => EndBuyPhase(match));
// Start round timer
float roundDuration = 120f;
match.RoundTimer = timer.Once(roundDuration, () => EndRound(match, RoundEndReason.TimeExpired));
NotifyRoundStart(match);
}
private void SwapTeams(Match match)
{
match.HasSwappedSides = true;
foreach (var playerId in match.TerroristPlayers.ToList())
{
var data = GetPlayerData(playerId);
data.CurrentTeam = Team.CounterTerrorist;
}
foreach (var playerId in match.CounterTerroristPlayers.ToList())
{
var data = GetPlayerData(playerId);
data.CurrentTeam = Team.Terrorist;
}
var tempList = match.TerroristPlayers.ToList();
match.TerroristPlayers = match.CounterTerroristPlayers.ToList();
match.CounterTerroristPlayers = tempList;
NotifyTeamSwap(match);
}
private void TeleportPlayersToSpawns(Match match)
{
if (!mapConfigurations.ContainsKey(match.MapName)) return;
var mapConfig = mapConfigurations[match.MapName];
for (int i = 0; i < match.TerroristPlayers.Count; i++)
{
var player = BasePlayer.FindByID(match.TerroristPlayers[i]);
if (player != null && i < mapConfig.TerroristSpawns.Count)
{
TeleportPlayer(player, mapConfig.TerroristSpawns[i]);
GetPlayerData(player.userID).IsSpectating = false;
}
}
for (int i = 0; i < match.CounterTerroristPlayers.Count; i++)
{
var player = BasePlayer.FindByID(match.CounterTerroristPlayers[i]);
if (player != null && i < mapConfig.CounterTerroristSpawns.Count)
{
TeleportPlayer(player, mapConfig.CounterTerroristSpawns[i]);
GetPlayerData(player.userID).IsSpectating = false;
}
}
}
private void TeleportPlayer(BasePlayer player, Vector3 position)
{
if (player == null) return;
player.Teleport(position);
player.SendNetworkUpdateImmediate();
}
private void GiveSatchelToTerrorists(Match match)
{
foreach (var playerId in match.TerroristPlayers)
{
var player = BasePlayer.FindByID(playerId);
if (player != null && !player.IsDead())
{
GiveItem(player, "explosive.satchel", 1);
SendReply(player, "You have the bomb! Plant it at a bomb site.");
break;
}
}
}
private void EndBuyPhase(Match match)
{
match.IsBuyPhase = false;
var allPlayers = match.TerroristPlayers.Concat(match.CounterTerroristPlayers);
foreach (var playerId in allPlayers)
{
var player = BasePlayer.FindByID(playerId);
if (player != null)
{
var data = GetPlayerData(playerId);
if (data.HasBuyMenuOpen)
{
CloseBuyMenu(player);
}
SendReply(player, "Buy phase ended!");
}
}
}
#endregion
#region Essential Commands (Simplified)
private void CmdSetQueueZone(BasePlayer player, string command, string[] args)
{
if (!permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN))
{
SendReply(player, "You don't have permission to use this command!");
return;
}
if (args.Length == 0)
{
SendReply(player, "Usage: /setqzone <name> [teamsize]");
return;
}
string zoneName = args[0];
int teamSize = args.Length > 1 ? int.Parse(args[1]) : config.DefaultTeamSize;
Vector3 playerPos = player.transform.position;
queueZones[zoneName] = new QueueZone
{
Name = zoneName,
MinBounds = playerPos - new Vector3(10f, 5f, 10f),
MaxBounds = playerPos + new Vector3(10f, 5f, 10f),
TeamSize = teamSize,
IsActive = true,
SelectedMap = "dust2"
};
SendReply(player, $"Queue zone '{zoneName}' created with team size {teamSize}!");
SaveData();
}
private void CmdGenerateMap(BasePlayer player, string command, string[] args)
{
if (!permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN))
{
SendReply(player, "You don't have permission to use this command!");
return;
}
if (args.Length < 2)
{
SendReply(player, "Usage: /generatemap <template> <mapname>");
SendReply(player, "Templates: dust2, mirage, inferno, cache, custom");
return;
}
string template = args[0];
string mapName = args[1];
Vector3 playerPos = player.transform.position;
SendReply(player, $"Generating map '{mapName}' using template '{template}'...");
GenerateCompetitiveZone(mapName, playerPos, 0);
SendReply(player, $"Map '{mapName}' generated successfully!");
}
private void CmdListMaps(BasePlayer player, string command, string[] args)
{
SendReply(player, "=== AVAILABLE MAPS ===");
if (mapConfigurations.Count == 0)
{
SendReply(player, "No maps configured yet!");
return;
}
foreach (var map in mapConfigurations.Values)
{
SendReply(player, $"• {map.MapName} - T:{map.TerroristSpawns.Count} CT:{map.CounterTerroristSpawns.Count}");
}
}
private void CmdJoinQueue(BasePlayer player, string command, string[] args)
{
var data = GetPlayerData(player.userID);
if (data.IsInQueue)
{
SendReply(player, "You are already in a queue!");
return;
}
if (data.CurrentMatchId != 0)
{
SendReply(player, "You are already in a match!");
return;
}
// ... [Code preview truncated due to length. Full source in attached file] ...