/// <summary>Creates a scripting command from action input.</summary> /// <param name="actionInput">The action input to transform into a ScriptingCommand instance.</param> /// <returns>A new ScriptingCommand instance for the specified input, if found, else null.</returns> public ScriptingCommand Create(ActionInput actionInput) { // TODO: Build targeting into command selection when there are multiple valid targets; IE if multiple // openable things registered an "open" context command, then if the user said "open door" then we // want to select the context command attached to the closest match for "door" as our command; if // there are still multiple targets, we can start a conflict resolution prompt, etc. Individual non- // context commands may also wish to use such targeting code, so it should be built to be reusable. ScriptingCommand command = TryCreateMasterCommand(actionInput, 0) ?? TryCreateMasterCommand(actionInput, 1) ?? TryCreateContextCommand(actionInput, 0) ?? TryCreateContextCommand(actionInput, 1); if (command == null) { actionInput.Controller.Write(new OutputBuilder().AppendLine(unknownCommandResponse)); } return(command); }
/// <summary>Try to execute the specified action input as a command.</summary> /// <param name="actionInput">The action input to try to execute.</param> private void TryExecuteAction(ActionInput actionInput) { try { ScriptingCommand command = CommandCreator.Instance.Create(actionInput); if (command == null) { return; } // Verify the user has permissions to use this command. string guardsErrorMessage = CommandGuardHelpers.VerifyCommandPermission(command); if (guardsErrorMessage == null) { guardsErrorMessage = (string)command.GuardsDelegate.DynamicInvoke(actionInput); } // Verify that the other command-specific guards are passed. if (guardsErrorMessage == null) { // Execute the command if we passed all the guards. command.ExecuteDelegate.DynamicInvoke(actionInput); } else { // Otherwise display what went wrong to the user of the action. IController controller = actionInput.Controller; controller.Write(new OutputBuilder().AppendLine(guardsErrorMessage)); } } catch (Exception ex) { // Most of our exceptions should be TargetInvocationExceptions but we're going to // handle them basically the same way as others, except that we only care about the // inner exception (what actually went wrong, since we know we're doing invokes here). if (ex is TargetInvocationException && ex.InnerException != null) { ex = ex.InnerException; } // In order to isolate command-specific issues, we're going to trap the exception, log // the details, and kill that command. (Other commands and the game itself should be // able to continue through such situations.) // Start gathering info, but carefully to avoid causing potential further exceptions here. IController controller = actionInput.Controller; #if DEBUG Thing thing = controller != null ? controller.Thing : null; string thingName = thing != null ? thing.Name : "[null]"; string thingID = thing != null?thing.Id.ToString() : "[null]"; string fullCommand = actionInput != null ? actionInput.FullText : "[null]"; string format = "Exception encountered for command: {1}{0}From thing: {2} (ID: {3}){0}{4}"; string message = string.Format(format, Environment.NewLine, fullCommand, thingName, thingID, ex.ToDeepString()); #endif // If the debugger is attached, we probably want to break now in order to better debug // the issue closer to where it occurred; if your debugger broke here you may want to // look at the stack trace to see where the exception originated. if (Debugger.IsAttached) { string stackTrace = ex.StackTrace; Debugger.Break(); } // TODO: FIX: this.host..UpdateSubSystemHost(this, message); if (controller != null) { #if DEBUG controller.Write(new OutputBuilder().AppendLine(message)); #else controller.Write(new OutputBuilder().AppendLine("An error occured processing your command.")); #endif } } }
/// <summary>Verifies that the proposed command can be performed by the acting entity.</summary> /// <param name="command">The issued command to be carried out.</param> /// <returns>A string defining why the permission was not given, else null if permission is given.</returns> public static string VerifyCommandPermission(ScriptingCommand command) { if (command.SecurityRole == SecurityRole.none) { // If you are debugging here after trying to set up your own action, // you probably forgot to assign an appropriate [ActionSecurity(...)] // attribute for your new GameAction class. return string.Format("Nobody can use '{0}' right now!", command.Name); } Thing entity = command.ActionInput.Controller.Thing; if (entity == null) { return "You must exist before issuing commands!"; } PlayerBehavior player = entity.Behaviors.FindFirst<PlayerBehavior>(); if (player != null) { // @@@ TODO: Ascertain the ACTUAL player's specific permissions, so we can // check for fullAdmin, fullBuilder, etc, instead of assuming just // 'SecurityRole.player' SecurityRole playerRoles = SecurityRole.player | SecurityRole.minorBuilder | SecurityRole.fullBuilder | SecurityRole.minorAdmin | SecurityRole.fullAdmin; // If any of the command's security roles and the player's security roles // overlap (such as the command is marked with 'minorBuilder' and the // player has the 'minorBuilder' flag) then we permit the command. if ((command.SecurityRole & playerRoles) != SecurityRole.none) { return null; } // Otherwise, this player does not have permission; we do not want to // check the mobile/item/room security role on players, so we're done. return string.Format("You do not have permission to use '{0}' right now.", command.Name); } MobileBehavior mobile = entity.Behaviors.FindFirst<MobileBehavior>(); if (mobile != null) { if ((command.SecurityRole & SecurityRole.mobile) != SecurityRole.none) { return null; } return string.Format("A mobile can not use '{0}' right now!", command.Name); } RoomBehavior room = entity.Behaviors.FindFirst<RoomBehavior>(); if (room != null) { if ((command.SecurityRole & SecurityRole.room) == SecurityRole.none) { return null; } return string.Format("A room can not use '{0}' right now!", command.Name); } // @@@ For now, everything else which doesn't meet any above category will need the 'item' security // role. (Do we need an ItemBehavior or is there something else relevant... CanPickupBehavior etc?) if ((command.SecurityRole & SecurityRole.item) != SecurityRole.none) { return null; } return string.Format("An item (or unrecognized entity) can not use '{0}' right now!", command.Name); }
/// <summary>Try to execute the specified action input as a command.</summary> /// <param name="actionInput">The action input to try to execute.</param> private void TryExecuteAction(ActionInput actionInput) { Debug.Assert(actionInput != null, "actionInput must be defined."); try { ScriptingCommand command = CommandCreator.Instance.Create(actionInput); if (command == null) { return; } // Verify the user has permissions to use this command. string guardsErrorMessage = CommandGuardHelpers.VerifyCommandPermission(command); if (guardsErrorMessage == null) { guardsErrorMessage = (string)command.GuardsDelegate.DynamicInvoke(actionInput); } // Verify that the other command-specific guards are passed. if (guardsErrorMessage == null) { // Execute the command if we passed all the guards. command.ExecuteDelegate.DynamicInvoke(actionInput); } else { // Otherwise display what went wrong to the issuing session of this action input. // (If there is no such session, such as an AI issuing a malformed action, for now this is just ignored. TODO: Send this issue to server log?) actionInput.Session?.WriteLine(guardsErrorMessage); } } catch (Exception ex) { // Most of our exceptions should be TargetInvocationExceptions but we're going to // handle them basically the same way as others, except that we only care about the // inner exception (what actually went wrong, since we know we're doing invokes here). if (ex is TargetInvocationException && ex.InnerException != null) { ex = ex.InnerException; } // In order to isolate command-specific issues, we're going to trap the exception, log // the details, and kill that command. (Other commands and the game itself should be // able to continue through such situations.) // Start gathering info, but carefully to avoid causing potential further exceptions here. #if DEBUG // TODO: Improve action error output to always provide full details to server log and admin players. // https://github.com/DavidRieman/WheelMUD/issues/136 string thingName = actionInput.Actor?.Name ?? "[null]"; string thingID = actionInput.Actor?.Id.ToString() ?? "[null]"; string fullCommand = actionInput.FullText ?? "[null]"; string format = "Exception encountered for command: {1}{0}From thing: {2} (ID: {3}){0}{4}"; string message = string.Format(format, Environment.NewLine, fullCommand, thingName, thingID, ex.ToDeepString()); #else string message = "An error occurred while processing your command."; #endif actionInput.Session?.WriteLine(message); // If the debugger is attached, we probably want to break now in order to better debug // the issue closer to where it occurred; if your debugger broke here you may want to // look at the stack trace to see where the exception originated. if (Debugger.IsAttached) { string stackTrace = ex.StackTrace; Debugger.Break(); } } }