FindPlayers() public static method

public static FindPlayers ( IPAddress address ) : fCraft.PlayerInfo[]
address System.Net.IPAddress
return fCraft.PlayerInfo[]
Example #1
0
        internal static void Unignore(Player player, Command cmd)
        {
            string name = cmd.Next();

            if (name != null)
            {
                PlayerInfo targetInfo;
                if (!PlayerDB.FindPlayerInfo(name, out targetInfo))
                {
                    PlayerInfo[] infos = PlayerDB.FindPlayers(name);
                    if (infos.Length == 1)
                    {
                        targetInfo = infos[0];
                    }
                    else if (infos.Length > 1)
                    {
                        player.ManyMatchesMessage("player", infos);
                        return;
                    }
                    else
                    {
                        player.NoPlayerMessage(name);
                        return;
                    }
                }
                else if (targetInfo == null)
                {
                    player.NoPlayerMessage(name);
                    return;
                }
                if (player.Unignore(targetInfo))
                {
                    player.MessageNow("You are no longer ignoring {0}", targetInfo.GetClassyName());
                }
                else
                {
                    player.MessageNow("You are not currently ignoring {0}", targetInfo.GetClassyName());
                }
            }
            else
            {
                PlayerInfo[] ignoreList = player.GetIgnoreList();
                if (ignoreList.Length > 0)
                {
                    player.MessageNow("Ignored players: {0}", ignoreList.JoinToClassyString());
                }
                else
                {
                    player.MessageNow("You are not currently ignoring anyone.");
                }
                return;
            }
        }
Example #2
0
        /// <summary> Unbans given IP address and all accounts on that IP. </summary>
        /// <param name="targetAddress"> IP address that is being unbanned. </param>
        /// <param name="player"> Player who is unbanning. </param>
        /// <param name="reason"> Reason for unban. May be null or empty, if permitted by server configuration. </param>
        /// <param name="announce"> Whether unban should be publicly announced on the server. </param>
        /// <param name="raiseEvents"> Whether RemovingIPBan, RemovedIPBan, BanChanging, and BanChanged events should be raised. </param>
        /// <exception cref="ArgumentNullException"> targetAddress or player is null. </exception>
        /// <exception cref="PlayerOpException"> Permission or configuration issues arise, or if everyone has already been unbanned. </exception>
        public static void UnbanAll([NotNull] this IPAddress targetAddress, [NotNull] Player player, [CanBeNull] string reason,
                                    bool announce, bool raiseEvents)
        {
            if (targetAddress == null)
            {
                throw new ArgumentNullException("targetAddress");
            }
            if (player == null)
            {
                throw new ArgumentNullException("player");
            }
            if (reason != null && reason.Trim().Length == 0)
            {
                reason = null;
            }

            if (!player.Can(Permission.Ban, Permission.BanIP, Permission.BanAll))
            {
                PlayerOpException.ThrowPermissionMissing(player, null, "unban-all",
                                                         Permission.Ban, Permission.BanIP, Permission.BanAll);
            }

            // Check if player is trying to unban self
            if (targetAddress.Equals(player.IP) && !player.IsSuper)
            {
                PlayerOpException.ThrowCannotTargetSelf(player, null, "unban-all");
            }

            // Check if a non-bannable address was given (0.0.0.0 or 255.255.255.255)
            if (targetAddress.Equals(IPAddress.None) || targetAddress.Equals(IPAddress.Any))
            {
                PlayerOpException.ThrowInvalidIP(player, null, targetAddress);
            }

            PlayerOpException.CheckBanReason(reason, player, null, true);
            bool somethingGotUnbanned = false;

            lock ( BanListLock ) {
                CheckIfLoaded();
                // Unban the IP
                if (Contains(targetAddress))
                {
                    if (Remove(targetAddress, raiseEvents))
                    {
                        Logger.Log(LogType.UserActivity,
                                   "{0} unbanned {1} (UnbanAll {1}). Reason: {2}",
                                   player.Name, targetAddress, reason ?? "");

                        // Announce unban on the server
                        if (announce)
                        {
                            var can = Server.Players.Can(Permission.ViewPlayerIPs);
                            can.Message("&W{0} was unbanned by {1}", targetAddress, player.ClassyName);
                            var cant = Server.Players.Cant(Permission.ViewPlayerIPs);
                            cant.Message("&WAn IP was unbanned by {0}", player.ClassyName);
                        }

                        somethingGotUnbanned = true;
                    }
                }

                // Unban individual players
                PlayerInfo[] allPlayersOnIP = PlayerDB.FindPlayers(targetAddress);
                foreach (PlayerInfo targetAlt in allPlayersOnIP)
                {
                    if (targetAlt.BanStatus != BanStatus.Banned)
                    {
                        continue;
                    }

                    // Raise PlayerInfo.BanChanging event
                    PlayerInfoBanChangingEventArgs e = new PlayerInfoBanChangingEventArgs(targetAlt, player, true,
                                                                                          reason, announce);
                    if (raiseEvents)
                    {
                        PlayerInfo.RaiseBanChangingEvent(e);
                        if (e.Cancel)
                        {
                            continue;
                        }
                        reason = e.Reason;
                    }

                    // Do the ban
                    if (targetAlt.ProcessUnban(player.Name, reason))
                    {
                        if (raiseEvents)
                        {
                            PlayerInfo.RaiseBanChangedEvent(e);
                        }

                        // Log and announce ban
                        Logger.Log(LogType.UserActivity,
                                   "{0} unbanned {1} (UnbanAll {2}). Reason: {3}",
                                   player.Name, targetAlt.Name, targetAddress, reason ?? "");
                        if (announce)
                        {
                            Server.Message("&WPlayer {0}&W was unbanned by {1}&W (UnbanAll)",
                                           targetAlt.ClassyName, player.ClassyName);
                        }
                        somethingGotUnbanned = true;
                    }
                }
            }

            // If no one ended up getting unbanned, quit here
            if (!somethingGotUnbanned)
            {
                PlayerOpException.ThrowNoOneToUnban(player, null, targetAddress);
            }

            // Announce UnbanAll reason towards the end of all unbans
            if (announce && ConfigKey.AnnounceKickAndBanReasons.Enabled() && reason != null)
            {
                Server.Message("&WUnbanAll reason: {0}", reason);
            }
        }
