using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Runtime;
using System.Threading.Tasks;
using log4net;
using ACE.Common;
using ACE.Common.Extensions;
using ACE.Database;
using ACE.Database.Models.Shard;
using ACE.Database.Models.World;
using ACE.DatLoader;
using ACE.DatLoader.FileTypes;
using ACE.Entity;
using ACE.Entity.Enum;
using ACE.Entity.Enum.Properties;
using ACE.Entity.Models;
using ACE.Server.Entity;
using ACE.Server.Entity.Actions;
using ACE.Server.Factories;
using ACE.Server.Factories.Enum;
using ACE.Server.Managers;
using ACE.Server.Network;
using ACE.Server.Network.GameEvent.Events;
using ACE.Server.Network.GameMessages.Messages;
using ACE.Server.Physics.Entity;
using ACE.Server.Physics.Extensions;
using ACE.Server.Physics.Managers;
using ACE.Server.WorldObjects;
using ACE.Server.WorldObjects.Entity;
using Position = ACE.Entity.Position;
using Spell = ACE.Server.Entity.Spell;
namespace ACE.Server.Command.Handlers
{
public static class DeveloperCommands
{
private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// TODO: Replace later with a command to spawn a generator at the player's location
/*
///
/// Debug command to spawn a creature in front of the player and save it as a static spawn if the static option is specified.
///
[CommandHandler("createcreature", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld,
"Debug command to spawn a creature in front of the player and save it as a static spawn if the static option is specified.",
"weenieClassId")]
public static void CreateStaticCreature(Session session, params string[] parameters)
{
Creature newC = null;
if (!(parameters?.Length > 0))
{
ChatPacket.SendServerMessage(session, "Usage: @createcreature [static] weenieClassId",
ChatMessageType.Broadcast);
return;
}
if (parameters?[0] == "static")
{
if (parameters?.Length > 1)
{
uint weenie = Convert.ToUInt32(parameters[1]);
newC = MonsterFactory.SpawnCreature(weenie, true, session.Player.Location.InFrontOf(2.0f));
}
else
{
ChatPacket.SendServerMessage(session, "Specify a valid weenieClassId after the static option.",
ChatMessageType.Broadcast);
return;
}
}
else
{
uint weenie = Convert.ToUInt32(parameters[0]);
newC = MonsterFactory.SpawnCreature(weenie, false, session.Player.Location.InFrontOf(2.0f));
}
if (newC != null)
{
ChatPacket.SendServerMessage(session, $"Now spawning {newC.Name}.",
ChatMessageType.Broadcast);
LandblockManager.AddObject(newC);
}
else
{
ChatPacket.SendServerMessage(session, "Couldn't find that creature in the database or save it's location.",
ChatMessageType.Broadcast);
}
}
*/
[CommandHandler("nudge", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Correct player position cell ID after teleporting into black space.")]
public static void HandleNudge(Session session, params string[] parameters)
{
var pos = session.Player.GetPosition(PositionType.Location);
if (WorldObject.AdjustDungeonCells(pos))
{
pos.PositionZ += 0.005000f;
var posReadable = PostionAsLandblocksGoogleSpreadsheetFormat(pos);
AdminCommands.HandleTeleportLOC(session, posReadable.Split(' '));
var positionMessage = new GameMessageSystemChat($"Nudge player to {posReadable}", ChatMessageType.Broadcast);
session.Network.EnqueueSend(positionMessage);
}
}
///
/// Attempts to remove the hourglass / fix the busy state for the player
///
[CommandHandler("fixbusy", AccessLevel.Player, CommandHandlerFlag.RequiresWorld, 0, "Attempts to remove the hourglass / fix the busy state for the player")]
public static void HandleFixBusy(Session session, params string[] parameters)
{
session.Player.SendUseDoneEvent();
}
static string PostionAsLandblocksGoogleSpreadsheetFormat(Position pos)
{
return $"0x{pos.Cell.ToString("X")} {pos.Pos.X} {pos.Pos.Y} {pos.Pos.Z} {pos.Rotation.W} {pos.Rotation.X} {pos.Rotation.Y} {pos.Rotation.Z}";
}
///
/// Debug command to test the ObjDescEvent message.
///
[CommandHandler("equiptest", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Simulates equipping a new item to your character, replacing all other items.")]
public static void EquipTest(Session session, params string[] parameters)
{
if (!(parameters?.Length > 0))
{
ChatPacket.SendServerMessage(session, "Usage: @equiptest (hex)clothingTableId [palette_index] [shade].\neg '@equiptest 0x100005fd'",
ChatMessageType.Broadcast);
return;
}
try
{
uint modelId;
if (parameters[0].StartsWith("0x"))
{
string strippedmodelid = parameters[0].Substring(2);
modelId = UInt32.Parse(strippedmodelid, NumberStyles.HexNumber);
}
else
modelId = UInt32.Parse(parameters[0], NumberStyles.HexNumber);
int palOption = -1;
if (parameters.Length > 1)
palOption = Int32.Parse(parameters[1]);
float shade = 0;
if (parameters.Length > 2)
shade = Single.Parse(parameters[2]);
if (shade < 0)
shade = 0;
if (shade > 1)
shade = 1;
//if ((modelId >= 0x10000001) && (modelId <= 0x1000086B))
// session.Player.TestWieldItem(session, modelId, palOption, shade);
//else
// ChatPacket.SendServerMessage(session, "Please enter a value greater than 0x10000000 and less than 0x1000086C",
// ChatMessageType.Broadcast);
}
catch (Exception)
{
ChatPacket.SendServerMessage(session, "Please enter a value greater than 0x10000000 and less than 0x1000086C", ChatMessageType.Broadcast);
}
}
/////
///// Force PhysicsState change that occurs upon login complete.
/////
//[CommandHandler("fakelogin", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Fake Login Complete response")]
//public static void HandleFakeLogin(Session session, params string[] parameters)
//{
// session.Player.ReportCollisions = true;
// session.Player.IgnoreCollisions = false;
// session.Player.Hidden = false;
// session.Player.EnqueueBroadcastPhysicsState();
//}
[CommandHandler("netstats", AccessLevel.Developer, CommandHandlerFlag.None, "View network statistics")]
public static void HandleNetStats(Session session, params string[] parameters)
{
CommandHandlerHelper.WriteOutputInfo(session, NetworkStatistics.Summary(), ChatMessageType.Broadcast);
}
///
/// List all clothing bases which are compatible with setup
///
[CommandHandler("listcb", AccessLevel.Developer, CommandHandlerFlag.ConsoleInvoke, "List Clothing Tables available")]
public static void HandleShowCompatibleClothingBases(Session session, params string[] parameters)
{
uint.TryParse(parameters[0], out var setupId);
uint cbStart = 0x10000001;
uint cbEnd = 0x1000086c;
List compatibleCBs = new List();
for (uint i = cbStart; i < cbEnd; i++)
{
var cbToTest = DatManager.PortalDat.ReadFromDat(i);
if (cbToTest.ClothingBaseEffects.ContainsKey(setupId))
compatibleCBs.Add(i);
}
Console.WriteLine($"There are {compatibleCBs.Count} compatible clothingbase tables for setup {setupId}");
Console.WriteLine("");
Console.WriteLine($"{string.Join("\n", compatibleCBs.ToArray())}");
}
// ==================================
// Client Testing
// ==================================
///
/// echo "text to send back to yourself" [ChatMessageType]
///
[CommandHandler("echo", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Send text back to yourself.", "\"text to send back to yourself\" [ChatMessageType]\n" + "ChatMessageType can be a uint or enum name")]
public static void HandleDebugEcho(Session session, params string[] parameters)
{
try
{
if (Enum.TryParse(parameters[1], true, out ChatMessageType cmt))
{
if (Enum.IsDefined(typeof(ChatMessageType), cmt))
ChatPacket.SendServerMessage(session, parameters[0], cmt);
else
ChatPacket.SendServerMessage(session, parameters[0], ChatMessageType.Broadcast);
}
}
catch (Exception)
{
ChatPacket.SendServerMessage(session, parameters[0], ChatMessageType.Broadcast);
}
}
///
/// playsound [Sound] (volumelevel)
///
[CommandHandler("playsound", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Plays a sound.", "sound (volume) (guid)\n" + "Sound can be uint or enum name\n" + "Volume and source guid are optional")]
public static void HandlePlaySound(Session session, params string[] parameters)
{
try
{
float volume = 1f;
if (parameters.Length > 1)
float.TryParse(parameters[1], out volume);
uint guid = session.Player.Guid.Full;
if (parameters.Length > 2)
uint.TryParse(parameters[2].TrimStart("0x"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out guid);
var message = $"Unable to find a sound called {parameters[0]} to play.";
if (Enum.TryParse(parameters[0], true, out Sound sound))
{
if (Enum.IsDefined(typeof(Sound), sound))
{
message = $"Playing sound {Enum.GetName(typeof(Sound), sound)}";
// add the sound to the player queue for everyone to hear
// player action queue items will execute on the landblock
// player.playsound will play a sound on only the client session that called the function
session.Player.PlaySoundEffect(sound, new ObjectGuid(guid), volume);
}
}
var sysChatMessage = new GameMessageSystemChat(message, ChatMessageType.Broadcast);
session.Network.EnqueueSend(sysChatMessage);
}
catch (Exception)
{
// Do Nothing
}
}
///
/// effect [Effect] (scale)
///
[CommandHandler("effect", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Plays an effect.", "effect (float)\n" + "Effect can be uint or enum name" + "float is scale level")]
public static void HandlePlayEffect(Session session, params string[] parameters)
{
try
{
float scale = 1f;
var effectEvent = new GameMessageScript(session.Player.Guid, PlayScript.Invalid);
if (parameters.Length > 1)
{
if (parameters[1] != "")
scale = float.Parse(parameters[1]);
}
var message = $"Unable to find a effect called {parameters[0]} to play.";
if (Enum.TryParse(parameters[0], true, out PlayScript effect))
{
if (Enum.IsDefined(typeof(PlayScript), effect))
{
message = $"Playing effect {Enum.GetName(typeof(PlayScript), effect)}";
session.Player.ApplyVisualEffects(effect);
}
}
var sysChatMessage = new GameMessageSystemChat(message, ChatMessageType.Broadcast);
session.Network.EnqueueSend(sysChatMessage);
}
catch (Exception)
{
// Do Nothing
}
}
[CommandHandler("chatdump", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Spews 1000 lines of text to you.")]
public static void ChatDump(Session session, params string[] parameters)
{
for (int i = 0; i < 1000; i++)
ChatPacket.SendServerMessage(session, "Test Message " + i, ChatMessageType.Broadcast);
}
[CommandHandler("animation", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Plays an animation on the current player, or optionally another object", "MotionCommand (optional target guid)\n")]
public static void Animation(Session session, params string[] parameters)
{
if (!Enum.TryParse(parameters[0], out MotionCommand motionCommand))
{
ChatPacket.SendServerMessage(session, $"MotionCommand: {parameters[0]} not found", ChatMessageType.Broadcast);
return;
}
WorldObject obj = session.Player;
if (parameters.Length > 1)
{
if (!uint.TryParse(parameters[1].TrimStart("0x"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var guid))
{
ChatPacket.SendServerMessage(session, $"Invalid guid: {parameters[1]}", ChatMessageType.Broadcast);
return;
}
obj = session.Player.FindObject(guid, Player.SearchLocations.Everywhere);
if (obj == null)
{
ChatPacket.SendServerMessage(session, $"Couldn't find guid: {parameters[1]}", ChatMessageType.Broadcast);
return;
}
if (obj.CurrentMotionState == null)
{
ChatPacket.SendServerMessage(session, $"{obj.Name} ({obj.Guid}) has no CurrentMotionState", ChatMessageType.Broadcast);
return;
}
}
var stance = obj.CurrentMotionState.Stance;
var suffix = "";
if (obj != session.Player)
suffix = $" on {obj.Name} ({obj.Guid})";
ChatPacket.SendServerMessage(session, $"Playing animation {stance}.{motionCommand}{suffix}", ChatMessageType.Broadcast);
obj.EnqueueBroadcastMotion(new Motion(stance, motionCommand));
}
///
/// This function is just used to exercise the ability to have player movement without animation. Once we are solid on this it can be removed. Og II
///
[CommandHandler("movement", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Movement testing command, to be removed soon")]
public static void Movement(Session session, params string[] parameters)
{
var forwardCommand = (MotionCommand)Convert.ToInt16(parameters[0]);
var movement = new Motion(session.Player, forwardCommand);
session.Network.EnqueueSend(new GameMessageUpdateMotion(session.Player, movement));
movement = new Motion(session.Player, MotionCommand.Ready);
session.Network.EnqueueSend(new GameMessageUpdateMotion(session.Player, movement));
}
///
/// This function is just used to exercise the ability to have player movement without animation. Once we are solid on this it can be removed. Og II
///
[CommandHandler("MoveTo", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Used to test the MoveToObject message. It will spawn a training wand in front of you and then move to that object.", "moveto\n" + "optional parameter distance if omitted 10f")]
public static void MoveTo(Session session, params string[] parameters)
{
var distance = 10.0f;
ushort trainingWandTarget = 12748;
if ((parameters?.Length > 0))
distance = Convert.ToInt16(parameters[0]);
WorldObject loot = WorldObjectFactory.CreateNewWorldObject(trainingWandTarget);
loot.Location = session.Player.Location.InFrontOf((loot.UseRadius ?? 2) > 2 ? loot.UseRadius.Value : 2);
loot.Location.LandblockId = new LandblockId(loot.Location.GetCell());
loot.EnterWorld();
session.Player.HandleActionPutItemInContainer(loot.Guid.Full, session.Player.Guid.Full);
}
///
/// Debug command to spawn the Barber UI
///
[CommandHandler("barbershop", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Displays the barber ui")]
public static void BarberShop(Session session, params string[] parameters)
{
session.Player.BarberActive = true;
session.Network.EnqueueSend(new GameEventStartBarber(session));
}
// ==================================
// Server
// ==================================
///
/// Debug command to print out all of the active players connected too the server.
///
[CommandHandler("listplayers", AccessLevel.Developer, CommandHandlerFlag.None, 0, "Displays all of the active players connected too the server.")]
public static void HandleListPlayers(Session session, params string[] parameters)
{
string message = "";
uint playerCounter = 0;
AccessLevel? targetAccessLevel = null;
if (parameters?.Length > 0)
{
if (Enum.TryParse(parameters[0], true, out AccessLevel parsedAccessLevel))
{
targetAccessLevel = parsedAccessLevel;
}
else
{
try
{
uint accessLevel = Convert.ToUInt16(parameters[0]);
targetAccessLevel = (AccessLevel)accessLevel;
}
catch (Exception)
{
CommandHandlerHelper.WriteOutputInfo(session, "Invalid AccessLevel value", ChatMessageType.Broadcast);
return;
}
}
}
if (targetAccessLevel.HasValue)
message += $"Listing only {targetAccessLevel.Value.ToString()}s:\n";
foreach (var player in PlayerManager.GetAllOnline())
{
if (targetAccessLevel.HasValue && player.Account.AccessLevel != ((uint)targetAccessLevel.Value))
continue;
message += $"{player.Name} : {player.Session.AccountId}\n";
playerCounter++;
}
message += $"Total connected Players: {playerCounter}\n";
CommandHandlerHelper.WriteOutputInfo(session, message, ChatMessageType.Broadcast);
}
///
/// Debug command to saves the character from in-game.
///
/// Added a quick way to invoke the character save routine.
[CommandHandler("save-now", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Saves your session.")]
public static void HandleSaveNow(Session session, params string[] parameters)
{
session.Player.SavePlayerToDatabase();
}
///
/// This is a VERY crude test. It should never be used on a live server.
/// There isn't really much point to this command other than making sure landblocks can load and are semi-efficient.
///
[CommandHandler("loadalllandblocks", AccessLevel.Developer, CommandHandlerFlag.None, "Loads all Landblocks. This is VERY crude. Do NOT use it on a live server!!! It will likely crash the server. Landblock resources will be loaded async and will continue to do work even after all landblocks have been loaded.")]
public static void HandleLoadAllLandblocks(Session session, params string[] parameters)
{
CommandHandlerHelper.WriteOutputInfo(session, "Loading landblocks. This will likely crash the server. Landblock resources will be loaded async and will continue to do work even after all landblocks have been loaded.");
Task.Run(() =>
{
for (int x = 0; x <= 0xFE; x++)
{
CommandHandlerHelper.WriteOutputInfo(session, $"Loading landblocks, x = 0x{x:X2} of 0xFE....");
for (int y = 0; y <= 0xFE; y++)
{
var blockid = new LandblockId((byte)x, (byte)y);
LandblockManager.GetLandblock(blockid, false, false);
}
}
CommandHandlerHelper.WriteOutputInfo(session, "Loading landblocks completed. Async landblock resources are likely still loading...");
});
}
// ==================================
// World Object Properties
// ==================================
[CommandHandler("propertydump", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Lists all properties for the last world object you examined.")]
public static void HandlePropertyDump(Session session, params string[] parameters)
{
var target = CommandHandlerHelper.GetLastAppraisedObject(session);
if (target != null)
session.Network.EnqueueSend(new GameMessageSystemChat($"\n{target.DebugOutputString(target)}", ChatMessageType.System));
}
// ==================================
// Player Properties
// ==================================
///
/// Returns the Player's GUID
///
/// Added a quick way to access the player GUID.
[CommandHandler("whoami", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows you your GUIDs.")]
public static void HandleWhoAmI(Session session, params string[] parameters)
{
ChatPacket.SendServerMessage(session, $"GUID: {session.Player.Guid.Full} (0x{session.Player.Guid}) | ID(low): {session.Player.Guid.Low} High:{session.Player.Guid.High}", ChatMessageType.Broadcast);
}
///
/// echoflags [flagtype] [int]
///
[CommandHandler("echoflags", AccessLevel.Developer, CommandHandlerFlag.None, 2, "Echo flags back to you", "[type to test] [int]\n")]
public static void HandleDebugEchoFlags(Session session, params string[] parameters)
{
try
{
if (parameters?.Length == 2)
{
string debugOutput;
switch (parameters[0].ToLower())
{
case "descriptionflags":
var objectDescFlag = (ObjectDescriptionFlag)Convert.ToUInt32(parameters[1]);
debugOutput = $"{objectDescFlag.GetType().Name} = {objectDescFlag.ToString()}" + " (" + (uint)objectDescFlag + ")";
break;
case "weenieflags":
var weenieHdr = (WeenieHeaderFlag)Convert.ToUInt32(parameters[1]);
debugOutput = $"{weenieHdr.GetType().Name} = {weenieHdr.ToString()}" + " (" + (uint)weenieHdr + ")";
break;
case "weenieflags2":
var weenieHdr2 = (WeenieHeaderFlag2)Convert.ToUInt32(parameters[1]);
debugOutput = $"{weenieHdr2.GetType().Name} = {weenieHdr2.ToString()}" + " (" + (uint)weenieHdr2 + ")";
break;
case "positionflag":
var posFlag = (PositionFlags)Convert.ToUInt32(parameters[1]);
debugOutput = $"{posFlag.GetType().Name} = {posFlag.ToString()}" + " (" + (uint)posFlag + ")";
break;
case "type":
var objectType = (ItemType)Convert.ToUInt32(parameters[1]);
debugOutput = $"{objectType.GetType().Name} = {objectType.ToString()}" + " (" + (uint)objectType + ")";
break;
case "containertype":
var contType = (ContainerType)Convert.ToUInt32(parameters[1]);
debugOutput = $"{contType.GetType().Name} = {contType.ToString()}" + " (" + (uint)contType + ")";
break;
case "usable":
var usableType = (Usable)Convert.ToInt64(parameters[1]);
debugOutput = $"{usableType.GetType().Name} = {usableType.ToString()}" + " (" + (Int64)usableType + ")";
break;
case "radarbehavior":
var radarBeh = (RadarBehavior)Convert.ToUInt32(parameters[1]);
debugOutput = $"{radarBeh.GetType().Name} = {radarBeh.ToString()}" + " (" + (uint)radarBeh + ")";
break;
case "physicsdescriptionflags":
var physicsDescFlag = (PhysicsDescriptionFlag)Convert.ToUInt32(parameters[1]);
debugOutput = $"{physicsDescFlag.GetType().Name} = {physicsDescFlag.ToString()}" + " (" + (uint)physicsDescFlag + ")";
break;
case "physicsstate":
var physState = (PhysicsState)Convert.ToUInt32(parameters[1]);
debugOutput = $"{physState.GetType().Name} = {physState.ToString()}" + " (" + (uint)physState + ")";
break;
case "validlocations":
var locFlags = (EquipMask)Convert.ToUInt32(parameters[1]);
debugOutput = $"{locFlags.GetType().Name} = {locFlags.ToString()}" + " (" + (uint)locFlags + ")";
break;
case "currentwieldedlocation":
var locFlags2 = (EquipMask)Convert.ToUInt32(parameters[1]);
debugOutput = $"{locFlags2.GetType().Name} = {locFlags2.ToString()}" + " (" + (uint)locFlags2 + ")";
break;
case "priority":
var covMask = (CoverageMask)Convert.ToUInt32(parameters[1]);
debugOutput = $"{covMask.GetType().Name} = {covMask.ToString()}" + " (" + (uint)covMask + ")";
break;
case "radarcolor":
var radarBlipColor = (RadarColor)Convert.ToUInt32(parameters[1]);
debugOutput = $"{radarBlipColor.GetType().Name} = {radarBlipColor.ToString()}" + " (" + (uint)radarBlipColor + ")";
break;
default:
debugOutput = "No valid type to test";
break;
}
CommandHandlerHelper.WriteOutputInfo(session, debugOutput);
}
}
catch (Exception)
{
string debugOutput = "Exception Error, check input and try again";
CommandHandlerHelper.WriteOutputInfo(session, debugOutput);
}
}
[CommandHandler("setcoin", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Set Coin display debug only usage")]
public static void HandleSetCoin(Session session, params string[] parameters)
{
int coins;
try
{
coins = Convert.ToInt32(parameters[0]);
}
catch (Exception)
{
ChatPacket.SendServerMessage(session, "Not a valid number - must be a number between 0 - 2,147,483,647", ChatMessageType.Broadcast);
return;
}
session.Player.CoinValue = coins;
session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(session.Player, PropertyInt.CoinValue, coins));
}
// ==================================
// Teleport + Positions/Locations
// ==================================
///
/// telexyz cell x y z qx qy qz qw
///
[CommandHandler("telexyz", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 8, "Teleport to a location.", "cell x y z qx qy qz qw\n" + "all parameters must be specified and cell must be in decimal form")]
public static void HandleDebugTeleportXYZ(Session session, params string[] parameters)
{
if (!uint.TryParse(parameters[0], out var cell))
return;
var positionData = new float[7];
for (uint i = 0u; i < 7u; i++)
{
if (!float.TryParse(parameters[i + 1], out var position))
return;
positionData[i] = position;
}
session.Player.Teleport(new Position(cell, positionData[0], positionData[1], positionData[2], positionData[3], positionData[4], positionData[5], positionData[6]));
}
///
/// Debug command to teleport a player to a saved position, if the position type exists within the database.
///
[CommandHandler("teletype", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Teleport to a saved character position.", "uint 0-22\n" + "@teletype 1")]
public static void HandleTeleType(Session session, params string[] parameters)
{
if (parameters?.Length > 0)
{
string parsePositionString = parameters[0].Length > 3 ? parameters[0].Substring(0, 3) : parameters[0];
if (Enum.TryParse(parsePositionString, true, out PositionType positionType))
{
if (session.Player.TeleToPosition(positionType))
session.Network.EnqueueSend(new GameMessageSystemChat($"{PositionType.Location} {session.Player.Location}", ChatMessageType.Broadcast));
else
session.Network.EnqueueSend(new GameMessageSystemChat($"Error finding saved character position: {positionType}", ChatMessageType.Broadcast));
}
}
}
///
/// Debug command to print out all of the saved character positions.
///
[CommandHandler("listpositions", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Displays all available saved character positions from the database.")]
public static void HandleListPositions(Session session, params string[] parameters)
{
var posDict = session.Player.GetAllPositions();
string message = "Saved character positions:\n";
foreach (var posPair in posDict)
message += "ID: " + (uint)posPair.Key + " Loc: " + posPair.Value + "\n";
message += "Total positions: " + posDict.Count + "\n";
var positionMessage = new GameMessageSystemChat(message, ChatMessageType.Broadcast);
session.Network.EnqueueSend(positionMessage);
}
///
/// Debug command to save the player's current location as specific position type.
///
[CommandHandler("setposition", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Saves the supplied character position type to the database.", "uint 1-27\n" + "@setposition 1")]
public static void HandleSetPosition(Session session, params string[] parameters)
{
if (parameters?.Length == 1)
{
string parsePositionString = parameters[0].Length > 19 ? parameters[0].Substring(0, 19) : parameters[0];
// The enum labels max character length has been observered as length 19
// int value can be: 0-27
if (Enum.TryParse(parsePositionString, true, out PositionType positionType))
{
if (positionType != PositionType.Undef)
{
// Create a new position from the current player location
var playerPosition = new Position(session.Player.Location);
// Save the position
session.Player.SetPosition(positionType, playerPosition);
// Report changes to client
var positionMessage = new GameMessageSystemChat($"Set: {positionType} to Loc: {playerPosition}", ChatMessageType.Broadcast);
session.Network.EnqueueSend(positionMessage);
return;
}
}
}
session.Network.EnqueueSend(new GameMessageSystemChat("Could not determine the correct position type.\nPlease supply a single integer value from within the range of 1 through 27.", ChatMessageType.Broadcast));
}
[CommandHandler("gps", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Display location.")]
public static void HandleDebugGPS(Session session, params string[] parameters)
{
var position = session.Player.Location;
ChatPacket.SendServerMessage(session, $"Position: [Cell: 0x{position.LandblockId.Landblock:X4} | Offset: {position.PositionX}, {position.PositionY}, {position.PositionZ} | Facing: {position.RotationX}, {position.RotationY}, {position.RotationZ}, {position.RotationW}]", ChatMessageType.Broadcast);
}
// ==================================
// Titles
// ==================================
///
/// Add a specific title to yourself
///
[CommandHandler("addtitle", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Add title to yourself", "[titleid]")]
public static void HandleAddTitle(Session session, params string[] parameters)
{
if (uint.TryParse(parameters[0], out var titleId))
session.Player.AddTitle(titleId);
}
///
/// Add all titles to yourself
///
[CommandHandler("addalltitles", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Add all titles to yourself")]
public static void HandleAddAllTitles(Session session, params string[] parameters)
{
foreach (CharacterTitle title in Enum.GetValues(typeof(CharacterTitle)))
session.Player.AddTitle((uint)title);
}
// ==================================
// Experience
// ==================================
[CommandHandler("grantxp", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Give XP to yourself (or the specified character).", "ulong\n" + "@grantxp [name] 191226310247 is max level 275")]
public static void HandleGrantXp(Session session, params string[] parameters)
{
if (parameters?.Length > 0)
{
List aceParams = new List()
{
new CommandParameterHelpers.ACECommandParameter() {
Type = CommandParameterHelpers.ACECommandParameterType.OnlinePlayerNameOrIid,
Required = false,
DefaultValue = session.Player
},
new CommandParameterHelpers.ACECommandParameter()
{
Type = CommandParameterHelpers.ACECommandParameterType.PositiveLong,
Required = true,
ErrorMessage = "You must specify the amount of xp."
}
};
if (CommandParameterHelpers.ResolveACEParameters(session, parameters, aceParams))
{
try
{
var amount = aceParams[1].AsLong;
aceParams[0].AsPlayer.GrantXP(amount, XpType.Admin, ShareType.None);
session.Network.EnqueueSend(new GameMessageSystemChat($"{amount:N0} experience granted.", ChatMessageType.Advancement));
PlayerManager.BroadcastToAuditChannel(session.Player, $"{session.Player.Name} granted {amount:N0} experience to {aceParams[0].AsPlayer.Name}.");
return;
}
catch
{
//overflow
}
}
}
ChatPacket.SendServerMessage(session, "Usage: /grantxp [name] 1234 (max 999999999999)", ChatMessageType.Broadcast);
}
[CommandHandler("grantluminance", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Give luminance to yourself (or the specified character).", "ulong\n" + "@grantluminance [name] 1500000 is max luminance")]
public static void HandleGrantLuminance(Session session, params string[] parameters)
{
if (parameters?.Length > 0)
{
List aceParams = new List()
{
new CommandParameterHelpers.ACECommandParameter() {
Type = CommandParameterHelpers.ACECommandParameterType.OnlinePlayerNameOrIid,
Required = false,
DefaultValue = session.Player
},
new CommandParameterHelpers.ACECommandParameter()
{
Type = CommandParameterHelpers.ACECommandParameterType.PositiveLong,
Required = true,
ErrorMessage = "You must specify the amount of luminance."
}
};
if (CommandParameterHelpers.ResolveACEParameters(session, parameters, aceParams))
{
try
{
var amount = aceParams[1].AsLong;
aceParams[0].AsPlayer.GrantLuminance(amount, XpType.Admin, ShareType.None);
session.Network.EnqueueSend(new GameMessageSystemChat($"{amount:N0} luminance granted.", ChatMessageType.Advancement));
PlayerManager.BroadcastToAuditChannel(session.Player, $"{session.Player.Name} granted {amount:N0} luminance to {aceParams[0].AsPlayer.Name}.");
return;
}
catch
{
//overflow
}
}
}
ChatPacket.SendServerMessage(session, "Usage: /grantluminance [name] 1234 (max 999999999999)", ChatMessageType.Broadcast);
}
[CommandHandler("grantitemxp", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Give item XP to the last appraised item.")]
public static void HandleGrantItemXp(Session session, params string[] parameters)
{
if (!long.TryParse(parameters[0], out var amount))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Invalid amount {parameters[0]}", ChatMessageType.Broadcast));
return;
}
var item = CommandHandlerHelper.GetLastAppraisedObject(session);
if (item == null) return;
if (item is Player player)
{
player.GrantItemXP(amount);
foreach (var i in player.EquippedObjects.Values.Where(i => i.HasItemLevel))
session.Network.EnqueueSend(new GameMessageSystemChat($"{amount:N0} experience granted to {i.Name}.", ChatMessageType.Broadcast));
}
else
{
if (item.HasItemLevel)
{
session.Player.GrantItemXP(item, amount);
session.Network.EnqueueSend(new GameMessageSystemChat($"{amount:N0} experience granted to {item.Name}.", ChatMessageType.Broadcast));
}
else
session.Network.EnqueueSend(new GameMessageSystemChat($"{item.Name} is not a levelable item.", ChatMessageType.Broadcast));
}
}
[CommandHandler("spendallxp", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Spend all available XP on Attributes, Vitals and Skills.")]
public static void HandleSpendAllXp(Session session, params string[] parameters)
{
session.Player.SpendAllXp();
ChatPacket.SendServerMessage(session, "All available xp has been spent. You must now log out for the updated values to take effect.", ChatMessageType.Broadcast);
}
// ==================================
// Vitals
// ==================================
[CommandHandler("setvital", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 2,
"Sets the specified vital to a specified value",
"Usage: @setvital \n" +
" is one of the following strings:\n" +
" health, hp\n" +
" stamina, stam, sp\n" +
" mana, mp\n" +
" is an integral value [0-9]+, or a relative value [-+][0-9]+")]
public static void SetVital(Session session, params string[] parameters)
{
string paramVital = parameters[0].ToLower();
string paramValue = parameters[1];
bool relValue = paramValue[0] == '+' || paramValue[0] == '-';
if (!int.TryParse(paramValue, out var value))
{
ChatPacket.SendServerMessage(session, "setvital Error: Invalid set value", ChatMessageType.Broadcast);
return;
}
// Parse args...
CreatureVital vital;
if (paramVital == "health" || paramVital == "hp")
vital = session.Player.Health;
else if (paramVital == "stamina" || paramVital == "stam" || paramVital == "sp")
vital = session.Player.Stamina;
else if (paramVital == "mana" || paramVital == "mp")
vital = session.Player.Mana;
else
{
ChatPacket.SendServerMessage(session, "setvital Error: Invalid vital", ChatMessageType.Broadcast);
return;
}
int delta = 0;
if (!relValue)
delta = session.Player.UpdateVital(vital, (uint)value);
else
delta = session.Player.UpdateVitalDelta(vital, value);
if (vital == session.Player.Health)
{
if (delta > 0)
session.Player.DamageHistory.OnHeal((uint)delta);
else
session.Player.DamageHistory.Add(session.Player, DamageType.Health, (uint)-delta);
}
}
[CommandHandler("sethealth", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "sets your current health to a specific value.", "ushort")]
public static void HandleSetHealth(Session session, params string[] parameters)
{
if (parameters?.Length > 0)
{
if (ushort.TryParse(parameters[0], out var health))
{
session.Player.Health.Current = health;
var updatePlayersHealth = new GameMessagePrivateUpdateAttribute2ndLevel(session.Player, Vital.Health, session.Player.Health.Current);
var message = new GameMessageSystemChat($"Attempting to set health to {health}...", ChatMessageType.Broadcast);
session.Network.EnqueueSend(updatePlayersHealth, message);
return;
}
}
ChatPacket.SendServerMessage(session, "Usage: /sethealth 200 (max Max Health)", ChatMessageType.Broadcast);
}
///
/// Debug command to set player vitals to 1
///
[CommandHandler("harmself", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Sets all player vitals to 1")]
public static void HarmSelf(Session session, params string[] parameters)
{
session.Player.UpdateVital(session.Player.Health, 1);
session.Player.UpdateVital(session.Player.Stamina, 1);
session.Player.UpdateVital(session.Player.Mana, 1);
}
// ==================================
// Create Objects in Player Inventory
// ==================================
private static void AddWeeniesToInventory(Session session, HashSet weenieIds, ushort? stackSize = null)
{
foreach (uint weenieId in weenieIds)
{
var loot = WorldObjectFactory.CreateNewWorldObject(weenieId);
if (loot == null) // weenie doesn't exist
continue;
var stackSizeForThisWeenieId = stackSize ?? loot.MaxStackSize;
if (stackSizeForThisWeenieId > 1)
loot.SetStackSize(stackSizeForThisWeenieId);
session.Player.TryCreateInInventoryWithNetworking(loot);
}
}
[CommandHandler("weapons", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Creates testing items in your inventory.")]
public static void HandleWeapons(Session session, params string[] parameters)
{
HashSet weenieIds = new HashSet { 93, 148, 300, 307, 311, 326, 338, 348, 350, 7765, 12748, 12463, 31812 };
AddWeeniesToInventory(session, weenieIds);
}
[CommandHandler("inv", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Creates sample items, foci and containers in your inventory.")]
public static void HandleInv(Session session, params string[] parameters)
{
HashSet weenieIds = new HashSet { 44, 45, 46, 136, 5893, 15268, 15269, 15270, 15271, 12748 };
AddWeeniesToInventory(session, weenieIds);
}
[CommandHandler("splits", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Creates some stackable items in your inventory for testing.")]
public static void HandleSplits(Session session, params string[] parameters)
{
HashSet weenieIds = new HashSet { 300, 690, 20630, 20631, 31198, 37155 };
AddWeeniesToInventory(session, weenieIds);
}
[CommandHandler("comps", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Creates spell component items in your inventory for testing.")]
public static void HandleComps(Session session, params string[] parameters)
{
HashSet weenieIds = new HashSet { 686, 687, 688, 689, 690, 691, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 1643, 1644, 1645, 1646, 1647, 1648, 1649, 1650, 1651, 1652, 1653, 1654, 7299, 7581, 8897, 20631 };
AddWeeniesToInventory(session, weenieIds, 1);
}
[CommandHandler("food", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Creates some food items in your inventory for testing.")]
public static void HandleFood(Session session, params string[] parameters)
{
HashSet weenieIds = new HashSet { 259, 259, 260, 377, 378, 379 };
AddWeeniesToInventory(session, weenieIds);
}
[CommandHandler("currency", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Creates some currency items in your inventory for testing.")]
public static void HandleCurrency(Session session, params string[] parameters)
{
HashSet weenieIds = new HashSet { 273, 20630 };
AddWeeniesToInventory(session, weenieIds);
}
[CommandHandler("cirand", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Creates random objects in your inventory.", "type (string or number) defaults to 10 if omitted, max 50")]
public static void HandleCIRandom(Session session, params string[] parameters)
{
if (!Enum.TryParse(parameters[0], true, out WeenieType weenieType) || !Enum.IsDefined(typeof(WeenieType), weenieType))
{
ChatPacket.SendServerMessage(session, $"{parameters[0]} is not a valid WeenieType", ChatMessageType.Broadcast);
return;
}
if (!AdminCommands.VerifyCreateWeenieType(weenieType))
{
ChatPacket.SendServerMessage(session, $"{weenieType} is not a valid WeenieType for create commands", ChatMessageType.Broadcast);
return;
}
var numItems = 10;
if (parameters.Length > 1)
{
if (!int.TryParse(parameters[1], out numItems) || numItems < 1 || numItems > 50)
{
ChatPacket.SendServerMessage(session, $" must be a number between 1 - 50", ChatMessageType.Broadcast);
return;
}
}
var items = LootGenerationFactory.CreateRandomObjectsOfType(weenieType, numItems);
var stuck = new List();
foreach (var item in items)
{
if (!item.Stuck)
session.Player.TryCreateInInventoryWithNetworking(item);
else
stuck.Add(item);
}
if (stuck.Count != 0)
session.Network.EnqueueSend(new GameMessageSystemChat($"You cannot spawn {string.Join(", ", stuck.Select(i => i.WeenieClassName))} in your inventory because it cannot be picked up", ChatMessageType.Broadcast));
}
// ==================================
// Spells
// ==================================
// addallspells
[CommandHandler("addallspells", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Adds all known spells to your own spellbook.")]
public static void HandleAddAllSpells(Session session, params string[] parameters)
{
for (uint spellLevel = 1; spellLevel <= 8; spellLevel++)
{
session.Player.LearnSpellsInBulk(MagicSchool.CreatureEnchantment, spellLevel);
session.Player.LearnSpellsInBulk(MagicSchool.ItemEnchantment, spellLevel);
session.Player.LearnSpellsInBulk(MagicSchool.LifeMagic, spellLevel);
session.Player.LearnSpellsInBulk(MagicSchool.VoidMagic, spellLevel);
session.Player.LearnSpellsInBulk(MagicSchool.WarMagic, spellLevel);
}
}
///
/// Debug console command to test the GetSpellFormula function.
///
[CommandHandler("getspellformula", AccessLevel.Developer, CommandHandlerFlag.ConsoleInvoke, 0, "Tests spell formula calculation")]
public static void GetSpellFormula(Session session, params string[] parameters)
{
if (parameters?.Length != 2)
{
Console.WriteLine("getspellformula ");
return;
}
if (!uint.TryParse(parameters[1], out var spellid))
{
Console.WriteLine("getspellformula ");
return;
}
SpellTable spellTable = DatManager.PortalDat.SpellTable;
SpellComponentsTable comps = DatManager.PortalDat.SpellComponentsTable;
Console.WriteLine("Formula for " + spellTable.Spells[spellid].Name);
Console.WriteLine("Spell Words: " + spellTable.Spells[spellid].GetSpellWords(DatManager.PortalDat.SpellComponentsTable));
Console.WriteLine(spellTable.Spells[spellid].Desc);
var formula = SpellTable.GetSpellFormula(DatManager.PortalDat.SpellTable, spellid, parameters[0]);
for (int i = 0; i < formula.Count; i++)
{
if (comps.SpellComponents.ContainsKey(formula[i]))
Console.WriteLine("Comp " + i + ": " + comps.SpellComponents[formula[i]].Name);
else
Console.WriteLine("Comp " + i + " : Unknown Component " + formula[i]);
}
Console.WriteLine();
}
///
/// Debug console command to test the GetSpellFormula function.
///
[CommandHandler("getallspellformula", AccessLevel.Developer, CommandHandlerFlag.ConsoleInvoke, 0, "Tests spell formula calculation")]
public static void GetAllSpellFormula(Session session, params string[] parameters)
{
if (parameters?.Length != 1)
{
Console.WriteLine("getallspellformula ");
return;
}
SpellTable spellTable = DatManager.PortalDat.SpellTable;
SpellComponentsTable comps = DatManager.PortalDat.SpellComponentsTable;
foreach (KeyValuePair entry in spellTable.Spells)
{
uint spellid = entry.Key;
Console.WriteLine("Formula for " + spellTable.Spells[spellid].Name + " (" + spellid + ")");
var formula = SpellTable.GetSpellFormula(DatManager.PortalDat.SpellTable, spellid, parameters[0]);
for (int i = 0; i < formula.Count; i++)
Console.WriteLine("Comp " + i + ": " + comps.SpellComponents[formula[i]].Name);
Console.WriteLine();
}
}
///
/// Debug console command for testing reading the client_portal.dat
///
[CommandHandler("readdat", AccessLevel.Developer, CommandHandlerFlag.ConsoleInvoke, 0, "Tests reading the client_portal.dat")]
public static void ReadDat(Session session, params string[] parameters)
{
//int total = 0;
//uint min = 0x0E010000;
//uint max = 0x0E01FFFF;
var test = DatManager.PortalDat.SkillTable;
return;
/*
foreach (KeyValuePair entry in DatManager.PortalDat.AllFiles)
{
if (entry.Value.ObjectId >= min && entry.Value.ObjectId <= max)
{
// Console.WriteLine("Reading " + entry.Value.ObjectId.ToString("X8"));
QualityFilter item = DatManager.PortalDat.ReadFromDat(entry.Value.ObjectId);
total++;
}
}
if (DatManager.HighResDat != null)
{
foreach (KeyValuePair entry in DatManager.HighResDat.AllFiles)
{
if (entry.Value.ObjectId >= min && entry.Value.ObjectId <= max)
{
// Console.WriteLine("Reading " + entry.Value.ObjectId.ToString("X8"));
QualityFilter item = DatManager.PortalDat.ReadFromDat(entry.Value.ObjectId);
total++;
}
}
}
if(DatManager.LanguageDat != null)
{
foreach (KeyValuePair entry in DatManager.LanguageDat.AllFiles)
{
if (entry.Value.ObjectId >= min && entry.Value.ObjectId <= max)
{
// Console.WriteLine("Reading " + entry.Value.ObjectId.ToString("X8"));
QualityFilter item = DatManager.PortalDat.ReadFromDat(entry.Value.ObjectId);
total++;
}
}
}
Console.WriteLine(total.ToString() + " files read.");
*/
}
// ==================================
// Quests/Contracts
// ==================================
[CommandHandler("contract", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1,
"Query, stamp, and erase contracts on the targeted player",
"[list | bestow | erase]\n"
+ "contract list - List the contracts for the targeted player\n"
+ "contract bestow - Stamps the specific contract on the targeted player. If this fails, it's probably because the contract is invalid.\n"
+ "contract erase - Erase the specific contract from the targeted player. If no quest flag is given, it erases the entire contract table for the targeted player.\n")]
public static void HandleContract(Session session, params string[] parameters)
{
if (parameters.Length == 0)
{
// todo: display help screen
return;
}
var objectId = new ObjectGuid();
if (session.Player.HealthQueryTarget.HasValue)
objectId = new ObjectGuid((uint)session.Player.HealthQueryTarget);
else if (session.Player.ManaQueryTarget.HasValue)
objectId = new ObjectGuid((uint)session.Player.ManaQueryTarget);
else if (session.Player.CurrentAppraisalTarget.HasValue)
objectId = new ObjectGuid((uint)session.Player.CurrentAppraisalTarget);
var wo = session.Player.CurrentLandblock?.GetObject(objectId);
if (wo != null && wo is Player player)
{
if (parameters[0].Equals("list"))
{
var contractsHdr = $"Contract Registry for {player.Name} (0x{player.Guid}):\n";
contractsHdr += "================================================\n";
contractsHdr += $"Contracts.Count: {player.Character.GetContractsCount(player.CharacterDatabaseLock)}\n";
contractsHdr += "================================================\n";
var contracts = "";
foreach (var contract in player.ContractManager.ContractTrackerTable)
{
var contractTracker = contract.Value;
contracts += $"Contract Id: {contractTracker.Contract.ContractId} | Contract Name: {contractTracker.Contract.ContractName}\nStage: {contractTracker.Stage.ToString()}\n";
if (contractTracker.Stage == Network.Structure.ContractStage.InProgress)
{
var timeWhenDone = new TimeSpan(0, 0, (int)contractTracker.TimeWhenDone);
if (timeWhenDone == TimeSpan.MinValue || timeWhenDone.TotalSeconds == 0)
contracts += $"TimeWhenDone: Expired ({contractTracker.TimeWhenDone})\n";
else if (timeWhenDone == TimeSpan.MaxValue)
contracts += $"TimeWhenDone: Unlimited ({contractTracker.TimeWhenDone})\n";
else
contracts += $"TimeWhenDone: In {timeWhenDone:%d} days, {timeWhenDone:%h} hours, {timeWhenDone:%m} minutes and, {timeWhenDone:%s} seconds. ({(DateTime.UtcNow + timeWhenDone).ToLocalTime().ToCommonString()})\n";
}
if (contractTracker.Stage == Network.Structure.ContractStage.DoneOrPendingRepeat)
{
var timeWhenRepeats = new TimeSpan(0, 0, (int)contractTracker.TimeWhenRepeats);
if (timeWhenRepeats == TimeSpan.MinValue || timeWhenRepeats.TotalSeconds == 0)
contracts += $"TimeWhenRepeats: Available ({contractTracker.TimeWhenDone})\n";
else if (timeWhenRepeats == TimeSpan.MaxValue)
contracts += $"TimeWhenRepeats: Unlimited ({contractTracker.TimeWhenDone})\n";
else
contracts += $"TimeWhenRepeats: In {timeWhenRepeats:%d} days, {timeWhenRepeats:%h} hours, {timeWhenRepeats:%m} minutes and, {timeWhenRepeats:%s} seconds. ({(DateTime.UtcNow + timeWhenRepeats).ToLocalTime().ToCommonString()})\n";
}
contracts += "--====--\n";
}
session.Player.SendMessage($"{contractsHdr}{(contracts != "" ? contracts : "No contracts found.")}");
return;
}
if (parameters[0].Equals("bestow"))
{
if (parameters.Length < 2)
{
// delete all contracts?
// seems unsafe, maybe a confirmation?
return;
}
if (!uint.TryParse(parameters[1], out var contractId))
return;
var datContract = player.ContractManager.GetContractFromDat(contractId);
if (datContract == null)
{
session.Player.SendMessage($"Unable to find contract for id {contractId} in dat file.");
return;
}
if (player.ContractManager.HasContract(contractId))
{
session.Player.SendMessage($"{player.Name} already has the contract for \"{datContract.ContractName}\" ({contractId})");
return;
}
var hasContract = player.ContractManager.HasContract(contractId);
if (!hasContract)
{
player.ContractManager.Add(contractId);
session.Player.SendMessage($"Contract for \"{datContract.ContractName}\" ({contractId}) bestowed on {player.Name}");
return;
}
else
{
session.Player.SendMessage($"Couldn't bestow {contractId} on {player.Name}");
return;
}
}
if (parameters[0].Equals("erase"))
{
if (parameters.Length < 2)
{
// delete all contracts?
// seems unsafe, maybe a confirmation?
session.Player.SendMessage($"You must specify a contract to delete, if you want to delete all contracts use the following command: /contract delete *");
return;
}
if (parameters[1] == "*")
{
player.ContractManager.EraseAll();
session.Player.SendMessage($"All contracts deleted for {player.Name}.");
return;
}
if (!uint.TryParse(parameters[1], out var contractId))
return;
var datContract = player.ContractManager.GetContractFromDat(contractId);
if (datContract == null)
{
session.Player.SendMessage($"Unable to find contract for id {contractId} in dat file.");
return;
}
if (!player.ContractManager.HasContract(contractId))
{
session.Player.SendMessage($"{datContract.ContractName} ({contractId}) not found in {player.Name}'s registry.");
return;
}
player.ContractManager.Erase(contractId);
session.Player.SendMessage($"{datContract.ContractName} ({contractId}) deleted for {player.Name}.");
return;
}
}
else
{
if (wo == null)
session.Player.SendMessage($"Selected object (0x{objectId}) not found.");
else
session.Player.SendMessage($"Selected object {wo.Name} (0x{objectId}) is not a player.");
}
}
// ==================================
// Monster movement
// ==================================
[CommandHandler("turnto", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Turns the last appraised object to the player", "turnto")]
public static void HandleRequestTurnTo(Session session, params string[] parameters)
{
// get the last appraised object
var targetID = session.Player.CurrentAppraisalTarget;
if (targetID == null)
{
Console.WriteLine("ERROR: no appraisal target");
return;
}
var targetGuid = new ObjectGuid(targetID.Value);
var target = session.Player.CurrentLandblock?.GetObject(targetGuid);
if (target == null)
{
Console.WriteLine("Couldn't find " + targetGuid);
return;
}
var creature = target as Creature;
if (creature == null)
{
Console.WriteLine(target.Name + " is not a creature / monster");
return;
}
creature.TurnTo(session.Player, true);
}
[CommandHandler("debugmove", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Toggles movement debugging for the last appraised monster", "")]
public static void ToggleMovementDebug(Session session, params string[] parameters)
{
// get the last appraised object
var creature = CommandHandlerHelper.GetLastAppraisedObject(session) as Creature;
if (creature == null)
return;
bool enabled = true;
if (parameters.Length > 0 && parameters[0].Equals("off"))
enabled = false;
creature.DebugMove = enabled;
}
[CommandHandler("lostest", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Tests for direct visibilty with latest appraised object")]
public static void HandleVisible(Session session, params string[] parameters)
{
// get the last appraised object
var targetID = session.Player.CurrentAppraisalTarget;
if (targetID == null)
{
Console.WriteLine("ERROR: no appraisal target");
return;
}
var targetGuid = new ObjectGuid(targetID.Value);
var target = session.Player.CurrentLandblock?.GetObject(targetGuid);
if (target == null)
{
Console.WriteLine("Couldn't find " + targetGuid);
return;
}
var visible = session.Player.IsDirectVisible(target);
Console.WriteLine("Visible: " + visible);
}
[CommandHandler("showstats", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Shows a list of a creature's current attribute/skill levels", "showstats")]
public static void HandleShowStats(Session session, params string[] parameters)
{
// get the last appraised object
var item = CommandHandlerHelper.GetLastAppraisedObject(session) as Creature;
if (item == null)
{
session.Player.SendMessage("ERROR: You must appraise a creature or player to use this function.");
return;
}
Creature creature = (Creature)item;
string output = "Strength: " + creature.Strength.Current;
output += "\nEndurance: " + creature.Endurance.Current;
output += "\nCoordination: " + creature.Coordination.Current;
output += "\nQuickness: " + creature.Quickness.Current;
output += "\nFocus: " + creature.Focus.Current;
output += "\nSelf: " + creature.Self.Current;
output += "\n\nHealth: " + creature.Health.Current + "/" + creature.Health.MaxValue;
output += "\nStamina: " + creature.Stamina.Current + "/" + creature.Stamina.MaxValue;
output += "\nMana: " + creature.Mana.Current + "/" + creature.Mana.MaxValue;
var specialized = creature.Skills.Values.Where(s => s.AdvancementClass == SkillAdvancementClass.Specialized).OrderBy(s => s.Skill.ToString());
var trained = creature.Skills.Values.Where(s => s.AdvancementClass == SkillAdvancementClass.Trained).OrderBy(s => s.Skill.ToString());
var untrained = creature.Skills.Values.Where(s => s.AdvancementClass == SkillAdvancementClass.Untrained && s.IsUsable).OrderBy(s => s.Skill.ToString());
var unusable = creature.Skills.Values.Where(s => s.AdvancementClass == SkillAdvancementClass.Untrained && !s.IsUsable).OrderBy(s => s.Skill.ToString());
if (specialized.Count() > 0)
{
output += "\n\n== Specialized ==";
foreach (var skill in specialized)
output += "\n" + skill.Skill + ": " + skill.Current;
}
if (trained.Count() > 0)
{
output += "\n\n== Trained ==";
foreach (var skill in trained)
output += "\n" + skill.Skill + ": " + skill.Current;
}
if (untrained.Count() > 0)
{
output += "\n\n== Untrained ==";
foreach (var skill in untrained)
output += "\n" + skill.Skill + ": " + skill.Current;
}
if (unusable.Count() > 0)
{
output += "\n\n== Unusable ==";
foreach (var skill in unusable)
output += "\n" + skill.Skill + ": " + skill.Current;
}
session.Player.SendMessage(output);
}
[CommandHandler("givemana", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Gives mana to the last appraised object", "")]
public static void HandleGiveMana(Session session, params string[] parameters)
{
if (parameters.Length == 0) return;
var amount = Int32.Parse(parameters[0]);
var obj = CommandHandlerHelper.GetLastAppraisedObject(session);
if (obj == null) return;
amount = Math.Min(amount, (obj.ItemMaxMana ?? 0) - (obj.ItemCurMana ?? 0));
obj.ItemCurMana += amount;
session.Network.EnqueueSend(new GameMessageSystemChat($"You give {amount} points of mana to the {obj.Name}.", ChatMessageType.Magic));
}
///
/// Returns the distance to the last appraised object
///
[CommandHandler("dist", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Returns the distance to the last appraised object")]
public static void HandleDist(Session session, params string[] parameters)
{
var obj = CommandHandlerHelper.GetLastAppraisedObject(session);
if (obj == null || obj.PhysicsObj == null) return;
var sourcePos = session.Player.Location.ToGlobal();
var targetPos = obj.Location.ToGlobal();
var dist = Vector3.Distance(sourcePos, targetPos);
var dist2d = Vector2.Distance(new Vector2(sourcePos.X, sourcePos.Y), new Vector2(targetPos.X, targetPos.Y));
var cylDist = session.Player.PhysicsObj.get_distance_to_object(obj.PhysicsObj, true);
session.Network.EnqueueSend(new GameMessageSystemChat($"Dist: {dist}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"2D Dist: {dist2d}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"CylDist: {cylDist}", ChatMessageType.Broadcast));
}
///
/// Teleport object culling precision test
///
[CommandHandler("teledist", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Teleports a some distance ahead of the last object spawned", "")]
public static void HandleTeleportDist(Session session, params string[] parameters)
{
if (parameters.Length < 1)
return;
var lastSpawnPos = AdminCommands.LastSpawnPos;
var distance = float.Parse(parameters[0]);
var newPos = new Position();
newPos.LandblockId = new LandblockId(lastSpawnPos.LandblockId.Raw);
newPos.Pos = lastSpawnPos.Pos;
newPos.Rotation = session.Player.Location.Rotation;
var dir = Vector3.Normalize(Vector3.Transform(Vector3.UnitY, newPos.Rotation));
var offset = dir * distance;
newPos.SetPosition(newPos.Pos + offset);
session.Player.Teleport(newPos);
var globLastSpawnPos = lastSpawnPos.ToGlobal();
var globNewPos = newPos.ToGlobal();
var totalDist = Vector3.Distance(globLastSpawnPos, globNewPos);
var totalDist2d = Vector2.Distance(new Vector2(globLastSpawnPos.X, globLastSpawnPos.Y), new Vector2(globNewPos.X, globNewPos.Y));
ChatPacket.SendServerMessage(session, $"Teleporting player to {newPos.Cell:X8} @ {newPos.Pos}", ChatMessageType.System);
ChatPacket.SendServerMessage(session, "2D Distance: " + totalDist2d, ChatMessageType.System);
ChatPacket.SendServerMessage(session, "3D Distance: " + totalDist, ChatMessageType.System);
}
public static WorldObject GetObjectMaintTarget(Session session, params string[] parameters)
{
WorldObject target = session.Player;
if (parameters.Length > 0)
{
var targetType = parameters[0];
if (targetType.Equals("target", StringComparison.OrdinalIgnoreCase))
target = CommandHandlerHelper.GetLastAppraisedObject(session);
else if (uint.TryParse(targetType, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var targetGuid))
{
if (ServerObjectManager.ServerObjects.TryGetValue(targetGuid, out var physicsObj))
target = physicsObj.WeenieObj.WorldObject;
}
}
if (target == null)
{
var param = parameters.Length > 0 ? $" {parameters[0]}" : "";
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find target{param}", ChatMessageType.Broadcast));
}
return target;
}
///
/// Shows the list of objects currently known to an object
///
[CommandHandler("knownobjs", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Shows the list of objects currently known to an object", "")]
public static void HandleKnownObjs(Session session, params string[] parameters)
{
var target = GetObjectMaintTarget(session, parameters);
if (target == null)
return;
Console.WriteLine($"\nKnown objects to {target.Name}: {target.PhysicsObj.ObjMaint.GetKnownObjectsCount()}");
foreach (var obj in target.PhysicsObj.ObjMaint.GetKnownObjectsValues())
Console.WriteLine($"{obj.Name} ({obj.ID:X8})");
}
///
/// Shows the list of objects currently visible to an object
///
[CommandHandler("visibleobjs", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Shows the list of objects currently visible to an object", "")]
public static void HandleVisibleObjs(Session session, params string[] parameters)
{
var target = GetObjectMaintTarget(session, parameters);
if (target == null)
return;
Console.WriteLine($"\nVisible objects to {target.Name}: {target.PhysicsObj.ObjMaint.GetVisibleObjectsCount()}");
foreach (var obj in target.PhysicsObj.ObjMaint.GetVisibleObjectsValues())
Console.WriteLine($"{obj.Name} ({obj.ID:X8})");
}
///
/// Shows the list of players known to an object
/// KnownPlayers are used for broadcasting
///
[CommandHandler("knownplayers", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Shows the list of players known to an object", "")]
public static void HandleKnownPlayers(Session session, params string[] parameters)
{
var target = GetObjectMaintTarget(session, parameters);
if (target == null)
return;
Console.WriteLine($"\nKnown players to {target.Name}: {target.PhysicsObj.ObjMaint.GetKnownPlayersCount()}");
foreach (var obj in target.PhysicsObj.ObjMaint.GetKnownPlayersValues())
Console.WriteLine($"{obj.Name} ({obj.ID:X8})");
}
///
/// Shows the list of players visible to a player
///
[CommandHandler("visibleplayers", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Shows the list of players visible to a player", "")]
public static void HandleVisiblePlayers(Session session, params string[] parameters)
{
var target = GetObjectMaintTarget(session, parameters);
if (target == null)
return;
Console.WriteLine($"\nVisible players to {target.Name}: {target.PhysicsObj.ObjMaint.GetVisibleObjectsValuesWhere(o => o.IsPlayer).Count}");
foreach (var obj in target.PhysicsObj.ObjMaint.GetVisibleObjectsValuesWhere(o => o.IsPlayer))
Console.WriteLine($"{obj.Name} ({obj.ID:X8})");
}
///
/// Shows the list of targets currently visible to a monster
///
[CommandHandler("visibletargets", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Shows the list of targets currently visible to a monster", "")]
public static void HandleVisibleTargets(Session session, params string[] parameters)
{
var target = GetObjectMaintTarget(session, parameters);
if (target == null)
return;
Console.WriteLine($"\nVisible targets to {target.Name}: {target.PhysicsObj.ObjMaint.GetVisibleTargetsCount()}");
foreach (var obj in target.PhysicsObj.ObjMaint.GetVisibleTargetsValues())
Console.WriteLine($"{obj.Name} ({obj.ID:X8})");
}
///
/// Shows the list of retaliate targets for a monster
///
[CommandHandler("retaliatetargets", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Shows the list of retaliate targets for a monster", "")]
public static void HandleRetaliateTargets(Session session, params string[] parameters)
{
var target = GetObjectMaintTarget(session, parameters);
if (target == null)
return;
Console.WriteLine($"\nRetaliate targets to {target.Name}: {target.PhysicsObj.ObjMaint.GetRetaliateTargetsCount()}");
foreach (var obj in target.PhysicsObj.ObjMaint.GetRetaliateTargetsValues())
Console.WriteLine($"{obj.Name} ({obj.ID:X8})");
}
///
/// Shows the list of previously visible objects queued for destruction for a player
///
[CommandHandler("destructionqueue", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Shows the list of previously visible objects queued for destruction for a player", "")]
public static void HandleDestructionQueue(Session session, params string[] parameters)
{
var target = GetObjectMaintTarget(session, parameters);
if (target == null)
return;
Console.WriteLine($"\nDestruction queue for {target.Name}: {target.PhysicsObj.ObjMaint.GetDestructionQueueCount()}");
var currentTime = Physics.Common.PhysicsTimer.CurrentTime;
foreach (var obj in target.PhysicsObj.ObjMaint.GetDestructionQueueCopy())
Console.WriteLine($"{obj.Key.Name} ({obj.Key.ID:X8}): {obj.Value - currentTime}");
}
///
/// Enables emote debugging for the last appraised object
///
[CommandHandler("debugemote", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Enables emote debugging for the last appraised object")]
public static void HandleDebugEmote(Session session, params string[] parameters)
{
var obj = CommandHandlerHelper.GetLastAppraisedObject(session);
if (obj != null)
{
Console.WriteLine($"Showing emotes for {obj.Name}");
obj.EmoteManager.Debug = true;
}
}
///
/// Shows the current player location, from the server perspective
///
[CommandHandler("myloc", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Shows the current player location, from the server perspective")]
public static void HandleMyLoc(Session session, params string[] parameters)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"CurrentLandblock: {session.Player.CurrentLandblock.Id.Landblock:X4}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"Location: {session.Player.Location.ToLOCString()}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"Physics : {session.Player.PhysicsObj.Position}", ChatMessageType.Broadcast));
}
///
/// Gets a property for the last appraised object
///
[CommandHandler("getproperty", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Gets a property for the last appraised object", "")]
public static void HandleGetProperty(Session session, params string[] parameters)
{
var obj = CommandHandlerHelper.GetLastAppraisedObject(session);
if (obj == null) return;
if (parameters.Length < 1)
return;
var prop = parameters[0];
var props = prop.Split('.');
if (props.Length != 2)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Unknown {prop}", ChatMessageType.Broadcast));
return;
}
var propType = props[0];
var propName = props[1];
Type pType;
if (propType.Equals("PropertyInt", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyInt);
else if (propType.Equals("PropertyInt64", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyInt64);
else if (propType.Equals("PropertyBool", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyBool);
else if (propType.Equals("PropertyFloat", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyFloat);
else if (propType.Equals("PropertyString", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyString);
else if (propType.Equals("PropertyInstanceId", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyInstanceId);
else if (propType.Equals("PropertyDataId", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyDataId);
else
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Unknown property type: {propType}", ChatMessageType.Broadcast));
return;
}
if (!Enum.TryParse(pType, propName, true, out var result))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find {prop}", ChatMessageType.Broadcast));
return;
}
var value = "";
if (propType.Equals("PropertyInt", StringComparison.OrdinalIgnoreCase))
value = Convert.ToString(obj.GetProperty((PropertyInt)result));
else if (propType.Equals("PropertyInt64", StringComparison.OrdinalIgnoreCase))
value = Convert.ToString(obj.GetProperty((PropertyInt64)result));
else if (propType.Equals("PropertyBool", StringComparison.OrdinalIgnoreCase))
value = Convert.ToString(obj.GetProperty((PropertyBool)result));
else if (propType.Equals("PropertyFloat", StringComparison.OrdinalIgnoreCase))
value = Convert.ToString(obj.GetProperty((PropertyFloat)result));
else if (propType.Equals("PropertyString", StringComparison.OrdinalIgnoreCase))
value = Convert.ToString(obj.GetProperty((PropertyString)result));
else if (propType.Equals("PropertyInstanceId", StringComparison.OrdinalIgnoreCase))
value = Convert.ToString(obj.GetProperty((PropertyInstanceId)result));
else if (propType.Equals("PropertyDataId", StringComparison.OrdinalIgnoreCase))
value = Convert.ToString(obj.GetProperty((PropertyDataId)result));
session.Network.EnqueueSend(new GameMessageSystemChat($"{obj.Name} ({obj.Guid}): {prop} = {value}", ChatMessageType.Broadcast));
}
///
/// Sets a property for the last appraised object
///
[CommandHandler("setproperty", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 2, "Sets a property for the last appraised object", " ")]
public static void HandleSetProperty(Session session, params string[] parameters)
{
var obj = CommandHandlerHelper.GetLastAppraisedObject(session);
if (obj == null) return;
if (parameters.Length < 2)
return;
var prop = parameters[0];
var value = parameters[1];
var props = prop.Split('.');
if (props.Length != 2)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Unknown {prop}", ChatMessageType.Broadcast));
return;
}
var propType = props[0];
var propName = props[1];
Type pType;
if (propType.Equals("PropertyInt", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyInt);
else if (propType.Equals("PropertyInt64", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyInt64);
else if (propType.Equals("PropertyBool", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyBool);
else if (propType.Equals("PropertyFloat", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyFloat);
else if (propType.Equals("PropertyString", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyString);
else if (propType.Equals("PropertyInstanceId", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyInstanceId);
else if (propType.Equals("PropertyDataId", StringComparison.OrdinalIgnoreCase))
pType = typeof(PropertyDataId);
else
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Unknown property type: {propType}", ChatMessageType.Broadcast));
return;
}
if (!Enum.TryParse(pType, propName, true, out var result))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find {prop}", ChatMessageType.Broadcast));
return;
}
if (value == "null")
{
if (propType.Equals("PropertyInt", StringComparison.OrdinalIgnoreCase))
obj.RemoveProperty((PropertyInt)result);
else if (propType.Equals("PropertyInt64", StringComparison.OrdinalIgnoreCase))
obj.RemoveProperty((PropertyInt64)result);
else if (propType.Equals("PropertyBool", StringComparison.OrdinalIgnoreCase))
obj.RemoveProperty((PropertyBool)result);
else if (propType.Equals("PropertyFloat", StringComparison.OrdinalIgnoreCase))
obj.RemoveProperty((PropertyFloat)result);
else if (propType.Equals("PropertyString", StringComparison.OrdinalIgnoreCase))
obj.RemoveProperty((PropertyString)result);
else if (propType.Equals("PropertyInstanceId", StringComparison.OrdinalIgnoreCase))
obj.RemoveProperty((PropertyInstanceId)result);
else if (propType.Equals("PropertyDataId", StringComparison.OrdinalIgnoreCase))
obj.RemoveProperty((PropertyDataId)result);
}
else
{
try
{
if (propType.Equals("PropertyInt", StringComparison.OrdinalIgnoreCase))
{
var intValue = Convert.ToInt32(value, value.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? 16 : 10);
session.Player.UpdateProperty(obj, (PropertyInt)result, intValue, true);
}
else if (propType.Equals("PropertyInt64", StringComparison.OrdinalIgnoreCase))
{
var int64Value = Convert.ToInt64(value, value.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? 16 : 10);
session.Player.UpdateProperty(obj, (PropertyInt64)result, int64Value, true);
}
else if (propType.Equals("PropertyBool", StringComparison.OrdinalIgnoreCase))
{
var boolValue = Convert.ToBoolean(value);
session.Player.UpdateProperty(obj, (PropertyBool)result, boolValue, true);
}
else if (propType.Equals("PropertyFloat", StringComparison.OrdinalIgnoreCase))
{
var floatValue = Convert.ToDouble(value);
session.Player.UpdateProperty(obj, (PropertyFloat)result, floatValue, true);
}
else if (propType.Equals("PropertyString", StringComparison.OrdinalIgnoreCase))
{
session.Player.UpdateProperty(obj, (PropertyString)result, value, true);
}
else if (propType.Equals("PropertyInstanceId", StringComparison.OrdinalIgnoreCase))
{
var iidValue = Convert.ToUInt32(value, value.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? 16 : 10);
session.Player.UpdateProperty(obj, (PropertyInstanceId)result, iidValue, true);
}
else if (propType.Equals("PropertyDataId", StringComparison.OrdinalIgnoreCase))
{
var didValue = Convert.ToUInt32(value, value.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? 16 : 10);
session.Player.UpdateProperty(obj, (PropertyDataId)result, didValue, true);
}
}
catch (Exception e)
{
Console.WriteLine(e);
return;
}
}
session.Network.EnqueueSend(new GameMessageSystemChat($"{obj.Name} ({obj.Guid}): {prop} = {value}", ChatMessageType.Broadcast));
PlayerManager.BroadcastToAuditChannel(session.Player, $"{session.Player.Name} changed a property for {obj.Name} ({obj.Guid}): {prop} = {value}");
}
///
/// Sets the house purchase time for this player
///
[CommandHandler("setpurchasetime", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Sets the house purchase time for this player")]
public static void HandleSetPurchaseTime(Session session, params string[] parameters)
{
var currentTime = DateTime.UtcNow;
Console.WriteLine($"Current time: {currentTime.ToCommonString()}");
// subtract 30 days
var purchaseTime = currentTime - TimeSpan.FromDays(30);
// add buffer
purchaseTime += TimeSpan.FromSeconds(1);
//purchaseTime += TimeSpan.FromMinutes(2);
var rentDue = DateTimeOffset.FromUnixTimeSeconds(session.Player.House.GetRentDue((uint)Time.GetUnixTime(purchaseTime))).UtcDateTime;
var prevPurchaseTime = DateTimeOffset.FromUnixTimeSeconds(session.Player.HousePurchaseTimestamp ?? 0).UtcDateTime;
var prevRentDue = DateTimeOffset.FromUnixTimeSeconds(session.Player.House.GetRentDue((uint)(session.Player.HousePurchaseTimestamp ?? 0))).UtcDateTime;
Console.WriteLine($"Previous purchase time: {prevPurchaseTime.ToCommonString()}");
Console.WriteLine($"New purchase time: {purchaseTime.ToCommonString()}");
Console.WriteLine($"Previous rent time: {prevRentDue.ToCommonString()}");
Console.WriteLine($"New rent time: {rentDue.ToCommonString()}");
session.Player.HousePurchaseTimestamp = (int)Time.GetUnixTime(purchaseTime);
session.Player.HouseRentTimestamp = (int)session.Player.House.GetRentDue((uint)Time.GetUnixTime(purchaseTime));
HouseManager.BuildRentQueue();
}
///
/// Toggles the display for player damage info
///
[CommandHandler("debugdamage", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Toggles the display for player damage info", "")]
public static void HandleDebugDamage(Session session, params string[] parameters)
{
// get last appraisal creature target
var targetCreature = CommandHandlerHelper.GetLastAppraisedObject(session) as Creature;
if (targetCreature == null) return;
if (parameters.Length == 0)
{
// toggle
if (targetCreature.DebugDamage == Creature.DebugDamageType.None)
targetCreature.DebugDamage = Creature.DebugDamageType.All;
else
targetCreature.DebugDamage = Creature.DebugDamageType.None;
}
else
{
var param = parameters[0].ToLower();
if (param.Equals("on") || param.Equals("all"))
targetCreature.DebugDamage = Creature.DebugDamageType.All;
else if (param.Equals("off"))
targetCreature.DebugDamage = Creature.DebugDamageType.None;
else if (param.StartsWith("attack"))
targetCreature.DebugDamage = Creature.DebugDamageType.Attacker;
else if (param.StartsWith("defen"))
targetCreature.DebugDamage = Creature.DebugDamageType.Defender;
else
{
session.Network.EnqueueSend(new GameMessageSystemChat($"DebugDamage: - unknown {param} ({targetCreature.Name})", ChatMessageType.Broadcast));
return;
}
}
session.Network.EnqueueSend(new GameMessageSystemChat($"DebugDamage: - {targetCreature.DebugDamage} ({targetCreature.Name})", ChatMessageType.Broadcast));
targetCreature.DebugDamageTarget = session.Player.Guid;
}
///
/// Enables the aetheria slots for the player
///
[CommandHandler("enable-aetheria", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Enables the aetheria slots for the player")]
public static void HandleEnableAetheria(Session session, params string[] parameters)
{
var flags = (int)AetheriaBitfield.All;
if (parameters.Length > 0)
int.TryParse(parameters[0], out flags);
session.Player.UpdateProperty(session.Player, PropertyInt.AetheriaBitfield, flags);
}
[CommandHandler("debugchess", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows the chess move history for a player")]
public static void HandleDebugChess(Session session, params string[] parameters)
{
session.Player.ChessMatch?.DebugMove();
}
[CommandHandler("debugboard", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows the current chess board state")]
public static void HandleDebugBoard(Session session, params string[] parameters)
{
session.Player.ChessMatch?.Logic?.DebugBoard();
}
///
/// Teleports directly to a dungeon by name or landblock
///
[CommandHandler("teledungeon", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Teleport to a dungeon", "")]
public static void HandleTeleDungeon(Session session, params string[] parameters)
{
var isBlock = true;
var param = parameters[0];
if (parameters.Length > 1)
isBlock = false;
var landblock = 0u;
if (isBlock)
{
try
{
landblock = Convert.ToUInt32(param, 16);
if (landblock >= 0xFFFF)
landblock = landblock >> 16;
}
catch (Exception)
{
isBlock = false;
}
}
// teleport to dungeon landblock
if (isBlock)
HandleTeleDungeonBlock(session, landblock);
// teleport to dungeon by name
else
HandleTeleDungeonName(session, parameters);
}
public static void HandleTeleDungeonBlock(Session session, uint landblock)
{
using (var ctx = new WorldDbContext())
{
var query = from weenie in ctx.Weenie
join wpos in ctx.WeeniePropertiesPosition on weenie.ClassId equals wpos.ObjectId
where weenie.Type == (int)WeenieType.Portal && wpos.PositionType == (int)PositionType.Destination
select new
{
Weenie = weenie,
Dest = wpos
};
var results = query.ToList();
var dest = results.Where(i => i.Dest.ObjCellId >> 16 == landblock).Select(i => i.Dest).FirstOrDefault();
if (dest == null)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find dungeon {landblock:X4}", ChatMessageType.Broadcast));
return;
}
var pos = new Position(dest.ObjCellId, dest.OriginX, dest.OriginY, dest.OriginZ, dest.AnglesX, dest.AnglesY, dest.AnglesZ, dest.AnglesW);
WorldObject.AdjustDungeon(pos);
session.Player.Teleport(pos);
}
}
public static void HandleTeleDungeonName(Session session, params string[] parameters)
{
var searchName = string.Join(" ", parameters);
using (var ctx = new WorldDbContext())
{
var query = from weenie in ctx.Weenie
join wstr in ctx.WeeniePropertiesString on weenie.ClassId equals wstr.ObjectId
join wpos in ctx.WeeniePropertiesPosition on weenie.ClassId equals wpos.ObjectId
where weenie.Type == (int)WeenieType.Portal && wstr.Type == (int)PropertyString.Name && wpos.PositionType == (int)PositionType.Destination
select new
{
Weenie = weenie,
Name = wstr,
Dest = wpos
};
var results = query.ToList();
var dest = results.Where(i => i.Name.Value.Contains(searchName, StringComparison.OrdinalIgnoreCase)).Select(i => i.Dest).FirstOrDefault();
if (dest == null)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find dungeon name {searchName}", ChatMessageType.Broadcast));
return;
}
var pos = new Position(dest.ObjCellId, dest.OriginX, dest.OriginY, dest.OriginZ, dest.AnglesX, dest.AnglesY, dest.AnglesZ, dest.AnglesW);
WorldObject.AdjustDungeon(pos);
session.Player.Teleport(pos);
}
}
///
/// Shows the dungeon name for the current landblock
///
[CommandHandler("dungeonname", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows the dungeon name for the current landblock")]
public static void HandleDungeonName(Session session, params string[] parameters)
{
var landblock = session.Player.Location.Landblock;
var blockStart = landblock << 16;
var blockEnd = blockStart | 0xFFFF;
using (var ctx = new WorldDbContext())
{
var query = from weenie in ctx.Weenie
join wstr in ctx.WeeniePropertiesString on weenie.ClassId equals wstr.ObjectId
join wpos in ctx.WeeniePropertiesPosition on weenie.ClassId equals wpos.ObjectId
where weenie.Type == (int)WeenieType.Portal && wpos.PositionType == (int)PositionType.Destination && wpos.ObjCellId >= blockStart && wpos.ObjCellId <= blockEnd
select wstr;
var results = query.ToList();
if (results.Count() == 0)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find dungeon {landblock:X4}", ChatMessageType.Broadcast));
return;
}
foreach (var result in results)
{
var name = result.Value.TrimStart("Portal to ").TrimEnd(" Portal");
session.Network.EnqueueSend(new GameMessageSystemChat(name, ChatMessageType.Broadcast));
}
}
}
[CommandHandler("clearphysicscaches", AccessLevel.Developer, CommandHandlerFlag.None, 0, "Clears Physics Object Caches")]
public static void HandleClearPhysicsCaches(Session session, params string[] parameters)
{
BSPCache.Clear();
GfxObjCache.Clear();
PolygonCache.Clear();
VertexCache.Clear();
CommandHandlerHelper.WriteOutputInfo(session, "Physics caches cleared");
}
[CommandHandler("forcegc", AccessLevel.Developer, CommandHandlerFlag.None, 0, "Forces .NET Garbage Collection")]
public static void HandleForceGC(Session session, params string[] parameters)
{
GC.Collect();
CommandHandlerHelper.WriteOutputInfo(session, ".NET Garbage Collection forced");
}
[CommandHandler("forcegc2", AccessLevel.Developer, CommandHandlerFlag.None, 0, "Forces .NET Garbage Collection with LOH Compact")]
public static void HandleForceGC2(Session session, params string[] parameters)
{
// https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals
// https://learn.microsoft.com/en-us/dotnet/api/system.runtime.gcsettings.largeobjectheapcompactionmode
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
CommandHandlerHelper.WriteOutputInfo(session, ".NET Garbage Collection forced with LOH Compact");
}
[CommandHandler("auditobjectmaint", AccessLevel.Developer, CommandHandlerFlag.None, 0, "Iterates over physics objects to find leaks")]
public static void HandleAuditObjectMaint(Session session, params string[] parameters)
{
var serverObjects = ServerObjectManager.ServerObjects.Keys.ToHashSet();
int objectTableErrors = 0;
int visibleObjectTableErrors = 0;
int voyeurTableErrors = 0;
foreach (var value in ServerObjectManager.ServerObjects.Values)
{
{
var kvps = value.ObjMaint.GetKnownObjectsWhere(kvp => !serverObjects.Contains(kvp.Key));
foreach (var kvp in kvps)
{
if (value.ObjMaint.RemoveKnownObject(kvp.Value, false))
{
if (log.IsDebugEnabled)
log.Debug($"AuditObjectMaint removed 0x{kvp.Value.ID:X8}:{kvp.Value.Name} (IsDestroyed:{kvp.Value.WeenieObj?.WorldObject?.IsDestroyed}, Position:{kvp.Value.Position}) from 0x{value.ID:X8}:{value.Name} (IsDestroyed:{value.WeenieObj?.WorldObject?.IsDestroyed}, Position:{value.Position}) [ObjectTable]");
objectTableErrors++;
}
}
}
{
var kvps = value.ObjMaint.GetVisibleObjectsWhere(kvp => !serverObjects.Contains(kvp.Key));
foreach (var kvp in kvps)
{
if (value.ObjMaint.RemoveVisibleObject(kvp.Value, false))
{
if (log.IsDebugEnabled)
log.Debug($"AuditObjectMaint removed 0x{kvp.Value.ID:X8}:{kvp.Value.Name} (IsDestroyed:{kvp.Value.WeenieObj?.WorldObject?.IsDestroyed}, Position:{kvp.Value.Position}) from 0x{value.ID:X8}:{value.Name} (IsDestroyed:{value.WeenieObj?.WorldObject?.IsDestroyed}, Position:{value.Position}) [VisibleObjectTable]");
visibleObjectTableErrors++;
}
}
}
{
var kvps = value.ObjMaint.GetKnownPlayersWhere(kvp => !serverObjects.Contains(kvp.Key));
foreach (var kvp in kvps)
{
if (value.ObjMaint.RemoveKnownPlayer(kvp.Value))
{
if (log.IsDebugEnabled)
log.Debug($"AuditObjectMaint removed 0x{kvp.Value.ID:X8}:{kvp.Value.Name} (IsDestroyed:{kvp.Value.WeenieObj?.WorldObject?.IsDestroyed}, Position:{kvp.Value.Position}) from 0x{value.ID:X8}:{value.Name} (IsDestroyed:{value.WeenieObj?.WorldObject?.IsDestroyed}, Position:{value.Position}) [VoyeurTable]");
voyeurTableErrors++;
}
}
}
}
if (session != null)
CommandHandlerHelper.WriteOutputInfo(session, $"Physics ObjMaint Audit Completed. Errors - objectTable: {objectTableErrors}, visibleObjectTable: {visibleObjectTableErrors}, voyeurTable: {voyeurTableErrors}");
log.Info($"Physics ObjMaint Audit Completed. Errors - objectTable: {objectTableErrors}, visibleObjectTable: {visibleObjectTableErrors}, voyeurTable: {voyeurTableErrors}");
}
[CommandHandler("lootgen", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Generate a piece of loot from the LootGenerationFactory.", " ")]
public static void HandleLootGen(Session session, params string[] parameters)
{
WorldObject wo = null;
// create base item
if (uint.TryParse(parameters[0], out var wcid))
wo = WorldObjectFactory.CreateNewWorldObject(wcid);
else
wo = WorldObjectFactory.CreateNewWorldObject(parameters[0]);
if (wo == null)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find {parameters[0]}", ChatMessageType.Broadcast));
return;
}
int tier = 1;
if (parameters.Length > 1)
int.TryParse(parameters[1], out tier);
if (tier < 1 || tier > 8)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Loot Tier must be a number between 1 and 8", ChatMessageType.Broadcast));
return;
}
if (wo.TsysMutationData == null && !Aetheria.IsAetheria(wo.WeenieClassId) && !(wo is PetDevice))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"{wo.Name} ({wo.WeenieClassId}) missing PropertyInt.TsysMutationData", ChatMessageType.Broadcast));
return;
}
var profile = new TreasureDeath()
{
Tier = tier,
LootQualityMod = 0
};
var success = LootGenerationFactory.MutateItem(wo, profile, true);
session.Player.TryCreateInInventoryWithNetworking(wo);
}
[CommandHandler("ciloot", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Generates randomized loot in player's inventory", " optional: <# items>")]
public static void HandleCILoot(Session session, params string[] parameters)
{
var tier = 1;
int.TryParse(parameters[0], out tier);
tier = Math.Clamp(tier, 1, 8);
var numItems = 1;
if (parameters.Length > 1)
int.TryParse(parameters[1], out numItems);
// Create a dummy treasure profile for passing in tier value
TreasureDeath profile = new TreasureDeath
{
Tier = tier,
LootQualityMod = 0,
MagicItemTreasureTypeSelectionChances = 9, // 8 or 9?
};
for (var i = 0; i < numItems; i++)
{
//var wo = LootGenerationFactory.CreateRandomLootObjects(profile, true);
var wo = LootGenerationFactory.CreateRandomLootObjects(profile, TreasureItemCategory.MagicItem);
if (wo != null)
session.Player.TryCreateInInventoryWithNetworking(wo);
else
log.Error($"{session.Player.Name}.HandleCILoot: LootGenerationFactory.CreateRandomLootObjects({tier}) returned null");
}
}
[CommandHandler("makeiou", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Make an IOU and put it in your inventory", "")]
public static void HandleMakeIOU(Session session, params string[] parameters)
{
string weenieClassDescription = parameters[0];
bool wcid = uint.TryParse(weenieClassDescription, out uint weenieClassId);
if (!wcid)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"WCID must be a valid weenie id", ChatMessageType.Broadcast));
return;
}
var iou = PlayerFactory.CreateIOU(weenieClassId);
if (iou != null)
session.Player.TryCreateInInventoryWithNetworking(iou);
}
[CommandHandler("testdeathitems", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Test death item selection", "")]
public static void HandleTestDeathItems(Session session, params string[] parameters)
{
var target = session.Player;
if (parameters.Length > 0)
{
target = PlayerManager.GetOnlinePlayer(parameters[0]);
if (target == null)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find {parameters[0]}", ChatMessageType.Broadcast));
return;
}
}
var inventory = target.GetAllPossessions();
var sorted = new DeathItems(inventory);
var i = 0;
foreach (var item in sorted.Inventory)
{
var bonded = item.WorldObject.Bonded ?? BondedStatus.Normal;
if (bonded != BondedStatus.Normal)
continue;
session.Network.EnqueueSend(new GameMessageSystemChat($"{++i}. {item.Name} ({item.Category}, AdjustedValue: {item.AdjustedValue})", ChatMessageType.Broadcast));
}
}
[CommandHandler("forcelogout", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Force log off of specified character or last appraised character")]
public static void HandleForceLogout(Session session, params string[] parameters)
{
HandleForceLogoff(session, parameters);
}
[CommandHandler("forcelogoff", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Force log off of specified character or last appraised character")]
public static void HandleForceLogoff(Session session, params string[] parameters)
{
var playerName = "";
if (parameters.Length > 0)
playerName = string.Join(" ", parameters);
WorldObject target = null;
if (!string.IsNullOrEmpty(playerName))
{
var plr = PlayerManager.FindByName(playerName);
if (plr != null)
{
target = PlayerManager.GetOnlinePlayer(plr.Guid);
if (target == null)
{
CommandHandlerHelper.WriteOutputInfo(session, $"Unable to force log off for {plr.Name}: Player is not online.");
return;
}
}
else
{
CommandHandlerHelper.WriteOutputInfo(session, $"Unable to force log off for {playerName}: Player not found in manager.");
return;
}
}
else
target = CommandHandlerHelper.GetLastAppraisedObject(session);
if (target != null && target is Player player)
{
//if (player.Session != null)
// player.Session.LogOffPlayer(true);
//else
// player.LogOut();
var msg = $"Player {player.Name} (0x{player.Guid}) found in PlayerManager.onlinePlayers.\n";
msg += $"------- Session: {(player.Session != null ? $"C2S: {player.Session.EndPointC2S} | S2C: {player.Session.EndPointS2C}" : "NULL")}\n";
msg += $"------- CurrentLandblock: {(player.CurrentLandblock != null ? $"0x{player.CurrentLandblock.Id:X4}" : "NULL")}\n";
msg += $"------- Location: {(player.Location != null ? $"{player.Location.ToLOCString()}" : "NULL")}\n";
msg += $"------- IsLoggingOut: {player.IsLoggingOut}\n";
msg += $"------- IsInDeathProcess: {player.IsInDeathProcess}\n";
var foundOnLandblock = false;
if (player.CurrentLandblock != null)
foundOnLandblock = LandblockManager.GetLandblock(player.CurrentLandblock.Id, false).GetObject(player.Guid) != null;
msg += $"------- FoundOnLandblock: {foundOnLandblock}\n";
var playerForcedLogOffRequested = player.ForcedLogOffRequested;
msg += $"------- ForcedLogOffRequested: {playerForcedLogOffRequested}\n";
msg += "Log off path taken: ";
if (playerForcedLogOffRequested)
{
player.Session?.Terminate(Network.Enum.SessionTerminationReason.ForcedLogOffRequested, new GameMessageBootAccount(" because the character was forced to log off by an admin"));
player.ForceLogoff();
msg += "player.Session?.Terminate() | player.ForceLogoff()";
}
else if (player.Session != null)
{
player.ForcedLogOffRequested = true;
player.Session.Terminate(Network.Enum.SessionTerminationReason.ForcedLogOffRequested, new GameMessageBootAccount(" because the character was forced to log off by an admin"));
msg += "player.ForcedLogOffRequested = true | player.Session.Terminate()";
}
else if (player.CurrentLandblock != null && foundOnLandblock)
{
player.ForcedLogOffRequested = true;
player.LogOut();
msg += "player.ForcedLogOffRequested = true | player.LogOut()";
}
else if (player.IsInDeathProcess)
{
player.ForcedLogOffRequested = true;
player.IsInDeathProcess = false;
player.LogOut_Inner(true);
msg += "player.ForcedLogOffRequested = true | player.IsInDeathProcess = false | player.LogOut_Inner(true)";
}
else
{
player.ForcedLogOffRequested = true;
msg += "player.ForcedLogOffRequested = true";
}
if (!playerForcedLogOffRequested)
msg += "\nUse this command again if this player does not properly log off within the next minute.";
else
msg += "\nPlease send the above report to ACEmulator development team via Discord.";
CommandHandlerHelper.WriteOutputInfo(session, msg);
PlayerManager.BroadcastToAuditChannel(session?.Player, $"Forcing Log Off of {player.Name}...");
}
else
{
if (target != null)
CommandHandlerHelper.WriteOutputInfo(session, $"Unable to force log off for {target.Name}: Target is not a player.");
//else
// CommandHandlerHelper.WriteOutputInfo(session, $"Unable to force log off for {playerName}: Player not found in manager.");
}
}
[CommandHandler("showsession", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Show IP and ID for network session of last appraised character")]
public static void HandleShowSession(Session session, params string[] parameters)
{
var target = CommandHandlerHelper.GetLastAppraisedObject(session);
if (target != null && target is Player player)
{
if (player.Session != null)
session.Network.EnqueueSend(new GameMessageSystemChat($"Session IP: {player.Session.EndPointC2S.Address} | C2S Port: {player.Session.EndPointC2S.Port} | S2C Port: {player.Session.EndPointS2C?.Port} | ClientId: {player.Session.Network.ClientId} is connected to Character: {player.Name} (0x{player.Guid.Full.ToString("X8")}), Account: {player.Account.AccountName} ({player.Account.AccountId})", ChatMessageType.Broadcast));
else
session.Network.EnqueueSend(new GameMessageSystemChat($"Session is null for {player.Name} which shouldn't occur.", ChatMessageType.Broadcast));
}
}
[CommandHandler("requirecomps", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1,
"Sets whether spell components are required to cast spells.",
"[ on | off ]\n"
+ "This command sets whether spell components are required to cast spells..\n When turned on, spell components are required.\n When turned off, spell components are ignored.")]
public static void HandleRequireComps(Session session, params string[] parameters)
{
var param = parameters[0];
switch (param)
{
case "off":
session.Player.SpellComponentsRequired = false;
session.Player.EnqueueBroadcast(new GameMessagePublicUpdatePropertyBool(session.Player, PropertyBool.SpellComponentsRequired, session.Player.SpellComponentsRequired));
session.Network.EnqueueSend(new GameMessageSystemChat("You can now cast spells without components.", ChatMessageType.Broadcast));
break;
case "on":
default:
session.Player.SpellComponentsRequired = true;
session.Player.EnqueueBroadcast(new GameMessagePublicUpdatePropertyBool(session.Player, PropertyBool.SpellComponentsRequired, session.Player.SpellComponentsRequired));
session.Network.EnqueueSend(new GameMessageSystemChat("You can no longer cast spells without components.", ChatMessageType.Broadcast));
break;
}
}
///
/// Enables / disables spell component burning
///
[CommandHandler("safecomps", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0, "Enables / disables spell component burning", "")]
public static void HandleSafeComps(Session session, params string[] parameters)
{
var safeComps = true;
if (parameters.Length > 0 && parameters[0].ToLower().Equals("off"))
safeComps = false;
session.Player.SafeSpellComponents = safeComps;
if (safeComps)
session.Network.EnqueueSend(new GameMessageSystemChat("Your spell components are now safe, and will not be consumed when casting spells.", ChatMessageType.Broadcast));
else
session.Network.EnqueueSend(new GameMessageSystemChat("Your spell components will now be consumed when casting spells.", ChatMessageType.Broadcast));
}
///
/// This is to add spells to items (whether loot or quest generated). For making weapons to check damage from pcaps or other sources
///
[CommandHandler("additemspell", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Adds a spell to the last appraised item's spellbook.", "")]
public static void HandleAddItemSpell(Session session, params string[] parameters)
{
var obj = CommandHandlerHelper.GetLastAppraisedObject(session);
if (obj == null)
return;
if (!Enum.TryParse(parameters[0], true, out SpellId spellId))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"{parameters[0]} is not a valid spell id", ChatMessageType.Broadcast));
return;
}
// ensure valid spell id
var spell = new Spell(spellId);
if (spell.NotFound)
{
session.Network.EnqueueSend(new GameMessageSystemChat("SpellID is not found", ChatMessageType.Broadcast));
return;
}
obj.Biota.GetOrAddKnownSpell((int)spellId, obj.BiotaDatabaseLock, out var spellAdded);
var msg = spellAdded ? "added to" : "already on";
session.Network.EnqueueSend(new GameMessageSystemChat($"{spell.Name} ({spell.Id}) {msg} {obj.Name}", ChatMessageType.Broadcast));
}
[CommandHandler("removeitemspell", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Removes a spell to the last appraised item's spellbook.", "")]
public static void HandleRemoveItemSpell(Session session, params string[] parameters)
{
var obj = CommandHandlerHelper.GetLastAppraisedObject(session);
if (obj == null)
return;
if (!Enum.TryParse(parameters[0], true, out SpellId spellId))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"{parameters[0]} is not a valid spell id", ChatMessageType.Broadcast));
return;
}
// ensure valid spell id
var spell = new Spell(spellId);
if (spell.NotFound)
{
session.Network.EnqueueSend(new GameMessageSystemChat("SpellID is not found", ChatMessageType.Broadcast));
return;
}
var spellRemoved = obj.Biota.TryRemoveKnownSpell((int)spellId, obj.BiotaDatabaseLock);
var msg = spellRemoved ? "removed from" : "not found on";
session.Network.EnqueueSend(new GameMessageSystemChat($"{spell.Name} ({spell.Id}) {msg} {obj.Name}", ChatMessageType.Broadcast));
}
[CommandHandler("pktimer", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Sets your PK timer to the current time")]
public static void HandlePKTimer(Session session, params string[] parameters)
{
session.Player.UpdatePKTimer();
session.Network.EnqueueSend(new GameMessageSystemChat($"Updated PK timer", ChatMessageType.Broadcast));
}
[CommandHandler("fellow-info", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows debug info for fellowships.")]
public static void HandleFellowInfo(Session session, params string[] parameters)
{
var player = CommandHandlerHelper.GetLastAppraisedObject(session) as Player;
if (player == null)
player = session.Player;
var fellowship = player.Fellowship;
if (fellowship == null)
{
session.Network.EnqueueSend(new GameMessageSystemChat("Player target must be in a fellowship to use this command.", ChatMessageType.Broadcast));
return;
}
var fellows = fellowship.GetFellowshipMembers();
//var levelSum = fellows.Values.Select(f => f.Level.Value).Sum();
var levelXPSum = fellows.Values.Select(f => f.GetXPToNextLevel(f.Level.Value)).Sum();
// this should match up with the client
foreach (var fellow in fellows.Values.OrderBy(f => f.Level))
{
//var levelScale = (double)fellow.Level.Value / levelSum;
var levelXPScale = (double)fellow.GetXPToNextLevel(fellow.Level.Value) / levelXPSum;
//session.Network.EnqueueSend(new GameMessageSystemChat($"{fellow.Name}: {Math.Round(levelScale * 100, 2)}% / {Math.Round(levelXPScale * 100, 2)}%", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"{fellow.Name}: {Math.Round(levelXPScale * 100, 2)}%", ChatMessageType.Broadcast));
}
session.Network.EnqueueSend(new GameMessageSystemChat($"----------", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"ShareXP: {fellowship.ShareXP}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"EvenShare: {fellowship.EvenShare}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"Distance scale:", ChatMessageType.Broadcast));
foreach (var fellow in fellows.Values)
{
var dist = player.Location.Distance2D(fellow.Location);
var distanceScalar = fellowship.GetDistanceScalar(player, fellow, XpType.Kill);
session.Network.EnqueueSend(new GameMessageSystemChat($"{fellow.Name}: {Math.Round(dist):N0} ({distanceScalar:F2}) - {fellow.Location}", ChatMessageType.Broadcast));
}
}
[CommandHandler("fellow-dist", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows distance to each fellowship member")]
public static void HandleFellowDist(Session session, params string[] parameters)
{
var player = session.Player;
var fellowship = player.Fellowship;
if (fellowship == null)
{
session.Network.EnqueueSend(new GameMessageSystemChat("You must be in a fellowship to use this command.", ChatMessageType.Broadcast));
return;
}
var fellows = fellowship.GetFellowshipMembers();
foreach (var fellow in fellows.Values)
{
var dist2d = session.Player.Location.Distance2D(fellow.Location);
var dist3d = session.Player.Location.DistanceTo(fellow.Location);
var scalar = session.Player.Fellowship.GetDistanceScalar(session.Player, fellow, XpType.Kill);
session.Network.EnqueueSend(new GameMessageSystemChat($"{fellow.Name} | 2d: {dist2d:N0} | 3d: {dist3d:N0} | Scalar: {scalar:N0}", ChatMessageType.Broadcast));
}
}
[CommandHandler("generatordump", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0,
"Lists all properties for the last generator you examined.",
"")]
public static void HandleGeneratorDump(Session session, params string[] parameters)
{
// TODO: output
var objectId = new ObjectGuid();
if (session.Player.HealthQueryTarget.HasValue || session.Player.ManaQueryTarget.HasValue || session.Player.CurrentAppraisalTarget.HasValue)
{
if (session.Player.HealthQueryTarget.HasValue)
objectId = new ObjectGuid((uint)session.Player.HealthQueryTarget);
else if (session.Player.ManaQueryTarget.HasValue)
objectId = new ObjectGuid((uint)session.Player.ManaQueryTarget);
else
objectId = new ObjectGuid((uint)session.Player.CurrentAppraisalTarget);
var wo = session.Player.CurrentLandblock?.GetObject(objectId);
if (wo == null)
return;
if (objectId.IsPlayer())
return;
var msg = "";
if (wo.IsGenerator)
{
msg = $"Generator Dump for {wo.Name} (0x{wo.Guid.ToString()})\n";
msg += $"Generator WCID: {wo.WeenieClassId}\n";
msg += $"Generator WeenieClassName: {wo.WeenieClassName}\n";
msg += $"Generator WeenieType: {wo.WeenieType.ToString()}\n";
msg += $"Generator Status: {(wo.GeneratorDisabled ? "Disabled" : "Enabled")}\n";
msg += $"GeneratorType: {wo.GeneratorType.ToString()}\n";
msg += $"GeneratorTimeType: {wo.GeneratorTimeType.ToString()}\n";
if (wo.GeneratorTimeType == GeneratorTimeType.Event)
msg += $"GeneratorEvent: {(!string.IsNullOrWhiteSpace(wo.GeneratorEvent) ? wo.GeneratorEvent : "Undef")}\n";
if (wo.GeneratorTimeType == GeneratorTimeType.RealTime)
{
msg += $"GeneratorStartTime: {wo.GeneratorStartTime} ({Time.GetDateTimeFromTimestamp(wo.GeneratorStartTime).ToLocalTime().ToCommonString()})\n";
msg += $"GeneratorEndTime: {wo.GeneratorEndTime} ({Time.GetDateTimeFromTimestamp(wo.GeneratorEndTime).ToLocalTime().ToCommonString()})\n";
}
msg += $"GeneratorEndDestructionType: {wo.GeneratorEndDestructionType.ToString()}\n";
msg += $"GeneratorDestructionType: {wo.GeneratorDestructionType.ToString()}\n";
msg += $"GeneratorRadius: {wo.GetProperty(PropertyFloat.GeneratorRadius) ?? 0f}\n";
msg += $"InitGeneratedObjects: {wo.InitGeneratedObjects}\n";
msg += $"MaxGeneratedObjects: {wo.MaxGeneratedObjects}\n";
msg += $"GeneratorInitialDelay: {wo.GeneratorInitialDelay}\n";
msg += $"RegenerationInterval: {wo.RegenerationInterval}\n";
msg += $"GeneratorUpdateTimestamp: {wo.GeneratorUpdateTimestamp} ({Time.GetDateTimeFromTimestamp(wo.GeneratorUpdateTimestamp).ToLocalTime().ToCommonString()})\n";
msg += $"NextGeneratorUpdateTime: {wo.NextGeneratorUpdateTime} ({((wo.NextGeneratorUpdateTime == double.MaxValue) ? "Disabled" : Time.GetDateTimeFromTimestamp(wo.NextGeneratorUpdateTime).ToLocalTime().ToCommonString())})\n";
msg += $"RegenerationTimestamp: {wo.RegenerationTimestamp} ({Time.GetDateTimeFromTimestamp(wo.RegenerationTimestamp).ToLocalTime().ToCommonString()})\n";
msg += $"NextGeneratorRegenerationTime: {wo.NextGeneratorRegenerationTime} ({((wo.NextGeneratorRegenerationTime == double.MaxValue) ? "On Demand" : Time.GetDateTimeFromTimestamp(wo.NextGeneratorRegenerationTime).ToLocalTime().ToCommonString())})\n";
msg += $"GeneratorProfiles.Count: {wo.GeneratorProfiles.Count(g => !g.IsPlaceholder)}\n";
msg += $"GeneratorActiveProfiles.Count: {wo.GeneratorActiveProfiles.Count}\n";
msg += $"CurrentCreate: {wo.CurrentCreate}\n";
msg += $"===============================================\n";
foreach (var activeProfile in wo.GeneratorActiveProfiles)
{
var profile = wo.GeneratorProfiles[activeProfile];
msg += $"Active GeneratorProfile id: {activeProfile} | LinkId: {profile.LinkId}\n";
msg += $"Probability: {profile.Biota.Probability} | WCID: {profile.Biota.WeenieClassId} | Delay: {profile.Biota.Delay} | Init: {profile.Biota.InitCreate} | Max: {profile.Biota.MaxCreate}\n";
msg += $"WhenCreate: {profile.Biota.WhenCreate.ToString()} | WhereCreate: {profile.Biota.WhereCreate.ToString()}\n";
msg += $"StackSize: {profile.Biota.StackSize} | PaletteId: {profile.Biota.PaletteId} | Shade: {profile.Biota.Shade}\n";
msg += $"CurrentCreate: {profile.CurrentCreate} | Spawned.Count: {profile.Spawned.Count} | SpawnQueue.Count: {profile.SpawnQueue.Count}\n";
msg += $"GeneratedTreasureItem: {profile.GeneratedTreasureItem}\n";
msg += $"IsMaxed: {profile.IsMaxed}\n";
if (!profile.IsMaxed)
msg += $"IsAvailable: {profile.IsAvailable}{(profile.IsAvailable ? "" : $", NextAvailable: {profile.NextAvailable.ToLocalTime().ToCommonString()}")}\n";
msg += $"--====--\n";
if (profile.Spawned.Count > 0)
{
msg += "Spawned Objects:\n";
foreach (var spawn in profile.Spawned.Values)
{
msg += $"0x{spawn.Guid}: {spawn.Name} - {spawn.WeenieClassId} - {spawn.WeenieType}\n";
var spawnWO = spawn.TryGetWorldObject();
if (spawnWO != null)
{
if (spawnWO.Location != null)
msg += $" LOC: {spawnWO.Location.ToLOCString()}\n";
else if (spawnWO.ContainerId == wo.Guid.Full)
msg += $" Contained by Generator\n";
else if (spawnWO.WielderId == wo.Guid.Full)
msg += $" Wielded by Generator\n";
else
msg += $" Location Unknown\n";
}
else
msg += $" LOC: Unknown, WorldObject could not be found\n";
}
msg += $"--====--\n";
}
if (profile.SpawnQueue.Count > 0)
{
msg += "Pending Spawn Times:\n";
foreach (var spawn in profile.SpawnQueue)
{
msg += $"{spawn.ToLocalTime().ToCommonString()}\n";
}
msg += $"--====--\n";
}
msg += $"===============================================\n";
}
}
else
msg = $"{wo.Name} (0x{wo.Guid.ToString()}) is not a generator.";
session.Network.EnqueueSend(new GameMessageSystemChat(msg, ChatMessageType.System));
}
}
[CommandHandler("purchase-house", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Instantly purchase the house for the last appraised covenant crystal.")]
public static void HandlePurchaseHouse(Session session, params string[] parameters)
{
var slumlord = CommandHandlerHelper.GetLastAppraisedObject(session) as SlumLord;
if (slumlord == null)
{
session.Network.EnqueueSend(new GameMessageSystemChat("Couldn't find slumlord", ChatMessageType.Broadcast));
return;
}
session.Player.SetHouseOwner(slumlord);
session.Player.GiveDeed(slumlord);
}
[CommandHandler("barrier-test", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows debug information for house barriers")]
public static void HandleBarrierTest(Session session, params string[] parameters)
{
var cell = session.Player.Location.Cell;
Console.WriteLine($"CurCell: {cell:X8}");
if (session.Player.CurrentLandblock.IsDungeon)
{
Console.WriteLine($"Dungeon landblock");
if (!HouseManager.ApartmentBlocks.ContainsKey(session.Player.Location.Landblock))
return;
}
else
{
cell = session.Player.Location.GetOutdoorCell();
Console.WriteLine($"OutdoorCell: {cell:X8}");
}
var barrier = HouseCell.HouseCells.ContainsKey(cell);
Console.WriteLine($"Barrier: {barrier}");
}
[CommandHandler("targetloc", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows the location of the last appraised object")]
public static void HandleTargetLoc(Session session, params string[] parameters)
{
WorldObject wo = null;
if (parameters.Length == 0)
{
wo = CommandHandlerHelper.GetLastAppraisedObject(session);
if (wo == null) return;
}
else
{
if (parameters[0].StartsWith("0x"))
parameters[0] = parameters[0].Substring(2);
if (!uint.TryParse(parameters[0], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint guid))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"{parameters[0]} is not a valid guid", ChatMessageType.Broadcast));
return;
}
wo = session.Player.CurrentLandblock?.GetObject(guid);
if (wo == null)
wo = ServerObjectManager.GetObjectA(guid)?.WeenieObj?.WorldObject;
if (wo == null)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find {parameters[0]}", ChatMessageType.Broadcast));
return;
}
}
session.Network.EnqueueSend(new GameMessageSystemChat($"CurrentLandblock: 0x{wo.CurrentLandblock?.Id.Landblock:X4}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"Location: {wo.Location?.ToLOCString()}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"Physics : {wo.PhysicsObj?.Position}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"CurCell: 0x{wo.PhysicsObj?.CurCell?.ID:X8}", ChatMessageType.Broadcast));
}
[CommandHandler("damagehistory", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld)]
public static void HandleDamageHistory(Session session, params string[] parameters)
{
session.Network.EnqueueSend(new GameMessageSystemChat(session.Player.DamageHistory.ToString(), ChatMessageType.Broadcast));
}
[CommandHandler("remove-vitae", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Removes vitae from last appraised player")]
public static void HandleRemoveVitae(Session session, params string[] parameters)
{
var player = CommandHandlerHelper.GetLastAppraisedObject(session) as Player;
if (player == null)
player = session.Player;
player.EnchantmentManager.RemoveVitae();
if (player != session.Player)
session.Network.EnqueueSend(new GameMessageSystemChat($"Removed vitae for {player.Name}", ChatMessageType.Broadcast));
}
[CommandHandler("fast", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld)]
public static void HandleFast(Session session, params string[] parameters)
{
var spell = new Spell(SpellId.QuicknessSelf8);
session.Player.CreateEnchantment(session.Player, session.Player, null, spell);
spell = new Spell(SpellId.SprintSelf8);
session.Player.CreateEnchantment(session.Player, session.Player, null, spell);
spell = new Spell(SpellId.StrengthSelf8);
session.Player.CreateEnchantment(session.Player, session.Player, null, spell);
}
[CommandHandler("slow", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld)]
public static void HandleSlow(Session session, params string[] parameters)
{
var spell = new Spell(SpellId.SlownessSelf8);
session.Player.CreateEnchantment(session.Player, session.Player, null, spell);
spell = new Spell(SpellId.LeadenFeetSelf8);
session.Player.CreateEnchantment(session.Player, session.Player, null, spell);
spell = new Spell(SpellId.WeaknessSelf8);
session.Player.CreateEnchantment(session.Player, session.Player, null, spell);
}
[CommandHandler("rip", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld)]
public static void HandleRip(Session session, params string[] parameters)
{
// insta-death, without the confirmation dialog from /die
// useful during developer testing
session.Player.TakeDamage(session.Player, DamageType.Bludgeon, session.Player.Health.Current);
}
public static List ResistProperties = new List()
{
PropertyFloat.ResistSlash,
PropertyFloat.ResistPierce,
PropertyFloat.ResistBludgeon,
PropertyFloat.ResistFire,
PropertyFloat.ResistCold,
PropertyFloat.ResistAcid,
PropertyFloat.ResistElectric,
PropertyFloat.ResistNether
};
[CommandHandler("resist-info", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows the resistance info for the last appraised creature.")]
public static void HandleResistInfo(Session session, params string[] parameters)
{
var creature = CommandHandlerHelper.GetLastAppraisedObject(session) as Creature;
if (creature == null)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"You must appraise a creature to use this command.", ChatMessageType.Broadcast));
return;
}
session.Network.EnqueueSend(new GameMessageSystemChat($"{creature.Name} ({creature.Guid}):", ChatMessageType.Broadcast));
var resistInfo = new Dictionary();
foreach (var prop in ResistProperties)
resistInfo.Add(prop, creature.GetProperty(prop));
foreach (var kvp in resistInfo.OrderByDescending(i => i.Value))
session.Network.EnqueueSend(new GameMessageSystemChat($"{kvp.Key} - {kvp.Value}", ChatMessageType.Broadcast));
}
[CommandHandler("debugspell", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Toggles spell projectile debugging info")]
public static void HandleDebugSpell(Session session, params string[] parameters)
{
if (parameters.Length == 0)
{
session.Player.DebugSpell = !session.Player.DebugSpell;
}
else
{
if (parameters[0].Equals("on", StringComparison.OrdinalIgnoreCase))
session.Player.DebugSpell = true;
else
session.Player.DebugSpell = false;
}
session.Network.EnqueueSend(new GameMessageSystemChat($"Spell projectile debugging is {(session.Player.DebugSpell ? "enabled" : "disabled")}", ChatMessageType.Broadcast));
}
[CommandHandler("recordcast", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Records spell casting keypresses to server for debugging")]
public static void HandleRecordCast(Session session, params string[] parameters)
{
if (parameters.Length == 0)
{
session.Player.RecordCast.Enabled = !session.Player.RecordCast.Enabled;
}
else
{
if (parameters[0].Equals("on", StringComparison.OrdinalIgnoreCase))
session.Player.RecordCast.Enabled = true;
else
session.Player.RecordCast.Enabled = false;
}
session.Network.EnqueueSend(new GameMessageSystemChat($"Record cast {(session.Player.RecordCast.Enabled ? "enabled" : "disabled")}", ChatMessageType.Broadcast));
}
[CommandHandler("pscript", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1)]
public static void HandlePScript(Session session, params string[] parameters)
{
var wo = CommandHandlerHelper.GetLastAppraisedObject(session);
if (wo == null) return;
if (!Enum.TryParse(typeof(PlayScript), parameters[0], true, out var pscript))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find PlayScript.{parameters[0]}", ChatMessageType.Broadcast));
return;
}
wo.EnqueueBroadcast(new GameMessageScript(wo.Guid, (PlayScript)pscript));
}
[CommandHandler("getinfo", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows basic info for the last appraised object.")]
public static void HandleGetInfo(Session session, params string[] parameters)
{
var wo = CommandHandlerHelper.GetLastAppraisedObject(session);
if (wo != null)
session.Network.EnqueueSend(new GameMessageSystemChat($"GUID: {wo.Guid}\nWeenieClassId: {wo.WeenieClassId}\nWeenieClassName: {wo.WeenieClassName}", ChatMessageType.Broadcast));
}
public static WorldObject LastTestAim;
[CommandHandler("testaim", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Tests the aim high/low motions, and projectile spawn position")]
public static void HandleTestAim(Session session, params string[] parameters)
{
var motionStr = parameters[0];
if (!motionStr.StartsWith("Aim", StringComparison.OrdinalIgnoreCase))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Motion must start with Aim!", ChatMessageType.Broadcast));
return;
}
if (!Enum.TryParse(motionStr, true, out MotionCommand motionCommand))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Couldn't find MotionCommand {motionStr}", ChatMessageType.Broadcast));
return;
}
var positive = motionCommand >= MotionCommand.AimHigh15 && motionCommand <= MotionCommand.AimHigh90;
if (LastTestAim != null)
LastTestAim.Destroy();
var motion = new Motion(session.Player, motionCommand);
session.Player.EnqueueBroadcastMotion(motion);
// spawn ethereal arrow w/ no velocity or gravity
var localOrigin = session.Player.GetProjectileSpawnOrigin(300, motionCommand);
var globalOrigin = session.Player.Location.Pos + Vector3.Transform(localOrigin, session.Player.Location.Rotation);
var wo = WorldObjectFactory.CreateNewWorldObject(300);
wo.Ethereal = true;
wo.GravityStatus = false;
var angle = motionCommand.GetAimAngle().ToRadians();
var zRotation = Quaternion.CreateFromAxisAngle(Vector3.UnitX, angle);
wo.Location = new Position(session.Player.Location);
wo.Location.Pos = globalOrigin;
wo.Location.Rotation *= zRotation;
session.Player.CurrentLandblock.AddWorldObject(wo);
LastTestAim = wo;
}
[CommandHandler("reload-landblock", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Reloads the current landblock.")]
public static void HandleReloadLandblocks(Session session, params string[] parameters)
{
var landblock = session.Player.CurrentLandblock;
var landblockId = landblock.Id.Raw | 0xFFFF;
session.Network.EnqueueSend(new GameMessageSystemChat($"Reloading 0x{landblockId:X8}", ChatMessageType.Broadcast));
// destroy all non-player server objects
landblock.DestroyAllNonPlayerObjects();
// clear landblock cache
DatabaseManager.World.ClearCachedInstancesByLandblock(landblock.Id.Landblock);
// reload landblock
var actionChain = new ActionChain();
actionChain.AddDelayForOneTick();
actionChain.AddAction(session.Player, () =>
{
landblock.Init(true);
});
actionChain.EnqueueChain();
}
[CommandHandler("showvelocity", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows the velocity of the last appraised object.")]
public static void HandleShowVelocity(Session session, params string[] parameters)
{
var obj = CommandHandlerHelper.GetLastAppraisedObject(session);
if (obj?.PhysicsObj == null)
return;
session.Network.EnqueueSend(new GameMessageSystemChat($"Velocity: {obj.Velocity}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"Physics.Velocity: {obj.PhysicsObj.Velocity}", ChatMessageType.Broadcast));
session.Network.EnqueueSend(new GameMessageSystemChat($"CachedVelocity: {obj.PhysicsObj.CachedVelocity}", ChatMessageType.Broadcast));
}
[CommandHandler("bumpvelocity", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Bumps the velocity of the last appraised object.")]
public static void HandleBumpVelocity(Session session, params string[] parameters)
{
var obj = CommandHandlerHelper.GetLastAppraisedObject(session);
if (obj?.PhysicsObj == null)
return;
var velocity = new Vector3(0, 0, 0.5f);
obj.PhysicsObj.Velocity = velocity;
session.Network.EnqueueSend(new GameMessageVectorUpdate(obj));
}
[CommandHandler("check-collision", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Checks if the player is currently colliding with any other objects.")]
public static void HandleCheckEthereal(Session session, params string[] parameters)
{
var colliding = session.Player.PhysicsObj.ethereal_check_for_collisions();
session.Network.EnqueueSend(new GameMessageSystemChat($"IsColliding: {colliding}", ChatMessageType.Broadcast));
}
// faction
[CommandHandler("faction", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0,
"sets your own faction state.",
"< none / ch / ew / rb > (rank)\n" +
"This command sets your current faction state\n" +
"< none > No Faction\n" +
"< ch > Celestial Hand\n" +
"< ew > Eldrytch Web\n" +
"< rb > Radiant Blood\n" +
"(rank) 1 = Initiate | 2 = Adept | 3 = Knight | 4 = Lord | 5 = Master")]
public static void HandleFaction(Session session, params string[] parameters)
{
var rankStr = "Initiate";
if (parameters.Length == 0)
{
var message = $"Your current Faction state is: {session.Player.Society.ToSentence()}\n"
+ "You can change it to the following:\n"
+ "NONE = No Faction\n"
+ "CH = Celestial Hand\n"
+ "EW = Eldrytch Web\n"
+ "RB = Radiant Blood\n"
+ "Optionally you can also include a rank, otherwise rank will be set to Initiate\n1 = Initiate | 2 = Adept | 3 = Knight | 4 = Lord | 5 = Master";
CommandHandlerHelper.WriteOutputInfo(session, message, ChatMessageType.Broadcast);
}
else
{
switch (parameters[0].ToLower())
{
case "none":
session.Player.Faction1Bits = null;
session.Player.SocietyRankCelhan = null;
session.Player.SocietyRankEldweb = null;
session.Player.SocietyRankRadblo = null;
session.Player.QuestManager.Erase("SocietyMember");
session.Player.QuestManager.Erase("SocietyFlag");
session.Player.QuestManager.Erase("CelestialHandMember");
session.Player.QuestManager.Erase("EldrytchWebMember");
session.Player.QuestManager.Erase("RadiantBloodMember");
break;
case "ch":
session.Player.Faction1Bits = FactionBits.CelestialHand;
session.Player.SocietyRankCelhan = 1;
session.Player.SocietyRankEldweb = null;
session.Player.SocietyRankRadblo = null;
session.Player.QuestManager.SetQuestBits("SocietyMember", (int)FactionBits.CelestialHand, true);
session.Player.QuestManager.SetQuestBits("SocietyFlag", (int)FactionBits.CelestialHand, true);
session.Player.QuestManager.SetQuestBits("SocietyMember", (int)(FactionBits.EldrytchWeb | FactionBits.RadiantBlood), false);
session.Player.QuestManager.SetQuestBits("SocietyFlag", (int)(FactionBits.EldrytchWeb | FactionBits.RadiantBlood), false);
session.Player.QuestManager.Stamp("CelestialHandMember");
session.Player.QuestManager.Erase("EldrytchWebMember");
session.Player.QuestManager.Erase("RadiantBloodMember");
if (parameters.Length == 2 && int.TryParse(parameters[1], out var rank))
{
switch (rank)
{
case 1:
session.Player.SocietyRankCelhan = 1;
rankStr = "Initiate";
break;
case 2:
session.Player.SocietyRankCelhan = 101;
rankStr = "Adept";
break;
case 3:
session.Player.SocietyRankCelhan = 301;
rankStr = "Knight";
break;
case 4:
session.Player.SocietyRankCelhan = 601;
rankStr = "Lord";
break;
case 5:
session.Player.SocietyRankCelhan = 1001;
rankStr = "Master";
break;
}
}
break;
case "ew":
session.Player.Faction1Bits = FactionBits.EldrytchWeb;
session.Player.SocietyRankCelhan = null;
session.Player.SocietyRankEldweb = 1;
session.Player.SocietyRankRadblo = null;
session.Player.QuestManager.SetQuestBits("SocietyMember", (int)FactionBits.EldrytchWeb, true);
session.Player.QuestManager.SetQuestBits("SocietyFlag", (int)FactionBits.EldrytchWeb, true);
session.Player.QuestManager.SetQuestBits("SocietyMember", (int)(FactionBits.CelestialHand | FactionBits.RadiantBlood), false);
session.Player.QuestManager.SetQuestBits("SocietyFlag", (int)(FactionBits.CelestialHand | FactionBits.RadiantBlood), false);
session.Player.QuestManager.Erase("CelestialHandMember");
session.Player.QuestManager.Stamp("EldrytchWebMember");
session.Player.QuestManager.Erase("RadiantBloodMember");
if (parameters.Length == 2 && int.TryParse(parameters[1], out rank))
{
switch (rank)
{
case 1:
session.Player.SocietyRankEldweb = 1;
rankStr = "Initiate";
break;
case 2:
session.Player.SocietyRankEldweb = 101;
rankStr = "Adept";
break;
case 3:
session.Player.SocietyRankEldweb = 301;
rankStr = "Knight";
break;
case 4:
session.Player.SocietyRankEldweb = 601;
rankStr = "Lord";
break;
case 5:
session.Player.SocietyRankEldweb = 1001;
rankStr = "Master";
break;
}
}
break;
case "rb":
session.Player.Faction1Bits = FactionBits.RadiantBlood;
session.Player.SocietyRankCelhan = null;
session.Player.SocietyRankEldweb = null;
session.Player.SocietyRankRadblo = 1;
session.Player.QuestManager.SetQuestBits("SocietyMember", (int)FactionBits.RadiantBlood, true);
session.Player.QuestManager.SetQuestBits("SocietyFlag", (int)FactionBits.RadiantBlood, true);
session.Player.QuestManager.SetQuestBits("SocietyMember", (int)(FactionBits.CelestialHand | FactionBits.EldrytchWeb), false);
session.Player.QuestManager.SetQuestBits("SocietyFlag", (int)(FactionBits.CelestialHand | FactionBits.EldrytchWeb), false);
session.Player.QuestManager.Erase("CelestialHandMember");
session.Player.QuestManager.Erase("EldrytchWebMember");
session.Player.QuestManager.Stamp("RadiantBloodMember");
if (parameters.Length == 2 && int.TryParse(parameters[1], out rank))
{
switch (rank)
{
case 1:
session.Player.SocietyRankRadblo = 1;
rankStr = "Initiate";
break;
case 2:
session.Player.SocietyRankRadblo = 101;
rankStr = "Adept";
break;
case 3:
session.Player.SocietyRankRadblo = 301;
rankStr = "Knight";
break;
case 4:
session.Player.SocietyRankRadblo = 601;
rankStr = "Lord";
break;
case 5:
session.Player.SocietyRankRadblo = 1001;
rankStr = "Master";
break;
}
}
break;
}
session.Player.EnqueueBroadcast(new GameMessagePrivateUpdatePropertyInt(session.Player, PropertyInt.Faction1Bits, (int)(session.Player.Faction1Bits ?? 0)));
session.Player.EnqueueBroadcast(new GameMessagePrivateUpdatePropertyInt(session.Player, PropertyInt.SocietyRankCelhan, session.Player.SocietyRankCelhan ?? 0));
session.Player.EnqueueBroadcast(new GameMessagePrivateUpdatePropertyInt(session.Player, PropertyInt.SocietyRankEldweb, session.Player.SocietyRankEldweb ?? 0));
session.Player.EnqueueBroadcast(new GameMessagePrivateUpdatePropertyInt(session.Player, PropertyInt.SocietyRankRadblo, session.Player.SocietyRankRadblo ?? 0));
session.Player.SendTurbineChatChannels();
CommandHandlerHelper.WriteOutputInfo(session, $"Your current Faction state is now set to: {session.Player.Society.ToSentence()}{(session.Player.Society != FactionBits.None ? $" with a rank of {rankStr}" : "")}", ChatMessageType.Broadcast);
PlayerManager.BroadcastToAuditChannel(session.Player, $"{session.Player.Name} changed their Faction state to {session.Player.Society.ToSentence()}{(session.Player.Society != FactionBits.None ? $" with a rank of {rankStr}" : "")}.");
}
}
///
/// Shows the DeathTreasure tier for the last appraised monster
///
[CommandHandler("showtier", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows the DeathTreasure tier for the last appraised monster")]
public static void HandleShowTier(Session session, params string[] parameters)
{
var creature = CommandHandlerHelper.GetLastAppraisedObject(session) as Creature;
if (creature != null)
{
var msg = creature.DeathTreasure != null ? $"DeathTreasure - Tier: {creature.DeathTreasure.Tier}" : "doesn't have PropertyDataId.DeathTreasureType";
CommandHandlerHelper.WriteOutputInfo(session, $"{creature.Name} ({creature.Guid}) {msg}");
}
}
///
/// Shows a list of monsters for a particular tier #
///
[CommandHandler("tiermobs", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Shows a list of monsters for a particular tier #", "tier")]
public static void HandleTierMobs(Session session, params string[] parameters)
{
if (!uint.TryParse(parameters[0], out var tier))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid tier {parameters[0]}");
return;
}
if (tier < 1 || tier > 8)
{
CommandHandlerHelper.WriteOutputInfo(session, "Please enter a tier between 1-8");
return;
}
using (var ctx = new WorldDbContext())
{
var query = from weenie in ctx.Weenie
join deathTreasure in ctx.WeeniePropertiesDID on weenie.ClassId equals deathTreasure.ObjectId
join treasureDeath in ctx.TreasureDeath on deathTreasure.Value equals treasureDeath.TreasureType
where weenie.Type == (int)WeenieType.Creature && deathTreasure.Type == (ushort)PropertyDataId.DeathTreasureType && treasureDeath.Tier == tier
select weenie;
var results = query.ToList();
CommandHandlerHelper.WriteOutputInfo(session, $"Found {results.Count()} monsters for tier {tier}");
foreach (var result in results)
CommandHandlerHelper.WriteOutputInfo(session, result.ClassName);
}
}
[CommandHandler("delevel", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Attempts to delevel the current player. Requires enough unassigned xp and unspent skill credits.", "new level")]
public static void HandleDelevel(Session session, params string[] parameters)
{
HandleDelevel(session, false, parameters);
}
public static void HandleDelevel(Session session, bool confirmed, params string[] parameters)
{
if (!int.TryParse(parameters[0], out int delevel))
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Invalid level {parameters[0]}", ChatMessageType.Broadcast));
return;
}
if (delevel < 1 || delevel > Player.GetMaxLevel())
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Invalid level {delevel}", ChatMessageType.Broadcast));
return;
}
if (delevel > session.Player.Level)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Delevel # must be less than current level {session.Player.Level}", ChatMessageType.Broadcast));
return;
}
// get amount of unassigned xp required
var currentLevel = session.Player.Level.Value;
var xpBetweenLevels = (long)session.Player.GetXPBetweenLevels(delevel, currentLevel);
var xpIntoCurrentLevel = session.Player.TotalExperience - (long)DatManager.PortalDat.XpTable.CharacterLevelXPList[currentLevel];
var unassignedXPRequired = xpBetweenLevels + xpIntoCurrentLevel;
session.Network.EnqueueSend(new GameMessageSystemChat($"Unassigned XP required: {unassignedXPRequired:N0}", ChatMessageType.Broadcast));
if (session.Player.AvailableExperience < unassignedXPRequired)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"You only have {session.Player.AvailableExperience:N0} unassigned XP -- delevel failed", ChatMessageType.Broadcast));
return;
}
// get # of available skill credits required
var skillCreditsRequired = 0;
for (var i = delevel + 1; i <= currentLevel; i++)
skillCreditsRequired += (int)DatManager.PortalDat.XpTable.CharacterLevelSkillCreditList[i];
session.Network.EnqueueSend(new GameMessageSystemChat($"Skill credits required: {skillCreditsRequired:N0}", ChatMessageType.Broadcast));
if (session.Player.AvailableSkillCredits < skillCreditsRequired)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"You only have {session.Player.AvailableSkillCredits:N0} available skill credits -- delevel failed", ChatMessageType.Broadcast));
return;
}
if (!confirmed)
{
var msg = $"Are you sure you want to delevel {session.Player.Name} to level {delevel}?";
if (!session.Player.ConfirmationManager.EnqueueSend(new Confirmation_Custom(session.Player.Guid, () => HandleDelevel(session, true, parameters)), msg))
session.Player.SendWeenieError(WeenieError.ConfirmationInProgress);
return;
}
session.Network.EnqueueSend(new GameMessageSystemChat($"Deleveling {session.Player.Name} to level {delevel}", ChatMessageType.Broadcast));
var newAvailableExperience = session.Player.AvailableExperience - unassignedXPRequired;
var newTotalExperience = session.Player.TotalExperience - unassignedXPRequired;
var newAvailableSkillCredits = session.Player.AvailableSkillCredits - skillCreditsRequired;
var newTotalSkillCredits = session.Player.TotalSkillCredits - skillCreditsRequired;
session.Player.UpdateProperty(session.Player, PropertyInt64.AvailableExperience, newAvailableExperience);
session.Player.UpdateProperty(session.Player, PropertyInt64.TotalExperience, newTotalExperience);
session.Player.UpdateProperty(session.Player, PropertyInt.AvailableSkillCredits, newAvailableSkillCredits);
session.Player.UpdateProperty(session.Player, PropertyInt.TotalSkillCredits, newTotalSkillCredits);
session.Player.UpdateProperty(session.Player, PropertyInt.Level, delevel);
PlayerManager.BroadcastToAuditChannel(session.Player, $"{session.Player.Name} has deleveled themselves from {currentLevel} to {session.Player.Level} - unassignedXPRequired: {unassignedXPRequired:N0} | skillCreditsRequired: {skillCreditsRequired:N0}");
}
[CommandHandler("monsterspell", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "The last appraised creature casts a spell. For targeted spells, defaults to the current player.", "optional target guid")]
public static void HandleMonsterProj(Session session, params string[] parameters)
{
if (!Enum.TryParse(parameters[0], out SpellId spellId))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid SpellId {spellId}");
return;
}
var spell = new Spell(spellId);
if (spell.NotFound)
{
CommandHandlerHelper.WriteOutputInfo(session, $"Couldn't find SpellId {spellId}");
return;
}
var attackTarget = session.Player as Creature;
if (parameters.Length > 1)
{
if (!uint.TryParse(parameters[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var targetGuid))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid target guid: {parameters[1]}");
return;
}
attackTarget = session.Player.FindObject(targetGuid, Player.SearchLocations.Landblock) as Creature;
if (attackTarget == null)
{
CommandHandlerHelper.WriteOutputInfo(session, $"Couldn't find attack target {targetGuid:X8}");
return;
}
}
var monster = CommandHandlerHelper.GetLastAppraisedObject(session) as Creature;
if (monster == null)
return;
var prevAttackTarget = monster.AttackTarget;
monster.AttackTarget = attackTarget;
monster.CastSpell(spell);
monster.AttackTarget = prevAttackTarget;
}
[CommandHandler("debugspellbook", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Shows the spellbook for the last appraised object")]
public static void HandleDebugSpellbook(Session session, params string[] parameters)
{
var creature = CommandHandlerHelper.GetLastAppraisedObject(session) as Creature;
if (creature == null || creature.Biota.PropertiesSpellBook == null)
return;
var lines = new List();
foreach (var entry in creature.Biota.PropertiesSpellBook)
lines.Add($"{(SpellId)entry.Key} - {entry.Value}");
CommandHandlerHelper.WriteOutputInfo(session, string.Join('\n', lines));
}
[CommandHandler("trywield", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 2)]
public static void HandleTryWield(Session session, params string[] parameters)
{
if (!uint.TryParse(parameters[0], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var itemGuid))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid item guid {parameters[0]}", ChatMessageType.Broadcast);
return;
}
var item = session.Player.FindObject(itemGuid, Player.SearchLocations.MyInventory);
if (item == null)
{
CommandHandlerHelper.WriteOutputInfo(session, $"Couldn't find item guid {parameters[0]}", ChatMessageType.Broadcast);
return;
}
if (!Enum.TryParse(parameters[1], out EquipMask equipMask))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid EquipMask {parameters[1]}", ChatMessageType.Broadcast);
return;
}
session.Player.HandleActionGetAndWieldItem(itemGuid, equipMask);
}
[CommandHandler("show-wielded-treasure", AccessLevel.Developer, CommandHandlerFlag.None, 1, "Shows the WieldedTreasure table for a Creature", "wcid")]
public static void HandleShowWieldedTreasure(Session session, params string[] parameters)
{
if (!uint.TryParse(parameters[0], out var wcid))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid wcid {parameters[0]}", ChatMessageType.Broadcast);
return;
}
var creature = WorldObjectFactory.CreateNewWorldObject(wcid) as Creature;
if (creature == null)
{
CommandHandlerHelper.WriteOutputInfo(session, $"Couldn't find weenie {wcid}");
return;
}
if (creature.WieldedTreasure == null)
{
CommandHandlerHelper.WriteOutputInfo(session, $"{creature.Name} ({creature.WeenieClassId}) missing WieldedTreasure");
return;
}
var table = new TreasureWieldedTable(creature.WieldedTreasure);
foreach (var set in table.Sets)
OutputWieldedTreasureSet(session, set);
}
private static void OutputWieldedTreasureSet(Session session, TreasureWieldedSet set, int depth = 0)
{
var prefix = new string(' ', depth * 2);
var totalProbability = 0.0f;
var spacer = false;
foreach (var item in set.Items)
{
if (totalProbability >= 1.0f)
{
totalProbability = 0.0f;
//spacer = true;
}
totalProbability += item.Item.Probability;
var wo = WorldObjectFactory.CreateNewWorldObject(item.Item.WeenieClassId);
var itemName = wo?.Name ?? "Unknown";
if (spacer)
{
CommandHandlerHelper.WriteOutputInfo(session, "");
spacer = false;
}
CommandHandlerHelper.WriteOutputInfo(session, $"{prefix}- {item.Item.WeenieClassId} - {itemName} ({item.Item.Probability * 100}%)");
if (item.Subset != null)
{
OutputWieldedTreasureSet(session, item.Subset, depth + 1);
//spacer = true;
}
}
}
private static readonly Dictionary AetheriaWcids = new Dictionary()
{
{ AetheriaColor.Blue, Aetheria.AetheriaBlue },
{ AetheriaColor.Yellow, Aetheria.AetheriaYellow },
{ AetheriaColor.Red, Aetheria.AetheriaRed }
};
private static readonly Dictionary SurgeSpells = new Dictionary()
{
{ Surge.Destruction, SpellId.AetheriaProcDamageBoost },
{ Surge.Protection, SpellId.AetheriaProcDamageReduction },
{ Surge.Regeneration, SpellId.AetheriaProcHealthOverTime },
{ Surge.Affliction, SpellId.AetheriaProcDamageOverTime },
{ Surge.Festering, SpellId.AetheriaProcHealDebuff },
};
[CommandHandler("ciaetheria", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 4, "Spawns an Aetheria in the player's inventory", "[color] [set] [surge] [level]" +
"\nColor: Blue, Yellow, Red" +
"\nSet: Defense, Destruction, Fury, Growth, Vigor" +
"\nSurge: Destruction, Protection, Regeneration, Affliction, Festering" +
"\nLevel: 1 - 5")]
public static void HandleCIAetheria(Session session, params string[] parameters)
{
if (!Enum.TryParse(parameters[0], true, out AetheriaColor color))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid color: {parameters[0]}", ChatMessageType.Broadcast);
CommandHandlerHelper.WriteOutputInfo(session, $"Available colors: Blue, Yellow, Red", ChatMessageType.Broadcast);
return;
}
if (!Enum.TryParse(parameters[1], true, out Sigil set))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid set: {parameters[1]}", ChatMessageType.Broadcast);
CommandHandlerHelper.WriteOutputInfo(session, $"Available sets: Defense, Destruction, Fury, Growth, Vigor", ChatMessageType.Broadcast);
return;
}
if (!Enum.TryParse(parameters[2], true, out Surge surgeSpell))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid surge spell: {parameters[2]}", ChatMessageType.Broadcast);
CommandHandlerHelper.WriteOutputInfo(session, $"Available surge spells: Destruction, Protection, Regeneration, Affliction, Festering", ChatMessageType.Broadcast);
return;
}
if (!int.TryParse(parameters[3], out var maxLevel) || maxLevel < 1 || maxLevel > 5)
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid level: {parameters[3]}", ChatMessageType.Broadcast);
CommandHandlerHelper.WriteOutputInfo(session, $"Available levels: 1 - 5", ChatMessageType.Broadcast);
return;
}
var wcid = AetheriaWcids[color];
var wo = WorldObjectFactory.CreateNewWorldObject(wcid) as Gem;
if (wo == null)
{
CommandHandlerHelper.WriteOutputInfo(session, $"Failed to create Aetheria wcid", ChatMessageType.Broadcast);
return;
}
wo.ItemMaxLevel = maxLevel;
wo.IconOverlayId = LootGenerationFactory.IconOverlay_ItemMaxLevel[wo.ItemMaxLevel.Value - 1];
wo.EquipmentSetId = Aetheria.SigilToEquipmentSet[set];
wo.IconId = Aetheria.Icons[color][set];
var procSpell = SurgeSpells[surgeSpell];
wo.ProcSpell = (uint)procSpell;
if (Aetheria.SurgeTargetSelf[procSpell])
wo.ProcSpellSelfTargeted = true;
wo.ValidLocations = Aetheria.ColorToMask[color];
wo.ItemTotalXp = (long)ExperienceSystem.ItemLevelToTotalXP(wo.ItemMaxLevel.Value, (ulong)wo.ItemBaseXp, wo.ItemMaxLevel.Value, wo.ItemXpStyle.Value);
if (!session.Player.TryCreateInInventoryWithNetworking(wo))
CommandHandlerHelper.WriteOutputInfo(session, $"Failed to add Aetheria item to player inventory", ChatMessageType.Broadcast);
}
[CommandHandler("vendordump", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 0,
"Lists all properties for the last vendor you examined.",
"")]
public static void HandleVendorDump(Session session, params string[] parameters)
{
var objectId = new ObjectGuid();
if (session.Player.HealthQueryTarget.HasValue || session.Player.ManaQueryTarget.HasValue || session.Player.CurrentAppraisalTarget.HasValue)
{
if (session.Player.HealthQueryTarget.HasValue)
objectId = new ObjectGuid((uint)session.Player.HealthQueryTarget);
else if (session.Player.ManaQueryTarget.HasValue)
objectId = new ObjectGuid((uint)session.Player.ManaQueryTarget);
else
objectId = new ObjectGuid((uint)session.Player.CurrentAppraisalTarget);
var wo = session.Player.CurrentLandblock?.GetObject(objectId);
if (wo == null)
{
session.Network.EnqueueSend(new GameMessageSystemChat($"Unable to find 0x{objectId:X8}", ChatMessageType.System));
return;
}
if (objectId.IsPlayer())
{
session.Network.EnqueueSend(new GameMessageSystemChat($"{wo.Name} (0x{wo.Guid}) is not a vendor.", ChatMessageType.System));
return;
}
var all = false;
var summary = false;
var createList = false;
var uniques = false;
if (parameters.Length > 0)
{
var args = string.Join(" ", parameters);
if (args.Contains("all", StringComparison.OrdinalIgnoreCase))
all = true;
if (args.Contains("summary", StringComparison.OrdinalIgnoreCase))
summary = true;
if (args.Contains("createList", StringComparison.OrdinalIgnoreCase))
createList = true;
if (args.Contains("uniques", StringComparison.OrdinalIgnoreCase))
uniques = true;
if (!all && !summary && !createList && !uniques)
all = true;
}
else
all = true;
var msg = "";
if (wo is Vendor vendor)
{
var currencyWCID = vendor.AlternateCurrency ?? (uint)ACE.Entity.Enum.WeenieClassName.W_COINSTACK_CLASS;
var currencyWeenie = DatabaseManager.World.GetCachedWeenie(currencyWCID);
msg = $"Vendor Dump for {wo.Name} (0x{wo.Guid})\n";
if (all || summary)
{
msg += $"Vendor WCID: {wo.WeenieClassId}\n";
msg += $"Vendor WeenieClassName: {wo.WeenieClassName}\n";
msg += $"Vendor WeenieType: {wo.WeenieType}\n";
msg += $"OpenForBusiness: {vendor.OpenForBusiness}\n";
msg += $"Currency: ";
if (currencyWeenie != null)
{
msg += $"{currencyWeenie.GetPluralName()} (WCID: {currencyWCID}){(vendor.AlternateCurrency.HasValue ? " | AlternateCurrency" : "")}\n";
}
else
{
var errorMsg = $"WCID {currencyWCID}{(vendor.AlternateCurrency.HasValue ? ", which comes from PropertyDataId.AlternateCurrency," : "")} is not found in the database, Vendor has been disabled as a result!\n";
msg += errorMsg;
}
msg += $"BuyPrice: {(vendor.BuyPrice.HasValue ? $"{vendor.BuyPrice:F}" : "NULL")}\n";
msg += $"SellPrice: {(vendor.SellPrice.HasValue ? $"{vendor.SellPrice:F}" : "NULL")}\n";
msg += $"DealMagicalItems: {(vendor.DealMagicalItems.HasValue ? $"{vendor.DealMagicalItems}" : "NULL")}\n";
msg += $"VendorService: {(vendor.VendorService.HasValue ? $"{vendor.VendorService}" : "NULL")}\n";
msg += $"MerchandiseItemTypes: {(vendor.MerchandiseItemTypes.HasValue ? $"{(ItemType)vendor.MerchandiseItemTypes} ({vendor.MerchandiseItemTypes})" : "NULL")}\n";
msg += $"MerchandiseMinValue: {(vendor.MerchandiseMinValue.HasValue ? $"{vendor.MerchandiseMinValue}" : "NULL")}\n";
msg += $"MerchandiseMaxValue: {(vendor.MerchandiseMaxValue.HasValue ? $"{vendor.MerchandiseMaxValue}" : "NULL")}\n";
msg += $"VendorHappyMean: {(vendor.VendorHappyMean.HasValue ? $"{vendor.VendorHappyMean}" : "NULL")}\n";
msg += $"VendorHappyVariance: {(vendor.VendorHappyVariance.HasValue ? $"{vendor.VendorHappyVariance}" : "NULL")}\n";
msg += $"VendorHappyMaxItems: {(vendor.VendorHappyMaxItems.HasValue ? $"{vendor.VendorHappyMaxItems}" : "NULL")}\n";
msg += $"MoneyOutflow: {vendor.MoneyOutflow:N0} {(vendor.MoneyOutflow == 1 ? currencyWeenie.GetName() : currencyWeenie.GetPluralName())}\n";
msg += $"NumItemsBought: {vendor.NumItemsBought:N0}\n";
msg += $"NumItemsSold: {vendor.NumItemsSold:N0}\n";
msg += $"NumServicesSold: {vendor.NumServicesSold:N0}\n";
msg += $"MoneyIncome: {vendor.MoneyIncome:N0} {(vendor.MoneyIncome == 1 ? currencyWeenie.GetName() : currencyWeenie.GetPluralName())}\n";
}
if (all || createList)
{
var createListShop = vendor.Biota.PropertiesCreateList.Where(x => x.DestinationType == DestinationType.Shop);
msg += $"createListShop.Count: {createListShop.Count()}\n";
msg += $"===============================================\n";
foreach (var shopItem in createListShop)
{
var itemWeenie = DatabaseManager.World.GetCachedWeenie(shopItem.WeenieClassId);
if (itemWeenie == null)
msg += $"{shopItem.WeenieClassId} is not in the database, which will be skipped on load, and will not be sold by this vendor.\n";
else
{
msg += $"{itemWeenie.GetName()} ({itemWeenie.WeenieClassId} | {itemWeenie.ClassName} | {itemWeenie.WeenieType})\n";
if (itemWeenie.IsVendorService())
{
var serviceSpell = itemWeenie.GetProperty(PropertyDataId.Spell);
var spell = new Spell(serviceSpell ?? 0);
msg += $"This is a vendor service which casts the following spell on purchaser: {(serviceSpell.HasValue ? $"{spell.Name} ({spell.Id}): {spell.Description}" : "NULL SPELL")}\n";
}
else
msg += $"StackSize: {shopItem.StackSize}{(shopItem.StackSize == -1 ? " (Unlimited)" : " (per single transction)")} | PaletteTemplate: {(PaletteTemplate)shopItem.Palette} ({shopItem.Palette}) | Shade: {shopItem.Shade}\n";
var cost = vendor.GetSellCost(itemWeenie);
msg += $"Cost: {cost:N0} {(cost == 1 ? currencyWeenie.GetName() : currencyWeenie.GetPluralName())}\n";
}
msg += $"===============================================\n";
}
}
if (all || uniques)
{
msg += $"UniqueItemsForSale.Count: {vendor.UniqueItemsForSale.Count}\n";
msg += $"===============================================\n";
foreach (var shopItem in vendor.UniqueItemsForSale.Values)
{
msg += $"{shopItem.Name} (0x{shopItem.Guid} | {shopItem.WeenieClassId} | {shopItem.WeenieClassName} | {shopItem.WeenieType})\n";
msg += $"StackSize: {shopItem.StackSize ?? 1} | PaletteTemplate: {(PaletteTemplate)shopItem.PaletteTemplate} ({shopItem.PaletteTemplate}) | Shade: {shopItem.Shade:F3}\n";
var soldTimestamp = Time.GetDateTimeFromTimestamp(shopItem.SoldTimestamp ?? 0);
msg += $"SoldTimestamp: {soldTimestamp.ToLocalTime().ToCommonString()} ({(shopItem.SoldTimestamp.HasValue ? $"{shopItem.SoldTimestamp}" : "NULL")})\n";
var rotTime = soldTimestamp.AddSeconds(PropertyManager.GetDouble("vendor_unique_rot_time").Item);
msg += $"RotTimestamp: {rotTime.ToLocalTime().ToCommonString()}\n";
var payout = vendor.GetBuyCost(shopItem);
msg += $"Paid: {payout:N0} {(payout == 1 ? currencyWeenie.GetName() : currencyWeenie.GetPluralName())}\n";
var cost = vendor.GetSellCost(shopItem);
msg += $"Cost: {cost:N0} {(cost == 1 ? currencyWeenie.GetName() : currencyWeenie.GetPluralName())}\n";
msg += $"===============================================\n";
}
}
}
else
msg = $"{wo.Name} (0x{wo.Guid}) is not a vendor.";
session.Network.EnqueueSend(new GameMessageSystemChat(msg, ChatMessageType.System));
}
}
[CommandHandler("castspell", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Casts a spell on the last appraised object", "spell id")]
public static void HandleCastSpell(Session session, params string[] parameters)
{
if (!uint.TryParse(parameters[0], out var spellId))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid spell id {parameters[0]}");
return;
}
var spell = new Spell(spellId);
if (spell.NotFound)
{
CommandHandlerHelper.WriteOutputInfo(session, $"Spell {spellId} not found");
return;
}
WorldObject target = null;
if (spell.NonComponentTargetType != ItemType.None)
{
target = CommandHandlerHelper.GetLastAppraisedObject(session);
if (target == null) return;
}
session.Player.TryCastSpell(spell, target, tryResist: false);
}
[CommandHandler("usewith", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Uses specified object on last appraised object", "guid")]
public static void HandleUseWithTarget(Session session, params string[] parameters)
{
uint guid;
if (parameters[0].StartsWith("0x"))
{
string hex = parameters[0][2..];
if (!uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out guid))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid guid {parameters[0]}");
return;
}
}
else
if (!uint.TryParse(parameters[0], out guid))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid guid {parameters[0]}");
return;
}
//var spell = new Spell(spellId);
//if (spell.NotFound)
//{
// CommandHandlerHelper.WriteOutputInfo(session, $"Spell {spellId} not found");
// return;
//}
var target = CommandHandlerHelper.GetLastAppraisedObject(session);
if (target == null) return;
//session.Player.TryCastSpell(spell, target, tryResist: false);
session.Player.HandleActionUseWithTarget(guid, target.Guid.Full);
}
[CommandHandler("portalstorm", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, 1, "Tests starting a portal storm on yourself", "storm_level [0=Brewing, 1=Imminent, 2=Stormed, 3=Subsided]")]
public static void HandlePortalStorm(Session session, params string[] parameters)
{
if (!uint.TryParse(parameters[0], out var storm_level))
{
CommandHandlerHelper.WriteOutputInfo(session, $"Invalid storm level {parameters[0]}");
return;
}
if (storm_level > 3) storm_level = 3;
switch (storm_level) {
case 0:
session.Network.EnqueueSend(new GameEventPortalStormBrewing(session));
break;
case 1:
session.Network.EnqueueSend(new GameEventPortalStormImminent(session));
break;
case 2:
// Portal Storm Event comes immediatley before the teleport
session.Network.EnqueueSend(new GameEventPortalStorm(session));
// We're going to move the player to 0,0
Position newPos = new Position(0x7F7F001C, 84, 84, 80, 0, 0, 0, 1);
session.Player.Teleport(newPos);
break;
case 3:
session.Network.EnqueueSend(new GameEventPortalStormSubsided(session));
break;
}
}
}
}