void CUser::ZoneChange(int zone, float x, float z)
{
	m_bZoneChangeFlag = true;

	C3DMap* pMap = nullptr;
	_ZONE_SERVERINFO *pInfo = nullptr;

	pMap = g_pMain->GetZoneByID(zone);
	if (!pMap) 
		return;

	if( pMap->m_bType == 2 ) {	// If Target zone is frontier zone.
		if( GetLevel() < 20 && g_pMain->m_byBattleOpen != SNOW_BATTLE)
			return;
	}

	if( g_pMain->m_byBattleOpen == NATION_BATTLE )	{		// Battle zone open
		if( m_bZone == BATTLE_ZONE )	{
			if( pMap->m_bType == 1 && m_bNation != zone && (zone < 10 || zone > 21))	{	// ???? ?????? ???? ????..
				if( m_bNation == KARUS && !g_pMain->m_byElmoradOpenFlag )	{
					TRACE("#### ZoneChange Fail ,,, id=%s, nation=%d, flag=%d\n", GetName().c_str(), m_bNation, g_pMain->m_byElmoradOpenFlag);
					return;
				}
				else if( m_bNation == ELMORAD && !g_pMain->m_byKarusOpenFlag )	{
					TRACE("#### ZoneChange Fail ,,, id=%s, nation=%d, flag=%d\n", GetName().c_str(), m_bNation, g_pMain->m_byKarusOpenFlag);
					return;
				}
			}
		}
		else if( pMap->m_bType == 1 && m_bNation != zone && (zone < 10 || zone > 21)) {		// ???? ?????? ???? ????..
			return;
		}
//
		else if( pMap->m_bType == 2 && zone == ZONE_RONARK_LAND ) {	 // You can't go to frontier zone when Battlezone is open.
			Packet result(WIZ_WARP_LIST, uint8(2));
			result << uint8(0);
			Send(&result);
			return;
		}
//
	}
	else if( g_pMain->m_byBattleOpen == SNOW_BATTLE )	{					// Snow Battle zone open
		if( pMap->m_bType == 1 && m_bNation != zone ) {		// ???? ?????? ???? ????..
			return;
		}
		else if( pMap->m_bType == 2 && (zone == ZONE_RONARK_LAND || zone == ZONE_BATTLE ) ) {			// You can't go to frontier zone when Battlezone is open.
			return;
		}
	}
	else	{					// Battle zone close
		if( pMap->m_bType == 1 && m_bNation != zone && (zone < 10 || zone > 21))
			return;
	}

	m_bWarp = 0x01;

	UserInOut(INOUT_OUT);

	if( m_bZone == ZONE_SNOW_BATTLE )	{
		//TRACE("ZoneChange - name=%s\n", m_id);
		SetMaxHp( 1 );
	}

	bool bSameZone = (GetZoneID() == zone);
	m_bZone = zone;
	m_curx = x;
	m_curz = z;

	if (!bSameZone)
	{
		SetZoneAbilityChange();

		// Reset the user's anger gauge when leaving the zone
		// Unknown if this is official behaviour, but it's logical.
		if (GetAngerGauge() > 0)
			UpdateAngerGauge(0);

		/* 
			Here we also send a clan packet with subopcode 0x16 (with a byte flag of 2) if war zone/Moradon
			or subopcode 0x17 (with nWarEnemyID) for all else
		*/
#if 0
		if (isInClan())
		{
			CKnights * pKnights = g_pMain->GetClanPtr(GetClanID());
			if (pKnights != nullptr
					&& pKnights->bKnightsWarStarted)
			{
				Packet clanPacket(WIZ_KNIGHTS_PROCESS);
				if ((GetZoneID() / 100) == 1
					|| GetZoneID() == 21)
					clanPacket << uint8(0x17) << uint8(2);
				else 
					clanPacket << uint16(0x16) << uint16(0 /*nWarEnemyID*/);

				Send(&clanPacket);
			}
		}
#endif


		if (GetZoneID() == ZONE_SNOW_BATTLE)
		{
			SetMaxHp();
		}

		if (isInParty())
			PartyRemove(GetSocketID());

		ResetWindows();
	}


	m_pMap = pMap;

	if( g_pMain->m_nServerNo != pMap->m_nServerNo ) {
		pInfo = g_pMain->m_ServerArray.GetData( pMap->m_nServerNo );
		if( !pInfo ) 
			return;

		UserDataSaveToAgent();

		m_bLogout = 2;	// server change flag
		SendServerChange(pInfo->strServerIP, 2);
		return;
	}
	
	SetRegion(GetNewRegionX(), GetNewRegionZ());

	Packet result(WIZ_ZONE_CHANGE, uint8(3)); // magic numbers, sigh.
	result << uint16(GetZoneID()) << GetSPosX() << GetSPosZ() << GetSPosY() << g_pMain->m_byOldVictory;
	Send(&result);

	if (!m_bZoneChangeSameZone)
	{
		m_sWhoKilledMe = -1;
		m_iLostExp = 0;
		m_bRegeneType = 0;
		m_tLastRegeneTime = 0;
		m_sBind = -1;
		InitType3();
		InitType4();
		CMagicProcess::CheckExpiredType9Skills(this, true);
		SetUserAbility();
	}	

	result.Initialize(AG_ZONE_CHANGE);
	result << GetSocketID() << GetZoneID();
	Send_AIServer(&result);

	m_bZoneChangeSameZone = false;
	m_bZoneChangeFlag = false;
}
void CUser::ZoneChange(uint16 sNewZone, float x, float z)
{
	C3DMap * pMap = g_pMain->GetZoneByID(sNewZone);
	if (pMap == nullptr) 
		return;

	ZoneChangeError errorReason;
	if (!CanChangeZone(pMap, errorReason))
	{
		Packet result;

		switch (errorReason)
		{
		case ZoneChangeErrorWrongLevel:
			/* this will depend on the zone */
			break;

		case ZoneChangeErrorWarActive:
			result.Initialize(WIZ_WARP_LIST);
			result << uint8(2) << uint8(4);
			Send(&result);
			break;

		case ZoneChangeErrorNeedLoyalty:
			/* does this have an error? */
			break;
		}

		return;
	}

	m_bWarp = true;
	m_bZoneChangeFlag = true;

	UserInOut(INOUT_OUT);

	if (sNewZone == ZONE_SNOW_BATTLE)
		SetMaxHp(1);

	if (GetZoneID() != sNewZone)
	{
		SetZoneAbilityChange();

		// Reset the user's anger gauge when leaving the zone
		// Unknown if this is official behaviour, but it's logical.
		if (GetAngerGauge() > 0)
			UpdateAngerGauge(0);

		/* 
			Here we also send a clan packet with subopcode 0x16 (with a byte flag of 2) if war zone/Moradon
			or subopcode 0x17 (with nWarEnemyID) for all else
		*/
#if 0
		if (isInClan())
		{
			CKnights * pKnights = g_pMain->GetClanPtr(GetClanID());
			if (pKnights != nullptr
					&& pKnights->bKnightsWarStarted)
			{
				Packet clanPacket(WIZ_KNIGHTS_PROCESS);
				if (pMap->isWarZone() || byNewZone == ZONE_MORADON)
					clanPacket << uint8(0x17) << uint8(2);
				else 
					clanPacket << uint16(0x16) << uint16(0 /*nWarEnemyID*/);

				Send(&clanPacket);
			}
		}
#endif


		if (sNewZone == ZONE_SNOW_BATTLE)
			SetMaxHp();

		if (isInParty())
			PartyRemove(GetSocketID());

		ResetWindows();
	}

	m_bZone = (uint8) sNewZone; // this is 2 bytes to support the warp data loaded from SMDs. It should not go above a byte, however.
	SetPosition(x, 0.0f, z);
	m_pMap = pMap;

	if (g_pMain->m_nServerNo != pMap->m_nServerNo)
	{
		_ZONE_SERVERINFO *pInfo = g_pMain->m_ServerArray.GetData(pMap->m_nServerNo);
		if (pInfo == nullptr) 
			return;

		UserDataSaveToAgent();

		m_bLogout = 2;	// server change flag
		SendServerChange(pInfo->strServerIP, 2);
		return;
	}
	
	SetRegion(GetNewRegionX(), GetNewRegionZ());

	Packet result(WIZ_ZONE_CHANGE, uint8(ZoneChangeTeleport));
	result << uint16(GetZoneID()) << GetSPosX() << GetSPosZ() << GetSPosY() << g_pMain->m_byOldVictory;
	Send(&result);

	if (!m_bZoneChangeSameZone)
	{
		m_sWhoKilledMe = -1;
		m_iLostExp = 0;
		m_bRegeneType = 0;
		m_tLastRegeneTime = 0;
		m_sBind = -1;
		InitType3();
		InitType4();
		CMagicProcess::CheckExpiredType9Skills(this, true);
		SetUserAbility();
	}	

	result.Initialize(AG_ZONE_CHANGE);
	result << GetSocketID() << GetZoneID();
	Send_AIServer(&result);

	m_bZoneChangeSameZone = false;
	m_bZoneChangeFlag = false;
}