 92c59963f8
			
		
	
	
		92c59963f8
		
	
	
	
	
		
			
			git-svn-id: http://mc-server.googlecode.com/svn/trunk@606 0a769ca7-a7f5-676a-18bf-c427514a06d6
		
			
				
	
	
		
			1063 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1063 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| 
 | |
| #include "Globals.h"  // NOTE: MSVC stupidness requires this to be the same across all modules
 | |
| 
 | |
| #include "cPlayer.h"
 | |
| #include "cServer.h"
 | |
| #include "cCreativeInventory.h"
 | |
| #include "cSurvivalInventory.h"
 | |
| #include "cClientHandle.h"
 | |
| #include "cWorld.h"
 | |
| #include "cPickup.h"
 | |
| #include "cPluginManager.h"
 | |
| #include "cWindow.h"
 | |
| #include "cBlockEntity.h"
 | |
| #include "cGroupManager.h"
 | |
| #include "cGroup.h"
 | |
| #include "cChatColor.h"
 | |
| #include "cItem.h"
 | |
| #include "cTracer.h"
 | |
| #include "cRoot.h"
 | |
| #include "cMakeDir.h"
 | |
| #include "cTimer.h"
 | |
| #include "MersenneTwister.h"
 | |
| 
 | |
| #include "packets/cPacket_NamedEntitySpawn.h"
 | |
| #include "packets/cPacket_EntityLook.h"
 | |
| #include "packets/cPacket_TeleportEntity.h"
 | |
| #include "packets/cPacket_RelativeEntityMove.h"
 | |
| #include "packets/cPacket_RelativeEntityMoveLook.h"
 | |
| #include "packets/cPacket_UpdateHealth.h"
 | |
| #include "packets/cPacket_Respawn.h"
 | |
| #include "packets/cPacket_DestroyEntity.h"
 | |
| #include "packets/cPacket_Metadata.h"
 | |
| #include "packets/cPacket_Chat.h"
 | |
| #include "packets/cPacket_NewInvalidState.h"
 | |
| #include "packets/cPacket_BlockAction.h"
 | |
| 
 | |
| #include "Vector3d.h"
 | |
| #include "Vector3f.h"
 | |
| 
 | |
| #include "../iniFile/iniFile.h"
 | |
| #include <json/json.h>
 | |
| 
 | |
| #define float2int(x) ((x)<0 ? ((int)(x))-1 : (int)(x))
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| CLASS_DEFINITION( cPlayer, cPawn );
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName)
 | |
| 	: m_GameMode( eGameMode_Survival )
 | |
| 	, m_IP("")
 | |
| 	, m_LastBlockActionTime( 0 )
 | |
| 	, m_LastBlockActionCnt( 0 )
 | |
| 	, m_bVisible( true )
 | |
| 	, m_LastGroundHeight( 0 )
 | |
| 	, m_bTouchGround( false )
 | |
| 	, m_Stance( 0.0 )
 | |
| 	, m_Inventory( 0 )
 | |
| 	, m_CurrentWindow( 0 )
 | |
| 	, m_TimeLastPickupCheck( 0.f )
 | |
| 	, m_Color('-')
 | |
| 	, m_ClientHandle( a_Client )
 | |
