Beispiel #1
0
/**
 * @brief	Determine if an NPC can attack the specified unit.
 *
 * @param	pTarget	The target we are attempting to attack.
 *
 * @return	true if we can attack, false if not.
 */
bool CNpc::CanAttack(Unit * pTarget)
{
	if (!Unit::CanAttack(pTarget))
		return false;
	
	// A nation of 0 indicates friendliness to all
	if (GetNation() == Nation::ALL)
		return false;

	// A nation of 3 indicates hostility to all (or friendliness to none)
	if (GetNation() == Nation::NONE)
		return true;

	// An NPC cannot attack a unit of the same nation
	return (GetNation() != pTarget->GetNation());
}
void CUser::GetUserInfo(Packet & pkt)
{
	CKnights *pKnights = NULL;
	pkt.SByte();

	pkt		<< GetName()
			<< uint16(GetNation()) << GetClanID() << getFame();

	pKnights = g_pMain->GetClanPtr(GetClanID());
	if (pKnights == NULL)
	{
		pkt	<< uint32(0) << uint16(0) << uint8(0) << uint16(-1) << uint32(0) << uint8(0);
	}
	else
	{
		pkt	<< uint16(pKnights->m_sAlliance)
				<< pKnights->m_strName
				<< pKnights->m_byGrade << pKnights->m_byRanking
				<< uint16(pKnights->m_sMarkVersion) // symbol/mark version
				<< uint16(pKnights->m_sCape) // cape ID
				<< pKnights->m_bCapeR << pKnights->m_bCapeG << pKnights->m_bCapeB << uint8(0) // this is stored in 4 bytes after all.
				// not sure what this is, but it (just?) enables the clan symbol on the cape 
				// value in dump was 9, but everything tested seems to behave as equally well...
				// we'll probably have to implement logic to respect requirements.
				<< uint8(1); 
	}

	pkt	<< GetLevel() << m_bRace << m_sClass
		<< GetSPosX() << GetSPosZ() << GetSPosY()
		<< m_bFace << m_nHair
		<< m_bResHpType << uint32(m_bAbnormalType)
		<< m_bNeedParty
		<< m_bAuthority
		<< m_bPartyLeader // is party leader (bool)
		<< m_bInvisibilityType // visibility state
		<< uint8(0) // team colour (i.e. in soccer, 0=none, 1=blue, 2=red)
		<< m_bIsHidingHelmet // either this is correct and items are super buggy, or it causes baldness. You choose.
		<< m_sDirection // direction 
		<< m_bIsChicken // chicken/beginner flag
		<< m_bRank // king flag
		<< m_bPersonalRank << m_bKnightsRank // NP ranks (total, monthly)
		<< m_sItemArray[BREAST].nNum << m_sItemArray[BREAST].sDuration << uint8(0)
		<< m_sItemArray[LEG].nNum << m_sItemArray[LEG].sDuration << uint8(0)
		<< m_sItemArray[HEAD].nNum << m_sItemArray[HEAD].sDuration << uint8(0)
		<< m_sItemArray[GLOVE].nNum << m_sItemArray[GLOVE].sDuration << uint8(0)
		<< m_sItemArray[FOOT].nNum << m_sItemArray[FOOT].sDuration << uint8(0)
		<< m_sItemArray[SHOULDER].nNum << m_sItemArray[SHOULDER].sDuration << uint8(0)
		<< m_sItemArray[RIGHTHAND].nNum << m_sItemArray[RIGHTHAND].sDuration << uint8(0)
		<< m_sItemArray[LEFTHAND].nNum << m_sItemArray[LEFTHAND].sDuration << uint8(0)
		<< m_sItemArray[CWING].nNum << m_sItemArray[CWING].sDuration << uint8(0)
		<< m_sItemArray[CTOP].nNum << m_sItemArray[CTOP].sDuration << uint8(0)
		<< m_sItemArray[CHELMET].nNum << m_sItemArray[CHELMET].sDuration << uint8(0)
		<< m_sItemArray[CRIGHT].nNum << m_sItemArray[CRIGHT].sDuration << uint8(0)
		<< m_sItemArray[CLEFT].nNum << m_sItemArray[CLEFT].sDuration << uint8(0)
		<< GetZoneID() << uint8(-1) << uint8(-1) << uint16(0) << uint16(0) << uint16(0);
}
Beispiel #3
0
/**
* @brief        Gets NPC information for use in various NPC packets.
*
* @param        pkt        The packet the information will be stored in.
*/
void CNpc::GetNpcInfo(Packet & pkt)
{
        pkt << GetProtoID()
                << uint8(isMonster() ? 1 : 2) // Monster = 1, NPC = 2 (need to use a better flag)
                << m_sPid
                << GetType()
                << m_iSellingGroup
                << m_sSize
                << m_iWeapon_1 << m_iWeapon_2
                // Monsters require 0 regardless, otherwise they'll act as NPCs.
                << uint8(isMonster() ? 0 : GetNation())
                << GetLevel()
                << GetSPosX() << GetSPosZ() << GetSPosY()
                << uint32(isGateOpen())
                << m_byObjectType
                << uint16(0) << uint16(0) // unknown
                << int16(m_byDirection);
}
void CUser::SelectCharacter(Packet & pkt)
{
	Packet result(WIZ_SEL_CHAR);
	uint8 bResult, bInit;

	if (isBanned())
	{
		Disconnect();
		return;
	}

	pkt >> bResult >> bInit;
	result << bResult;

	if (bResult == 0 || !GetZoneID()) 
		goto fail_return;

	m_pMap = g_pMain->GetZoneByID(GetZoneID());
	if (GetMap() == nullptr)
		goto fail_return;

	if (g_pMain->m_nServerNo != GetMap()->m_nServerNo)
	{
		_ZONE_SERVERINFO *pInfo = g_pMain->m_ServerArray.GetData(GetMap()->m_nServerNo);
		if (pInfo == nullptr) 
			goto fail_return;

		SendServerChange(pInfo->strServerIP, bInit);
		return;
	}

	if (!g_pMain->isWarOpen() && GetFame() == COMMAND_CAPTAIN)
		m_bFame = CHIEF;

	// Disallow players from relogging in the opposite nation's home zone when an invasion's not running.
	if (((GetZoneID() != GetNation() && GetZoneID() <= ZONE_ELMORAD && !g_pMain->m_byBattleOpen)
		// also disallow players from logging back into war zones that aren't currently active...
			|| (GetMap()->isWarZone() && !g_pMain->m_byBattleOpen)
			// Chaos, bdw and juraid montuain
			|| isInTempleEventZone()
			// Ronark Land, Ardream, RLB, Bifrost, Krowaz Dominion.
			|| (g_pMain->m_byBattleOpen && (GetZoneID() == ZONE_RONARK_LAND 
			|| GetZoneID() == ZONE_ARDREAM 
			|| GetZoneID() == ZONE_RONARK_LAND_BASE
			|| GetZoneID() == ZONE_BIFROST
			|| GetZoneID() == ZONE_KROWAZ_DOMINION))) && !isGM())
	{
		NativeZoneReturn();
		Disconnect();
		return;
	}

	SetLogInInfoToDB(bInit);

	result << GetZoneID() << GetSPosX() << GetSPosZ() << GetSPosY() << g_pMain->m_byOldVictory;
	m_bSelectedCharacter = true;
	Send(&result);

	SetUserAbility(false);

	if (GetLevel() > MAX_LEVEL) 
	{
		Disconnect();
		return;
	}

	m_iMaxExp = g_pMain->GetExpByLevel(GetLevel());
	SetRegion(GetNewRegionX(), GetNewRegionZ());

	if (GetClanID() == -1)
	{
		SetClanID(0);
		m_bFame = 0;
		return;
	}
	else if (GetClanID() != 0
		&& GetZoneID() > 2)
	{
		result.Initialize(WIZ_KNIGHTS_PROCESS);
		result << uint8(KNIGHTS_LIST_REQ) << GetClanID();
		g_pMain->AddDatabaseRequest(result, this);
	}
	return;

fail_return:
	Send(&result);
}
void CUser::GetUserInfo(Packet & pkt)
{
	pkt.SByte();
	pkt		<< GetName()
			<< uint16(GetNation()) << GetClanID() << GetFame();

	CKnights * pKnights = g_pMain->GetClanPtr(GetClanID());
	if (pKnights == nullptr)
	{
		pkt	<< uint32(0) << uint16(0) << uint8(0) << uint16(-1) << uint32(0) << uint8(0);
	}
	else
	{
		pkt	<< pKnights->GetAllianceID()
				<< pKnights->m_strName
				<< pKnights->m_byGrade << pKnights->m_byRanking
				<< uint16(pKnights->m_sMarkVersion) // symbol/mark version
				<< uint16(pKnights->m_sCape) // cape ID
				<< pKnights->m_bCapeR << pKnights->m_bCapeG << pKnights->m_bCapeB << uint8(0) // this is stored in 4 bytes after all.
				// not sure what this is, but it (just?) enables the clan symbol on the cape 
				// value in dump was 9, but everything tested seems to behave as equally well...
				// we'll probably have to implement logic to respect requirements.
				<< uint8(1); 
	}

	// There are two event-driven invisibility states; dispel on attack, and dispel on move.
	// These are handled primarily server-side; from memory the client only cares about value 1 (which we class as 'dispel on move').
	// As this is the only place where this flag is actually sent to the client, we'll just convert 'dispel on attack' 
	// back to 'dispel on move' as the client expects.
	uint8 bInvisibilityType = m_bInvisibilityType;
	if (bInvisibilityType == INVIS_DISPEL_ON_ATTACK)
		bInvisibilityType = INVIS_DISPEL_ON_MOVE;

	pkt	<< GetLevel() << m_bRace << m_sClass
		<< GetSPosX() << GetSPosZ() << GetSPosY()
		<< m_bFace << m_nHair
		<< m_bResHpType << uint32(m_bAbnormalType)
		<< m_bNeedParty
		<< m_bAuthority
		<< m_bPartyLeader // is party leader (bool)
		<< bInvisibilityType // visibility state
		<< uint8(0) // team colour (i.e. in soccer, 0=none, 1=blue, 2=red)
		<< m_bIsHidingHelmet // either this is correct and items are super buggy, or it causes baldness. You choose.
		<< m_sDirection // direction 
		<< m_bIsChicken // chicken/beginner flag
		<< m_bRank // king flag
		<< m_bPersonalRank << m_bKnightsRank // NP ranks (total, monthly)
		<< m_sItemArray[BREAST].nNum << m_sItemArray[BREAST].sDuration << uint8(0)
		<< m_sItemArray[LEG].nNum << m_sItemArray[LEG].sDuration << uint8(0)
		<< m_sItemArray[HEAD].nNum << m_sItemArray[HEAD].sDuration << uint8(0)
		<< m_sItemArray[GLOVE].nNum << m_sItemArray[GLOVE].sDuration << uint8(0)
		<< m_sItemArray[FOOT].nNum << m_sItemArray[FOOT].sDuration << uint8(0)
		<< m_sItemArray[SHOULDER].nNum << m_sItemArray[SHOULDER].sDuration << uint8(0)
		<< m_sItemArray[RIGHTHAND].nNum << m_sItemArray[RIGHTHAND].sDuration << uint8(0)
		<< m_sItemArray[LEFTHAND].nNum << m_sItemArray[LEFTHAND].sDuration << uint8(0)
		<< m_sItemArray[CWING].nNum << m_sItemArray[CWING].sDuration << uint8(0)
		<< m_sItemArray[CTOP].nNum << m_sItemArray[CTOP].sDuration << uint8(0)
		<< m_sItemArray[CHELMET].nNum << m_sItemArray[CHELMET].sDuration << uint8(0)
		<< m_sItemArray[CRIGHT].nNum << m_sItemArray[CRIGHT].sDuration << uint8(0)
		<< m_sItemArray[CLEFT].nNum << m_sItemArray[CLEFT].sDuration << uint8(0)
		<< GetZoneID() << uint8(-1) << uint8(-1) << uint16(0) << uint16(0) << uint16(0);
}
Beispiel #6
0
void CUser::Regene(uint8 regene_type, uint32 magicid /*= 0*/)
{
	ASSERT(GetMap() != nullptr);

	_OBJECT_EVENT* pEvent = nullptr;
	_HOME_INFO* pHomeInfo = nullptr;
	float x = 0.0f, z = 0.0f;

	if (!isDead())
		return;

	if (regene_type != 1 && regene_type != 2)
		regene_type = 1;

	if (regene_type == 2) 
	{
		magicid = 490041;	// The Stone of Ressurection magic ID

		// Is our level high enough to be able to resurrect using this skill?
		if (GetLevel() <= 5
			// Do we have enough resurrection stones?
				|| !RobItem(379006000, 3 * GetLevel()))
				return;
	}

	// If we're in a home zone, we'll want the coordinates from there. Otherwise, assume our own home zone.
	pHomeInfo = g_pMain->m_HomeArray.GetData(GetZoneID() <= ZONE_ELMORAD ? GetZoneID() : GetNation());
	if (pHomeInfo == nullptr)
		return;

	UserInOut(INOUT_OUT);

	pEvent = GetMap()->GetObjectEvent(m_sBind);	

	// If we're not using a spell to resurrect.
	if (magicid == 0) 
	{
		// Resurrect at a bind/respawn point
		if (pEvent != nullptr && pEvent->byLife == 1)
		{
			SetPosition(pEvent->fPosX + x, 0.0f, pEvent->fPosZ + z);
		}
		// Are we trying to respawn in a home zone?
		else if (GetZoneID() <= ZONE_ELMORAD) 
		{
			// Use the proper respawn area for our nation, as the opposite nation can
			// enter this zone at a war's invasion stage.
			if (GetNation() == KARUS) 
			{
				x = (float)(pHomeInfo->KarusZoneX + myrand(0, pHomeInfo->KarusZoneLX));
				z = (float)(pHomeInfo->KarusZoneZ + myrand(0, pHomeInfo->KarusZoneLZ));			
			}
			else 
			{
				x = (float)(pHomeInfo->ElmoZoneX + myrand(0, pHomeInfo->ElmoZoneLX));
				z = (float)(pHomeInfo->ElmoZoneZ + myrand(0, pHomeInfo->ElmoZoneLZ));
			}		
		}
		else
		{
			// If we're in a war zone (aside from snow wars, which apparently use different coords), use BattleZone coordinates.
			if (GetZoneID() != ZONE_SNOW_BATTLE 
				&& GetZoneID() == (ZONE_BATTLE_BASE + g_pMain->m_byBattleZone))
			{
				x = (float)(pHomeInfo->BattleZoneX + myrand(0, pHomeInfo->BattleZoneLX));
				z = (float)(pHomeInfo->BattleZoneZ + myrand(0, pHomeInfo->BattleZoneLZ));
			}
			// If we died in the Moradon arena, we need to spawn near the Arena.
			else if (GetZoneID() == ZONE_MORADON && isInArena())
			{
				x = (float)(MINI_ARENA_RESPAWN_X + myrand(-MINI_ARENA_RESPAWN_RADIUS, MINI_ARENA_RESPAWN_RADIUS));
				z = (float)(MINI_ARENA_RESPAWN_Z + myrand(-MINI_ARENA_RESPAWN_RADIUS, MINI_ARENA_RESPAWN_RADIUS));
			}
			// For all else, just grab the start position (/town coordinates) from the START_POSITION table.
			else
			{
				short sx, sz;
				GetStartPosition(sx, sz);

				x = sx;
				z = sz;
			}
		}

		SetPosition(x, 0.0f, z);

		m_bResHpType = USER_STANDING;	
		m_bRegeneType = REGENE_NORMAL;
	}
	else // we're respawning using a resurrect skill.
	{
		_MAGIC_TYPE5 * pType = g_pMain->m_Magictype5Array.GetData(magicid);     
		if (pType == nullptr)
			return;

		MSpChange(-m_iMaxMp); // reset us to 0 MP. 

		if (m_sWhoKilledMe == -1) 
			ExpChange((m_iLostExp * pType->bExpRecover) / 100); // Restore 

		m_bResHpType = USER_STANDING;
		m_bRegeneType = REGENE_MAGIC;
	}

	Packet result(WIZ_REGENE);
	result << GetSPosX() << GetSPosZ() << GetSPosY();
	Send(&result);

	HpChange(GetMaxHealth());

	m_tLastRegeneTime = UNIXTIME;
	m_sWhoKilledMe = -1;
	m_iLostExp = 0;

	if (magicid == 0)
		BlinkStart();

	if (!isBlinking())
	{
		result.Initialize(AG_USER_REGENE);
		result << GetSocketID() << m_sHp;
		Send_AIServer(&result);
	}

	SetRegion(GetNewRegionX(), GetNewRegionZ());

	UserInOut(INOUT_RESPAWN);		

	g_pMain->RegionUserInOutForMe(this);
	g_pMain->RegionNpcInfoForMe(this);

	InitializeStealth();
	SendUserStatusUpdate(USER_STATUS_DOT, USER_STATUS_CURE);
	SendUserStatusUpdate(USER_STATUS_POISON, USER_STATUS_CURE);

	if (isInArena())
		SendUserStatusUpdate(USER_STATUS_SPEED, USER_STATUS_CURE);

	RecastSavedMagic();

	// If we actually respawned (i.e. we weren't resurrected by a skill)...
	if (magicid == 0)
	{
		// In PVP zones (not war zones), we must kick out players if they no longer
		// have any national points.
		if (GetMap()->isNationPVPZone() 
			&& GetMap()->isWarZone()
			&& GetLoyalty() == 0)
			KickOutZoneUser(false);
	}
}
Beispiel #7
0
void CUser::Chat(Packet & pkt)
{
	Packet result;
	uint16 sessID;
	uint8 type = pkt.read<uint8>(), bNation;
	string chatstr, finalstr, strSender, * strMessage;

	bool isAnnouncement = false;

	if (isMuted())
		return;	

	pkt >> chatstr;
	if (chatstr.empty() || chatstr.size() > 128)
		return;

	// Process GM commands
	if (isGM() && ProcessChatCommand(chatstr))
		return;

#if 0 // Removed this - all it seems to do is cause chat to break for GMs (is it 19xx+ only?)
	if( isGM() && type == GENERAL_CHAT)
		type = 0x14;
#endif

	// Handle GM notice & announcement commands
	if (type == PUBLIC_CHAT || type == ANNOUNCEMENT_CHAT)
	{
		// Trying to use a GM command without authorisation? Bad player!
		if (!isGM())
			return;

		if (type == ANNOUNCEMENT_CHAT)
			type = WAR_SYSTEM_CHAT;

		// This is horrible, but we'll live with it for now.
		// Pull the notice string (#### NOTICE : %s ####) from the database.
		// Format the chat string around it, so our chat data is within the notice
		g_pMain->GetServerResource(IDP_ANNOUNCEMENT, &finalstr, chatstr.c_str());
		isAnnouncement = true;
	}


	if (isAnnouncement)
	{
		// GM notice/announcements show no name, so don't bother setting it.
		strMessage = &finalstr; // use the formatted message from the user
		bNation = KARUS; // arbitrary nation
		sessID = -1;
	}
	else
	{
		strMessage = &chatstr; // use the raw message from the user
		strSender = GetName(); // everything else uses a name, so set it
		bNation = GetNation();
		sessID = GetSocketID();
	}

	ChatPacket::Construct(&result, type, strMessage, &strSender, bNation, sessID);

	switch (type) 
	{
	case GENERAL_CHAT:
		g_pMain->Send_NearRegion(&result, GetMap(), GetRegionX(), GetRegionZ(), GetX(), GetZ());
		break;

	case PRIVATE_CHAT:
	{
		CUser *pUser = g_pMain->GetUserPtr(m_sPrivateChatUser);
		if (pUser != nullptr) 
			pUser->Send(&result);
	} break;

	case PARTY_CHAT:
		if (isInParty())
			g_pMain->Send_PartyMember(m_sPartyIndex, &result);
		break;

	case SHOUT_CHAT:
		if (m_sMp < (m_iMaxMp / 5))
			break;

		// Characters under level 35 require 3,000 coins to shout.
		if (!isGM()
			&& GetLevel() < 35
			&& !GoldLose(SHOUT_COIN_REQUIREMENT))
			break;

		MSpChange(-(m_iMaxMp / 5));
		SendToRegion(&result);
		break;

	case KNIGHTS_CHAT:
		if (isInClan())
			g_pMain->Send_KnightsMember(GetClanID(), &result);
		break;
	case PUBLIC_CHAT:
	case ANNOUNCEMENT_CHAT:
		if (isGM())
			g_pMain->Send_All(&result);
		break;
	case COMMAND_CHAT:
		if (GetFame() == COMMAND_CAPTAIN)
			g_pMain->Send_CommandChat(&result, m_bNation, this);
		break;
	case MERCHANT_CHAT:
		if (isMerchanting())
			SendToRegion(&result);
		break;
	case ALLIANCE_CHAT:
		if (isInClan())
		{
			CKnights *pKnights = g_pMain->GetClanPtr(GetClanID());
			if (pKnights != nullptr && pKnights->isInAlliance())
				g_pMain->Send_KnightsAlliance(pKnights->GetAllianceID(), &result);
		}
		break;
	case WAR_SYSTEM_CHAT:
		if (isGM())
			g_pMain->Send_All(&result);
		break;
	}
}
void CUser::Attack(Packet & pkt)
{
	Packet result;
	int16 sid = -1, tid = -1, damage, delaytime, distance;	
	uint8 bType, bResult;	
	
	CUser* pTUser = NULL;

	pkt >> bType >> bResult >> tid >> delaytime >> distance;

//	delaytime = delaytime / 100.0f;
//	distance = distance / 10.0f;

	if (isBlinking()
		|| isDead())
		return;

	_ITEM_TABLE *pTable = GetItemPrototype(RIGHTHAND);
	if (pTable == NULL) 
		return;
	
	// If you're holding a weapon, do a client-based (ugh, do not trust!) delay check.
	if (pTable 
		&& (delaytime < pTable->m_sDelay
			|| distance > pTable->m_sRange))
		return;	
	// Empty handed.
	else if (delaytime < 100)
		return;			

	// We're attacking a player...
	if (tid < NPC_BAND)
	{
		pTUser = g_pMain->GetUserPtr(tid);
 
		if (pTUser == NULL || pTUser->isDead() || pTUser->isBlinking()
				|| (pTUser->GetNation() == GetNation() && GetZoneID() != 48 /* TO-DO: implement better checks */)
				|| !isAttackZone()) 
			bResult = 0;
		else 
		{
			damage = GetDamage(pTUser, NULL);
			if (GetZoneID() == ZONE_SNOW_BATTLE && g_pMain->m_byBattleOpen == SNOW_BATTLE)
				damage = 0;		

			if (damage <= 0)
				bResult = 0;
			else 
			{
				// TO-DO: Move all this redundant code into appropriate event-based methods so that all the other cases don't have to copypasta (and forget stuff).
				pTUser->HpChange(-damage, this);
				if (pTUser->isDead())
					bResult = 2;

				ItemWoreOut(ATTACK, damage);
				pTUser->ItemWoreOut(DEFENCE, damage);
				SendTargetHP(0, tid, -damage);
			}
		}
	}
	// We're attacking an NPC...
	else if (tid >= NPC_BAND)
	{
		// AI hasn't loaded yet
		if (g_pMain->m_bPointCheckFlag == FALSE)	
			return;	

		CNpc *pNpc = g_pMain->m_arNpcArray.GetData(tid);		
		if (pNpc != NULL && pNpc->isAlive() 
			&& (pNpc->GetNation() == 0 || pNpc->GetNation() == GetNation()))
		{
			result.SetOpcode(AG_ATTACK_REQ);
			result	<< bType << bResult
					<< GetSocketID() << tid
					<< uint16(m_sTotalHit * m_bAttackAmount / 100)
					<< uint16(m_sTotalAc + m_sACAmount)
					<< m_sTotalHitrate /* this is actually a float. screwed up naming... */
					<< m_sTotalEvasionrate /* also a float */
					<< m_sItemAc
					<< m_bMagicTypeLeftHand << m_bMagicTypeRightHand
					<< m_sMagicAmountLeftHand << m_sMagicAmountRightHand;
			g_pMain->Send_AIServer(&result);	
			return;
		}
	}

	result.SetOpcode(WIZ_ATTACK);
	result << bType << bResult << GetSocketID() << tid;
	SendToRegion(&result);

	if (tid < NPC_BAND
		&& bResult == 2 // 2 means a player died.
		&& pTUser) 
	{
		pTUser->Send(&result);
		TRACE("*** User Attack Dead, id=%s, result=%d, type=%d, HP=%d\n", pTUser->m_pUserData->m_id, bResult, pTUser->m_bResHpType, pTUser->m_pUserData->m_sHp);
	}
}
void CUser::SelectCharacter(Packet & pkt)
{
	Packet result(WIZ_SEL_CHAR);
	uint8 bResult, bInit;

	if (isBanned())
	{
		Disconnect();
		return;
	}

	pkt >> bResult >> bInit;
	result << bResult;

	if (bResult == 0 || !GetZoneID()) 
		goto fail_return;

	m_pMap = g_pMain->GetZoneByID(GetZoneID());
	if (GetMap() == NULL)
		goto fail_return;

	// Temporarily convert the old quest storage format to the new one.
	// This won't be necessary when Aujard's out of the picture.
	m_questMap.clear();
	for (int i = 0, index = 0; i < m_pUserData->m_sQuestCount; i++)
	{
		uint16	sQuestID	= GetShort(m_pUserData->m_bstrQuest, index);
		uint8	bQuestState	= GetByte(m_pUserData->m_bstrQuest, index);

		m_questMap.insert(std::make_pair(sQuestID, bQuestState));
	}

	if (g_pMain->m_nServerNo != GetMap()->m_nServerNo)
	{
		_ZONE_SERVERINFO *pInfo = g_pMain->m_ServerArray.GetData(GetMap()->m_nServerNo);
		if (pInfo == NULL) 
			goto fail_return;

		SendServerChange(pInfo->strServerIP, bInit);
		return;
	}

	if (g_pMain->m_byBattleOpen == NO_BATTLE && getFame() == COMMAND_CAPTAIN)
		m_pUserData->m_bFame = CHIEF;

	if ((GetZoneID() != GetNation() && GetZoneID() < 3 && !g_pMain->m_byBattleOpen)
		|| (GetZoneID() == ZONE_BATTLE && (g_pMain->m_byBattleOpen != NATION_BATTLE))
		|| (GetZoneID() == ZONE_SNOW_BATTLE && (g_pMain->m_byBattleOpen != SNOW_BATTLE))
		|| (GetZoneID() == ZONE_FRONTIER && g_pMain->m_byBattleOpen))
	{
		NativeZoneReturn();
		Disconnect();
		return;
	}

	SetLogInInfoToDB(bInit);

	result << GetZoneID() << GetSPosX() << GetSPosZ() << GetSPosY() << g_pMain->m_byOldVictory;
	m_bSelectedCharacter = true;
	Send(&result);

	SetSlotItemValue();
	SetUserAbility(false);

	if (GetLevel() > MAX_LEVEL) 
	{
		Disconnect();
		return;
	}

	m_iMaxExp = g_pMain->GetExpByLevel(GetLevel());
	SetRegion(GetNewRegionX(), GetNewRegionZ());

	if (GetClanID() == -1)
	{
		SetClanID(0);
		m_pUserData->m_bFame = 0;
		return;
	}
	else if (GetClanID() != 0)
	{
		CKnights* pKnights = g_pMain->GetClanPtr( GetClanID() );
		if (pKnights != NULL)
		{
			g_pMain->m_KnightsManager.SetKnightsUser( GetClanID(), m_pUserData->m_id );
		}
		else if (GetZoneID() > 2)
		{
			result.Initialize(WIZ_KNIGHTS_PROCESS);
			result << uint8(KNIGHTS_LIST_REQ) << GetClanID();
			g_pMain->m_LoggerSendQueue.PutData(&result, GetSocketID());
		}
	}
	return;

fail_return:
	Send(&result);
}
void CUser::GetUserInfo(Packet & pkt)
{
	pkt.SByte();
	pkt
		<< GetName()
		<< GetNation()
		<< GetClanID()
		<< GetFame();

	CKnights * pKnights = g_pMain->GetClanPtr(GetClanID());
	if (pKnights == nullptr)
	{
		//pkt /*<< uint8(0)*/ << uint16(0) << uint8(0) << uint8(0);
		pkt << uint32(0) << uint16(0) << uint8(0) << uint16(-1);
	}
	else
	{
		pkt << pKnights->GetAllianceID()
			<< pKnights->m_strName
			<< pKnights->m_byGrade << pKnights->m_byRanking
			<< uint16(pKnights->m_sMarkVersion) // symbol/mark version
			<< pKnights->GetCapeID(pKnights); // cape ID 
	}

	// There are two event-driven invisibility states; dispel on attack, and dispel on move.
	// These are handled primarily server-side; from memory the client only cares about value 1 (which we class as 'dispel on move').
	// As this is the only place where this flag is actually sent to the client, we'll just convert 'dispel on attack' 
	// back to 'dispel on move' as the client expects.
	uint8 bInvisibilityType = m_bInvisibilityType;
	if (bInvisibilityType != INVIS_NONE)
		bInvisibilityType = INVIS_DISPEL_ON_MOVE;

	pkt << GetLevel() << m_bRace << m_sClass
		<< GetSPosX() << GetSPosZ() << GetSPosY()
		<< m_bFace << m_nHair
		<< m_bResHpType << uint32(m_bAbnormalType)//uint8(m_bAbnormalType)
		<< m_bNeedParty
		<< m_bAuthority
		<< m_bPartyLeader // is party leader (bool)
		<< bInvisibilityType // visibility state
		//<< uint8(m_teamColour) // team colour (i.e. in soccer, 0=none, 1=blue, 2=red)
		//<< m_bIsHidingHelmet // either this is correct and items are super buggy, or it causes baldness. You choose.
		<< m_sDirection // direction
		<< m_bIsChicken // chicken/beginner flag
		<< m_bRank // king flag
		<< m_bKnightsRank << m_bPersonalRank; // NP ranks (total, monthly)

	uint8 equippedItems[] =
	{
		BREAST, LEG, HEAD, GLOVE, FOOT, SHOULDER, RIGHTHAND, LEFTHAND, CTOP, CHELMET
	};

	foreach_array(i, equippedItems)
	{
		_ITEM_DATA * pItem = GetItem(equippedItems[i]);

		if (pItem == nullptr)
			continue;

		pkt << pItem->nNum << pItem->sDuration << pItem->bFlag;
	}
void CUser::SelectCharacter(Packet & pkt)
{
	Packet result(WIZ_SEL_CHAR);
	uint8 bResult, bInit;

	if (isBanned())
	{
		Disconnect();
		return;
	}

	pkt >> bResult >> bInit;
	result << bResult;

	if (bResult == 0 || !GetZoneID()) 
		goto fail_return;

	m_pMap = g_pMain->GetZoneByID(GetZoneID());
	if (GetMap() == nullptr)
		goto fail_return;

	if (g_pMain->m_nServerNo != GetMap()->m_nServerNo)
	{
		_ZONE_SERVERINFO *pInfo = g_pMain->m_ServerArray.GetData(GetMap()->m_nServerNo);
		if (pInfo == nullptr) 
			goto fail_return;

		SendServerChange(pInfo->strServerIP, bInit);
		return;
	}

	if (g_pMain->m_byBattleOpen == NO_BATTLE && GetFame() == COMMAND_CAPTAIN)
		m_bFame = CHIEF;

	if ((GetZoneID() != GetNation() && GetZoneID() < 3 && !g_pMain->m_byBattleOpen)
		|| (GetZoneID() == ZONE_BATTLE && (g_pMain->m_byBattleOpen != NATION_BATTLE))
		|| (GetZoneID() == ZONE_SNOW_BATTLE && (g_pMain->m_byBattleOpen != SNOW_BATTLE))
		|| (GetZoneID() == ZONE_RONARK_LAND && g_pMain->m_byBattleOpen))
	{
		NativeZoneReturn();
		Disconnect();
		return;
	}

	SetLogInInfoToDB(bInit);

	result << GetZoneID() << GetSPosX() << GetSPosZ() << GetSPosY() << g_pMain->m_byOldVictory;
	m_bSelectedCharacter = true;
	Send(&result);

	SetSlotItemValue();
	SetUserAbility(false);

	if (GetLevel() > MAX_LEVEL) 
	{
		Disconnect();
		return;
	}

	m_iMaxExp = g_pMain->GetExpByLevel(GetLevel());
	SetRegion(GetNewRegionX(), GetNewRegionZ());

	if (GetClanID() == -1)
	{
		SetClanID(0);
		m_bFame = 0;
		return;
	}
	else if (GetClanID() != 0
		&& GetZoneID() > 2)
	{
		result.Initialize(WIZ_KNIGHTS_PROCESS);
		result << uint8(KNIGHTS_LIST_REQ) << GetClanID();
		g_pMain->AddDatabaseRequest(result, this);
	}
	return;

fail_return:
	Send(&result);
}
void CUser::Chat(Packet & pkt)
{
	Packet result(WIZ_CHAT);
	uint8 type = pkt.read<uint8>();
	char finalstr[1024] = ""; 
	std::string buff, chatstr;
	bool isAnnouncement = false;

	if (isMuted())
		return;	

	pkt >> chatstr;
	if (chatstr.empty() || chatstr.size() > 128)
		return;

	// Process GM commands
	if (isGM() && ProcessChatCommand(chatstr))
		return;

#if 0 // Removed this - all it seems to do is cause chat to break for GMs (is it 19xx+ only?)
	if( isGM() && type == GENERAL_CHAT)
		type = 0x14;
#endif

	uint8 bNation = GetNation();
	uint16 sessID = GetSocketID();

	// Handle GM notice & announcement commands
	if (type == PUBLIC_CHAT || type == ANNOUNCEMENT_CHAT)
	{
		// Trying to use a GM command without authorisation? Bad player!
		if (!isGM())
			return;

		if (type == ANNOUNCEMENT_CHAT)
			type = WAR_SYSTEM_CHAT;

		// This is horrible, but we'll live with it for now.
		// Pull the notice string (#### NOTICE : %s ####) from the database.
		CString noticeText = g_pMain->GetServerResource(IDP_ANNOUNCEMENT);
		
		// Format the chat string around it, so our chat data is within the notice
		sprintf_s(finalstr, sizeof(finalstr), noticeText, chatstr.c_str());

		bNation = KARUS; // arbitrary nation
		sessID = -1;
		isAnnouncement = true;
	}

	result.SByte();
	result << type << bNation << sessID;
	if (isAnnouncement)
	{
		result << uint8(0); // GM notice/announcements show no name (so specify length of 0)
		result.DByte();
		result << finalstr; // now tack on the formatted message from the user
	}
	else
	{
		result << m_pUserData.m_id; // everything else provides a name
		result.DByte();
		result << chatstr; // now tack on the chat message from the user
	}

	switch (type) 
	{
	case GENERAL_CHAT:
		g_pMain->Send_NearRegion(&result, GetMap(), GetRegionX(), GetRegionZ(), GetX(), GetZ());
		break;

	case PRIVATE_CHAT:
	{
		if (m_sPrivateChatUser == GetSocketID()) 
			break;

		CUser *pUser = g_pMain->GetUserPtr(m_sPrivateChatUser);
		if (pUser != NULL) 
			pUser->Send(&result);
	} break;

	case PARTY_CHAT:
		if (isInParty())
			g_pMain->Send_PartyMember(m_sPartyIndex, &result);
		break;

	case SHOUT_CHAT:
		if (m_pUserData.m_sMp < (m_iMaxMp / 5))
			break;

		// Characters under level 35 require 3,000 coins to shout.
		if (!isGM()
			&& GetLevel() < 35
			&& !GoldLose(SHOUT_COIN_REQUIREMENT))
			break;

		MSpChange(-(m_iMaxMp / 5));
		SendToRegion(&result);
		break;

	case KNIGHTS_CHAT:
		if (isInClan())
			g_pMain->Send_KnightsMember(GetClanID(), &result);
		break;
	case PUBLIC_CHAT:
	case ANNOUNCEMENT_CHAT:
		if (isGM())
			g_pMain->Send_All(&result);
		break;
	case COMMAND_CHAT:
		if (getFame() == COMMAND_CAPTAIN)
			g_pMain->Send_CommandChat(&result, m_pUserData.m_bNation, this);
		break;
	case MERCHANT_CHAT:
		if (isMerchanting())
			SendToRegion(&result);
		break;
	case WAR_SYSTEM_CHAT:
		if (isGM())
			g_pMain->Send_All(&result);
		break;
	}
}
bool CUser::CanChangeZone(C3DMap * pTargetMap, ZoneChangeError & errorReason)
{
	// While unofficial, game masters should be allowed to teleport anywhere.
	if (isGM())
		return true;

	// Generic error reason; this should only be checked when the method returns false.
	errorReason = ZoneChangeErrorGeneric;

	// Ensure the user meets the zone's level requirements
	if (GetLevel() < pTargetMap->GetMinLevelReq()
		|| GetLevel() > pTargetMap->GetMaxLevelReq())
	{
		errorReason = ZoneChangeErrorWrongLevel;
		return false;
	}

	switch (pTargetMap->GetID())
	{
	case ZONE_ELMORAD:
	case ZONE_KARUS:
		// Users may enter Luferson (1)/El Morad (2) if they are that nation, 
		if (GetNation() == pTargetMap->GetID())
			return true;

		// Users may also enter if there's a war invasion happening in that zone.
		if (GetNation() == KARUS)
			return g_pMain->m_byElmoradOpenFlag;
		else
			return g_pMain->m_byKarusOpenFlag;

	case ZONE_KARUS_ESLANT:
		return GetNation() == KARUS;

	case ZONE_ELMORAD_ESLANT:
		return GetNation() == ELMORAD;

	// Delos (30) may be entered by anybody, unless CSW is started -- in which case, it should only allow members of the clans competing for the castle (right?).
	case ZONE_DELOS: // TO-DO: implement CSW logic
		return true;

	// Bifrost (31) may only be entered if the zone is open by the event. 
	// If it's open, it should only be open for the zone in control of the monument in the first stage. 
	// After that, it's open to both zones (note: this extended logic won't be implemented until we actually implement the event -- #84).
	case ZONE_BIFROST: // TO-DO: implement Bifrost logic
		return true;

	case ZONE_RONARK_LAND:
	case ZONE_ARDREAM:
		// PVP zones such as Ronark Land/Ardream (do we include both?) may only be entered if a war is not started. 
		if (g_pMain->m_byBattleOpen != NO_BATTLE)
		{
			errorReason = ZoneChangeErrorWarActive;
			return false;
		}

		// They may also not be entered if the user has no NP.
		if (GetLoyalty() <= 0)
		{
			errorReason = ZoneChangeErrorNeedLoyalty;
			return false;
		}
		break;

	default:
		// War zones may only be entered if that war zone is active.
		if (pTargetMap->isWarZone())
		{
			if ((ZONE_BATTLE_BASE - pTargetMap->GetID()) != g_pMain->m_byBattleZone)
				return false;
		}
	}

	return true;
}