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; } } } }