diff options
Diffstat (limited to 'src/Entities/Entity.cpp')
-rw-r--r-- | src/Entities/Entity.cpp | 1447 |
1 files changed, 1447 insertions, 0 deletions
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp new file mode 100644 index 000000000..0a2145e81 --- /dev/null +++ b/src/Entities/Entity.cpp @@ -0,0 +1,1447 @@ +#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 "../Bindings/PluginManager.h" +#include "../Tracer.h" +#include "Minecart.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_TicksSinceLastVoidDamage(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); + + // Spawn the entity on the clients: + a_World->BroadcastSpawnEntity(*this); + + return true; +} + + + + + +void cEntity::WrapHeadYaw(void) +{ + m_HeadYaw = NormalizeAngleDegrees(m_HeadYaw); +} + + + + + +void cEntity::WrapRotation(void) +{ + m_Rot.x = NormalizeAngleDegrees(m_Rot.x); + m_Rot.y = NormalizeAngleDegrees(m_Rot.y); +} + + + + +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::SetRotationFromSpeed(void) +{ + const double EPS = 0.0000001; + if ((abs(m_Speed.x) < EPS) && (abs(m_Speed.z) < EPS)) + { + // atan2() may overflow or is undefined, pick any number + SetRotation(0); + return; + } + SetRotation(atan2(m_Speed.x, m_Speed.z) * 180 / PI); +} + + + + + +void cEntity::SetPitchFromSpeed(void) +{ + const double EPS = 0.0000001; + double xz = sqrt(m_Speed.x * m_Speed.x + m_Speed.z * m_Speed.z); // Speed XZ-plane component + if ((abs(xz) < EPS) && (abs(m_Speed.y) < EPS)) + { + // atan2() may overflow or is undefined, pick any number + SetPitch(0); + return; + } + SetPitch(atan2(m_Speed.y, xz) * 180 / PI); +} + + + + + +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); + } + if ((a_Chunk.IsValid()) && (GetPosY() < -46)) + { + TickInVoid(a_Chunk); + } + else { m_TicksSinceLastVoidDamage = 0; } +} + + + + + +void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) +{ + // TODO Add collision detection with entities. + a_Dt /= 1000; // Convert from msec to sec + 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 + + cChunk * NextChunk = a_Chunk.GetNeighborChunk(BlockX, BlockZ); + // See if we can commit our changes. If not, we will discard them. + if (NextChunk != NULL) + { + SetSpeed(NextSpeed); + NextPos += (NextSpeed * a_Dt); + SetPosition(NextPos); + } + 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 ); + BLOCKTYPE BlockBelow = (BlockY > 0) ? NextChunk->GetBlock(RelBlockX, BlockY - 1, RelBlockZ) : E_BLOCK_AIR; + if (!g_BlockIsSolid[BlockIn]) // Making sure we are not inside a solid block + { + if (m_bOnGround) // check if it's still on the ground + { + if (!g_BlockIsSolid[BlockBelow]) // Check if block below is air or water. + { + m_bOnGround = false; + } + } + } + else + { + // Push out entity. + BLOCKTYPE GotBlock; + + static const struct + { + int x, y, z; + } gCrossCoords[] = + { + { 1, 0, 0}, + {-1, 0, 0}, + { 0, 0, 1}, + { 0, 0, -1}, + } ; + + bool IsNoAirSurrounding = true; + for (int i = 0; i < ARRAYCOUNT(gCrossCoords); i++) + { + if (!NextChunk->UnboundedRelGetBlockType(RelBlockX + gCrossCoords[i].x, BlockY, RelBlockZ + gCrossCoords[i].z, GotBlock)) + { + // The pickup is too close to an unloaded chunk, bail out of any physics handling + return; + } + if (!g_BlockIsSolid[GotBlock]) + { + NextPos.x += gCrossCoords[i].x; + NextPos.z += gCrossCoords[i].z; + IsNoAirSurrounding = false; + break; + } + } // for i - gCrossCoords[] + + if (IsNoAirSurrounding) + { + NextPos.y += 0.5; + } + + m_bOnGround = true; + + 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 = m_Gravity * a_Dt / 3; // Fall 3x slower in water. + } + else if (IsBlockRail(BlockBelow) && IsMinecart()) // Rails aren't solid, except for Minecarts + { + fallspeed = 0; + m_bOnGround = true; + } + 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 + { + if (IsMinecart()) + { + if (!IsBlockRail(BlockBelow)) + { + // Friction if minecart is off track, otherwise, Minecart.cpp handles this + 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; + } + } + } + } + else + { + // Friction for non-minecarts + 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 = 0.2f; + m_bOnGround = false; + break; + case X_MINUS: + m_WaterSpeed.x = -0.2f; + m_bOnGround = false; + break; + case Z_PLUS: + m_WaterSpeed.z = 0.2f; + m_bOnGround = false; + break; + case Z_MINUS: + m_WaterSpeed.z = -0.2f; + 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.3f; + NextPos.y += Tracer.HitNormal.y * 0.05f; // Any larger produces entity vibration-upon-the-spot + NextPos.z += Tracer.HitNormal.z * 0.3f; + } + 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(); + } +} + + + + + +void cEntity::TickInVoid(cChunk & a_Chunk) +{ + if (m_TicksSinceLastVoidDamage == 20) + { + TakeDamage(dtInVoid, NULL, 2, 0); + m_TicksSinceLastVoidDamage = 0; + } + else + { + m_TicksSinceLastVoidDamage++; + } +} + + + + + +/// 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::SetYaw(double a_Yaw) +{ + m_Rot.x = a_Yaw; + 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(); +} + + + + + +void cEntity::SteerVehicle(float a_Forward, float a_Sideways) +{ + if (m_AttachedTo == NULL) + { + return; + } + if ((a_Forward != 0) || (a_Sideways != 0)) + { + Vector3d LookVector = GetLookVector(); + double AddSpeedX = LookVector.x * a_Forward + LookVector.z * a_Sideways; + double AddSpeedZ = LookVector.z * a_Forward - LookVector.x * a_Sideways; + m_AttachedTo->AddSpeed(AddSpeedX, 0, AddSpeedZ); + } +} + + + + + +////////////////////////////////////////////////////////////////////////// +// 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); +} + + + + |