/// <summary>Called when the client is allowed to zone.  Handles all which must happen when a client zones out.</summary>
        internal void ZoneClient(ZoneChange zc, Zone targetZone, float destX, float destY, float destZ, float destH, byte ignoreRestrictions, Client client)
        {
            this.SendLogoutPackets(client);

            _mobMgr.RemoveFromAllHateLists(client.ZonePlayer);

            // TODO: clear aggro for pet, if present

            _log.DebugFormat("{0} is ATTEMPTING to zone to {1} ({2}) {3}x {4}y {5}z", client.ZonePlayer.Name, targetZone.ShortName, targetZone.ZoneID,
                destX, destY, destZ);

            ZoneChange zcOut = new ZoneChange();
            if (targetZone.ZoneID == this.Zone.ZoneID)
            {
                // Zoning to same zone (maybe a bind point, etc.)
                zcOut.ZoneID = this.Zone.ZoneID;
                zcOut.Success = (int)ZoneError.Success;

                client.ZonePlayer.X = destX;
                client.ZonePlayer.Y = destY;
                client.ZonePlayer.Z = destZ;
                client.ZonePlayer.Heading = destH;

                _log.InfoFormat("{0} is zoning to same zone {1}x {2}y {3}z (no error)", client.ZonePlayer.Name, destX, destY, destZ);
                AddClientAuth(client.IPEndPoint.Address.ToString(), true);   // add to expected clients list
            }
            else
            {
                // Send a ZTZ to World
                ZoneToZone ztz = new ZoneToZone();
                ztz.CharName = client.ZonePlayer.Name;
                ztz.CharId = client.ZonePlayer.ID;
                ztz.ClientIp = client.IPEndPoint.Address.ToString();
                ztz.CurrentZoneId = this.Zone.ZoneID;
                ztz.RequestedZoneId = targetZone.ZoneID;
                ztz.AccountStatus = client.ZonePlayer.AccountStatus;
                ztz.IgnoreRestrictions = ignoreRestrictions;
                int ztzResult = WorldSvc.ZoneToZone(ztz);

                if (ztzResult > 0)
                {
                    // problems
                    zcOut.Success = (int)ZoneError.NotReady;
                    client.ZonePlayer.ZoneId = this.Zone.ZoneID;    // client isn't zoning after all, so set the id back to this zone

                    _log.InfoFormat("{0} is zoning to same zone {1}x {2}y {3}z due to error code {4} when asking world to zone",
                        client.ZonePlayer.Name, destX, destY, destZ, ztzResult);

                    client.ZonePlayer.MsgMgr.SendSpecialMessage(MessageType.Default, string.Format("There was a problem zoning.  Code {0}.", ztzResult));
                }
                else
                {
                    zcOut.Init();
                    Buffer.BlockCopy(Encoding.ASCII.GetBytes(client.ZonePlayer.Name), 0, zcOut.CharName, 0, client.ZonePlayer.Name.Length);
                    zcOut.ZoneID = targetZone.ZoneID;
                    zcOut.Success = (int)ZoneError.Success;

                    client.ZonePlayer.X = destX;
                    client.ZonePlayer.Y = destY;
                    client.ZonePlayer.Z = destZ;
                    client.ZonePlayer.Heading = destH;
                    client.ZonePlayer.ZoneId = targetZone.ZoneID;

                    _log.InfoFormat("{0} is zoning to {1} ({2}) {3}x {4}y {5}z", client.ZonePlayer.Name, targetZone.ShortName, targetZone.ZoneID,
                        destX, destY, destZ);

                    // TODO: for ignoreRestrictions of 3, get safe coords for target zone
                }
            }

            client.ZonePlayer.ZoneMode = ZoneMode.Unsolicited;  // reset the zoneMode
            client.ZonePlayer.Save();   // this forced save ensures the correct zone info is available to world when zoning the client

            EQApplicationPacket<ZoneChange> zcPack = new EQApplicationPacket<ZoneChange>(AppOpCode.ZoneChange, zcOut);
            client.SendApplicationPacket(zcPack);
        }
        /// <summary>Handles a client zoning from one zone server to another.  This is sent from a zone server.</summary>
        /// <returns>0 = success, 1 = max clients reached, 2 = zone locked, 3 = general fubar error.</returns>
        public int ZoneToZone(ZoneToZone ztz)
        {
            _log.InfoFormat("ZoneToZone request for {0}: current zone is {1}, target zone is {2}", ztz.CharName, ztz.CurrentZoneId, ztz.RequestedZoneId);

            // TODO: add checks for appropriate ignoring of zone restrictions and the bypass of ExpectNewClient calls this would require

            // Try to fetch the zone process
            ZoneProcess zp = GetZoneProcess(ztz.RequestedZoneId);
            if (zp == null)
                return 3;   // couldn't boot the zone for some reason, check the logs TODO: return different codes for No server free, zone full, etc.

            int retCode = zp.ZoneService.ExpectNewClient(ztz.CharId, ztz.ClientIp, ztz.IsLocalNet);
            if (retCode > 0)
            {
                _log.ErrorFormat("Attempt to zone to {0} failed with error code {1}", ztz.RequestedZoneId, retCode);
                return retCode;
            }

            _log.InfoFormat("ExpectNewClient successful for zone {0} during a ZoneToZone request", ztz.RequestedZoneId);

            // Update the who info for this client
            _authClientsLock.EnterWriteLock();
            try
            {
                ClientWho cw = _authClients[ztz.ClientIp];
                cw.ZoneId = ztz.RequestedZoneId;
            }
            catch (Exception e)
            {
                _log.Error("Error updating who information.", e);
            }
            finally
            {
                _authClientsLock.ExitWriteLock();
            }

            _log.DebugFormat("Who info updated for {0}", ztz.CharName);

            return 0;
        }