using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using PhoenixLib.Logging; using PhoenixLib.ServiceBus; using Qmmands; using WingsAPI.Communication; using WingsAPI.Communication.DbServer.AccountService; using WingsAPI.Communication.DbServer.CharacterService; using WingsAPI.Communication.Punishment; using WingsAPI.Data.Account; using WingsAPI.Data.Character; using WingsEmu.Commands.Checks; using WingsEmu.Commands.Entities; using WingsEmu.DTOs.Account; using WingsEmu.Game.Extensions; using WingsEmu.Game.Managers; using WingsEmu.Game.Networking; using WingsEmu.Packets.Enums; namespace WingsEmu.Plugins.Essentials.GameMaster; [Name("Punishment")] [Description("Module related to player punishment system")] [RequireAuthority(AuthorityType.GameMaster)] public class PunishmentModule : SaltyModuleBase { private readonly IAccountService _accountService; private readonly ICharacterService _characterService; private readonly IMessagePublisher _kickMessage; private readonly ISessionManager _sessionManager; public PunishmentModule(IMessagePublisher kickMessage, ISessionManager sessionManager, ICharacterService characterService, IAccountService accountService) { _kickMessage = kickMessage; _sessionManager = sessionManager; _characterService = characterService; _accountService = accountService; } [Command("penaltyinfo")] [Description("Get player penalty history")] public async Task PenaltyInfo(IClientSession target) { if (!target.Account.Logs.Any()) { return new SaltyCommandResult(true, "Player doesn't have any penalties!"); } IClientSession session = Context.Player; foreach (AccountPenaltyDto penalty in target.Account.Logs) { session.SendErrorChatMessage($"------[Id: {penalty.Id}]-------"); session.SendInformationChatMessage($"Penalty type: {penalty.PenaltyType.ToString()}"); session.SendInformationChatMessage($"Judge name: {penalty.JudgeName}"); session.SendInformationChatMessage($"Target name: {penalty.TargetName}"); session.SendInformationChatMessage($"Start: {penalty.Start}"); session.SendInformationChatMessage($"Mute remaining time: {penalty.RemainingTime}"); session.SendInformationChatMessage($"Reason: {penalty.Reason}"); if (!string.IsNullOrEmpty(penalty.UnlockReason)) { session.SendInformationChatMessage($"Unlock reason: {penalty.UnlockReason}"); } session.SendErrorChatMessage("----------------"); } return new SaltyCommandResult(true); } [Command("penaltyinfo")] [Description("Get penalty history by account id")] public async Task PenaltyInfo(long accountId) { AccountPenaltyGetAllResponse response = null; try { response = await _accountService.GetAccountPenalties(new AccountPenaltyGetRequest { AccountId = accountId }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'penaltyinfo'] Unexpected error: ", e); } if (response?.ResponseType != RpcResponseType.SUCCESS) { return new SaltyCommandResult(false, "Couldn't retrieve the account's penalties from AccountService"); } IEnumerable penalties = response.AccountPenaltyDtos.ToList(); if (!penalties.Any()) { return new SaltyCommandResult(true, $"No history was found in the database by account id: {accountId}."); } IClientSession session = Context.Player; foreach (AccountPenaltyDto penalty in penalties) { session.SendErrorChatMessage($"------[Id: {penalty.Id}]-------"); session.SendInformationChatMessage($"Penalty type: {penalty.PenaltyType.ToString()}"); session.SendInformationChatMessage($"Judge name: {penalty.JudgeName}"); session.SendInformationChatMessage($"Target name: {penalty.TargetName}"); session.SendInformationChatMessage($"Start: {penalty.Start}"); session.SendInformationChatMessage($"Mute remaining time: {penalty.RemainingTime}"); session.SendInformationChatMessage($"Reason: {penalty.Reason}"); if (!string.IsNullOrEmpty(penalty.UnlockReason)) { session.SendInformationChatMessage($"Unlock reason: {penalty.UnlockReason}"); } session.SendErrorChatMessage("----------------"); } return new SaltyCommandResult(true); } [Command("kick-id")] [Description("Kick player by player id")] public async Task KickAsync(long playerId) { IClientSession session = _sessionManager.GetSessionByCharacterId(playerId); if (session != null) { session.ForceDisconnect(); return new SaltyCommandResult(true, $"Player [{session.PlayerEntity.Name}] has been kicked."); } if (!_sessionManager.IsOnline(playerId)) { return new SaltyCommandResult(false, "Player is offline"); } await _kickMessage.PublishAsync(new PlayerKickMessage { PlayerId = playerId }); return new SaltyCommandResult(true, $"Kicking player with ID: {playerId} on different channel..."); } [Command("kick")] [Description("Kick player by player name")] public async Task KickAsync(string playerName) { IClientSession session = _sessionManager.GetSessionByCharacterName(playerName); if (session != null) { session.ForceDisconnect(); return new SaltyCommandResult(true, $"Player [{playerName}] has been kicked."); } if (!_sessionManager.IsOnline(playerName)) { return new SaltyCommandResult(false, "Player is offline"); } await _kickMessage.PublishAsync(new PlayerKickMessage { PlayerName = playerName }); return new SaltyCommandResult(true, $"Kicking player [{playerName}] on different channel..."); } [Command("mute")] [Description("Mute player (duration in minutes)")] public async Task Mute(IClientSession target, short minutes, [Remainder] string reason) { if (target.PlayerEntity.MuteRemainingTime.HasValue) { string timeLeft = target.PlayerEntity.MuteRemainingTime.Value.ToString(@"hh\:mm\:ss"); return new SaltyCommandResult(false, $"Player is already muted - time left: {timeLeft}"); } DateTime now = DateTime.UtcNow; var time = TimeSpan.FromMinutes(minutes); int? seconds = (int?)time.TotalSeconds; IClientSession session = Context.Player; AccountPenaltyDto newPenalty = new() { JudgeName = session.PlayerEntity.Name, TargetName = target.PlayerEntity.Name, AccountId = target.Account.Id, Start = now, RemainingTime = seconds, PenaltyType = PenaltyType.Muted, Reason = reason }; target.Account.Logs.Add(newPenalty); target.PlayerEntity.MuteRemainingTime = TimeSpan.FromSeconds(time.TotalSeconds); target.PlayerEntity.LastChatMuteMessage = DateTime.MinValue; target.PlayerEntity.LastMuteTick = now; return new SaltyCommandResult(true, $"Player [{target.PlayerEntity.Name}] has been muted for [{reason}]."); } [Command("unmute")] [Description("Unmute player")] public async Task Unmute(IClientSession target, [Remainder] string reason) { TimeSpan? muteTime = target.PlayerEntity.MuteRemainingTime; if (muteTime == null) { return new SaltyCommandResult(false, $"Player [{target.PlayerEntity.Name}] isn't muted."); } target.PlayerEntity.LastChatMuteMessage = null; target.PlayerEntity.MuteRemainingTime = null; AccountPenaltyDto penalty = target.Account.Logs.FirstOrDefault(x => x.PenaltyType == PenaltyType.Muted && x.RemainingTime.HasValue); if (penalty != null) { penalty.UnlockReason = reason; penalty.RemainingTime = null; } return new SaltyCommandResult(true, $"Player [{target.PlayerEntity.Name}] has been unmuted."); } [Command("ban")] [Description("Ban player by player id (duration in hours - max 1 week)")] public async Task Ban(long playerId, short hours, [Remainder] string reason) { IClientSession session = Context.Player; IClientSession target = _sessionManager.GetSessionByCharacterId(playerId); // Max 1 week var time = TimeSpan.FromHours(hours > 168 ? 168 : hours); DateTime utcNow = DateTime.UtcNow; AccountBanDto newBan = new() { JudgeName = session.PlayerEntity.Name, Start = utcNow, End = utcNow + time, Reason = reason }; if (target != null) { newBan.AccountId = target.Account.Id; newBan.TargetName = target.PlayerEntity.Name; AccountBanSaveResponse response = null; try { response = await _accountService.SaveAccountBan(new AccountBanSaveRequest { AccountBanDto = newBan }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'ban'] Unexpected error: ", e); } if (response?.ResponseType != RpcResponseType.SUCCESS) { return new SaltyCommandResult(false, "Couldn't save the AccountBan through AccountService"); } target.ForceDisconnect(); return new SaltyCommandResult(true, $"Player [{target.PlayerEntity.Name}] has been banned for [{reason}] with duration of [{hours}] hours."); } DbServerGetCharacterResponse targetResponse = null; try { targetResponse = await _characterService.GetCharacterById(new DbServerGetCharacterByIdRequest { CharacterId = playerId }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'ban'] Unexpected error: ", e); } if (targetResponse?.RpcResponseType != RpcResponseType.SUCCESS) { return new SaltyCommandResult(false, "Couldn't retrieve the CharacterDTO from CharacterService"); } CharacterDTO targetCharacter = targetResponse.CharacterDto; await _kickMessage.PublishAsync(new PlayerKickMessage { PlayerId = playerId }); newBan.AccountId = targetCharacter.AccountId; newBan.TargetName = targetCharacter.Name; AccountBanSaveResponse response2 = null; try { response2 = await _accountService.SaveAccountBan(new AccountBanSaveRequest { AccountBanDto = newBan }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'ban'] Unexpected error: ", e); } return response2?.ResponseType != RpcResponseType.SUCCESS ? new SaltyCommandResult(false, "Couldn't save the AccountBan through AccountService") : new SaltyCommandResult(true, $"Banning player with ID: [{playerId}] for [{reason}] with duration of [{hours}] hours."); } [Command("ban")] [Description("Ban player by player name (duration in hours)")] public async Task Ban(string playerName, short hours, [Remainder] string reason) { IClientSession session = Context.Player; IClientSession target = _sessionManager.GetSessionByCharacterName(playerName); // Max 1 week var time = TimeSpan.FromHours(hours > 168 ? 168 : hours); DateTime utcNow = DateTime.UtcNow; AccountBanDto newBan = new() { JudgeName = session.PlayerEntity.Name, Start = utcNow, End = utcNow + time, Reason = reason }; if (target != null) { newBan.AccountId = target.Account.Id; newBan.TargetName = target.PlayerEntity.Name; AccountBanSaveResponse response = null; try { response = await _accountService.SaveAccountBan(new AccountBanSaveRequest { AccountBanDto = newBan }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'ban'] Unexpected error: ", e); } if (response?.ResponseType != RpcResponseType.SUCCESS) { return new SaltyCommandResult(false, "Couldn't save the AccountBan through AccountService"); } target.ForceDisconnect(); return new SaltyCommandResult(true, $"Player [{playerName}] has been banned for [{reason}] with duration of [{hours}] hours."); } DbServerGetCharacterResponse targetResponse = null; try { targetResponse = await _characterService.GetCharacterByName(new DbServerGetCharacterRequestByName { CharacterName = playerName }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'ban'] Unexpected error: ", e); } if (targetResponse?.RpcResponseType != RpcResponseType.SUCCESS) { return new SaltyCommandResult(false, "Couldn't retrieve the CharacterDTO from CharacterService"); } CharacterDTO targetCharacter = targetResponse.CharacterDto; await _kickMessage.PublishAsync(new PlayerKickMessage { PlayerName = playerName }); newBan.AccountId = targetCharacter.AccountId; newBan.TargetName = targetCharacter.Name; AccountBanSaveResponse response2 = null; try { response2 = await _accountService.SaveAccountBan(new AccountBanSaveRequest { AccountBanDto = newBan }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'ban'] Unexpected error: ", e); } return response2?.ResponseType != RpcResponseType.SUCCESS ? new SaltyCommandResult(false, "Couldn't save the AccountBan through AccountService") : new SaltyCommandResult(true, $"Banning [{playerName}] for [{reason}] with duration of [{hours}] hours."); } [Command("unban")] [Description("Unban player by player id")] public async Task Unban(long playerId, [Remainder] string reason) { DbServerGetCharacterResponse targetResponse = null; try { targetResponse = await _characterService.GetCharacterById(new DbServerGetCharacterByIdRequest { CharacterId = playerId }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'unban'] Unexpected error: ", e); } if (targetResponse?.RpcResponseType != RpcResponseType.SUCCESS) { return new SaltyCommandResult(false, "Couldn't retrieve the CharacterDTO from CharacterService"); } CharacterDTO target = targetResponse.CharacterDto; AccountBanGetResponse response = null; try { response = await _accountService.GetAccountBan(new AccountBanGetRequest { AccountId = target.AccountId }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'unban'] Unexpected error: ", e); } if (response?.ResponseType != RpcResponseType.SUCCESS) { return new SaltyCommandResult(false, "Couldn't get the AccountBan through AccountService"); } AccountBanDto ban = response.AccountBanDto; ban.End = DateTime.UtcNow; ban.UnlockReason = reason; AccountBanSaveResponse saveResponse = null; try { saveResponse = await _accountService.SaveAccountBan(new AccountBanSaveRequest { AccountBanDto = ban }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'unban'] Unexpected error: ", e); } return saveResponse?.ResponseType != RpcResponseType.SUCCESS ? new SaltyCommandResult(false, "Couldn't save the AccountBan through AccountService") : new SaltyCommandResult(true, $"Player [{target.Name}] has been unbanned with the reason [{reason}]"); } [Command("unban")] [Description("Unban player by player name")] public async Task Unban(string playerName, [Remainder] string reason) { DbServerGetCharacterResponse targetResponse = null; try { targetResponse = await _characterService.GetCharacterByName(new DbServerGetCharacterRequestByName { CharacterName = playerName }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'unban'] Unexpected error: ", e); } if (targetResponse?.RpcResponseType != RpcResponseType.SUCCESS) { return new SaltyCommandResult(false, "Couldn't retrieve the CharacterDTO from CharacterService"); } CharacterDTO target = targetResponse.CharacterDto; AccountBanGetResponse response = null; try { response = await _accountService.GetAccountBan(new AccountBanGetRequest { AccountId = target.AccountId }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'unban'] Unexpected error: ", e); } if (response?.ResponseType != RpcResponseType.SUCCESS) { return new SaltyCommandResult(false, "Couldn't get the AccountBan through AccountService"); } AccountBanDto ban = response.AccountBanDto; ban.End = DateTime.UtcNow; ban.UnlockReason = reason; AccountBanSaveResponse saveResponse = null; try { saveResponse = await _accountService.SaveAccountBan(new AccountBanSaveRequest { AccountBanDto = ban }); } catch (Exception e) { Log.Error("[PUNISHMENT_MODULE][Command: 'ban'] Unexpected error: ", e); } return saveResponse?.ResponseType != RpcResponseType.SUCCESS ? new SaltyCommandResult(false, "Couldn't save the AccountBan through AccountService") : new SaltyCommandResult(true, $"Player [{playerName}] has been unbanned with the reason [{reason}]"); } }