Example #3
0
        /// <summary> Bans given IP address and all accounts on that IP. All players from IP are kicked.
        /// Throws PlayerOpException on problems. </summary>
        /// <param name="targetAddress"> IP address that is being banned. </param>
        /// <param name="player"> Player who is banning. </param>
        /// <param name="reason"> Reason for ban. May be empty, if permitted by server configuration. </param>
        /// <param name="announce"> Whether ban should be publicly announced on the server. </param>
        /// <param name="raiseEvents"> Whether AddingIPBan, AddedIPBan, BanChanging, and BanChanged events should be raised. </param>
        /// <exception cref="ArgumentNullException"> targetAddress or player is null. </exception>
        /// <exception cref="PlayerOpException"> Permission or configuration issues arise, or if everyone has already been banned. </exception>
        public static void BanAll([NotNull] this IPAddress targetAddress, [NotNull] Player player, [CanBeNull] string reason,
                                  bool announce, bool raiseEvents)
        {
            if (targetAddress == null)
            {
                throw new ArgumentNullException("targetAddress");
            }
            if (player == null)
            {
                throw new ArgumentNullException("player");
            }
            if (reason != null && reason.Trim().Length == 0)
            {
                reason = null;
            }

            if (!player.Can(Permission.Ban, Permission.BanIP, Permission.BanAll))
            {
                PlayerOpException.ThrowPermissionMissing(player, null, "ban-all",
                                                         Permission.Ban, Permission.BanIP, Permission.BanAll);
            }

            // Check if player is trying to ban self
            if (targetAddress.Equals(player.IP) && !player.IsSuper)
            {
                PlayerOpException.ThrowCannotTargetSelf(player, null, "ban-all");
            }

            // Check if a non-bannable address was given (0.0.0.0 or 255.255.255.255)
            if (targetAddress.Equals(IPAddress.None) || targetAddress.Equals(IPAddress.Any))
            {
                PlayerOpException.ThrowInvalidIP(player, null, targetAddress);
            }

            // Check if any high-ranked players use this address
            PlayerInfo[] allPlayersOnIP        = PlayerDB.FindPlayers(targetAddress);
            PlayerInfo   infoWhomPlayerCantBan = allPlayersOnIP.FirstOrDefault(info => !player.Can(Permission.Ban, info.Rank));

            if (infoWhomPlayerCantBan != null)
            {
                PlayerOpException.ThrowPermissionLimitIP(player, infoWhomPlayerCantBan, targetAddress);
            }

            PlayerOpException.CheckBanReason(reason, player, null, false);
            bool somethingGotBanned = false;

            lock ( BanListLock ) {
                CheckIfLoaded();
                // Ban the IP
                if (!Contains(targetAddress))
                {
                    IPBanInfo banInfo = new IPBanInfo(targetAddress, null, player.Name, reason);
                    if (Add(banInfo, raiseEvents))
                    {
                        Logger.Log(LogType.UserActivity,
                                   "{0} banned {1} (BanAll {1}). Reason: {2}",
                                   player.Name, targetAddress, reason ?? "");

                        // Announce ban on the server
                        if (announce)
                        {
                            var can = Server.Players.Can(Permission.ViewPlayerIPs);
                            can.Message("&W{0} was banned by {1}", targetAddress, player.ClassyName);
                            var cant = Server.Players.Cant(Permission.ViewPlayerIPs);
                            cant.Message("&WAn IP was banned by {0}", player.ClassyName);
                        }
                        somethingGotBanned = true;
                    }
                }

                // Ban individual players
                foreach (PlayerInfo targetAlt in allPlayersOnIP)
                {
                    if (targetAlt.BanStatus != BanStatus.NotBanned)
                    {
                        continue;
                    }

                    // Raise PlayerInfo.BanChanging event
                    PlayerInfoBanChangingEventArgs e = new PlayerInfoBanChangingEventArgs(targetAlt, player, false,
                                                                                          reason, announce);
                    if (raiseEvents)
                    {
                        PlayerInfo.RaiseBanChangingEvent(e);
                        if (e.Cancel)
                        {
                            continue;
                        }
                        reason = e.Reason;
                    }

                    // Do the ban
                    if (targetAlt.ProcessBan(player, player.Name, reason))
                    {
                        if (raiseEvents)
                        {
                            PlayerInfo.RaiseBanChangedEvent(e);
                        }

                        // Log and announce ban
                        Logger.Log(LogType.UserActivity,
                                   "{0} banned {1} (BanAll {2}). Reason: {3}",
                                   player.Name, targetAlt.Name, targetAddress, reason ?? "");
                        if (announce)
                        {
                            Server.Message("&WPlayer {0}&W was banned by {1}&W (BanAll)",
                                           targetAlt.ClassyName, player.ClassyName);
                        }
                        somethingGotBanned = true;
                    }
                }
            }

            // If no one ended up getting banned, quit here
            if (!somethingGotBanned)
            {
                PlayerOpException.ThrowNoOneToBan(player, null, targetAddress);
            }

            // Announce BanAll reason towards the end of all bans
            if (announce && ConfigKey.AnnounceKickAndBanReasons.Enabled() && reason != null)
            {
                Server.Message("&WBanAll reason: {0}", reason);
            }

            // Kick all players from IP
            Player[] targetsOnline = Server.Players.FromIP(targetAddress).ToArray();
            if (targetsOnline.Length > 0)
            {
                string kickReason;
                if (reason != null)
                {
                    kickReason = String.Format("Banned by {0}: {1}", player.Name, reason);
                }
                else
                {
                    kickReason = String.Format("Banned by {0}", player.Name);
                }
                for (int i = 0; i < targetsOnline.Length; i++)
                {
                    if (targetsOnline[i].Info.BanStatus != BanStatus.IPBanExempt)
                    {
                        targetsOnline[i].Kick(kickReason, LeaveReason.BanAll);
                    }
                }
            }
        }
