ace/Source/ACE.Server/Command/CommandManager.cs

324 lines
13 KiB
C#
Raw Permalink Normal View History

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using ACE.Entity.Enum;
using ACE.Server.Network;
using log4net;
namespace ACE.Server.Command
{
public static class CommandManager
{
private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public static readonly bool NonInteractiveConsole = Convert.ToBoolean(Environment.GetEnvironmentVariable("ACE_NONINTERACTIVE_CONSOLE"));
private static Dictionary<string, CommandHandlerInfo> commandHandlers = new Dictionary<string, CommandHandlerInfo>(StringComparer.OrdinalIgnoreCase);
public static IEnumerable<CommandHandlerInfo> GetCommands()
{
return commandHandlers.Select(p => p.Value);
}
public static IEnumerable<CommandHandlerInfo> GetCommandByName(string commandname)
{
return commandHandlers.Select(p => p.Value).Where(p => p.Attribute.Command == commandname);
}
public static CommandHandler GetDelegate(Action<Session, string[]> handler) => (CommandHandler)Delegate.CreateDelegate(typeof(CommandHandler), handler.Method);
public static bool TryAddCommand(MethodInfo handler, string command, AccessLevel access, CommandHandlerFlag flags = CommandHandlerFlag.None, string description = "", string usage = "", bool overrides = true)
{
var del = (CommandHandler)Delegate.CreateDelegate(typeof(CommandHandler), handler);
var info = new CommandHandlerInfo()
{
Attribute = new CommandHandlerAttribute(command, access, flags, description, usage),
Handler = del,
};
return TryAddCommand(info, overrides);
}
public static bool TryAddCommand(Action<Session, string[]> handler, string command, AccessLevel access, CommandHandlerFlag flags = CommandHandlerFlag.None, string description = "", string usage = "", bool overrides = true)
{
var del = (CommandHandler)Delegate.CreateDelegate(typeof(CommandHandler), handler.Method);
var info = new CommandHandlerInfo()
{
Attribute = new CommandHandlerAttribute(command, access, flags, description, usage),
Handler = del
};
if (TryAddCommand(info, overrides))
return true;
return false;
}
public static bool TryAddCommand(CommandHandlerInfo commandHandler, bool overrides = true)
{
if (commandHandler is null)
return false;
var command = commandHandler.Attribute.Command;
//Add if the command doesn't exist
if (!commandHandlers.ContainsKey(command))
{
commandHandlers.Add(command, commandHandler);
log.Info($"Command created: {command}");
return true;
}
//Update if overriding and the command exists
else if (overrides)
{
log.Info($"Command updated: {command}");
commandHandlers[command] = commandHandler;
return true;
}
log.Warn($"Failed to add command: {command}");
return false;
}
public static bool TryRemoveCommand(string command)
{
if (!commandHandlers.ContainsKey(command))
return false;
log.Info($"Removed command: {command}");
commandHandlers.Remove(command);
return true;
}
public static void Initialize()
{
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
foreach (var method in type.GetMethods())
{
foreach (var attribute in method.GetCustomAttributes<CommandHandlerAttribute>())
{
var commandHandler = new CommandHandlerInfo()
{
2017-03-20 08:05:50 -04:00
Handler = (CommandHandler)Delegate.CreateDelegate(typeof(CommandHandler), method),
Attribute = attribute
};
commandHandlers[attribute.Command] = commandHandler;
}
}
}
if (NonInteractiveConsole)
{
log.Info("ACEmulator command prompt disabled - Environment.GetEnvironmentVariable(ACE_NONINTERACTIVE_CONSOLE) was true");
return;
}
var thread = new Thread(new ThreadStart(CommandThread));
thread.Name = "Command Manager";
thread.IsBackground = true;
thread.Start();
}
private static void CommandThread()
{
Console.WriteLine("");
Console.WriteLine("ACEmulator command prompt ready.");
Console.WriteLine("");
Console.WriteLine("Type \"acecommands\" for help.");
Console.WriteLine("");
for (; ; )
{
Console.Write("ACE >> ");
string commandLine = Console.ReadLine();
if (string.IsNullOrWhiteSpace(commandLine))
continue;
string command = null;
string[] parameters = null;
try
{
ParseCommand(commandLine, out command, out parameters);
}
catch (Exception ex)
{
log.Error($"Exception while parsing command: {commandLine}", ex);
return;
}
try
{
if (GetCommandHandler(null, command, parameters, out var commandHandler) == CommandHandlerResponse.Ok)
{
try
{
if (commandHandler.Attribute.IncludeRaw)
{
parameters = StuffRawIntoParameters(commandLine, command, parameters);
}
// Add command to world manager's main thread...
((CommandHandler)commandHandler.Handler).Invoke(null, parameters);
}
catch (Exception ex)
{
log.Error($"Exception while invoking command handler for: {commandLine}", ex);
}
}
}
catch (Exception ex)
{
log.Error($"Exception while getting command handler for: {commandLine}", ex);
}
}
}
public static string[] StuffRawIntoParameters(string raw, string command, string[] parameters)
{
List<string> parametersRehash = new List<string>();
var regex = new Regex(Regex.Escape(command));
var newCmdLine = regex.Replace(raw, "", 1).TrimStart();
parametersRehash.Add(newCmdLine);
parametersRehash.AddRange(parameters);
parameters = parametersRehash.ToArray();
return parameters;
}
public static void ParseCommand(string commandLine, out string command, out string[] parameters)
{
if (commandLine == "/" || commandLine == "")
{
command = null;
parameters = null;
return;
}
var commandSplit = commandLine.Split(' ', StringSplitOptions.RemoveEmptyEntries);
2017-03-20 08:05:50 -04:00
command = commandSplit[0];
// remove leading '/' or '@' if erroneously entered in console
if (command.StartsWith("/") || command.StartsWith("@"))
command = command.Substring(1);
parameters = new string[commandSplit.Length - 1];
Array.Copy(commandSplit, 1, parameters, 0, commandSplit.Length - 1);
if (commandLine.Contains("\""))
{
var listParameters = new List<string>();
for (int start = 0; start < parameters.Length; start++)
{
if (!parameters[start].StartsWith("\"") || parameters[start].EndsWith("\"")) // Make sure we catch parameters like: "someParam"
listParameters.Add(parameters[start].Replace("\"", ""));
else
{
2017-03-20 08:05:50 -04:00
listParameters.Add(parameters[start].Replace("\"", ""));
for (int end = start + 1; end < parameters.Length; end++)
{
if (!parameters[end].EndsWith("\""))
listParameters[listParameters.Count - 1] += " " + parameters[end];
else
{
listParameters[listParameters.Count - 1] += " " + parameters[end].Replace("\"", "");
start = end;
break;
}
}
}
}
Array.Resize(ref parameters, listParameters.Count);
parameters = listParameters.ToArray();
}
}
public static CommandHandlerResponse GetCommandHandler(Session session, string command, string[] parameters, out CommandHandlerInfo commandInfo)
{
if (command == null || parameters == null)
{
commandInfo = null;
return CommandHandlerResponse.InvalidCommand;
}
2017-02-19 17:19:56 -05:00
bool isSUDOauthorized = false;
if (command.ToLower() == "sudo")
{
string sudoCommand = "";
if (parameters.Length > 0)
sudoCommand = parameters[0];
if (!commandHandlers.TryGetValue(sudoCommand, out commandInfo))
return CommandHandlerResponse.InvalidCommand;
if (session == null)
{
Console.WriteLine("SUDO does not work on the console because you already have full access. Remove SUDO from command and execute again.");
return CommandHandlerResponse.InvalidCommand;
}
2017-02-19 17:19:56 -05:00
if (commandInfo.Attribute.Access <= session.AccessLevel)
isSUDOauthorized = true;
if (isSUDOauthorized)
{
command = sudoCommand;
var sudoParameters = new string[parameters.Length - 1];
2017-02-19 17:19:56 -05:00
for (int i = 1; i < parameters.Length; i++)
sudoParameters[i - 1] = parameters[i];
parameters = sudoParameters;
}
}
if (!commandHandlers.TryGetValue(command, out commandInfo))
{
// Provide some feedback for why the console command failed
if (session == null)
Console.WriteLine($"Invalid Command");
return CommandHandlerResponse.InvalidCommand;
}
if ((commandInfo.Attribute.Flags & CommandHandlerFlag.ConsoleInvoke) != 0 && session != null)
return CommandHandlerResponse.NoConsoleInvoke;
if (session != null)
{
bool isAdvocate = session.Player.IsAdvocate;
bool isSentinel = session.Player.IsSentinel;
bool isEnvoy = session.Player.IsEnvoy;
Object overhaul (#384) * Object overhaul - clean up enums (#337) * Give property enums a static value to aviod positional fubar later on. Cleaned up using. * Cleaning up enums * Fixed Line ending update change log. * Review & Comment only || Object overhaul - POC (#339) * Give property enums a static value to aviod positional fubar later on. Cleaned up using. * Cleaning up enums * Fixed Line ending update change log. * Added new positionType * Started work on implementing object overhaul - want to make sure I am heading in the right direction. * Minor clean up, responded to feedback and fixed a few bugs. Thanks for the second and third set of eyes. * Object Overhaul - ready for review and merge. Still Work in Progress but a good spot. (#341) * Give property enums a static value to aviod positional fubar later on. Cleaned up using. * Cleaning up enums * Fixed Line ending update change log. * Added new positionType * Started work on implementing object overhaul - want to make sure I am heading in the right direction. * Minor clean up, responded to feedback and fixed a few bugs. Thanks for the second and third set of eyes. * Continued work. Please review as these are a LOT of manual changes. Compiles and you can log in. * Continued work. I have all of the items pulling from properties now. Still work to make baseAceObject and AceObject on thing / finish work around workmanship and actually kick the tires and start pulling data. Sorry this is taking so long. * Had time for a few more and cleaned up whitespace. * Good point to check in. All of the property tables are hooked up and loading data. I created the workmanship hackery we discussed. As always comments and suggestions much appreciated. * One quick shot to fix appveyor before bed. * * Continued work on Overhaul * Created mapping cross reference file to faciltate docuatation and cleaning up names - work in progress https://goo.gl/eaaNQb * Created new database schema for world_object - modified mysqlinstall.bat to refect new baseline * created script to ETL old schema to new schema. I have not included it as we will be refactoring the initial data load to use the new * schema according to Ripley. I have the script tested and posted on my dropbox if anyone wants it. I also have a complete data dump that * zips down to a manageable size. * BaseAceObject is now using the new schema * I re-hooked up telepoi to use the new schema. Still to do, convert to constructed statement * Next steps - refactor AceObject and make sure world objects load again. * Once that is complete - start same process using clone of this schema for character as discussed. * renabled more data loading. * Continued work on hooking data back up. Working on moveto debug command - this creates a world object. Not quite there yet. * WIP * Making progress. Test object able to be spwaned from new schema. Still to do, renable constructed statements, clean up and re-enable the other constructed statements. * updated worldbase * * Intital work done for new schema. Rebased and removed update sql files. (#353) * Tested and the auto setting of Physics and Weenie Header Flags looks really solid. Side note, the values we have * in the database for weenieHeaderFlags and PhysicsDescriptionFlag should probably be dropped we don't read them or use them now. * Doors, portals NPC's and signs are all back in the world now. * I would suggest we remearge with the main branch sooner rather than later - we can create another branch to introduce these * changes into the character side. * TODO items: * Creature spawns need to be refactored - I left them commented out. We need to ditch seperate tables that we have now, I think all * creature can use what we have now, plus maybe two additional world tables. Everything else fits into the current schema. * Test Ripley data and update ACE_World. * Refactor portals - I just left the old way in and faked it out with a view. * Once this looks good and stable and we fix any found bugs - we need to clone this over to the character database and start refactoring * That will get us to persisted inventory. * Objects - Game data refactored - issue with character login (#355) * wip * WIP - character login is trying to persist a world object * Still throwing an exception on player login. * Working Code - finished GameData - need to complete PhysicsData and ModelData. Thanks to @Lidefeath for finding my issue. * WIP compiles but has error on run. Data changes have broken something. Will fix tomorrow. * Wip - nothing really working yet. I can log in but item creation is not working. * Objects (#357) * wip * WIP - character login is trying to persist a world object * Still throwing an exception on player login. * Working Code - finished GameData - need to complete PhysicsData and ModelData. Thanks to @Lidefeath for finding my issue. * WIP compiles but has error on run. Data changes have broken something. Will fix tomorrow. * Wip - nothing really working yet. I can log in but item creation is not working. * Best attempt stabbed (#358) * wip 1 * wip 2 * wip 3 * wip 3 * i think thats it for now. good luck! * Read cached characters and launch to char list (#360) * First corrections and commenting out so the start to command line works * The basics for launching to character list is working. * Edit config.json for AppVeyor * Rework Base SQL scripts. (#361) Expect broken execution until views are corrected for ace_shard * Cleaning up error list (#362) Clearing stylecop issues and warnings in Error List * ACE Enum adjustments (#363) * More Enum Adjustments (#364) * More Enum Adjustments * Further Enum Refinement * Changed Enum Value * More Enum and Object Realignment (#365) * Adding Enums to better align with AC standards. GroupTypeChat and AccessLevel will be converted to these. * Object level changes to go with enum resorting * Extend ORM to allow aggregate functions i.e. MAX(column) and (#366) * Extend ORM to allow aggregate functions i.e. MAX(column) and implement GetNextCharacterId * For AppVeyor, thanks @ogmage78 for the sql * Implements IsCharacterNameAvailable and fixes some errors in the chargen code. (#367) * Character Loading (#368) * Cleaned up some whitespace issues. Still understanding the direction. * WIP added enums for PropertyAttribute * Wip got a lot of aceCharacter done - working on position - about 1/2 done. * WIP - Ace Character Loading. * Intital work done for loading character from ace_shard. Rebased and removed update sql files. * Tested up to position loading. Made changes to position, but have not loaded the dictionary yet. * Fixed a few bugs - attribute loading had a few. * TODO items: * Finish up loading positions then see what is left. * Work on saving as OptimShi - has character creation ready to go. * Fixed for rebase and apveyor * Hand wrote view per request and removed unneeded position. * Read character creation values from client_portal.dat (#369) * Read character creation values from client_portal.dat and assign to appropriate Character Properties * Added AceCharacter properties for the character appearance data * More fun in Zombieland - character loading. (#370) * [Og II] * Continued work loading character from ace_shard. Rebased * Added cascade delete to all child tables in ace_shard. * Completed and tested up to skillz loading. Made a few changes on the db side for key consistancy. * TODO items: * Finish up loading - spells, friends, allegiance info, spell comps, spell bars etc. etc.... * Fixed bug in setXXXProperty methods with the add new. Was not setting the AceObjectId * Fixed auto inc and unsealed position class and made aceObjectId virtual again. * SaveObject implemented in ShardDatabase.cs (#371) * First steps of SaveObject * Hopefully I didn't bork anything too hard. * Character creation -- data present on save appears to make its way to the DB. Thanks to Lidefeath for the starter here. * Removed all references to SaveCharacterOptions -- replace with SaveObject(). * Removed all references to SaveCharacterOptions -- replace with SaveObject(). * Cleanup of DbGetListAttribute (#372) * First steps of SaveObject * Hopefully I didn't bork anything too hard. * Character creation -- data present on save appears to make its way to the DB. Thanks to Lidefeath for the starter here. * Removed all references to SaveCharacterOptions -- replace with SaveObject(). * Removed all references to SaveCharacterOptions -- replace with SaveObject(). * Reworked DbGetListAttribute, got rid of hacky code. * Forgot to remove sloppy comments from refactoring. * Create Database.cs * Fix the "You do not own this character" error. (#373) * Refactoring, save/restore progress on object overhaul (#375) * First steps of SaveObject * added todo * Hopefully I didn't bork anything too hard. * Character creation -- data present on save appears to make its way to the DB. Thanks to Lidefeath for the starter here. * Removed all references to SaveCharacterOptions -- replace with SaveObject(). * Removed all references to SaveCharacterOptions -- replace with SaveObject(). * Working on player login. Getting closer. At SendSelf in player.cs * Reworked DbGetListAttribute, got rid of hacky code. * Forgot to remove sloppy comments from refactoring. * Create Database.cs * Continued work on character login. A little closer - we can get into the world now. Please see change log for list of outstanding items. * Can log in, and walk around. Some properties still wrong. Still need to submit several DB level fixes. * Many fixups/bugfixes. Positions now save. Character logs in. Game doesn't crash. Can open multiple sessions. (some) ORM issues fixed Issues: World Objects still don't appear in game. Race found in ShardDatabase (commented) * Minor cleanups. * Object overhaul (#376) * First steps of SaveObject * added todo * Hopefully I didn't bork anything too hard. * Character creation -- data present on save appears to make its way to the DB. Thanks to Lidefeath for the starter here. * Removed all references to SaveCharacterOptions -- replace with SaveObject(). * Removed all references to SaveCharacterOptions -- replace with SaveObject(). * Working on player login. Getting closer. At SendSelf in player.cs * Reworked DbGetListAttribute, got rid of hacky code. * Forgot to remove sloppy comments from refactoring. * Create Database.cs * Continued work on character login. A little closer - we can get into the world now. Please see change log for list of outstanding items. * Can log in, and walk around. Some properties still wrong. Still need to submit several DB level fixes. * Many fixups/bugfixes. Positions now save. Character logs in. Game doesn't crash. Can open multiple sessions. (some) ORM issues fixed Issues: World Objects still don't appear in game. Race found in ShardDatabase (commented) * Properly load Data Id instances. Objects now "spawn" in-world, but don't interact properly. * Fixes to worldbase vw_teleport_location, player description, character handler. Player now gets proper stats and can teleportpoi. Vital inital values are off. * Sets options on load. May be bad/broken. Need to seriously think how AceObject/AceCharacter and WorldObject/Player should interact. We don't do that well now. * Fix to attribute2nd, using uint24 instead of uint16 for value (because monster's health can get huuuge). * Position refactor into backend object AceObjectPropertiesPosition and front-end object Position. * * fixed issues with the weenieHeaderFlags setting method, fixed PhysicsDescriptionFlag setting issue as well. * cleaned up and rebased data scripts for WorldBase and ShardBase * Found issue in my data that was ETL to our new schema - had two flag fields Reversed - that is fixed but would be no issue * once we have Ripley's new data export. I have shared out a link to the cleaned up data for use while we finish the new export. * https://www.dropbox.com/s/pohcruvalt9s38h/WorldandShardData.zip?dl=0 * I put in a nasty hack - hard coded weenieClassId on character save - it was being set to 0 and I could not find it. I marked with with a todo. * player.cs line 490 * Create Landblock.cs * Updated changelog * Fear the Walking Dead - More work on our object overhaul - bug fixes and continued work (#377) * Testing - fixed a few null reference check errors. Added in OptimShi player apperance code. * added back code needed for ci * Sending DataId and InstanceId data to character description. * Forgot to update change log * Added Ripley's changes to scheam * Create ShardBase.sql * Create WorldBase.sql * Made requested changes * Lord BucketHead - temp fix. Added start of equipping items and POC code (#378) * * Added getting and setting ContainerID and Wielder into AceObject and surfaced in worldObject * Put in a hack to show how equiped weapons and shields work. We will need to establish a place to store this data. * I put in big TODO to indentify the hack - it is benign and will not impact anything else other than to demo the placement * of the spear in Rand the Game Hunters hand in Holtburg. * Modified SetPhysicsDescriptionFlags so it can be container and weilder aware. * Worked with OptimShi and we put in a fix for Lord BucketHead. * Found and fixed one more bug with hair styles * Update Player.cs removed comment that was out of date. * SQL Scripts Re-based (#379) * SQL Scripts Rebased * More alignment of base DB script. * Final SQL script rebase * Code cleanup and a few bug fixes. (#380) * Code cleanup and a few bug fixes. Working to trello card. Board updated. * Added first part of wield item. * Update to remove class diagram * Update GameActionGetAndWieldItem.cs * Update GameEventWieldItem.cs * Fixed Position Saving. (#381) * Fixed Position Saving. * Fixed other minor character initialization bugs * Cleanup backing AceObject (#382) * Reworked AceObject/Player/WorldObject to better repsect AceObject as a unit backing. * Enough cleanup to commit. Fixed vitals (Attribute2nd) in the process. * Fixed bad rebase errors. Appears to work now. Can confirm vitals (Attribute2nds) initialize well, and can be raised. * Fixed bug with player creation and made a bunch of comments and notes about things I discovered so they would not get lost. (#383)
2017-06-16 09:54:39 -04:00
bool isArch = session.Player.IsArch;
bool isAdmin = session.Player.IsAdmin;
2017-03-20 08:05:50 -04:00
2017-02-19 17:19:56 -05:00
if (commandInfo.Attribute.Access == AccessLevel.Advocate && !(isAdvocate || isSentinel || isEnvoy || isArch || isAdmin || isSUDOauthorized)
|| commandInfo.Attribute.Access == AccessLevel.Sentinel && !(isSentinel || isEnvoy || isArch || isAdmin || isSUDOauthorized)
|| commandInfo.Attribute.Access == AccessLevel.Envoy && !(isEnvoy || isArch || isAdmin || isSUDOauthorized)
|| commandInfo.Attribute.Access == AccessLevel.Developer && !(isArch || isAdmin || isSUDOauthorized)
|| commandInfo.Attribute.Access == AccessLevel.Admin && !(isAdmin || isSUDOauthorized))
return CommandHandlerResponse.NotAuthorized;
}
if (commandInfo.Attribute.ParameterCount != -1 && parameters.Length < commandInfo.Attribute.ParameterCount)
{
// Provide some feedback for why the console command failed
if (session == null)
Console.WriteLine($"The syntax of the command is incorrect.\nUsage: " + commandInfo.Attribute.Command + " " + commandInfo.Attribute.Usage);
return CommandHandlerResponse.InvalidParameterCount;
}
if ((commandInfo.Attribute.Flags & CommandHandlerFlag.RequiresWorld) != 0 && (session == null || session.Player == null || session.Player.CurrentLandblock == null))
return CommandHandlerResponse.NotInWorld;
2017-02-19 17:19:56 -05:00
if (isSUDOauthorized)
return CommandHandlerResponse.SudoOk;
return CommandHandlerResponse.Ok;
}
}
}