From 1a7912744ff2e0abfeae0d2d75af80d73209580c Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Mon, 19 Aug 2013 11:39:13 +0200 Subject: Moved entities into the Entities subfolder. --- source/Entities/Entity.cpp | 1292 ++++++++++++++++++++++++++++++++ source/Entities/Entity.h | 416 +++++++++++ source/Entities/FallingBlock.cpp | 107 +++ source/Entities/FallingBlock.h | 44 ++ source/Entities/Minecart.cpp | 190 +++++ source/Entities/Minecart.h | 145 ++++ source/Entities/Pawn.cpp | 19 + source/Entities/Pawn.h | 28 + source/Entities/Pickup.cpp | 176 +++++ source/Entities/Pickup.h | 59 ++ source/Entities/Player.cpp | 1523 ++++++++++++++++++++++++++++++++++++++ source/Entities/Player.h | 372 ++++++++++ source/Entities/TNTEntity.cpp | 76 ++ source/Entities/TNTEntity.h | 33 + 14 files changed, 4480 insertions(+) create mode 100644 source/Entities/Entity.cpp create mode 100644 source/Entities/Entity.h create mode 100644 source/Entities/FallingBlock.cpp create mode 100644 source/Entities/FallingBlock.h create mode 100644 source/Entities/Minecart.cpp create mode 100644 source/Entities/Minecart.h create mode 100644 source/Entities/Pawn.cpp create mode 100644 source/Entities/Pawn.h create mode 100644 source/Entities/Pickup.cpp create mode 100644 source/Entities/Pickup.h create mode 100644 source/Entities/Player.cpp create mode 100644 source/Entities/Player.h create mode 100644 source/Entities/TNTEntity.cpp create mode 100644 source/Entities/TNTEntity.h (limited to 'source/Entities') 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 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 +#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 + +#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 +}; + + + + -- cgit v1.2.3