Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions Configs/BaseConfigs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public class SpectatorConfig : BasePluginConfig
[JsonPropertyName("DisplaySettings")]
public DisplaySettings Display { get; set; } = new();

[JsonPropertyName("Database")]
public DatabaseConfig Database { get; set; } = new DatabaseConfig();
[JsonPropertyName("StorageSettings")]
public StorageSettings Storage { get; set; } = new();
}

public class UpdateSettings
Expand Down Expand Up @@ -75,6 +75,15 @@ public class ScreenViewSettings
public string CountColor { get; set; } = "#87CEEB";
}

public class StorageSettings
{
[JsonPropertyName("StorageType")]
public string StorageType { get; set; } = "PlayerSettings";

[JsonPropertyName("Database")]
public DatabaseConfig Database { get; set; } = new DatabaseConfig();
}

public class DatabaseConfig
{
[JsonPropertyName("Host")]
Expand Down
160 changes: 66 additions & 94 deletions Managers/DisplayManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,87 +11,81 @@ namespace SpectatorList.Managers
public class DisplayManager : IDisposable
{
private readonly Dictionary<int, ScreenViewDisplay> _screenDisplays;
private readonly HashSet<int> _disabledPlayers;
private readonly SpectatorConfig _config;
private readonly BasePlugin _plugin;
private readonly DatabaseService _databaseService;
private readonly IStorageService _storageService;

public DisplayManager(SpectatorConfig config, BasePlugin plugin)
public DisplayManager(SpectatorConfig config, BasePlugin plugin, IStorageService storageService)
{
_config = config;
_plugin = plugin;
_storageService = storageService;
_screenDisplays = new Dictionary<int, ScreenViewDisplay>();
_disabledPlayers = new HashSet<int>();
_databaseService = new DatabaseService(config);

if (_databaseService.IsEnabled)
{
_ = InitializeDatabaseAsync();
}
_ = InitializeStorageAsync();
}

private async Task InitializeDatabaseAsync()
private async Task InitializeStorageAsync()
{
try
{
await _databaseService.InitializeDatabase();
var success = await _storageService.InitializeAsync();
var storageType = _storageService.GetStorageType();

Server.NextFrame(() =>
{
if (success)
{
Server.PrintToConsole($"[SpectatorList] Storage initialized successfully: {storageType}");
}
else
{
Server.PrintToConsole($"[SpectatorList] Storage initialization failed, using: {storageType}");
}
});
}
catch (Exception ex)
{
Server.PrintToConsole($"[SpectatorList] Error initializing database: {ex.Message}");
var errorMessage = ex.Message;
Server.NextFrame(() =>
{
Server.PrintToConsole($"[SpectatorList] Error initializing storage: {errorMessage}");
});
}
}

public async Task<bool> IsPlayerDisplayEnabledAsync(CCSPlayerController player)
{
if (_databaseService.IsEnabled)
try
{
try
{
var steamId = player.SteamID.ToString();
var preferences = await _databaseService.LoadOrCreatePlayerPreferences(steamId);
return preferences.DisplayEnabled;
}
catch (Exception ex)
{
Server.NextFrame(() =>
{
Server.PrintToConsole($"[SpectatorList] Error loading preferences for {player.PlayerName}: {ex.Message}");
});
return !_disabledPlayers.Contains(player.Slot);
}
return await _storageService.IsPlayerDisplayEnabledAsync(player);
}

return !_disabledPlayers.Contains(player.Slot);
}

public bool IsPlayerDisplayEnabled(CCSPlayerController player)
{
if (_databaseService.IsEnabled)
catch (Exception ex)
{
var cachedPrefs = _databaseService.GetCachedPlayerPreferences(player.SteamID.ToString());
if (cachedPrefs != null)
{
return cachedPrefs.DisplayEnabled;
}
var playerName = player.PlayerName;
var errorMessage = ex.Message;

_ = LoadPlayerPreferencesAsync(player);
Server.NextFrame(() =>
{
Server.PrintToConsole($"[SpectatorList] Error checking display status for {playerName}: {errorMessage}");
});
return true;
}

return !_disabledPlayers.Contains(player.Slot);
}

private async Task LoadPlayerPreferencesAsync(CCSPlayerController player)
public bool IsPlayerDisplayEnabled(CCSPlayerController player)
{
try
{
var steamId = player.SteamID.ToString();
await _databaseService.LoadOrCreatePlayerPreferences(steamId);
return _storageService.IsPlayerDisplayEnabled(player);
}
catch (Exception ex)
{
Server.PrintToConsole($"[SpectatorList] Error loading preferences for {player.PlayerName}: {ex.Message}");
Server.NextFrame(() =>
{
Server.PrintToConsole($"[SpectatorList] Error checking display status for {player.PlayerName}: {ex.Message}");
});
return true;
}
}

Expand All @@ -105,60 +99,43 @@ public bool CanPlayerViewList(CCSPlayerController player)

public async Task TogglePlayerDisplayAsync(CCSPlayerController player)
{
if (_databaseService.IsEnabled)
try
{
try
{
var steamId = player.SteamID.ToString();
var preferences = await _databaseService.LoadOrCreatePlayerPreferences(steamId);

preferences.DisplayEnabled = !preferences.DisplayEnabled;
preferences.LastUpdated = DateTime.Now;
await _storageService.TogglePlayerDisplayAsync(player);

await _databaseService.SavePlayerPreferences(preferences);

if (!preferences.DisplayEnabled)
{
Server.NextFrame(() => CleanupPlayerDisplay(player));
}
}
catch (Exception ex)
var isEnabled = await _storageService.IsPlayerDisplayEnabledAsync(player);
if (!isEnabled)
{
Server.NextFrame(() =>
{
Server.PrintToConsole($"[SpectatorList] Error toggling display for {player.PlayerName}: {ex.Message}");
});
Server.NextFrame(() => TogglePlayerDisplayMemory(player));
Server.NextFrame(() => CleanupPlayerDisplay(player));
}
}
else
catch (Exception ex)
{
Server.NextFrame(() => TogglePlayerDisplayMemory(player));
var playerName = player.PlayerName;
var errorMessage = ex.Message;

Server.NextFrame(() =>
{
Server.PrintToConsole($"[SpectatorList] Error toggling display for {playerName}: {errorMessage}");
});
}
}

public void TogglePlayerDisplay(CCSPlayerController player)
{
if (_databaseService.IsEnabled)
{
_ = TogglePlayerDisplayAsync(player);
}
else
try
{
TogglePlayerDisplayMemory(player);
}
}
_storageService.TogglePlayerDisplay(player);

private void TogglePlayerDisplayMemory(CCSPlayerController player)
{
if (_disabledPlayers.Contains(player.Slot))
{
_disabledPlayers.Remove(player.Slot);
var isEnabled = _storageService.IsPlayerDisplayEnabled(player);
if (!isEnabled)
{
CleanupPlayerDisplay(player);
}
}
else
catch (Exception ex)
{
_disabledPlayers.Add(player.Slot);
CleanupPlayerDisplay(player);
Server.PrintToConsole($"[SpectatorList] Error toggling display for {player.PlayerName}: {ex.Message}");
}
}

Expand All @@ -185,9 +162,10 @@ public List<CCSPlayerController> FilterSpectators(List<CCSPlayerController> spec
filteredList.Add(spectator);
}

var errorMessage = ex.Message;
Server.NextFrame(() =>
{
Server.PrintToConsole($"[SpectatorList] Error checking permissions for spectator: {ex.Message}");
Server.PrintToConsole($"[SpectatorList] Error checking permissions for spectator: {errorMessage}");
});
}
}
Expand Down Expand Up @@ -338,19 +316,13 @@ public void HidePlayerDisplay(CCSPlayerController player)
public void OnPlayerDisconnect(CCSPlayerController player)
{
CleanupPlayerDisplay(player);
_disabledPlayers.Remove(player.Slot);

if (_databaseService.IsEnabled)
{
_databaseService.RemoveFromCache(player.SteamID.ToString());
}
_storageService.OnPlayerDisconnect(player);
}

