C#:
/*
* KillaDome.cs - Full COD-Style Rust Server Experience Plugin
*
* Features:
* - Lobby system with comprehensive UI (Play/Loadouts/Store/Stats tabs)
* - Drag-and-drop loadout editor with image library
* - Persistent weapon progression and attachment upgrades
* - Custom VFX/SFX for bullets and attachments
* - Store integration (Tebex-compatible)
* - High performance, GC-friendly architecture
*
* Version: 1.0.0
* Author: KillaDome Dev Team
*/
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Newtonsoft.Json;
using System.IO;
namespace Oxide.Plugins {
[Info("KillaDome", "KillaDome", "1.0.0")]
[Description("Full COD-style server experience with lobby, loadouts, and progression")]
public class KillaDome : RustPlugin {
#region Fields
[PluginReference]
private Plugin ImageLibrary;
private DomeManager _domeManager;
private LobbyUI _lobbyUI;
private LoadoutEditor _loadoutEditor;
private AttachmentSystem _attachmentSystem;
private VFXManager _vfxManager;
private SFXManager _sfxManager;
private ForgeStationSystem _forgeStation;
private BloodTokenEconomy _tokenEconomy;
private SaveManager _saveManager;
private AntiExploit _antiExploit;
private TelemetrySystem _telemetry;
private PluginConfig _config;
private GunConfig _gunConfig;
private OutfitConfig _outfitConfig;
private Dictionary<ulong, PlayerSession> _activeSessions = new Dictionary<ulong, PlayerSession>();
private const string PERMISSION_ADMIN = "killadome.admin";
private const string PERMISSION_VIP = "killadome.vip";
#endregion
#region Gun & Image Configuration
/// <summary>
/// SIMPLIFIED GUN AND SKIN CONFIGURATION
/// Now loaded from external JSON file for easy management!
///
/// File:
/// - oxide/data/KillaDome/Guns.json - All weapon definitions with skin lists
///
/// SIMPLIFIED FEATURES:
/// - Each gun has a Cost and list of AvailableSkins (just skin IDs)
/// - Base guns must be purchased (not free)
/// - Skins are just IDs: "0" (default), or workshop IDs like "3611341751"
/// - Skin pricing based on rarity tiers (Common/Rare/Epic/Legendary)
///
/// To reload changes: Use 'oxide.reload KillaDome' command
/// </summary>
public class GunConfig {
[JsonProperty("Guns")]
public Dictionary<string, GunDefinition> Guns {
get;
set;
}
= new Dictionary<string, GunDefinition>();
[JsonProperty("SkinPricing")]
public SkinPricing SkinPricing {
get;
set;
}
= new SkinPricing();
// ===== HELPER METHODS =====
public string GetGunImageUrl(string gunId) {
return Guns.ContainsKey(gunId) ? Guns[gunId].ImageUrl : "";
}
public string[] GetAllGunIds() {
return Guns.Keys.ToArray();
}
public List<string> GetSkinsForWeapon(string weaponId) {
if (Guns.ContainsKey(weaponId)) {
return Guns[weaponId].GetAllSkinIds();
}
return new List<string>();
}
public SkinDefinition GetSkinDefinition(string weaponId, string skinId) {
if (Guns.ContainsKey(weaponId)) {
return Guns[weaponId].GetSkin(skinId);
}
return null;
}
public List<SkinDefinition> GetAllSkinDefinitions(string weaponId) {
if (Guns.ContainsKey(weaponId) && Guns[weaponId].Skins != null) {
return Guns[weaponId].Skins;
}
return new List<SkinDefinition>();
}
// ===== DEFAULT CONFIGURATION =====
public static GunConfig CreateDefault() {
var config = new GunConfig();
config.Guns = new Dictionary<string, GunDefinition> {
["ak47"]
= new GunDefinition {
Id = "ak47", DisplayName = "AK-47", RustItemShortname = "rifle.ak", ImageUrl = "https://i.imgur.com/YourAK47Image.png", Cost = 500, DefaultSkinId = "0", Skins = new List<SkinDefinition> {
new SkinDefinition {
SkinId = "0", DisplayName = "Default", ImageUrl = "https://i.imgur.com/DefaultAK47.png", Cost = 0, Rarity = "Common", Tag = ""
}
, new SkinDefinition {
SkinId = "3602286295", DisplayName = "Tempered AK47", ImageUrl = "https://i.imgur.com/TemperedAK.png", Cost = 600, Rarity = "Epic", Tag = "HOT"
}
, new SkinDefinition {
SkinId = "3102802323", DisplayName = "Glory AK47", ImageUrl = "https://i.imgur.com/GloryAK.png", Cost = 800, Rarity = "Legendary", Tag = "NEW"
}
, new SkinDefinition {
SkinId = "2854463727", DisplayName = "Alien Red", ImageUrl = "https://i.imgur.com/AlienRedAK.png", Cost = 400, Rarity = "Rare", Tag = ""
}
}
}
, ["lr300"] = new GunDefinition {
Id = "lr300", DisplayName = "LR-300", RustItemShortname = "rifle.lr300", ImageUrl = "https://i.imgur.com/YourLR300Image.png", Cost = 500, DefaultSkinId = "0", Skins = new List<SkinDefinition> {
new SkinDefinition {
SkinId = "0", DisplayName = "Default", ImageUrl = "https://i.imgur.com/DefaultLR.png", Cost = 0, Rarity = "Common", Tag = ""
}
, new SkinDefinition {
SkinId = "2561668054", DisplayName = "Gold LR300", ImageUrl = "https://i.imgur.com/GoldLR.png", Cost = 800, Rarity = "Legendary", Tag = "POPULAR"
}
}
}
, ["m249"] = new GunDefinition {
Id = "m249", DisplayName = "M249", RustItemShortname = "lmg.m249", ImageUrl = "https://i.imgur.com/YourM249Image.png", Cost = 600, DefaultSkinId = "0", Skins = new List<SkinDefinition> {
new SkinDefinition {
SkinId = "0", DisplayName = "Default", ImageUrl = "https://i.imgur.com/DefaultM249.png", Cost = 0, Rarity = "Common", Tag = ""
}
, new SkinDefinition {
SkinId = "2854146553", DisplayName = "Chrome M249", ImageUrl = "https://i.imgur.com/ChromeM249.png", Cost = 600, Rarity = "Epic", Tag = ""
}
}
}
, ["mp5"] = new GunDefinition {
Id = "mp5", DisplayName = "MP5A4", RustItemShortname = "smg.mp5", ImageUrl = "https://i.imgur.com/YourMP5Image.png", Cost = 400, DefaultSkinId = "0", Skins = new List<SkinDefinition> {
new SkinDefinition {
SkinId = "0", DisplayName = "Default", ImageUrl = "https://i.imgur.com/DefaultMP5.png", Cost = 0, Rarity = "Common", Tag = ""
}
, new SkinDefinition {
SkinId = "2561668055", DisplayName = "Tactical MP5", ImageUrl = "https://i.imgur.com/TacticalMP5.png", Cost = 400, Rarity = "Rare", Tag = ""
}
}
}
, ["thompson"] = new GunDefinition {
Id = "thompson", DisplayName = "Thompson", RustItemShortname = "smg.thompson", ImageUrl = "https://i.imgur.com/YourThompsonImage.png", Cost = 400, DefaultSkinId = "0", Skins = new List<SkinDefinition> {
new SkinDefinition {
SkinId = "0", DisplayName = "Default", ImageUrl = "https://i.imgur.com/DefaultThompson.png", Cost = 0, Rarity = "Common", Tag = ""
}
, new SkinDefinition {
SkinId = "2561668056", DisplayName = "Dragon Thompson", ImageUrl = "https://i.imgur.com/DragonThompson.png", Cost = 600, Rarity = "Epic", Tag = "NEW"
}
}
}
, ["python"] = new GunDefinition {
Id = "python", DisplayName = "Python Revolver", RustItemShortname = "pistol.python", ImageUrl = "https://i.imgur.com/YourPythonImage.png", Cost = 300, DefaultSkinId = "0", Skins = new List<SkinDefinition> {
new SkinDefinition {
SkinId = "0", DisplayName = "Default", ImageUrl = "https://i.imgur.com/DefaultPython.png", Cost = 0, Rarity = "Common", Tag = ""
}
, new SkinDefinition {
SkinId = "2561668057", DisplayName = "Black Python", ImageUrl = "https://i.imgur.com/BlackPython.png", Cost = 400, Rarity = "Rare", Tag = ""
}
}
}
, ["bolt"] = new GunDefinition {
Id = "bolt", DisplayName = "Bolt Action Rifle", RustItemShortname = "rifle.bolt", ImageUrl = "https://i.imgur.com/YourBoltImage.png", Cost = 550, DefaultSkinId = "0", Skins = new List<SkinDefinition> {
new SkinDefinition {
SkinId = "0", DisplayName = "Default", ImageUrl = "https://i.imgur.com/DefaultBolt.png", Cost = 0, Rarity = "Common", Tag = ""
}
}
}
, ["sarpistol"] = new GunDefinition {
Id = "sarpistol", DisplayName = "Semi-Auto Pistol", RustItemShortname = "pistol.semiauto", ImageUrl = "https://i.imgur.com/YourSARImage.png", Cost = 250, DefaultSkinId = "0", Skins = new List<SkinDefinition> {
new SkinDefinition {
SkinId = "0", DisplayName = "Default", ImageUrl = "https://i.imgur.com/DefaultSAR.png", Cost = 0, Rarity = "Common", Tag = ""
}
}
}
, ["custom"] = new GunDefinition {
Id = "custom", DisplayName = "Custom SMG", RustItemShortname = "smg.2", ImageUrl = "https://i.imgur.com/YourCustomImage.png", Cost = 350, DefaultSkinId = "0", Skins = new List<SkinDefinition> {
new SkinDefinition {
SkinId = "0", DisplayName = "Default", ImageUrl = "https://i.imgur.com/DefaultCustom.png", Cost = 0, Rarity = "Common", Tag = ""
}
}
}
, ["m39"] = new GunDefinition {
Id = "m39", DisplayName = "M39 Rifle", RustItemShortname = "rifle.m39", ImageUrl = "https://i.imgur.com/YourM39Image.png", Cost = 450, DefaultSkinId = "0", Skins = new List<SkinDefinition> {
new SkinDefinition {
SkinId = "0", DisplayName = "Default", ImageUrl = "https://i.imgur.com/DefaultM39.png", Cost = 0, Rarity = "Common", Tag = ""
}
}
}
}
;
config.SkinPricing = new SkinPricing {
CommonCost = 250, RareCost = 400, EpicCost = 600, LegendaryCost = 800
}
;
return config;
}
}
public class GunDefinition {
public string Id {
get;
set;
}
public string DisplayName {
get;
set;
}
public string RustItemShortname {
get;
set;
}
public string ImageUrl {
get;
set;
}
public int Cost {
get;
set;
}
= 500;
// Cost to purchase the base gun
public string DefaultSkinId {
get;
set;
}
= "0";
// Default Rust skin ID (0 = no skin)
// Full skin definitions with individual image, price, rarity per skin
[JsonProperty("Skins")]
public List<SkinDefinition> Skins {
get;
set;
}
= new List<SkinDefinition>();
// Legacy support - kept for backward compatibility with old config files
// Legacy skin IDs are included in GetAllSkinIds() but won't have full metadata
[JsonProperty("AvailableSkins")]
public List<string> AvailableSkins {
get;
set;
}
= new List<string>();
// Helper to get all skin IDs (combines Skins and legacy AvailableSkins)
public List<string> GetAllSkinIds() {
var ids = new List<string>();
if (Skins != null) {
foreach (var skin in Skins) {
ids.Add(skin.SkinId);
}
}
// Also include legacy AvailableSkins that aren't in Skins list
if (AvailableSkins != null) {
foreach (var skinId in AvailableSkins) {
if (!ids.Contains(skinId)) ids.Add(skinId);
}
}
return ids;
}
// Get skin definition by ID (returns null for legacy skins without definitions)
public SkinDefinition GetSkin(string skinId) {
if (Skins != null) {
return Skins.FirstOrDefault(s => s.SkinId == skinId);
}
return null;
}
}
/// <summary>
/// Individual skin definition with image, price, and rarity
/// </summary>
public class SkinDefinition {
[JsonProperty("SkinId")]
public string SkinId {
get;
set;
}
= "0";
[JsonProperty("DisplayName")]
public string DisplayName {
get;
set;
}
= "Default";
[JsonProperty("ImageUrl")]
public string ImageUrl {
get;
set;
}
= "";
[JsonProperty("Cost")]
public int Cost {
get;
set;
}
= 0;
[JsonProperty("Rarity")]
public string Rarity {
get;
set;
}
= "Common";
// Common, Rare, Epic, Legendary
[JsonProperty("Tag")]
public string Tag {
get;
set;
}
= "";
// NEW, HOT, POPULAR, etc.
}
public class SkinPricing {
[JsonProperty("CommonCost")]
public int CommonCost {
get;
set;
}
= 250;
[JsonProperty("RareCost")]
public int RareCost {
get;
set;
}
= 400;
[JsonProperty("EpicCost")]
public int EpicCost {
get;
set;
}
= 600;
[JsonProperty("LegendaryCost")]
public int LegendaryCost {
get;
set;
}
= 800;
/// <summary>
/// Get default cost for a rarity tier. Used as fallback when skin doesn't have explicit cost.
/// </summary>
public int GetCostForRarity(string rarity) {
switch (rarity?.ToLower()) {
case "legendary" : return LegendaryCost;
case "epic" : return EpicCost;
case "rare" : return RareCost;
case "common" : default : return CommonCost;
}
}
/// <summary>
/// Get cost for a skin ID using heuristics. Used when skin definition is not available.
/// </summary>
public int GetCostForSkinId(string skinId) {
// Simple heuristic: if skin ID is "0", it's free (default)
if (skinId == "0") return 0;
// Workshop IDs starting with 3 are typically higher quality
if (skinId.StartsWith("3") && skinId.Length > 8) return LegendaryCost;
// You can customize this logic or add a mapping
return RareCost;
}
}
// ===== OUTFIT/ARMOR CONFIGURATION =====
/// <summary>
/// Armor/Outfit configuration now loaded from external JSON file
/// File: oxide/data/KillaDome/Armor.json
/// Edit the JSON file to add/remove armor pieces without touching code!
/// </summary>
public class OutfitConfig {
[JsonProperty("Armors")]
public List<ArmorItem> Armors {
get;
set;
}
= new List<ArmorItem>();
public ArmorItem[] GetArmorsBySlot(string slot) {
return Armors.Where(a => a.Slot == slot).ToArray();
}
// ===== DEFAULT CONFIGURATION =====
public static OutfitConfig CreateDefault() {
var config = new OutfitConfig();
config.Armors = new List<ArmorItem> {
// Head Armor
new ArmorItem {
Name = "Metal Facemask", ItemShortname = "metal.facemask", Slot = "head", SkinId = "0", ImageUrl = "https://i.imgur.com/mVY2Uav.png", Cost = 300, Rarity = "Common"
}
, new ArmorItem {
Name = "Coffee Can Helmet", ItemShortname = "coffeecan.helmet", Slot = "head", SkinId = "0", ImageUrl = "https://i.imgur.com/YourCoffeeCanImage.png", Cost = 250, Rarity = "Common"
}
, // Chest Armor
new ArmorItem {
Name = "Metal Chest Plate", ItemShortname = "metal.plate.torso", Slot = "chest", SkinId = "0", ImageUrl = "https://i.imgur.com/YourMetalChestImage.png", Cost = 400, Rarity = "Rare"
}
, new ArmorItem {
Name = "Road Sign Jacket", ItemShortname = "roadsign.jacket", Slot = "chest", SkinId = "0", ImageUrl = "https://i.imgur.com/YourRoadSignImage.png", Cost = 300, Rarity = "Common"
}
, // Legs Armor
new ArmorItem {
Name = "Heavy Plate Pants", ItemShortname = "heavy.plate.pants", Slot = "legs", SkinId = "0", ImageUrl = "https://i.imgur.com/YourHeavyPantsImage.png", Cost = 400, Rarity = "Rare"
}
, new ArmorItem {
Name = "Road Sign Kilt", ItemShortname = "roadsign.kilt", Slot = "legs", SkinId = "0", ImageUrl = "https://i.imgur.com/YourRoadSignKiltImage.png", Cost = 300, Rarity = "Common"
}
, // Hands/Gloves
new ArmorItem {
Name = "Tactical Gloves", ItemShortname = "tactical.gloves", Slot = "hands", SkinId = "0", ImageUrl = "https://i.imgur.com/YourTacticalGlovesImage.png", Cost = 200, Rarity = "Common"
}
, // Feet/Boots
new ArmorItem {
Name = "Heavy Plate Boots", ItemShortname = "shoes.boots", Slot = "feet", SkinId = "0", ImageUrl = "https://i.imgur.com/YourBootsImage.png", Cost = 250, Rarity = "Common"
}
}
;
return config;
}
}
public class ArmorItem {
public string Name {
get;
set;
}
public string DisplayName => Name;
// Alias for consistency
public string ItemShortname {
get;
set;
}
public string Slot {
get;
set;
}
// "head", "chest", "legs", "hands", "feet"
public string SkinId {
get;
set;
}
= "0";
public string ImageUrl {
get;
set;
}
public int Cost {
get;
set;
}
= 300;
public string Rarity {
get;
set;
}
= "Common";
public string Tag {
get;
set;
}
= "";
}
#endregion
#region Configuration
internal class PluginConfig {
[JsonProperty("Lobby Spawn Position")]
public Vector3 LobbySpawnPosition {
get;
set;
}
= new Vector3(0, 100, 0);
[JsonProperty("Arena Spawn Positions")]
public List<Vector3> ArenaSpawnPositions {
get;
set;
}
= new List<Vector3> {
new Vector3(0, 100, 500)
}
;
[JsonProperty("Starting Blood Tokens")]
public int StartingTokens {
get;
set;
}
= 500;
[JsonProperty("Tokens Per Kill")]
public int TokensPerKill {
get;
set;
}
= 10;
[JsonProperty("Admin Daily Tokens")]
public int AdminDailyTokens {
get;
set;
}
= 10000;
[JsonProperty("Daily Token Refill Enabled")]
public bool DailyRefillEnabled {
get;
set;
}
= true;
[JsonProperty("UI Update Throttle MS")]
public int UIUpdateThrottleMS {
get;
set;
}
= 100;
[JsonProperty("Auto Save Interval Seconds")]
public float AutoSaveInterval {
get;
set;
}
= 300f;
[JsonProperty("Enable Debug Logging")]
public bool EnableDebugLogging {
get;
set;
}
= false;
}
protected override void LoadDefaultConfig() {
_config = new PluginConfig();
SaveConfig();
}
protected override void LoadConfig() {
base.LoadConfig();
try {
_config = Config.ReadObject<PluginConfig>();
if (_config == null) {
LoadDefaultConfig();
}
}
catch {
PrintError("Configuration file is corrupt. Loading defaults...");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(_config, true);
// ===== LOAD EXTERNAL DATA CONFIGURATIONS =====
private GunConfig LoadGunConfig() {
string dataDirectory = Path.Combine(Interface.Oxide.DataDirectory, "KillaDome");
string filePath = Path.Combine(dataDirectory, "Guns.json");
if (!Directory.Exists(dataDirectory)) {
Directory.CreateDirectory(dataDirectory);
}
if (!File.Exists(filePath)) {
Puts("Guns.json not found. Creating default configuration...");
var defaultConfig = GunConfig.CreateDefault();
File.WriteAllText(filePath, JsonConvert.SerializeObject(defaultConfig, Formatting.Indented));
return defaultConfig;
}
try {
string json = File.ReadAllText(filePath);
var config = JsonConvert.DeserializeObject<GunConfig>(json);
if (config == null || config.Guns == null || config.SkinPricing == null) {
PrintWarning("Guns.json is invalid. Using defaults...");
return GunConfig.CreateDefault();
}
// Count total skins across all guns
int totalSkins = 0;
foreach (var gun in config.Guns.Values) {
if (gun.AvailableSkins != null) totalSkins += gun.AvailableSkins.Count;
}
Puts($"Loaded {config.Guns.Count} guns with {totalSkins} total skins from Guns.json");
return config;
}
catch (Exception ex) {
PrintError($"Failed to load Guns.json: {ex.Message}. Using defaults...");
return GunConfig.CreateDefault();
}
}
private OutfitConfig LoadOutfitConfig() {
string dataDirectory = Path.Combine(Interface.Oxide.DataDirectory, "KillaDome");
string filePath = Path.Combine(dataDirectory, "Armor.json");
if (!Directory.Exists(dataDirectory)) {
Directory.CreateDirectory(dataDirectory);
}
if (!File.Exists(filePath)) {
Puts("Armor.json not found. Creating default configuration...");
var defaultConfig = OutfitConfig.CreateDefault();
File.WriteAllText(filePath, JsonConvert.SerializeObject(defaultConfig, Formatting.Indented));
return defaultConfig;
}
try {
string json = File.ReadAllText(filePath);
var config = JsonConvert.DeserializeObject<OutfitConfig>(json);
if (config == null || config.Armors == null) {
PrintWarning("Armor.json is invalid. Using defaults...");
return OutfitConfig.CreateDefault();
}
Puts($"Loaded {config.Armors.Count} armor pieces from Armor.json");
return config;
}
catch (Exception ex) {
PrintError($"Failed to load Armor.json: {ex.Message}. Using defaults...");
return OutfitConfig.CreateDefault();
}
}
#endregion
#region Oxide Hooks
private void Init() {
permission.RegisterPermission(PERMISSION_ADMIN, this);
permission.RegisterPermission(PERMISSION_VIP, this);
// Load configurations from JSON files
_gunConfig = LoadGunConfig();
_outfitConfig = LoadOutfitConfig();
// Initialize all systems
_saveManager = new SaveManager(this, _config);
_antiExploit = new AntiExploit(this);
_tokenEconomy = new BloodTokenEconomy(this, _config);
_attachmentSystem = new AttachmentSystem(this, _config);
_vfxManager = new VFXManager(this);
_sfxManager = new SFXManager(this);
_forgeStation = new ForgeStationSystem(this, _config, _tokenEconomy, _attachmentSystem);
_loadoutEditor = new LoadoutEditor(this, _attachmentSystem);
_lobbyUI = new LobbyUI(this, _loadoutEditor, _forgeStation, _tokenEconomy);
_domeManager = new DomeManager(this, _config);
_telemetry = new TelemetrySystem(this);
LogDebug("KillaDome initialized successfully");
}
private void OnServerInitialized() {
timer.Every(_config.AutoSaveInterval, () => AutoSaveAllPlayers());
LogDebug("Auto-save timer started");
// Load images after server is ready
timer.Once(5f, () => LoadImages());
}
private void LoadImages() {
if (ImageLibrary == null || !ImageLibrary.IsLoaded) {
PrintWarning("ImageLibrary not loaded. Images will not display. Please install ImageLibrary plugin.");
return;
}
int skinCount = 0;
// Load gun images and individual skin images
foreach (var gun in _gunConfig.Guns.Values) {
if (!string.IsNullOrEmpty(gun.ImageUrl)) {
ImageLibrary.Call("AddImage", gun.ImageUrl, gun.ImageUrl);
}
// Load individual skin images
if (gun.Skins != null) {
foreach (var skin in gun.Skins) {
if (!string.IsNullOrEmpty(skin.ImageUrl)) {
// Use a unique key combining gun ID and skin ID
string imageKey = $"{gun.Id}_skin_{skin.SkinId}";
ImageLibrary.Call("AddImage", skin.ImageUrl, imageKey);
skinCount++;
}
}
}
}
// Load armor images
foreach (var armor in _outfitConfig.Armors) {
if (!string.IsNullOrEmpty(armor.ImageUrl)) {
ImageLibrary.Call("AddImage", armor.ImageUrl, armor.ImageUrl);
}
}
Puts($"Loaded {_gunConfig.Guns.Count} gun images, {skinCount} skin images, and {_outfitConfig.Armors.Count} armor images into ImageLibrary");
}
private void Unload() {
// Clean up all UI
foreach (var player in BasePlayer.activePlayerList) {
_lobbyUI?.DestroyUI(player);
}
// Save all player data
foreach (var session in _activeSessions.Values) {
_saveManager?.SavePlayerProfile(session.Profile);
}
_activeSessions.Clear();
LogDebug("KillaDome unloaded and cleaned up");
}
private void OnPlayerConnected(BasePlayer player) {
if (player == null || _saveManager == null || _lobbyUI == null) return;
NextTick(() => {
if (player == null || !player.IsConnected) return;
var profile = _saveManager.LoadPlayerProfile(player.userID);
// Check for admin daily token refill
if (_config.DailyRefillEnabled && permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN)) {
// Check if 24 hours have passed since last refill
TimeSpan timeSinceRefill = DateTime.UtcNow - profile.LastDailyRefill;
if (timeSinceRefill.TotalHours >= 24) {
profile.Tokens = _config.AdminDailyTokens;
profile.LastDailyRefill = DateTime.UtcNow;
_saveManager.SavePlayerProfile(profile);
SendReply(player, $"<color=#00ff00>✓ Admin Daily Tokens:</color> You received {_config.AdminDailyTokens} Blood Tokens!");
LogDebug($"Admin {player.displayName} received daily refill of {_config.AdminDailyTokens} tokens");
}
}
var session = new PlayerSession(player, profile);
_activeSessions[player.userID] = session;
// Teleport to lobby
TeleportToLobby(player);
// Show lobby UI
timer.Once(1f, () => {
if (player != null && player.IsConnected) {
_lobbyUI.ShowLobbyUI(player);
}
}
);
LogDebug($"Player {player.displayName} ({player.userID}) connected");
}
);
}
private void OnPlayerDisconnected(BasePlayer player, string reason) {
if (player == null) return;
_lobbyUI?.DestroyUI(player);
if (_activeSessions.TryGetValue(player.userID, out var session)) {
_saveManager?.SavePlayerProfile(session.Profile);
_activeSessions.Remove(player.userID);
}
LogDebug($"Player {player.displayName} disconnected: {reason}");
}
private void OnEntityDeath(BasePlayer victim, HitInfo info) {
if (victim == null || _tokenEconomy == null || _telemetry == null) return;
var attacker = info?.InitiatorPlayer;
if (attacker != null && attacker != victim && attacker.IsConnected) {
// Award tokens for kill
_tokenEconomy.AwardTokens(attacker.userID, _config.TokensPerKill);
// Track telemetry
_telemetry.RecordKill(attacker.userID, victim.userID);
LogDebug($"{attacker.displayName} killed {victim.displayName}");
}
// Respawn victim in lobby after delay
timer.Once(3f, () => {
if (victim != null && victim.IsConnected) {
TeleportToLobby(victim);
victim.Respawn();
}
}
);
}
#endregion
#region Helper Methods
private void TeleportToLobby(BasePlayer player) {
if (player == null || !player.IsConnected) return;
player.Teleport(_config.LobbySpawnPosition);
}
private void TeleportToArena(BasePlayer player) {
if (player == null || !player.IsConnected) return;
// Select random spawn point from configured arena spawns
Vector3 spawnPos;
if (_config.ArenaSpawnPositions != null && _config.ArenaSpawnPositions.Count > 0) {
spawnPos = _config.ArenaSpawnPositions[UnityEngine.Random.Range(0, _config.ArenaSpawnPositions.Count)];
}
else {
// Fallback to default if no spawns configured
spawnPos = new Vector3(0, 100, 500);
}
player.Teleport(spawnPos);
// Apply loadout when entering arena
ApplyLoadout(player);
}
private void ApplyLoadout(BasePlayer player) {
var session = GetSession(player.userID);
if (session == null || session.Profile.Loadouts.Count == 0) return;
var loadout = session.Profile.Loadouts[0];
// Strip existing items
player.inventory.Strip();
// Give primary weapon
GiveWeapon(player, loadout.Primary, loadout.PrimaryAttachments, loadout.Skins);
// Give secondary weapon
GiveWeapon(player, loadout.Secondary, loadout.SecondaryAttachments, loadout.Skins);
// Give equipped armor/outfit
GiveArmor(player, loadout);
LogDebug($"Applied loadout to {player.displayName}");
}
private void GiveWeapon(BasePlayer player, string weaponName, Dictionary<string, string> attachments, Dictionary<string, string> skins) {
if (string.IsNullOrEmpty(weaponName)) return;
// Get weapon info from centralized config
string itemName = "rifle.ak";
// Default fallback
if (_gunConfig?.Guns != null && _gunConfig.Guns.TryGetValue(weaponName, out var gunDef)) {
itemName = gunDef.RustItemShortname;
}
var item = ItemManager.CreateByName(itemName, 1);
if (item == null) {
LogDebug($"Failed to create weapon: {itemName}");
return;
}
// Apply skin if exists
if (skins != null && skins.TryGetValue(weaponName, out string skinId)) {
if (ulong.TryParse(skinId, out ulong skin)) {
item.skin = skin;
item.MarkDirty();
// Mark for network update
}
}
// Apply attachments if exists
if (attachments != null && attachments.Count > 0) {
var heldEntity = item.GetHeldEntity() as BaseProjectile;
if (heldEntity != null && item.contents != null) {
foreach (var attachmentEntry in attachments) {
string attachmentId = attachmentEntry.Value;
if (!string.IsNullOrEmpty(attachmentId)) {
var attachmentItem = ItemManager.CreateByName(attachmentId, 1);
if (attachmentItem != null) {
// Add attachment to weapon's content container
if (!attachmentItem.MoveToContainer(item.contents)) {
attachmentItem.Remove();
// Clean up if can't add
}
}
}
}
}
}
// Give item to player
if (!player.inventory.GiveItem(item)) {
LogDebug($"Failed to give weapon {itemName} to {player.displayName} - inventory full?");
item.Remove();
// Clean up item if can't give
return;
}
// If item was given to belt, ensure visual update
var heldItem = item.GetHeldEntity();
if (heldItem != null) {
heldItem.skinID = item.skin;
heldItem.SendNetworkUpdate();
}
// Give ammo
string ammoType = weaponName == "pistol" ? "ammo.pistol" : "ammo.rifle";
var ammo = ItemManager.CreateByName(ammoType, 250);
if (ammo != null) {
player.inventory.GiveItem(ammo);
}
}
private void GiveArmor(BasePlayer player, Loadout loadout) {
if (player == null || loadout == null) return;
// Give head armor
if (!string.IsNullOrEmpty(loadout.ArmorHead)) {
GiveArmorPiece(player, loadout.ArmorHead);
}
// Give chest armor
if (!string.IsNullOrEmpty(loadout.ArmorChest)) {
GiveArmorPiece(player, loadout.ArmorChest);
}
// Give legs armor
if (!string.IsNullOrEmpty(loadout.ArmorLegs)) {
GiveArmorPiece(player, loadout.ArmorLegs);
}
// Give hands armor
if (!string.IsNullOrEmpty(loadout.ArmorHands)) {
GiveArmorPiece(player, loadout.ArmorHands);
}
// Give feet armor
if (!string.IsNullOrEmpty(loadout.ArmorFeet)) {
GiveArmorPiece(player, loadout.ArmorFeet);
}
LogDebug($"Applied armor to {player.displayName}");
}
private void GiveArmorPiece(BasePlayer player, string armorShortname) {
if (string.IsNullOrEmpty(armorShortname)) return;
var item = ItemManager.CreateByName(armorShortname, 1);
if (item == null) {
LogDebug($"Failed to create armor item: {armorShortname}");
return;
}
// Try to move to wear container (for clothing/armor)
if (!item.MoveToContainer(player.inventory.containerWear)) {
// If wear container is full or item can't be worn, give to main inventory
player.inventory.GiveItem(item);
}
LogDebug($"Gave armor piece {armorShortname} to {player.displayName}");
}
private void AutoSaveAllPlayers() {
if (_saveManager == null) return;
int saved = 0;
foreach (var session in _activeSessions.Values) {
if (session?.Profile != null) {
_saveManager.SavePlayerProfile(session.Profile);
saved++;
}
}
LogDebug($"Auto-saved {saved} player profiles");
}
private void LogDebug(string message) {
if (_config?.EnableDebugLogging == true) {
Puts($"[DEBUG] {message}");
}
}
internal PlayerSession GetSession(ulong steamId) {
_activeSessions.TryGetValue(steamId, out var session);
return session;
}
#endregion
#region Console Commands
[ConsoleCommand("kd.open")]
private void CmdOpen(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN)) {
SendReply(arg, "You don't have permission to use this command");
return;
}
_lobbyUI.ShowLobbyUI(player);
SendReply(arg, "Lobby UI opened");
}
[ConsoleCommand("kd.start")]
private void CmdStart(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN)) {
SendReply(arg, "You don't have permission to use this command");
return;
}
_domeManager.StartMatch();
SendReply(arg, "Match started");
}
[ConsoleCommand("kd.giveskin")]
private void CmdGiveSkin(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null || !arg.HasArgs(2)) {
SendReply(arg, "Usage: kd.giveskin <steamid> <skinid>");
return;
}
if (!permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN)) {
SendReply(arg, "You don't have permission to use this command");
return;
}
if (!ulong.TryParse(arg.Args[0], out ulong targetId)) {
SendReply(arg, "Invalid Steam ID");
return;
}
string skinId = arg.Args[1];
if (_activeSessions.TryGetValue(targetId, out var session)) {
session.Profile.OwnedSkins.Add(skinId);
_saveManager.SavePlayerProfile(session.Profile);
SendReply(arg, $"Granted skin {skinId} to player {targetId}");
}
else {
SendReply(arg, "Player not found or not online");
}
}
[ConsoleCommand("kd.resetprogress")]
private void CmdResetProgress(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null || !arg.HasArgs(1)) {
SendReply(arg, "Usage: kd.resetprogress <steamid>");
return;
}
if (!permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN)) {
SendReply(arg, "You don't have permission to use this command");
return;
}
if (!ulong.TryParse(arg.Args[0], out ulong targetId)) {
SendReply(arg, "Invalid Steam ID");
return;
}
var newProfile = new PlayerProfile(targetId, _config.StartingTokens);
_saveManager.SavePlayerProfile(newProfile);
if (_activeSessions.TryGetValue(targetId, out var session)) {
session.Profile = newProfile;
}
SendReply(arg, $"Reset progress for player {targetId}");
}
#endregion
#region Chat Commands
[ChatCommand("kd")]
private void CmdKD(BasePlayer player, string command, string[] args) {
if (args.Length == 0) {
SendReply(player, "KillaDome Commands:\n" + "/kd open - Open lobby UI\n" + "/kd stats - View your stats\n" + "/kd help - Show this help");
return;
}
switch (args[0].ToLower()) {
case "open" : _lobbyUI.ShowLobbyUI(player);
SendReply(player, "Lobby UI opened");
break;
case "stats" : if (_activeSessions.TryGetValue(player.userID, out var session)) {
SendReply(player, $"Blood Tokens: {session.Profile.Tokens}\n" + $"VIP Status: {(session.Profile.IsVIP ? "Active" : "Inactive")}");
}
break;
case "help" : SendReply(player, "KillaDome - Full COD Experience\n" + "Use /kd open to access the lobby");
break;
default : SendReply(player, "Unknown command. Use /kd help");
break;
}
}
#endregion
#region UI Console Commands
[ConsoleCommand("killadome.close")]
private void CmdUIClose(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null) return;
_lobbyUI.DestroyUI(player);
}
[ConsoleCommand("killadome.tab")]
private void CmdUITab(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null || !arg.HasArgs(1)) return;
string tab = arg.Args[0].ToLower();
_lobbyUI.ShowLobbyUIWithTab(player, tab);
LogDebug($"Player {player.displayName} opened tab: {tab}");
}
[ConsoleCommand("killadome.joinqueue")]
private void CmdUIJoinQueue(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null) return;
_domeManager.AddToQueue(player.userID);
SendReply(player, "You have joined the queue!");
}
[ConsoleCommand("killadome.weapon.prev")]
private void CmdWeaponPrev(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null || !arg.HasArgs(1)) return;
if (!_antiExploit.CheckRateLimit(player.userID)) {
SendReply(player, "Please slow down!");
return;
}
string slot = arg.Args[0];
// "primary" or "secondary"
CycleWeapon(player, slot, -1);
_lobbyUI.ShowLobbyUIWithTab(player, "loadouts");
}
[ConsoleCommand("killadome.weapon.next")]
private void CmdWeaponNext(ConsoleSystem.Arg arg) {
var player = arg.Player();
if (player == null || !arg.HasArgs(1)) return;
if (!
// ... [Code preview truncated due to length. Full source in attached file] ...