Example #4
0
        /// <summary> Bans given IP address. All players from IP are kicked. If an associated PlayerInfo is known,
        /// use a different overload of this method instead. Throws PlayerOpException on problems. </summary>
        /// <param name="targetAddress"> IP address that is being banned. </param>
        /// <param name="player"> Player who is banning. </param>
        /// <param name="reason"> Reason for ban. May be empty, if permitted by server configuration. </param>
        /// <param name="announce"> Whether ban should be publicly announced on the server. </param>
        /// <param name="raiseEvents"> Whether AddingIPBan and AddedIPBan events should be raised. </param>
        /// <exception cref="ArgumentNullException"> targetAddress or player is null. </exception>
        /// <exception cref="PlayerOpException"> Permission or configuration issues arise, or if IP is already banned. </exception>
        public static void BanIP([NotNull] this IPAddress targetAddress, [NotNull] Player player, [CanBeNull] string reason,
                                 bool announce, bool raiseEvents)
        {
            if (targetAddress == null)
            {
                throw new ArgumentNullException("targetAddress");
            }
            if (player == null)
            {
                throw new ArgumentNullException("player");
            }
            if (reason != null && reason.Trim().Length == 0)
            {
                reason = null;
            }

            // Check if player can ban IPs in general
            if (!player.Can(Permission.Ban, Permission.BanIP))
            {
                PlayerOpException.ThrowPermissionMissing(player, null, "IP-ban", Permission.Ban, Permission.BanIP);
            }

            // Check if a non-bannable address was given (0.0.0.0 or 255.255.255.255)
            if (targetAddress.Equals(IPAddress.None) || targetAddress.Equals(IPAddress.Any))
            {
                PlayerOpException.ThrowInvalidIP(player, null, targetAddress);
            }

            // Check if player is trying to ban self
            if (targetAddress.Equals(player.IP) && !player.IsSuper)
            {
                PlayerOpException.ThrowCannotTargetSelf(player, null, "IP-ban");
            }

            lock ( BanListLock ) {
                CheckIfLoaded();
                // Check if target is already banned
                IPBanInfo existingBan = Get(targetAddress);
                if (existingBan != null)
                {
                    string msg;
                    if (player.Can(Permission.ViewPlayerIPs))
                    {
                        msg = String.Format("IP address {0} is already banned.", targetAddress);
                    }
                    else
                    {
                        msg = String.Format("Given IP address is already banned.");
                    }
                    string colorMsg = "&S" + msg;
                    throw new PlayerOpException(player, null, PlayerOpExceptionCode.NoActionNeeded, msg, colorMsg);
                }

                // Check if any high-ranked players use this address
                PlayerInfo infoWhomPlayerCantBan = PlayerDB.FindPlayers(targetAddress)
                                                   .FirstOrDefault(info => !player.Can(Permission.Ban, info.Rank));
                if (infoWhomPlayerCantBan != null)
                {
                    PlayerOpException.ThrowPermissionLimitIP(player, infoWhomPlayerCantBan, targetAddress);
                }

                PlayerOpException.CheckBanReason(reason, player, null, false);

                // Actually ban
                IPBanInfo banInfo = new IPBanInfo(targetAddress, null, player.Name, reason);
                bool      result  = Add(banInfo, raiseEvents);

                if (result)
                {
                    Logger.Log(LogType.UserActivity,
                               "{0} banned {1} (BanIP {1}). Reason: {2}",
                               player.Name, targetAddress, reason ?? "");
                    if (announce)
                    {
                        // Announce ban on the server
                        var can = Server.Players.Can(Permission.ViewPlayerIPs);
                        can.Message("&W{0} was banned by {1}", targetAddress, player.ClassyName);
                        var cant = Server.Players.Cant(Permission.ViewPlayerIPs);
                        cant.Message("&WAn IP was banned by {0}", player.ClassyName);
                        if (ConfigKey.AnnounceKickAndBanReasons.Enabled() && reason != null)
                        {
                            Server.Message("&WBanIP reason: {0}", reason);
                        }
                    }

                    // Kick all players connected from address
                    string kickReason;
                    if (reason != null)
                    {
                        kickReason = String.Format("IP-Banned by {0}: {1}", player.Name, reason);
                    }
                    else
                    {
                        kickReason = String.Format("IP-Banned by {0}", player.Name);
                    }
                    foreach (Player other in Server.Players.FromIP(targetAddress))
                    {
                        if (other.Info.BanStatus != BanStatus.IPBanExempt)
                        {
                            other.Kick(kickReason, LeaveReason.BanIP);   // TODO: check side effects of not using DoKick
                        }
                    }
                }
                else
                {
                    // address is already banned
                    string msg;
                    if (player.Can(Permission.ViewPlayerIPs))
                    {
                        msg = String.Format("{0} is already banned.", targetAddress);
                    }
                    else
                    {
                        msg = "Given IP address is already banned.";
                    }
                    string colorMsg = "&S" + msg;
                    throw new PlayerOpException(player, null, PlayerOpExceptionCode.NoActionNeeded, msg, colorMsg);
                }
            }
        }