public void Dispose()
{
CleanupAllDisplays();
_disabledPlayers.Clear();
_databaseService.ClearCache();
_storageService.ClearCache();
}
}
}
Binary file added PlayerSettingsApi.dll
Binary file not shown.
53 changes: 41 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,59 @@ Shows real-time spectators both in chat messages and on-screen display with cust

---

### Installation
## 🚀 Installation

### Basic Installation
1. Install [CounterStrike Sharp](https://github.com/roflmuffin/CounterStrikeSharp) and [Metamod:Source](https://www.sourcemm.net/downloads.php/?branch=master)
2. Download [SpectatorList.zip](https://github.com/wiruwiru/SpectatorList-CS2/releases/latest) from releases
3. Extract and upload to your game server
4. Start server and configure the generated config file
5. (Optional) Configure MySQL database for persistent user preferences

### Optional Dependencies (for PlayerSettings storage)
If you want to use `PlayerSettings` storage type:
2. Install [PlayerSettingsCS2](https://github.com/NickFox007/PlayerSettingsCS2/releases/latest) (required dependency)
1. Install [AnyBaseLibCS2](https://github.com/NickFox007/AnyBaseLibCS2/releases/latest) (required for PlayerSettings)
3. Set `StorageType` to `"PlayerSettings"` in your config

### Optional Dependencies (for MySQL storage)
If you want to use `MySQL` storage type:
1. Configure your MySQL database
2. Set `StorageType` to `"MySQL"` in your config
3. Fill in the database connection details

---

### Main Configuration Parameters
## ⚙️ Storage Options

The plugin supports three different storage methods for user preferences:

| Storage Type | Description | Persistence | Performance | Dependencies |
|--------------|-------------|-------------|-------------|--------------|
| **PlayerSettings** | Uses PlayerSettings plugin | ✅ Persistent | ⚡ Fast | PlayerSettingsCS2 + AnyBaseLibCS2 |
| **MySQL** | Traditional database storage | ✅ Persistent | 🔄 Database queries | MySQL Server |
| **Memory** | Temporary in-memory storage | ❌ Lost on restart | ⚡⚡ Fastest | None |

**Recommendation**: Use `PlayerSettings` for most servers, `MySQL` if you don't care about the number of active pool connections, and `Memory` for testing.

---

## 📋 Main Configuration Parameters
| Parameter | Description | Required |
|----------------------|---------------------------------------------------------------------------------------------------|----------|
| `Commands` | List of chat commands players can use to toggle spectator list display. (**Default**: `["css_speclist", "css_specs", "css_spectators"]`) | **YES** |
| `CommandPermissions` | Permission flag required to use the toggle commands. Leave empty for all players. (**Default**: `"@css/root"`) | **YES** |
| `CanViewList` | Permission flag required to view spectator lists (both chat and screen). Leave empty for all players. (**Default**: `""`) | **YES** |
| `UpdateSettings` | Configuration for automatic updates and periodic displays. | **YES** |
| `DisplaySettings` | Configuration for how spectator lists are displayed. | **YES** |
| `Database` | MySQL database configuration for persistent user preferences. | **NO** |
| `StorageSettings` | Configuration for user preference storage method. | **YES** |

### Update Settings Parameters
### Storage Settings Parameters
| Parameter | Description | Required |
|-------------------|-----------------------------------------------------------------------------------------------------|----------|
| `StorageType` | Storage method to use: `"PlayerSettings"`, `"MySQL"`, or `"Memory"`. (**Default**: `"PlayerSettings"`) | **YES** |
| `Database` | MySQL database configuration (only used when `StorageType` is `"MySQL"`). | **NO** |

### Update Settings Parameters
| Parameter | Description | Required |
|-------------------|-----------------------------------------------------------------------------------------------------|----------|
| `CheckInterval` | How often (in seconds) to check for spectator changes. (**Default**: `2.0`) | **YES** |
Expand All @@ -35,7 +66,6 @@ Shows real-time spectators both in chat messages and on-screen display with cust
| `PeriodicInterval` | Interval (in seconds) for periodic displays when `ShowPeriodic` is enabled. (**Default**: `5.0`) | **YES** |

### Display Settings Parameters

| Parameter | Description | Required |
|-------------------|-----------------------------------------------------------------------------------------------------|----------|
| `ExclusionFlag` | Players with this flag will be hidden from spectator lists. (**Default**: `"@css/generic"`) | **YES** |
Expand All @@ -45,7 +75,6 @@ Shows real-time spectators both in chat messages and on-screen display with cust
| `ScreenViewSettings` | Configuration for on-screen display positioning and appearance. | **YES** |

### Screen View Settings Parameters

| Parameter | Description | Required |
|-------------------|-----------------------------------------------------------------------------------------------------|----------|
| `PositionX` | Horizontal position offset for on-screen display. (**Default**: `-8.0`) | **YES** |
Expand All @@ -55,17 +84,17 @@ Shows real-time spectators both in chat messages and on-screen display with cust
| `CountColor` | Hex color code for spectator count. (**Default**: `"#87CEEB"`) | **YES** |

### Database Settings Parameters

*(Only used when `StorageType` is set to `"MySQL"`)*
| Parameter | Description | Required |
|-------------------|-----------------------------------------------------------------------------------------------------|----------|
| `Host` | MySQL server hostname or IP address. Leave empty to disable database. (**Default**: `""`) | **NO** |
| `Host` | MySQL server hostname or IP address. (**Default**: `"localhost"`) | **NO** |
| `Port` | MySQL server port. (**Default**: `3306`) | **NO** |
| `User` | MySQL username for database connection. (**Default**: `""`) | **NO** |
| `User` | MySQL username for database connection. (**Default**: `"root"`) | **NO** |
| `Password` | MySQL password for database connection. (**Default**: `""`) | **NO** |
| `DatabaseName` | Name of the MySQL database to use. Leave empty to disable database. (**Default**: `""`) | **NO** |
| `DatabaseName` | Name of the MySQL database to use. (**Default**: `""`) | **NO** |

---

## Support
## 📊 Support

For issues, questions, or feature requests, please visit our [GitHub Issues](https://github.com/wiruwiru/SpectatorList-CS2/issues) page.
Loading