| {
 | |
| 	LOGD("Created a player object for \"%s\" @ \"%s\" at %p, ID %d", 
 | |
| 		a_PlayerName.c_str(), a_Client->GetSocket().GetIPString().c_str(),
 | |
| 		this, GetUniqueID()
 | |
| 	);
 | |
| 	m_EntityType = eEntityType_Player;
 | |
| 	SetMaxHealth(20);
 | |
| 	SetMaxFoodLevel(125);
 | |
| 	m_Inventory = new cSurvivalInventory( this );
 | |
| 	m_CreativeInventory = new cCreativeInventory(this);
 | |
| 	cTimer t1;
 | |
| 	m_LastPlayerListTime = t1.GetNowTime();
 | |
| 
 | |
| 	m_TimeLastTeleportPacket = cWorld::GetTime();
 | |
| 	m_TimeLastPickupCheck = cWorld::GetTime();
 | |
| 	
 | |
| 	m_PlayerName = a_PlayerName;
 | |
| 	m_bDirtyPosition = true; // So chunks are streamed to player at spawn
 | |
| 
 | |
| 	if( !LoadFromDisk() )
 | |
| 	{
 | |
| 		m_Inventory->Clear();
 | |
| 		m_CreativeInventory->Clear();
 | |
| 		m_Pos.x = cRoot::Get()->GetDefaultWorld()->GetSpawnX();
 | |
| 		m_Pos.y = cRoot::Get()->GetDefaultWorld()->GetSpawnY();
 | |
| 		m_Pos.z = cRoot::Get()->GetDefaultWorld()->GetSpawnZ();
 | |
| 		
 | |
| 		LOGD("Player \"%s\" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f}",
 | |
| 			a_PlayerName.c_str(), m_Pos.x, m_Pos.y, m_Pos.z
 | |
| 		);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::Initialize( cWorld* a_World )
 | |
| {
 | |
| 	cPawn::Initialize( a_World );
 | |
| 	GetWorld()->AddPlayer( this );
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| cPlayer::~cPlayer(void)
 | |
| {
 | |
| 	LOG("Deleting cPlayer \"%s\" at %p, ID %d", m_PlayerName.c_str(), this, GetUniqueID());
 | |
| 	
 | |
| 	SaveToDisk();
 | |
| 
 | |
| 	m_World->RemovePlayer( this );
 | |
| 
 | |
| 	m_ClientHandle = NULL;
 | |
| 	
 | |
| 	delete m_Inventory;
 | |
| 	m_Inventory = NULL;
 | |
| 
 | |
| 	delete m_CreativeInventory;
 | |
| 	
 | |
| 	LOG("Player %p deleted", this);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::Destroyed()
 | |
| {
 | |
| 	CloseWindow(-1);
 | |
| 	m_ClientHandle = NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| cPacket * cPlayer::GetSpawnPacket(void) const
 | |
| {
 | |
| 	LOGD("cPlayer::GetSpawnPacket for \"%s\" at pos {%.2f, %.2f, %.2f}",
 | |
| 		m_PlayerName.c_str(), m_Pos.x, m_Pos.y, m_Pos.z
 | |
| 	);
 | |
| 	
 | |
| 	if (!m_bVisible )
 | |
| 	{
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	
 | |
| 	cPacket_NamedEntitySpawn * SpawnPacket = new cPacket_NamedEntitySpawn;
 | |
| 	SpawnPacket->m_UniqueID    = m_UniqueID;
 | |
| 	SpawnPacket->m_PlayerName  = m_PlayerName;
 | |
| 	SpawnPacket->m_PosX        = (int)(m_Pos.x * 32);
 | |
| 	SpawnPacket->m_PosY        = (int)(m_Pos.y * 32);
 | |
| 	SpawnPacket->m_PosZ        = (int)(m_Pos.z * 32);
 | |
| 	SpawnPacket->m_Rotation    = (char)((m_Rot.x / 360.f) * 256);
 | |
| 	SpawnPacket->m_Pitch       = (char)((m_Rot.y / 360.f) * 256);
 | |
| 	short ItemID = (short)m_Inventory->GetEquippedItem().m_ItemID;
 | |
| 	SpawnPacket->m_CurrentItem = (ItemID > 0) ? ItemID : 0;  // Unlike -1 in inventory, the named entity packet uses 0 for "none"
 | |
| 	return SpawnPacket;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::Tick(float a_Dt)
 | |
| {
 | |
| 	if (!m_ClientHandle->IsPlaying())
 | |
| 	{
 | |
| 		// We're not yet in the game, ignore everything
 | |
| 		return;
 | |
| 	}
 | |
| 	
 | |
| 	cPawn::Tick(a_Dt);
 | |
| 
 | |
| 	if (m_bDirtyOrientation && !m_bDirtyPosition)
 | |
| 	{
 | |
| 		cPacket_EntityLook EntityLook(*this);
 | |
| 		m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, EntityLook, m_ClientHandle );
 | |
| 		cPacket_EntityHeadLook EntityHeadLook(*this);
 | |
| 		m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, EntityHeadLook, m_ClientHandle);
 | |
| 		m_bDirtyOrientation = false;
 | |
| 	}
 | |
| 	else if (m_bDirtyPosition )
 | |
| 	{
 | |
| 		cRoot::Get()->GetPluginManager()->CallHook( cPluginManager::E_PLUGIN_PLAYER_MOVE, 1, this );
 | |
| 
 | |
| 		float DiffX = (float)(GetPosX() - m_LastPosX );
 | |
| 		float DiffY = (float)(GetPosY() - m_LastPosY );
 | |
| 		float DiffZ = (float)(GetPosZ() - m_LastPosZ );
 | |
| 		float SqrDist = DiffX * DiffX + DiffY * DiffY + DiffZ * DiffZ;
 | |
| 		if (
 | |
| 			(SqrDist > 4 * 4) ||  // 4 blocks is max Relative Move
 | |
| 			(cWorld::GetTime() - m_TimeLastTeleportPacket > 2 )  // Send an absolute position every 2 seconds
 | |
| 		)
 | |
| 		{
 | |
| 			//LOG("Teleported %f", sqrtf(SqrDist) );
 | |
| 			cPacket_TeleportEntity TeleportEntity( this );
 | |
| 			m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, TeleportEntity, m_ClientHandle);
 | |
| 			m_TimeLastTeleportPacket = cWorld::GetTime();
 | |
| 		}
 | |
| 		else
 | |
| 		{	// Relative move sucks balls! It's always wrong wtf!
 | |
| 			if( m_bDirtyOrientation )
 | |
| 			{
 | |
| 				cPacket_RelativeEntityMoveLook RelativeEntityMoveLook;
 | |
| 				RelativeEntityMoveLook.m_UniqueID = GetUniqueID();
 | |
| 				RelativeEntityMoveLook.m_MoveX = (char)(DiffX*32);
 | |
| 				RelativeEntityMoveLook.m_MoveY = (char)(DiffY*32);
 | |
| 				RelativeEntityMoveLook.m_MoveZ = (char)(DiffZ*32);
 | |
| 				RelativeEntityMoveLook.m_Yaw = (char)((GetRotation()/360.f)*256);
 | |
| 				RelativeEntityMoveLook.m_Pitch    = (char)((GetPitch()/360.f)*256);
 | |
| 				m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, RelativeEntityMoveLook, m_ClientHandle );
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				cPacket_RelativeEntityMove RelativeEntityMove;
 | |
| 				RelativeEntityMove.m_UniqueID = GetUniqueID();
 | |
| 				RelativeEntityMove.m_MoveX = (char)(DiffX*32);
 | |
| 				RelativeEntityMove.m_MoveY = (char)(DiffY*32);
 | |
| 				RelativeEntityMove.m_MoveZ = (char)(DiffZ*32);
 | |
| 				m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, RelativeEntityMove, m_ClientHandle );
 | |
| 			}
 | |
| 		}
 | |
| 		m_LastPosX = GetPosX();
 | |
| 		m_LastPosY = GetPosY();
 | |
| 		m_LastPosZ = GetPosZ();
 | |
| 		m_bDirtyPosition = false;
 | |
| 		m_ClientHandle->StreamChunks();
 | |
| 	}
 | |
| 
 | |
| 	if (m_Health > 0) // make sure player is alive
 | |
| 	{
 | |
| 		m_World->CollectPickupsByPlayer(this);
 | |
| 	}
 | |
| 	
 | |
| 	cTimer t1;
 | |
| 	// Send Player List (Once per m_LastPlayerListTime/1000 ms)
 | |
| 	if (m_LastPlayerListTime + cPlayer::PLAYER_LIST_TIME_MS <= t1.GetNowTime())
 | |
| 	{
 | |
| 		m_World->SendPlayerList(this);
 | |
| 		m_LastPlayerListTime = t1.GetNowTime();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::SetTouchGround( bool a_bTouchGround )
 | |
| {
 | |
| 	m_bTouchGround = a_bTouchGround;
 | |
| 
 | |
| 	if( !m_bTouchGround )
 | |
| 	{
 | |
| 		cWorld* World = GetWorld();
 | |
| 		char BlockID = World->GetBlock( float2int(m_Pos.x), float2int(m_Pos.y), float2int(m_Pos.z) );
 | |
| 		if( BlockID != E_BLOCK_AIR )
 | |
| 		{
 | |
| 			m_bTouchGround = true;
 | |
| 		}
 | |
| 		if( BlockID == E_BLOCK_WATER || BlockID == E_BLOCK_STATIONARY_WATER || BlockID == E_BLOCK_LADDER || BlockID == E_BLOCK_TORCH )
 | |
| 		{
 | |
| 			m_LastGroundHeight = (float)m_Pos.y;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if( m_bTouchGround )
 | |
| 	{
 | |
| 		float Dist = (float)(m_LastGroundHeight - m_Pos.y);
 | |
| 		if( Dist > 4.f ) // Player dropped
 | |
| 		{
 | |
| 			int Damage = (int)(Dist - 4.f);
 | |
| 			if( Damage > 0 )
 | |
| 			{
 | |
| 				TakeDamage( Damage, 0 );
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		m_LastGroundHeight = (float)m_Pos.y;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void cPlayer::Heal( int a_Health )
 | |
| {
 | |
| 	if( m_Health < GetMaxHealth() )
 | |
| 	{
 | |
| 		m_Health = (short) MIN(a_Health + m_Health, GetMaxHealth());
 | |
| 
 | |
| 		cPacket_UpdateHealth Health;
 | |
| 		Health.m_Health = m_Health;
 | |
| 		Health.m_Food = GetFood();
 | |
| 		Health.m_Saturation = GetFoodSaturation();
 | |
| 		m_ClientHandle->Send( Health );
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| bool cPlayer::Feed(short a_Food)
 | |
| {
 | |
| 	if (m_FoodLevel >= GetMaxFoodLevel())
 | |
| 	{
 | |
| 		return false;
 | |
| 	}
 | |
| 	
 | |
| 	m_FoodLevel = MIN(a_Food + m_FoodLevel, GetMaxFoodLevel());
 | |
| 
 | |
| 	cPacket_UpdateHealth Health;
 | |
| 	Health.m_Health = m_Health;
 | |
| 	Health.m_Food = GetFood();
 | |
| 	Health.m_Saturation = GetFoodSaturation();
 | |
| 	m_ClientHandle->Send( Health );
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::TakeDamage( int a_Damage, cEntity* a_Instigator )
 | |
| {
 | |
| 	if ( !(m_GameMode == 1) ) {
 | |
| 		cPawn::TakeDamage( a_Damage, a_Instigator );
 | |
| 
 | |
| 		cPacket_UpdateHealth Health;
 | |
| 		Health.m_Health = m_Health;
 | |
| 		Health.m_Food = GetFood();
 | |
| 		Health.m_Saturation = GetFoodSaturation();
 | |
| 		//TODO: Causes problems sometimes O.o (E.G. Disconnecting when attacked)
 | |
| 		if(m_ClientHandle != 0)
 | |
| 			m_ClientHandle->Send( Health );
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::KilledBy(cEntity * a_Killer)
 | |
| {
 | |
| 	cPawn::KilledBy(a_Killer);
 | |
| 
 | |
| 	if (m_Health > 0)
 | |
| 	{
 | |
| 		return; //  not dead yet =]
 | |
| 	}
 | |
| 
 | |
| 	m_bVisible = false; // So new clients don't see the player
 | |
| 
 | |
| 	// Puke out all the items
 | |
| 	cItem* Items = m_Inventory->GetSlots();
 | |
| 	cItems Pickups;
 | |
| 	for (unsigned int i = 1; i < m_Inventory->c_NumSlots; ++i)
 | |
| 	{
 | |
| 		if( !Items[i].IsEmpty() )
 | |
| 		{
 | |
| 			Pickups.push_back(Items[i]);
 | |
| 		}
 | |
| 		Items[i].Empty();
 | |
| 	}
 | |
| 	m_World->SpawnItemPickups(Pickups, m_Pos.x, m_Pos.y, m_Pos.z, 10);
 | |
| 	SaveToDisk(); // Save it, yeah the world is a tough place !
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::Respawn()
 | |
| {
 | |
| 	m_Health = GetMaxHealth();
 | |
| 
 | |
| 	// Create Respawn player packet
 | |
| 	cPacket_Respawn Packet;
 | |
| 	//Set Gamemode for packet by looking at world's gamemode (Need to check players gamemode.)
 | |
| 	//Packet.m_CreativeMode = (char)GetWorld()->GetGameMode();
 | |
| 	Packet.m_CreativeMode = (char)m_GameMode; //Set GameMode packet based on Player's GameMode;
 | |
| 
 | |
| 	//Send Packet
 | |
| 	m_ClientHandle->Send( Packet );
 | |
| 	
 | |
| 	//Set non Burning
 | |
| 	SetMetaData(NORMAL);
 | |
| 
 | |
| 	TeleportTo( GetWorld()->GetSpawnX(), GetWorld()->GetSpawnY(), GetWorld()->GetSpawnZ() );
 | |
| 
 | |
| 	SetVisible( true );
 | |
| }
 | |
| 
 | |
| double cPlayer::GetEyeHeight()
 | |
| {
 | |
| 	return m_Stance;
 | |
| }
 | |
| 
 | |
| Vector3d cPlayer::GetEyePosition()
 | |
| {
 | |
| 	return Vector3d( m_Pos.x, m_Stance, m_Pos.z );
 | |
| }
 | |
| 
 | |
| void cPlayer::OpenWindow( cWindow* a_Window )
 | |
| {
 | |
| 	CloseWindow(m_CurrentWindow ? (char)m_CurrentWindow->GetWindowType() : 0);
 | |
| 	a_Window->Open( *this );
 | |
| 	m_CurrentWindow = a_Window;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::CloseWindow(char a_WindowType)
 | |
| {
 | |
| 	if (a_WindowType == 0)
 | |
| 	{
 | |
| 		// Inventory
 | |
| 		if (
 | |
| 			(m_Inventory->GetWindow()->GetDraggingItem() != NULL) && 
 | |
| 			(m_Inventory->GetWindow()->GetDraggingItem()->m_ItemCount > 0)
 | |
| 		)
 | |
| 		{
 | |
| 			LOG("Player holds item! Dropping it...");
 | |
| 			TossItem( true, m_Inventory->GetWindow()->GetDraggingItem()->m_ItemCount );
 | |
| 		}
 | |
| 
 | |
| 		//Drop whats in the crafting slots (1, 2, 3, 4)
 | |
| 		cItems Drops;
 | |
| 		for (int i = 1; i <= 4; i++)
 | |
| 		{
 | |
| 			cItem* Item = m_Inventory->GetSlot( i );
 | |
| 			if (!Item->IsEmpty())
 | |
| 			{
 | |
| 				Drops.push_back(*Item);
 | |
| 			}
 | |
| 			Item->Empty();
 | |
| 		}
 | |
| 		float vX = 0, vY = 0, vZ = 0;
 | |
| 		EulerToVector(-GetRotation(), GetPitch(), vZ, vX, vY);
 | |
| 		vY = -vY*2 + 1.f;
 | |
| 		m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY() + 1.6f, GetPosZ(), vX * 2, vY * 2, vZ * 2);
 | |
| 	}
 | |
| 	
 | |
| 	if (m_CurrentWindow)
 | |
| 	{
 | |
| 		// FIXME: If the player entity is destroyed while having a chest window open, the chest will not close
 | |
| 		if (a_WindowType == 1 && strcmp(m_CurrentWindow->GetWindowTitle().c_str(), "UberChest") == 0) { // Chest
 | |
| 			cBlockEntity *block = m_CurrentWindow->GetOwner()->GetEntity();
 | |
| 			cPacket_BlockAction ChestClose;
 | |
| 			ChestClose.m_PosX = block->GetPosX();
 | |
| 			ChestClose.m_PosY = (short)block->GetPosY();
 | |
| 			ChestClose.m_PosZ = block->GetPosZ();
 | |
| 			ChestClose.m_Byte1 = 1;
 | |
| 			ChestClose.m_Byte2 = 0;
 | |
| 			m_World->Broadcast(ChestClose);
 | |
| 		}
 | |
| 		
 | |
| 		m_CurrentWindow->Close( *this );
 | |
| 	}
 | |
| 	m_CurrentWindow = NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::SetLastBlockActionTime()
 | |
| {
 | |
| 	if (m_World != NULL)
 | |
| 	{
 | |
| 		m_LastBlockActionTime = m_World->GetTime();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::SetLastBlockActionCnt( int a_LastBlockActionCnt )
 | |
| {
 | |
| 	m_LastBlockActionCnt = a_LastBlockActionCnt;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::SetGameMode( eGameMode a_GameMode )
 | |
| {
 | |
| 	if ( (a_GameMode < 2) && (a_GameMode >= 0) )
 | |
| 	{
 | |
| 		if (m_GameMode != a_GameMode)
 | |
| 		{
 | |
| 			cInventory *OldInventory = 0;
 | |
| 			if(m_GameMode == eGameMode_Survival)
 | |
| 				OldInventory = m_Inventory;
 | |
| 			else
 | |
| 				OldInventory = m_CreativeInventory;
 | |
| 
 | |
| 			m_GameMode = a_GameMode;
 | |
| 			cPacket_NewInvalidState GameModePacket;
 | |
| 			GameModePacket.m_Reason = 3; //GameModeChange
 | |
| 			GameModePacket.m_GameMode = (char)a_GameMode; //GameModeChange
 | |
| 			m_ClientHandle->Send ( GameModePacket );
 | |
| 			GetInventory().SendWholeInventory(m_ClientHandle);
 | |
| 			
 | |
| 			OldInventory->SetEquippedSlot(GetInventory().GetEquippedSlot());
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::LoginSetGameMode( eGameMode a_GameMode )
 | |
| {
 | |
| 	m_GameMode = a_GameMode;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::SetIP( std::string a_IP )
 | |
| {
 | |
| 	m_IP = a_IP;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::SendMessage( const char* a_Message )
 | |
| {
 | |
| 	m_ClientHandle->Send( cPacket_Chat( a_Message ) );
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::TeleportTo( const double & a_PosX, const double & a_PosY, const double & a_PosZ )
 | |
| {
 | |
| 	SetPosition( a_PosX, a_PosY, a_PosZ );
 | |
| 
 | |
| 	cPacket_TeleportEntity TeleportEntity( this );
 | |
| 	cRoot::Get()->GetServer()->Broadcast( TeleportEntity, GetClientHandle() );
 | |
| 	
 | |
| 
 | |
| 	cPacket_PlayerPosition PlayerPosition( this );
 | |
| 
 | |
| 	m_ClientHandle->Send( PlayerPosition );
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::MoveTo( const Vector3d & a_NewPos )
 | |
| {
 | |
| 	// TODO: should do some checks to see if player is not moving through terrain
 | |
| 	// TODO: Official server refuses position packets too far away from each other, kicking "hacked" clients; we should, too
 | |
| 	
 | |
| 	SetPosition( a_NewPos );
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::SetVisible( bool a_bVisible )
 | |
| {
 | |
| 	if (a_bVisible && !m_bVisible) // Make visible
 | |
| 	{
 | |
| 		m_bVisible = true;
 | |
| 		SpawnOn( NULL ); // Spawn on everybody
 | |
| 	}
 | |
| 	if (!a_bVisible && m_bVisible)
 | |
| 	{
 | |
| 		m_bVisible = false;
 | |
| 		cPacket_DestroyEntity DestroyEntity( this );
 | |
| 		m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, DestroyEntity );	// Destroy on all clients
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::AddToGroup( const char* a_GroupName )
 | |
| {
 | |
| 	cGroup* Group = cRoot::Get()->GetGroupManager()->GetGroup( a_GroupName );
 | |
| 	m_Groups.push_back( Group );
 | |
| 	LOG("Added %s to group %s", m_PlayerName.c_str(), a_GroupName );
 | |
| 	ResolveGroups();
 | |
| 	ResolvePermissions();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| bool cPlayer::CanUseCommand( const char* a_Command )
 | |
| {
 | |
| 	for( GroupList::iterator itr = m_Groups.begin(); itr != m_Groups.end(); ++itr )
 | |
| 	{
 | |
| 		if( (*itr)->HasCommand( a_Command ) ) return true;
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| bool cPlayer::HasPermission( const char* a_Permission )
 | |
| {
 | |
| 	AStringVector Split = StringSplit( a_Permission, "." );
 | |
| 	PermissionMap Possibilities = m_ResolvedPermissions;
 | |
| 	// Now search the namespaces
 | |
| 	while( Possibilities.begin() != Possibilities.end() )
 | |
| 	{
 | |
| 		PermissionMap::iterator itr = Possibilities.begin();
 | |
| 		if( itr->second )
 | |
| 		{
 | |
| 			AStringVector OtherSplit = StringSplit( itr->first, "." );
 | |
| 			if( OtherSplit.size() <= Split.size() )
 | |
| 			{
 | |
| 				unsigned int i;
 | |
| 				for( i = 0; i < OtherSplit.size(); ++i )
 | |
| 				{
 | |
| 					if( OtherSplit[i].compare( Split[i] ) != 0 )
 | |
| 					{
 | |
| 						if( OtherSplit[i].compare("*") == 0 ) return true; // WildCard man!! WildCard!
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 				if( i == Split.size() ) return true;
 | |
| 			}
 | |
| 		}
 | |
| 		Possibilities.erase( itr );
 | |
| 	}
 | |
| 
 | |
| 	// Nothing that matched :(
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| bool cPlayer::IsInGroup( const char* a_Group )
 | |
| {
 | |
| 	for( GroupList::iterator itr = m_ResolvedGroups.begin(); itr != m_ResolvedGroups.end(); ++itr )
 | |
| 	{
 | |
| 		if( strcmp( a_Group, (*itr)->GetName().c_str() ) == 0 )
 | |
| 			return true;
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::ResolvePermissions()
 | |
| {
 | |
| 	m_ResolvedPermissions.clear();	// Start with an empty map yo~
 | |
| 
 | |
| 	// Copy all player specific permissions into the resolved permissions map
 | |
| 	for( PermissionMap::iterator itr = m_Permissions.begin(); itr != m_Permissions.end(); ++itr )
 | |
| 	{
 | |
| 		m_ResolvedPermissions[ itr->first ] = itr->second;
 | |
| 	}
 | |
| 
 | |
| 	for( GroupList::iterator GroupItr = m_ResolvedGroups.begin(); GroupItr != m_ResolvedGroups.end(); ++GroupItr )
 | |
| 	{
 | |
| 		const cGroup::PermissionMap & Permissions = (*GroupItr)->GetPermissions();
 | |
| 		for( cGroup::PermissionMap::const_iterator itr = Permissions.begin(); itr != Permissions.end(); ++itr )
 | |
| 		{
 | |
| 			m_ResolvedPermissions[ itr->first ] = itr->second;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::ResolveGroups()
 | |
| {
 | |
| 	// Clear resolved groups first
 | |
| 	m_ResolvedGroups.clear();
 | |
| 
 | |
| 	// Get a complete resolved list of all groups the player is in
 | |
| 	std::map< cGroup*, bool > AllGroups;	// Use a map, because it's faster than iterating through a list to find duplicates
 | |
| 	GroupList ToIterate;
 | |
| 	for( GroupList::iterator GroupItr = m_Groups.begin(); GroupItr != m_Groups.end(); ++GroupItr )
 | |
| 	{
 | |
| 		ToIterate.push_back( *GroupItr );
 | |
| 	}
 | |
| 	while( ToIterate.begin() != ToIterate.end() )
 | |
| 	{
 | |
| 		cGroup* CurrentGroup = *ToIterate.begin();
 | |
| 		if( AllGroups.find( CurrentGroup ) != AllGroups.end() )
 | |
| 		{
 | |
| 			LOGWARNING("ERROR: Player \"%s\" is in the group multiple times (\"%s\"). Please fix your settings in users.ini!",
 | |
| 				m_PlayerName.c_str(), CurrentGroup->GetName().c_str()
 | |
| 			);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			AllGroups[ CurrentGroup ] = true;
 | |
| 			m_ResolvedGroups.push_back( CurrentGroup );	// Add group to resolved list
 | |
| 			const cGroup::GroupList & Inherits = CurrentGroup->GetInherits();
 | |
| 			for( cGroup::GroupList::const_iterator itr = Inherits.begin(); itr != Inherits.end(); ++itr )
 | |
| 			{
 | |
| 				if( AllGroups.find( *itr ) != AllGroups.end() )
 | |
| 				{
 | |
| 					LOGERROR("ERROR: Player %s is in the same group multiple times due to inheritance (%s). FIX IT!", m_PlayerName.c_str(), (*itr)->GetName().c_str() );
 | |
| 					continue;
 | |
| 				}
 | |
| 				ToIterate.push_back( *itr );
 | |
| 			}
 | |
| 		}
 | |
| 		ToIterate.erase( ToIterate.begin() );
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| AString cPlayer::GetColor(void) const
 | |
| {
 | |
| 	if ( m_Color != '-' )
 | |
| 	{
 | |
| 		return cChatColor::MakeColor( m_Color );
 | |
| 	}
 | |
| 
 | |
| 	if ( m_Groups.size() < 1 )
 | |
| 	{
 | |
| 		return cChatColor::White;
 | |
| 	}
 | |
| 
 | |
| 	return (*m_Groups.begin())->GetColor();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::TossItem( bool a_bDraggingItem, int a_Amount /* = 1 */ )
 | |
| {
 | |
| 	cItems Drops;
 | |
| 	if (a_bDraggingItem)
 | |
| 	{
 | |
| 		cItem * Item = GetInventory().GetWindow()->GetDraggingItem();
 | |
| 		if (!Item->IsEmpty())
 | |
| 		{
 | |
| 			Drops.push_back(*Item);
 | |
| 			if( Item->m_ItemCount > a_Amount )
 | |
| 				Item->m_ItemCount -= (char)a_Amount;
 | |
| 			else
 | |
| 				Item->Empty();
 | |
| 		}
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		// Else drop equipped item
 | |
| 		cItem DroppedItem = GetInventory().GetEquippedItem();
 | |
| 		if (!DroppedItem.IsEmpty())
 | |
| 		{
 | |
| 			DroppedItem.m_ItemCount = 1;
 | |
| 			if (GetInventory().RemoveItem(DroppedItem))
 | |
| 			{
 | |
| 				DroppedItem.m_ItemCount = 1; // RemoveItem decreases the count, so set it to 1 again
 | |
| 				Drops.push_back(DroppedItem);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	float vX = 0, vY = 0, vZ = 0;
 | |
| 	EulerToVector( -GetRotation(), GetPitch(), vZ, vX, vY );
 | |
| 	vY = -vY*2 + 1.f;
 | |
| 	m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY() + 1.6f, GetPosZ(), vX * 2, vY * 2, vZ * 2);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| bool cPlayer::MoveToWorld( const char* a_WorldName )
 | |
| {
 | |
| 	cWorld * World = cRoot::Get()->GetWorld( a_WorldName );
 | |
| 	if ( World )
 | |
| 	{
 | |
| 		/* Remove all links to the old world */
 | |
| 		m_World->RemovePlayer( this );
 | |
| 		m_ClientHandle->RemoveFromAllChunks();
 | |
| 		m_World->RemoveEntityFromChunk(this, m_ChunkX, m_ChunkY, m_ChunkZ);
 | |
| 
 | |
| 		/* Add player to all the necessary parts of the new world */
 | |
| 		SetWorld( World );
 | |
| 		GetWorld()->AddPlayer( this );
 | |
| 		MoveToCorrectChunk(true);
 | |
| 		GetClientHandle()->StreamChunks();
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::LoadPermissionsFromDisk()
 | |
| {
 | |
| 	m_Groups.clear();
 | |
| 	m_Permissions.clear();
 | |
| 
 | |
| 	cIniFile IniFile("users.ini");
 | |
| 	if( IniFile.ReadFile() )
 | |
| 	{
 | |
| 		std::string Groups = IniFile.GetValue(m_PlayerName, "Groups", "");
 | |
| 		if( Groups.size() > 0 )
 | |
| 		{
 | |
| 			AStringVector Split = StringSplit( Groups, "," );
 | |
| 			for( unsigned int i = 0; i < Split.size(); i++ )
 | |
| 			{
 | |
| 				AddToGroup( Split[i].c_str() );
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			AddToGroup("Default");
 | |
| 		}
 | |
| 
 | |
| 		m_Color = IniFile.GetValue(m_PlayerName, "Color", "-")[0];
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		LOGWARN("WARNING: Failed to read ini file users.ini");
 | |
| 		AddToGroup("Default");
 | |
| 	}
 | |
| 	ResolvePermissions();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| bool cPlayer::LoadFromDisk()
 | |
| {
 | |
| 	LoadPermissionsFromDisk();
 | |
| 
 | |
| 	// Log player permissions, cause it's what the cool kids do
 | |
| 	LOGINFO("Player %s has permissions:", m_PlayerName.c_str() );
 | |
| 	for( PermissionMap::iterator itr = m_ResolvedPermissions.begin(); itr != m_ResolvedPermissions.end(); ++itr )
 | |
| 	{
 | |
| 		if( itr->second ) LOGINFO("%s", itr->first.c_str() );
 | |
| 	}
 | |
| 
 | |
| 	AString SourceFile;
 | |
| 	Printf(SourceFile, "players/%s.json", m_PlayerName.c_str() );
 | |
| 
 | |
| 	cFile f;
 | |
| 	if (!f.Open(SourceFile, cFile::fmRead))
 | |
| 	{
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	AString buffer;
 | |
| 	if (f.ReadRestOfFile(buffer) != f.GetSize())
 | |
| 	{
 | |
| 		LOGERROR("ERROR READING FROM FILE \"%s\"", SourceFile.c_str()); 
 | |
| 		return false;
 | |
| 	}
 | |
| 	f.Close();
 | |
| 
 | |
| 	Json::Value root;
 | |
| 	Json::Reader reader;
 | |
| 	if (!reader.parse(buffer, root, false))
 | |
| 	{
 | |
| 		LOGERROR("ERROR WHILE PARSING JSON FROM FILE %s", SourceFile.c_str());
 | |
| 	}
 | |
| 
 | |
| 	Json::Value & JSON_PlayerPosition = root["position"];
 | |
| 	if( JSON_PlayerPosition.size() == 3 )
 | |
| 	{
 | |
| 		m_Pos.x = JSON_PlayerPosition[(unsigned int)0].asDouble();
 | |
| 		m_Pos.y = JSON_PlayerPosition[(unsigned int)1].asDouble();
 | |
| 		m_Pos.z = JSON_PlayerPosition[(unsigned int)2].asDouble();
 | |
| 	}
 | |
| 
 | |
| 	Json::Value & JSON_PlayerRotation = root["rotation"];
 | |
| 	if( JSON_PlayerRotation.size() == 3 )
 | |
| 	{
 | |
| 		m_Rot.x = (float)JSON_PlayerRotation[(unsigned int)0].asDouble();
 | |
| 		m_Rot.y = (float)JSON_PlayerRotation[(unsigned int)1].asDouble();
 | |
| 		m_Rot.z = (float)JSON_PlayerRotation[(unsigned int)2].asDouble();
 | |
| 	}
 | |
| 
 | |
| 	m_Health = (short)root.get("health", 0 ).asInt();
 | |
| 	m_FoodLevel = (short)root.get("food", 0 ).asInt();
 | |
| 	m_Inventory->LoadFromJson(root["inventory"]);
 | |
| 	m_CreativeInventory->LoadFromJson(root["creativeinventory"]);
 | |
| 
 | |
| 	m_LoadedWorldName = root.get("world", "world").asString();
 | |
| 	
 | |
| 	LOGD("Player \"%s\" was read from file, spawning at {%.2f, %.2f, %.2f} in world \"%s\"",
 | |
| 		m_PlayerName.c_str(), m_Pos.x, m_Pos.y, m_Pos.z, m_LoadedWorldName.c_str()
 | |
| 	);
 | |
| 	
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| bool cPlayer::SaveToDisk()
 | |
| {
 | |
| 	cMakeDir::MakeDir("players");
 | |
| 
 | |
| 	// create the JSON data
 | |
| 	Json::Value JSON_PlayerPosition;
 | |
| 	JSON_PlayerPosition.append( Json::Value( m_Pos.x ) );
 | |
| 	JSON_PlayerPosition.append( Json::Value( m_Pos.y ) );
 | |
| 	JSON_PlayerPosition.append( Json::Value( m_Pos.z ) );
 | |
| 
 | |
| 	Json::Value JSON_PlayerRotation;
 | |
| 	JSON_PlayerRotation.append( Json::Value( m_Rot.x ) );
 | |
| 	JSON_PlayerRotation.append( Json::Value( m_Rot.y ) );
 | |
| 	JSON_PlayerRotation.append( Json::Value( m_Rot.z ) );
 | |
| 
 | |
| 	Json::Value JSON_Inventory;
 | |
| 	m_Inventory->SaveToJson( JSON_Inventory );
 | |
| 
 | |
| 	Json::Value JSON_CreativeInventory;
 | |
| 	m_CreativeInventory->SaveToJson( JSON_CreativeInventory );
 | |
| 
 | |
| 	Json::Value root;
 | |
| 	root["position"] = JSON_PlayerPosition;
 | |
| 	root["rotation"] = JSON_PlayerRotation;
 | |
| 	root["inventory"] = JSON_Inventory;
 | |
| 	root["creativeinventory"] = JSON_CreativeInventory;
 | |
| 	root["health"] = m_Health;
 | |
| 	root["food"] = m_FoodLevel;
 | |
| 	root["world"] = GetWorld()->GetName();
 | |
| 
 | |
| 	Json::StyledWriter writer;
 | |
| 	std::string JsonData = writer.write( root );
 | |
| 
 | |
| 	AString SourceFile;
 | |
| 	Printf(SourceFile, "players/%s.json", m_PlayerName.c_str() );
 | |
| 
 | |
| 	cFile f;
 | |
| 	if (!f.Open(SourceFile, cFile::fmWrite))
 | |
| 	{
 | |
| 		LOGERROR("ERROR WRITING PLAYER \"%s\" TO FILE \"%s\" - cannot open file", m_PlayerName.c_str(), SourceFile.c_str());
 | |
| 		return false;
 | |
| 	}
 | |
| 	if (f.Write(JsonData.c_str(), JsonData.size()) != (int)JsonData.size())
 | |
| 	{
 | |
| 		LOGERROR("ERROR WRITING PLAYER JSON TO FILE \"%s\"", SourceFile.c_str()); 
 | |
| 		return false;
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| cPlayer::StringList cPlayer::GetResolvedPermissions()
 | |
| {
 | |
| 	StringList Permissions;
 | |
| 
 | |
| 	const PermissionMap& ResolvedPermissions = m_ResolvedPermissions;
 | |
| 	for( PermissionMap::const_iterator itr = ResolvedPermissions.begin(); itr != ResolvedPermissions.end(); ++itr )
 | |
| 	{
 | |
| 		if( itr->second ) Permissions.push_back( itr->first );
 | |
| 	}
 | |
| 
 | |
| 	return Permissions;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| void cPlayer::UseEquippedItem()
 | |
| {
 | |
| 	if(GetGameMode() != 1)		//No damage in creative
 | |
| 	{
 | |
| 		if (GetInventory().GetEquippedItem().DamageItem()) 
 | |
| 		{
 | |
| 			LOG("Player %s Broke ID: %i", GetClientHandle()->GetUsername().c_str(), GetInventory().GetEquippedItem().m_ItemID);
 | |
| 			GetInventory().RemoveItem( GetInventory().GetEquippedItem());
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| bool cPlayer::EatItem(int a_ItemType)
 | |
| {
 | |
| 	// TODO: Handle hunger
 | |
| 	switch (a_ItemType)
 | |
| 	{
 | |
| 		case E_ITEM_APPLE:          return Feed(24); // 2 food bars
 | |
| 		case E_ITEM_GOLDEN_APPLE:   return Feed(60); // 5 food
 | |
| 		case E_ITEM_MUSHROOM_SOUP:  return Feed(48); // 4 food
 | |
| 		case E_ITEM_BREAD:          return Feed(30); // 2.5 food
 | |
| 		case E_ITEM_RAW_MEAT:       return Feed(18); // 1.5 food
 | |
| 		case E_ITEM_COOKED_MEAT:    return Feed(48); // 4 food
 | |
| 		case E_ITEM_RAW_FISH:       return Feed(12); // 1 food
 | |
| 		case E_ITEM_COOKED_FISH:    return Feed(30); // 2.5 food
 | |
| 		case E_ITEM_COOKED_CHICKEN: return Feed(36); // 3 food
 | |
| 		case E_ITEM_RAW_BEEF:       return Feed(18); // 1.5 food
 | |
| 		case E_ITEM_STEAK:          return Feed(48); // 4 food
 | |
| 		case E_ITEM_RAW_CHICKEN:
 | |
| 		{
 | |
| 			if (!Feed(12)) // 1 food
 | |
| 			{
 | |
| 				return false;
 | |
| 			}
 | |
| 			// TODO: A random chance to get food-poisoned
 | |
| 			return true;
 | |
| 		}
 | |
| 		
 | |
| 		case E_ITEM_ROTTEN_FLESH:
 | |
| 		{
 | |
| 			if (!Feed(24))
 | |
| 			{
 | |
| 				return false;
 | |
| 			}
 | |
| 			// TODO: Food-poisoning
 | |
| 			return true;
 | |
| 		}
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 |