/// <summary>Checks against the guards for the command.</summary> /// <param name="actionInput">The full input specified for executing the command.</param> /// <returns>A string with the error message for the user upon guard failure, else null.</returns> public override string Guards(ActionInput actionInput) { IController sender = actionInput.Controller; string commonFailure = VerifyCommonGuards(actionInput, ActionGuards); if (commonFailure != null) { return commonFailure; } // The comon guards already guarantees the sender is a player, hence no null checks here. this.player = sender.Thing; this.playerBehavior = sender.Thing.Behaviors.FindFirst<PlayerBehavior>(); // Rule: The new pretitle must be empty or meet the length requirements. this.oldPretitle = this.player.SingularPrefix; if (!string.IsNullOrEmpty(actionInput.Tail)) { this.newPretitle = actionInput.Tail; if (this.newPretitle.Length < 2 || this.newPretitle.Length > 15) { return "The pretitle may not be less than 2 nor more than 15 characters long."; } } //// One could implement 'no color' or 'no swearing' or 'no non-alpha character' rules here, etc. return null; }
/// <summary>Checks against the guards for the command.</summary> /// <param name="actionInput">The full input specified for executing the command.</param> /// <returns>A string with the error message for the user upon guard failure, else null.</returns> public override string Guards(ActionInput actionInput) { IController sender = actionInput.Controller; string commonFailure = VerifyCommonGuards(actionInput, ActionGuards); if (commonFailure != null) { return commonFailure; } // This initiator is already guaranteed by VerifyCommonGuards to be a player, hence no null check. this.playerBehavior = sender.Thing.Behaviors.FindFirst<PlayerBehavior>(); return null; }
/// <summary>Called upon authentication of a session.</summary> /// <param name="session">The authenticated session.</param> public void OnSessionAuthenticated(Session session) { // If there was already a connected player for this new, authentic user session, // kick the old one (as it may have been a prior disconnect or whatnot). PlayerBehavior previousPlayer = this.FindLoggedInPlayer(session.UserName); if (previousPlayer != null) { var msg = "Duplicate player match, kicking session id " + previousPlayer.SessionId; this.SystemHost.UpdateSystemHost(this, msg); var existingUserControlledBehavior = previousPlayer.Parent.Behaviors.FindFirst <UserControlledBehavior>(); if (existingUserControlledBehavior != null) { existingUserControlledBehavior.Controller.Write("Another connection has logged in as you; closing this connection."); } // @@@ TEST: Ensure this closes the connection correctly, etc; used to be rigged // dangerously directly through the ServerManager. previousPlayer.LogOut(); this.RemovePlayer(previousPlayer.Parent); } bool wasPlayerMissingDocument = false; // If this session doesn't have a player thing attached yet, load it up. Note that // for situations like character creation, we might already have our Thing, so we // don't want to load a duplicate version of the just-created player Thing. if (session.Thing == null) { var playerBehavior = new PlayerBehavior(); playerBehavior.Load(session.UserName); var player = new Thing(null) { Name = playerBehavior.PlayerData.DisplayName }; // Make sure that the playerBehavior has a parent set. playerBehavior.Parent = player; // Load game data from disk (RavenDb/NoSQL) PlayerDocument pd = this.LoadPlayerDocument(playerBehavior.PlayerData.ID); if (pd == null) { // If we are here, this means that the player that we are trying to // load does not have a corresponding player document in the NoSQL // (RavenDb) store. Let's go and create a player document with default // values. player = PrepareBaseCharacter(session); player.Name = playerBehavior.PlayerData.DisplayName; playerBehavior.Parent = player; playerBehavior.CreateMissingPlayerDocument(); var sb = new StringBuilder(); sb.AppendLine("This character is missing gaming data."); sb.AppendFormat("The MUD engine is creating a default game settings for {0}.", playerBehavior.PlayerData.DisplayName); sb.Append(Environment.NewLine); sb.AppendLine("The system will now log you out. Please login again, to continue playing."); session.Write(sb.ToString()); playerBehavior.LogOut(); } // Get SensesBehavior and UserControlledBehavior from the PlayerDocument. var sensesBehavior = pd.Behaviors.OfType <SensesBehavior>().FirstOrDefault(); var userControlledBehavior = pd.Behaviors.OfType <UserControlledBehavior>().FirstOrDefault(); // Setup the controlled behavior controller. userControlledBehavior.Controller = session; // Initialize the player behavior event processor. playerBehavior.InitEventProcessor(sensesBehavior, userControlledBehavior); // Get the player behavior with the game data var persistedPlayerBehavior = pd.Behaviors.OfType <PlayerBehavior>().FirstOrDefault(); // Get data from the persisted player behavior and merge it into the manually created one playerBehavior.Gender = persistedPlayerBehavior.Gender; playerBehavior.Race = persistedPlayerBehavior.Race; playerBehavior.SessionId = session.ID; playerBehavior.Name = session.UserName; playerBehavior.Prompt = pd.PlayerPrompt; playerBehavior.RoleData = persistedPlayerBehavior.RoleData; playerBehavior.ID = persistedPlayerBehavior.ID; // We don't need the persisted player behavior anymore, so remove it pd.Behaviors.Remove(persistedPlayerBehavior); if (!wasPlayerMissingDocument) { // Put all the persisted game data onto the right objects. this.TranslateFromPlayerDocument(ref player, pd); // Make sure to add the player behavior to the player Thing object. playerBehavior.Parent = player; player.Behaviors.Add(playerBehavior); player.ID = "player/" + playerBehavior.ID; } if (playerBehavior.LogIn(session)) { lock (this.lockObject) { if (!this.playersList.Contains(playerBehavior)) { this.playersList.Add(playerBehavior); } } // Determine the screen buffer size. if (session.Connection != null) { if (userControlledBehavior.PagingRowLimit >= 0) { session.Connection.PagingRowLimit = userControlledBehavior.PagingRowLimit; } else { int terminalHeight = session.Terminal.Height; // If a broken client doesn't provide a valid terminal height, who knows // what it might contain. In that case, default to 0 (no paging). // 100 is an arbitrary realistic number. If this changes, the "buffer" // command should also be changed for consistency. Or define the // max/min as constants somewhere. if (terminalHeight >= 0 && terminalHeight <= 100) { session.Connection.PagingRowLimit = session.Terminal.Height; } else { session.Connection.PagingRowLimit = 0; } } } session.Thing = player; // @@@ HACK: Add player to Krondor's first room PlacesManager.Instance.World.Children[0].Children[0].Add(player); // Finally give the player some initial sensory feedback by having them look. CommandManager.Instance.EnqueueAction(new ActionInput("look", userControlledBehavior.Controller)); } else { // @@@ TODO: Login denied? Back out of the session, disconnect, etc. throw new NotImplementedException("Cancellation of login event is not yet supported."); } } // Finally, if the newly-logged in character replaced an old connection, notify the new // user of the problem. We could also vary behavior/logging based on whether the IP // addresses match; same IP is safer to assume as replaced connection instead of breach. if (previousPlayer != null) { // @@@ TODO: Implement } }
/// <summary> /// Initializes a new instance of the <see cref="PlayerEventProcessor"/> class. /// </summary> /// <param name="playerBehavior">The player behavior.</param> /// <param name="sensesBehavior">The senses behavior.</param> /// <param name="userControlledBehavior">The user controlled behavior.</param> public PlayerEventProcessor(PlayerBehavior playerBehavior, SensesBehavior sensesBehavior, UserControlledBehavior userControlledBehavior) { this.playerBehavior = playerBehavior; this.sensesBehavior = sensesBehavior; this.userControlledBehavior = userControlledBehavior; }
/// <summary>Checks against the guards for the command.</summary> /// <param name="actionInput">The full input specified for executing the command.</param> /// <returns>A string with the error message for the user upon guard failure, else null.</returns> public override string Guards(ActionInput actionInput) { string commonFailure = VerifyCommonGuards(actionInput, ActionGuards); if (commonFailure != null) { return commonFailure; } // The comon guards already guarantees the sender is a player, hence no null checks here. this.player = actionInput.Controller.Thing; this.playerBehavior = this.player.Behaviors.FindFirst<PlayerBehavior>(); return null; }
public void Init() { this.playerBehavior = new PlayerBehavior(); }
/// <summary>Checks against the guards for the command.</summary> /// <param name="actionInput">The full input specified for executing the command.</param> /// <returns>A string with the error message for the user upon guard failure, else null.</returns> public override string Guards(ActionInput actionInput) { string commonFailure = VerifyCommonGuards(actionInput, ActionGuards); if (commonFailure != null) { return commonFailure; } this.playerBehavior = actionInput.Controller.Thing.Behaviors.FindFirst<PlayerBehavior>(); return null; }
/// <summary>Called upon authentication of a session.</summary> /// <param name="session">The authenticated session.</param> public void OnSessionAuthenticated(Session session) { // If there was already a connected player for this new, authentic user session, // kick the old one (as it may have been a prior disconnect or whatnot). PlayerBehavior previousPlayer = this.FindLoggedInPlayer(session.UserName); if (previousPlayer != null) { var msg = "Duplicate player match, kicking session id " + previousPlayer.SessionId; this.SystemHost.UpdateSystemHost(this, msg); var existingUserControlledBehavior = previousPlayer.Parent.Behaviors.FindFirst<UserControlledBehavior>(); if (existingUserControlledBehavior != null) { existingUserControlledBehavior.Controller.Write("Another connection has logged in as you; closing this connection."); } // @@@ TEST: Ensure this closes the connection correctly, etc; used to be rigged // dangerously directly through the ServerManager. previousPlayer.LogOut(); this.RemovePlayer(previousPlayer.Parent); } bool wasPlayerMissingDocument = false; // If this session doesn't have a player thing attached yet, load it up. Note that // for situations like character creation, we might already have our Thing, so we // don't want to load a duplicate version of the just-created player Thing. if (session.Thing == null) { var playerBehavior = new PlayerBehavior(); playerBehavior.Load(session.UserName); var player = new Thing(null) { Name = playerBehavior.PlayerData.DisplayName }; // Make sure that the playerBehavior has a parent set. playerBehavior.Parent = player; // Load game data from disk (RavenDb/NoSQL) PlayerDocument pd = this.LoadPlayerDocument(playerBehavior.PlayerData.ID); if (pd == null) { // If we are here, this means that the player that we are trying to // load does not have a corresponding player document in the NoSQL // (RavenDb) store. Let's go and create a player document with default // values. player = PrepareBaseCharacter(session); player.Name = playerBehavior.PlayerData.DisplayName; playerBehavior.Parent = player; playerBehavior.CreateMissingPlayerDocument(); var sb = new StringBuilder(); sb.AppendLine("This character is missing gaming data."); sb.AppendFormat("The MUD engine is creating a default game settings for {0}.", playerBehavior.PlayerData.DisplayName); sb.Append(Environment.NewLine); sb.AppendLine("The system will now log you out. Please login again, to continue playing."); session.Write(sb.ToString()); playerBehavior.LogOut(); } // Get SensesBehavior and UserControlledBehavior from the PlayerDocument. var sensesBehavior = pd.Behaviors.OfType<SensesBehavior>().FirstOrDefault(); var userControlledBehavior = pd.Behaviors.OfType<UserControlledBehavior>().FirstOrDefault(); // Setup the controlled behavior controller. userControlledBehavior.Controller = session; // Initialize the player behavior event processor. playerBehavior.InitEventProcessor(sensesBehavior, userControlledBehavior); // Get the player behavior with the game data var persistedPlayerBehavior = pd.Behaviors.OfType<PlayerBehavior>().FirstOrDefault(); // Get data from the persisted player behavior and merge it into the manually created one playerBehavior.Gender = persistedPlayerBehavior.Gender; playerBehavior.Race = persistedPlayerBehavior.Race; playerBehavior.SessionId = session.ID; playerBehavior.Name = session.UserName; playerBehavior.Prompt = pd.PlayerPrompt; playerBehavior.RoleData = persistedPlayerBehavior.RoleData; playerBehavior.ID = persistedPlayerBehavior.ID; // We don't need the persisted player behavior anymore, so remove it pd.Behaviors.Remove(persistedPlayerBehavior); if (!wasPlayerMissingDocument) { // Put all the persisted game data onto the right objects. this.TranslateFromPlayerDocument(ref player, pd); // Make sure to add the player behavior to the player Thing object. playerBehavior.Parent = player; player.Behaviors.Add(playerBehavior); player.ID = "player/" + playerBehavior.ID; } if (playerBehavior.LogIn(session)) { lock (this.lockObject) { if (!this.playersList.Contains(playerBehavior)) { this.playersList.Add(playerBehavior); } } // Determine the screen buffer size. if (session.Connection != null) { if (userControlledBehavior.PagingRowLimit >= 0) { session.Connection.PagingRowLimit = userControlledBehavior.PagingRowLimit; } else { int terminalHeight = session.Terminal.Height; // If a broken client doesn't provide a valid terminal height, who knows // what it might contain. In that case, default to 0 (no paging). // 100 is an arbitrary realistic number. If this changes, the "buffer" // command should also be changed for consistency. Or define the // max/min as constants somewhere. if (terminalHeight >= 0 && terminalHeight <= 100) { session.Connection.PagingRowLimit = session.Terminal.Height; } else { session.Connection.PagingRowLimit = 0; } } } session.Thing = player; // @@@ HACK: Add player to Krondor's first room PlacesManager.Instance.World.Children[0].Children[0].Add(player); // Finally give the player some initial sensory feedback by having them look. CommandManager.Instance.EnqueueAction(new ActionInput("look", userControlledBehavior.Controller)); } else { // @@@ TODO: Login denied? Back out of the session, disconnect, etc. throw new NotImplementedException("Cancellation of login event is not yet supported."); } } // Finally, if the newly-logged in character replaced an old connection, notify the new // user of the problem. We could also vary behavior/logging based on whether the IP // addresses match; same IP is safer to assume as replaced connection instead of breach. if (previousPlayer != null) { // @@@ TODO: Implement } }
/// <summary>Prepares the base character.</summary> /// <param name="session">The session.</param> /// <returns>Filled out base character.</returns> public static Thing PrepareBaseCharacter(Session session) { var movableBehavior = new MovableBehavior(); var livingBehavior = new LivingBehavior(); var sensesBehavior = new SensesBehavior(); var userControlledBehavior = new UserControlledBehavior() { Controller = session, }; var playerBehavior = new PlayerBehavior(sensesBehavior, userControlledBehavior) { SessionId = session.ID, }; var player = new Thing(livingBehavior, sensesBehavior, userControlledBehavior, playerBehavior, movableBehavior); var game = GameSystemController.Instance; // Load the default stats for the current gaming system foreach (var gameStat in game.GameStats) { var currStat = new GameStat(session, gameStat.Name, gameStat.Abbreviation, gameStat.Formula, gameStat.Value, gameStat.MinValue, gameStat.MaxValue, gameStat.Visible); player.Stats.Add(currStat.Abbreviation, currStat); } // Load the secondary stats\attributes for the current gaming system foreach (var attribute in game.GameAttributes) { var newAttribute = new GameAttribute(session, attribute.Name, attribute.Abbreviation, attribute.Formula, attribute.Value, attribute.MinValue, attribute.MaxValue, attribute.Visible); player.Attributes.Add(newAttribute.Abbreviation, newAttribute); } return player; }