summaryrefslogtreecommitdiffstats
path: root/source/Entities
diff options
context:
space:
mode:
authormadmaxoft <github@xoft.cz>2013-08-19 11:39:13 +0200
committermadmaxoft <github@xoft.cz>2013-08-19 11:39:13 +0200
commit1a7912744ff2e0abfeae0d2d75af80d73209580c (patch)
treee5372cdfece1852c00ed7b0219ad77ee82ea1858 /source/Entities
parentFixed timed event wait on Linux. (diff)
downloadcuberite-1a7912744ff2e0abfeae0d2d75af80d73209580c.tar
cuberite-1a7912744ff2e0abfeae0d2d75af80d73209580c.tar.gz
cuberite-1a7912744ff2e0abfeae0d2d75af80d73209580c.tar.bz2
cuberite-1a7912744ff2e0abfeae0d2d75af80d73209580c.tar.lz
cuberite-1a7912744ff2e0abfeae0d2d75af80d73209580c.tar.xz
cuberite-1a7912744ff2e0abfeae0d2d75af80d73209580c.tar.zst
cuberite-1a7912744ff2e0abfeae0d2d75af80d73209580c.zip
Diffstat (limited to 'source/Entities')
-rw-r--r--source/Entities/Entity.cpp1292
-rw-r--r--source/Entities/Entity.h416
-rw-r--r--source/Entities/FallingBlock.cpp107
-rw-r--r--source/Entities/FallingBlock.h44
-rw-r--r--source/Entities/Minecart.cpp190
-rw-r--r--source/Entities/Minecart.h145
-rw-r--r--source/Entities/Pawn.cpp19
-rw-r--r--source/Entities/Pawn.h28
-rw-r--r--source/Entities/Pickup.cpp176
-rw-r--r--source/Entities/Pickup.h59
-rw-r--r--source/Entities/Player.cpp1523
-rw-r--r--source/Entities/Player.h372
-rw-r--r--source/Entities/TNTEntity.cpp76
-rw-r--r--source/Entities/TNTEntity.h33
14 files changed, 4480 insertions, 0 deletions
diff --git a/source/Entities/Entity.cpp b/source/Entities/Entity.cpp
new file mode 100644
index 000000000..19a65ef4e
--- /dev/null
+++ b/source/Entities/Entity.cpp
@@ -0,0 +1,1292 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "Entity.h"
+#include "../World.h"
+#include "../Server.h"
+#include "../Root.h"
+#include "../Vector3d.h"
+#include "../Matrix4f.h"
+#include "../ReferenceManager.h"
+#include "../ClientHandle.h"
+#include "../Chunk.h"
+#include "../Simulator/FluidSimulator.h"
+#include "../PluginManager.h"
+#include "../Tracer.h"
+
+
+
+
+
+int cEntity::m_EntityCount = 0;
+cCriticalSection cEntity::m_CSCount;
+
+
+
+
+
+cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height)
+ : m_UniqueID(0)
+ , m_Health(1)
+ , m_MaxHealth(1)
+ , m_AttachedTo(NULL)
+ , m_Attachee(NULL)
+ , m_Referencers(new cReferenceManager(cReferenceManager::RFMNGR_REFERENCERS))
+ , m_References(new cReferenceManager(cReferenceManager::RFMNGR_REFERENCES))
+ , m_HeadYaw( 0.0 )
+ , m_Rot(0.0, 0.0, 0.0)
+ , m_Pos(a_X, a_Y, a_Z)
+ , m_Mass (0.001) //Default 1g
+ , m_bDirtyHead(true)
+ , m_bDirtyOrientation(true)
+ , m_bDirtyPosition(true)
+ , m_bDirtySpeed(true)
+ , m_bOnGround( false )
+ , m_Gravity( -9.81f )
+ , m_IsInitialized(false)
+ , m_LastPosX( 0.0 )
+ , m_LastPosY( 0.0 )
+ , m_LastPosZ( 0.0 )
+ , m_TimeLastTeleportPacket(0)
+ , m_TimeLastMoveReltPacket(0)
+ , m_TimeLastSpeedPacket(0)
+ , m_EntityType(a_EntityType)
+ , m_World(NULL)
+ , m_TicksSinceLastBurnDamage(0)
+ , m_TicksSinceLastLavaDamage(0)
+ , m_TicksSinceLastFireDamage(0)
+ , m_TicksLeftBurning(0)
+ , m_WaterSpeed(0, 0, 0)
+ , m_Width(a_Width)
+ , m_Height(a_Height)
+{
+ cCSLock Lock(m_CSCount);
+ m_EntityCount++;
+ m_UniqueID = m_EntityCount;
+}
+
+
+
+
+
+cEntity::~cEntity()
+{
+ ASSERT(!m_World->HasEntity(m_UniqueID)); // Before deleting, the entity needs to have been removed from the world
+
+ LOGD("Deleting entity %d at pos {%.2f, %.2f, %.2f} ~ [%d, %d]; ptr %p",
+ m_UniqueID,
+ m_Pos.x, m_Pos.y, m_Pos.z,
+ (int)(m_Pos.x / cChunkDef::Width), (int)(m_Pos.z / cChunkDef::Width),
+ this
+ );
+
+ if (m_AttachedTo != NULL)
+ {
+ Detach();
+ }
+ if (m_Attachee != NULL)
+ {
+ m_Attachee->Detach();
+ }
+
+ if (m_IsInitialized)
+ {
+ LOGWARNING("ERROR: Entity deallocated without being destroyed");
+ ASSERT(!"Entity deallocated without being destroyed or unlinked");
+ }
+ delete m_Referencers;
+ delete m_References;
+}
+
+
+
+
+
+const char * cEntity::GetClass(void) const
+{
+ return "cEntity";
+}
+
+
+
+
+
+const char * cEntity::GetClassStatic(void)
+{
+ return "cEntity";
+}
+
+
+
+
+
+const char * cEntity::GetParentClass(void) const
+{
+ return "";
+}
+
+
+
+
+
+bool cEntity::Initialize(cWorld * a_World)
+{
+ if (cPluginManager::Get()->CallHookSpawningEntity(*a_World, *this))
+ {
+ return false;
+ }
+
+ LOGD("Initializing entity #%d (%s) at {%.02f, %.02f, %.02f}",
+ m_UniqueID, GetClass(), m_Pos.x, m_Pos.y, m_Pos.z
+ );
+ m_IsInitialized = true;
+ m_World = a_World;
+ m_World->AddEntity(this);
+
+ cPluginManager::Get()->CallHookSpawnedEntity(*a_World, *this);
+ return true;
+}
+
+
+
+
+
+void cEntity::WrapHeadYaw(void)
+{
+ while (m_HeadYaw > 180.f) m_HeadYaw -= 360.f; // Wrap it
+ while (m_HeadYaw < -180.f) m_HeadYaw += 360.f;
+}
+
+
+
+
+
+void cEntity::WrapRotation(void)
+{
+ while (m_Rot.x > 180.f) m_Rot.x -= 360.f; // Wrap it
+ while (m_Rot.x < -180.f) m_Rot.x += 360.f;
+ while (m_Rot.y > 180.f) m_Rot.y -= 360.f;
+ while (m_Rot.y < -180.f) m_Rot.y += 360.f;
+}
+
+
+
+
+void cEntity::WrapSpeed(void)
+{
+ // There shoudn't be a need for flipping the flag on because this function is called
+ // after any update, so the flag is already turned on
+ if (m_Speed.x > 78.0f) m_Speed.x = 78.0f;
+ else if (m_Speed.x < -78.0f) m_Speed.x = -78.0f;
+ if (m_Speed.y > 78.0f) m_Speed.y = 78.0f;
+ else if (m_Speed.y < -78.0f) m_Speed.y = -78.0f;
+ if (m_Speed.z > 78.0f) m_Speed.z = 78.0f;
+ else if (m_Speed.z < -78.0f) m_Speed.z = -78.0f;
+}
+
+
+
+
+
+void cEntity::Destroy(bool a_ShouldBroadcast)
+{
+ if (!m_IsInitialized)
+ {
+ return;
+ }
+
+ if (a_ShouldBroadcast)
+ {
+ m_World->BroadcastDestroyEntity(*this);
+ }
+
+ m_IsInitialized = false;
+
+ Destroyed();
+}
+
+
+
+
+
+void cEntity::TakeDamage(cEntity & a_Attacker)
+{
+ int RawDamage = a_Attacker.GetRawDamageAgainst(*this);
+
+ TakeDamage(dtAttack, &a_Attacker, RawDamage, a_Attacker.GetKnockbackAmountAgainst(*this));
+}
+
+
+
+
+
+void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_RawDamage, double a_KnockbackAmount)
+{
+ int FinalDamage = a_RawDamage - GetArmorCoverAgainst(a_Attacker, a_DamageType, a_RawDamage);
+ cEntity::TakeDamage(a_DamageType, a_Attacker, a_RawDamage, FinalDamage, a_KnockbackAmount);
+}
+
+
+
+
+
+void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_RawDamage, int a_FinalDamage, double a_KnockbackAmount)
+{
+ TakeDamageInfo TDI;
+ TDI.DamageType = a_DamageType;
+ TDI.Attacker = a_Attacker;
+ TDI.RawDamage = a_RawDamage;
+ TDI.FinalDamage = a_FinalDamage;
+ Vector3d Heading;
+ Heading.x = sin(GetRotation());
+ Heading.y = 0.4; // TODO: adjust the amount of "up" knockback when testing
+ Heading.z = cos(GetRotation());
+ TDI.Knockback = Heading * a_KnockbackAmount;
+ DoTakeDamage(TDI);
+}
+
+
+
+
+
+void cEntity::DoTakeDamage(TakeDamageInfo & a_TDI)
+{
+ if (cRoot::Get()->GetPluginManager()->CallHookTakeDamage(*this, a_TDI))
+ {
+ return;
+ }
+
+ if (m_Health <= 0)
+ {
+ // Can't take damage if already dead
+ return;
+ }
+
+ m_Health -= (short)a_TDI.FinalDamage;
+
+ // TODO: Apply damage to armor
+
+ if (m_Health < 0)
+ {
+ m_Health = 0;
+ }
+
+ m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_HURT);
+
+ if (m_Health <= 0)
+ {
+ KilledBy(a_TDI.Attacker);
+ }
+}
+
+
+
+
+
+int cEntity::GetRawDamageAgainst(const cEntity & a_Receiver)
+{
+ // Returns the hitpoints that this pawn can deal to a_Receiver using its equipped items
+ // Ref: http://www.minecraftwiki.net/wiki/Damage#Dealing_damage as of 2012_12_20
+ switch (this->GetEquippedWeapon().m_ItemType)
+ {
+ case E_ITEM_WOODEN_SWORD: return 4;
+ case E_ITEM_GOLD_SWORD: return 4;
+ case E_ITEM_STONE_SWORD: return 5;
+ case E_ITEM_IRON_SWORD: return 6;
+ case E_ITEM_DIAMOND_SWORD: return 7;
+
+ case E_ITEM_WOODEN_AXE: return 3;
+ case E_ITEM_GOLD_AXE: return 3;
+ case E_ITEM_STONE_AXE: return 4;
+ case E_ITEM_IRON_AXE: return 5;
+ case E_ITEM_DIAMOND_AXE: return 6;
+
+ case E_ITEM_WOODEN_PICKAXE: return 2;
+ case E_ITEM_GOLD_PICKAXE: return 2;
+ case E_ITEM_STONE_PICKAXE: return 3;
+ case E_ITEM_IRON_PICKAXE: return 4;
+ case E_ITEM_DIAMOND_PICKAXE: return 5;
+
+ case E_ITEM_WOODEN_SHOVEL: return 1;
+ case E_ITEM_GOLD_SHOVEL: return 1;
+ case E_ITEM_STONE_SHOVEL: return 2;
+ case E_ITEM_IRON_SHOVEL: return 3;
+ case E_ITEM_DIAMOND_SHOVEL: return 4;
+ }
+ // All other equipped items give a damage of 1:
+ return 1;
+}
+
+
+
+
+
+int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_Damage)
+{
+ // Returns the hitpoints out of a_RawDamage that the currently equipped armor would cover
+
+ // Filter out damage types that are not protected by armor:
+ // Ref.: http://www.minecraftwiki.net/wiki/Armor#Effects as of 2012_12_20
+ switch (a_DamageType)
+ {
+ case dtOnFire:
+ case dtSuffocating:
+ case dtDrowning: // TODO: This one could be a special case - in various MC versions (PC vs XBox) it is and isn't armor-protected
+ case dtStarving:
+ case dtInVoid:
+ case dtPoisoning:
+ case dtPotionOfHarming:
+ case dtFalling:
+ case dtLightning:
+ {
+ return 0;
+ }
+ }
+
+ // Add up all armor points:
+ // Ref.: http://www.minecraftwiki.net/wiki/Armor#Defense_points as of 2012_12_20
+ int ArmorValue = 0;
+ switch (GetEquippedHelmet().m_ItemType)
+ {
+ case E_ITEM_LEATHER_CAP: ArmorValue += 1; break;
+ case E_ITEM_GOLD_HELMET: ArmorValue += 2; break;
+ case E_ITEM_CHAIN_HELMET: ArmorValue += 2; break;
+ case E_ITEM_IRON_HELMET: ArmorValue += 2; break;
+ case E_ITEM_DIAMOND_HELMET: ArmorValue += 3; break;
+ }
+ switch (GetEquippedChestplate().m_ItemType)
+ {
+ case E_ITEM_LEATHER_TUNIC: ArmorValue += 3; break;
+ case E_ITEM_GOLD_CHESTPLATE: ArmorValue += 5; break;
+ case E_ITEM_CHAIN_CHESTPLATE: ArmorValue += 5; break;
+ case E_ITEM_IRON_CHESTPLATE: ArmorValue += 6; break;
+ case E_ITEM_DIAMOND_CHESTPLATE: ArmorValue += 8; break;
+ }
+ switch (GetEquippedLeggings().m_ItemType)
+ {
+ case E_ITEM_LEATHER_PANTS: ArmorValue += 2; break;
+ case E_ITEM_GOLD_LEGGINGS: ArmorValue += 3; break;
+ case E_ITEM_CHAIN_LEGGINGS: ArmorValue += 4; break;
+ case E_ITEM_IRON_LEGGINGS: ArmorValue += 5; break;
+ case E_ITEM_DIAMOND_LEGGINGS: ArmorValue += 6; break;
+ }
+ switch (GetEquippedBoots().m_ItemType)
+ {
+ case E_ITEM_LEATHER_BOOTS: ArmorValue += 1; break;
+ case E_ITEM_GOLD_BOOTS: ArmorValue += 1; break;
+ case E_ITEM_CHAIN_BOOTS: ArmorValue += 1; break;
+ case E_ITEM_IRON_BOOTS: ArmorValue += 2; break;
+ case E_ITEM_DIAMOND_BOOTS: ArmorValue += 3; break;
+ }
+
+ // TODO: Special armor cases, such as wool, saddles, dog's collar
+ // Ref.: http://www.minecraftwiki.net/wiki/Armor#Mob_armor as of 2012_12_20
+
+ // Now ArmorValue is in [0, 20] range, which corresponds to [0, 80%] protection. Calculate the hitpoints from that:
+ return a_Damage * (ArmorValue * 4) / 100;
+}
+
+
+
+
+
+double cEntity::GetKnockbackAmountAgainst(const cEntity & a_Receiver)
+{
+ // Returns the knockback amount that the currently equipped items would cause to a_Receiver on a hit
+
+ // TODO: Enchantments
+ return 1;
+}
+
+
+
+
+
+void cEntity::KilledBy(cEntity * a_Killer)
+{
+ m_Health = 0;
+
+ cRoot::Get()->GetPluginManager()->CallHookKilling(*this, a_Killer);
+
+ if (m_Health > 0)
+ {
+ // Plugin wants to 'unkill' the pawn. Abort
+ return;
+ }
+
+ // Drop loot:
+ cItems Drops;
+ GetDrops(Drops, a_Killer);
+ m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ());
+
+ m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_DEAD);
+}
+
+
+
+
+
+void cEntity::Heal(int a_HitPoints)
+{
+ m_Health += a_HitPoints;
+ if (m_Health > m_MaxHealth)
+ {
+ m_Health = m_MaxHealth;
+ }
+}
+
+
+
+
+
+void cEntity::SetHealth(int a_Health)
+{
+ m_Health = std::max(0, std::min(m_MaxHealth, a_Health));
+}
+
+
+
+
+
+void cEntity::Tick(float a_Dt, cChunk & a_Chunk)
+{
+ if (m_AttachedTo != NULL)
+ {
+ if ((m_Pos - m_AttachedTo->GetPosition()).Length() > 0.5)
+ {
+ SetPosition(m_AttachedTo->GetPosition());
+ }
+ }
+ else
+ {
+ if (a_Chunk.IsValid())
+ {
+ HandlePhysics(a_Dt, a_Chunk);
+ }
+ }
+ if (a_Chunk.IsValid())
+ {
+ TickBurning(a_Chunk);
+ }
+}
+
+
+
+
+
+void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk)
+{
+ // TODO Add collision detection with entities.
+ a_Dt /= 1000;
+ Vector3d NextPos = Vector3d(GetPosX(),GetPosY(),GetPosZ());
+ Vector3d NextSpeed = Vector3d(GetSpeedX(),GetSpeedY(),GetSpeedZ());
+ int BlockX = (int) floor(NextPos.x);
+ int BlockY = (int) floor(NextPos.y);
+ int BlockZ = (int) floor(NextPos.z);
+
+ if ((BlockY >= cChunkDef::Height) || (BlockY < 0))
+ {
+ // Outside of the world
+ // TODO: Current speed should still be added to the entity position
+ // Otherwise TNT explosions in the void will still effect the bottommost layers of the world
+ return;
+ }
+
+ // Make sure we got the correct chunk and a valid one. No one ever knows...
+ cChunk * NextChunk = a_Chunk.GetNeighborChunk(BlockX,BlockZ);
+ if (NextChunk != NULL)
+ {
+ int RelBlockX = BlockX - (NextChunk->GetPosX() * cChunkDef::Width);
+ int RelBlockZ = BlockZ - (NextChunk->GetPosZ() * cChunkDef::Width);
+ BLOCKTYPE BlockIn = NextChunk->GetBlock( RelBlockX, BlockY, RelBlockZ );
+ if (!g_BlockIsSolid[BlockIn]) // Making sure we are not inside a solid block
+ {
+ if (m_bOnGround) // check if it's still on the ground
+ {
+ BLOCKTYPE BlockBelow = NextChunk->GetBlock( RelBlockX, BlockY - 1, RelBlockZ );
+ if (!g_BlockIsSolid[BlockBelow]) // Check if block below is air or water.
+ {
+ m_bOnGround = false;
+ }
+ }
+ }
+ else
+ {
+ //Push out entity.
+ m_bOnGround = true;
+ NextPos.y += 0.2;
+ LOGD("Entity #%d (%s) is inside a block at {%d,%d,%d}",
+ m_UniqueID, GetClass(), BlockX, BlockY, BlockZ);
+ }
+
+ if (!m_bOnGround)
+ {
+ float fallspeed;
+ if (IsBlockWater(BlockIn))
+ {
+ fallspeed = -3.0f * a_Dt; //Fall slower in water.
+ }
+ else if (BlockIn == E_BLOCK_COBWEB)
+ {
+ NextSpeed.y *= 0.05; //Reduce overall falling speed
+ fallspeed = 0; //No falling.
+ }
+ else
+ {
+ //Normal gravity
+ fallspeed = m_Gravity * a_Dt;
+ }
+ NextSpeed.y += fallspeed;
+ }
+ else
+ {
+ //Friction
+ if (NextSpeed.SqrLength() > 0.0004f)
+ {
+ NextSpeed.x *= 0.7f/(1+a_Dt);
+ if ( fabs(NextSpeed.x) < 0.05 ) NextSpeed.x = 0;
+ NextSpeed.z *= 0.7f/(1+a_Dt);
+ if ( fabs(NextSpeed.z) < 0.05 ) NextSpeed.z = 0;
+ }
+ }
+
+ //Adjust X and Z speed for COBWEB temporary. This speed modification should be handled inside block handlers since we
+ //might have different speed modifiers according to terrain.
+ if (BlockIn == E_BLOCK_COBWEB)
+ {
+ NextSpeed.x *= 0.25;
+ NextSpeed.z *= 0.25;
+ }
+
+ //Get water direction
+ Direction WaterDir = m_World->GetWaterSimulator()->GetFlowingDirection(BlockX, BlockY, BlockZ);
+
+ m_WaterSpeed *= 0.9f; //Reduce speed each tick
+
+ switch(WaterDir)
+ {
+ case X_PLUS:
+ m_WaterSpeed.x = 1.f;
+ m_bOnGround = false;
+ break;
+ case X_MINUS:
+ m_WaterSpeed.x = -1.f;
+ m_bOnGround = false;
+ break;
+ case Z_PLUS:
+ m_WaterSpeed.z = 1.f;
+ m_bOnGround = false;
+ break;
+ case Z_MINUS:
+ m_WaterSpeed.z = -1.f;
+ m_bOnGround = false;
+ break;
+
+ default:
+ break;
+ }
+
+ if (fabs(m_WaterSpeed.x) < 0.05)
+ {
+ m_WaterSpeed.x = 0;
+ }
+
+ if (fabs(m_WaterSpeed.z) < 0.05)
+ {
+ m_WaterSpeed.z = 0;
+ }
+
+ NextSpeed += m_WaterSpeed;
+
+ if( NextSpeed.SqrLength() > 0.f )
+ {
+ cTracer Tracer( GetWorld() );
+ int Ret = Tracer.Trace( NextPos, NextSpeed, 2 );
+ if( Ret ) // Oh noez! we hit something
+ {
+ // Set to hit position
+ if( (Tracer.RealHit - NextPos).SqrLength() <= ( NextSpeed * a_Dt ).SqrLength() )
+ {
+ if( Ret == 1 )
+ {
+
+ if( Tracer.HitNormal.x != 0.f ) NextSpeed.x = 0.f;
+ if( Tracer.HitNormal.y != 0.f ) NextSpeed.y = 0.f;
+ if( Tracer.HitNormal.z != 0.f ) NextSpeed.z = 0.f;
+
+ if( Tracer.HitNormal.y > 0 ) // means on ground
+ {
+ m_bOnGround = true;
+ }
+ }
+ NextPos.Set(Tracer.RealHit.x,Tracer.RealHit.y,Tracer.RealHit.z);
+ NextPos.x += Tracer.HitNormal.x * 0.5f;
+ NextPos.z += Tracer.HitNormal.z * 0.5f;
+ }
+ else
+ NextPos += (NextSpeed * a_Dt);
+ }
+ else
+ {
+ // We didn't hit anything, so move =]
+ NextPos += (NextSpeed * a_Dt);
+ }
+ }
+ BlockX = (int) floor(NextPos.x);
+ BlockZ = (int) floor(NextPos.z);
+ NextChunk = NextChunk->GetNeighborChunk(BlockX,BlockZ);
+ // See if we can commit our changes. If not, we will discard them.
+ if (NextChunk != NULL)
+ {
+ if (NextPos.x != GetPosX()) SetPosX(NextPos.x);
+ if (NextPos.y != GetPosY()) SetPosY(NextPos.y);
+ if (NextPos.z != GetPosZ()) SetPosZ(NextPos.z);
+ if (NextSpeed.x != GetSpeedX()) SetSpeedX(NextSpeed.x);
+ if (NextSpeed.y != GetSpeedY()) SetSpeedY(NextSpeed.y);
+ if (NextSpeed.z != GetSpeedZ()) SetSpeedZ(NextSpeed.z);
+ }
+ }
+}
+
+
+
+
+
+void cEntity::TickBurning(cChunk & a_Chunk)
+{
+ // Remember the current burning state:
+ bool HasBeenBurning = (m_TicksLeftBurning > 0);
+
+ // Do the burning damage:
+ if (m_TicksLeftBurning > 0)
+ {
+ m_TicksSinceLastBurnDamage++;
+ if (m_TicksSinceLastBurnDamage >= BURN_TICKS_PER_DAMAGE)
+ {
+ TakeDamage(dtOnFire, NULL, BURN_DAMAGE, 0);
+ m_TicksSinceLastBurnDamage = 0;
+ }
+ m_TicksLeftBurning--;
+ }
+
+ // Update the burning times, based on surroundings:
+ int MinRelX = (int)floor(GetPosX() - m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width;
+ int MaxRelX = (int)floor(GetPosX() + m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width;
+ int MinRelZ = (int)floor(GetPosZ() - m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width;
+ int MaxRelZ = (int)floor(GetPosZ() + m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width;
+ int MinY = std::max(0, std::min(cChunkDef::Height - 1, (int)floor(GetPosY())));
+ int MaxY = std::max(0, std::min(cChunkDef::Height - 1, (int)ceil (GetPosY() + m_Height)));
+ bool HasWater = false;
+ bool HasLava = false;
+ bool HasFire = false;
+
+ for (int x = MinRelX; x <= MaxRelX; x++)
+ {
+ for (int z = MinRelZ; z <= MaxRelZ; z++)
+ {
+ int RelX = x;
+ int RelZ = z;
+ cChunk * CurChunk = a_Chunk.GetRelNeighborChunkAdjustCoords(RelX, RelZ);
+ if (CurChunk == NULL)
+ {
+ continue;
+ }
+ for (int y = MinY; y <= MaxY; y++)
+ {
+ switch (CurChunk->GetBlock(RelX, y, RelZ))
+ {
+ case E_BLOCK_FIRE:
+ {
+ HasFire = true;
+ break;
+ }
+ case E_BLOCK_LAVA:
+ case E_BLOCK_STATIONARY_LAVA:
+ {
+ HasLava = true;
+ break;
+ }
+ case E_BLOCK_STATIONARY_WATER:
+ case E_BLOCK_WATER:
+ {
+ HasWater = true;
+ break;
+ }
+ } // switch (BlockType)
+ } // for y
+ } // for z
+ } // for x
+
+ if (HasWater)
+ {
+ // Extinguish the fire
+ m_TicksLeftBurning = 0;
+ }
+
+ if (HasLava)
+ {
+ // Burn:
+ m_TicksLeftBurning = BURN_TICKS;
+
+ // Periodically damage:
+ m_TicksSinceLastLavaDamage++;
+ if (m_TicksSinceLastLavaDamage >= LAVA_TICKS_PER_DAMAGE)
+ {
+ TakeDamage(dtLavaContact, NULL, LAVA_DAMAGE, 0);
+ m_TicksSinceLastLavaDamage = 0;
+ }
+ }
+ else
+ {
+ m_TicksSinceLastLavaDamage = 0;
+ }
+
+ if (HasFire)
+ {
+ // Burn:
+ m_TicksLeftBurning = BURN_TICKS;
+
+ // Periodically damage:
+ m_TicksSinceLastFireDamage++;
+ if (m_TicksSinceLastFireDamage >= FIRE_TICKS_PER_DAMAGE)
+ {
+ TakeDamage(dtFireContact, NULL, FIRE_DAMAGE, 0);
+ m_TicksSinceLastFireDamage = 0;
+ }
+ }
+ else
+ {
+ m_TicksSinceLastFireDamage = 0;
+ }
+
+ // If just started / finished burning, notify descendants:
+ if ((m_TicksLeftBurning > 0) && !HasBeenBurning)
+ {
+ OnStartedBurning();
+ }
+ else if ((m_TicksLeftBurning <= 0) && HasBeenBurning)
+ {
+ OnFinishedBurning();
+ }
+}
+
+
+
+
+
+/// Called when the entity starts burning
+void cEntity::OnStartedBurning(void)
+{
+ // Broadcast the change:
+ m_World->BroadcastEntityMetadata(*this);
+}
+
+
+
+
+
+/// Called when the entity finishes burning
+void cEntity::OnFinishedBurning(void)
+{
+ // Broadcast the change:
+ m_World->BroadcastEntityMetadata(*this);
+}
+
+
+
+
+
+/// Sets the maximum value for the health
+void cEntity::SetMaxHealth(int a_MaxHealth)
+{
+ m_MaxHealth = a_MaxHealth;
+
+ // Reset health, if too high:
+ if (m_Health > a_MaxHealth)
+ {
+ m_Health = a_MaxHealth;
+ }
+}
+
+
+
+
+
+/// Puts the entity on fire for the specified amount of ticks
+void cEntity::StartBurning(int a_TicksLeftBurning)
+{
+ if (m_TicksLeftBurning > 0)
+ {
+ // Already burning, top up the ticks left burning and bail out:
+ m_TicksLeftBurning = std::max(m_TicksLeftBurning, a_TicksLeftBurning);
+ return;
+ }
+
+ m_TicksLeftBurning = a_TicksLeftBurning;
+ OnStartedBurning();
+}
+
+
+
+
+
+/// Stops the entity from burning, resets all burning timers
+void cEntity::StopBurning(void)
+{
+ bool HasBeenBurning = (m_TicksLeftBurning > 0);
+ m_TicksLeftBurning = 0;
+ m_TicksSinceLastBurnDamage = 0;
+ m_TicksSinceLastFireDamage = 0;
+ m_TicksSinceLastLavaDamage = 0;
+
+ // Notify if the entity has stopped burning
+ if (HasBeenBurning)
+ {
+ OnFinishedBurning();
+ }
+}
+
+
+
+
+
+void cEntity::TeleportToEntity(cEntity & a_Entity)
+{
+ TeleportToCoords(a_Entity.GetPosX(), a_Entity.GetPosY(), a_Entity.GetPosZ());
+}
+
+
+
+
+
+void cEntity::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
+{
+ SetPosition(a_PosX, a_PosY, a_PosZ);
+ m_World->BroadcastTeleportEntity(*this);
+}
+
+
+
+
+
+void cEntity::BroadcastMovementUpdate(const cClientHandle * a_Exclude)
+{
+ //We need to keep updating the clients when there is movement or if there was a change in speed and after 2 ticks
+ if( (m_Speed.SqrLength() > 0.0004f || m_bDirtySpeed) && (m_World->GetWorldAge() - m_TimeLastSpeedPacket >= 2))
+ {
+ m_World->BroadcastEntityVelocity(*this,a_Exclude);
+ m_bDirtySpeed = false;
+ m_TimeLastSpeedPacket = m_World->GetWorldAge();
+ }
+
+ //Have to process position related packets this every two ticks
+ if (m_World->GetWorldAge() % 2 == 0)
+ {
+ int DiffX = (int) (floor(GetPosX() * 32.0) - floor(m_LastPosX * 32.0));
+ int DiffY = (int) (floor(GetPosY() * 32.0) - floor(m_LastPosY * 32.0));
+ int DiffZ = (int) (floor(GetPosZ() * 32.0) - floor(m_LastPosZ * 32.0));
+ Int64 DiffTeleportPacket = m_World->GetWorldAge() - m_TimeLastTeleportPacket;
+ // 4 blocks is max Relative So if the Diff is greater than 127 or. Send an absolute position every 20 seconds
+ if (DiffTeleportPacket >= 400 ||
+ ((DiffX > 127) || (DiffX < -128) ||
+ (DiffY > 127) || (DiffY < -128) ||
+ (DiffZ > 127) || (DiffZ < -128)))
+ {
+ //
+ m_World->BroadcastTeleportEntity(*this,a_Exclude);
+ m_TimeLastTeleportPacket = m_World->GetWorldAge();
+ m_TimeLastMoveReltPacket = m_TimeLastTeleportPacket; //Must synchronize.
+ m_LastPosX = GetPosX();
+ m_LastPosY = GetPosY();
+ m_LastPosZ = GetPosZ();
+ m_bDirtyPosition = false;
+ m_bDirtyOrientation = false;
+ }
+ else
+ {
+ Int64 DiffMoveRelPacket = m_World->GetWorldAge() - m_TimeLastMoveReltPacket;
+ //if the change is big enough.
+ if ((abs(DiffX) >= 4 || abs(DiffY) >= 4 || abs(DiffZ) >= 4 || DiffMoveRelPacket >= 60) && m_bDirtyPosition)
+ {
+ if (m_bDirtyOrientation)
+ {
+ m_World->BroadcastEntityRelMoveLook(*this, (char)DiffX, (char)DiffY, (char)DiffZ,a_Exclude);
+ m_bDirtyOrientation = false;
+ }
+ else
+ {
+ m_World->BroadcastEntityRelMove(*this, (char)DiffX, (char)DiffY, (char)DiffZ,a_Exclude);
+ }
+ m_LastPosX = GetPosX();
+ m_LastPosY = GetPosY();
+ m_LastPosZ = GetPosZ();
+ m_bDirtyPosition = false;
+ m_TimeLastMoveReltPacket = m_World->GetWorldAge();
+ }
+ else
+ {
+ if (m_bDirtyOrientation)
+ {
+ m_World->BroadcastEntityLook(*this,a_Exclude);
+ m_bDirtyOrientation = false;
+ }
+ }
+ }
+ if (m_bDirtyHead)
+ {
+ m_World->BroadcastEntityHeadLook(*this,a_Exclude);
+ m_bDirtyHead = false;
+ }
+ }
+}
+
+
+
+
+
+void cEntity::AttachTo(cEntity * a_AttachTo)
+{
+ if (m_AttachedTo == a_AttachTo)
+ {
+ // Already attached to that entity, nothing to do here
+ return;
+ }
+
+ // Detach from any previous entity:
+ Detach();
+
+ // Attach to the new entity:
+ m_AttachedTo = a_AttachTo;
+ a_AttachTo->m_Attachee = this;
+ m_World->BroadcastAttachEntity(*this, a_AttachTo);
+}
+
+
+
+
+
+void cEntity::Detach(void)
+{
+ if (m_AttachedTo == NULL)
+ {
+ // Attached to no entity, our work is done
+ return;
+ }
+ m_AttachedTo->m_Attachee = NULL;
+ m_AttachedTo = NULL;
+ m_World->BroadcastAttachEntity(*this, NULL);
+}
+
+
+
+
+
+bool cEntity::IsA(const char * a_ClassName) const
+{
+ return (strcmp(a_ClassName, "cEntity") == 0);
+}
+
+
+
+
+
+void cEntity::SetRot(const Vector3f & a_Rot)
+{
+ m_Rot = a_Rot;
+ m_bDirtyOrientation = true;
+}
+
+
+
+
+
+void cEntity::SetHeadYaw(double a_HeadYaw)
+{
+ m_HeadYaw = a_HeadYaw;
+ m_bDirtyHead = true;
+ WrapHeadYaw();
+}
+
+
+
+
+
+void cEntity::SetHeight(double a_Height)
+{
+ m_Height = a_Height;
+}
+
+
+
+
+
+void cEntity::SetMass(double a_Mass)
+{
+ if (a_Mass > 0)
+ {
+ m_Mass = a_Mass;
+ }
+ else
+ {
+ //Make sure that mass is not zero. 1g is the default because we
+ //have to choose a number. It's perfectly legal to have a mass
+ //less than 1g as long as is NOT equal or less than zero.
+ m_Mass = 0.001;
+ }
+}
+
+
+
+
+
+void cEntity::SetRotation(double a_Rotation)
+{
+ m_Rot.x = a_Rotation;
+ m_bDirtyOrientation = true;
+ WrapRotation();
+}
+
+
+
+
+
+void cEntity::SetPitch(double a_Pitch)
+{
+ m_Rot.y = a_Pitch;
+ m_bDirtyOrientation = true;
+ WrapRotation();
+}
+
+
+
+
+
+void cEntity::SetRoll(double a_Roll)
+{
+ m_Rot.z = a_Roll;
+ m_bDirtyOrientation = true;
+}
+
+
+
+
+
+void cEntity::SetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ)
+{
+ m_Speed.Set(a_SpeedX, a_SpeedY, a_SpeedZ);
+ m_bDirtySpeed = true;
+ WrapSpeed();
+}
+
+
+
+
+void cEntity::SetSpeedX(double a_SpeedX)
+{
+ m_Speed.x = a_SpeedX;
+ m_bDirtySpeed = true;
+ WrapSpeed();
+}
+
+
+
+
+void cEntity::SetSpeedY(double a_SpeedY)
+{
+ m_Speed.y = a_SpeedY;
+ m_bDirtySpeed = true;
+ WrapSpeed();
+}
+
+
+
+
+void cEntity::SetSpeedZ(double a_SpeedZ)
+{
+ m_Speed.z = a_SpeedZ;
+ m_bDirtySpeed = true;
+ WrapSpeed();
+}
+
+
+
+
+
+void cEntity::SetWidth(double a_Width)
+{
+ m_Width = a_Width;
+}
+
+
+
+
+
+void cEntity::AddPosX(double a_AddPosX)
+{
+ m_Pos.x += a_AddPosX;
+ m_bDirtyPosition = true;
+}
+
+
+
+
+void cEntity::AddPosY(double a_AddPosY)
+{
+ m_Pos.y += a_AddPosY;
+ m_bDirtyPosition = true;
+}
+
+
+
+
+void cEntity::AddPosZ(double a_AddPosZ)
+{
+ m_Pos.z += a_AddPosZ;
+ m_bDirtyPosition = true;
+}
+
+
+
+
+void cEntity::AddPosition(double a_AddPosX, double a_AddPosY, double a_AddPosZ)
+{
+ m_Pos.x += a_AddPosX;
+ m_Pos.y += a_AddPosY;
+ m_Pos.z += a_AddPosZ;
+ m_bDirtyPosition = true;
+}
+
+
+
+
+void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeedZ)
+{
+ m_Speed.x += a_AddSpeedX;
+ m_Speed.y += a_AddSpeedY;
+ m_Speed.z += a_AddSpeedZ;
+ m_bDirtySpeed = true;
+ WrapSpeed();
+}
+
+
+
+
+
+void cEntity::AddSpeedX(double a_AddSpeedX)
+{
+ m_Speed.x += a_AddSpeedX;
+ m_bDirtySpeed = true;
+ WrapSpeed();
+}
+
+
+
+
+
+void cEntity::AddSpeedY(double a_AddSpeedY)
+{
+ m_Speed.y += a_AddSpeedY;
+ m_bDirtySpeed = true;
+ WrapSpeed();
+}
+
+
+
+
+
+void cEntity::AddSpeedZ(double a_AddSpeedZ)
+{
+ m_Speed.z += a_AddSpeedZ;
+ m_bDirtySpeed = true;
+ WrapSpeed();
+}
+
+
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Get look vector (this is NOT a rotation!)
+Vector3d cEntity::GetLookVector(void) const
+{
+ Matrix4d m;
+ m.Init(Vector3f(), 0, m_Rot.x, -m_Rot.y);
+ Vector3d Look = m.Transform(Vector3d(0, 0, 1));
+ return Look;
+}
+
+
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Set position
+void cEntity::SetPosition(double a_PosX, double a_PosY, double a_PosZ)
+{
+ m_Pos.Set(a_PosX, a_PosY, a_PosZ);
+ m_bDirtyPosition = true;
+}
+
+
+
+
+
+void cEntity::SetPosX(double a_PosX)
+{
+ m_Pos.x = a_PosX;
+ m_bDirtyPosition = true;
+}
+
+
+
+
+
+void cEntity::SetPosY(double a_PosY)
+{
+ m_Pos.y = a_PosY;
+ m_bDirtyPosition = true;
+}
+
+
+
+
+
+void cEntity::SetPosZ(double a_PosZ)
+{
+ m_Pos.z = a_PosZ;
+ m_bDirtyPosition = true;
+}
+
+
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Reference stuffs
+void cEntity::AddReference(cEntity * & a_EntityPtr)
+{
+ m_References->AddReference(a_EntityPtr);
+ a_EntityPtr->ReferencedBy(a_EntityPtr);
+}
+
+
+
+
+
+void cEntity::ReferencedBy(cEntity * & a_EntityPtr)
+{
+ m_Referencers->AddReference(a_EntityPtr);
+}
+
+
+
+
+
+void cEntity::Dereference(cEntity * & a_EntityPtr)
+{
+ m_Referencers->Dereference(a_EntityPtr);
+}
+
+
+
+
diff --git a/source/Entities/Entity.h b/source/Entities/Entity.h
new file mode 100644
index 000000000..820405cb9
--- /dev/null
+++ b/source/Entities/Entity.h
@@ -0,0 +1,416 @@
+
+#pragma once
+
+#include "../Item.h"
+#include "../Vector3d.h"
+#include "../Vector3f.h"
+
+
+
+
+
+// Place this macro in the public section of each cEntity descendant class and you're done :)
+#define CLASS_PROTODEF(classname) \
+ virtual bool IsA(const char * a_ClassName) const override\
+ { \
+ return ((strcmp(a_ClassName, #classname) == 0) || super::IsA(a_ClassName)); \
+ } \
+ virtual const char * GetClass(void) const override \
+ { \
+ return #classname; \
+ } \
+ static const char * GetClassStatic(void) \
+ { \
+ return #classname; \
+ } \
+ virtual const char * GetParentClass(void) const override \
+ { \
+ return super::GetClass(); \
+ }
+
+
+
+
+
+class cWorld;
+class cReferenceManager;
+class cClientHandle;
+class cPlayer;
+class cChunk;
+
+
+
+
+
+// tolua_begin
+struct TakeDamageInfo
+{
+ eDamageType DamageType; // Where does the damage come from? Being hit / on fire / contact with cactus / ...
+ cEntity * Attacker; // The attacking entity; valid only for dtAttack
+ int RawDamage; // What damage would the receiver get without any armor. Usually: attacker mob type + weapons
+ int FinalDamage; // What actual damage will be received. Usually: m_RawDamage minus armor
+ Vector3d Knockback; // The amount and direction of knockback received from the damage
+ // TODO: Effects - list of effects that the hit is causing. Unknown representation yet
+} ;
+// tolua_end
+
+
+
+
+
+// tolua_begin
+class cEntity
+{
+public:
+ enum
+ {
+ ENTITY_STATUS_HURT = 2,
+ ENTITY_STATUS_DEAD = 3,
+ ENTITY_STATUS_WOLF_TAMING = 6,
+ ENTITY_STATUS_WOLF_TAMED = 7,
+ ENTITY_STATUS_WOLF_SHAKING = 8,
+ ENTITY_STATUS_EATING_ACCEPTED = 9,
+ ENTITY_STATUS_SHEEP_EATING = 10,
+ } ;
+
+ enum
+ {
+ FIRE_TICKS_PER_DAMAGE = 10, ///< How many ticks to wait between damaging an entity when it stands in fire
+ FIRE_DAMAGE = 1, ///< How much damage to deal when standing in fire
+ LAVA_TICKS_PER_DAMAGE = 10, ///< How many ticks to wait between damaging an entity when it stands in lava
+ LAVA_DAMAGE = 5, ///< How much damage to deal when standing in lava
+ BURN_TICKS_PER_DAMAGE = 20, ///< How many ticks to wait between damaging an entity when it is burning
+ BURN_DAMAGE = 1, ///< How much damage to deal when the entity is burning
+ BURN_TICKS = 200, ///< How long to keep an entity burning after it has stood in lava / fire
+ } ;
+
+ enum eEntityType
+ {
+ etEntity, // For all other types
+ etPlayer,
+ etPickup,
+ etMonster,
+ etMob = etMonster, // DEPRECATED, use etMonster instead!
+ etFallingBlock,
+ etMinecart,
+ etTNT,
+
+ // DEPRECATED older constants, left over for compatibility reasons (plugins)
+ eEntityType_Entity = etEntity,
+ eEntityType_Player = etPlayer,
+ eEntityType_Pickup = etPickup,
+ eEntityType_Mob = etMob,
+ } ;
+
+ // tolua_end
+
+ cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height);
+ virtual ~cEntity();
+
+ /// Spawns the entity in the world; returns true if spawned, false if not (plugin disallowed)
+ virtual bool Initialize(cWorld * a_World);
+
+ // tolua_begin
+
+ eEntityType GetEntityType(void) const { return m_EntityType; }
+
+ bool IsPlayer (void) const { return (m_EntityType == etPlayer); }
+ bool IsPickup (void) const { return (m_EntityType == etPickup); }
+ bool IsMob (void) const { return (m_EntityType == etMob); }
+ bool IsMinecart(void) const { return (m_EntityType == etMinecart); }
+ bool IsTNT (void) const { return (m_EntityType == etTNT); }
+
+ /// Returns true if the entity is of the specified class or a subclass (cPawn's IsA("cEntity") returns true)
+ virtual bool IsA(const char * a_ClassName) const;
+
+ /// Returns the topmost class name for the object
+ virtual const char * GetClass(void) const;
+
+ // Returns the class name of this class
+ static const char * GetClassStatic(void);
+
+ /// Returns the topmost class's parent class name for the object. cEntity returns an empty string (no parent).
+ virtual const char * GetParentClass(void) const;
+
+ cWorld * GetWorld(void) const { return m_World; }
+
+ double GetHeadYaw (void) const { return m_HeadYaw; }
+ double GetHeight (void) const { return m_Height; }
+ double GetMass (void) const { return m_Mass; }
+ const Vector3d & GetPosition (void) const { return m_Pos; }
+ double GetPosX (void) const { return m_Pos.x; }
+ double GetPosY (void) const { return m_Pos.y; }
+ double GetPosZ (void) const { return m_Pos.z; }
+ const Vector3d & GetRot (void) const { return m_Rot; }
+ double GetRotation (void) const { return m_Rot.x; }
+ double GetPitch (void) const { return m_Rot.y; }
+ double GetRoll (void) const { return m_Rot.z; }
+ Vector3d GetLookVector(void) const;
+ const Vector3d & GetSpeed (void) const { return m_Speed; }
+ double GetSpeedX (void) const { return m_Speed.x; }
+ double GetSpeedY (void) const { return m_Speed.y; }
+ double GetSpeedZ (void) const { return m_Speed.z; }
+ double GetWidth (void) const { return m_Width; }
+
+ int GetChunkX(void) const {return (int)floor(m_Pos.x / cChunkDef::Width); }
+ int GetChunkZ(void) const {return (int)floor(m_Pos.z / cChunkDef::Width); }
+
+ void SetHeadYaw (double a_HeadYaw);
+ void SetHeight (double a_Height);
+ void SetMass (double a_Mass);
+ void SetPosX (double a_PosX);
+ void SetPosY (double a_PosY);
+ void SetPosZ (double a_PosZ);
+ void SetPosition(double a_PosX, double a_PosY, double a_PosZ);
+ void SetPosition(const Vector3d & a_Pos) { SetPosition(a_Pos.x,a_Pos.y,a_Pos.z);}
+ void SetRot (const Vector3f & a_Rot);
+ void SetRotation(double a_Rotation);
+ void SetPitch (double a_Pitch);
+ void SetRoll (double a_Roll);
+ void SetSpeed (double a_SpeedX, double a_SpeedY, double a_SpeedZ);
+ void SetSpeed (const Vector3d & a_Speed) { SetSpeed(a_Speed.x, a_Speed.y, a_Speed.z); }
+ void SetSpeedX (double a_SpeedX);
+ void SetSpeedY (double a_SpeedY);
+ void SetSpeedZ (double a_SpeedZ);
+ void SetWidth (double a_Width);
+
+ void AddPosX (double a_AddPosX);
+ void AddPosY (double a_AddPosY);
+ void AddPosZ (double a_AddPosZ);
+ void AddPosition(double a_AddPosX, double a_AddPosY, double a_AddPosZ);
+ void AddPosition(const Vector3d & a_AddPos) { AddPosition(a_AddPos.x,a_AddPos.y,a_AddPos.z);}
+ void AddSpeed (double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeedZ);
+ void AddSpeed (const Vector3d & a_AddSpeed) { AddSpeed(a_AddSpeed.x,a_AddSpeed.y,a_AddSpeed.z);}
+ void AddSpeedX (double a_AddSpeedX);
+ void AddSpeedY (double a_AddSpeedY);
+ void AddSpeedZ (double a_AddSpeedZ);
+
+ inline int GetUniqueID(void) const { return m_UniqueID; }
+ inline bool IsDestroyed(void) const { return !m_IsInitialized; }
+
+ /// Schedules the entity for destroying; if a_ShouldBroadcast is set to true, broadcasts the DestroyEntity packet
+ void Destroy(bool a_ShouldBroadcast = true);
+
+ /// Makes this pawn take damage from an attack by a_Attacker. Damage values are calculated automatically and DoTakeDamage() called
+ void TakeDamage(cEntity & a_Attacker);
+
+ /// Makes this entity take the specified damage. The final damage is calculated using current armor, then DoTakeDamage() called
+ void TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_RawDamage, double a_KnockbackAmount);
+
+ /// Makes this entity take the specified damage. The values are packed into a TDI, knockback calculated, then sent through DoTakeDamage()
+ void TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_RawDamage, int a_FinalDamage, double a_KnockbackAmount);
+
+ // tolua_end
+
+ /// Makes this entity take damage specified in the a_TDI. The TDI is sent through plugins first, then applied
+ virtual void DoTakeDamage(TakeDamageInfo & a_TDI);
+
+ // tolua_begin
+
+ /// Returns the hitpoints that this pawn can deal to a_Receiver using its equipped items
+ virtual int GetRawDamageAgainst(const cEntity & a_Receiver);
+
+ /// Returns the hitpoints out of a_RawDamage that the currently equipped armor would cover
+ virtual int GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_RawDamage);
+
+ /// Returns the knockback amount that the currently equipped items would cause to a_Receiver on a hit
+ virtual double GetKnockbackAmountAgainst(const cEntity & a_Receiver);
+
+ /// Returns the curently equipped weapon; empty item if none
+ virtual cItem GetEquippedWeapon(void) const { return cItem(); }
+
+ /// Returns the currently equipped helmet; empty item if nonte
+ virtual cItem GetEquippedHelmet(void) const { return cItem(); }
+
+ /// Returns the currently equipped chestplate; empty item if nonte
+ virtual cItem GetEquippedChestplate(void) const { return cItem(); }
+
+ /// Returns the currently equipped leggings; empty item if nonte
+ virtual cItem GetEquippedLeggings(void) const { return cItem(); }
+
+ /// Returns the currently equipped boots; empty item if nonte
+ virtual cItem GetEquippedBoots(void) const { return cItem(); }
+
+ /// Called when the health drops below zero. a_Killer may be NULL (environmental damage)
+ virtual void KilledBy(cEntity * a_Killer);
+
+ /// Heals the specified amount of HPs
+ void Heal(int a_HitPoints);
+
+ /// Returns the health of this entity
+ int GetHealth(void) const { return m_Health; }
+
+ /// Sets the health of this entity; doesn't broadcast any hurt animation
+ void SetHealth(int a_Health);
+
+ // tolua_end
+
+ virtual void Tick(float a_Dt, cChunk & a_Chunk);
+
+ /// Handles the physics of the entity - updates position based on speed, updates speed based on environment
+ virtual void HandlePhysics(float a_Dt, cChunk & a_Chunk);
+
+ /// Updates the state related to this entity being on fire
+ virtual void TickBurning(cChunk & a_Chunk);
+
+ /// Called when the entity starts burning
+ virtual void OnStartedBurning(void);
+
+ /// Called when the entity finishes burning
+ virtual void OnFinishedBurning(void);
+
+ // tolua_begin
+
+ /// Sets the maximum value for the health
+ void SetMaxHealth(int a_MaxHealth);
+
+ int GetMaxHealth(void) const { return m_MaxHealth; }
+
+ /// Puts the entity on fire for the specified amount of ticks
+ void StartBurning(int a_TicksLeftBurning);
+
+ /// Stops the entity from burning, resets all burning timers
+ void StopBurning(void);
+
+ // tolua_end
+
+ /** Descendants override this function to send a command to the specified client to spawn the entity on the client.
+ To spawn on all eligible clients, use cChunkMap::BroadcastSpawnEntity()
+ Needs to have a default implementation due to Lua bindings.
+ */
+ virtual void SpawnOn(cClientHandle & a_Client) {ASSERT(!"SpawnOn() unimplemented!"); }
+
+ // tolua_begin
+
+ /// Teleports to the entity specified
+ virtual void TeleportToEntity(cEntity & a_Entity);
+
+ /// Teleports to the coordinates specified
+ virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ);
+
+ // tolua_end
+
+ /// Updates clients of changes in the entity.
+ virtual void BroadcastMovementUpdate(const cClientHandle * a_Exclude = NULL);
+
+ /// Attaches to the specified entity; detaches from any previous one first
+ void AttachTo(cEntity * a_AttachTo);
+
+ /// Detaches from the currently attached entity, if any
+ void Detach(void);
+
+ /// Makes sure head yaw is not over the specified range.
+ void WrapHeadYaw();
+
+ /// Makes sure rotation is not over the specified range.
+ void WrapRotation();
+
+ /// Makes speed is not over 20. Max speed is 20 blocks / second
+ void WrapSpeed();
+
+ // tolua_begin
+
+ // Metadata flags; descendants may override the defaults:
+ virtual bool IsOnFire (void) const {return (m_TicksLeftBurning > 0); }
+ virtual bool IsCrouched (void) const {return false; }
+ virtual bool IsRiding (void) const {return false; }
+ virtual bool IsSprinting(void) const {return false; }
+ virtual bool IsRclking (void) const {return false; }
+
+ // tolua_end
+
+ /// Called when the specified player right-clicks this entity
+ virtual void OnRightClicked(cPlayer & a_Player) {};
+
+ /// Returns the list of drops for this pawn when it is killed. May check a_Killer for special handling (sword of looting etc.). Called from KilledBy().
+ virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) {}
+
+protected:
+ static cCriticalSection m_CSCount;
+ static int m_EntityCount;
+
+ int m_UniqueID;
+
+ int m_Health;
+ int m_MaxHealth;
+
+ /// The entity to which this entity is attached (vehicle), NULL if none
+ cEntity * m_AttachedTo;
+
+ /// The entity which is attached to this entity (rider), NULL if none
+ cEntity * m_Attachee;
+
+ cReferenceManager* m_Referencers;
+ cReferenceManager* m_References;
+
+ // Flags that signal that we haven't updated the clients with the latest.
+ bool m_bDirtyHead;
+ bool m_bDirtyOrientation;
+ bool m_bDirtyPosition;
+ bool m_bDirtySpeed;
+
+ bool m_bOnGround;
+ float m_Gravity;
+
+ // Last Position.
+ double m_LastPosX, m_LastPosY, m_LastPosZ;
+
+ // This variables keep track of the last time a packet was sent
+ Int64 m_TimeLastTeleportPacket,m_TimeLastMoveReltPacket,m_TimeLastSpeedPacket; // In ticks
+
+ bool m_IsInitialized; // Is set to true when it's initialized, until it's destroyed (Initialize() till Destroy() )
+
+ eEntityType m_EntityType;
+
+ cWorld * m_World;
+
+ /// Time, in ticks, since the last damage dealt by being on fire. Valid only if on fire (IsOnFire())
+ int m_TicksSinceLastBurnDamage;
+
+ /// Time, in ticks, since the last damage dealt by standing in lava. Reset to zero when moving out of lava.
+ int m_TicksSinceLastLavaDamage;
+
+ /// Time, in ticks, since the last damage dealt by standing in fire. Reset to zero when moving out of fire.
+ int m_TicksSinceLastFireDamage;
+
+ /// Time, in ticks, until the entity extinguishes its fire
+ int m_TicksLeftBurning;
+
+ virtual void Destroyed(void) {} // Called after the entity has been destroyed
+
+ void SetWorld(cWorld * a_World) { m_World = a_World; }
+
+ friend class cReferenceManager;
+ void AddReference( cEntity*& a_EntityPtr );
+ void ReferencedBy( cEntity*& a_EntityPtr );
+ void Dereference( cEntity*& a_EntityPtr );
+
+private:
+ // Measured in degrees (MAX 360°)
+ double m_HeadYaw;
+ // Measured in meter/second (m/s)
+ Vector3d m_Speed;
+ // Measured in degrees (MAX 360°)
+ Vector3d m_Rot;
+
+ /// Position of the entity's XZ center and Y bottom
+ Vector3d m_Pos;
+
+ // Measured in meter / second
+ Vector3d m_WaterSpeed;
+
+ // Measured in Kilograms (Kg)
+ double m_Mass;
+
+ /// Width of the entity, in the XZ plane. Since entities are represented as cylinders, this is more of a diameter.
+ double m_Width;
+
+ /// Height of the entity (Y axis)
+ double m_Height;
+} ; // tolua_export
+
+typedef std::list<cEntity *> cEntityList;
+
+
+
+
diff --git a/source/Entities/FallingBlock.cpp b/source/Entities/FallingBlock.cpp
new file mode 100644
index 000000000..237327975
--- /dev/null
+++ b/source/Entities/FallingBlock.cpp
@@ -0,0 +1,107 @@
+#include "Globals.h"
+
+#include "FallingBlock.h"
+#include "../World.h"
+#include "../ClientHandle.h"
+#include "../Simulator/SandSimulator.h"
+#include "../Chunk.h"
+
+
+
+
+
+cFallingBlock::cFallingBlock(const Vector3i & a_BlockPosition, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) :
+ super(etFallingBlock, a_BlockPosition.x + 0.5f, a_BlockPosition.y + 0.5f, a_BlockPosition.z + 0.5f, 0.98, 0.98),
+ m_BlockType(a_BlockType),
+ m_BlockMeta(a_BlockMeta),
+ m_OriginalPosition(a_BlockPosition)
+{
+}
+
+
+
+
+
+bool cFallingBlock::Initialize(cWorld * a_World)
+{
+ if (super::Initialize(a_World))
+ {
+ a_World->BroadcastSpawnEntity(*this);
+ return true;
+ }
+ return false;
+}
+
+
+
+
+
+void cFallingBlock::SpawnOn(cClientHandle & a_ClientHandle)
+{
+ a_ClientHandle.SendSpawnFallingBlock(*this);
+}
+
+
+
+
+
+void cFallingBlock::Tick(float a_Dt, cChunk & a_Chunk)
+{
+ float MilliDt = a_Dt * 0.001f;
+ AddSpeedY(MilliDt * -9.8f);
+ AddPosY(GetSpeedY() * MilliDt);
+
+ // GetWorld()->BroadcastTeleportEntity(*this); // Test position
+
+ int BlockX = m_OriginalPosition.x;
+ int BlockY = (int)(GetPosY() - 0.5);
+ int BlockZ = m_OriginalPosition.z;
+
+ if (BlockY < 0)
+ {
+ // Fallen out of this world, just continue falling until out of sight, then destroy:
+ if (BlockY < 100)
+ {
+ Destroy(true);
+ }
+ return;
+ }
+
+ if (BlockY >= cChunkDef::Height)
+ {
+ // Above the world, just wait for it to fall back down
+ return;
+ }
+
+ int idx = a_Chunk.MakeIndexNoCheck(BlockX - a_Chunk.GetPosX() * cChunkDef::Width, BlockY, BlockZ - a_Chunk.GetPosZ() * cChunkDef::Width);
+ BLOCKTYPE BlockBelow = a_Chunk.GetBlock(idx);
+ NIBBLETYPE BelowMeta = a_Chunk.GetMeta(idx);
+ if (cSandSimulator::DoesBreakFallingThrough(BlockBelow, BelowMeta))
+ {
+ // Fallen onto a block that breaks this into pickups (e. g. half-slab)
+ // Must finish the fall with coords one below the block:
+ cSandSimulator::FinishFalling(m_World, BlockX, BlockY, BlockZ, m_BlockType, m_BlockMeta);
+ Destroy(true);
+ return;
+ }
+ else if (!cSandSimulator::CanContinueFallThrough(BlockBelow))
+ {
+ // Fallen onto a solid block
+ /*
+ LOGD(
+ "Sand: Checked below at {%d, %d, %d} (rel {%d, %d, %d}), it's %s, finishing the fall.",
+ BlockX, BlockY, BlockZ,
+ BlockX - a_Chunk.GetPosX() * cChunkDef::Width, BlockY, BlockZ - a_Chunk.GetPosZ() * cChunkDef::Width,
+ ItemTypeToString(BlockBelow).c_str()
+ );
+ */
+
+ cSandSimulator::FinishFalling(m_World, BlockX, BlockY + 1, BlockZ, m_BlockType, m_BlockMeta);
+ Destroy(true);
+ return;
+ }
+}
+
+
+
+
diff --git a/source/Entities/FallingBlock.h b/source/Entities/FallingBlock.h
new file mode 100644
index 000000000..13931f061
--- /dev/null
+++ b/source/Entities/FallingBlock.h
@@ -0,0 +1,44 @@
+
+#pragma once
+
+#include "Entity.h"
+
+
+
+
+class cPlayer;
+class cItem;
+
+
+
+
+
+
+class cFallingBlock :
+ public cEntity
+{
+ typedef cEntity super;
+
+public:
+ CLASS_PROTODEF(cFallingBlock);
+
+ /// Creates a new falling block. a_BlockPosition is expected in world coords
+ cFallingBlock(const Vector3i & a_BlockPosition, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
+
+ BLOCKTYPE GetBlockType(void) const { return m_BlockType; }
+ NIBBLETYPE GetBlockMeta(void) const { return m_BlockMeta; }
+
+ // cEntity overrides:
+ virtual bool Initialize(cWorld * a_World) override;
+ virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
+ virtual void Tick(float a_Dt, cChunk & a_Chunk) override;
+
+private:
+ BLOCKTYPE m_BlockType;
+ NIBBLETYPE m_BlockMeta;
+ Vector3i m_OriginalPosition; // Position where the falling block has started, in world coords
+} ;
+
+
+
+
diff --git a/source/Entities/Minecart.cpp b/source/Entities/Minecart.cpp
new file mode 100644
index 000000000..3e6069237
--- /dev/null
+++ b/source/Entities/Minecart.cpp
@@ -0,0 +1,190 @@
+
+// Minecart.cpp
+
+// Implements the cMinecart class representing a minecart in the world
+
+#include "Globals.h"
+#include "Minecart.h"
+#include "../World.h"
+#include "../ClientHandle.h"
+#include "Player.h"
+
+
+
+
+
+cMinecart::cMinecart(ePayload a_Payload, double a_X, double a_Y, double a_Z) :
+ super(etMinecart, a_X, a_Y, a_Z, 0.98, 0.7),
+ m_Payload(a_Payload)
+{
+}
+
+
+
+
+bool cMinecart::Initialize(cWorld * a_World)
+{
+ if (super::Initialize(a_World))
+ {
+ a_World->BroadcastSpawnEntity(*this);
+ return true;
+ }
+ return false;
+}
+
+
+
+
+
+void cMinecart::SpawnOn(cClientHandle & a_ClientHandle)
+{
+ char Type = 0;
+ switch (m_Payload) //Wiki.vg is outdated on this!!
+ {
+ case mpNone: Type = 9; break; //?
+ case mpChest: Type = 10; break;
+ case mpFurnace: Type = 11; break; //?
+ case mpTNT: Type = 12; break; //?
+ case mpHopper: Type = 13; break; //?
+ default:
+ {
+ ASSERT(!"Unknown payload, cannot spawn on client");
+ return;
+ }
+ }
+ a_ClientHandle.SendSpawnVehicle(*this, Type);
+}
+
+
+
+
+
+void cMinecart::Tick(float a_Dt, cChunk & a_Chunk)
+{
+ // TODO: the physics
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cEmptyMinecart:
+
+cEmptyMinecart::cEmptyMinecart(double a_X, double a_Y, double a_Z) :
+ super(mpNone, a_X, a_Y, a_Z)
+{
+}
+
+
+
+
+
+void cEmptyMinecart::OnRightClicked(cPlayer & a_Player)
+{
+ if (m_Attachee != NULL)
+ {
+ if (m_Attachee->GetUniqueID() == a_Player.GetUniqueID())
+ {
+ // This player is already sitting in, they want out.
+ a_Player.Detach();
+ return;
+ }
+
+ if (m_Attachee->IsPlayer())
+ {
+ // Another player is already sitting in here, cannot attach
+ return;
+ }
+
+ // Detach whatever is sitting in this minecart now:
+ m_Attachee->Detach();
+ }
+
+ // Attach the player to this minecart
+ a_Player.AttachTo(this);
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cMinecartWithChest:
+
+cMinecartWithChest::cMinecartWithChest(double a_X, double a_Y, double a_Z) :
+ super(mpChest, a_X, a_Y, a_Z)
+{
+}
+
+
+
+
+
+void cMinecartWithChest::SetSlot(int a_Idx, const cItem & a_Item)
+{
+ ASSERT((a_Idx >= 0) && (a_Idx < ARRAYCOUNT(m_Items)));
+
+ m_Items[a_Idx] = a_Item;
+}
+
+
+
+
+
+void cMinecartWithChest::OnRightClicked(cPlayer & a_Player)
+{
+ // Show the chest UI window to the player
+ // TODO
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cMinecartWithFurnace:
+
+cMinecartWithFurnace::cMinecartWithFurnace(double a_X, double a_Y, double a_Z) :
+ super(mpFurnace, a_X, a_Y, a_Z)
+{
+}
+
+
+
+
+
+void cMinecartWithFurnace::OnRightClicked(cPlayer & a_Player)
+{
+ // Try to power the furnace with whatever the player is holding
+ // TODO
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cMinecartWithTNT:
+
+cMinecartWithTNT::cMinecartWithTNT(double a_X, double a_Y, double a_Z) :
+ super(mpTNT, a_X, a_Y, a_Z)
+{
+}
+
+// TODO: Make it activate when passing over activator rail
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cMinecartWithHopper:
+
+cMinecartWithHopper::cMinecartWithHopper(double a_X, double a_Y, double a_Z) :
+ super(mpHopper, a_X, a_Y, a_Z)
+{
+}
+
+// TODO: Make it suck up blocks and travel further than any other cart and physics and put and take blocks
+// AND AVARYTHING!! \ No newline at end of file
diff --git a/source/Entities/Minecart.h b/source/Entities/Minecart.h
new file mode 100644
index 000000000..91336673d
--- /dev/null
+++ b/source/Entities/Minecart.h
@@ -0,0 +1,145 @@
+
+// Minecart.h
+
+// Declares the cMinecart class representing a minecart in the world
+
+
+
+
+
+#pragma once
+
+#include "Entity.h"
+#include "../Item.h"
+
+
+
+
+
+class cMinecart :
+ public cEntity
+{
+ typedef cEntity super;
+
+public:
+ CLASS_PROTODEF(cMinecart);
+
+ enum ePayload
+ {
+ mpNone, // Empty minecart, ridable by player or mobs
+ mpChest, // Minecart-with-chest, can store a grid of 3*8 items
+ mpFurnace, // Minecart-with-furnace, can be powered
+ mpTNT, // Minecart-with-TNT, can be blown up with activator rail
+ mpHopper, // Minecart-with-hopper, can be hopper
+ // TODO: Spawner minecarts, (and possibly any block in a minecart with NBT editing)
+ } ;
+
+ // cEntity overrides:
+ virtual bool Initialize(cWorld * a_World) override;
+ virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
+ virtual void Tick(float a_Dt, cChunk & a_Chunk) override;
+
+ ePayload GetPayload(void) const { return m_Payload; }
+
+protected:
+ ePayload m_Payload;
+
+ cMinecart(ePayload a_Payload, double a_X, double a_Y, double a_Z);
+} ;
+
+
+
+
+
+class cEmptyMinecart :
+ public cMinecart
+{
+ typedef cMinecart super;
+
+public:
+ CLASS_PROTODEF(cEmptyMinecart);
+
+ cEmptyMinecart(double a_X, double a_Y, double a_Z);
+
+ // cEntity overrides:
+ virtual void OnRightClicked(cPlayer & a_Player) override;
+} ;
+
+
+
+
+
+class cMinecartWithChest :
+ public cMinecart
+{
+ typedef cMinecart super;
+
+public:
+ CLASS_PROTODEF(cMinecartWithChest);
+
+ /// Number of item slots in the chest
+ static const int NumSlots = 9 * 3;
+
+ cMinecartWithChest(double a_X, double a_Y, double a_Z);
+
+ const cItem & GetSlot(int a_Idx) const { return m_Items[a_Idx]; }
+ cItem & GetSlot(int a_Idx) { return m_Items[a_Idx]; }
+
+ void SetSlot(int a_Idx, const cItem & a_Item);
+
+protected:
+
+ /// The chest contents:
+ cItem m_Items[NumSlots];
+
+ // cEntity overrides:
+ virtual void OnRightClicked(cPlayer & a_Player) override;
+} ;
+
+
+
+
+
+class cMinecartWithFurnace :
+ public cMinecart
+{
+ typedef cMinecart super;
+
+public:
+ CLASS_PROTODEF(cMinecartWithFurnace);
+
+ cMinecartWithFurnace(double a_X, double a_Y, double a_Z);
+
+ // cEntity overrides:
+ virtual void OnRightClicked(cPlayer & a_Player) override;
+} ;
+
+
+
+
+
+class cMinecartWithTNT :
+ public cMinecart
+{
+ typedef cMinecart super;
+
+public:
+ CLASS_PROTODEF(cMinecartWithTNT);
+
+ cMinecartWithTNT(double a_X, double a_Y, double a_Z);
+} ;
+
+
+
+
+
+class cMinecartWithHopper :
+ public cMinecart
+{
+ typedef cMinecart super;
+
+public:
+ CLASS_PROTODEF(cMinecartWithHopper);
+
+ cMinecartWithHopper(double a_X, double a_Y, double a_Z);
+} ; \ No newline at end of file
diff --git a/source/Entities/Pawn.cpp b/source/Entities/Pawn.cpp
new file mode 100644
index 000000000..fffefd538
--- /dev/null
+++ b/source/Entities/Pawn.cpp
@@ -0,0 +1,19 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "Pawn.h"
+
+
+
+
+
+cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height)
+ : cEntity(a_EntityType, 0, 0, 0, a_Width, a_Height)
+ , m_bBurnable(true)
+{
+}
+
+
+
+
+
diff --git a/source/Entities/Pawn.h b/source/Entities/Pawn.h
new file mode 100644
index 000000000..e76337d86
--- /dev/null
+++ b/source/Entities/Pawn.h
@@ -0,0 +1,28 @@
+
+#pragma once
+
+#include "Entity.h"
+
+
+
+
+
+// tolua_begin
+class cPawn :
+ public cEntity
+{
+ // tolua_end
+ typedef cEntity super;
+
+public:
+ CLASS_PROTODEF(cPawn);
+
+ cPawn(eEntityType a_EntityType, double a_Width, double a_Height);
+
+protected:
+ bool m_bBurnable;
+} ; // tolua_export
+
+
+
+
diff --git a/source/Entities/Pickup.cpp b/source/Entities/Pickup.cpp
new file mode 100644
index 000000000..0417b861d
--- /dev/null
+++ b/source/Entities/Pickup.cpp
@@ -0,0 +1,176 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#ifndef _WIN32
+#include <cstdlib>
+#endif
+
+#include "Pickup.h"
+#include "../ClientHandle.h"
+#include "../Inventory.h"
+#include "../World.h"
+#include "../Simulator/FluidSimulator.h"
+#include "../Server.h"
+#include "Player.h"
+#include "../PluginManager.h"
+#include "../Item.h"
+#include "../Root.h"
+#include "../Chunk.h"
+
+#include "../Vector3d.h"
+#include "../Vector3f.h"
+
+
+
+
+
+cPickup::cPickup(int a_MicroPosX, int a_MicroPosY, int a_MicroPosZ, const cItem & a_Item, float a_SpeedX /* = 0.f */, float a_SpeedY /* = 0.f */, float a_SpeedZ /* = 0.f */)
+ : cEntity(etPickup, ((double)(a_MicroPosX)) / 32, ((double)(a_MicroPosY)) / 32, ((double)(a_MicroPosZ)) / 32, 0.2, 0.2)
+ , m_Timer( 0.f )
+ , m_Item(a_Item)
+ , m_bCollected( false )
+{
+ m_MaxHealth = 5;
+ m_Health = 5;
+ SetSpeed(a_SpeedX, a_SpeedY, a_SpeedZ);
+ m_Gravity = -3.0;
+}
+
+
+
+
+
+bool cPickup::Initialize(cWorld * a_World)
+{
+ if (super::Initialize(a_World))
+ {
+ a_World->BroadcastSpawnEntity(*this);
+ return true;
+ }
+ return false;
+}
+
+
+
+
+
+void cPickup::SpawnOn(cClientHandle & a_Client)
+{
+ a_Client.SendPickupSpawn(*this);
+}
+
+
+
+
+
+void cPickup::Tick(float a_Dt, cChunk & a_Chunk)
+{
+ super::Tick(a_Dt, a_Chunk);
+ BroadcastMovementUpdate(); //Notify clients of position
+
+ m_Timer += a_Dt;
+
+ if (!m_bCollected)
+ {
+ int BlockY = (int) floor(GetPosY());
+ if (BlockY < cChunkDef::Height) // Don't do anything except for falling when above the world
+ {
+ int BlockX = (int) floor(GetPosX());
+ int BlockZ = (int) floor(GetPosZ());
+ //Position might have changed due to physics. So we have to make sure we have the correct chunk.
+ cChunk * CurrentChunk = a_Chunk.GetNeighborChunk(BlockX, BlockZ);
+ if (CurrentChunk != NULL) // Make sure the chunk is loaded
+ {
+ int RelBlockX = BlockX - (CurrentChunk->GetPosX() * cChunkDef::Width);
+ int RelBlockZ = BlockZ - (CurrentChunk->GetPosZ() * cChunkDef::Width);
+
+ BLOCKTYPE BlockBelow = CurrentChunk->GetBlock(RelBlockX, BlockY - 1, RelBlockZ);
+ BLOCKTYPE BlockIn = CurrentChunk->GetBlock(RelBlockX, BlockY, RelBlockZ);
+
+ if (
+ IsBlockLava(BlockBelow) || (BlockBelow == E_BLOCK_FIRE) ||
+ IsBlockLava(BlockIn) || (BlockIn == E_BLOCK_FIRE)
+ )
+ {
+ m_bCollected = true;
+ m_Timer = 0; // We have to reset the timer.
+ m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick.
+ if (m_Timer > 500.f)
+ {
+ Destroy(true);
+ return;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if (m_Timer > 500.f) // 0.5 second
+ {
+ Destroy(true);
+ return;
+ }
+ }
+
+ if (m_Timer > 1000 * 60 * 5) // 5 minutes
+ {
+ Destroy(true);
+ return;
+ }
+
+ if (GetPosY() < -8) // Out of this world and no more visible!
+ {
+ Destroy(true);
+ return;
+ }
+}
+
+
+
+
+
+bool cPickup::CollectedBy(cPlayer * a_Dest)
+{
+ ASSERT(a_Dest != NULL);
+
+ if (m_bCollected)
+ {
+ // LOG("Pickup %d cannot be collected by \"%s\", because it has already been collected.", m_UniqueID, a_Dest->GetName().c_str());
+ return false; // It's already collected!
+ }
+
+ // 800 is to long
+ if (m_Timer < 500.f)
+ {
+ // LOG("Pickup %d cannot be collected by \"%s\", because it is not old enough.", m_UniqueID, a_Dest->GetName().c_str());
+ return false; // Not old enough
+ }
+
+ if (cRoot::Get()->GetPluginManager()->CallHookCollectingPickup(a_Dest, *this))
+ {
+ // LOG("Pickup %d cannot be collected by \"%s\", because a plugin has said no.", m_UniqueID, a_Dest->GetName().c_str());
+ return false;
+ }
+
+ int NumAdded = a_Dest->GetInventory().AddItem(m_Item);
+ if (NumAdded > 0)
+ {
+ m_Item.m_ItemCount -= NumAdded;
+ m_World->BroadcastCollectPickup(*this, *a_Dest);
+ if (m_Item.m_ItemCount == 0)
+ {
+ // All of the pickup has been collected, schedule the pickup for destroying
+ m_bCollected = true;
+ }
+ m_Timer = 0;
+ return true;
+ }
+
+ // LOG("Pickup %d cannot be collected by \"%s\", because there's no space in the inventory.", a_Dest->GetName().c_str(), m_UniqueID);
+ return false;
+}
+
+
+
+
diff --git a/source/Entities/Pickup.h b/source/Entities/Pickup.h
new file mode 100644
index 000000000..1f32c97b5
--- /dev/null
+++ b/source/Entities/Pickup.h
@@ -0,0 +1,59 @@
+
+#pragma once
+
+#include "Entity.h"
+#include "../Item.h"
+
+
+
+
+
+class cPlayer;
+
+
+
+
+
+// tolua_begin
+class cPickup :
+ public cEntity
+{
+ // tolua_end
+ typedef cEntity super;
+
+public:
+ CLASS_PROTODEF(cPickup);
+
+ cPickup(int a_MicroPosX, int a_MicroPosY, int a_MicroPosZ, const cItem & a_Item, float a_SpeedX = 0.f, float a_SpeedY = 0.f, float a_SpeedZ = 0.f); // tolua_export
+
+ virtual bool Initialize(cWorld * a_World) override;
+
+ cItem & GetItem(void) {return m_Item; } // tolua_export
+ const cItem & GetItem(void) const {return m_Item; }
+
+ virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
+
+ virtual bool CollectedBy(cPlayer * a_Dest); // tolua_export
+
+ virtual void Tick(float a_Dt, cChunk & a_Chunk) override;
+
+ short GetHealth(void) const { return m_Health; }
+
+ /// Returns the number of ticks that this entity has existed
+ short GetAge(void) const { return (short)(m_Timer / 50); }
+
+private:
+ Vector3d m_ResultingSpeed; //Can be used to modify the resulting speed for the current tick ;)
+
+ Vector3d m_WaterSpeed;
+
+ float m_Timer;
+
+ cItem m_Item;
+
+ bool m_bCollected;
+}; // tolua_export
+
+
+
+
diff --git a/source/Entities/Player.cpp b/source/Entities/Player.cpp
new file mode 100644
index 000000000..8ad071453
--- /dev/null
+++ b/source/Entities/Player.cpp
@@ -0,0 +1,1523 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "Player.h"
+#include "../Server.h"
+#include "../ClientHandle.h"
+#include "../UI/Window.h"
+#include "../UI/WindowOwner.h"
+#include "../World.h"
+#include "Pickup.h"
+#include "../PluginManager.h"
+#include "../BlockEntities/BlockEntity.h"
+#include "../GroupManager.h"
+#include "../Group.h"
+#include "../ChatColor.h"
+#include "../Item.h"
+#include "../Tracer.h"
+#include "../Root.h"
+#include "../OSSupport/MakeDir.h"
+#include "../OSSupport/Timer.h"
+#include "../MersenneTwister.h"
+#include "../Chunk.h"
+#include "../Items/ItemHandler.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))
+
+
+
+
+
+cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName)
+ : super(etPlayer, 0.6, 1.8)
+ , m_GameMode(eGameMode_NotSet)
+ , m_IP("")
+ , m_LastBlockActionTime( 0 )
+ , m_LastBlockActionCnt( 0 )
+ , m_AirLevel( MAX_AIR_LEVEL )
+ , m_AirTickTimer( DROWNING_TICKS )
+ , m_bVisible( true )
+ , m_LastGroundHeight( 0 )
+ , m_bTouchGround( false )
+ , m_Stance( 0.0 )
+ , m_Inventory(*this)
+ , m_CurrentWindow(NULL)
+ , m_InventoryWindow(NULL)
+ , m_TimeLastPickupCheck( 0.f )
+ , m_Color('-')
+ , m_ClientHandle( a_Client )
+ , m_FoodLevel(MAX_FOOD_LEVEL)
+ , m_FoodSaturationLevel(5)
+ , m_FoodTickTimer(0)
+ , m_FoodExhaustionLevel(0)
+ , m_FoodPoisonedTicksRemaining(0)
+ , m_NormalMaxSpeed(0.1)
+ , m_SprintingMaxSpeed(0.13)
+ , m_IsCrouched(false)
+ , m_IsSprinting(false)
+ , m_IsSwimming(false)
+ , m_IsSubmerged(false)
+ , m_EatingFinishTick(-1)
+{
+ LOGD("Created a player object for \"%s\" @ \"%s\" at %p, ID %d",
+ a_PlayerName.c_str(), a_Client->GetIPString().c_str(),
+ this, GetUniqueID()
+ );
+
+ m_InventoryWindow = new cInventoryWindow(*this);
+ m_CurrentWindow = m_InventoryWindow;
+ m_InventoryWindow->OpenedByPlayer(*this);
+
+ SetMaxHealth(MAX_HEALTH);
+ m_Health = MAX_HEALTH;
+
+ cTimer t1;
+ m_LastPlayerListTime = t1.GetNowTime();
+
+ m_TimeLastTeleportPacket = 0;
+ m_TimeLastPickupCheck = 0;
+
+ m_PlayerName = a_PlayerName;
+ m_bDirtyPosition = true; // So chunks are streamed to player at spawn
+
+ if (!LoadFromDisk())
+ {
+ m_Inventory.Clear();
+ SetPosX(cRoot::Get()->GetDefaultWorld()->GetSpawnX());
+ SetPosY(cRoot::Get()->GetDefaultWorld()->GetSpawnY());
+ SetPosZ(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(), GetPosX(), GetPosY(), GetPosZ()
+ );
+ }
+ m_LastJumpHeight = (float)(GetPosY());
+ m_LastGroundHeight = (float)(GetPosY());
+ m_Stance = GetPosY() + 1.62;
+
+ cRoot::Get()->GetServer()->PlayerCreated(this);
+}
+
+
+
+
+
+cPlayer::~cPlayer(void)
+{
+ LOGD("Deleting cPlayer \"%s\" at %p, ID %d", m_PlayerName.c_str(), this, GetUniqueID());
+
+ // Notify the server that the player is being destroyed
+ cRoot::Get()->GetServer()->PlayerDestroying(this);
+
+ SaveToDisk();
+
+ m_World->RemovePlayer( this );
+
+ m_ClientHandle = NULL;
+
+ delete m_InventoryWindow;
+
+ LOGD("Player %p deleted", this);
+}
+
+
+
+
+
+bool cPlayer::Initialize(cWorld * a_World)
+{
+ ASSERT(a_World != NULL);
+
+ if (super::Initialize(a_World))
+ {
+ // Remove the client handle from the server, it will be ticked from this object from now on
+ if (m_ClientHandle != NULL)
+ {
+ cRoot::Get()->GetServer()->ClientMovedToWorld(m_ClientHandle);
+ }
+
+ GetWorld()->AddPlayer(this);
+ return true;
+ }
+ return false;
+}
+
+
+
+
+
+void cPlayer::Destroyed()
+{
+ CloseWindow(false);
+
+ m_ClientHandle = NULL;
+}
+
+
+
+
+
+void cPlayer::SpawnOn(cClientHandle & a_Client)
+{
+ if (!m_bVisible || (m_ClientHandle == (&a_Client)))
+ {
+ return;
+ }
+ a_Client.SendPlayerSpawn(*this);
+ a_Client.SendEntityHeadLook(*this);
+ a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem() );
+ a_Client.SendEntityEquipment(*this, 1, m_Inventory.GetEquippedBoots() );
+ a_Client.SendEntityEquipment(*this, 2, m_Inventory.GetEquippedLeggings() );
+ a_Client.SendEntityEquipment(*this, 3, m_Inventory.GetEquippedChestplate() );
+ a_Client.SendEntityEquipment(*this, 4, m_Inventory.GetEquippedHelmet() );
+}
+
+
+
+
+
+void cPlayer::Tick(float a_Dt, cChunk & a_Chunk)
+{
+ if (m_ClientHandle != NULL)
+ {
+ if (m_ClientHandle->IsDestroyed())
+ {
+ // This should not happen, because destroying a client will remove it from the world, but just in case
+ m_ClientHandle = NULL;
+ return;
+ }
+
+ if (!m_ClientHandle->IsPlaying())
+ {
+ // We're not yet in the game, ignore everything
+ return;
+ }
+ }
+
+ super::Tick(a_Dt, a_Chunk);
+
+ // Set player swimming state
+ SetSwimState(a_Chunk);
+
+ // Handle air drowning stuff
+ HandleAir();
+
+ if (m_bDirtyPosition)
+ {
+ // Apply food exhaustion from movement:
+ ApplyFoodExhaustionFromMovement();
+
+ cRoot::Get()->GetPluginManager()->CallHookPlayerMoving(*this);
+ BroadcastMovementUpdate(m_ClientHandle);
+ m_ClientHandle->StreamChunks();
+ }
+ else
+ {
+ BroadcastMovementUpdate(m_ClientHandle);
+ }
+
+ if (m_Health > 0) // make sure player is alive
+ {
+ m_World->CollectPickupsByPlayer(this);
+
+ if ((m_EatingFinishTick >= 0) && (m_EatingFinishTick <= m_World->GetWorldAge()))
+ {
+ FinishEating();
+ }
+
+ HandleFood();
+ }
+
+ // Send Player List (Once per m_LastPlayerListTime/1000 ms)
+ cTimer t1;
+ if (m_LastPlayerListTime + cPlayer::PLAYER_LIST_TIME_MS <= t1.GetNowTime())
+ {
+ m_World->SendPlayerList(this);
+ m_LastPlayerListTime = t1.GetNowTime();
+ }
+}
+
+
+
+
+
+void cPlayer::SetTouchGround(bool a_bTouchGround)
+{
+ // If just
+ m_bTouchGround = a_bTouchGround;
+
+ if (!m_bTouchGround)
+ {
+ if (GetPosY() > m_LastJumpHeight)
+ {
+ m_LastJumpHeight = (float)GetPosY();
+ }
+ cWorld * World = GetWorld();
+ if ((GetPosY() >= 0) && (GetPosY() < 256))
+ {
+ BLOCKTYPE BlockType = World->GetBlock( float2int(GetPosX()), float2int(GetPosY()), float2int(GetPosZ()) );
+ if (BlockType != E_BLOCK_AIR)
+ {
+ // LOGD("TouchGround set to true by server");
+ m_bTouchGround = true;
+ }
+ if (
+ (BlockType == E_BLOCK_WATER) ||
+ (BlockType == E_BLOCK_STATIONARY_WATER) ||
+ (BlockType == E_BLOCK_LADDER) ||
+ (BlockType == E_BLOCK_VINES)
+ )
+ {
+ // LOGD("Water / Ladder / Torch");
+ m_LastGroundHeight = (float)GetPosY();
+ }
+ }
+ }
+
+ if (m_bTouchGround)
+ {
+ float Dist = (float)(m_LastGroundHeight - floor(GetPosY()));
+ int Damage = (int)(Dist - 3.f);
+ if(m_LastJumpHeight > m_LastGroundHeight) Damage++;
+ m_LastJumpHeight = (float)GetPosY();
+ if (Damage > 0)
+ {
+ TakeDamage(dtFalling, NULL, Damage, Damage, 0);
+ }
+
+ m_LastGroundHeight = (float)GetPosY();
+ }
+}
+
+
+
+
+
+void cPlayer::Heal(int a_Health)
+{
+ if (m_Health < GetMaxHealth())
+ {
+ m_Health = (short)std::min((int)a_Health + m_Health, (int)GetMaxHealth());
+ SendHealth();
+ }
+}
+
+
+
+
+
+void cPlayer::SetFoodLevel(int a_FoodLevel)
+{
+ m_FoodLevel = std::max(0, std::min(a_FoodLevel, (int)MAX_FOOD_LEVEL));
+ SendHealth();
+}
+
+
+
+
+
+void cPlayer::SetFoodSaturationLevel(double a_FoodSaturationLevel)
+{
+ m_FoodSaturationLevel = std::max(0.0, std::min(a_FoodSaturationLevel, (double)m_FoodLevel));
+}
+
+
+
+
+
+void cPlayer::SetFoodTickTimer(int a_FoodTickTimer)
+{
+ m_FoodTickTimer = a_FoodTickTimer;
+}
+
+
+
+
+
+void cPlayer::SetFoodExhaustionLevel(double a_FoodSaturationLevel)
+{
+ m_FoodExhaustionLevel = std::max(0.0, std::min(a_FoodSaturationLevel, 4.0));
+}
+
+
+
+
+
+void cPlayer::SetFoodPoisonedTicksRemaining(int a_FoodPoisonedTicksRemaining)
+{
+ m_FoodPoisonedTicksRemaining = a_FoodPoisonedTicksRemaining;
+}
+
+
+
+
+
+bool cPlayer::Feed(int a_Food, double a_Saturation)
+{
+ if (m_FoodLevel >= MAX_FOOD_LEVEL)
+ {
+ return false;
+ }
+
+ m_FoodLevel = std::min(a_Food + m_FoodLevel, (int)MAX_FOOD_LEVEL);
+ m_FoodSaturationLevel = std::min(m_FoodSaturationLevel + a_Saturation, (double)m_FoodLevel);
+
+ SendHealth();
+ return true;
+}
+
+
+
+
+
+void cPlayer::FoodPoison(int a_NumTicks)
+{
+ bool HasBeenFoodPoisoned = (m_FoodPoisonedTicksRemaining > 0);
+ m_FoodPoisonedTicksRemaining = std::max(m_FoodPoisonedTicksRemaining, a_NumTicks);
+ if (!HasBeenFoodPoisoned)
+ {
+ // TODO: Send the poisoning indication to the client - how?
+ SendHealth();
+ }
+}
+
+
+
+
+
+void cPlayer::StartEating(void)
+{
+ // Set the timer:
+ m_EatingFinishTick = m_World->GetWorldAge() + EATING_TICKS;
+
+ // Send the packets:
+ m_World->BroadcastPlayerAnimation(*this, 5);
+ m_World->BroadcastEntityMetadata(*this);
+}
+
+
+
+
+
+void cPlayer::FinishEating(void)
+{
+ // Reset the timer:
+ m_EatingFinishTick = -1;
+
+ // Send the packets:
+ m_ClientHandle->SendEntityStatus(*this, ENTITY_STATUS_EATING_ACCEPTED);
+ m_World->BroadcastPlayerAnimation(*this, 0);
+ m_World->BroadcastEntityMetadata(*this);
+
+ // consume the item:
+ cItem Item(GetEquippedItem());
+ Item.m_ItemCount = 1;
+ cItemHandler * ItemHandler = cItemHandler::GetItemHandler(Item.m_ItemType);
+ if (!ItemHandler->EatItem(this, &Item))
+ {
+ return;
+ }
+ ItemHandler->OnFoodEaten(m_World, this, &Item);
+
+ GetInventory().RemoveOneEquippedItem();
+
+ //if the food is mushroom soup, return a bowl to the inventory
+ if( Item.m_ItemType == E_ITEM_MUSHROOM_SOUP ) {
+ cItem emptyBowl(E_ITEM_BOWL, 1, 0, "");
+ GetInventory().AddItem(emptyBowl, true, true);
+ }
+}
+
+
+
+
+
+void cPlayer::AbortEating(void)
+{
+ m_EatingFinishTick = -1;
+ m_World->BroadcastPlayerAnimation(*this, 0);
+ m_World->BroadcastEntityMetadata(*this);
+}
+
+
+
+
+
+void cPlayer::SendHealth(void)
+{
+ if (m_ClientHandle != NULL)
+ {
+ m_ClientHandle->SendHealth();
+ }
+}
+
+
+
+
+
+void cPlayer::ClearInventoryPaintSlots(void)
+{
+ // Clear the list of slots that are being inventory-painted. Used by cWindow only
+ m_InventoryPaintSlots.clear();
+}
+
+
+
+
+
+void cPlayer::AddInventoryPaintSlot(int a_SlotNum)
+{
+ // Add a slot to the list for inventory painting. Used by cWindow only
+ m_InventoryPaintSlots.push_back(a_SlotNum);
+}
+
+
+
+
+
+const cSlotNums & cPlayer::GetInventoryPaintSlots(void) const
+{
+ // Return the list of slots currently stored for inventory painting. Used by cWindow only
+ return m_InventoryPaintSlots;
+}
+
+
+
+
+
+double cPlayer::GetMaxSpeed(void) const
+{
+ return m_IsSprinting ? m_SprintingMaxSpeed : m_NormalMaxSpeed;
+}
+
+
+
+
+
+void cPlayer::SetNormalMaxSpeed(double a_Speed)
+{
+ m_NormalMaxSpeed = a_Speed;
+ if (!m_IsSprinting)
+ {
+ m_ClientHandle->SendPlayerMaxSpeed();
+ }
+}
+
+
+
+
+
+void cPlayer::SetSprintingMaxSpeed(double a_Speed)
+{
+ m_SprintingMaxSpeed = a_Speed;
+ if (m_IsSprinting)
+ {
+ m_ClientHandle->SendPlayerMaxSpeed();
+ }
+}
+
+
+
+
+
+void cPlayer::SetCrouch(bool a_IsCrouched)
+{
+ // Set the crouch status, broadcast to all visible players
+
+ if (a_IsCrouched == m_IsCrouched)
+ {
+ // No change
+ return;
+ }
+ m_IsCrouched = a_IsCrouched;
+ m_World->BroadcastEntityMetadata(*this);
+}
+
+
+
+
+
+void cPlayer::SetSprint(bool a_IsSprinting)
+{
+ if (a_IsSprinting == m_IsSprinting)
+ {
+ // No change
+ return;
+ }
+
+ m_IsSprinting = a_IsSprinting;
+ m_ClientHandle->SendPlayerMaxSpeed();
+}
+
+
+
+
+
+void cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI)
+{
+ if (m_GameMode == eGameMode_Creative)
+ {
+ // No damage / health in creative mode
+ return;
+ }
+
+ super::DoTakeDamage(a_TDI);
+
+ // Any kind of damage adds food exhaustion
+ AddFoodExhaustion(0.3f);
+
+ SendHealth();
+}
+
+
+
+
+
+void cPlayer::KilledBy(cEntity * a_Killer)
+{
+ super::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
+ cItems Pickups;
+ m_Inventory.CopyToItems(Pickups);
+ m_Inventory.Clear();
+ m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10);
+ SaveToDisk(); // Save it, yeah the world is a tough place !
+}
+
+
+
+
+
+void cPlayer::Respawn(void)
+{
+ m_Health = GetMaxHealth();
+
+ // Reset food level:
+ m_FoodLevel = MAX_FOOD_LEVEL;
+ m_FoodSaturationLevel = 5;
+
+ m_ClientHandle->SendRespawn();
+
+ // Extinguish the fire:
+ StopBurning();
+
+ TeleportToCoords(GetWorld()->GetSpawnX(), GetWorld()->GetSpawnY(), GetWorld()->GetSpawnZ());
+
+ SetVisible(true);
+}
+
+
+
+
+
+double cPlayer::GetEyeHeight(void) const
+{
+ return m_Stance;
+}
+
+
+
+
+Vector3d cPlayer::GetEyePosition(void) const
+{
+ return Vector3d( GetPosX(), m_Stance, GetPosZ() );
+}
+
+
+
+
+
+bool cPlayer::IsGameModeCreative(void) const
+{
+ return (m_GameMode == gmCreative) || // Either the player is explicitly in Creative
+ ((m_GameMode == gmNotSet) && m_World->IsGameModeCreative()); // or they inherit from the world and the world is Creative
+}
+
+
+
+
+
+bool cPlayer::IsGameModeSurvival(void) const
+{
+ return (m_GameMode == gmSurvival) || // Either the player is explicitly in Survival
+ ((m_GameMode == gmNotSet) && m_World->IsGameModeSurvival()); // or they inherit from the world and the world is Survival
+}
+
+
+
+
+
+bool cPlayer::IsGameModeAdventure(void) const
+{
+ return (m_GameMode == gmCreative) || // Either the player is explicitly in Adventure
+ ((m_GameMode == gmNotSet) && m_World->IsGameModeCreative()); // or they inherit from the world and the world is Adventure
+}
+
+
+
+
+
+void cPlayer::OpenWindow(cWindow * a_Window)
+{
+ if (a_Window != m_CurrentWindow)
+ {
+ CloseWindow(false);
+ }
+ a_Window->OpenedByPlayer(*this);
+ m_CurrentWindow = a_Window;
+ a_Window->SendWholeWindow(*GetClientHandle());
+}
+
+
+
+
+
+void cPlayer::CloseWindow(bool a_CanRefuse)
+{
+ if (m_CurrentWindow == NULL)
+ {
+ m_CurrentWindow = m_InventoryWindow;
+ return;
+ }
+
+ if (m_CurrentWindow->ClosedByPlayer(*this, a_CanRefuse) || !a_CanRefuse)
+ {
+ // Close accepted, go back to inventory window (the default):
+ m_CurrentWindow = m_InventoryWindow;
+ }
+ else
+ {
+ // Re-open the window
+ m_CurrentWindow->OpenedByPlayer(*this);
+ m_CurrentWindow->SendWholeWindow(*GetClientHandle());
+ }
+}
+
+
+
+
+
+void cPlayer::CloseWindowIfID(char a_WindowID, bool a_CanRefuse)
+{
+ if ((m_CurrentWindow == NULL) || (m_CurrentWindow->GetWindowID() != a_WindowID))
+ {
+ return;
+ }
+ CloseWindow();
+}
+
+
+
+
+
+void cPlayer::SetLastBlockActionTime()
+{
+ if (m_World != NULL)
+ {
+ m_LastBlockActionTime = m_World->GetWorldAge() / 20.0f;
+ }
+}
+
+
+
+
+
+void cPlayer::SetLastBlockActionCnt( int a_LastBlockActionCnt )
+{
+ m_LastBlockActionCnt = a_LastBlockActionCnt;
+}
+
+
+
+
+
+void cPlayer::SetGameMode(eGameMode a_GameMode)
+{
+ if ((a_GameMode < gmMin) || (a_GameMode >= gmMax))
+ {
+ LOGWARNING("%s: Setting invalid gamemode: %d", GetName().c_str(), a_GameMode);
+ return;
+ }
+
+ if (m_GameMode == a_GameMode)
+ {
+ // Gamemode already set
+ return;
+ }
+
+ m_GameMode = a_GameMode;
+ m_ClientHandle->SendGameMode(a_GameMode);
+}
+
+
+
+
+
+void cPlayer::LoginSetGameMode( eGameMode a_GameMode )
+{
+ m_GameMode = a_GameMode;
+}
+
+
+
+
+
+void cPlayer::SetIP(const AString & a_IP)
+{
+ m_IP = a_IP;
+}
+
+
+
+
+
+void cPlayer::SendMessage(const AString & a_Message)
+{
+ m_ClientHandle->SendChat(a_Message);
+}
+
+
+
+
+
+void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
+{
+ SetPosition( a_PosX, a_PosY, a_PosZ );
+ m_LastGroundHeight = (float)a_PosY;
+
+ m_World->BroadcastTeleportEntity(*this, GetClientHandle());
+ m_ClientHandle->SendPlayerMoveLook();
+}
+
+
+
+
+
+void cPlayer::MoveTo( const Vector3d & a_NewPos )
+{
+ if ((a_NewPos.y < -990) && (GetPosY() > -100))
+ {
+ // When attached to an entity, the client sends position packets with weird coords:
+ // Y = -999 and X, Z = attempting to create speed, usually up to 0.03
+ // We cannot test m_AttachedTo, because when deattaching, the server thinks the client is already deattached while
+ // the client may still send more of these nonsensical packets.
+ if (m_AttachedTo != NULL)
+ {
+ Vector3d AddSpeed(a_NewPos);
+ AddSpeed.y = 0;
+ m_AttachedTo->AddSpeed(AddSpeed);
+ }
+ return;
+ }
+
+ // 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 );
+ SetStance(a_NewPos.y + 1.62);
+}
+
+
+
+
+
+void cPlayer::SetVisible(bool a_bVisible)
+{
+ if (a_bVisible && !m_bVisible) // Make visible
+ {
+ m_bVisible = true;
+ m_World->BroadcastSpawnEntity(*this);
+ }
+ if (!a_bVisible && m_bVisible)
+ {
+ m_bVisible = false;
+ m_World->BroadcastDestroyEntity(*this, m_ClientHandle); // Destroy on all clients
+ }
+}
+
+
+
+
+
+void cPlayer::AddToGroup( const AString & a_GroupName )
+{
+ cGroup* Group = cRoot::Get()->GetGroupManager()->GetGroup( a_GroupName );
+ m_Groups.push_back( Group );
+ LOGD("Added %s to group %s", m_PlayerName.c_str(), a_GroupName.c_str() );
+ ResolveGroups();
+ ResolvePermissions();
+}
+
+
+
+
+
+void cPlayer::RemoveFromGroup( const AString & a_GroupName )
+{
+ bool bRemoved = false;
+ for( GroupList::iterator itr = m_Groups.begin(); itr != m_Groups.end(); ++itr )
+ {
+ if( (*itr)->GetName().compare(a_GroupName ) == 0 )
+ {
+ m_Groups.erase( itr );
+ bRemoved = true;
+ break;
+ }
+ }
+
+ if( bRemoved )
+ {
+ LOGD("Removed %s from group %s", m_PlayerName.c_str(), a_GroupName.c_str() );
+ ResolveGroups();
+ ResolvePermissions();
+ }
+ else
+ {
+ LOGWARN("Tried to remove %s from group %s but was not in that group", m_PlayerName.c_str(), a_GroupName.c_str() );
+ }
+}
+
+
+
+
+
+bool cPlayer::CanUseCommand( const AString & 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 AString & a_Permission)
+{
+ if (a_Permission.empty())
+ {
+ // Empty permission request is always granted
+ return true;
+ }
+
+ 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 AString & a_Group )
+{
+ for( GroupList::iterator itr = m_ResolvedGroups.begin(); itr != m_ResolvedGroups.end(); ++itr )
+ {
+ if( a_Group.compare( (*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,
+ char a_Amount /* = 1 */,
+ short a_CreateType /* = 0 */,
+ short a_CreateHealth /* = 0 */
+)
+{
+ cItems Drops;
+ if (a_CreateType != 0)
+ {
+ // Just create item without touching the inventory (used in creative mode)
+ Drops.push_back(cItem(a_CreateType, a_Amount, a_CreateHealth));
+ }
+ else
+ {
+ // Drop an item from the inventory:
+ if (a_bDraggingItem)
+ {
+ cItem & Item = GetDraggingItem();
+ if (!Item.IsEmpty())
+ {
+ char OriginalItemAmount = Item.m_ItemCount;
+ Item.m_ItemCount = std::min(OriginalItemAmount, a_Amount);
+ Drops.push_back(Item);
+ if (OriginalItemAmount > a_Amount)
+ {
+ Item.m_ItemCount = OriginalItemAmount - (char)a_Amount;
+ }
+ else
+ {
+ Item.Empty();
+ }
+ }
+ }
+ else
+ {
+ // Else drop equipped item
+ cItem DroppedItem(GetInventory().GetEquippedItem());
+ if (!DroppedItem.IsEmpty())
+ {
+ if (GetInventory().RemoveOneEquippedItem())
+ {
+ DroppedItem.m_ItemCount = 1; // RemoveItem decreases the count, so set it to 1 again
+ Drops.push_back(DroppedItem);
+ }
+ }
+ }
+ }
+ double 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 == NULL)
+ {
+ LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName);
+ return false;
+ }
+
+ eDimension OldDimension = m_World->GetDimension();
+
+ // Remove all links to the old world
+ m_World->RemovePlayer(this);
+ m_ClientHandle->RemoveFromAllChunks();
+ m_World->RemoveEntity(this);
+
+ // If the dimension is different, we can send the respawn packet
+ // http://wiki.vg/Protocol#0x09 says "don't send if dimension is the same" as of 2013_07_02
+ m_ClientHandle->MoveToWorld(*World, (OldDimension != World->GetDimension()));
+
+ // Add player to all the necessary parts of the new world
+ SetWorld(World);
+ World->AddEntity(this);
+ World->AddPlayer(this);
+
+ return true;
+}
+
+
+
+
+
+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))
+ {
+ // This is a new player whom we haven't seen yet, bail out, let them have the defaults
+ return false;
+ }
+
+ AString buffer;
+ if (f.ReadRestOfFile(buffer) != f.GetSize())
+ {
+ LOGWARNING("Cannot read player data from file \"%s\"", SourceFile.c_str());
+ return false;
+ }
+ f.Close();
+
+ Json::Value root;
+ Json::Reader reader;
+ if (!reader.parse(buffer, root, false))
+ {
+ LOGWARNING("Cannot parse player data in file \"%s\", player will be reset", SourceFile.c_str());
+ }
+
+ Json::Value & JSON_PlayerPosition = root["position"];
+ if (JSON_PlayerPosition.size() == 3)
+ {
+ SetPosX(JSON_PlayerPosition[(unsigned int)0].asDouble());
+ SetPosY(JSON_PlayerPosition[(unsigned int)1].asDouble());
+ SetPosZ(JSON_PlayerPosition[(unsigned int)2].asDouble());
+ m_LastPosX = GetPosX();
+ m_LastPosY = GetPosY();
+ m_LastPosZ = GetPosZ();
+ m_LastFoodPos = GetPosition();
+ }
+
+ Json::Value & JSON_PlayerRotation = root["rotation"];
+ if (JSON_PlayerRotation.size() == 3)
+ {
+ SetRotation ((float)JSON_PlayerRotation[(unsigned int)0].asDouble());
+ SetPitch ((float)JSON_PlayerRotation[(unsigned int)1].asDouble());
+ SetRoll ((float)JSON_PlayerRotation[(unsigned int)2].asDouble());
+ }
+
+ m_Health = root.get("health", 0).asInt();
+ m_AirLevel = root.get("air", MAX_AIR_LEVEL).asInt();
+ m_FoodLevel = root.get("food", MAX_FOOD_LEVEL).asInt();
+ m_FoodSaturationLevel = root.get("foodSaturation", MAX_FOOD_LEVEL).asDouble();
+ m_FoodTickTimer = root.get("foodTickTimer", 0).asInt();
+ m_FoodExhaustionLevel = root.get("foodExhaustion", 0).asDouble();
+
+ m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt();
+
+ m_Inventory.LoadFromJson(root["inventory"]);
+
+ 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(), GetPosX(), GetPosY(), GetPosZ(), 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(GetPosX()));
+ JSON_PlayerPosition.append(Json::Value(GetPosY()));
+ JSON_PlayerPosition.append(Json::Value(GetPosZ()));
+
+ Json::Value JSON_PlayerRotation;
+ JSON_PlayerRotation.append(Json::Value(GetRotation()));
+ JSON_PlayerRotation.append(Json::Value(GetPitch()));
+ JSON_PlayerRotation.append(Json::Value(GetRoll()));
+
+ Json::Value JSON_Inventory;
+ m_Inventory.SaveToJson(JSON_Inventory);
+
+ Json::Value root;
+ root["position"] = JSON_PlayerPosition;
+ root["rotation"] = JSON_PlayerRotation;
+ root["inventory"] = JSON_Inventory;
+ root["health"] = m_Health;
+ root["air"] = m_AirLevel;
+ root["food"] = m_FoodLevel;
+ root["foodSaturation"] = m_FoodSaturationLevel;
+ root["foodTickTimer"] = m_FoodTickTimer;
+ root["foodExhaustion"] = m_FoodExhaustionLevel;
+ root["world"] = GetWorld()->GetName();
+
+ if (m_GameMode == GetWorld()->GetGameMode())
+ {
+ root["gamemode"] = (int) eGameMode_NotSet;
+ }
+ else
+ {
+ root["gamemode"] = (int) m_GameMode;
+ }
+
+ 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(void)
+{
+ if (GetGameMode() == gmCreative) // No damage in creative
+ {
+ return;
+ }
+
+ GetInventory().DamageEquippedItem();
+}
+
+
+
+
+
+void cPlayer::SetSwimState(cChunk & a_Chunk)
+{
+ int RelY = (int)floor(m_LastPosY + 0.1);
+ if ((RelY < 0) || (RelY >= cChunkDef::Height - 1))
+ {
+ m_IsSwimming = false;
+ m_IsSubmerged = false;
+ return;
+ }
+
+ BLOCKTYPE BlockIn;
+ int RelX = (int)floor(m_LastPosX) - a_Chunk.GetPosX() * cChunkDef::Width;
+ int RelZ = (int)floor(m_LastPosZ) - a_Chunk.GetPosZ() * cChunkDef::Width;
+
+ // Check if the player is swimming:
+ // Use Unbounded, because we're being called *after* processing super::Tick(), which could have changed our chunk
+ VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn));
+ m_IsSwimming = IsBlockWater(BlockIn);
+
+ // Check if the player is submerged:
+ VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, RelY + 1, RelZ, BlockIn));
+ m_IsSubmerged = IsBlockWater(BlockIn);
+}
+
+
+
+
+
+void cPlayer::HandleAir(void)
+{
+ // Ref.: http://www.minecraftwiki.net/wiki/Chunk_format
+ // see if the player is /submerged/ water (block above is water)
+ // Get the type of block the player's standing in:
+
+ if (IsSubmerged())
+ {
+ // either reduce air level or damage player
+ if (m_AirLevel < 1)
+ {
+ if (m_AirTickTimer < 1)
+ {
+ // damage player
+ TakeDamage(dtDrowning, NULL, 1, 1, 0);
+ // reset timer
+ m_AirTickTimer = DROWNING_TICKS;
+ }
+ else
+ {
+ m_AirTickTimer -= 1;
+ }
+ }
+ else
+ {
+ // reduce air supply
+ m_AirLevel -= 1;
+ }
+ }
+ else
+ {
+ // set the air back to maximum
+ m_AirLevel = MAX_AIR_LEVEL;
+ m_AirTickTimer = DROWNING_TICKS;
+ }
+}
+
+
+
+
+
+void cPlayer::HandleFood(void)
+{
+ // Ref.: http://www.minecraftwiki.net/wiki/Hunger
+
+ // Remember the food level before processing, for later comparison
+ int LastFoodLevel = m_FoodLevel;
+
+ // Heal or damage, based on the food level, using the m_FoodTickTimer:
+ if ((m_FoodLevel > 17) || (m_FoodLevel <= 0))
+ {
+ m_FoodTickTimer++;
+ if (m_FoodTickTimer >= 80)
+ {
+ m_FoodTickTimer = 0;
+
+ if (m_FoodLevel >= 17)
+ {
+ // Regenerate health from food, incur 3 pts of food exhaustion:
+ Heal(1);
+ m_FoodExhaustionLevel += 3;
+ }
+ else if (m_FoodLevel <= 0)
+ {
+ // Damage from starving
+ TakeDamage(dtStarving, NULL, 1, 1, 0);
+ }
+ }
+ }
+
+ // Apply food poisoning food exhaustion:
+ if (m_FoodPoisonedTicksRemaining > 0)
+ {
+ m_FoodPoisonedTicksRemaining--;
+ m_FoodExhaustionLevel += 0.025; // 0.5 per second = 0.025 per tick
+ }
+
+ // Apply food exhaustion that has accumulated:
+ if (m_FoodExhaustionLevel >= 4)
+ {
+ m_FoodExhaustionLevel -= 4;
+
+ if (m_FoodSaturationLevel >= 1)
+ {
+ m_FoodSaturationLevel -= 1;
+ }
+ else
+ {
+ m_FoodLevel = std::max(m_FoodLevel - 1, 0);
+ }
+ }
+
+ if (m_FoodLevel != LastFoodLevel)
+ {
+ SendHealth();
+ }
+}
+
+
+
+
+
+void cPlayer::ApplyFoodExhaustionFromMovement()
+{
+ if (IsGameModeCreative())
+ {
+ return;
+ }
+
+ // Calculate the distance travelled, update the last pos:
+ Vector3d Movement(GetPosition() - m_LastFoodPos);
+ Movement.y = 0; // Only take XZ movement into account
+ m_LastFoodPos = GetPosition();
+
+ // If riding anything, apply no food exhaustion
+ if (m_AttachedTo != NULL)
+ {
+ return;
+ }
+
+ // Apply the exhaustion based on distance travelled:
+ double BaseExhaustion = Movement.Length();
+ if (IsSprinting())
+ {
+ // 0.1 pt per meter sprinted
+ BaseExhaustion = BaseExhaustion * 0.1;
+ }
+ else if (IsSwimming())
+ {
+ // 0.015 pt per meter swum
+ BaseExhaustion = BaseExhaustion * 0.015;
+ }
+ else
+ {
+ // 0.01 pt per meter walked / sneaked
+ BaseExhaustion = BaseExhaustion * 0.01;
+ }
+ m_FoodExhaustionLevel += BaseExhaustion;
+}
+
+
+
+
diff --git a/source/Entities/Player.h b/source/Entities/Player.h
new file mode 100644
index 000000000..62595f980
--- /dev/null
+++ b/source/Entities/Player.h
@@ -0,0 +1,372 @@
+
+#pragma once
+
+#include "Pawn.h"
+#include "../Inventory.h"
+#include "../Defines.h"
+
+
+
+
+
+class cGroup;
+class cWindow;
+class cClientHandle;
+
+
+
+
+
+// tolua_begin
+class cPlayer :
+ public cPawn
+{
+ typedef cPawn super;
+
+public:
+ enum
+ {
+ MAX_HEALTH = 20,
+ MAX_FOOD_LEVEL = 20,
+ EATING_TICKS = 30, ///< Number of ticks it takes to eat an item
+ MAX_AIR_LEVEL = 300,
+ DROWNING_TICKS = 10, //number of ticks per heart of damage
+ } ;
+ // tolua_end
+
+ CLASS_PROTODEF(cPlayer)
+
+
+ cPlayer(cClientHandle * a_Client, const AString & a_PlayerName);
+ virtual ~cPlayer();
+
+ virtual bool Initialize(cWorld * a_World) override;
+
+ virtual void SpawnOn(cClientHandle & a_Client) override;
+
+ virtual void Tick(float a_Dt, cChunk & a_Chunk) override;
+
+ virtual void HandlePhysics(float a_Dt, cChunk & a_Chunk) override { };
+
+ /// Returns the curently equipped weapon; empty item if none
+ virtual cItem GetEquippedWeapon(void) const override { return m_Inventory.GetEquippedItem(); }
+
+ /// Returns the currently equipped helmet; empty item if nonte
+ virtual cItem GetEquippedHelmet(void) const override { return m_Inventory.GetEquippedHelmet(); }
+
+ /// Returns the currently equipped chestplate; empty item if none
+ virtual cItem GetEquippedChestplate(void) const override { return m_Inventory.GetEquippedChestplate(); }
+
+ /// Returns the currently equipped leggings; empty item if none
+ virtual cItem GetEquippedLeggings(void) const override { return m_Inventory.GetEquippedLeggings(); }
+
+ /// Returns the currently equipped boots; empty item if none
+ virtual cItem GetEquippedBoots(void) const override { return m_Inventory.GetEquippedBoots(); }
+
+ void SetTouchGround( bool a_bTouchGround );
+ inline void SetStance( const double a_Stance ) { m_Stance = a_Stance; }
+ double GetEyeHeight(void) const; // tolua_export
+ Vector3d GetEyePosition(void) const; // tolua_export
+ inline bool IsOnGround(void) const {return m_bTouchGround; } // tolua_export
+ inline const double GetStance(void) const { return GetPosY() + 1.62; } // tolua_export // TODO: Proper stance when crouching etc.
+ inline cInventory & GetInventory(void) { return m_Inventory; } // tolua_export
+ inline const cInventory & GetInventory(void) const { return m_Inventory; }
+
+ inline const cItem & GetEquippedItem(void) const { return GetInventory().GetEquippedItem(); } // tolua_export
+
+ virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) override;
+
+ // tolua_begin
+
+ /// Returns the current gamemode. Partly OBSOLETE, you should use IsGameModeXXX() functions wherever applicable
+ eGameMode GetGameMode(void) const { return m_GameMode; }
+
+ /** Sets the gamemode for the player.
+ The gamemode may be gmNotSet, in that case the player inherits the world's gamemode.
+ Updates the gamemode on the client (sends the packet)
+ */
+ void SetGameMode(eGameMode a_GameMode);
+
+ /// Returns true if the player is in Creative mode, either explicitly, or by inheriting from current world
+ bool IsGameModeCreative(void) const;
+
+ /// Returns true if the player is in Survival mode, either explicitly, or by inheriting from current world
+ bool IsGameModeSurvival(void) const;
+
+ /// Returns true if the player is in Adventure mode, either explicitly, or by inheriting from current world
+ bool IsGameModeAdventure(void) const;
+
+ AString GetIP(void) const { return m_IP; } // tolua_export
+
+ // tolua_end
+
+ void SetIP(const AString & a_IP);
+
+ float GetLastBlockActionTime() { return m_LastBlockActionTime; }
+ int GetLastBlockActionCnt() { return m_LastBlockActionCnt; }
+ void SetLastBlockActionCnt( int );
+ void SetLastBlockActionTime();
+
+ // Sets the current gamemode, doesn't check validity, doesn't send update packets to client
+ void LoginSetGameMode(eGameMode a_GameMode);
+
+ /// Tries to move to a new position, with collision checks and stuff
+ virtual void MoveTo( const Vector3d & a_NewPos ); // tolua_export
+
+ cWindow * GetWindow(void) { return m_CurrentWindow; } // tolua_export
+ const cWindow * GetWindow(void) const { return m_CurrentWindow; }
+
+ /// Opens the specified window; closes the current one first using CloseWindow()
+ void OpenWindow(cWindow * a_Window); // Exported in ManualBindings.cpp
+
+ // tolua_begin
+
+ /// Closes the current window, resets current window to m_InventoryWindow. A plugin may refuse the closing if a_CanRefuse is true
+ void CloseWindow(bool a_CanRefuse = true);
+
+ /// Closes the current window if it matches the specified ID, resets current window to m_InventoryWindow
+ void CloseWindowIfID(char a_WindowID, bool a_CanRefuse = true);
+
+ cClientHandle * GetClientHandle(void) const { return m_ClientHandle; }
+
+ void SendMessage(const AString & a_Message);
+
+ const AString & GetName(void) const { return m_PlayerName; }
+ void SetName(const AString & a_Name) { m_PlayerName = a_Name; }
+
+ // tolua_end
+
+ typedef std::list< cGroup* > GroupList;
+ typedef std::list< std::string > StringList;
+
+ /// Adds a player to existing group or creates a new group when it doesn't exist
+ void AddToGroup( const AString & a_GroupName ); // tolua_export
+ /// Removes a player from the group, resolves permissions and group inheritance (case sensitive)
+ void RemoveFromGroup( const AString & a_GroupName ); // tolua_export
+ bool CanUseCommand( const AString & a_Command ); // tolua_export
+ bool HasPermission( const AString & a_Permission ); // tolua_export
+ const GroupList & GetGroups() { return m_Groups; } // >> EXPORTED IN MANUALBINDINGS <<
+ StringList GetResolvedPermissions(); // >> EXPORTED IN MANUALBINDINGS <<
+ bool IsInGroup( const AString & a_Group ); // tolua_export
+
+ AString GetColor(void) const; // tolua_export
+
+ void TossItem(bool a_bDraggingItem, char a_Amount = 1, short a_CreateType = 0, short a_CreateHealth = 0); // tolua_export
+
+ void Heal( int a_Health ); // tolua_export
+
+ // tolua_begin
+
+ int GetFoodLevel (void) const { return m_FoodLevel; }
+ double GetFoodSaturationLevel (void) const { return m_FoodSaturationLevel; }
+ int GetFoodTickTimer (void) const { return m_FoodTickTimer; }
+ double GetFoodExhaustionLevel (void) const { return m_FoodExhaustionLevel; }
+ int GetFoodPoisonedTicksRemaining(void) const { return m_FoodPoisonedTicksRemaining; }
+
+ int GetAirLevel (void) const { return m_AirLevel; }
+
+ /// Returns true if the player is satiated, i. e. their foodlevel is at the max and they cannot eat anymore
+ bool IsSatiated(void) const { return (m_FoodLevel >= MAX_FOOD_LEVEL); }
+
+ void SetFoodLevel (int a_FoodLevel);
+ void SetFoodSaturationLevel (double a_FoodSaturationLevel);
+ void SetFoodTickTimer (int a_FoodTickTimer);
+ void SetFoodExhaustionLevel (double a_FoodSaturationLevel);
+ void SetFoodPoisonedTicksRemaining(int a_FoodPoisonedTicksRemaining);
+
+ /// Adds to FoodLevel and FoodSaturationLevel, returns true if any food has been consumed, false if player "full"
+ bool Feed(int a_Food, double a_Saturation);
+
+ /// Adds the specified exhaustion to m_FoodExhaustion. Expects only positive values.
+ void AddFoodExhaustion(double a_Exhaustion) { m_FoodExhaustionLevel += a_Exhaustion; }
+
+ /// Starts the food poisoning for the specified amount of ticks; if already foodpoisoned, sets FoodPoisonedTicksRemaining to the larger of the two
+ void FoodPoison(int a_NumTicks);
+
+ /// Returns true if the player is currently in the process of eating the currently equipped item
+ bool IsEating(void) const { return (m_EatingFinishTick >= 0); }
+
+ // tolua_end
+
+ /// Starts eating the currently equipped item. Resets the eating timer and sends the proper animation packet
+ void StartEating(void);
+
+ /// Finishes eating the currently equipped item. Consumes the item, updates health and broadcasts the packets
+ void FinishEating(void);
+
+ /// Aborts the current eating operation
+ void AbortEating(void);
+
+ virtual void KilledBy(cEntity * a_Killer) override;
+
+ void Respawn(void); // tolua_export
+
+ void SetVisible( bool a_bVisible ); // tolua_export
+ bool IsVisible(void) const { return m_bVisible; } // tolua_export
+
+ bool MoveToWorld(const char * a_WorldName); // tolua_export
+
+ bool SaveToDisk(void);
+ bool LoadFromDisk(void);
+ void LoadPermissionsFromDisk(void); // tolua_export
+
+ const AString & GetLoadedWorldName() { return m_LoadedWorldName; }
+
+ void UseEquippedItem(void);
+
+ void SendHealth(void);
+
+ // In UI windows, the item that the player is dragging:
+ bool IsDraggingItem(void) const { return !m_DraggingItem.IsEmpty(); }
+ cItem & GetDraggingItem(void) {return m_DraggingItem; }
+
+ // In UI windows, when inventory-painting:
+ /// Clears the list of slots that are being inventory-painted. To be used by cWindow only
+ void ClearInventoryPaintSlots(void);
+
+ /// Adds a slot to the list for inventory painting. To be used by cWindow only
+ void AddInventoryPaintSlot(int a_SlotNum);
+
+ /// Returns the list of slots currently stored for inventory painting. To be used by cWindow only
+ const cSlotNums & GetInventoryPaintSlots(void) const;
+
+ // tolua_begin
+
+ /// Returns the current maximum speed, as reported in the 1.6.1+ protocol (takes current sprinting state into account)
+ double GetMaxSpeed(void) const;
+
+ /// Gets the normal maximum speed, as reported in the 1.6.1+ protocol, in the protocol units
+ double GetNormalMaxSpeed(void) const { return m_NormalMaxSpeed; }
+
+ /// Gets the sprinting maximum speed, as reported in the 1.6.1+ protocol, in the protocol units
+ double GetSprintingMaxSpeed(void) const { return m_SprintingMaxSpeed; }
+
+ /// Sets the normal maximum speed, as reported in the 1.6.1+ protocol. Sends the update to player, if needed.
+ void SetNormalMaxSpeed(double a_Speed);
+
+ /// Sets the sprinting maximum speed, as reported in the 1.6.1+ protocol. Sends the update to player, if needed.
+ void SetSprintingMaxSpeed(double a_Speed);
+
+ /// Sets the crouch status, broadcasts to all visible players
+ void SetCrouch(bool a_IsCrouched);
+
+ /// Starts or stops sprinting, sends the max speed update to the client, if needed
+ void SetSprint(bool a_IsSprinting);
+
+ /// Returns whether the player is swimming or not
+ virtual bool IsSwimming(void) const{ return m_IsSwimming; }
+
+ /// Return whether the player is under water or not
+ virtual bool IsSubmerged(void) const{ return m_IsSubmerged; }
+
+ // tolua_end
+
+ // cEntity overrides:
+ virtual bool IsCrouched (void) const { return m_IsCrouched; }
+ virtual bool IsSprinting(void) const { return m_IsSprinting; }
+ virtual bool IsRclking (void) const { return IsEating(); }
+
+
+
+protected:
+ typedef std::map< std::string, bool > PermissionMap;
+ PermissionMap m_ResolvedPermissions;
+ PermissionMap m_Permissions;
+
+ GroupList m_ResolvedGroups;
+ GroupList m_Groups;
+
+ std::string m_PlayerName;
+ std::string m_LoadedWorldName;
+
+ /// Player's air level (for swimming)
+ int m_AirLevel;
+ /// used to time ticks between damage taken via drowning/suffocation
+ int m_AirTickTimer;
+
+ bool m_bVisible;
+
+ // Food-related variables:
+ /// Represents the food bar, one point equals half a "drumstick"
+ int m_FoodLevel;
+
+ /// "Overcharge" for the m_FoodLevel; is depleted before m_FoodLevel
+ double m_FoodSaturationLevel;
+
+ /// Count-up to the healing or damaging action, based on m_FoodLevel
+ int m_FoodTickTimer;
+
+ /// A "buffer" which adds up hunger before it is substracted from m_FoodSaturationLevel or m_FoodLevel. Each action adds a little
+ double m_FoodExhaustionLevel;
+
+ /// Number of ticks remaining for the foodpoisoning effect; zero if not foodpoisoned
+ int m_FoodPoisonedTicksRemaining;
+
+ /// Last position that has been recorded for food-related processing:
+ Vector3d m_LastFoodPos;
+
+ float m_LastJumpHeight;
+ float m_LastGroundHeight;
+ bool m_bTouchGround;
+ double m_Stance;
+ cInventory m_Inventory;
+ cWindow * m_CurrentWindow;
+ cWindow * m_InventoryWindow;
+
+ float m_TimeLastPickupCheck;
+
+ void ResolvePermissions();
+
+ void ResolveGroups();
+ char m_Color;
+
+ float m_LastBlockActionTime;
+ int m_LastBlockActionCnt;
+ eGameMode m_GameMode;
+ std::string m_IP;
+
+ cItem m_DraggingItem;
+
+ long long m_LastPlayerListTime;
+ static const unsigned short PLAYER_LIST_TIME_MS = 1000; // 1000 = once per second
+
+ cClientHandle * m_ClientHandle;
+
+ cSlotNums m_InventoryPaintSlots;
+
+ /// Max speed, in ENTITY_PROPERTIES packet's units, when the player is walking. 0.1 by default
+ double m_NormalMaxSpeed;
+
+ /// Max speed, in ENTITY_PROPERTIES packet's units, when the player is sprinting. 0.13 by default
+ double m_SprintingMaxSpeed;
+
+ bool m_IsCrouched;
+ bool m_IsSprinting;
+
+ bool m_IsSwimming;
+ bool m_IsSubmerged;
+
+ /// The world tick in which eating will be finished. -1 if not eating
+ Int64 m_EatingFinishTick;
+
+ virtual void Destroyed(void);
+
+ /// Filters out damage for creative mode
+ virtual void DoTakeDamage(TakeDamageInfo & TDI) override;
+
+ /// Called in each tick to handle food-related processing
+ void HandleFood(void);
+
+ /// Called in each tick to handle air-related processing i.e. drowning
+ void HandleAir();
+
+ /// Called once per tick to set IsSwimming and IsSubmerged
+ void SetSwimState(cChunk & a_Chunk);
+
+ /// Adds food exhaustion based on the difference between Pos and LastPos, sprinting status and swimming (in water block)
+ void ApplyFoodExhaustionFromMovement();
+} ; // tolua_export
+
+
+
+
diff --git a/source/Entities/TNTEntity.cpp b/source/Entities/TNTEntity.cpp
new file mode 100644
index 000000000..43a0dea09
--- /dev/null
+++ b/source/Entities/TNTEntity.cpp
@@ -0,0 +1,76 @@
+#include "Globals.h"
+
+#include "TNTEntity.h"
+#include "../World.h"
+#include "../ClientHandle.h"
+
+
+
+
+
+cTNTEntity::cTNTEntity(double a_X, double a_Y, double a_Z, double a_FuseTimeInSec) :
+ super(etTNT, a_X, a_Y, a_Z, 0.98, 0.98),
+ m_Counter(0),
+ m_MaxFuseTime(a_FuseTimeInSec)
+{
+}
+
+
+
+
+
+cTNTEntity::cTNTEntity(const Vector3d & a_Pos, double a_FuseTimeInSec) :
+ super(etTNT, a_Pos.x, a_Pos.y, a_Pos.z, 0.98, 0.98),
+ m_Counter(0),
+ m_MaxFuseTime(a_FuseTimeInSec)
+{
+}
+
+
+
+
+bool cTNTEntity::Initialize(cWorld * a_World)
+{
+ if (super::Initialize(a_World))
+ {
+ a_World->BroadcastSpawnEntity(*this);
+ return true;
+ }
+ return false;
+}
+
+
+
+
+
+void cTNTEntity::SpawnOn(cClientHandle & a_ClientHandle)
+{
+ a_ClientHandle.SendSpawnObject(*this, 50, 1, 0, 0); // 50 means TNT
+ m_bDirtyPosition = false;
+ m_bDirtySpeed = false;
+ m_bDirtyOrientation = false;
+ m_bDirtyHead = false;
+}
+
+
+
+
+
+void cTNTEntity::Tick(float a_Dt, cChunk & a_Chunk)
+{
+ super::Tick(a_Dt, a_Chunk);
+ BroadcastMovementUpdate();
+ float delta_time = a_Dt / 1000; // Convert miliseconds to seconds
+ m_Counter += delta_time;
+ if (m_Counter > m_MaxFuseTime) // Check if we go KABOOOM
+ {
+ Destroy(true);
+ LOGD("BOOM at {%f,%f,%f}", GetPosX(), GetPosY(), GetPosZ());
+ m_World->DoExplosiontAt(4.0, GetPosX() + 0.49, GetPosY() + 0.49, GetPosZ() + 0.49, true, esPrimedTNT, this);
+ return;
+ }
+}
+
+
+
+
diff --git a/source/Entities/TNTEntity.h b/source/Entities/TNTEntity.h
new file mode 100644
index 000000000..ae6fc75e2
--- /dev/null
+++ b/source/Entities/TNTEntity.h
@@ -0,0 +1,33 @@
+
+#pragma once
+
+#include "Entity.h"
+
+
+
+
+
+class cTNTEntity :
+ public cEntity
+{
+ typedef cEntity super;
+
+public:
+ CLASS_PROTODEF(cTNTEntity);
+
+ cTNTEntity(double a_X, double a_Y, double a_Z, double a_FuseTimeInSec);
+ cTNTEntity(const Vector3d & a_Pos, double a_FuseTimeInSec);
+
+ // cEntity overrides:
+ virtual bool Initialize(cWorld * a_World) override;
+ virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
+ virtual void Tick(float a_Dt, cChunk & a_Chunk) override;
+
+protected:
+ double m_Counter; ///< How much time has elapsed since the object was created, in seconds
+ double m_MaxFuseTime; ///< How long the fuse is, in seconds
+};
+
+
+
+