Example #5
0
        internal static void Info(Player player, Command cmd)
        {
            string name = cmd.Next();

            if (name == null)
            {
                name = player.Name;
            }
            else if (!player.Can(Permission.ViewOthersInfo))
            {
                player.NoAccessMessage(Permission.ViewOthersInfo);
                return;
            }

            IPAddress ip;

            PlayerInfo[] infos;
            if (Server.IsIP(name) && IPAddress.TryParse(name, out ip))
            {
                // find players by IP
                infos = PlayerDB.FindPlayers(ip, PlayerDB.NumberOfMatchesToPrint);
            }
            else if (name.Contains("*") || name.Contains("."))
            {
                // find players by regex/wildcard
                string regexString = "^" + RegexNonNameChars.Replace(name, "").Replace("*", ".*") + "$";
                Regex  regex       = new Regex(regexString, RegexOptions.IgnoreCase | RegexOptions.Compiled);
                infos = PlayerDB.FindPlayers(regex, PlayerDB.NumberOfMatchesToPrint);
            }
            else
            {
                // find players by partial matching
                PlayerInfo tempInfo;
                if (!PlayerDB.FindPlayerInfo(name, out tempInfo))
                {
                    infos = PlayerDB.FindPlayers(name, PlayerDB.NumberOfMatchesToPrint);
                }
                else if (tempInfo == null)
                {
                    player.NoPlayerMessage(name);
                    return;
                }
                else
                {
                    infos = new[] { tempInfo };
                }
            }

            if (infos.Length == 1)
            {
                PrintPlayerInfo(player, infos[0]);
            }
            else if (infos.Length > 1)
            {
                player.ManyMatchesMessage("player", infos);
                if (infos.Length == PlayerDB.NumberOfMatchesToPrint)
                {
                    player.Message("NOTE: Only first {0} matches are shown.", PlayerDB.NumberOfMatchesToPrint);
                }
            }
            else
            {
                player.NoPlayerMessage(name);
            }
        }
