server-master/srcs/_plugins/WingsEmu.Plugins.Essentials/Administrator/AdministratorModule.cs
2026-02-10 18:21:30 +01:00

1117 lines
No EOL
43 KiB
C#

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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> setsp(int amount)
{
Context.Player.PlayerEntity.SpPointsBasic = amount;
Context.Player.RefreshSpPoint();
return new SaltyCommandResult(true);
}
[Command("mapdance")]
[Description("Dance!")]
public async Task<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> MapMusic()
{
Context.Player.CurrentMapInstance.MapMusic = null;
return new SaltyCommandResult(true);
}
[Command("hlevel", "hlvl")]
[Description("Set player hero level")]
public async Task<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> AddAllTitles()
{
IClientSession session = Context.Player;
IEnumerable<IGameItem> 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<SaltyCommandResult> Entities(byte range)
{
IReadOnlyList<IBattleEntity> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> Recipe(
[Description("Item vnum to create")] short vNum)
{
IClientSession session = Context.Player;
IReadOnlyList<Recipe> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> Sound(int value)
{
Context.Player.SendGuriPacket(19, 0, value);
return new SaltyCommandResult(true);
}
[Command("sound")]
public async Task<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> Butcher(byte range = 0)
{
IClientSession session = Context.Player;
int count = 0;
IReadOnlyList<IMonsterEntity> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> MapRespawn(byte range = 0)
{
IClientSession session = Context.Player;
int count = 0;
IReadOnlyList<IMonsterEntity> 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<SaltyCommandResult> ClearDrops(byte range = 0)
{
IClientSession session = Context.Player;
int count = 0;
IReadOnlyList<MapItem> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> DumpCharacterInformation() => await DumpCharacterInformation(Context.Player.PlayerEntity.Name);
[Command("charInfo", "characterInformation")]
[Description("Current Map informations")]
public async Task<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> RefreshMonthlyCompliments([Description("Force refresh")] bool force = false)
{
Context.Player.EmitEvent(new ComplimentsMonthlyRefreshEvent { Force = force });
return new SaltyCommandResult(true);
}
[Command("changechannel")]
public async Task<SaltyCommandResult> 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<SaltyCommandResult> 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<SaltyCommandResult> 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.");
}
}