diff options
Diffstat (limited to 'src/Entities')
27 files changed, 1843 insertions, 834 deletions
diff --git a/src/Entities/Boat.cpp b/src/Entities/Boat.cpp index 184aeeeeb..921252253 100644 --- a/src/Entities/Boat.cpp +++ b/src/Entities/Boat.cpp @@ -89,10 +89,20 @@ void cBoat::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); BroadcastMovementUpdate(); - SetSpeed(GetSpeed() * 0.97); // Slowly decrease the speed. - if (IsBlockWater(m_World->GetBlock((int) GetPosX(), (int) GetPosY(), (int) GetPosZ()))) + + SetSpeed(GetSpeed() * 0.97); // Slowly decrease the speed + + if ((POSY_TOINT < 0) || (POSY_TOINT > cChunkDef::Height)) + { + return; + } + + if (IsBlockWater(m_World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT))) { - SetSpeedY(1); + if (GetSpeedY() < 2) + { + AddSpeedY(0.2); + } } } @@ -102,15 +112,13 @@ void cBoat::Tick(float a_Dt, cChunk & a_Chunk) void cBoat::HandleSpeedFromAttachee(float a_Forward, float a_Sideways) { - if (GetSpeed().Length() > 7) + if (GetSpeed().Length() > 7.5) { return; } - Vector3d ToAddSpeed(m_Attachee->GetLookVector() * (a_Sideways * 1.5)); + Vector3d ToAddSpeed = m_Attachee->GetLookVector() * (a_Sideways * 0.4) ; ToAddSpeed.y = 0; AddSpeed(ToAddSpeed); } - -
\ No newline at end of file diff --git a/src/Entities/Effects.h b/src/Entities/Effects.h index e7611847d..baf3302fb 100644 --- a/src/Entities/Effects.h +++ b/src/Entities/Effects.h @@ -27,4 +27,4 @@ enum ENUM_ENTITY_EFFECT E_EFFECT_ABSORPTION = 22, E_EFFECT_SATURATION = 23, } ; -// tolua_end
\ No newline at end of file +// tolua_end diff --git a/src/Entities/EnderCrystal.cpp b/src/Entities/EnderCrystal.cpp new file mode 100644 index 000000000..a640b236c --- /dev/null +++ b/src/Entities/EnderCrystal.cpp @@ -0,0 +1,56 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "EnderCrystal.h" +#include "ClientHandle.h" +#include "Player.h" +#include "../Chunk.h" + + + + + +cEnderCrystal::cEnderCrystal(double a_X, double a_Y, double a_Z) + : cEntity(etEnderCrystal, a_X, a_Y, a_Z, 1.0, 1.0) +{ + SetMaxHealth(5); +} + + + + + +void cEnderCrystal::SpawnOn(cClientHandle & a_ClientHandle) +{ + a_ClientHandle.SendSpawnObject(*this, 51, 0, (Byte)GetYaw(), (Byte)GetPitch()); +} + + + + + +void cEnderCrystal::Tick(float a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + + a_Chunk.SetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT, E_BLOCK_FIRE, 0); + + // No further processing (physics e.t.c.) is needed +} + + + + + +void cEnderCrystal::KilledBy(cEntity * a_Killer) +{ + super::KilledBy(a_Killer); + + m_World->DoExplosionAt(6.0, GetPosX(), GetPosY(), GetPosZ(), true, esEnderCrystal, this); + + Destroy(); +} + + + + diff --git a/src/Entities/EnderCrystal.h b/src/Entities/EnderCrystal.h new file mode 100644 index 000000000..5b86df987 --- /dev/null +++ b/src/Entities/EnderCrystal.h @@ -0,0 +1,33 @@ + +#pragma once + +#include "Entity.h" + + + + + +// tolua_begin +class cEnderCrystal : + public cEntity +{ + // tolua_end + typedef cEntity super; + +public: + CLASS_PROTODEF(cEnderCrystal); + + cEnderCrystal(double a_X, double a_Y, double a_Z); + +private: + + // cEntity overrides: + virtual void SpawnOn(cClientHandle & a_ClientHandle) override; + virtual void Tick(float a_Dt, cChunk & a_Chunk) override; + virtual void KilledBy(cEntity * a_Killer) override; + +}; // tolua_export + + + + diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 565c78dfd..8ef45f1a5 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -4,9 +4,7 @@ #include "../World.h" #include "../Server.h" #include "../Root.h" -#include "../Vector3d.h" -#include "../Matrix4f.h" -#include "../ReferenceManager.h" +#include "../Matrix4.h" #include "../ClientHandle.h" #include "../Chunk.h" #include "../Simulator/FluidSimulator.h" @@ -32,8 +30,6 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d , m_MaxHealth(1) , m_AttachedTo(NULL) , m_Attachee(NULL) - , m_Referencers(new cReferenceManager(cReferenceManager::RFMNGR_REFERENCERS)) - , m_References(new cReferenceManager(cReferenceManager::RFMNGR_REFERENCES)) , m_bDirtyHead(true) , m_bDirtyOrientation(true) , m_bDirtyPosition(true) @@ -54,6 +50,8 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d , m_TicksSinceLastFireDamage(0) , m_TicksLeftBurning(0) , m_TicksSinceLastVoidDamage(0) + , m_IsSwimming(false) + , m_IsSubmerged(false) , m_HeadYaw( 0.0 ) , m_Rot(0.0, 0.0, 0.0) , m_Pos(a_X, a_Y, a_Z) @@ -73,7 +71,8 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d cEntity::~cEntity() { - ASSERT(!m_World->HasEntity(m_UniqueID)); // Before deleting, the entity needs to have been removed from the world + // Before deleting, the entity needs to have been removed from the world, if ever added + ASSERT((m_World == NULL) || !m_World->HasEntity(m_UniqueID)); /* // DEBUG: @@ -99,8 +98,6 @@ cEntity::~cEntity() LOGWARNING("ERROR: Entity deallocated without being destroyed"); ASSERT(!"Entity deallocated without being destroyed or unlinked"); } - delete m_Referencers; - delete m_References; } @@ -400,6 +397,7 @@ int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_Dama case dtPotionOfHarming: case dtFalling: case dtLightning: + case dtPlugin: { return 0; } @@ -476,7 +474,7 @@ void cEntity::KilledBy(cEntity * a_Killer) return; } - // Drop loot: + // Drop loot: cItems Drops; GetDrops(Drops, a_Killer); m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ()); @@ -523,18 +521,36 @@ void cEntity::Tick(float a_Dt, cChunk & a_Chunk) { if (a_Chunk.IsValid()) { - HandlePhysics(a_Dt, a_Chunk); + cChunk * NextChunk = a_Chunk.GetNeighborChunk(POSX_TOINT, POSZ_TOINT); + + if ((NextChunk == NULL) || !NextChunk->IsValid()) + { + return; + } + + TickBurning(*NextChunk); + + if (GetPosY() < VOID_BOUNDARY) + { + TickInVoid(*NextChunk); + } + else + { + m_TicksSinceLastVoidDamage = 0; + } + + if (IsMob() || IsPlayer()) + { + // Set swimming state + SetSwimState(*NextChunk); + + // Handle drowning + HandleAir(); + } + + HandlePhysics(a_Dt, *NextChunk); } } - if (a_Chunk.IsValid()) - { - TickBurning(a_Chunk); - } - if ((a_Chunk.IsValid()) && (GetPosY() < -46)) - { - TickInVoid(a_Chunk); - } - else { m_TicksSinceLastVoidDamage = 0; } } @@ -554,7 +570,7 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) 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) @@ -563,210 +579,205 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) 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 - (a_Chunk.GetPosX() * cChunkDef::Width); + int RelBlockZ = BlockZ - (a_Chunk.GetPosZ() * cChunkDef::Width); + BLOCKTYPE BlockIn = a_Chunk.GetBlock( RelBlockX, BlockY, RelBlockZ ); + BLOCKTYPE BlockBelow = (BlockY > 0) ? a_Chunk.GetBlock(RelBlockX, BlockY - 1, RelBlockZ) : E_BLOCK_AIR; + if (!cBlockInfo::IsSolid(BlockIn)) // Making sure we are not inside a solid block { - 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 (m_bOnGround) // check if it's still on the ground + if (!cBlockInfo::IsSolid(BlockBelow)) // Check if block below is air or water. { - if (!g_BlockIsSolid[BlockBelow]) // Check if block below is air or water. - { - m_bOnGround = false; - } + m_bOnGround = false; } } - else - { - // Push out entity. - BLOCKTYPE GotBlock; + } + 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 (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++) + static const struct + { + int x, y, z; + } gCrossCoords[] = + { + { 1, 0, 0}, + {-1, 0, 0}, + { 0, 0, 1}, + { 0, 0, -1}, + } ; + + bool IsNoAirSurrounding = true; + for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++) + { + if (!a_Chunk.UnboundedRelGetBlockType(RelBlockX + gCrossCoords[i].x, BlockY, RelBlockZ + gCrossCoords[i].z, GotBlock)) { - 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) + // The pickup is too close to an unloaded chunk, bail out of any physics handling + return; + } + if (!cBlockInfo::IsSolid(GotBlock)) { - NextPos.y += 0.5; + 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; + m_bOnGround = true; - /* - // DEBUG: - LOGD("Entity #%d (%s) is inside a block at {%d, %d, %d}", - m_UniqueID, GetClass(), BlockX, BlockY, BlockZ - ); - */ - } + /* + // DEBUG: + LOGD("Entity #%d (%s) is inside a block at {%d, %d, %d}", + m_UniqueID, GetClass(), BlockX, BlockY, BlockZ + ); + */ + } - if (!m_bOnGround) + if (!m_bOnGround) + { + float fallspeed; + if (IsBlockWater(BlockIn)) { - float fallspeed; - if (IsBlockWater(BlockIn)) - { - fallspeed = m_Gravity * a_Dt / 3; // Fall 3x 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; + fallspeed = m_Gravity * a_Dt / 3; // Fall 3x slower in water. + } + else if (BlockIn == E_BLOCK_COBWEB) + { + NextSpeed.y *= 0.05; // Reduce overall falling speed + fallspeed = 0; // No falling. } else { - // Friction - if (NextSpeed.SqrLength() > 0.0004f) + // 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.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; - } + 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; - } + // 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); + //Get water direction + Direction WaterDir = m_World->GetWaterSimulator()->GetFlowingDirection(BlockX, BlockY, BlockZ); - m_WaterSpeed *= 0.9f; //Reduce speed each tick + 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: + 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.x) < 0.05) + { + m_WaterSpeed.x = 0; + } - if (fabs(m_WaterSpeed.z) < 0.05) - { - m_WaterSpeed.z = 0; - } + if (fabs(m_WaterSpeed.z) < 0.05) + { + m_WaterSpeed.z = 0; + } - NextSpeed += m_WaterSpeed; + NextSpeed += m_WaterSpeed; - if( NextSpeed.SqrLength() > 0.f ) + if( NextSpeed.SqrLength() > 0.f ) + { + cTracer Tracer( GetWorld() ); + bool HasHit = Tracer.Trace( NextPos, NextSpeed, 2 ); + if (HasHit) // Oh noez! we hit something { - 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()) { - // 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 + 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 { - NextPos += (NextSpeed * a_Dt); + 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 { - // 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) + else { - 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); + // We didn't hit anything, so move =] + NextPos += (NextSpeed * a_Dt); } } + + BlockX = (int) floor(NextPos.x); + BlockZ = (int) floor(NextPos.z); + + cChunk * NextChunk = a_Chunk.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); + } } @@ -807,14 +818,13 @@ void cEntity::TickBurning(cChunk & a_Chunk) { 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)) + BLOCKTYPE Block; + a_Chunk.UnboundedRelGetBlockType(RelX, y, RelZ, Block); + + switch (Block) { case E_BLOCK_FIRE: { @@ -912,6 +922,86 @@ void cEntity::TickInVoid(cChunk & a_Chunk) +void cEntity::SetSwimState(cChunk & a_Chunk) +{ + int RelY = (int)floor(GetPosY() + 0.1); + if ((RelY < 0) || (RelY >= cChunkDef::Height - 1)) + { + m_IsSwimming = false; + m_IsSubmerged = false; + return; + } + + BLOCKTYPE BlockIn; + int RelX = POSX_TOINT - a_Chunk.GetPosX() * cChunkDef::Width; + int RelZ = POSZ_TOINT - a_Chunk.GetPosZ() * cChunkDef::Width; + + // Check if the player is swimming: + if (!a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn)) + { + // This sometimes happens on Linux machines + // Ref.: http://forum.mc-server.org/showthread.php?tid=1244 + LOGD("SetSwimState failure: RelX = %d, RelZ = %d, LastPos = {%.02f, %.02f}, Pos = %.02f, %.02f}", + RelX, RelY, m_LastPosX, m_LastPosZ, GetPosX(), GetPosZ() + ); + m_IsSwimming = false; + m_IsSubmerged = false; + return; + } + m_IsSwimming = IsBlockWater(BlockIn); + + // Check if the player is submerged: + VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, RelY + 1, RelZ, BlockIn)); + m_IsSubmerged = IsBlockWater(BlockIn); +} + + + + + +void cEntity::HandleAir(void) +{ + // Ref.: http://www.minecraftwiki.net/wiki/Chunk_format + // See if the entity is /submerged/ water (block above is water) + // Get the type of block the entity is standing in: + + if (IsSubmerged()) + { + SetSpeedY(1); // Float in the water + + // 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; + } +} + + + + + /// Called when the entity starts burning void cEntity::OnStartedBurning(void) { @@ -1379,7 +1469,7 @@ void cEntity::SteerVehicle(float a_Forward, float a_Sideways) Vector3d cEntity::GetLookVector(void) const { Matrix4d m; - m.Init(Vector3f(), 0, m_Rot.x, -m_Rot.y); + m.Init(Vector3d(), 0, m_Rot.x, -m_Rot.y); Vector3d Look = m.Transform(Vector3d(0, 0, 1)); return Look; } @@ -1429,33 +1519,3 @@ void cEntity::SetPosZ(double a_PosZ) - -////////////////////////////////////////////////////////////////////////// -// 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/src/Entities/Entity.h b/src/Entities/Entity.h index 91463bfd6..6e3f8292b 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -2,8 +2,7 @@ #pragma once #include "../Item.h" -#include "../Vector3d.h" -#include "../Vector3f.h" +#include "../Vector3.h" @@ -28,12 +27,16 @@ return super::GetClass(); \ } +#define POSX_TOINT (int)floor(GetPosX()) +#define POSY_TOINT (int)floor(GetPosY()) +#define POSZ_TOINT (int)floor(GetPosZ()) +#define POS_TOINT Vector3i(POSXTOINT, POSYTOINT, POSZTOINT) + class cWorld; -class cReferenceManager; class cClientHandle; class cPlayer; class cChunk; @@ -66,6 +69,7 @@ public: enum eEntityType { etEntity, // For all other types + etEnderCrystal, etPlayer, etPickup, etMonster, @@ -76,6 +80,8 @@ public: etProjectile, etExpOrb, etFloater, + etItemFrame, + etPainting, // Common variations etMob = etMonster, // DEPRECATED, use etMonster instead! @@ -110,6 +116,9 @@ public: 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 + MAX_AIR_LEVEL = 300, ///< Maximum air an entity can have + DROWNING_TICKS = 20, ///< Number of ticks per heart of damage + VOID_BOUNDARY = -46 ///< At what position Y to begin applying void damage } ; cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height); @@ -122,16 +131,19 @@ public: 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 == etMonster); } + bool IsEnderCrystal(void) const { return (m_EntityType == etEnderCrystal); } + bool IsPlayer (void) const { return (m_EntityType == etPlayer); } + bool IsPickup (void) const { return (m_EntityType == etPickup); } + bool IsMob (void) const { return (m_EntityType == etMonster); } bool IsFallingBlock(void) const { return (m_EntityType == etFallingBlock); } - bool IsMinecart (void) const { return (m_EntityType == etMinecart); } - bool IsBoat (void) const { return (m_EntityType == etBoat); } - bool IsTNT (void) const { return (m_EntityType == etTNT); } - bool IsProjectile (void) const { return (m_EntityType == etProjectile); } - bool IsExpOrb (void) const { return (m_EntityType == etExpOrb); } - bool IsFloater (void) const { return (m_EntityType == etFloater); } + bool IsMinecart (void) const { return (m_EntityType == etMinecart); } + bool IsBoat (void) const { return (m_EntityType == etBoat); } + bool IsTNT (void) const { return (m_EntityType == etTNT); } + bool IsProjectile (void) const { return (m_EntityType == etProjectile); } + bool IsExpOrb (void) const { return (m_EntityType == etExpOrb); } + bool IsFloater (void) const { return (m_EntityType == etFloater); } + bool IsItemFrame (void) const { return (m_EntityType == etItemFrame); } + bool IsPainting (void) const { return (m_EntityType == etPainting); } /// 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; @@ -147,7 +159,7 @@ public: cWorld * GetWorld(void) const { return m_World; } - double GetHeadYaw (void) const { return m_HeadYaw; } + double GetHeadYaw (void) const { return m_HeadYaw; } // In degrees double GetHeight (void) const { return m_Height; } double GetMass (void) const { return m_Mass; } const Vector3d & GetPosition (void) const { return m_Pos; } @@ -155,9 +167,9 @@ public: double GetPosY (void) const { return m_Pos.y; } double GetPosZ (void) const { return m_Pos.z; } const Vector3d & GetRot (void) const { return m_Rot; } // OBSOLETE, use individual GetYaw(), GetPitch, GetRoll() components - double GetYaw (void) const { return m_Rot.x; } - double GetPitch (void) const { return m_Rot.y; } - double GetRoll (void) const { return m_Rot.z; } + double GetYaw (void) const { return m_Rot.x; } // In degrees, [-180, +180) + double GetPitch (void) const { return m_Rot.y; } // In degrees, [-180, +180), but normal client clips to [-90, +90] + double GetRoll (void) const { return m_Rot.z; } // In degrees, unused in current client Vector3d GetLookVector(void) const; const Vector3d & GetSpeed (void) const { return m_Speed; } double GetSpeedX (void) const { return m_Speed.x; } @@ -177,9 +189,9 @@ public: 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); // OBSOLETE, use individual SetYaw(), SetPitch(), SetRoll() components - void SetYaw (double a_Yaw); - void SetPitch (double a_Pitch); - void SetRoll (double a_Roll); + void SetYaw (double a_Yaw); // In degrees, normalizes to [-180, +180) + void SetPitch (double a_Pitch); // In degrees, normalizes to [-180, +180) + void SetRoll (double a_Roll); // In degrees, normalizes to [-180, +180) 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); @@ -344,7 +356,14 @@ public: virtual bool IsRiding (void) const {return false; } virtual bool IsSprinting(void) const {return false; } virtual bool IsRclking (void) const {return false; } - virtual bool IsInvisible(void) const {return false; } + virtual bool IsInvisible(void) const { return false; } + + /** 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; } + /** Gets remaining air of a monster */ + int GetAirLevel(void) const { return m_AirLevel; } // tolua_end @@ -373,9 +392,6 @@ protected: /// 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; @@ -415,11 +431,18 @@ protected: 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 ); + + /** Called in each tick to handle air-related processing i.e. drowning */ + virtual void HandleAir(); + /** Called once per tick to set IsSwimming and IsSubmerged */ + virtual void SetSwimState(cChunk & a_Chunk); + + /** If an entity is currently swimming in or submerged under water */ + bool m_IsSwimming, m_IsSubmerged; + + /** Air level of a mobile */ + int m_AirLevel; + int m_AirTickTimer; private: // Measured in degrees, [-180, +180) diff --git a/src/Entities/ExpOrb.cpp b/src/Entities/ExpOrb.cpp index 04ee85823..3623c869a 100644 --- a/src/Entities/ExpOrb.cpp +++ b/src/Entities/ExpOrb.cpp @@ -5,20 +5,26 @@ #include "../ClientHandle.h" -cExpOrb::cExpOrb(double a_X, double a_Y, double a_Z, int a_Reward) : - cEntity(etExpOrb, a_X, a_Y, a_Z, 0.98, 0.98), - m_Reward(a_Reward) +cExpOrb::cExpOrb(double a_X, double a_Y, double a_Z, int a_Reward) + : cEntity(etExpOrb, a_X, a_Y, a_Z, 0.98, 0.98) + , m_Reward(a_Reward) + , m_Timer(0.f) { + SetMaxHealth(5); + SetHealth(5); } -cExpOrb::cExpOrb(const Vector3d & a_Pos, int a_Reward) : - cEntity(etExpOrb, a_Pos.x, a_Pos.y, a_Pos.z, 0.98, 0.98), - m_Reward(a_Reward) +cExpOrb::cExpOrb(const Vector3d & a_Pos, int a_Reward) + : cEntity(etExpOrb, a_Pos.x, a_Pos.y, a_Pos.z, 0.98, 0.98) + , m_Reward(a_Reward) + , m_Timer(0.f) { + SetMaxHealth(5); + SetHealth(5); } @@ -51,7 +57,10 @@ void cExpOrb::Tick(float a_Dt, cChunk & a_Chunk) { LOGD("Player %s picked up an ExpOrb. His reward is %i", a_ClosestPlayer->GetName().c_str(), m_Reward); a_ClosestPlayer->DeltaExperience(m_Reward); - Destroy(true); + + m_World->BroadcastSoundEffect("random.orb", (int)(GetPosX() * 8), (int)(GetPosY() * 8), (int)(GetPosZ() * 8), 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + + Destroy(); } a_Distance.Normalize(); a_Distance *= ((float) (5.5 - Distance)); @@ -61,4 +70,10 @@ void cExpOrb::Tick(float a_Dt, cChunk & a_Chunk) BroadcastMovementUpdate(); } HandlePhysics(a_Dt, a_Chunk); -}
\ No newline at end of file + + m_Timer += a_Dt; + if (m_Timer >= 1000 * 60 * 5) // 5 minutes + { + Destroy(true); + } +} diff --git a/src/Entities/ExpOrb.h b/src/Entities/ExpOrb.h index a062eedd3..e76274ac9 100644 --- a/src/Entities/ExpOrb.h +++ b/src/Entities/ExpOrb.h @@ -7,13 +7,17 @@ +// tolua_begin class cExpOrb : public cEntity { typedef cExpOrb super; public: - + // tolua_end + + CLASS_PROTODEF(cExpOrb); + cExpOrb(double a_X, double a_Y, double a_Z, int a_Reward); cExpOrb(const Vector3d & a_Pos, int a_Reward); @@ -21,9 +25,21 @@ public: virtual void Tick(float a_Dt, cChunk & a_Chunk) override; virtual void SpawnOn(cClientHandle & a_Client) override; - // cExpOrb functions - int GetReward(void) const { return m_Reward; } + /** Returns the number of ticks that this entity has existed */ + int GetAge(void) const { return (int)(m_Timer / 50); } // tolua_export + + /** Set the number of ticks that this entity has existed */ + void SetAge(int a_Age) { m_Timer = (float)(a_Age * 50); } // tolua_export + + /** Get the exp amount */ + int GetReward(void) const { return m_Reward; } // tolua_export + + /** Set the exp amount */ + void SetReward(int a_Reward) { m_Reward = a_Reward; } // tolua_export protected: int m_Reward; -} ;
\ No newline at end of file + + /** The number of ticks that the entity has existed / timer between collect and destroy; in msec */ + float m_Timer; +} ; // tolua_export diff --git a/src/Entities/FallingBlock.cpp b/src/Entities/FallingBlock.cpp index 9fcd9ac80..a66c7e4ae 100644 --- a/src/Entities/FallingBlock.cpp +++ b/src/Entities/FallingBlock.cpp @@ -33,20 +33,16 @@ void cFallingBlock::SpawnOn(cClientHandle & a_ClientHandle) 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 BlockX = POSX_TOINT; int BlockY = (int)(GetPosY() - 0.5); - int BlockZ = m_OriginalPosition.z; + int BlockZ = POSZ_TOINT; if (BlockY < 0) { // Fallen out of this world, just continue falling until out of sight, then destroy: - if (BlockY < 100) + if (BlockY < VOID_BOUNDARY) { Destroy(true); } @@ -86,6 +82,15 @@ void cFallingBlock::Tick(float a_Dt, cChunk & a_Chunk) Destroy(true); return; } + + float MilliDt = a_Dt * 0.001f; + AddSpeedY(MilliDt * -9.8f); + AddPosition(GetSpeed() * MilliDt); + + if ((GetSpeedX() != 0) || (GetSpeedZ() != 0)) + { + BroadcastMovementUpdate(); + } } diff --git a/src/Entities/Floater.cpp b/src/Entities/Floater.cpp index dfe77f059..b910c3769 100644 --- a/src/Entities/Floater.cpp +++ b/src/Entities/Floater.cpp @@ -35,7 +35,7 @@ public: cBoundingBox EntBox(a_Entity->GetPosition(), a_Entity->GetWidth() / 2, a_Entity->GetHeight()); double LineCoeff; - char Face; + eBlockFace Face; EntBox.Expand(m_Floater->GetWidth() / 2, m_Floater->GetHeight() / 2, m_Floater->GetWidth() / 2); if (!EntBox.CalcLineIntersection(m_Pos, m_NextPos, LineCoeff, Face)) { @@ -103,10 +103,10 @@ protected: cFloater::cFloater(double a_X, double a_Y, double a_Z, Vector3d a_Speed, int a_PlayerID, int a_CountDownTime) : cEntity(etFloater, a_X, a_Y, a_Z, 0.2, 0.2), - m_PickupCountDown(0), - m_PlayerID(a_PlayerID), m_CanPickupItem(false), + m_PickupCountDown(0), m_CountDownTime(a_CountDownTime), + m_PlayerID(a_PlayerID), m_AttachedMobID(-1) { SetSpeed(a_Speed); @@ -215,4 +215,4 @@ void cFloater::Tick(float a_Dt, cChunk & a_Chunk) SetSpeedZ(GetSpeedZ() * 0.95); BroadcastMovementUpdate(); -}
\ No newline at end of file +} diff --git a/src/Entities/Floater.h b/src/Entities/Floater.h index 162b74e75..547d503f1 100644 --- a/src/Entities/Floater.h +++ b/src/Entities/Floater.h @@ -11,11 +11,13 @@ class cFloater : public cEntity { - typedef cFloater super; + typedef cEntity super; public: - //tolua_end + + CLASS_PROTODEF(cFloater); + cFloater(double a_X, double a_Y, double a_Z, Vector3d a_Speed, int a_PlayerID, int a_CountDownTime); virtual void SpawnOn(cClientHandle & a_Client) override; @@ -41,4 +43,4 @@ protected: // Entity IDs int m_PlayerID; int m_AttachedMobID; -} ; // tolua_export
\ No newline at end of file +} ; // tolua_export diff --git a/src/Entities/HangingEntity.cpp b/src/Entities/HangingEntity.cpp new file mode 100644 index 000000000..41ac86268 --- /dev/null +++ b/src/Entities/HangingEntity.cpp @@ -0,0 +1,53 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "HangingEntity.h" +#include "ClientHandle.h" +#include "Player.h" + + + + + +cHangingEntity::cHangingEntity(eEntityType a_EntityType, eBlockFace a_BlockFace, double a_X, double a_Y, double a_Z) + : cEntity(a_EntityType, a_X, a_Y, a_Z, 0.8, 0.8) + , m_BlockFace(a_BlockFace) +{ + SetMaxHealth(1); + SetHealth(1); +} + + + + + +void cHangingEntity::SpawnOn(cClientHandle & a_ClientHandle) +{ + int Dir = 0; + + // The client uses different values for item frame directions and block faces. Our constants are for the block faces, so we convert them here to item frame faces + switch (m_BlockFace) + { + case BLOCK_FACE_ZP: break; // Initialised to zero + case BLOCK_FACE_ZM: Dir = 2; break; + case BLOCK_FACE_XM: Dir = 1; break; + case BLOCK_FACE_XP: Dir = 3; break; + default: ASSERT(!"Unhandled block face when trying to spawn item frame!"); return; + } + + if ((Dir == 0) || (Dir == 2)) // Probably a client bug, but two directions are flipped and contrary to the norm, so we do -180 + { + SetYaw((Dir * 90) - 180); + } + else + { + SetYaw(Dir * 90); + } + + a_ClientHandle.SendSpawnObject(*this, 71, Dir, (Byte)GetYaw(), (Byte)GetPitch()); + a_ClientHandle.SendEntityMetadata(*this); +} + + + + diff --git a/src/Entities/HangingEntity.h b/src/Entities/HangingEntity.h new file mode 100644 index 000000000..6498e4b5b --- /dev/null +++ b/src/Entities/HangingEntity.h @@ -0,0 +1,49 @@ + +#pragma once + +#include "Entity.h" + + + + + +// tolua_begin +class cHangingEntity : + public cEntity +{ + // tolua_end + typedef cEntity super; + +public: + + CLASS_PROTODEF(cHangingEntity); + + cHangingEntity(eEntityType a_EntityType, eBlockFace a_BlockFace, double a_X, double a_Y, double a_Z); + + /** Returns the orientation from the hanging entity */ + eBlockFace GetDirection() const { return m_BlockFace; } // tolua_export + + /** Set the orientation from the hanging entity */ + void SetDirection(eBlockFace a_BlockFace) { m_BlockFace = a_BlockFace; } // tolua_export + + /** Returns the X coord. */ + int GetTileX() const { return POSX_TOINT; } // tolua_export + + /** Returns the Y coord. */ + int GetTileY() const { return POSY_TOINT; } // tolua_export + + /** Returns the Z coord. */ + int GetTileZ() const { return POSZ_TOINT; } // tolua_export + +private: + + virtual void SpawnOn(cClientHandle & a_ClientHandle) override; + virtual void Tick(float a_Dt, cChunk & a_Chunk) override {}; + + eBlockFace m_BlockFace; + +}; // tolua_export + + + + diff --git a/src/Entities/ItemFrame.cpp b/src/Entities/ItemFrame.cpp new file mode 100644 index 000000000..9dd909880 --- /dev/null +++ b/src/Entities/ItemFrame.cpp @@ -0,0 +1,91 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "ItemFrame.h" +#include "ClientHandle.h" +#include "Player.h" + + + + + +cItemFrame::cItemFrame(eBlockFace a_BlockFace, double a_X, double a_Y, double a_Z) + : cHangingEntity(etItemFrame, a_BlockFace, a_X, a_Y, a_Z) + , m_Item(E_BLOCK_AIR) + , m_Rotation(0) +{ +} + + + + + +void cItemFrame::OnRightClicked(cPlayer & a_Player) +{ + if (!m_Item.IsEmpty()) + { + // Item not empty, rotate, clipping values to zero to three inclusive + m_Rotation++; + if (m_Rotation >= 4) + { + m_Rotation = 0; + } + } + else if (!a_Player.GetEquippedItem().IsEmpty()) + { + // Item empty, and player held item not empty - add this item to self + m_Item = a_Player.GetEquippedItem(); + m_Item.m_ItemCount = 1; + + if (!a_Player.IsGameModeCreative()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + } + } + + GetWorld()->BroadcastEntityMetadata(*this); // Update clients +} + + + + + + +void cItemFrame::KilledBy(cEntity * a_Killer) +{ + if (m_Item.IsEmpty()) + { + super::KilledBy(a_Killer); + Destroy(); + return; + } + + if ((a_Killer != NULL) && a_Killer->IsPlayer() && !((cPlayer *)a_Killer)->IsGameModeCreative()) + { + cItems Item; + Item.push_back(m_Item); + + GetWorld()->SpawnItemPickups(Item, GetPosX(), GetPosY(), GetPosZ()); + } + + SetHealth(GetMaxHealth()); + m_Item.Clear(); + m_Rotation = 0; + GetWorld()->BroadcastEntityMetadata(*this); +} + + + + + +void cItemFrame::GetDrops(cItems & a_Items, cEntity * a_Killer) +{ + if ((a_Killer != NULL) && a_Killer->IsPlayer() && !((cPlayer *)a_Killer)->IsGameModeCreative()) + { + a_Items.push_back(cItem(E_ITEM_ITEM_FRAME)); + } +} + + + + diff --git a/src/Entities/ItemFrame.h b/src/Entities/ItemFrame.h new file mode 100644 index 000000000..6577e7d94 --- /dev/null +++ b/src/Entities/ItemFrame.h @@ -0,0 +1,48 @@ + +#pragma once + +#include "HangingEntity.h" + + + + + +// tolua_begin +class cItemFrame : + public cHangingEntity +{ + // tolua_end + typedef cHangingEntity super; + +public: + + CLASS_PROTODEF(cItemFrame); + + cItemFrame(eBlockFace a_BlockFace, double a_X, double a_Y, double a_Z); + + /** Returns the item in the frame */ + const cItem & GetItem(void) { return m_Item; } // tolua_export + + /** Set the item in the frame */ + void SetItem(cItem & a_Item) { m_Item = a_Item; }; // tolua_export + + /** Returns the rotation from the item in the frame */ + Byte GetRotation(void) const { return m_Rotation; } // tolua_export + + /** Set the rotation from the item in the frame */ + void SetRotation(Byte a_Rotation) { m_Rotation = a_Rotation; } // tolua_export + +private: + + virtual void OnRightClicked(cPlayer & a_Player) override; + virtual void KilledBy(cEntity * a_Killer) override; + virtual void GetDrops(cItems & a_Items, cEntity * a_Killer) override; + + cItem m_Item; + Byte m_Rotation; + +}; // tolua_export + + + + diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp index d0d384481..7f38aa35a 100644 --- a/src/Entities/Minecart.cpp +++ b/src/Entities/Minecart.cpp @@ -19,6 +19,70 @@ +class cMinecartCollisionCallback : + public cEntityCallback +{ +public: + cMinecartCollisionCallback(Vector3d a_Pos, double a_Height, double a_Width, int a_UniqueID, int a_AttacheeUniqueID) : + m_DoesInteserct(false), + m_CollidedEntityPos(0, 0, 0), + m_Pos(a_Pos), + m_Height(a_Height), + m_Width(a_Width), + m_UniqueID(a_UniqueID), + m_AttacheeUniqueID(a_AttacheeUniqueID) + { + } + + virtual bool Item(cEntity * a_Entity) override + { + ASSERT(a_Entity != NULL); + + if (!a_Entity->IsPlayer() && !a_Entity->IsMob() && !a_Entity->IsMinecart() && !a_Entity->IsBoat()) + { + return false; + } + else if ((a_Entity->GetUniqueID() == m_UniqueID) || (a_Entity->GetUniqueID() == m_AttacheeUniqueID)) + { + return false; + } + + cBoundingBox bbEntity(a_Entity->GetPosition(), a_Entity->GetWidth() / 2, a_Entity->GetHeight()); + cBoundingBox bbMinecart(Vector3d(m_Pos.x, floor(m_Pos.y), m_Pos.z), m_Width / 2, m_Height); + + if (bbEntity.DoesIntersect(bbMinecart)) + { + m_CollidedEntityPos = a_Entity->GetPosition(); + m_DoesInteserct = true; + return true; + } + return false; + } + + bool FoundIntersection(void) const + { + return m_DoesInteserct; + } + + Vector3d GetCollidedEntityPosition(void) const + { + return m_CollidedEntityPos; + } + +protected: + bool m_DoesInteserct; + + Vector3d m_CollidedEntityPos; + + Vector3d m_Pos; + double m_Height, m_Width; + int m_UniqueID; + int m_AttacheeUniqueID; +}; + + + + 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), @@ -30,7 +94,7 @@ cMinecart::cMinecart(ePayload a_Payload, double a_X, double a_Y, double a_Z) : SetMass(20.f); SetMaxHealth(6); SetHealth(6); - SetWidth(1.2); + SetWidth(1); SetHeight(0.9); } @@ -96,10 +160,17 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) if (IsBlockRail(InsideType)) AddPosY(1); // Push cart upwards } + bool WasDetectorRail = false; if (IsBlockRail(InsideType)) { - bool WasDetectorRail = false; - SnapToRail(InsideMeta); + if (InsideType == E_BLOCK_RAIL) + { + SnapToRail(InsideMeta); + } + else + { + SnapToRail(InsideMeta & 0x07); + } switch (InsideType) { @@ -116,12 +187,6 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) } AddPosition(GetSpeed() * (a_Dt / 1000)); // Commit changes; as we use our own engine when on rails, this needs to be done, whereas it is normally in Entity.cpp - - if (m_bIsOnDetectorRail && !WasDetectorRail) - { - m_World->SetBlock(m_DetectorRailPosition.x, m_DetectorRailPosition.y, m_DetectorRailPosition.z, E_BLOCK_DETECTOR_RAIL, m_World->GetBlockMeta(m_DetectorRailPosition) & 0x07); - m_bIsOnDetectorRail = false; - } } else { @@ -129,6 +194,17 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) SetPosY(floor(GetPosY()) + 0.35); // HandlePhysics overrides this if minecart can fall, else, it is to stop ground clipping minecart bottom when off-rail super::HandlePhysics(a_Dt, *Chunk); } + + if (m_bIsOnDetectorRail && !Vector3i((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())).Equals(m_DetectorRailPosition)) + { + m_World->SetBlock(m_DetectorRailPosition.x, m_DetectorRailPosition.y, m_DetectorRailPosition.z, E_BLOCK_DETECTOR_RAIL, m_World->GetBlockMeta(m_DetectorRailPosition) & 0x07); + m_bIsOnDetectorRail = false; + } + else if (WasDetectorRail) + { + m_bIsOnDetectorRail = true; + m_DetectorRailPosition = Vector3i((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); + } // Broadcast positioning changes to client BroadcastMovementUpdate(); @@ -154,7 +230,9 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) SetSpeedY(0); // Don't move vertically as on ground SetSpeedX(0); // Correct diagonal movement from curved rails - if (TestBlockCollision(a_RailMeta)) return; + // Execute both the entity and block collision checks + bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) return; if (GetSpeedZ() != 0) // Don't do anything if cart is stationary { @@ -178,7 +256,8 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) SetSpeedY(0); SetSpeedZ(0); - if (TestBlockCollision(a_RailMeta)) return; + bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) return; if (GetSpeedX() != 0) { @@ -281,94 +360,46 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) { SetYaw(315); // Set correct rotation server side SetPosY(floor(GetPosY()) + 0.55); // Levitate dat cart + SetSpeedY(0); - if (TestBlockCollision(a_RailMeta)) return; + TestBlockCollision(a_RailMeta); + TestEntityCollision(a_RailMeta); + + // SnapToRail handles turning - if (GetSpeedZ() > 0) // Cart moving south - { - int OldX = (int)floor(GetPosX()); - AddSpeedX(-GetSpeedZ() + 0.5); // See below - AddPosX(-GetSpeedZ() * (a_Dt / 1000)); // Diagonally move southwest (which will make cart hit a southwest rail) - // If we are already at southwest rail, set Z speed to zero as we can be moving so fast, MCS doesn't tick fast enough to active the handle for the rail... - // ...and so we derail unexpectedly. - if (GetPosX() <= OldX - 1) SetSpeedZ(0); - } - else if (GetSpeedX() > 0) // Cart moving east - { - int OldZ = (int)floor(GetPosZ()); - AddSpeedZ(-GetSpeedX() + 0.5); - AddPosZ(-GetSpeedX() * (a_Dt / 1000)); // Diagonally move northeast - if (GetPosZ() <= OldZ - 1) SetSpeedX(0); - } break; } case E_META_RAIL_CURVED_ZM_XP: // Curved NORTH EAST { SetYaw(225); SetPosY(floor(GetPosY()) + 0.55); + SetSpeedY(0); - if (TestBlockCollision(a_RailMeta)) return; + TestBlockCollision(a_RailMeta); + TestEntityCollision(a_RailMeta); - if (GetSpeedZ() > 0) - { - int OldX = (int)floor(GetPosX()); - AddSpeedX(GetSpeedZ() - 0.5); - AddPosX(GetSpeedZ() * (a_Dt / 1000)); - if (GetPosX() >= OldX + 1) SetSpeedZ(0); - } - else if (GetSpeedX() < 0) - { - int OldZ = (int)floor(GetPosZ()); - AddSpeedZ(GetSpeedX() + 0.5); - AddPosZ(GetSpeedX() * (a_Dt / 1000)); - if (GetPosZ() <= OldZ - 1) SetSpeedX(0); - } break; } case E_META_RAIL_CURVED_ZP_XM: // Curved SOUTH WEST { SetYaw(135); SetPosY(floor(GetPosY()) + 0.55); + SetSpeedY(0); - if (TestBlockCollision(a_RailMeta)) return; + TestBlockCollision(a_RailMeta); + TestEntityCollision(a_RailMeta); - if (GetSpeedZ() < 0) - { - int OldX = (int)floor(GetPosX()); - AddSpeedX(GetSpeedZ() + 0.5); - AddPosX(GetSpeedZ() * (a_Dt / 1000)); - if (GetPosX() <= OldX - 1) SetSpeedZ(0); - } - else if (GetSpeedX() > 0) - { - int OldZ = (int)floor(GetPosZ()); - AddSpeedZ(GetSpeedX() - 0.5); - AddPosZ(GetSpeedX() * (a_Dt / 1000)); - if (GetPosZ() >= OldZ + 1) SetSpeedX(0); - } break; } case E_META_RAIL_CURVED_ZP_XP: // Curved SOUTH EAST { SetYaw(45); SetPosY(floor(GetPosY()) + 0.55); + SetSpeedY(0); - if (TestBlockCollision(a_RailMeta)) return; + TestBlockCollision(a_RailMeta); + TestEntityCollision(a_RailMeta); - if (GetSpeedZ() < 0) - { - int OldX = (int)floor(GetPosX()); - AddSpeedX(-GetSpeedZ() - 0.5); - AddPosX(-GetSpeedZ() * (a_Dt / 1000)); - if (GetPosX() >= OldX + 1) SetSpeedZ(0); - } - else if (GetSpeedX() < 0) - { - int OldZ = (int)floor(GetPosZ()); - AddSpeedZ(-GetSpeedX() - 0.5); - AddPosZ(-GetSpeedX() * (a_Dt / 1000)); - if (GetPosZ() >= OldZ + 1) SetSpeedX(0); - } break; } default: @@ -385,8 +416,8 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) { // Initialise to 'slow down' values - int AccelDecelSpeed = -1; - int AccelDecelNegSpeed = 1; + int AccelDecelSpeed = -2; + int AccelDecelNegSpeed = 2; if ((a_RailMeta & 0x8) == 0x8) { @@ -404,17 +435,18 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) SetSpeedY(0); SetSpeedX(0); - if (TestBlockCollision(a_RailMeta)) return; + bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) return; if (GetSpeedZ() != 0) { if (GetSpeedZ() > 0) { - AddSpeedZ(AccelDecelNegSpeed); + AddSpeedZ(AccelDecelSpeed); } else { - AddSpeedZ(AccelDecelSpeed); + AddSpeedZ(AccelDecelNegSpeed); } } break; @@ -426,7 +458,8 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) SetSpeedY(0); SetSpeedZ(0); - if (TestBlockCollision(a_RailMeta)) return; + bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) return; if (GetSpeedX() != 0) { @@ -441,6 +474,87 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) } break; } + case E_META_RAIL_ASCEND_XM: // ASCEND EAST + { + SetYaw(180); + SetSpeedZ(0); + + if (GetSpeedX() >= 0) + { + if (GetSpeedX() <= MAX_SPEED) + { + AddSpeedX(AccelDecelSpeed); + SetSpeedY(-GetSpeedX()); + } + } + else + { + AddSpeedX(AccelDecelNegSpeed); + SetSpeedY(-GetSpeedX()); + } + break; + } + case E_META_RAIL_ASCEND_XP: // ASCEND WEST + { + SetYaw(180); + SetSpeedZ(0); + + if (GetSpeedX() > 0) + { + AddSpeedX(AccelDecelSpeed); + SetSpeedY(GetSpeedX()); + } + else + { + if (GetSpeedX() >= MAX_SPEED_NEGATIVE) + { + AddSpeedX(AccelDecelNegSpeed); + SetSpeedY(GetSpeedX()); + } + } + break; + } + case E_META_RAIL_ASCEND_ZM: // ASCEND NORTH + { + SetYaw(270); + SetSpeedX(0); + + if (GetSpeedZ() >= 0) + { + if (GetSpeedZ() <= MAX_SPEED) + { + AddSpeedZ(AccelDecelSpeed); + SetSpeedY(-GetSpeedZ()); + } + } + else + { + AddSpeedZ(AccelDecelNegSpeed); + SetSpeedY(-GetSpeedZ()); + } + break; + } + case E_META_RAIL_ASCEND_ZP: // ASCEND SOUTH + { + SetYaw(270); + SetSpeedX(0); + + if (GetSpeedZ() > 0) + { + AddSpeedZ(AccelDecelSpeed); + SetSpeedY(GetSpeedZ()); + } + else + { + if (GetSpeedZ() >= MAX_SPEED_NEGATIVE) + { + AddSpeedZ(AccelDecelNegSpeed); + SetSpeedY(GetSpeedZ()); + } + } + break; + } + default: ASSERT(!"Unhandled powered rail metadata!"); break; } } @@ -450,11 +564,17 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) void cMinecart::HandleDetectorRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) { - m_bIsOnDetectorRail = true; - m_DetectorRailPosition = Vector3i((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); - m_World->SetBlockMeta(m_DetectorRailPosition, a_RailMeta | 0x08); + // No special handling + HandleRailPhysics(a_RailMeta & 0x07, a_Dt); +} + + + + +void cMinecart::HandleActivatorRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) +{ HandleRailPhysics(a_RailMeta & 0x07, a_Dt); } @@ -482,6 +602,107 @@ void cMinecart::SnapToRail(NIBBLETYPE a_RailMeta) SetPosX(floor(GetPosX()) + 0.5); break; } + // Curved rail physics: once minecart has reached more than half of the block in the direction that it is travelling in, jerk it in the direction of curvature + case E_META_RAIL_CURVED_ZM_XM: + { + if (GetPosZ() > floor(GetPosZ()) + 0.5) + { + if (GetSpeedZ() > 0) + { + SetSpeedX(-GetSpeedZ() * 0.7); + } + + SetSpeedZ(0); + SetPosZ(floor(GetPosZ()) + 0.5); + } + else if (GetPosX() > floor(GetPosX()) + 0.5) + { + if (GetSpeedX() > 0) + { + SetSpeedZ(-GetSpeedX() * 0.7); + } + + SetSpeedX(0); + SetPosX(floor(GetPosX()) + 0.5); + } + SetSpeedY(0); + break; + } + case E_META_RAIL_CURVED_ZM_XP: + { + if (GetPosZ() > floor(GetPosZ()) + 0.5) + { + if (GetSpeedZ() > 0) + { + SetSpeedX(GetSpeedZ() * 0.7); + } + + SetSpeedZ(0); + SetPosZ(floor(GetPosZ()) + 0.5); + } + else if (GetPosX() < floor(GetPosX()) + 0.5) + { + if (GetSpeedX() < 0) + { + SetSpeedZ(GetSpeedX() * 0.7); + } + + SetSpeedX(0); + SetPosX(floor(GetPosX()) + 0.5); + } + SetSpeedY(0); + break; + } + case E_META_RAIL_CURVED_ZP_XM: + { + if (GetPosZ() < floor(GetPosZ()) + 0.5) + { + if (GetSpeedZ() < 0) + { + SetSpeedX(GetSpeedZ() * 0.7); + } + + SetSpeedZ(0); + SetPosZ(floor(GetPosZ()) + 0.5); + } + else if (GetPosX() > floor(GetPosX()) + 0.5) + { + if (GetSpeedX() > 0) + { + SetSpeedZ(GetSpeedX() * 0.7); + } + + SetSpeedX(0); + SetPosX(floor(GetPosX()) + 0.5); + } + SetSpeedY(0); + break; + } + case E_META_RAIL_CURVED_ZP_XP: + { + if (GetPosZ() < floor(GetPosZ()) + 0.5) + { + if (GetSpeedZ() < 0) + { + SetSpeedX(-GetSpeedZ() * 0.7); + } + + SetSpeedZ(0); + SetPosZ(floor(GetPosZ()) + 0.5); + } + else if (GetPosX() < floor(GetPosX()) + 0.5) + { + if (GetSpeedX() < 0) + { + SetSpeedZ(-GetSpeedX() * 0.7); + } + + SetSpeedX(0); + SetPosX(floor(GetPosX()) + 0.5); + } + SetSpeedY(0); + break; + } default: break; } } @@ -499,7 +720,7 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) if (GetSpeedZ() > 0) { BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)ceil(GetPosZ())); - if (!IsBlockRail(Block) && g_BlockIsSolid[Block]) + if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block)) { // We could try to detect a block in front based purely on coordinates, but xoft made a bounding box system - why not use? :P cBoundingBox bbBlock(Vector3d((int)floor(GetPosX()), (int)floor(GetPosY()), (int)ceil(GetPosZ())), 0.5, 1); @@ -513,10 +734,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) } } } - else + else if (GetSpeedZ() < 0) { BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) - 1); - if (!IsBlockRail(Block) && g_BlockIsSolid[Block]) + if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block)) { cBoundingBox bbBlock(Vector3d((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) - 1), 0.5, 1); cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ() - 1), GetWidth() / 2, GetHeight()); @@ -536,7 +757,7 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) if (GetSpeedX() > 0) { BLOCKTYPE Block = m_World->GetBlock((int)ceil(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); - if (!IsBlockRail(Block) && g_BlockIsSolid[Block]) + if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block)) { cBoundingBox bbBlock(Vector3d((int)ceil(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())), 0.5, 1); cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight()); @@ -549,10 +770,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) } } } - else + else if (GetSpeedX() < 0) { BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()) - 1, (int)floor(GetPosY()), (int)floor(GetPosZ())); - if (!IsBlockRail(Block) && g_BlockIsSolid[Block]) + if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block)) { cBoundingBox bbBlock(Vector3d((int)floor(GetPosX()) - 1, (int)floor(GetPosY()), (int)floor(GetPosZ())), 0.5, 1); cBoundingBox bbMinecart(Vector3d(GetPosX() - 1, floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight()); @@ -577,10 +798,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) BLOCKTYPE BlockZM = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) + 1); BLOCKTYPE BlockZP = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) + 1); if ( - (!IsBlockRail(BlockXM) && g_BlockIsSolid[BlockXM]) || - (!IsBlockRail(BlockXP) && g_BlockIsSolid[BlockXP]) || - (!IsBlockRail(BlockZM) && g_BlockIsSolid[BlockZM]) || - (!IsBlockRail(BlockZP) && g_BlockIsSolid[BlockZP]) + (!IsBlockRail(BlockXM) && cBlockInfo::IsSolid(BlockXM)) || + (!IsBlockRail(BlockXP) && cBlockInfo::IsSolid(BlockXP)) || + (!IsBlockRail(BlockZM) && cBlockInfo::IsSolid(BlockZM)) || + (!IsBlockRail(BlockZP) && cBlockInfo::IsSolid(BlockZP)) ) { SetSpeed(0, 0, 0); @@ -597,10 +818,93 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) +bool cMinecart::TestEntityCollision(NIBBLETYPE a_RailMeta) +{ + cMinecartCollisionCallback MinecartCollisionCallback(GetPosition(), GetHeight(), GetWidth(), GetUniqueID(), ((m_Attachee == NULL) ? -1 : m_Attachee->GetUniqueID())); + int ChunkX, ChunkZ; + cChunkDef::BlockToChunk((int)floor(GetPosX()), (int)floor(GetPosZ()), ChunkX, ChunkZ); + m_World->ForEachEntityInChunk(ChunkX, ChunkZ, MinecartCollisionCallback); + + if (!MinecartCollisionCallback.FoundIntersection()) + { + return false; + } + + switch (a_RailMeta) + { + case E_META_RAIL_ZM_ZP: + { + if (MinecartCollisionCallback.GetCollidedEntityPosition().z >= GetPosZ()) + { + if ((-GetSpeedZ() * 0.4) < 0.01) + { + AddSpeedZ(-4); + } + else + { + SetSpeedZ(-GetSpeedZ() * 0.4); + } + } + else + { + if ((GetSpeedZ() * 0.4) < 0.01) + { + AddSpeedZ(4); + } + else + { + SetSpeedZ(GetSpeedZ() * 0.4); + } + } + return true; + } + case E_META_RAIL_XM_XP: + { + if (MinecartCollisionCallback.GetCollidedEntityPosition().x >= GetPosX()) + { + if ((-GetSpeedX() * 0.4) < 0.01) + { + AddSpeedX(-4); + } + else + { + SetSpeedX(-GetSpeedX() * 0.4); + } + } + else + { + if ((GetSpeedX() * 0.4) < 0.01) + { + AddSpeedX(4); + } + else + { + SetSpeedX(GetSpeedX() * 0.4); + } + } + return true; + } + case E_META_RAIL_CURVED_ZM_XM: + case E_META_RAIL_CURVED_ZM_XP: + case E_META_RAIL_CURVED_ZP_XM: + case E_META_RAIL_CURVED_ZP_XP: + { + // TODO - simply can't be bothered right now + break; + } + default: break; + } + + return false; +} + + + + void cMinecart::DoTakeDamage(TakeDamageInfo & TDI) { - if (TDI.Attacker->IsPlayer() && ((cPlayer *)TDI.Attacker)->IsGameModeCreative()) + if ((TDI.Attacker != NULL) && TDI.Attacker->IsPlayer() && ((cPlayer *)TDI.Attacker)->IsGameModeCreative()) { Destroy(); TDI.FinalDamage = GetMaxHealth(); // Instant hit for creative @@ -727,9 +1031,9 @@ cMinecartWithChest::cMinecartWithChest(double a_X, double a_Y, double a_Z) : -void cMinecartWithChest::SetSlot(int a_Idx, const cItem & a_Item) +void cMinecartWithChest::SetSlot(size_t a_Idx, const cItem & a_Item) { - ASSERT((a_Idx >= 0) && (a_Idx < ARRAYCOUNT(m_Items))); + ASSERT(a_Idx < ARRAYCOUNT(m_Items)); m_Items[a_Idx] = a_Item; } @@ -753,8 +1057,8 @@ void cMinecartWithChest::OnRightClicked(cPlayer & a_Player) cMinecartWithFurnace::cMinecartWithFurnace(double a_X, double a_Y, double a_Z) : super(mpFurnace, a_X, a_Y, a_Z), - m_IsFueled(false), - m_FueledTimeLeft(-1) + m_FueledTimeLeft(-1), + m_IsFueled(false) { } @@ -833,4 +1137,4 @@ cMinecartWithHopper::cMinecartWithHopper(double a_X, double a_Y, double 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 +// AND AVARYTHING!! diff --git a/src/Entities/Minecart.h b/src/Entities/Minecart.h index 874d0204e..ebdb576e0 100644 --- a/src/Entities/Minecart.h +++ b/src/Entities/Minecart.h @@ -15,20 +15,6 @@ -inline bool IsBlockRail(BLOCKTYPE a_BlockType) - { - return ( - (a_BlockType == E_BLOCK_RAIL) || - (a_BlockType == E_BLOCK_ACTIVATOR_RAIL) || - (a_BlockType == E_BLOCK_DETECTOR_RAIL) || - (a_BlockType == E_BLOCK_POWERED_RAIL) - ) ; - } - - - - - class cMinecart : public cEntity { @@ -79,10 +65,16 @@ protected: */ void HandleDetectorRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt); - /** Snaps a minecart to a rail's axis, resetting its speed */ + /** Handles activator rails - placeholder for future implementation */ + void HandleActivatorRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt); + + /** Snaps a mincecart to a rail's axis, resetting its speed + For curved rails, it changes the cart's direction as well as snapping it to axis */ void SnapToRail(NIBBLETYPE a_RailMeta); - /** Tests is a solid block is in front of a cart, and stops the cart (and returns true) if so; returns false if no obstruction*/ + /** Tests if a solid block is in front of a cart, and stops the cart (and returns true) if so; returns false if no obstruction */ bool TestBlockCollision(NIBBLETYPE a_RailMeta); + /** Tests if this mincecart's bounding box is intersecting another entity's bounding box (collision) and pushes mincecart away */ + bool TestEntityCollision(NIBBLETYPE a_RailMeta); } ; @@ -130,7 +122,7 @@ public: 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); + void SetSlot(size_t a_Idx, const cItem & a_Item); protected: @@ -201,4 +193,4 @@ public: CLASS_PROTODEF(cMinecartWithHopper); cMinecartWithHopper(double a_X, double a_Y, double a_Z); -} ;
\ No newline at end of file +} ; diff --git a/src/Entities/Painting.cpp b/src/Entities/Painting.cpp new file mode 100644 index 000000000..e217556c7 --- /dev/null +++ b/src/Entities/Painting.cpp @@ -0,0 +1,54 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "Painting.h" +#include "ClientHandle.h" +#include "Player.h" +#include "../Chunk.h" + + + + + +cPainting::cPainting(const AString & a_Name, int a_Direction, double a_X, double a_Y, double a_Z) + : cEntity(etPainting, a_X, a_Y, a_Z, 1, 1), + m_Name(a_Name), + m_Direction(a_Direction) +{ +} + + + + + + +void cPainting::SpawnOn(cClientHandle & a_Client) +{ + a_Client.SendPaintingSpawn(*this); +} + + + + + +void cPainting::Tick(float a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); +} + + + + + +void cPainting::GetDrops(cItems & a_Items, cEntity * a_Killer) +{ + if ((a_Killer != NULL) && a_Killer->IsPlayer() && !((cPlayer *)a_Killer)->IsGameModeCreative()) + { + a_Items.push_back(cItem(E_ITEM_PAINTING)); + } +} + + + + diff --git a/src/Entities/Painting.h b/src/Entities/Painting.h new file mode 100644 index 000000000..c1024bd1b --- /dev/null +++ b/src/Entities/Painting.h @@ -0,0 +1,42 @@ + +#pragma once + +#include "Entity.h" + + + + + +// tolua_begin +class cPainting : + public cEntity +{ + // tolua_end + typedef cEntity super; + +public: + CLASS_PROTODEF(cPainting); + + cPainting(const AString & a_Name, int a_Direction, double a_X, double a_Y, double a_Z); + const AString & GetName(void) const { return m_Name; } // tolua_export + int GetDirection(void) const { return m_Direction; } // tolua_export + +private: + + virtual void SpawnOn(cClientHandle & a_Client) override; + virtual void Tick(float a_Dt, cChunk & a_Chunk) override; + virtual void GetDrops(cItems & a_Items, cEntity * a_Killer) override; + virtual void KilledBy(cEntity * a_Killer) override + { + super::KilledBy(a_Killer); + Destroy(); + } + + AString m_Name; + int m_Direction; + +}; // tolua_export + + + + diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 001e386a7..7fc89b62b 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -6,19 +6,58 @@ #endif #include "Pickup.h" +#include "Player.h" #include "../ClientHandle.h" -#include "../Inventory.h" #include "../World.h" -#include "../Simulator/FluidSimulator.h" #include "../Server.h" -#include "Player.h" #include "../Bindings/PluginManager.h" -#include "../Item.h" #include "../Root.h" #include "../Chunk.h" -#include "../Vector3d.h" -#include "../Vector3f.h" + + + +class cPickupCombiningCallback : + public cEntityCallback +{ +public: + cPickupCombiningCallback(Vector3d a_Position, cPickup * a_Pickup) : + m_FoundMatchingPickup(false), + m_Position(a_Position), + m_Pickup(a_Pickup) + { + } + + virtual bool Item(cEntity * a_Entity) override + { + if (!a_Entity->IsPickup() || (a_Entity->GetUniqueID() == m_Pickup->GetUniqueID()) || a_Entity->IsDestroyed()) + { + return false; + } + + Vector3d EntityPos = a_Entity->GetPosition(); + double Distance = (EntityPos - m_Position).Length(); + + if ((Distance < 1.2) && ((cPickup *)a_Entity)->GetItem().IsEqual(m_Pickup->GetItem())) + { + m_Pickup->GetItem().AddCount(((cPickup *)a_Entity)->GetItem().m_ItemCount); + a_Entity->Destroy(); + m_FoundMatchingPickup = true; + } + return false; + } + + inline bool FoundMatchingPickup() + { + return m_FoundMatchingPickup; + } + +protected: + bool m_FoundMatchingPickup; + + Vector3d m_Position; + cPickup * m_Pickup; +}; @@ -26,10 +65,10 @@ cPickup::cPickup(double a_PosX, double a_PosY, double a_PosZ, const cItem & a_Item, bool IsPlayerCreated, float a_SpeedX /* = 0.f */, float a_SpeedY /* = 0.f */, float a_SpeedZ /* = 0.f */) : cEntity(etPickup, a_PosX, a_PosY, a_PosZ, 0.2, 0.2) - , m_Timer( 0.f ) + , m_Timer(0.f) , m_Item(a_Item) - , m_bCollected( false ) - , m_bIsPlayerCreated( IsPlayerCreated ) + , m_bCollected(false) + , m_bIsPlayerCreated(IsPlayerCreated) { SetGravity(-10.5f); SetMaxHealth(5); @@ -43,7 +82,7 @@ cPickup::cPickup(double a_PosX, double a_PosY, double a_PosZ, const cItem & a_It void cPickup::SpawnOn(cClientHandle & a_Client) { - a_Client.SendPickupSpawn(*this); + a_Client.SendPickupSpawn(*this); } @@ -89,6 +128,16 @@ void cPickup::Tick(float a_Dt, cChunk & a_Chunk) return; } } + + if (!IsDestroyed()) // Don't try to combine if someone has tried to combine me + { + cPickupCombiningCallback PickupCombiningCallback(GetPosition(), this); + m_World->ForEachEntity(PickupCombiningCallback); // Not ForEachEntityInChunk, otherwise pickups don't combine across chunk boundaries + if (PickupCombiningCallback.FoundMatchingPickup()) + { + m_World->BroadcastEntityMetadata(*this); + } + } } } } diff --git a/src/Entities/Pickup.h b/src/Entities/Pickup.h index d39eda298..74b917bce 100644 --- a/src/Entities/Pickup.h +++ b/src/Entities/Pickup.h @@ -18,38 +18,42 @@ class cPlayer; class cPickup : public cEntity { - // tolua_end typedef cEntity super; public: + // tolua_end + CLASS_PROTODEF(cPickup); - cPickup(double a_PosX, double a_PosY, double a_PosZ, const cItem & a_Item, bool IsPlayerCreated, float a_SpeedX = 0.f, float a_SpeedY = 0.f, float a_SpeedZ = 0.f); // tolua_export - - cItem & GetItem(void) {return m_Item; } // tolua_export + cPickup(double a_PosX, double a_PosY, double a_PosZ, const cItem & a_Item, bool IsPlayerCreated, float a_SpeedX = 0.f, float a_SpeedY = 0.f, float a_SpeedZ = 0.f); + + cItem & GetItem(void) {return m_Item; } // tolua_export const cItem & GetItem(void) const {return m_Item; } virtual void SpawnOn(cClientHandle & a_ClientHandle) override; - + bool CollectedBy(cPlayer * a_Dest); // tolua_export virtual void Tick(float a_Dt, cChunk & a_Chunk) override; - - /// Returns the number of ticks that this entity has existed - int GetAge(void) const { return (int)(m_Timer / 50); } // tolua_export - - /// Returns true if the pickup has already been collected + + /** Returns the number of ticks that this entity has existed */ + int GetAge(void) const { return (int)(m_Timer / 50); } // tolua_export + + /** Set the number of ticks that this entity has existed */ + void SetAge(int a_Age) { m_Timer = (float)(a_Age * 50); } // tolua_export + + /** Returns true if the pickup has already been collected */ bool IsCollected(void) const { return m_bCollected; } // tolua_export - /// Returns true if created by player (i.e. vomiting), used for determining picking-up delay time + /** Returns true if created by player (i.e. vomiting), used for determining picking-up delay time */ bool IsPlayerCreated(void) const { return m_bIsPlayerCreated; } // tolua_export - + private: Vector3d m_ResultingSpeed; //Can be used to modify the resulting speed for the current tick ;) Vector3d m_WaterSpeed; - /// The number of ticks that the entity has existed / timer between collect and destroy; in msec + /** The number of ticks that the entity has existed / timer between collect and destroy; in msec */ float m_Timer; cItem m_Item; diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index c1f2456eb..7f2e5b4c2 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -3,32 +3,22 @@ #include "Player.h" #include "../Server.h" -#include "../ClientHandle.h" #include "../UI/Window.h" #include "../UI/WindowOwner.h" #include "../World.h" -#include "Pickup.h" #include "../Bindings/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/Timer.h" -#include "../MersenneTwister.h" #include "../Chunk.h" #include "../Items/ItemHandler.h" - -#include "../Vector3d.h" -#include "../Vector3f.h" +#include "../Vector3.h" #include "inifile/iniFile.h" #include "json/json.h" -#define float2int(x) ((x)<0 ? ((int)(x))-1 : (int)(x)) - @@ -36,8 +26,6 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) : super(etPlayer, 0.6, 1.8) - , m_AirLevel( MAX_AIR_LEVEL ) - , m_AirTickTimer(DROWNING_TICKS) , m_bVisible(true) , m_FoodLevel(MAX_FOOD_LEVEL) , m_FoodSaturationLevel(5) @@ -51,15 +39,13 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) , m_Inventory(*this) , m_CurrentWindow(NULL) , m_InventoryWindow(NULL) - , m_TimeLastPickupCheck(0.f) , m_Color('-') - , m_LastBlockActionTime(0) - , m_LastBlockActionCnt(0) , m_GameMode(eGameMode_NotSet) , m_IP("") , m_ClientHandle(a_Client) - , m_NormalMaxSpeed(0.1) - , m_SprintingMaxSpeed(0.13) + , m_NormalMaxSpeed(1.0) + , m_SprintingMaxSpeed(1.3) + , m_FlyingMaxSpeed(1.0) , m_IsCrouched(false) , m_IsSprinting(false) , m_IsFlying(false) @@ -74,6 +60,7 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) , m_IsChargingBow(false) , m_BowCharge(0) , m_FloaterID(-1) + , m_Team(NULL) { LOGD("Created a player object for \"%s\" @ \"%s\" at %p, ID %d", a_PlayerName.c_str(), a_Client->GetIPString().c_str(), @@ -91,7 +78,6 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) 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 @@ -107,9 +93,23 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ() ); } + m_LastJumpHeight = (float)(GetPosY()); m_LastGroundHeight = (float)(GetPosY()); m_Stance = GetPosY() + 1.62; + + if (m_GameMode == gmNotSet) + { + cWorld * World = cRoot::Get()->GetWorld(GetLoadedWorldName()); + if (World == NULL) + { + World = cRoot::Get()->GetDefaultWorld(); + } + if (World->IsGameModeCreative()) + { + m_CanFly = true; + } + } cRoot::Get()->GetServer()->PlayerCreated(this); } @@ -120,7 +120,13 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) cPlayer::~cPlayer(void) { - LOGD("Deleting cPlayer \"%s\" at %p, ID %d", m_PlayerName.c_str(), this, GetUniqueID()); + if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this)) + { + cRoot::Get()->BroadcastChatLeave(Printf("%s has left the game", GetName().c_str())); + LOGINFO("Player %s has left the game.", GetName().c_str()); + } + + LOGD("Deleting cPlayer \"%s\" at %p, ID %d", GetName().c_str(), this, GetUniqueID()); // Notify the server that the player is being destroyed cRoot::Get()->GetServer()->PlayerDestroying(this); @@ -196,12 +202,6 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) super::Tick(a_Dt, a_Chunk); - // Set player swimming state - SetSwimState(a_Chunk); - - // Handle air drowning stuff - HandleAir(); - // Handle charging the bow: if (m_IsChargingBow) { @@ -245,6 +245,9 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) HandleFloater(); } + // Update items (e.g. Maps) + m_Inventory.UpdateItems(); + // Send Player List (Once per m_LastPlayerListTime/1000 ms) cTimer t1; if (m_LastPlayerListTime + cPlayer::PLAYER_LIST_TIME_MS <= t1.GetNowTime()) @@ -434,7 +437,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) cWorld * World = GetWorld(); if ((GetPosY() >= 0) && (GetPosY() < cChunkDef::Height)) { - BLOCKTYPE BlockType = World->GetBlock(float2int(GetPosX()), float2int(GetPosY()), float2int(GetPosZ())); + BLOCKTYPE BlockType = World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); if (BlockType != E_BLOCK_AIR) { m_bTouchGround = true; @@ -459,12 +462,10 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) if (Damage > 0) { - if (!IsGameModeCreative()) - { - TakeDamage(dtFalling, NULL, Damage, Damage, 0); - } + // cPlayer makes sure damage isn't applied in creative, no need to check here + TakeDamage(dtFalling, NULL, Damage, Damage, 0); - // Mojang uses floor() to get X and Z positions, instead of just casting it to an (int) + // Fall particles GetWorld()->BroadcastSoundParticleEffect(2006, (int)floor(GetPosX()), (int)GetPosY() - 1, (int)floor(GetPosZ()), Damage /* Used as particle effect speed modifier */); } @@ -684,7 +685,21 @@ const cSlotNums & cPlayer::GetInventoryPaintSlots(void) const double cPlayer::GetMaxSpeed(void) const { - return m_IsSprinting ? m_SprintingMaxSpeed : m_NormalMaxSpeed; + if (m_IsFlying) + { + return m_FlyingMaxSpeed; + } + else + { + if (m_IsSprinting) + { + return m_SprintingMaxSpeed; + } + else + { + return m_NormalMaxSpeed; + } + } } @@ -694,7 +709,7 @@ double cPlayer::GetMaxSpeed(void) const void cPlayer::SetNormalMaxSpeed(double a_Speed) { m_NormalMaxSpeed = a_Speed; - if (!m_IsSprinting) + if (!m_IsSprinting && !m_IsFlying) { m_ClientHandle->SendPlayerMaxSpeed(); } @@ -707,7 +722,7 @@ void cPlayer::SetNormalMaxSpeed(double a_Speed) void cPlayer::SetSprintingMaxSpeed(double a_Speed) { m_SprintingMaxSpeed = a_Speed; - if (m_IsSprinting) + if (m_IsSprinting && !m_IsFlying) { m_ClientHandle->SendPlayerMaxSpeed(); } @@ -717,6 +732,18 @@ void cPlayer::SetSprintingMaxSpeed(double a_Speed) +void cPlayer::SetFlyingMaxSpeed(double a_Speed) +{ + m_FlyingMaxSpeed = a_Speed; + + // Update the flying speed, always: + m_ClientHandle->SendPlayerAbilities(); +} + + + + + void cPlayer::SetCrouch(bool a_IsCrouched) { // Set the crouch status, broadcast to all visible players @@ -782,14 +809,28 @@ void cPlayer::SetFlying(bool a_IsFlying) void cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) { - if (a_TDI.DamageType != dtInVoid) + if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtPlugin)) { if (IsGameModeCreative()) { - // No damage / health in creative mode + // No damage / health in creative mode if not void or plugin damage return; } } + + if ((a_TDI.Attacker != NULL) && (a_TDI.Attacker->IsPlayer())) + { + cPlayer* Attacker = (cPlayer*) a_TDI.Attacker; + + if ((m_Team != NULL) && (m_Team == Attacker->m_Team)) + { + if (!m_Team->AllowsFriendlyFire()) + { + // Friendly fire is disabled + return; + } + } + } super::DoTakeDamage(a_TDI); @@ -818,24 +859,34 @@ void cPlayer::KilledBy(cEntity * a_Killer) cItems Pickups; m_Inventory.CopyToItems(Pickups); m_Inventory.Clear(); + + if (GetName() == "Notch") + { + Pickups.Add(cItem(E_ITEM_RED_APPLE)); + } + m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10); SaveToDisk(); // Save it, yeah the world is a tough place ! if (a_Killer == NULL) { - GetWorld()->BroadcastChat(Printf("%s[DEATH] %s%s was killed by environmental damage", cChatColor::Red.c_str(), cChatColor::White.c_str(), GetName().c_str())); + GetWorld()->BroadcastChatDeath(Printf("%s was killed by environmental damage", GetName().c_str())); } else if (a_Killer->IsPlayer()) { - GetWorld()->BroadcastChat(Printf("%s[DEATH] %s%s was killed by %s", cChatColor::Red.c_str(), cChatColor::White.c_str(), GetName().c_str(), ((cPlayer *)a_Killer)->GetName().c_str())); + GetWorld()->BroadcastChatDeath(Printf("%s was killed by %s", GetName().c_str(), ((cPlayer *)a_Killer)->GetName().c_str())); + + m_World->GetScoreBoard().AddPlayerScore(((cPlayer *)a_Killer)->GetName(), cObjective::otPlayerKillCount, 1); } else { AString KillerClass = a_Killer->GetClass(); KillerClass.erase(KillerClass.begin()); // Erase the 'c' of the class (e.g. "cWitch" -> "Witch") - GetWorld()->BroadcastChat(Printf("%s[DEATH] %s%s was killed by a %s", cChatColor::Red.c_str(), cChatColor::White.c_str(), GetName().c_str(), KillerClass.c_str())); + GetWorld()->BroadcastChatDeath(Printf("%s was killed by a %s", GetName().c_str(), KillerClass.c_str())); } + + m_World->GetScoreBoard().AddPlayerScore(GetName(), cObjective::otDeathCount, 1); } @@ -916,6 +967,50 @@ bool cPlayer::IsGameModeAdventure(void) const +void cPlayer::SetTeam(cTeam * a_Team) +{ + if (m_Team == a_Team) + { + return; + } + + if (m_Team) + { + m_Team->RemovePlayer(GetName()); + } + + m_Team = a_Team; + + if (m_Team) + { + m_Team->AddPlayer(GetName()); + } +} + + + + + +cTeam * cPlayer::UpdateTeam(void) +{ + if (m_World == NULL) + { + SetTeam(NULL); + } + else + { + cScoreboard & Scoreboard = m_World->GetScoreBoard(); + + SetTeam(Scoreboard.QueryPlayerTeam(GetName())); + } + + return m_Team; +} + + + + + void cPlayer::OpenWindow(cWindow * a_Window) { if (a_Window != m_CurrentWindow) @@ -969,27 +1064,6 @@ void cPlayer::CloseWindowIfID(char a_WindowID, bool a_CanRefuse) -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)) @@ -1036,21 +1110,24 @@ void cPlayer::SetIP(const AString & a_IP) -void cPlayer::SendMessage(const AString & a_Message) +void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) { - m_ClientHandle->SendChat(a_Message); + SetPosition(a_PosX, a_PosY, a_PosZ); + m_LastGroundHeight = (float)a_PosY; + m_LastJumpHeight = (float)a_PosY; + + m_World->BroadcastTeleportEntity(*this, GetClientHandle()); + m_ClientHandle->SendPlayerMoveLook(); } -void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) +void cPlayer::SendRotation(double a_YawDegrees, double a_PitchDegrees) { - SetPosition( a_PosX, a_PosY, a_PosZ ); - m_LastGroundHeight = (float)a_PosY; - - m_World->BroadcastTeleportEntity(*this, GetClientHandle()); + SetYaw(a_YawDegrees); + SetPitch(a_PitchDegrees); m_ClientHandle->SendPlayerMoveLook(); } @@ -1186,19 +1263,6 @@ void cPlayer::RemoveFromGroup( const AString & a_GroupName ) -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()) @@ -1342,59 +1406,68 @@ AString cPlayer::GetColor(void) const -void cPlayer::TossItem( - bool a_bDraggingItem, - char a_Amount /* = 1 */, - short a_CreateType /* = 0 */, - short a_CreateHealth /* = 0 */ -) +void cPlayer::TossEquippedItem(char a_Amount) { cItems Drops; - if (a_CreateType != 0) + cItem DroppedItem(GetInventory().GetEquippedItem()); + if (!DroppedItem.IsEmpty()) { - // Just create item without touching the inventory (used in creative mode) - Drops.push_back(cItem(a_CreateType, a_Amount, a_CreateHealth)); + char NewAmount = a_Amount; + if (NewAmount > GetInventory().GetEquippedItem().m_ItemCount) + { + NewAmount = GetInventory().GetEquippedItem().m_ItemCount; // Drop only what's there + } + + GetInventory().GetHotbarGrid().ChangeSlotCount(GetInventory().GetEquippedSlotNum() /* Returns hotbar subslot, which HotbarGrid takes */, -a_Amount); + + DroppedItem.m_ItemCount = NewAmount; + Drops.push_back(DroppedItem); } - else + + double vX = 0, vY = 0, vZ = 0; + EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); + vY = -vY * 2 + 1.f; + m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player +} + + + + + +void cPlayer::TossHeldItem(char a_Amount) +{ + cItems Drops; + cItem & Item = GetDraggingItem(); + if (!Item.IsEmpty()) { - // Drop an item from the inventory: - if (a_bDraggingItem) + char OriginalItemAmount = Item.m_ItemCount; + Item.m_ItemCount = std::min(OriginalItemAmount, a_Amount); + Drops.push_back(Item); + if (OriginalItemAmount > a_Amount) { - 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(); - } - } + Item.m_ItemCount = OriginalItemAmount - a_Amount; } else { - // Else drop equipped item - cItem DroppedItem(GetInventory().GetEquippedItem()); - if (!DroppedItem.IsEmpty()) - { - char NewAmount = a_Amount; - if (NewAmount > GetInventory().GetEquippedItem().m_ItemCount) - { - NewAmount = GetInventory().GetEquippedItem().m_ItemCount; // Drop only what's there - } - - GetInventory().GetHotbarGrid().ChangeSlotCount(GetInventory().GetEquippedSlotNum() /* Returns hotbar subslot, which HotbarGrid takes */, -a_Amount); - - DroppedItem.m_ItemCount = NewAmount; - Drops.push_back(DroppedItem); - } + Item.Empty(); } } + + double vX = 0, vY = 0, vZ = 0; + EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); + vY = -vY * 2 + 1.f; + m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player +} + + + + + +void cPlayer::TossPickup(const cItem & a_Item) +{ + cItems Drops; + Drops.push_back(a_Item); + double vX = 0, vY = 0, vZ = 0; EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); vY = -vY * 2 + 1.f; @@ -1427,6 +1500,7 @@ bool cPlayer::MoveToWorld(const char * a_WorldName) // Add player to all the necessary parts of the new world SetWorld(World); + m_ClientHandle->StreamChunks(); World->AddEntity(this); World->AddPlayer(this); @@ -1448,10 +1522,14 @@ void cPlayer::LoadPermissionsFromDisk() std::string Groups = IniFile.GetValue(m_PlayerName, "Groups", ""); if (!Groups.empty()) { - AStringVector Split = StringSplit( Groups, "," ); - for( unsigned int i = 0; i < Split.size(); i++ ) + AStringVector Split = StringSplitAndTrim(Groups, ","); + for (AStringVector::const_iterator itr = Split.begin(), end = Split.end(); itr != end; ++itr) { - AddToGroup( Split[i].c_str() ); + if (!cRoot::Get()->GetGroupManager()->ExistsGroup(*itr)) + { + LOGWARNING("The group %s for player %s was not found!", itr->c_str(), m_PlayerName.c_str()); + } + AddToGroup(*itr); } } else @@ -1459,16 +1537,15 @@ void cPlayer::LoadPermissionsFromDisk() AddToGroup("Default"); } - m_Color = IniFile.GetValue(m_PlayerName, "Color", "-")[0]; + AString Color = IniFile.GetValue(m_PlayerName, "Color", "-"); + if (!Color.empty()) + { + m_Color = Color[0]; + } } else { - LOGWARN("Regenerating users.ini, player %s will be added to the \"Default\" group", m_PlayerName.c_str()); - IniFile.AddHeaderComment(" This is the file in which the group the player belongs to is stored"); - IniFile.AddHeaderComment(" The format is: [PlayerName] | Groups=GroupName"); - - IniFile.SetValue(m_PlayerName, "Groups", "Default"); - IniFile.WriteFile("users.ini"); + cGroupManager::GenerateDefaultUsersIni(IniFile); AddToGroup("Default"); } ResolvePermissions(); @@ -1543,27 +1620,12 @@ bool cPlayer::LoadFromDisk() m_CurrentXp = (short) root.get("xpCurrent", 0).asInt(); m_IsFlying = root.get("isflying", 0).asBool(); - //SetExperience(root.get("experience", 0).asInt()); - m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt(); if (m_GameMode == eGameMode_Creative) { m_CanFly = true; } - else if (m_GameMode == eGameMode_NotSet) - { - cWorld * World = cRoot::Get()->GetWorld(GetLoadedWorldName()); - if (World == NULL) - { - World = cRoot::Get()->GetDefaultWorld(); - } - - if (World->GetGameMode() == eGameMode_Creative) - { - m_CanFly = true; - } - } m_Inventory.LoadFromJson(root["inventory"]); @@ -1679,79 +1741,17 @@ void cPlayer::UseEquippedItem(void) - -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 - if (!a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn)) - { - // This sometimes happens on Linux machines - // Ref.: http://forum.mc-server.org/showthread.php?tid=1244 - LOGD("SetSwimState failure: RelX = %d, RelZ = %d, LastPos = {%.02f, %.02f}, Pos = %.02f, %.02f}", - RelX, RelY, m_LastPosX, m_LastPosZ, GetPosX(), GetPosZ() - ); - m_IsSwimming = false; - m_IsSubmerged = false; - return; - } - 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) +void cPlayer::TickBurning(cChunk & a_Chunk) { - // 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()) + // Don't burn in creative and stop burning in creative if necessary + if (!IsGameModeCreative()) { - // 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; - } + super::TickBurning(a_Chunk); } - else + else if (IsOnFire()) { - // set the air back to maximum - m_AirLevel = MAX_AIR_LEVEL; - m_AirTickTimer = DROWNING_TICKS; + m_TicksLeftBurning = 0; + OnFinishedBurning(); } } @@ -1763,6 +1763,12 @@ void cPlayer::HandleFood(void) { // Ref.: http://www.minecraftwiki.net/wiki/Hunger + if (IsGameModeCreative()) + { + // Hunger is disabled for Creative + return; + } + // Remember the food level before processing, for later comparison int LastFoodLevel = m_FoodLevel; @@ -1780,7 +1786,7 @@ void cPlayer::HandleFood(void) Heal(1); m_FoodExhaustionLevel += 3; } - else if (m_FoodLevel <= 0) + else if ((m_FoodLevel <= 0) && (m_Health > 1)) { // Damage from starving TakeDamage(dtStarving, NULL, 1, 1, 0); @@ -1906,7 +1912,7 @@ void cPlayer::Detach() { for (int z = PosZ - 2; z <= (PosZ + 2); ++z) { - if (!g_BlockIsSolid[m_World->GetBlock(x, y, z)] && g_BlockIsSolid[m_World->GetBlock(x, y - 1, z)]) + if (!cBlockInfo::IsSolid(m_World->GetBlock(x, y, z)) && cBlockInfo::IsSolid(m_World->GetBlock(x, y - 1, z))) { TeleportToCoords(x, y, z); return; diff --git a/src/Entities/Player.h b/src/Entities/Player.h index bf3ca08e8..05377a117 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -5,6 +5,7 @@ #include "../Inventory.h" #include "../Defines.h" #include "../World.h" +#include "../ClientHandle.h" @@ -13,6 +14,7 @@ class cGroup; class cWindow; class cClientHandle; +class cTeam; @@ -30,8 +32,6 @@ public: 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 @@ -47,19 +47,19 @@ public: virtual void HandlePhysics(float a_Dt, cChunk &) override { UNUSED(a_Dt); }; - /// Returns the curently equipped weapon; empty item if none + /** 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 + /** Returns the currently equipped helmet; empty item if none */ virtual cItem GetEquippedHelmet(void) const override { return m_Inventory.GetEquippedHelmet(); } - /// Returns the currently equipped chestplate; empty item if none + /** 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 + /** 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 + /** Returns the currently equipped boots; empty item if none */ virtual cItem GetEquippedBoots(void) const override { return m_Inventory.GetEquippedBoots(); } @@ -77,36 +77,41 @@ public: */ short DeltaExperience(short a_Xp_delta); - /// Gets the experience total - XpTotal for score on death + /** Gets the experience total - XpTotal for score on death */ inline short GetXpLifetimeTotal(void) { return m_LifetimeTotalXp; } - /// Gets the currrent experience + /** Gets the currrent experience */ inline short GetCurrentXp(void) { return m_CurrentXp; } - /// Gets the current level - XpLevel + /** Gets the current level - XpLevel */ short GetXpLevel(void); - /// Gets the experience bar percentage - XpP + /** Gets the experience bar percentage - XpP */ float GetXpPercentage(void); - /// Caculates the amount of XP needed for a given level, ref: http://minecraft.gamepedia.com/XP + /** Caculates the amount of XP needed for a given level + Ref: http://minecraft.gamepedia.com/XP + */ static short XpForLevel(short int a_Level); - /// inverse of XpForLevel, ref: http://minecraft.gamepedia.com/XP values are as per this with pre-calculations + /** Inverse of XpForLevel + Ref: http://minecraft.gamepedia.com/XP + values are as per this with pre-calculations + */ static short CalcLevelFromXp(short int a_CurrentXp); // tolua_end - /// Starts charging the equipped bow + /** Starts charging the equipped bow */ void StartChargingBow(void); - /// Finishes charging the current bow. Returns the number of ticks for which the bow has been charged + /** Finishes charging the current bow. Returns the number of ticks for which the bow has been charged */ int FinishChargingBow(void); - /// Cancels the current bow charging + /** Cancels the current bow charging */ void CancelChargingBow(void); - /// Returns true if the player is currently charging the bow + /** Returns true if the player is currently charging the bow */ bool IsChargingBow(void) const { return m_IsChargingBow; } void SetTouchGround( bool a_bTouchGround ); @@ -124,16 +129,22 @@ public: // tolua_begin - /// Returns the position where projectiles thrown by this player should start, player eye position + adjustment + /** Sends the "look" packet to the player, forcing them to set their rotation to the specified values. + a_YawDegrees is clipped to range [-180, +180), + a_PitchDegrees is clipped to range [-180, +180) but the client only uses [-90, +90] + */ + void SendRotation(double a_YawDegrees, double a_PitchDegrees); + + /** Returns the position where projectiles thrown by this player should start, player eye position + adjustment */ Vector3d GetThrowStartPos(void) const; - /// Returns the initial speed vector of a throw, with a 3D length of a_SpeedCoeff. + /** Returns the initial speed vector of a throw, with a 3D length of a_SpeedCoeff. */ Vector3d GetThrowSpeed(double a_SpeedCoeff) const; - /// Returns the current gamemode. Partly OBSOLETE, you should use IsGameModeXXX() functions wherever applicable + /** Returns the current gamemode. Partly OBSOLETE, you should use IsGameModeXXX() functions wherever applicable */ eGameMode GetGameMode(void) const { return m_GameMode; } - /// Returns the current effective gamemode (inherited gamemode is resolved before returning) + /** Returns the current effective gamemode (inherited gamemode is resolved before returning) */ eGameMode GetEffectiveGameMode(void) const { return (m_GameMode == gmNotSet) ? m_World->GetGameMode() : m_GameMode; } /** Sets the gamemode for the player. @@ -142,52 +153,63 @@ public: */ void SetGameMode(eGameMode a_GameMode); - /// Returns true if the player is in Creative mode, either explicitly, or by inheriting from current world + /** 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 + /** 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 + /** 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 + /** Returns the associated team, NULL if none */ + cTeam * GetTeam(void) { return m_Team; } // tolua_export + + /** Sets the player team, NULL if none */ + void SetTeam(cTeam * a_Team); + + /** Forces the player to query the scoreboard for his team */ + cTeam * UpdateTeam(void); + // 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); - /// Forces the player to move in the given direction. + /** Forces the player to move in the given direction. */ void ForceSetSpeed(Vector3d a_Direction); // tolua_export - /// Tries to move to a new position, with attachment-related checks (y == -999) + /** Tries to move to a new position, with attachment-related checks (y == -999) */ 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() + /** 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 + /** 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 + /** 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); + void SendMessage (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtCustom); } + void SendMessageInfo (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtInformation); } + void SendMessageFailure (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtFailure); } + void SendMessageSuccess (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtSuccess); } + void SendMessageWarning (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtWarning); } + void SendMessageFatal (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtFailure); } + void SendMessagePrivateMsg(const AString & a_Message, const AString & a_Sender) { m_ClientHandle->SendChat(a_Message, mtPrivateMessage, a_Sender); } + void SendMessage (const cCompositeChat & a_Message) { m_ClientHandle->SendChat(a_Message); } const AString & GetName(void) const { return m_PlayerName; } void SetName(const AString & a_Name) { m_PlayerName = a_Name; } @@ -197,13 +219,12 @@ public: 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 + /** 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) + /** 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 << @@ -211,12 +232,20 @@ public: // tolua_begin - /// Returns the full color code to use for this player, based on their primary group or set in m_Color + /** Returns the full color code to use for this player, based on their primary group or set in m_Color. + The returned value includes the cChatColor::Delimiter. */ AString GetColor(void) const; - void TossItem(bool a_bDraggingItem, char a_Amount = 1, short a_CreateType = 0, short a_CreateHealth = 0); + /** tosses the item in the selected hotbar slot */ + void TossEquippedItem(char a_Amount = 1); + + /** tosses the item held in hand (when in UI windows) */ + void TossHeldItem(char a_Amount = 1); - /// Heals the player by the specified amount of HPs (positive only); sends health update + /** tosses a pickup newly created from a_Item */ + void TossPickup(const cItem & a_Item); + + /** Heals the player by the specified amount of HPs (positive only); sends health update */ void Heal(int a_Health); int GetFoodLevel (void) const { return m_FoodLevel; } @@ -224,10 +253,8 @@ public: 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 + /** 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); @@ -236,25 +263,28 @@ public: void SetFoodExhaustionLevel (double a_FoodExhaustionLevel); void SetFoodPoisonedTicksRemaining(int a_FoodPoisonedTicksRemaining); - /// Adds to FoodLevel and FoodSaturationLevel, returns true if any food has been consumed, false if player "full" + /** 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. + /** 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 + /** 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 + /** Returns true if the player is currently in the process of eating the currently equipped item */ bool IsEating(void) const { return (m_EatingFinishTick >= 0); } - /// Returns true if the player is currently flying. + /** Returns true if the player is currently flying. */ bool IsFlying(void) const { return m_IsFlying; } - /// returns true if the player has thrown out a floater. + /** Returns if a player is sleeping in a bed */ + bool IsInBed(void) const { return m_bIsInBed; } + + /** returns true if the player has thrown out a floater. */ bool IsFishing(void) const { return m_IsFishing; } void SetIsFishing(bool a_IsFishing, int a_FloaterID = -1) { m_IsFishing = a_IsFishing; m_FloaterID = a_FloaterID; } @@ -262,14 +292,17 @@ public: int GetFloaterID(void) const { return m_FloaterID; } // tolua_end + + /** Sets a player's in-bed state; we can't be sure plugins will keep this value updated, so no exporting */ + void SetIsInBed(bool a_Flag) { m_bIsInBed = a_Flag; } - /// Starts eating the currently equipped item. Resets the eating timer and sends the proper animation packet + /** 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 + /** Finishes eating the currently equipped item. Consumes the item, updates health and broadcasts the packets */ void FinishEating(void); - /// Aborts the current eating operation + /** Aborts the current eating operation */ void AbortEating(void); virtual void KilledBy(cEntity * a_Killer) override; @@ -298,51 +331,51 @@ public: 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 + /** 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 + /** 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 + /** 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) + /** Returns the current relative maximum speed (takes current sprinting / flying state into account) */ double GetMaxSpeed(void) const; - /// Gets the normal maximum speed, as reported in the 1.6.1+ protocol, in the protocol units + /** Gets the normal relative maximum speed */ double GetNormalMaxSpeed(void) const { return m_NormalMaxSpeed; } - /// Gets the sprinting maximum speed, as reported in the 1.6.1+ protocol, in the protocol units + /** Gets the sprinting relative maximum speed */ 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. + /** Gets the flying relative maximum speed */ + double GetFlyingMaxSpeed(void) const { return m_FlyingMaxSpeed; } + + /** Sets the normal relative maximum speed. 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. + /** Sets the sprinting relative maximum speed. Sends the update to player, if needed. */ void SetSprintingMaxSpeed(double a_Speed); - /// Sets the crouch status, broadcasts to all visible players + /** Sets the flying relative maximum speed. Sends the update to player, if needed. */ + void SetFlyingMaxSpeed(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 + /** Starts or stops sprinting, sends the max speed update to the client, if needed */ void SetSprint(bool a_IsSprinting); - /// Flags the player as flying + /** Flags the player as flying */ void SetFlying(bool a_IsFlying); - /// If true the player can fly even when he's not in creative. + /** If true the player can fly even when he's not in creative. */ void SetCanFly(bool a_CanFly); - /// 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; } - - /// Returns wheter the player can fly or not. + /** Returns wheter the player can fly or not. */ virtual bool CanFly(void) const { return m_CanFly; } // tolua_end @@ -361,10 +394,10 @@ protected: GroupList m_ResolvedGroups; GroupList m_Groups; - std::string m_PlayerName; - std::string m_LoadedWorldName; + AString m_PlayerName; + AString m_LoadedWorldName; - /// Xp Level stuff + /** Xp Level stuff */ enum { XP_TO_LEVEL15 = 255, @@ -372,31 +405,25 @@ protected: XP_TO_LEVEL30 = 825 } ; - /// 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" + /** Represents the food bar, one point equals half a "drumstick" */ int m_FoodLevel; - /// "Overcharge" for the m_FoodLevel; is depleted before 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 + /** 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 + /** 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 + /** 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: + /** Last position that has been recorded for food-related processing: */ Vector3d m_LastFoodPos; float m_LastJumpHeight; @@ -407,16 +434,12 @@ protected: cWindow * m_CurrentWindow; cWindow * m_InventoryWindow; - float m_TimeLastPickupCheck; - char m_Color; - float m_LastBlockActionTime; - int m_LastBlockActionCnt; eGameMode m_GameMode; - std::string m_IP; + AString m_IP; - /// The item being dragged by the cursor while in a UI window + /** The item being dragged by the cursor while in a UI window */ cItem m_DraggingItem; long long m_LastPlayerListTime; @@ -426,12 +449,21 @@ protected: cSlotNums m_InventoryPaintSlots; - /// Max speed, in ENTITY_PROPERTIES packet's units, when the player is walking. 0.1 by default + /** Max speed, relative to the game default. + 1 means regular speed, 2 means twice as fast, 0.5 means half-speed. + Default value is 1. */ double m_NormalMaxSpeed; - /// Max speed, in ENTITY_PROPERTIES packet's units, when the player is sprinting. 0.13 by default + /** Max speed, relative to the game default max speed, when sprinting. + 1 means regular speed, 2 means twice as fast, 0.5 means half-speed. + Default value is 1.3. */ double m_SprintingMaxSpeed; + /** Max speed, relative to the game default flying max speed, when flying. + 1 means regular speed, 2 means twice as fast, 0.5 means half-speed. + Default value is 1. */ + double m_FlyingMaxSpeed; + bool m_IsCrouched; bool m_IsSprinting; bool m_IsFlying; @@ -441,10 +473,10 @@ protected: bool m_CanFly; // If this is true the player can fly. Even if he is not in creative. - /// The world tick in which eating will be finished. -1 if not eating + /** The world tick in which eating will be finished. -1 if not eating */ Int64 m_EatingFinishTick; - /// Player Xp level + /** Player Xp level */ short int m_LifetimeTotalXp; short int m_CurrentXp; @@ -456,6 +488,8 @@ protected: int m_FloaterID; + cTeam * m_Team; + void ResolvePermissions(void); @@ -463,23 +497,25 @@ protected: virtual void Destroyed(void); - /// Filters out damage for creative mode + /** Filters out damage for creative mode/friendly fire */ virtual void DoTakeDamage(TakeDamageInfo & TDI) override; + + /** Stops players from burning in creative mode */ + virtual void TickBurning(cChunk & a_Chunk) override; - /// Called in each tick to handle food-related processing + /** Called in each tick to handle food-related processing */ void HandleFood(void); - /// Called in each tick if the player is fishing to make sure the floater dissapears when the player doesn't have a fishing rod as equipped item. + /** Called in each tick if the player is fishing to make sure the floater dissapears when the player doesn't have a fishing rod as equipped item. */ void HandleFloater(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) + /** Adds food exhaustion based on the difference between Pos and LastPos, sprinting status and swimming (in water block) */ void ApplyFoodExhaustionFromMovement(); + + /** Flag representing whether the player is currently in a bed + Set by a right click on unoccupied bed, unset by a time fast forward or teleport */ + bool m_bIsInBed; + } ; // tolua_export diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp index 12ce9a303..e86bb48bd 100644 --- a/src/Entities/ProjectileEntity.cpp +++ b/src/Entities/ProjectileEntity.cpp @@ -4,6 +4,7 @@ // Implements the cProjectileEntity class representing the common base class for projectiles, as well as individual projectile types #include "Globals.h" +#include "../Bindings/PluginManager.h" #include "ProjectileEntity.h" #include "../ClientHandle.h" #include "Player.h" @@ -50,12 +51,12 @@ protected: LOGD("Hit block %d:%d at {%d, %d, %d} face %d, %s (%s)", a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_EntryFace, - g_BlockIsSolid[a_BlockType] ? "solid" : "non-solid", + cBlockInfo::IsSolid(a_BlockType) ? "solid" : "non-solid", ItemToString(cItem(a_BlockType, 1, a_BlockMeta)).c_str() ); */ - if (g_BlockIsSolid[a_BlockType]) + if (cBlockInfo::IsSolid(a_BlockType)) { // The projectile hit a solid block // Calculate the exact hit coords: @@ -63,9 +64,14 @@ protected: Vector3d Line1 = m_Projectile->GetPosition(); Vector3d Line2 = Line1 + m_Projectile->GetSpeed(); double LineCoeff = 0; - char Face; + eBlockFace Face; if (bb.CalcLineIntersection(Line1, Line2, LineCoeff, Face)) { + if (cPluginManager::Get()->CallHookProjectileHitBlock(*m_Projectile)) + { + return false; + } + Vector3d Intersection = Line1 + m_Projectile->GetSpeed() * LineCoeff; m_Projectile->OnHitSolidBlock(Intersection, Face); return true; @@ -138,7 +144,7 @@ public: // Instead of colliding the bounding box with another bounding box in motion, we collide an enlarged bounding box with a hairline. // The results should be good enough for our purposes double LineCoeff; - char Face; + eBlockFace Face; EntBox.Expand(m_Projectile->GetWidth() / 2, m_Projectile->GetHeight() / 2, m_Projectile->GetWidth() / 2); if (!EntBox.CalcLineIntersection(m_Pos, m_NextPos, LineCoeff, Face)) { @@ -147,7 +153,11 @@ public: } // TODO: Some entities don't interact with the projectiles (pickups, falling blocks) - // TODO: Allow plugins to interfere about which entities can be hit + if (cPluginManager::Get()->CallHookProjectileHitEntity(*m_Projectile, *a_Entity)) + { + // A plugin disagreed. + return false; + } if (LineCoeff < m_MinCoeff) { @@ -214,7 +224,7 @@ cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, const Ve -cProjectileEntity * cProjectileEntity::Create(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d * a_Speed) +cProjectileEntity * cProjectileEntity::Create(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, const cItem & a_Item, const Vector3d * a_Speed) { Vector3d Speed; if (a_Speed != NULL) @@ -231,8 +241,15 @@ cProjectileEntity * cProjectileEntity::Create(eKind a_Kind, cEntity * a_Creator, case pkGhastFireball: return new cGhastFireballEntity (a_Creator, a_X, a_Y, a_Z, Speed); case pkFireCharge: return new cFireChargeEntity (a_Creator, a_X, a_Y, a_Z, Speed); case pkExpBottle: return new cExpBottleEntity (a_Creator, a_X, a_Y, a_Z, Speed); - case pkFirework: return new cFireworkEntity (a_Creator, a_X, a_Y, a_Z ); - // TODO: the rest + case pkFirework: + { + if (a_Item.m_FireworkItem.m_Colours.empty()) + { + return NULL; + } + + return new cFireworkEntity(a_Creator, a_X, a_Y, a_Z, a_Item); + } } LOGWARNING("%s: Unknown projectile kind: %d", __FUNCTION__, a_Kind); @@ -243,7 +260,7 @@ cProjectileEntity * cProjectileEntity::Create(eKind a_Kind, cEntity * a_Creator, -void cProjectileEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +void cProjectileEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) { // Set the position based on what face was hit: SetPosition(a_HitPos); @@ -276,6 +293,7 @@ AString cProjectileEntity::GetMCAClassName(void) const case pkExpBottle: return "ThrownExpBottle"; case pkSplashPotion: return "ThrownPotion"; case pkWitherSkull: return "WitherSkull"; + case pkFirework: return "Firework"; case pkFishingFloat: return ""; // Unknown, perhaps MC doesn't save this? } ASSERT(!"Unhandled projectile entity kind!"); @@ -353,13 +371,14 @@ void cProjectileEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) SetYawFromSpeed(); SetPitchFromSpeed(); - // DEBUG: + /* LOGD("Projectile %d: pos {%.02f, %.02f, %.02f}, speed {%.02f, %.02f, %.02f}, rot {%.02f, %.02f}", m_UniqueID, GetPosX(), GetPosY(), GetPosZ(), GetSpeedX(), GetSpeedY(), GetSpeedZ(), GetYaw(), GetPitch() ); + */ } @@ -446,7 +465,7 @@ bool cArrowEntity::CanPickup(const cPlayer & a_Player) const -void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) { if (a_HitFace == BLOCK_FACE_NONE) { return; } @@ -590,7 +609,7 @@ cThrownEggEntity::cThrownEggEntity(cEntity * a_Creator, double a_X, double a_Y, -void cThrownEggEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +void cThrownEggEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) { if (m_World->GetTickRandomNumber(7) == 1) { @@ -623,7 +642,7 @@ cThrownEnderPearlEntity::cThrownEnderPearlEntity(cEntity * a_Creator, double a_X -void cThrownEnderPearlEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +void cThrownEnderPearlEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) { // Teleport the creator here, make them take 5 damage: if (m_Creator != NULL) @@ -653,10 +672,8 @@ cThrownSnowballEntity::cThrownSnowballEntity(cEntity * a_Creator, double a_X, do -void cThrownSnowballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +void cThrownSnowballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) { - // TODO: Apply damage to certain mobs (blaze etc.) and anger all mobs - Destroy(); } @@ -664,6 +681,30 @@ void cThrownSnowballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_Hi +void cThrownSnowballEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) +{ + int TotalDamage = 0; + if (a_EntityHit.IsMob()) + { + cMonster::eType MobType = ((cMonster &) a_EntityHit).GetMobType(); + if (MobType == cMonster::mtBlaze) + { + TotalDamage = 3; + } + else if (MobType == cMonster::mtEnderDragon) + { + TotalDamage = 1; + } + } + a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1); + + Destroy(true); +} + + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cBottleOEnchantingEntity : @@ -677,9 +718,11 @@ super(pkExpBottle, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) -void cExpBottleEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +void cExpBottleEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) { - // TODO: Spawn experience orbs + // Spawn an experience orb with a reward between 3 and 11. + m_World->BroadcastSoundParticleEffect(2002, POSX_TOINT, POSY_TOINT, POSZ_TOINT, 0); + m_World->SpawnExperienceOrb(GetPosX(), GetPosY(), GetPosZ(), 3 + m_World->GetTickRandomNumber(8)); Destroy(); } @@ -691,8 +734,10 @@ void cExpBottleEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cFireworkEntity : -cFireworkEntity::cFireworkEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z) : -super(pkFirework, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) +cFireworkEntity::cFireworkEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const cItem & a_Item) : +super(pkFirework, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25), + m_ExplodeTimer(0), + m_FireworkItem(a_Item) { } @@ -700,32 +745,20 @@ super(pkFirework, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) -void cFireworkEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +void cFireworkEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) { - if ((a_HitFace != BLOCK_FACE_BOTTOM) && (a_HitFace != BLOCK_FACE_NONE)) + int RelX = POSX_TOINT - a_Chunk.GetPosX() * cChunkDef::Width; + int RelZ = POSZ_TOINT - a_Chunk.GetPosZ() * cChunkDef::Width; + int PosY = POSY_TOINT; + + if ((PosY < 0) || (PosY >= cChunkDef::Height)) { - return; + goto setspeed; } - SetSpeed(0, 0, 0); - SetPosition(GetPosX(), GetPosY() - 0.5, GetPosZ()); - - std::cout << a_HitPos.x << " " << a_HitPos.y << " " << a_HitPos.z << std::endl; - - m_IsInGround = true; - - BroadcastMovementUpdate(); -} - - - - - -void cFireworkEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) -{ if (m_IsInGround) { - if (a_Chunk.GetBlock((int)GetPosX(), (int)GetPosY() + 1, (int)GetPosZ()) == E_BLOCK_AIR) + if (a_Chunk.GetBlock(RelX, POSY_TOINT + 1, RelZ) == E_BLOCK_AIR) { m_IsInGround = false; } @@ -734,28 +767,35 @@ void cFireworkEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) return; } } + else + { + if (a_Chunk.GetBlock(RelX, POSY_TOINT + 1, RelZ) != E_BLOCK_AIR) + { + OnHitSolidBlock(GetPosition(), BLOCK_FACE_YM); + return; + } + } - Vector3d PerTickSpeed = GetSpeed() / 20; - Vector3d Pos = GetPosition(); +setspeed: + AddSpeedY(1); + AddPosition(GetSpeed() * (a_Dt / 1000)); +} - // Trace the tick's worth of movement as a line: - Vector3d NextPos = Pos + PerTickSpeed; - cProjectileTracerCallback TracerCallback(this); - if (!cLineBlockTracer::Trace(*m_World, TracerCallback, Pos, NextPos)) + + + + +void cFireworkEntity::Tick(float a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + + if (m_ExplodeTimer == m_FireworkItem.m_FireworkItem.m_FlightTimeInTicks) { - // Something has been hit, abort all other processing - return; + m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_FIREWORK_EXPLODE); + Destroy(); } - // The tracer also checks the blocks for slowdown blocks - water and lava - and stores it for later in its SlowdownCoeff - - // Update the position: - SetPosition(NextPos); - // Add slowdown and gravity effect to the speed: - Vector3d NewSpeed(GetSpeed()); - NewSpeed.y += 2; - NewSpeed *= TracerCallback.GetSlowdownCoeff(); - SetSpeed(NewSpeed); + m_ExplodeTimer++; } @@ -785,7 +825,7 @@ void cGhastFireballEntity::Explode(int a_BlockX, int a_BlockY, int a_BlockZ) -void cGhastFireballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +void cGhastFireballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) { Destroy(); Explode((int)floor(a_HitPos.x), (int)floor(a_HitPos.y), (int)floor(a_HitPos.z)); @@ -831,7 +871,7 @@ void cFireChargeEntity::Explode(int a_BlockX, int a_BlockY, int a_BlockZ) -void cFireChargeEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +void cFireChargeEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) { Destroy(); Explode((int)floor(a_HitPos.x), (int)floor(a_HitPos.y), (int)floor(a_HitPos.z)); diff --git a/src/Entities/ProjectileEntity.h b/src/Entities/ProjectileEntity.h index 4721409ee..efb7ae783 100644 --- a/src/Entities/ProjectileEntity.h +++ b/src/Entities/ProjectileEntity.h @@ -46,10 +46,10 @@ public: cProjectileEntity(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, double a_Width, double a_Height); cProjectileEntity(eKind a_Kind, cEntity * a_Creator, const Vector3d & a_Pos, const Vector3d & a_Speed, double a_Width, double a_Height); - static cProjectileEntity * Create(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d * a_Speed = NULL); + static cProjectileEntity * Create(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, const cItem & a_Item, const Vector3d * a_Speed = NULL); /// Called by the physics blocktracer when the entity hits a solid block, the hit position and the face hit (BLOCK_FACE_) is given - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace); + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace); /// Called by the physics blocktracer when the entity hits another entity virtual void OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) @@ -174,7 +174,7 @@ protected: Vector3i m_HitBlockPos; // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) override; + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; virtual void OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) override; virtual void CollectedBy(cPlayer * a_Player) override; virtual void Tick(float a_Dt, cChunk & a_Chunk) override; @@ -204,7 +204,7 @@ protected: // tolua_end // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) override; + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; // tolua_begin @@ -232,7 +232,7 @@ protected: // tolua_end // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) override; + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; // tolua_begin @@ -258,7 +258,8 @@ public: protected: // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) override; + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; + virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override; // tolua_begin @@ -284,7 +285,7 @@ public: protected: // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) override; + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; // tolua_begin @@ -305,13 +306,19 @@ public: CLASS_PROTODEF(cFireworkEntity); - cFireworkEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z); + cFireworkEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const cItem & a_Item); + const cItem & GetItem(void) const { return m_FireworkItem; } protected: // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) override; virtual void HandlePhysics(float a_Dt, cChunk & a_Chunk) override; + virtual void Tick(float a_Dt, cChunk & a_Chunk) override; + +private: + + int m_ExplodeTimer; + cItem m_FireworkItem; // tolua_begin @@ -339,7 +346,7 @@ protected: void Explode(int a_BlockX, int a_BlockY, int a_BlockZ); // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) override; + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; virtual void OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) override; // TODO: Deflecting the fireballs by arrow- or sword- hits @@ -370,7 +377,7 @@ protected: void Explode(int a_BlockX, int a_BlockY, int a_BlockZ); // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) override; + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; virtual void OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) override; // tolua_begin diff --git a/src/Entities/TNTEntity.cpp b/src/Entities/TNTEntity.cpp index 339107b2e..02f31f5bb 100644 --- a/src/Entities/TNTEntity.cpp +++ b/src/Entities/TNTEntity.cpp @@ -8,10 +8,9 @@ -cTNTEntity::cTNTEntity(double a_X, double a_Y, double a_Z, double a_FuseTimeInSec) : +cTNTEntity::cTNTEntity(double a_X, double a_Y, double a_Z, int a_FuseTicks) : super(etTNT, a_X, a_Y, a_Z, 0.98, 0.98), - m_Counter(0), - m_MaxFuseTime(a_FuseTimeInSec) + m_FuseTicks(a_FuseTicks) { } @@ -19,10 +18,9 @@ cTNTEntity::cTNTEntity(double a_X, double a_Y, double a_Z, double a_FuseTimeInSe -cTNTEntity::cTNTEntity(const Vector3d & a_Pos, double a_FuseTimeInSec) : +cTNTEntity::cTNTEntity(const Vector3d & a_Pos, int a_FuseTicks) : super(etTNT, a_Pos.x, a_Pos.y, a_Pos.z, 0.98, 0.98), - m_Counter(0), - m_MaxFuseTime(a_FuseTimeInSec) + m_FuseTicks(a_FuseTicks) { } @@ -42,18 +40,27 @@ void cTNTEntity::SpawnOn(cClientHandle & a_ClientHandle) +void cTNTEntity::Explode(void) +{ + m_FuseTicks = 0; + Destroy(true); + LOGD("BOOM at {%f,%f,%f}", GetPosX(), GetPosY(), GetPosZ()); + m_World->DoExplosionAt(4.0, GetPosX() + 0.49, GetPosY() + 0.49, GetPosZ() + 0.49, true, esPrimedTNT, this); +} + + + + + 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 + + m_FuseTicks -= 1; + if (m_FuseTicks <= 0) { - Destroy(true); - LOGD("BOOM at {%f,%f,%f}", GetPosX(), GetPosY(), GetPosZ()); - m_World->DoExplosionAt(4.0, GetPosX() + 0.49, GetPosY() + 0.49, GetPosZ() + 0.49, true, esPrimedTNT, this); - return; + Explode(); } } diff --git a/src/Entities/TNTEntity.h b/src/Entities/TNTEntity.h index d1fcae766..116f5a8cb 100644 --- a/src/Entities/TNTEntity.h +++ b/src/Entities/TNTEntity.h @@ -16,19 +16,28 @@ public: // tolua_end CLASS_PROTODEF(cTNTEntity); - cTNTEntity(double a_X, double a_Y, double a_Z, double a_FuseTimeInSec); - cTNTEntity(const Vector3d & a_Pos, double a_FuseTimeInSec); + cTNTEntity(double a_X, double a_Y, double a_Z, int a_FuseTicks = 80); + cTNTEntity(const Vector3d & a_Pos, int a_FuseTicks = 80); // cEntity overrides: virtual void SpawnOn(cClientHandle & a_ClientHandle) override; virtual void Tick(float a_Dt, cChunk & a_Chunk) override; - - double GetCounterTime(void) const { return m_Counter; } // tolua_export - double GetMaxFuseTime(void) const { return m_MaxFuseTime; } // tolua_export + + // tolua_begin + + /** Explode the tnt */ + void Explode(void); + + /** Returns the fuse ticks until the tnt will explode */ + int GetFuseTicks(void) const { return m_FuseTicks; } + + /** Set the fuse ticks until the tnt will explode */ + void SetFuseTicks(int a_FuseTicks) { m_FuseTicks = a_FuseTicks; } + + // tolua_end 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 + int m_FuseTicks; ///< How much ticks is left, while the tnt will explode }; // tolua_export |