Example #6
0
        public static void PrintPlayerInfo(Player player, PlayerInfo info)
        {
            Player target = Server.FindPlayerExact(info.Name);

            // hide online status when hidden
            if (target != null && !player.CanSee(target))
            {
                target = null;
            }

            if (info.LastIP.Equals(IPAddress.None))
            {
                player.Message("About {0}&S: Never seen before.", info.GetClassyName());
            }
            else
            {
                if (target != null)
                {
                    if (target.IsHidden)
                    {
                        if (player.Can(Permission.ViewPlayerIPs))
                        {
                            player.Message("About {0}&S: HIDDEN. Online from {1}",
                                           info.GetClassyName(),
                                           info.LastIP);
                        }
                        else
                        {
                            player.Message("About {0}&S: HIDDEN.",
                                           info.GetClassyName());
                        }
                    }
                    else
                    {
                        if (player.Can(Permission.ViewPlayerIPs))
                        {
                            player.Message("About {0}&S: Online now from {1}",
                                           info.GetClassyName(),
                                           info.LastIP);
                        }
                        else
                        {
                            player.Message("About {0}&S: Online now.",
                                           info.GetClassyName());
                        }
                    }
                }
                else
                {
                    if (player.Can(Permission.ViewPlayerIPs))
                    {
                        player.Message("About {0}&S: Last seen {1} ago from {2}",
                                       info.GetClassyName(),
                                       info.TimeSinceLastSeen.ToMiniString(),
                                       info.LastIP);
                    }
                    else
                    {
                        player.Message("About {0}&S: Last seen {1} ago.",
                                       info.GetClassyName(),
                                       info.TimeSinceLastSeen.ToMiniString());
                    }
                }
                // Show login information
                player.Message("  Logged in {0} time(s) since {1:d MMM yyyy}.",
                               info.TimesVisited,
                               info.FirstLoginDate);
            }


            // Show ban information
            IPBanInfo ipBan = IPBanList.Get(info.LastIP);

            if (ipBan != null && info.Banned)
            {
                player.Message("  Both name and IP are {0}BANNED&S. See &H/baninfo", Color.Red);
            }
            else if (ipBan != null)
            {
                player.Message("  IP is {0}BANNED&S (but nick isn't). See &H/baninfo", Color.Red);
            }
            else if (info.Banned)
            {
                player.Message("  Nick is {0}BANNED&S (but IP isn't). See &H/baninfo", Color.Red);
            }


            if (info.LastIP.ToString() != IPAddress.None.ToString())
            {
                // Show alts
                List <PlayerInfo> altNames = new List <PlayerInfo>();
                int bannedAltCount         = 0;
                foreach (PlayerInfo playerFromSameIP in PlayerDB.FindPlayers(info.LastIP, 25))
                {
                    if (playerFromSameIP != info)
                    {
                        altNames.Add(playerFromSameIP);
                        if (playerFromSameIP.Banned)
                        {
                            bannedAltCount++;
                        }
                    }
                }

                if (altNames.Count > 0)
                {
                    if (bannedAltCount > 0)
                    {
                        player.Message("  {0} accounts ({1} banned) share this IP: {2}",
                                       altNames.Count,
                                       bannedAltCount,
                                       altNames.ToArray().JoinToClassyString());
                    }
                    else
                    {
                        player.Message("  {0} accounts share this IP: {1}",
                                       altNames.Count,
                                       altNames.ToArray().JoinToClassyString());
                    }
                }
            }


            // Stats
            if (info.BlocksDrawn > 500000000)
            {
                player.Message("  Built {0} and deleted {1} blocks, drew {2}M blocks, wrote {3} messages.",
                               info.BlocksBuilt,
                               info.BlocksDeleted,
                               info.BlocksDrawn / 1000000,
                               info.LinesWritten);
            }
            else if (info.BlocksDrawn > 500000)
            {
                player.Message("  Built {0} and deleted {1} blocks, drew {2}K blocks, wrote {3} messages.",
                               info.BlocksBuilt,
                               info.BlocksDeleted,
                               info.BlocksDrawn / 1000,
                               info.LinesWritten);
            }
            else if (info.BlocksDrawn > 0)
            {
                player.Message("  Built {0} and deleted {1} blocks, drew {2} blocks, wrote {3} messages.",
                               info.BlocksBuilt,
                               info.BlocksDeleted,
                               info.BlocksDrawn,
                               info.LinesWritten);
            }
            else
            {
                player.Message("  Built {0} and deleted {1} blocks, wrote {2} messages.",
                               info.BlocksBuilt,
                               info.BlocksDeleted,
                               info.LinesWritten);
            }


            // More stats
            if (info.TimesBannedOthers > 0 || info.TimesKickedOthers > 0)
            {
                player.Message("  Kicked {0} and banned {1} players.", info.TimesKickedOthers, info.TimesBannedOthers);
            }

            if (info.TimesKicked > 0)
            {
                if (info.LastKickDate != DateTime.MinValue)
                {
                    player.Message("  Got kicked {0} times. Last kick {1} ago by {2}",
                                   info.TimesKicked,
                                   info.TimeSinceLastKick.ToMiniString(),
                                   info.LastKickBy);
                    if (info.LastKickReason.Length > 0)
                    {
                        player.Message("  Last kick reason: {0}", info.LastKickReason);
                    }
                }
                else
                {
                    player.Message("  Got kicked {0} times", info.TimesKicked);
                }
            }


            // Promotion/demotion
            if (!String.IsNullOrEmpty(info.RankChangedBy))
            {
                if (info.PreviousRank == null)
                {
                    player.Message("  Promoted to {0}&S by {1} {2} ago.",
                                   info.Rank.GetClassyName(),
                                   info.RankChangedBy,
                                   info.TimeSinceRankChange.ToMiniString());
                }
                else if (info.PreviousRank < info.Rank)
                {
                    player.Message("  Promoted from {0}&S to {1}&S by {2} {3} ago.",
                                   info.PreviousRank.GetClassyName(),
                                   info.Rank.GetClassyName(),
                                   info.RankChangedBy,
                                   info.TimeSinceRankChange.ToMiniString());
                    if (!string.IsNullOrEmpty(info.RankChangeReason))
                    {
                        player.Message("  Promotion reason: {0}", info.RankChangeReason);
                    }
                }
                else
                {
                    player.Message("  Demoted from {0}&S to {1}&S by {2} {3} ago.",
                                   info.PreviousRank.GetClassyName(),
                                   info.Rank.GetClassyName(),
                                   info.RankChangedBy,
                                   info.TimeSinceRankChange.ToMiniString());
                    if (info.RankChangeReason.Length > 0)
                    {
                        player.Message("  Demotion reason: {0}", info.RankChangeReason);
                    }
                }
            }
            else
            {
                player.Message("  Rank is {0}&S (default).",
                               info.Rank.GetClassyName());
            }

            if (info.LastIP.ToString() != IPAddress.None.ToString())
            {
                // Time on the server
                TimeSpan totalTime = info.TotalTime;
                if (target != null)
                {
                    totalTime = totalTime.Add(info.TimeSinceLastLogin);
                }
                player.Message("  Spent a total of {0:F1} hours ({1:F1} minutes) here.",
                               totalTime.TotalHours,
                               totalTime.TotalMinutes);
            }
        }