using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using PhoenixLib.Events; using Qmmands; using WingsAPI.Communication; using WingsAPI.Communication.DbServer.CharacterService; using WingsAPI.Communication.ServerApi; using WingsAPI.Communication.ServerApi.Protocol; using WingsAPI.Game.Extensions.ItemExtension.Inventory; using WingsAPI.Game.Extensions.ItemExtension.Item; using WingsAPI.Game.Extensions.Quicklist; using WingsAPI.Packets.Enums; using WingsEmu.Commands.Checks; using WingsEmu.Commands.Entities; using WingsEmu.DTOs.Account; using WingsEmu.DTOs.Recipes; using WingsEmu.DTOs.Skills; using WingsEmu.DTOs.Titles; using WingsEmu.Game; using WingsEmu.Game._i18n; using WingsEmu.Game.Algorithm; using WingsEmu.Game.Battle; using WingsEmu.Game.Buffs; using WingsEmu.Game.Characters; using WingsEmu.Game.Characters.Events; using WingsEmu.Game.Compliments; using WingsEmu.Game.Entities; using WingsEmu.Game.Entities.Extensions; using WingsEmu.Game.Extensions; using WingsEmu.Game.Extensions.Mates; using WingsEmu.Game.Helpers.Damages; using WingsEmu.Game.Inventory; using WingsEmu.Game.Inventory.Event; using WingsEmu.Game.Items; using WingsEmu.Game.Managers; using WingsEmu.Game.Managers.ServerData; using WingsEmu.Game.Managers.StaticData; using WingsEmu.Game.Maps; using WingsEmu.Game.Mates; using WingsEmu.Game.Monster.Event; using WingsEmu.Game.Networking; using WingsEmu.Game.Networking.Broadcasting; using WingsEmu.Game.Skills; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Chat; namespace WingsEmu.Plugins.Essentials.Administrator; [Name("Administrator")] [Description("Module related to Administrator commands.")] [RequireAuthority(AuthorityType.GameAdmin)] public class AdministratorModule : SaltyModuleBase { private readonly IBuffFactory _buffFactory; private readonly ICardsManager _cards; private readonly ICharacterAlgorithm _characterAlgorithm; private readonly ICharacterService _characterService; private readonly IMonsterEntityFactory _entity; private readonly IAsyncEventPipeline _eventPipeline; private readonly IGameItemInstanceFactory _gameItemInstanceFactory; private readonly SerializableGameServer _gameServer; private readonly IItemsManager _itemManager; private readonly IGameLanguageService _language; private readonly IServerManager _manager; private readonly INpcMonsterManager _npcMonsterManager; private readonly IRecipeManager _recipeManager; private readonly IServerApiService _serverApiService; private readonly ISessionManager _sessionManager; private readonly ISkillsManager _skillManager; private readonly ISkillsManager _skillsManager; public AdministratorModule(IServerManager manager, IGameLanguageService language, ISessionManager sessionManager, IItemsManager itemsManager, INpcMonsterManager npcMonsterManager, ISkillsManager skillManager, IAsyncEventPipeline eventPipeline, IBuffFactory buffFactory, IRecipeManager recipeManager, ICharacterAlgorithm characterAlgorithm, ICardsManager cards, IGameItemInstanceFactory gameItemInstanceFactory, IMonsterEntityFactory entity, ICharacterService characterService, ISkillsManager skillsManager, IServerApiService serverApiService, SerializableGameServer gameServer) { _itemManager = itemsManager; _npcMonsterManager = npcMonsterManager; _skillManager = skillManager; _sessionManager = sessionManager; _manager = manager; _language = language; _eventPipeline = eventPipeline; _buffFactory = buffFactory; _recipeManager = recipeManager; _characterAlgorithm = characterAlgorithm; _cards = cards; _gameItemInstanceFactory = gameItemInstanceFactory; _entity = entity; _characterService = characterService; _skillsManager = skillsManager; _serverApiService = serverApiService; _gameServer = gameServer; } [Command("removeitem")] [Description("Remove item from the target inventory")] public async Task RemoveItem(IClientSession target, byte slot, InventoryType inventoryType, short? amount = null) { if (inventoryType == InventoryType.EquippedItems) { return new SaltyCommandResult(false, "Don't use it for this - use $remove-item-equipped"); } InventoryItem item = target.PlayerEntity.GetItemBySlotAndType(slot, inventoryType); if (item?.ItemInstance == null) { return new SaltyCommandResult(false, "Item not found in this slot and type"); } await target.RemoveItemFromInventory(item: item, amount: (short)(amount ?? item.ItemInstance.Amount)); return new SaltyCommandResult(true, "Item has been removed from the player"); } [Command("removeitem-equipped")] [Description("Remove equipped item from the target inventory")] public async Task RemoveItemEquipped(IClientSession target, EquipmentType slot) { InventoryItem item = target.PlayerEntity.GetAllPlayerInventoryItems().FirstOrDefault(x => x?.ItemInstance != null && x.IsEquipped && x.ItemInstance.GameItem.EquipmentSlot == slot); if (item?.ItemInstance == null) { return new SaltyCommandResult(false, "Item not found in this slot."); } await target.RemoveItemFromInventory(item: item, isEquiped: true); if (target.ShouldSendAmuletPacket(item.ItemInstance.GameItem.EquipmentSlot)) { target.SendEmptyAmuletBuffPacket(); } return new SaltyCommandResult(true, "Item has been removed from the player"); } [Command("skillscd")] public async Task SkillsCd() { IClientSession session = Context.Player; foreach (CharacterSkill skill in session.PlayerEntity.CharacterSkills.Values) { session.SendChatMessage($"Skill {skill.Skill.Id} can be used: {session.PlayerEntity.SkillCanBeUsed(skill)} - LastUse: {skill.LastUse}", ChatMessageColorType.Yellow); } return new SaltyCommandResult(true); } [Command("check-monster")] public async Task CheckMonster() { IClientSession session = Context.Player; (VisualType visualType, long id) = session.PlayerEntity.LastEntity; IBattleEntity entity = session.CurrentMapInstance.GetBattleEntity(visualType, id); if (entity is not IMonsterEntity monsterEntity) { return new SaltyCommandResult(false, "Entity is null or it's not the monster"); } session.SendChatMessage($" Id: {monsterEntity.Id}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" IsStillAlive: {monsterEntity.IsStillAlive}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" SpawnDate: {monsterEntity.SpawnDate}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" Targets count: {monsterEntity.Targets.Count}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" Damagers count: {monsterEntity.Damagers.Count}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" NextTick: {monsterEntity.NextTick}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" NextAttackReady: {monsterEntity.NextAttackReady}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" Target: {monsterEntity.Target?.Type}|{monsterEntity.Target?.Id}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" IsApproachingTarget: {monsterEntity.IsApproachingTarget}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" ShouldFindNewTarget: {monsterEntity.ShouldFindNewTarget}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" FindNewPositionAroundTarget: {monsterEntity.FindNewPositionAroundTarget}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" ShouldRespawn: {monsterEntity.ShouldRespawn}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" IsMonsterSpawningMonstersForQuest: {monsterEntity.IsMonsterSpawningMonstersForQuest()}", ChatMessageColorType.DarkGrey); session.SendChatMessage($" Speed: {monsterEntity.Speed}", ChatMessageColorType.DarkGrey); session.SendChatMessage("[TARGETS]", ChatMessageColorType.Green); foreach (IBattleEntity target in monsterEntity.Targets) { session.SendChatMessage($"{target.Type}|{target.Id}", ChatMessageColorType.Yellow); } session.SendChatMessage("[DAMAGERS]", ChatMessageColorType.Green); foreach (IBattleEntity damager in monsterEntity.Damagers) { if (damager == null) { continue; } if (damager.MapInstance == null) { session.SendChatMessage($"Damager: {damager.Type}|{damager.Id} MapInstance null", ChatMessageColorType.Red); continue; } if (monsterEntity.MapInstance.Id != damager.MapInstance.Id) { session.SendChatMessage($"Damager: {damager.Type}|{damager.Id} MapInstance is not at the same map", ChatMessageColorType.Red); continue; } session.SendChatMessage($"Damager: {damager.Type}|{damager.Id}", ChatMessageColorType.Yellow); } return new SaltyCommandResult(true); } [Command("setsp")] public async Task setsp(int amount) { Context.Player.PlayerEntity.SpPointsBasic = amount; Context.Player.RefreshSpPoint(); return new SaltyCommandResult(true); } [Command("mapdance")] [Description("Dance!")] public async Task MapDance() { Context.Player.CurrentMapInstance.IsDance = !Context.Player.CurrentMapInstance.IsDance; Context.Player.CurrentMapInstance.Broadcast(x => x.GenerateDance(Context.Player.CurrentMapInstance.IsDance)); return new SaltyCommandResult(true, $"Map dancing: {Context.Player.CurrentMapInstance.IsDance}"); } [Command("mapmusic")] [Description("Play music on map")] public async Task MapMusic(short music) { Context.Player.CurrentMapInstance.MapMusic = music; Context.Player.CurrentMapInstance.Broadcast(x => x.GenerateMapMusic(music)); return new SaltyCommandResult(true, $"Map music: {music}"); } [Command("mapmusic")] [Description("Turn off the music on map")] public async Task MapMusic() { Context.Player.CurrentMapInstance.MapMusic = null; return new SaltyCommandResult(true); } [Command("hlevel", "hlvl")] [Description("Set player hero level")] public async Task SetHLvl( [Description("Hero level")] byte level) { IClientSession session = Context.Player; session.PlayerEntity.HeroLevel = level; session.PlayerEntity.HeroXp = 0; session.PlayerEntity.Hp = session.PlayerEntity.MaxHp; session.PlayerEntity.Mp = session.PlayerEntity.MaxMp; session.RefreshStat(); session.RefreshStatInfo(); session.RefreshStatChar(); session.RefreshLevel(_characterAlgorithm); return new SaltyCommandResult(true, "Hero level has been updated."); } [Command("debug")] [Description("toggles the debug mode on the client side")] public async Task ToggleDebugModeAsync() { Context.Player.DebugMode = !Context.Player.DebugMode; Context.Player.SendChatMessage($"DEBUG_MODE: {(Context.Player.DebugMode ? "ON" : "OFF")}", ChatMessageColorType.Yellow); if (Context.Player.DebugMode) { Context.Player.SendPacket("debug"); } return new SaltyCommandResult(true); } [Command("unlockalltitles")] [Description("Unlocks all titles available")] public async Task AddAllTitles() { IClientSession session = Context.Player; IEnumerable allTitles = _itemManager.GetItemsByType(ItemType.Title); foreach (IGameItem title in allTitles.Where(title => session.PlayerEntity.Titles.All(x => x.ItemVnum != title.Id))) { session.PlayerEntity.Titles.Add(new CharacterTitleDto { TitleId = _itemManager.GetTitleId(title.Id), ItemVnum = title.Id }); } session.SendTitlePacket(); return new SaltyCommandResult(true, "All titles have been unlocked."); } [Command("entities")] public async Task Entities(byte range) { IReadOnlyList entities = Context.Player.CurrentMapInstance.GetBattleEntitiesInRange(Context.Player.PlayerEntity.Position, range); foreach (IBattleEntity entity in entities) { Context.Player.SendChatMessage($"Entity - Type: {entity.Type.ToString()}, Id: {entity.Id}", ChatMessageColorType.Green); } Context.Player.SendChatMessage($"Entities: {entities.Count}", ChatMessageColorType.Red); return new SaltyCommandResult(true); } [Command("setitemdate")] public async Task SetItemDate(short slot, short minutes) { IClientSession session = Context.Player; InventoryItem item = session.PlayerEntity.GetItemBySlotAndType(slot, InventoryType.Equipment); if (item?.ItemInstance == null) { return new SaltyCommandResult(false, "No item."); } item.ItemInstance.ItemDeleteTime = DateTime.UtcNow.AddMinutes(minutes); return new SaltyCommandResult(true, $"The item will disappear: {item.ItemInstance.ItemDeleteTime}"); } [Command("check-pos")] [Description("Checks if the position is alright")] public async Task CheckPosition() { Position position = Context.Player.PlayerEntity.Position; return new SaltyCommandResult(true, $"Position CanWalkAround: {Context.Player.CurrentMapInstance.CanWalkAround(position.X, position.Y).ToString()}"); } [Command("nocd")] [Description("Enable/disable cooldown reduction from skills")] public async Task NoCd() { IClientSession session = Context.Player; session.PlayerEntity.CheatComponent.HasNoCooldown = !session.PlayerEntity.CheatComponent.HasNoCooldown; return new SaltyCommandResult(true, $"No cooldown: {session.PlayerEntity.CheatComponent.HasNoCooldown}"); } [Command("nolimit", "notargetlimit")] [Description("Enable/disable cooldown reduction from skills")] public async Task NoTargetLimit() { IClientSession session = Context.Player; session.PlayerEntity.CheatComponent.HasNoTargetLimit = !session.PlayerEntity.CheatComponent.HasNoTargetLimit; return new SaltyCommandResult(true, $"No target limit: {session.PlayerEntity.CheatComponent.HasNoTargetLimit.ToString()}"); } [Command("clearinv")] [Description("Remove all items from your inventory.")] public async Task ClearInv() { IClientSession session = Context.Player; foreach (InventoryItem eqItem in session.PlayerEntity.EquippedItems) { if (eqItem?.ItemInstance == null) { continue; } session.PlayerEntity.TakeOffItem(eqItem.ItemInstance.GameItem.EquipmentSlot); session.PlayerEntity.RefreshEquipmentValues(eqItem.ItemInstance, true); } foreach (InventoryItem item in session.PlayerEntity.GetAllPlayerInventoryItems()) { if (item?.ItemInstance == null) { continue; } session.PlayerEntity.RemoveItemFromSlotAndType(item.Slot, item.InventoryType, out InventoryItem removedItem); session.SendInventoryRemovePacket(removedItem); } Context.Player.PlayerEntity.MinilandObjects.Clear(); Context.Player.RefreshEquipment(); Context.Player.RefreshStatChar(); Context.Player.RefreshStat(); return new SaltyCommandResult(true, "Inventory has been cleared."); } [Command("recv")] [Description("Send recv packet to you.")] public async Task Recv([Remainder] string packet) { Context.Player.SendPacket(packet); return new SaltyCommandResult(true); } [Command("recurrent-walk-bubble")] [Description("put a bubble on your head with your coordinates")] public async Task WalkBubble([Description("The amount of minutes you want your bubble")] int minutes) { var tmp = new CancellationTokenSource(TimeSpan.FromMinutes(minutes)); Task.Run(async () => { while (!tmp.Token.IsCancellationRequested) { Context.Player.SendPacket(Context.Player.GenerateBubble($"X: {Context.Player.PlayerEntity.PositionX} | Y: {Context.Player.PlayerEntity.PositionY}")); await Task.Delay(500, tmp.Token); } }); return new SaltyCommandResult(true); } [Command("haircolor")] [Description("Sets your hair color")] public async Task HairColor(byte hairColor) { IClientSession session = Context.Player; if (!Enum.IsDefined(typeof(HairColorType), hairColor)) { return new SaltyCommandResult(false, "The given color was not found."); } session.PlayerEntity.HairColor = (HairColorType)hairColor; session.BroadcastEq(); return new SaltyCommandResult(true); } [Command("recvtarget")] [Description("Send recv packet to target.")] public async Task RecvTarget(IClientSession target, [Remainder] string packet) { target.SendPacket(packet); return new SaltyCommandResult(true, $"Receiving packet: {packet}"); } [Command("kick-all", "clear-channel")] [Description("Kick all players in the channel.")] public async Task KickPlayers() { var tmp = _sessionManager.Sessions.ToList(); foreach (IClientSession session in tmp) { if (session == Context.Player) { continue; } Context.Player.SendSuccessChatMessage($"[KICK_ALL] Kicking {session.PlayerEntity.Name}..."); session.ForceDisconnect(); } return new SaltyCommandResult(true, "All player has been kicked on the channel."); } [Command("flush-all", "flush-save")] [Description("Kick all players in the channel.")] public async Task FlushAll() { var stopwatch = Stopwatch.StartNew(); await _characterService.FlushCharacterSaves(new DbServerFlushCharacterSavesRequest()); stopwatch.Stop(); return new SaltyCommandResult(true, $"Flushed after {stopwatch.ElapsedMilliseconds.ToString()}ms"); } [Command("recipe")] [Description("Get all items for recipe of item")] public async Task Recipe( [Description("Item vnum to create")] short vNum) { IClientSession session = Context.Player; IReadOnlyList recipes = _recipeManager.GetRecipeByProducedItemVnum(vNum); if (recipes == null) { return new SaltyCommandResult(false, "No recipe for given item."); } foreach (Recipe item in recipes) { foreach (RecipeItemDTO recipeItem in item.Items) { GameItemInstance newItem = _gameItemInstanceFactory.CreateItem(recipeItem.ItemVNum, recipeItem.Amount); await session.AddNewItemToInventory(newItem); } } return new SaltyCommandResult(true, "Items have been added to your inventory."); } [Command("drop")] [Description("Drop the item on the floor")] public async Task DropAsync(short vNum, short amount) { IClientSession session = Context.Player; var position = new Position(session.PlayerEntity.PositionX, session.PlayerEntity.PositionY); await _eventPipeline.ProcessEventAsync(new DropMapItemEvent(session.CurrentMapInstance, position, vNum, amount)); return new SaltyCommandResult(true, "The item dropped."); } [Command("sp")] [Description("Transform to SP instantly")] public async Task SpecialistAsync() { IClientSession session = Context.Player; GameItemInstance sp = session.PlayerEntity.Specialist; if (sp == null) { return new SaltyCommandResult(false); } await session.EmitEventAsync(new SpTransformEvent { Specialist = sp }); return new SaltyCommandResult(true); } [Command("sound")] public async Task Sound(int value) { Context.Player.SendGuriPacket(19, 0, value); return new SaltyCommandResult(true); } [Command("sound")] public async Task Sound(int value, IClientSession target) { if (target == null) { return new SaltyCommandResult(false); } target.SendGuriPacket(19, 0, value); return new SaltyCommandResult(true); } [Command("bufftarget")] public async Task BuffTarget(short buffId) { IClientSession session = Context.Player; IBattleEntity target = session.CurrentMapInstance.GetBattleEntity(session.PlayerEntity.LastEntity.Item1, session.PlayerEntity.LastEntity.Item2); Card card = _cards.GetCardByCardId(buffId); if (card == null) { return new SaltyCommandResult(false, "Not a buff"); } if (target == null) { return new SaltyCommandResult(false, "Target no exist!"); } Buff buff = _buffFactory.CreateBuff(card.Id, session.PlayerEntity); await target.AddBuffAsync(buff); return new SaltyCommandResult(true, $"Added buff {_language.GetLanguage(GameDataType.Card, card.Name, session.UserLanguage)} for target ({target.Type.ToString()}|{target.Id})"); } [Command("butcher")] public async Task Butcher(byte range = 0) { IClientSession session = Context.Player; int count = 0; IReadOnlyList entities = session.CurrentMapInstance.GetAliveMonsters(); foreach (IMonsterEntity monster in entities.ToList()) { if (range > 0 && !monster.Position.IsInAoeZone(session.PlayerEntity, range)) { continue; } if (!monster.IsAlive()) { continue; } monster.MapInstance.Broadcast(monster.GenerateOut()); await _eventPipeline.ProcessEventAsync(new MonsterDeathEvent(monster) { IsByCommand = true }); count++; } return new SaltyCommandResult(true, $"Killed {count} monsters"); } [Command("kill")] [Description("Kills a player in the map you are.")] public async Task Kill( [Description("Name of the character to kill")] string nickname, [Description("Should the players know that it was your fault?")] bool anonymous = true) { IClientSession target = Context.Player.PlayerEntity.MapInstance.Sessions.FirstOrDefault(s => s.CharacterName() == nickname); return BasicKillLogic(Context.Player.PlayerEntity, target.PlayerEntity, anonymous); } [Command("killtarget")] [Description("Kills a player in the map you are.")] public async Task KillTarget( [Description("Should the players know that it was your fault?")] bool anonymous = true) { IClientSession session = Context.Player; IBattleEntity target = session.CurrentMapInstance.GetBattleEntity(session.PlayerEntity.LastEntity.Item1, session.PlayerEntity.LastEntity.Item2); return BasicKillLogic(session.PlayerEntity, target, anonymous); } private SaltyCommandResult BasicKillLogic(IBattleEntity issuer, IBattleEntity victim, bool anonymous) => anonymous ? AnonymousKill(victim) : PublicKill(issuer, victim); private SaltyCommandResult AnonymousKill(IBattleEntity suicidalEntity) { if (suicidalEntity == default) { return new SaltyCommandResult(false, "The target provided is not valid"); } var algorithmResult = new DamageAlgorithmResult(int.MaxValue, HitType.Critical, true, false); _eventPipeline.ProcessEventAsync(new ApplyHitEvent(suicidalEntity, algorithmResult, new HitInformation(suicidalEntity, _skillsManager.GetSkill(299).GetInfo()))); return new SaltyCommandResult(true); } private SaltyCommandResult PublicKill(IBattleEntity issuer, IBattleEntity victim) { if (issuer == default || victim == default) { return new SaltyCommandResult(false, "The target provided is not valid"); } var algorithmResult = new DamageAlgorithmResult(int.MaxValue, HitType.Critical, true, false); _eventPipeline.ProcessEventAsync(new ApplyHitEvent(victim, algorithmResult, new HitInformation(issuer, _skillsManager.GetSkill(1049).GetInfo()))); return new SaltyCommandResult(true); } [Command("map-respawn")] public async Task MapRespawn(byte range = 0) { IClientSession session = Context.Player; int count = 0; IReadOnlyList entities = session.CurrentMapInstance.GetDeadMonsters(); foreach (IMonsterEntity monster in entities.ToList()) { if (range > 0 && !monster.Position.IsInAoeZone(session.PlayerEntity, range)) { continue; } monster.Death = DateTime.MinValue; count++; } return new SaltyCommandResult(true, $"{count} monsters are now alive!"); } [Command("cleardrops")] public async Task ClearDrops(byte range = 0) { IClientSession session = Context.Player; int count = 0; IReadOnlyList drops = session.CurrentMapInstance.Drops; foreach (MapItem drop in drops) { if (range > 0 && !session.PlayerEntity.Position.IsInAoeZone(new Position(drop.PositionX, drop.PositionY), range)) { continue; } count++; drop.BroadcastOut(); session.CurrentMapInstance.RemoveDrop(drop.TransportId); } return new SaltyCommandResult(true, $"{count} drops has been removed from the map."); } [Command("removebuff")] [Description("Remove the given buff from the target")] public async Task RemoveBuff(IClientSession target, int cardId, bool force = false) { IClientSession session = Context.Player; if (target == null) { return new SaltyCommandResult(false, "target is offline"); } Buff tmp = target.PlayerEntity.BuffComponent.GetBuff(cardId); if (tmp == null) { return new SaltyCommandResult(false, $"Given target does not have Buff with cardId: {cardId}"); } await target.PlayerEntity.RemoveBuffAsync(force, tmp); return new SaltyCommandResult(true); } [Command("sethp")] [Description("Set your character hp to selected value")] public async Task SetHp(int hp, bool mates = false) { IClientSession session = Context.Player; if (hp == 0) { return new SaltyCommandResult(false); } session.PlayerEntity.Hp = hp; session.RefreshStat(); session.RefreshStatInfo(); if (!mates) { return new SaltyCommandResult(true); } foreach (IMateEntity mate in session.PlayerEntity.MateComponent.TeamMembers()) { mate.Hp = hp; } session.RefreshMateStats(); return new SaltyCommandResult(true); } [Command("setmp")] [Description("Set your character mp to selected value")] public async Task SetMp(ushort mp, bool mates = false) { IClientSession session = Context.Player; session.PlayerEntity.Mp = mp; session.RefreshStat(); session.RefreshStatInfo(); if (!mates) { return new SaltyCommandResult(true); } foreach (IMateEntity mate in session.PlayerEntity.MateComponent.TeamMembers()) { mate.Mp = mp; } session.RefreshMateStats(); return new SaltyCommandResult(true); } [Command("addskill")] [Description("Add skill.")] public async Task AddSkillAsync( [Description("Skill VNUM.")] short skillVNum) { IClientSession session = Context.Player; SkillDTO skillinfo = _skillManager.GetSkill(skillVNum); if (skillinfo == null) { return new SaltyCommandResult(false, "The skill doesn't exist!"); } if (skillinfo.Id < 200) { foreach (CharacterSkill skill in session.PlayerEntity.CharacterSkills.Select(s => s.Value)) { if (skillinfo.CastId == skill.Skill.CastId && skill.Skill.Id < 200) { session.PlayerEntity.CharacterSkills.TryRemove(skill.SkillVNum, out CharacterSkill _); } } } else { if (session.PlayerEntity.CharacterSkills.ContainsKey(skillVNum)) { return new SaltyCommandResult(true, "You have already this skill learnt."); } if (skillinfo.UpgradeSkill != 0) { CharacterSkill oldupgrade = session.PlayerEntity.CharacterSkills.Select(s => s.Value).FirstOrDefault(s => s.Skill.UpgradeSkill == skillinfo.UpgradeSkill && s.Skill.UpgradeType == skillinfo.UpgradeType && s.Skill.UpgradeSkill != 0); if (oldupgrade != null) { session.PlayerEntity.CharacterSkills.TryRemove(oldupgrade.SkillVNum, out CharacterSkill _); } } } var newSkill = new CharacterSkill { SkillVNum = skillVNum }; session.PlayerEntity.CharacterSkills[skillVNum] = newSkill; session.PlayerEntity.Skills.Add(newSkill); session.RefreshSkillList(); session.RefreshQuicklist(); session.RefreshLevel(_characterAlgorithm); return new SaltyCommandResult(true, $"Skill {_language.GetLanguage(GameDataType.Skill, skillinfo.Name, session.UserLanguage)} has been added."); } [Command("removeskill")] [Description("Remove skill.")] public async Task RemoveSkillAsync( [Description("Skill VNUM.")] short skillvnum) { IClientSession session = Context.Player; SkillDTO skillinfo = _skillManager.GetSkill(skillvnum); if (skillinfo == null) { return new SaltyCommandResult(false, "The skill doesn't exist!"); } session.PlayerEntity.CharacterSkills.TryRemove(skillvnum, out CharacterSkill _); IBattleEntitySkill toRemove = session.PlayerEntity.Skills.FirstOrDefault(x => x.Skill.Id == skillvnum); session.PlayerEntity.Skills.Remove(toRemove); session.RefreshSkillList(); session.RefreshQuicklist(); session.RefreshLevel(_characterAlgorithm); return new SaltyCommandResult(true, "Skill has been removed."); } [Command("online")] [Description("Show players in game")] public async Task PlayersOnlineAsync() { IClientSession session = Context.Player; session.SendChatMessage("[Players on the server]", ChatMessageColorType.Red); foreach (IClientSession s in _sessionManager.Sessions) { session.SendChatMessage($"{s.PlayerEntity.Name} | AccID: {s.Account.Id} | CVersion: {s.ClientVersion}", ChatMessageColorType.LightPurple); } session.SendChatMessage($"Online: {_sessionManager.SessionsCount}", ChatMessageColorType.Red); return new SaltyCommandResult(true); } [Command("list-hw")] [Description("Show players in game")] public async Task ListHardwareIdsAsync() { IClientSession session = Context.Player; session.SendChatMessage("[Players on the server]", ChatMessageColorType.Red); foreach (IClientSession s in _sessionManager.Sessions) { session.SendChatMessage($"{s.PlayerEntity.Name} | HWID: {s.HardwareId}", ChatMessageColorType.LightPurple); } session.SendChatMessage($"Online: {_sessionManager.SessionsCount}", ChatMessageColorType.Red); return new SaltyCommandResult(true); } [Command("stat")] [Description("Current server configuration")] public async Task StatAsync() { IClientSession session = Context.Player; session.SendChatMessage("[Current server rates]", ChatMessageColorType.Red); session.SendChatMessage($"XP Rate: {_manager.MobXpRate}", ChatMessageColorType.LightPurple); session.SendChatMessage($"JobXP Rate: {_manager.JobXpRate}", ChatMessageColorType.LightPurple); session.SendChatMessage($"HeroXP Rate: {_manager.HeroXpRate}", ChatMessageColorType.LightPurple); session.SendChatMessage($"FairyXP Rate: {_manager.FairyXpRate}", ChatMessageColorType.LightPurple); session.SendChatMessage($"MateXP Rate: {_manager.MateXpRate}", ChatMessageColorType.LightPurple); session.SendChatMessage($"FamilyXP Rate: {_manager.FamilyExpRate}", ChatMessageColorType.LightPurple); session.SendChatMessage($"Reput Rate: {_manager.ReputRate}", ChatMessageColorType.LightPurple); session.SendChatMessage($"Drop Rate: {_manager.MobDropRate}", ChatMessageColorType.LightPurple); session.SendChatMessage($"GoldDrop Rate: {_manager.GoldDropRate}", ChatMessageColorType.LightPurple); session.SendChatMessage($"Gold Rate: {_manager.GoldRate}", ChatMessageColorType.LightPurple); session.SendChatMessage("[Current server configuration]", ChatMessageColorType.Red); session.SendChatMessage($"EXP Event: {_manager.ExpEvent}", ChatMessageColorType.LightPurple); return new SaltyCommandResult(true, ""); } [Command("mapinfo")] [Description("Current Map informations")] public async Task DumpMapInformations() { IClientSession session = Context.Player; IMapInstance mapInstance = Context.Player.CurrentMapInstance; session.SendChatMessage("[MAP_INFORMATIONS]", ChatMessageColorType.Red); foreach (PropertyInfo i in typeof(IMapInstance).GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(s => !s.PropertyType.IsClass && !s.PropertyType.IsAbstract && !s.PropertyType.IsInterface).OrderBy(s => s.Name)) { session.SendChatMessage($"{i.Name} : {i.GetValue(mapInstance)}", ChatMessageColorType.LightPurple); } return new SaltyCommandResult(true, ""); } [Command("targetInfo")] [Description("Current Map informations")] public async Task DumpTargetInformation() { IClientSession session = Context.Player; IBattleEntity entity = session.CurrentMapInstance.GetBattleEntity(session.PlayerEntity.LastEntity.Item1, session.PlayerEntity.LastEntity.Item2); if (entity == null) { return new SaltyCommandResult(false); } int? monsterVnum = entity switch { IMonsterEntity monsterEntity => monsterEntity.MonsterVNum, INpcEntity npcEntity => npcEntity.MonsterVNum, IMateEntity mateEntity => mateEntity.MonsterVNum, _ => null }; session.SendChatMessage("==========[ Target Info ]==========", ChatMessageColorType.Red); session.SendChatMessage($"Type: {entity.Type}", ChatMessageColorType.Yellow); session.SendChatMessage($"Id: {entity.Id}", ChatMessageColorType.Yellow); session.SendChatMessage($"Position: X - {entity.PositionX} Y - {entity.PositionY}", ChatMessageColorType.Yellow); session.SendChatMessage($"MonsterVnum: {monsterVnum?.ToString() ?? "None"}", ChatMessageColorType.Yellow); return new SaltyCommandResult(true, ""); } [Command("charInfo", "characterInformation")] [Description("Current Map informations")] public async Task DumpCharacterInformation() => await DumpCharacterInformation(Context.Player.PlayerEntity.Name); [Command("charInfo", "characterInformation")] [Description("Current Map informations")] public async Task DumpCharacterInformation(string name) { IClientSession session = _sessionManager.GetSessionByCharacterName(name); session.SendChatMessage("[CHARACTER_INFORMATION]", ChatMessageColorType.Red); foreach (PropertyInfo i in typeof(IPlayerEntity).GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(s => !s.PropertyType.IsClass && !s.PropertyType.IsAbstract && !s.PropertyType.IsInterface).OrderBy(s => s.Name)) { session.SendChatMessage($"{i.Name} : {i.GetValue(session.PlayerEntity)}", ChatMessageColorType.LightPurple); } return new SaltyCommandResult(true, ""); } [Command("buff")] [Description("Add given buff")] public async Task BuffAsync( [Description("Buff vnum")] short vnum) { IClientSession session = Context.Player; Buff buff = _buffFactory.CreateBuff(vnum, session.PlayerEntity); if (buff == null) { return new SaltyCommandResult(false); } await session.PlayerEntity.AddBuffAsync(buff); return new SaltyCommandResult(true, $"Created buff: {_language.GetLanguage(GameDataType.Card, buff.Name, Context.Player.UserLanguage)}"); } [Command("buff")] [Description("Add given buff")] public async Task BuffAsync( [Description("Buff vnum")] short vnum, [Description("Buff duration in seconds")] int duration) { IClientSession session = Context.Player; Buff buff = _buffFactory.CreateBuff(vnum, session.PlayerEntity, TimeSpan.FromSeconds(duration)); await session.PlayerEntity.AddBuffAsync(buff); return new SaltyCommandResult(true, $"Created buff: {_language.GetLanguage(GameDataType.Card, buff.Name, Context.Player.UserLanguage)}"); } [Command("removebuff")] [Description("Removes given buff")] public async Task RemoveBuffAsync( [Description("Buff vnum")] short vnum) { IClientSession session = Context.Player; if (!session.PlayerEntity.BuffComponent.HasBuff(vnum)) { return new SaltyCommandResult(false, "You don't have this buff!"); } Buff buff = session.PlayerEntity.BuffComponent.GetBuff(vnum); await session.PlayerEntity.RemoveBuffAsync(true, buff); return new SaltyCommandResult(true, $"Removed buff {_language.GetLanguage(GameDataType.Card, buff.Name, session.UserLanguage)}"); } [Command("eff")] [Description("Show effect")] public async Task EffAsync( [Description("Effect ID")] short vnum) { IClientSession session = Context.Player; session.BroadcastEffect(vnum, new RangeBroadcast(session.PlayerEntity.PositionX, session.PlayerEntity.PositionY)); return new SaltyCommandResult(true); } [Command("refresh-c", "refresh-compliments", "rc")] [Description("Refresh your monthly compliments.")] public async Task RefreshMonthlyCompliments([Description("Force refresh")] bool force = false) { Context.Player.EmitEvent(new ComplimentsMonthlyRefreshEvent { Force = force }); return new SaltyCommandResult(true); } [Command("changechannel")] public async Task ChangeChannel(int channelId) { if (channelId == _gameServer.ChannelId) { return new SaltyCommandResult(false, "It's the same channel"); } if (channelId == 51 || _gameServer.ChannelType == GameChannelType.ACT_4) { return new SaltyCommandResult(false, "Use $act4/$act4leave command instead"); } IClientSession session = Context.Player; GetChannelInfoResponse response = await _serverApiService.GetChannelInfo(new GetChannelInfoRequest { WorldGroup = _gameServer.WorldGroup, ChannelId = channelId }); if (response?.ResponseType != RpcResponseType.SUCCESS) { return new SaltyCommandResult(false, "Channel doesn't exist"); } IPlayerEntity player = session.PlayerEntity; await session.EmitEventAsync(new PlayerChangeChannelEvent(response.GameServer, ItModeType.ToPortAlveus, player.MapId, player.MapX, player.MapY)); return new SaltyCommandResult(true); } [Command("checkmate")] public async Task CheckMate() { IClientSession session = Context.Player; IMateEntity target = session.CurrentMapInstance.GetMateById(session.PlayerEntity.LastEntity.Item2); if (target == null) { return new SaltyCommandResult(false, "Mate doesn't exist."); } session.SendChatMessage($"Level: {target.Level}", ChatMessageColorType.Yellow); session.SendChatMessage($"MonsterRaceType: {target.MonsterRaceType}", ChatMessageColorType.Yellow); session.SendChatMessage($"AttackType: {target.AttackType}", ChatMessageColorType.Yellow); session.SendChatMessage($"WeaponLevel: {target.WeaponLevel}", ChatMessageColorType.Yellow); session.SendChatMessage($"WinfoValue: {target.WinfoValue}", ChatMessageColorType.Yellow); session.SendChatMessage($"BaseLevel: {target.BaseLevel}", ChatMessageColorType.Yellow); session.SendChatMessage($"GetModifier: {target.GetModifier()}", ChatMessageColorType.Yellow); session.SendChatMessage($"CleanDamageMin: {target.CleanDamageMin}", ChatMessageColorType.Yellow); session.SendChatMessage($"CleanDamageMax: {target.CleanDamageMax}", ChatMessageColorType.Yellow); session.SendChatMessage($"MateType: {target.MateType}", ChatMessageColorType.Yellow); return new SaltyCommandResult(true); } [Command("refreshmate")] public async Task RefreshMate() { IClientSession session = Context.Player; IMateEntity target = session.CurrentMapInstance.GetMateById(session.PlayerEntity.LastEntity.Item2); if (target == null) { return new SaltyCommandResult(false, "Mate doesn't exist."); } target.RefreshStatistics(); return new SaltyCommandResult(true, "Statistics refresh."); } }