using System.Linq; using System.Threading.Tasks; using Content.Server.Administration; using Content.Server.Database; using Content.Server.GameTicking; using Content.Shared.GameTicking; using Content.Shared._White.Reputation; using Robust.Server.Player; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; namespace Content.Server._White.Reputation; public sealed class ReputationManager : EntitySystem { [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IServerNetManager _netMgr = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IPlayerLocator _locator = default!; private readonly Dictionary _cacheReputation = new(); private readonly Dictionary _playerConnectionTime = new(); private ISawmill _sawmill = default!; private const string SawmillId = "reputation.logs"; public override void Initialize() { base.Initialize(); _netMgr.RegisterNetMessage(); _netMgr.Connecting += OnConnecting; _netMgr.Connected += OnConnected; SubscribeLocalEvent(OnRoundRestartCleanup); SubscribeLocalEvent(UpdateCachedReputation); SubscribeLocalEvent(OnPlayerSpawn); } #region Cache private void OnPlayerSpawn(PlayerBeforeSpawnEvent ev) { _playerConnectionTime[ev.Player.UserId] = DateTime.UtcNow; } private void OnConnected(object? sender, NetChannelArgs e) { _cacheReputation.TryGetValue(e.Channel.UserId, out var info); var msg = new ReputationNetMsg() { Info = info }; _netMgr.ServerSendMessage(msg, e.Channel); } private async Task OnConnecting(NetConnectingArgs e) { var uid = e.UserId; var value = await GetPlayerReputation(uid); if (value == null) return; var info = new ReputationInfo() { Value = value.Value }; _cacheReputation[e.UserId] = info; } private async void UpdateCachedReputation(UpdateCachedReputationEvent ev) { var player = ev.Player; if (!_cacheReputation.TryGetValue(player, out _)) return; var value = await GetPlayerReputation(player); if (value == null) return; var info = new ReputationInfo() { Value = value.Value }; _cacheReputation[player] = info; } private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev) { var connectedPlayers = _netMgr.Channels.Select(channel => channel.UserId).ToList(); var newDictionary = _cacheReputation .Where(player => connectedPlayers.Contains(player.Key)) .ToDictionary(player => player.Key, player => player.Value); _cacheReputation.Clear(); _playerConnectionTime.Clear(); foreach (var kvp in newDictionary) { _cacheReputation.Add(kvp.Key, kvp.Value); } } #endregion #region PublicApi public async void SetPlayerReputation(NetUserId player, float value, string? admin = null) { var preValue = await GetPlayerReputation(player); if (preValue == null) return; var guid = player.UserId; await SetPlayerReputationTask(guid, value); RaiseLocalEvent(new UpdateCachedReputationEvent(player)); await LogReputationChange(player, preValue.Value, false, admin); } public async void ModifyPlayerReputation(NetUserId player, float value, string? admin = null) { var preValue = await GetPlayerReputation(player); if (preValue == null) return; var guid = player.UserId; await ModifyPlayerReputationTask(guid, value); RaiseLocalEvent(new UpdateCachedReputationEvent(player)); await LogReputationChange(player, preValue.Value, true, admin); } public async Task GetPlayerReputation(NetUserId player) { var guid = player.UserId; return await GetPlayerReputationTask(guid); } public bool GetCachedPlayerReputation(NetUserId player, out float? value) { var success = _cacheReputation.TryGetValue(player, out var info); value = info?.Value; return success; } public bool GetCachedPlayerConnection(NetUserId player, out DateTime date) { var success = _playerConnectionTime.TryGetValue(player, out var dateTime); date = dateTime; return success; } public int GetPlayerWeight(float rep) { // Min-max return values const int minValue = 30; const int maxValue = 50; // Min-max reputation values const float minReputation = 0f; const float maxReputation = 1000f; if (rep < minReputation) return 20; var normalizedReputation = (rep - minReputation) / (maxReputation - minReputation); var result = (int)(minValue + (normalizedReputation * (maxValue - minValue))); result = Math.Max(minValue, Math.Min(maxValue, result)); return result; } public ICommonSession PickPlayerBasedOnReputation(List prefList) { var list = new List(); foreach (var session in prefList) { if (!GetCachedPlayerReputation(session.UserId, out var value)) continue; if (value == null) continue; var weight = GetPlayerWeight(value.Value); for (var i = 0; i < weight; i++) { list.Add(session); } } if (list.Count == 0) return _random.Pick(prefList); var number = _random.Next(list.Count - 1); return list[number]; } #endregion #region Private private async Task SetPlayerReputationTask(Guid player, float value) { try { await _db.SetPlayerReputation(player, value); } catch (Exception) { // Nope } } private async Task ModifyPlayerReputationTask(Guid player, float value) { try { await _db.ModifyPlayerReputation(player, value); } catch (Exception) { // Nope } } private async Task GetPlayerReputationTask(Guid player) { try { return await _db.GetPlayerReputation(player); } catch (Exception) { return null; } } private async Task LogReputationChange(NetUserId user, float preValue, bool modify, string? admin = null) { var located = await _locator.LookupIdAsync(user); if (located == null) return; var newValue = await GetPlayerReputation(user); if (newValue == null) return; var adminName = admin != null ? $" by {admin}" : ""; var msg = modify ? $"Reputation of {located.Username} was modified from {preValue} to {newValue.Value}{adminName}." : $"Reputation of {located.Username} was set from {preValue} to {newValue.Value}{adminName}."; _sawmill = _logManager.GetSawmill(SawmillId); _sawmill.Info(msg); } #endregion }