diff options
Diffstat (limited to '')
32 files changed, 1364 insertions, 1125 deletions
diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp new file mode 100644 index 000000000..847b39bbc --- /dev/null +++ b/src/Entities/ArrowEntity.cpp @@ -0,0 +1,193 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "Player.h" +#include "ArrowEntity.h" +#include "../Chunk.h" + + + + + +cArrowEntity::cArrowEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : + super(pkArrow, a_Creator, a_X, a_Y, a_Z, 0.5, 0.5), + m_PickupState(psNoPickup), + m_DamageCoeff(2), + m_IsCritical(false), + m_Timer(0), + m_HitGroundTimer(0), + m_bIsCollected(false), + m_HitBlockPos(Vector3i(0, 0, 0)) +{ + SetSpeed(a_Speed); + SetMass(0.1); + SetYawFromSpeed(); + SetPitchFromSpeed(); + LOGD("Created arrow %d with speed {%.02f, %.02f, %.02f} and rot {%.02f, %.02f}", + m_UniqueID, GetSpeedX(), GetSpeedY(), GetSpeedZ(), + GetYaw(), GetPitch() + ); +} + + + + + +cArrowEntity::cArrowEntity(cPlayer & a_Player, double a_Force) : + super(pkArrow, &a_Player, a_Player.GetThrowStartPos(), a_Player.GetThrowSpeed(a_Force * 1.5 * 20), 0.5, 0.5), + m_PickupState(psInSurvivalOrCreative), + m_DamageCoeff(2), + m_IsCritical((a_Force >= 1)), + m_Timer(0), + m_HitGroundTimer(0), + m_HasTeleported(false), + m_bIsCollected(false), + m_HitBlockPos(0, 0, 0) +{ +} + + + + + +bool cArrowEntity::CanPickup(const cPlayer & a_Player) const +{ + switch (m_PickupState) + { + case psNoPickup: return false; + case psInSurvivalOrCreative: return (a_Player.IsGameModeSurvival() || a_Player.IsGameModeCreative()); + case psInCreative: return a_Player.IsGameModeCreative(); + } + ASSERT(!"Unhandled pickup state"); + return false; +} + + + + + +void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) +{ + if (a_HitFace == BLOCK_FACE_NONE) { return; } + + super::OnHitSolidBlock(a_HitPos, a_HitFace); + int a_X = (int)a_HitPos.x, a_Y = (int)a_HitPos.y, a_Z = (int)a_HitPos.z; + + switch (a_HitFace) + { + case BLOCK_FACE_XM: // Strangely, bounding boxes / block tracers return the actual block for these two directions, so AddFace not needed + case BLOCK_FACE_YM: + { + break; + } + default: AddFaceDirection(a_X, a_Y, a_Z, a_HitFace, true); + } + + m_HitBlockPos = Vector3i(a_X, a_Y, a_Z); + + // Broadcast arrow hit sound + m_World->BroadcastSoundEffect("random.bowhit", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); +} + + + + + +void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) +{ + if (!a_EntityHit.IsMob() && !a_EntityHit.IsMinecart() && !a_EntityHit.IsPlayer() && !a_EntityHit.IsBoat()) + { + // Not an entity that interacts with an arrow + return; + } + + int Damage = (int)(GetSpeed().Length() / 20 * m_DamageCoeff + 0.5); + if (m_IsCritical) + { + Damage += m_World->GetTickRandomNumber(Damage / 2 + 2); + } + a_EntityHit.TakeDamage(dtRangedAttack, this, Damage, 1); + + // Broadcast successful hit sound + m_World->BroadcastSoundEffect("random.successful_hit", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + + Destroy(); +} + + + + + +void cArrowEntity::CollectedBy(cPlayer * a_Dest) +{ + if ((m_IsInGround) && (!m_bIsCollected) && (CanPickup(*a_Dest))) + { + int NumAdded = a_Dest->GetInventory().AddItem(E_ITEM_ARROW); + if (NumAdded > 0) // Only play effects if there was space in inventory + { + m_World->BroadcastCollectPickup((const cPickup &)*this, *a_Dest); + // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;) + m_World->BroadcastSoundEffect("random.pop", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_bIsCollected = true; + } + } +} + + + + + +void cArrowEntity::Tick(float a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + m_Timer += a_Dt; + + if (m_bIsCollected) + { + if (m_Timer > 500.f) // 0.5 seconds + { + Destroy(); + return; + } + } + else if (m_Timer > 1000 * 60 * 5) // 5 minutes + { + Destroy(); + return; + } + + if (m_IsInGround) + { + // When an arrow hits, the client doesn't think its in the ground and keeps on moving, IF BroadcastMovementUpdate() and TeleportEntity was called during flight, AT ALL + // Fix is to simply not sync with the client and send a teleport to confirm pos after arrow has stabilised (around 1 sec after landing) + // We can afford to do this because xoft's algorithm for trajectory is near perfect, so things are pretty close anyway without sync + // Besides, this seems to be what the vanilla server does, note how arrows teleport half a second after they hit to the server position + + if (!m_HasTeleported) // Sent a teleport already, don't do again + { + if (m_HitGroundTimer > 1000.f) // Send after a second, could be less, but just in case + { + m_World->BroadcastTeleportEntity(*this); + m_HasTeleported = true; + } + else + { + m_HitGroundTimer += a_Dt; + } + } + + int RelPosX = m_HitBlockPos.x - a_Chunk.GetPosX() * cChunkDef::Width; + int RelPosZ = m_HitBlockPos.z - a_Chunk.GetPosZ() * cChunkDef::Width; + cChunk * Chunk = a_Chunk.GetRelNeighborChunkAdjustCoords(RelPosX, RelPosZ); + + if (Chunk == NULL) + { + // Inside an unloaded chunk, abort + return; + } + + if (Chunk->GetBlock(RelPosX, m_HitBlockPos.y, RelPosZ) == E_BLOCK_AIR) // Block attached to was destroyed? + { + m_IsInGround = false; // Yes, begin simulating physics again + } + } +} diff --git a/src/Entities/ArrowEntity.h b/src/Entities/ArrowEntity.h new file mode 100644 index 000000000..1fe3032ee --- /dev/null +++ b/src/Entities/ArrowEntity.h @@ -0,0 +1,96 @@ +// +// ArrowEntity.h +// + +#pragma once + +#include "ProjectileEntity.h" + + + + + +// tolua_begin + +class cArrowEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + /// Determines when the arrow can be picked up (depending on player gamemode). Corresponds to the MCA file "pickup" field + enum ePickupState + { + psNoPickup = 0, + psInSurvivalOrCreative = 1, + psInCreative = 2, + } ; + + // tolua_end + + CLASS_PROTODEF(cArrowEntity); + + /// Creates a new arrow with psNoPickup state and default damage modifier coeff + cArrowEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); + + /// Creates a new arrow as shot by a player, initializes it from the player object + cArrowEntity(cPlayer & a_Player, double a_Force); + + // tolua_begin + + /// Returns whether the arrow can be picked up by players + ePickupState GetPickupState(void) const { return m_PickupState; } + + /// Sets a new pickup state + void SetPickupState(ePickupState a_PickupState) { m_PickupState = a_PickupState; } + + /// Returns the damage modifier coeff. + double GetDamageCoeff(void) const { return m_DamageCoeff; } + + /// Sets the damage modifier coeff + void SetDamageCoeff(double a_DamageCoeff) { m_DamageCoeff = a_DamageCoeff; } + + /// Returns true if the specified player can pick the arrow up + bool CanPickup(const cPlayer & a_Player) const; + + /// Returns true if the arrow is set as critical + bool IsCritical(void) const { return m_IsCritical; } + + /// Sets the IsCritical flag + void SetIsCritical(bool a_IsCritical) { m_IsCritical = a_IsCritical; } + + // tolua_end + +protected: + + /// Determines when the arrow can be picked up by players + ePickupState m_PickupState; + + /// The coefficient applied to the damage that the arrow will deal, based on the bow enchantment. 2.0 for normal arrow + double m_DamageCoeff; + + /// If true, the arrow deals more damage + bool m_IsCritical; + + /// Timer for pickup collection animation or five minute timeout + float m_Timer; + + /// Timer for client arrow position confirmation via TeleportEntity + float m_HitGroundTimer; + + // Whether the arrow has already been teleported into the proper position in the ground. + bool m_HasTeleported; + + /// If true, the arrow is in the process of being collected - don't go to anyone else + bool m_bIsCollected; + + /// Stores the block position that arrow is lodged into, sets m_IsInGround to false if it becomes air + Vector3i m_HitBlockPos; + + // cProjectileEntity overrides: + 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; + +}; // tolua_export diff --git a/src/Entities/Boat.cpp b/src/Entities/Boat.cpp index 921252253..31bfe3dc3 100644 --- a/src/Entities/Boat.cpp +++ b/src/Entities/Boat.cpp @@ -33,9 +33,12 @@ void cBoat::SpawnOn(cClientHandle & a_ClientHandle) -void cBoat::DoTakeDamage(TakeDamageInfo & TDI) +bool cBoat::DoTakeDamage(TakeDamageInfo & TDI) { - super::DoTakeDamage(TDI); + if (!super::DoTakeDamage(TDI)) + { + return false; + } if (GetHealth() == 0) { @@ -50,6 +53,7 @@ void cBoat::DoTakeDamage(TakeDamageInfo & TDI) } Destroy(true); } + return true; } diff --git a/src/Entities/Boat.h b/src/Entities/Boat.h index c4c9afe7a..0fcfbd602 100644 --- a/src/Entities/Boat.h +++ b/src/Entities/Boat.h @@ -26,7 +26,7 @@ public: // cEntity overrides: virtual void SpawnOn(cClientHandle & a_ClientHandle) override; virtual void OnRightClicked(cPlayer & a_Player) override; - virtual void DoTakeDamage(TakeDamageInfo & TDI) override; + virtual bool DoTakeDamage(TakeDamageInfo & TDI) override; virtual void Tick(float a_Dt, cChunk & a_Chunk) override; virtual void HandleSpeedFromAttachee(float a_Forward, float a_Sideways) override; diff --git a/src/Entities/CMakeLists.txt b/src/Entities/CMakeLists.txt index 85cc45494..c9ca44d38 100644 --- a/src/Entities/CMakeLists.txt +++ b/src/Entities/CMakeLists.txt @@ -6,6 +6,7 @@ include_directories ("${PROJECT_SOURCE_DIR}/../") file(GLOB SOURCE "*.cpp" + "*.h" ) add_library(Entities ${SOURCE}) diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 8ef45f1a5..4cf10a219 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -1,3 +1,4 @@ + #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Entity.h" @@ -10,7 +11,6 @@ #include "../Simulator/FluidSimulator.h" #include "../Bindings/PluginManager.h" #include "../Tracer.h" -#include "Minecart.h" #include "Player.h" @@ -32,19 +32,14 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d , m_Attachee(NULL) , m_bDirtyHead(true) , m_bDirtyOrientation(true) - , m_bDirtyPosition(true) - , m_bDirtySpeed(true) - , m_bOnGround( false ) - , m_Gravity( -9.81f ) - , m_LastPosX( 0.0 ) - , m_LastPosY( 0.0 ) - , m_LastPosZ( 0.0 ) - , m_TimeLastTeleportPacket(0) - , m_TimeLastMoveReltPacket(0) - , m_TimeLastSpeedPacket(0) + , m_bHasSentNoSpeed(true) + , m_bOnGround(false) + , m_Gravity(-9.81f) + , m_LastPos(a_X, a_Y, a_Z) , m_IsInitialized(false) , m_EntityType(a_EntityType) , m_World(NULL) + , m_IsFireproof(false) , m_TicksSinceLastBurnDamage(0) , m_TicksSinceLastLavaDamage(0) , m_TicksSinceLastFireDamage(0) @@ -52,13 +47,16 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d , m_TicksSinceLastVoidDamage(0) , m_IsSwimming(false) , m_IsSubmerged(false) - , m_HeadYaw( 0.0 ) + , m_AirLevel(0) + , m_AirTickTimer(0) + , m_HeadYaw(0.0) , m_Rot(0.0, 0.0, 0.0) , m_Pos(a_X, a_Y, a_Z) , m_WaterSpeed(0, 0, 0) , m_Mass (0.001) // Default 1g , m_Width(a_Width) , m_Height(a_Height) + , m_InvulnerableTicks(0) { cCSLock Lock(m_CSCount); m_EntityCount++; @@ -293,17 +291,23 @@ void cEntity::SetPitchFromSpeed(void) -void cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) +bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) { if (cRoot::Get()->GetPluginManager()->CallHookTakeDamage(*this, a_TDI)) { - return; + return false; } if (m_Health <= 0) { // Can't take damage if already dead - return; + return false; + } + + if (m_InvulnerableTicks > 0) + { + // Entity is invulnerable + return false; } if ((a_TDI.Attacker != NULL) && (a_TDI.Attacker->IsPlayer())) @@ -325,17 +329,49 @@ void cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) m_Health = 0; } - if (IsMob() || IsPlayer()) // Knockback for only players and mobs + if ((IsMob() || IsPlayer()) && (a_TDI.Attacker != NULL)) // Knockback for only players and mobs { - AddSpeed(a_TDI.Knockback * 2); + int KnockbackLevel = 0; + if (a_TDI.Attacker->GetEquippedWeapon().m_ItemType == E_ITEM_BOW) + { + KnockbackLevel = a_TDI.Attacker->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchPunch); + } + else + { + KnockbackLevel = a_TDI.Attacker->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchKnockback); + } + + Vector3d additionalSpeed(0, 0, 0); + switch (KnockbackLevel) + { + case 1: + { + additionalSpeed.Set(5, .3, 5); + break; + } + case 2: + { + additionalSpeed.Set(8, .3, 8); + break; + } + default: + { + additionalSpeed.Set(2, .3, 2); + break; + } + } + AddSpeed(a_TDI.Knockback * additionalSpeed); } - m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_HURT); + m_World->BroadcastEntityStatus(*this, esGenericHurt); + + m_InvulnerableTicks = 10; if (m_Health <= 0) { KilledBy(a_TDI.Attacker); } + return true; } @@ -380,11 +416,8 @@ int cEntity::GetRawDamageAgainst(const cEntity & a_Receiver) -int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_Damage) +bool cEntity::ArmorCoversAgainst(eDamageType a_DamageType) { - // Returns the hitpoints out of a_RawDamage that the currently equipped armor would cover - - // Filter out damage types that are not protected by armor: // Ref.: http://www.minecraftwiki.net/wiki/Armor#Effects as of 2012_12_20 switch (a_DamageType) { @@ -399,9 +432,34 @@ int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_Dama case dtLightning: case dtPlugin: { - return 0; + return false; + } + + case dtAttack: + case dtArrowAttack: + case dtCactusContact: + case dtLavaContact: + case dtFireContact: + case dtEnderPearl: + case dtExplosion: + { + return true; } } + ASSERT(!"Invalid damage type!"); + return false; +} + + + + + +int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_Damage) +{ + // Returns the hitpoints out of a_RawDamage that the currently equipped armor would cover + + // Filter out damage types that are not protected by armor: + if (!ArmorCoversAgainst(a_DamageType)) return 0; // Add up all armor points: // Ref.: http://www.minecraftwiki.net/wiki/Armor#Defense_points as of 2012_12_20 @@ -479,7 +537,7 @@ void cEntity::KilledBy(cEntity * a_Killer) GetDrops(Drops, a_Killer); m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ()); - m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_DEAD); + m_World->BroadcastEntityStatus(*this, esGenericDead); } @@ -510,6 +568,11 @@ void cEntity::SetHealth(int a_Health) void cEntity::Tick(float a_Dt, cChunk & a_Chunk) { + if (m_InvulnerableTicks > 0) + { + m_InvulnerableTicks--; + } + if (m_AttachedTo != NULL) { if ((m_Pos - m_AttachedTo->GetPosition()).Length() > 0.5) @@ -519,37 +582,36 @@ void cEntity::Tick(float a_Dt, cChunk & a_Chunk) } else { - if (a_Chunk.IsValid()) + if (!a_Chunk.IsValid()) { - cChunk * NextChunk = a_Chunk.GetNeighborChunk(POSX_TOINT, POSZ_TOINT); + return; + } - if ((NextChunk == NULL) || !NextChunk->IsValid()) - { - return; - } + // Position changed -> super::Tick() called + GET_AND_VERIFY_CURRENT_CHUNK(NextChunk, POSX_TOINT, POSZ_TOINT) - TickBurning(*NextChunk); + TickBurning(*NextChunk); - if (GetPosY() < VOID_BOUNDARY) - { - TickInVoid(*NextChunk); - } - else - { - m_TicksSinceLastVoidDamage = 0; - } - - if (IsMob() || IsPlayer()) - { - // Set swimming state - SetSwimState(*NextChunk); + if (GetPosY() < VOID_BOUNDARY) + { + TickInVoid(*NextChunk); + } + else + { + m_TicksSinceLastVoidDamage = 0; + } - // Handle drowning - HandleAir(); - } + if (IsMob() || IsPlayer()) + { + // Set swimming state + SetSwimState(*NextChunk); - HandlePhysics(a_Dt, *NextChunk); + // Handle drowning + HandleAir(); } + + // None of the above functions change position, we remain in the chunk of NextChunk + HandlePhysics(a_Dt, *NextChunk); } } @@ -559,34 +621,30 @@ void cEntity::Tick(float a_Dt, cChunk & a_Chunk) void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) { + int BlockX = POSX_TOINT; + int BlockY = POSY_TOINT; + int BlockZ = POSZ_TOINT; + + // Position changed -> super::HandlePhysics() called + GET_AND_VERIFY_CURRENT_CHUNK(NextChunk, BlockX, BlockZ) + // TODO Add collision detection with entities. a_Dt /= 1000; // Convert from msec to sec - Vector3d NextPos = Vector3d(GetPosX(),GetPosY(),GetPosZ()); - Vector3d NextSpeed = Vector3d(GetSpeedX(),GetSpeedY(),GetSpeedZ()); - int BlockX = (int) floor(NextPos.x); - int BlockY = (int) floor(NextPos.y); - int BlockZ = (int) floor(NextPos.z); + Vector3d NextPos = Vector3d(GetPosX(), GetPosY(), GetPosZ()); + Vector3d NextSpeed = Vector3d(GetSpeedX(), GetSpeedY(), GetSpeedZ()); if ((BlockY >= cChunkDef::Height) || (BlockY < 0)) { - // Outside of the world - - cChunk * NextChunk = a_Chunk.GetNeighborChunk(BlockX, BlockZ); - // See if we can commit our changes. If not, we will discard them. - if (NextChunk != NULL) - { - SetSpeed(NextSpeed); - NextPos += (NextSpeed * a_Dt); - SetPosition(NextPos); - } - + // Outside of the world + AddSpeedY(m_Gravity * a_Dt); + AddPosition(GetSpeed() * a_Dt); return; } - 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; + 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 (!cBlockInfo::IsSolid(BlockIn)) // Making sure we are not inside a solid block { if (m_bOnGround) // check if it's still on the ground @@ -616,7 +674,7 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) 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; @@ -730,30 +788,43 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) 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()); + // Distance traced is an integer, so we round up from the distance we should go (Speed * Delta), else we will encounter collision detection failurse + int DistanceToTrace = (int)(ceil((NextSpeed * a_Dt).SqrLength()) * 2); + bool HasHit = Tracer.Trace(NextPos, NextSpeed, DistanceToTrace); + + if (HasHit) { - // Set to hit position + // Oh noez! We hit something: verify that the (hit position - current) was smaller or equal to the (position that we should travel without obstacles - current) + // This is because previously, we traced with a length that was rounded up (due to integer limitations), and in the case that something was hit, we don't want to overshoot our projected movement if ((Tracer.RealHit - NextPos).SqrLength() <= (NextSpeed * a_Dt).SqrLength()) { + // Block hit was within our projected path + // Begin by stopping movement in the direction that we hit something. The Normal is the line perpendicular to a 2D face and in this case, stores what block face was hit through either -1 or 1. + // For example: HitNormal.y = -1 : BLOCK_FACE_YM; HitNormal.y = 1 : BLOCK_FACE_YP 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 + if (Tracer.HitNormal.y == 1) // Hit BLOCK_FACE_YP, we are on the 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; + + // Now, set our position to the hit block (i.e. move part way along our intended trajectory) + NextPos.Set(Tracer.RealHit.x, Tracer.RealHit.y, Tracer.RealHit.z); + NextPos.x += Tracer.HitNormal.x * 0.1; + NextPos.y += Tracer.HitNormal.y * 0.05; + NextPos.z += Tracer.HitNormal.z * 0.1; } else { + // We have hit a block but overshot our intended trajectory, move normally, safe in the warm cocoon of knowledge that we won't appear to teleport forwards on clients, + // and that this piece of software will come to be hailed as the epitome of performance and functionality in C++, never before seen, and of such a like that will never + // be henceforth seen again in the time of programmers and man alike + // </&sensationalist> NextPos += (NextSpeed * a_Dt); } } @@ -764,20 +835,8 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) } } - 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); - } + SetPosition(NextPos); + SetSpeed(NextSpeed); } @@ -788,6 +847,14 @@ void cEntity::TickBurning(cChunk & a_Chunk) { // Remember the current burning state: bool HasBeenBurning = (m_TicksLeftBurning > 0); + + if (m_World->IsWeatherWet()) + { + if (POSY_TOINT > m_World->GetHeight(POSX_TOINT, POSZ_TOINT)) + { + m_TicksLeftBurning = 0; + } + } // Do the burning damage: if (m_TicksLeftBurning > 0) @@ -795,7 +862,10 @@ void cEntity::TickBurning(cChunk & a_Chunk) m_TicksSinceLastBurnDamage++; if (m_TicksSinceLastBurnDamage >= BURN_TICKS_PER_DAMAGE) { - TakeDamage(dtOnFire, NULL, BURN_DAMAGE, 0); + if (!m_IsFireproof) + { + TakeDamage(dtOnFire, NULL, BURN_DAMAGE, 0); + } m_TicksSinceLastBurnDamage = 0; } m_TicksLeftBurning--; @@ -806,7 +876,7 @@ void cEntity::TickBurning(cChunk & a_Chunk) int MaxRelX = (int)floor(GetPosX() + m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width; int MinRelZ = (int)floor(GetPosZ() - m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width; int MaxRelZ = (int)floor(GetPosZ() + m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width; - int MinY = std::max(0, std::min(cChunkDef::Height - 1, (int)floor(GetPosY()))); + int MinY = std::max(0, std::min(cChunkDef::Height - 1, POSY_TOINT)); int MaxY = std::max(0, std::min(cChunkDef::Height - 1, (int)ceil (GetPosY() + m_Height))); bool HasWater = false; bool HasLava = false; @@ -863,7 +933,10 @@ void cEntity::TickBurning(cChunk & a_Chunk) m_TicksSinceLastLavaDamage++; if (m_TicksSinceLastLavaDamage >= LAVA_TICKS_PER_DAMAGE) { - TakeDamage(dtLavaContact, NULL, LAVA_DAMAGE, 0); + if (!m_IsFireproof) + { + TakeDamage(dtLavaContact, NULL, LAVA_DAMAGE, 0); + } m_TicksSinceLastLavaDamage = 0; } } @@ -881,7 +954,10 @@ void cEntity::TickBurning(cChunk & a_Chunk) m_TicksSinceLastFireDamage++; if (m_TicksSinceLastFireDamage >= FIRE_TICKS_PER_DAMAGE) { - TakeDamage(dtFireContact, NULL, FIRE_DAMAGE, 0); + if (!m_IsFireproof) + { + TakeDamage(dtFireContact, NULL, FIRE_DAMAGE, 0); + } m_TicksSinceLastFireDamage = 0; } } @@ -941,9 +1017,9 @@ void cEntity::SetSwimState(cChunk & a_Chunk) { // 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() - ); + LOGD("SetSwimState failure: RelX = %d, RelZ = %d, Pos = %.02f, %.02f}", + RelX, RelY, GetPosX(), GetPosZ() + ); m_IsSwimming = false; m_IsSubmerged = false; return; @@ -981,13 +1057,13 @@ void cEntity::HandleAir(void) } else { - m_AirTickTimer -= 1; + m_AirTickTimer--; } } else { // Reduce air supply - m_AirLevel -= 1; + m_AirLevel--; } } else @@ -1040,6 +1116,16 @@ void cEntity::SetMaxHealth(int a_MaxHealth) +/// Sets whether the entity is fireproof +void cEntity::SetIsFireproof(bool a_IsFireproof) +{ + m_IsFireproof = a_IsFireproof; +} + + + + + /// Puts the entity on fire for the specified amount of ticks void cEntity::StartBurning(int a_TicksLeftBurning) { @@ -1099,72 +1185,70 @@ void cEntity::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) void cEntity::BroadcastMovementUpdate(const cClientHandle * a_Exclude) { - //We need to keep updating the clients when there is movement or if there was a change in speed and after 2 ticks - if( (m_Speed.SqrLength() > 0.0004f || m_bDirtySpeed) && (m_World->GetWorldAge() - m_TimeLastSpeedPacket >= 2)) + // Process packet sending every two ticks + if (GetWorld()->GetWorldAge() % 2 == 0) { - m_World->BroadcastEntityVelocity(*this,a_Exclude); - m_bDirtySpeed = false; - m_TimeLastSpeedPacket = m_World->GetWorldAge(); - } - - //Have to process position related packets this every two ticks - if (m_World->GetWorldAge() % 2 == 0) - { - int DiffX = (int) (floor(GetPosX() * 32.0) - floor(m_LastPosX * 32.0)); - int DiffY = (int) (floor(GetPosY() * 32.0) - floor(m_LastPosY * 32.0)); - int DiffZ = (int) (floor(GetPosZ() * 32.0) - floor(m_LastPosZ * 32.0)); - Int64 DiffTeleportPacket = m_World->GetWorldAge() - m_TimeLastTeleportPacket; - // 4 blocks is max Relative So if the Diff is greater than 127 or. Send an absolute position every 20 seconds - if (DiffTeleportPacket >= 400 || - ((DiffX > 127) || (DiffX < -128) || - (DiffY > 127) || (DiffY < -128) || - (DiffZ > 127) || (DiffZ < -128))) + double SpeedSqr = GetSpeed().SqrLength(); + if (SpeedSqr == 0.0) { - // - m_World->BroadcastTeleportEntity(*this,a_Exclude); - m_TimeLastTeleportPacket = m_World->GetWorldAge(); - m_TimeLastMoveReltPacket = m_TimeLastTeleportPacket; //Must synchronize. - m_LastPosX = GetPosX(); - m_LastPosY = GetPosY(); - m_LastPosZ = GetPosZ(); - m_bDirtyPosition = false; - m_bDirtyOrientation = false; + // Speed is zero, send this to clients once only as well as an absolute position + if (!m_bHasSentNoSpeed) + { + m_World->BroadcastEntityVelocity(*this, a_Exclude); + m_World->BroadcastTeleportEntity(*this, a_Exclude); + m_bHasSentNoSpeed = true; + } } else { - Int64 DiffMoveRelPacket = m_World->GetWorldAge() - m_TimeLastMoveReltPacket; - //if the change is big enough. - if ((abs(DiffX) >= 4 || abs(DiffY) >= 4 || abs(DiffZ) >= 4 || DiffMoveRelPacket >= 60) && m_bDirtyPosition) + // Movin' + m_World->BroadcastEntityVelocity(*this, a_Exclude); + m_bHasSentNoSpeed = false; + } + + // TODO: Pickups move disgracefully if relative move packets are sent as opposed to just velocity. Have a system to send relmove only when SetPosXXX() is called with a large difference in position + int DiffX = (int)(floor(GetPosX() * 32.0) - floor(m_LastPos.x * 32.0)); + int DiffY = (int)(floor(GetPosY() * 32.0) - floor(m_LastPos.y * 32.0)); + int DiffZ = (int)(floor(GetPosZ() * 32.0) - floor(m_LastPos.z * 32.0)); + + if ((DiffX != 0) || (DiffY != 0) || (DiffZ != 0)) // Have we moved? + { + if ((abs(DiffX) <= 127) && (abs(DiffY) <= 127) && (abs(DiffZ) <= 127)) // Limitations of a Byte { + // Difference within Byte limitations, use a relative move packet if (m_bDirtyOrientation) { - m_World->BroadcastEntityRelMoveLook(*this, (char)DiffX, (char)DiffY, (char)DiffZ,a_Exclude); + m_World->BroadcastEntityRelMoveLook(*this, (char)DiffX, (char)DiffY, (char)DiffZ, a_Exclude); m_bDirtyOrientation = false; } else { - m_World->BroadcastEntityRelMove(*this, (char)DiffX, (char)DiffY, (char)DiffZ,a_Exclude); + m_World->BroadcastEntityRelMove(*this, (char)DiffX, (char)DiffY, (char)DiffZ, a_Exclude); } - m_LastPosX = GetPosX(); - m_LastPosY = GetPosY(); - m_LastPosZ = GetPosZ(); - m_bDirtyPosition = false; - m_TimeLastMoveReltPacket = m_World->GetWorldAge(); + // Clients seem to store two positions, one for the velocity packet and one for the teleport/relmove packet + // The latter is only changed with a relmove/teleport, and m_LastPos stores this position + m_LastPos = GetPosition(); } else { - if (m_bDirtyOrientation) - { - m_World->BroadcastEntityLook(*this,a_Exclude); - m_bDirtyOrientation = false; - } - } + // Too big a movement, do a teleport + m_World->BroadcastTeleportEntity(*this, a_Exclude); + m_LastPos = GetPosition(); // See above + m_bDirtyOrientation = false; + } } + if (m_bDirtyHead) { - m_World->BroadcastEntityHeadLook(*this,a_Exclude); + m_World->BroadcastEntityHeadLook(*this, a_Exclude); m_bDirtyHead = false; } + if (m_bDirtyOrientation) + { + // Send individual update in case above (sending with rel-move packet) wasn't done + GetWorld()->BroadcastEntityLook(*this, a_Exclude); + m_bDirtyOrientation = false; + } } } @@ -1304,7 +1388,7 @@ void cEntity::SetRoll(double a_Roll) void cEntity::SetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) { m_Speed.Set(a_SpeedX, a_SpeedY, a_SpeedZ); - m_bDirtySpeed = true; + WrapSpeed(); } @@ -1314,7 +1398,7 @@ void cEntity::SetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) void cEntity::SetSpeedX(double a_SpeedX) { m_Speed.x = a_SpeedX; - m_bDirtySpeed = true; + WrapSpeed(); } @@ -1324,7 +1408,7 @@ void cEntity::SetSpeedX(double a_SpeedX) void cEntity::SetSpeedY(double a_SpeedY) { m_Speed.y = a_SpeedY; - m_bDirtySpeed = true; + WrapSpeed(); } @@ -1334,7 +1418,7 @@ void cEntity::SetSpeedY(double a_SpeedY) void cEntity::SetSpeedZ(double a_SpeedZ) { m_Speed.z = a_SpeedZ; - m_bDirtySpeed = true; + WrapSpeed(); } @@ -1354,7 +1438,7 @@ void cEntity::SetWidth(double a_Width) void cEntity::AddPosX(double a_AddPosX) { m_Pos.x += a_AddPosX; - m_bDirtyPosition = true; + } @@ -1363,7 +1447,7 @@ void cEntity::AddPosX(double a_AddPosX) void cEntity::AddPosY(double a_AddPosY) { m_Pos.y += a_AddPosY; - m_bDirtyPosition = true; + } @@ -1372,7 +1456,7 @@ void cEntity::AddPosY(double a_AddPosY) void cEntity::AddPosZ(double a_AddPosZ) { m_Pos.z += a_AddPosZ; - m_bDirtyPosition = true; + } @@ -1383,7 +1467,7 @@ void cEntity::AddPosition(double a_AddPosX, double a_AddPosY, double a_AddPosZ) m_Pos.x += a_AddPosX; m_Pos.y += a_AddPosY; m_Pos.z += a_AddPosZ; - m_bDirtyPosition = true; + } @@ -1393,8 +1477,7 @@ void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeed { m_Speed.x += a_AddSpeedX; m_Speed.y += a_AddSpeedY; - m_Speed.z += a_AddSpeedZ; - m_bDirtySpeed = true; + m_Speed.z += a_AddSpeedZ; WrapSpeed(); } @@ -1404,8 +1487,7 @@ void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeed void cEntity::AddSpeedX(double a_AddSpeedX) { - m_Speed.x += a_AddSpeedX; - m_bDirtySpeed = true; + m_Speed.x += a_AddSpeedX; WrapSpeed(); } @@ -1415,8 +1497,7 @@ void cEntity::AddSpeedX(double a_AddSpeedX) void cEntity::AddSpeedY(double a_AddSpeedY) { - m_Speed.y += a_AddSpeedY; - m_bDirtySpeed = true; + m_Speed.y += a_AddSpeedY; WrapSpeed(); } @@ -1426,8 +1507,7 @@ void cEntity::AddSpeedY(double a_AddSpeedY) void cEntity::AddSpeedZ(double a_AddSpeedZ) { - m_Speed.z += a_AddSpeedZ; - m_bDirtySpeed = true; + m_Speed.z += a_AddSpeedZ; WrapSpeed(); } @@ -1482,8 +1562,7 @@ Vector3d cEntity::GetLookVector(void) const // Set position void cEntity::SetPosition(double a_PosX, double a_PosY, double a_PosZ) { - m_Pos.Set(a_PosX, a_PosY, a_PosZ); - m_bDirtyPosition = true; + m_Pos.Set(a_PosX, a_PosY, a_PosZ); } @@ -1492,8 +1571,7 @@ void cEntity::SetPosition(double a_PosX, double a_PosY, double a_PosZ) void cEntity::SetPosX(double a_PosX) { - m_Pos.x = a_PosX; - m_bDirtyPosition = true; + m_Pos.x = a_PosX; } @@ -1502,8 +1580,7 @@ void cEntity::SetPosX(double a_PosX) void cEntity::SetPosY(double a_PosY) { - m_Pos.y = a_PosY; - m_bDirtyPosition = true; + m_Pos.y = a_PosY; } @@ -1513,7 +1590,6 @@ void cEntity::SetPosY(double a_PosY) void cEntity::SetPosZ(double a_PosZ) { m_Pos.z = a_PosZ; - m_bDirtyPosition = true; } diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 6e3f8292b..df03d635b 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -32,6 +32,8 @@ #define POSZ_TOINT (int)floor(GetPosZ()) #define POS_TOINT Vector3i(POSXTOINT, POSYTOINT, POSZTOINT) +#define GET_AND_VERIFY_CURRENT_CHUNK(ChunkVarName, X, Z) cChunk * ChunkVarName = a_Chunk.GetNeighborChunk(X, Z); if ((ChunkVarName == NULL) || !ChunkVarName->IsValid()) { return; } + @@ -88,23 +90,42 @@ public: } ; // tolua_end - - enum + + enum eEntityStatus { - ENTITY_STATUS_HURT = 2, - ENTITY_STATUS_DEAD = 3, - ENTITY_STATUS_WOLF_TAMING = 6, - ENTITY_STATUS_WOLF_TAMED = 7, - ENTITY_STATUS_WOLF_SHAKING = 8, - ENTITY_STATUS_EATING_ACCEPTED = 9, - ENTITY_STATUS_SHEEP_EATING = 10, - ENTITY_STATUS_GOLEM_ROSING = 11, - ENTITY_STATUS_VILLAGER_HEARTS = 12, - ENTITY_STATUS_VILLAGER_ANGRY = 13, - ENTITY_STATUS_VILLAGER_HAPPY = 14, - ENTITY_STATUS_WITCH_MAGICKING = 15, + // TODO: Investiagate 0, 1, and 5 as Wiki.vg is not certain + + // Entity becomes coloured red + esGenericHurt = 2, + // Entity plays death animation (entity falls to ground) + esGenericDead = 3, + // Iron Golem plays attack animation (arms lift and fall) + esIronGolemAttacking = 4, + // Wolf taming particles spawn (smoke) + esWolfTaming = 6, + // Wolf tamed particles spawn (hearts) + esWolfTamed = 7, + // Wolf plays water removal animation (shaking and water particles) + esWolfDryingWater = 8, + // Informs client that eating was accepted + esPlayerEatingAccepted = 9, + // Sheep plays eating animation (head lowers to ground) + esSheepEating = 10, + // Iron Golem holds gift to villager children + esIronGolemGivingPlant = 11, + // Villager spawns heart particles + esVillagerBreeding = 12, + // Villager spawns thunderclound particles + esVillagerAngry = 13, + // Villager spawns green crosses + esVillagerHappy = 14, + // Witch spawns magic particle (TODO: investigation into what this is) + esWitchMagicking = 15, + // It seems 16 (zombie conversion) is now done with metadata - ENTITY_STATUS_FIREWORK_EXPLODE= 17, + + // Informs client to explode a firework based on its metadata + esFireworkExploding = 17, } ; enum @@ -118,7 +139,8 @@ public: 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 + VOID_BOUNDARY = -46, ///< At what position Y to begin applying void damage + FALL_DAMAGE_HEIGHT = 4 ///< At what position Y fall damage is applied } ; cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height); @@ -240,14 +262,19 @@ public: // tolua_end - /// Makes this entity take damage specified in the a_TDI. The TDI is sent through plugins first, then applied - virtual void DoTakeDamage(TakeDamageInfo & a_TDI); + /** Makes this entity take damage specified in the a_TDI. + The TDI is sent through plugins first, then applied. + If it returns false, the entity hasn't receive any damage. */ + virtual bool DoTakeDamage(TakeDamageInfo & a_TDI); // tolua_begin /// Returns the hitpoints that this pawn can deal to a_Receiver using its equipped items virtual int GetRawDamageAgainst(const cEntity & a_Receiver); + /** Returns whether armor will protect against the passed damage type **/ + virtual bool ArmorCoversAgainst(eDamageType a_DamageType); + /// Returns the hitpoints out of a_RawDamage that the currently equipped armor would cover virtual int GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_RawDamage); @@ -307,6 +334,11 @@ public: int GetMaxHealth(void) const { return m_MaxHealth; } + /// Sets whether the entity is fireproof + void SetIsFireproof(bool a_IsFireproof); + + bool IsFireproof(void) const { return m_IsFireproof; } + /// Puts the entity on fire for the specified amount of ticks void StartBurning(int a_TicksLeftBurning); @@ -364,6 +396,12 @@ public: virtual bool IsSubmerged(void) const{ return m_IsSubmerged; } /** Gets remaining air of a monster */ int GetAirLevel(void) const { return m_AirLevel; } + + /** Gets the invulnerable ticks from the entity */ + int GetInvulnerableTicks(void) const { return m_InvulnerableTicks; } + + /** Set the invulnerable ticks from the entity */ + void SetInvulnerableTicks(int a_InvulnerableTicks) { m_InvulnerableTicks = a_InvulnerableTicks; } // tolua_end @@ -392,27 +430,37 @@ protected: /// The entity which is attached to this entity (rider), NULL if none cEntity * m_Attachee; - // Flags that signal that we haven't updated the clients with the latest. - bool m_bDirtyHead; - bool m_bDirtyOrientation; - bool m_bDirtyPosition; - bool m_bDirtySpeed; - - bool m_bOnGround; - float m_Gravity; + /** Stores whether head yaw has been set manually */ + bool m_bDirtyHead; - // Last Position. - double m_LastPosX, m_LastPosY, m_LastPosZ; + /** Stores whether our yaw/pitch/roll (body orientation) has been set manually */ + bool m_bDirtyOrientation; + + /** Stores whether we have sent a Velocity packet with a speed of zero (no speed) to the client + Ensures that said packet is sent only once */ + bool m_bHasSentNoSpeed; - // This variables keep track of the last time a packet was sent - Int64 m_TimeLastTeleportPacket, m_TimeLastMoveReltPacket, m_TimeLastSpeedPacket; // In ticks + /** Stores if the entity is on the ground */ + bool m_bOnGround; + + /** Stores gravity that is applied to an entity every tick + For realistic effects, this should be negative. For spaaaaaaace, this can be zero or even positive */ + float m_Gravity; + + /** Last position sent to client via the Relative Move or Teleport packets (not Velocity) + Only updated if cEntity::BroadcastMovementUpdate() is called! */ + Vector3d m_LastPos; - bool m_IsInitialized; // Is set to true when it's initialized, until it's destroyed (Initialize() till Destroy() ) + /** True when entity is initialised (Initialize()) and false when destroyed pending deletion (Destroy()) */ + bool m_IsInitialized; eEntityType m_EntityType; cWorld * m_World; + /// Whether the entity is capable of taking fire or lava damage. + bool m_IsFireproof; + /// Time, in ticks, since the last damage dealt by being on fire. Valid only if on fire (IsOnFire()) int m_TicksSinceLastBurnDamage; @@ -428,12 +476,14 @@ protected: /// Time, in ticks, since the last damage dealt by the void. Reset to zero when moving out of the void. int m_TicksSinceLastVoidDamage; + virtual void Destroyed(void) {} // Called after the entity has been destroyed void SetWorld(cWorld * a_World) { m_World = a_World; } /** 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); @@ -445,29 +495,33 @@ protected: int m_AirTickTimer; private: - // Measured in degrees, [-180, +180) + /** Measured in degrees, [-180, +180) */ double m_HeadYaw; - // Measured in meter/second (m/s) + /** Measured in meter/second (m/s) */ Vector3d m_Speed; - // Measured in degrees, [-180, +180) + /** Measured in degrees, [-180, +180) */ Vector3d m_Rot; - /// Position of the entity's XZ center and Y bottom + /** Position of the entity's XZ center and Y bottom */ Vector3d m_Pos; - // Measured in meter / second + /** Measured in meter / second */ Vector3d m_WaterSpeed; - // Measured in Kilograms (Kg) + /** Measured in Kilograms (Kg) */ double m_Mass; - /// Width of the entity, in the XZ plane. Since entities are represented as cylinders, this is more of a diameter. + /** Width of the entity, in the XZ plane. Since entities are represented as cylinders, this is more of a diameter. */ double m_Width; - /// Height of the entity (Y axis) + /** Height of the entity (Y axis) */ double m_Height; + + /** If a player hit a entity, the entity receive a invulnerable of 10 ticks. + While this ticks, a player can't hit this entity. */ + int m_InvulnerableTicks; } ; // tolua_export typedef std::list<cEntity *> cEntityList; diff --git a/src/Entities/ExpBottleEntity.cpp b/src/Entities/ExpBottleEntity.cpp new file mode 100644 index 000000000..202dde942 --- /dev/null +++ b/src/Entities/ExpBottleEntity.cpp @@ -0,0 +1,27 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "ExpBottleEntity.h" +#include "../World.h" + + + + + +cExpBottleEntity::cExpBottleEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : + super(pkExpBottle, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) +{ + SetSpeed(a_Speed); +} + + + + + +void cExpBottleEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) +{ + // 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(); +} diff --git a/src/Entities/ExpBottleEntity.h b/src/Entities/ExpBottleEntity.h new file mode 100644 index 000000000..b2043d8f1 --- /dev/null +++ b/src/Entities/ExpBottleEntity.h @@ -0,0 +1,33 @@ +// +// ExpBottleEntity.h +// + +#pragma once + +#include "ProjectileEntity.h" + + + + + +// tolua_begin + +class cExpBottleEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cExpBottleEntity); + + cExpBottleEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); + +protected: + + // cProjectileEntity overrides: + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; + +}; // tolua_export diff --git a/src/Entities/ExpOrb.cpp b/src/Entities/ExpOrb.cpp index 3623c869a..10f79aedc 100644 --- a/src/Entities/ExpOrb.cpp +++ b/src/Entities/ExpOrb.cpp @@ -34,8 +34,6 @@ cExpOrb::cExpOrb(const Vector3d & a_Pos, int a_Reward) void cExpOrb::SpawnOn(cClientHandle & a_Client) { a_Client.SendExperienceOrb(*this); - m_bDirtyPosition = false; - m_bDirtySpeed = false; m_bDirtyOrientation = false; m_bDirtyHead = false; } diff --git a/src/Entities/FallingBlock.cpp b/src/Entities/FallingBlock.cpp index a66c7e4ae..beb58f207 100644 --- a/src/Entities/FallingBlock.cpp +++ b/src/Entities/FallingBlock.cpp @@ -87,7 +87,8 @@ void cFallingBlock::Tick(float a_Dt, cChunk & a_Chunk) AddSpeedY(MilliDt * -9.8f); AddPosition(GetSpeed() * MilliDt); - if ((GetSpeedX() != 0) || (GetSpeedZ() != 0)) + // If not static (one billionth precision) broadcast movement + if ((fabs(GetSpeedX()) > std::numeric_limits<double>::epsilon()) || (fabs(GetSpeedZ()) > std::numeric_limits<double>::epsilon())) { BroadcastMovementUpdate(); } diff --git a/src/Entities/FireChargeEntity.cpp b/src/Entities/FireChargeEntity.cpp new file mode 100644 index 000000000..aba32602f --- /dev/null +++ b/src/Entities/FireChargeEntity.cpp @@ -0,0 +1,50 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "FireChargeEntity.h" +#include "../World.h" + + + + + +cFireChargeEntity::cFireChargeEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : + super(pkFireCharge, a_Creator, a_X, a_Y, a_Z, 0.3125, 0.3125) +{ + SetSpeed(a_Speed); + SetGravity(0); +} + + + + + +void cFireChargeEntity::Explode(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + if (m_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ) == E_BLOCK_AIR) + { + m_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_FIRE, 1); + } +} + + + + + +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)); +} + + + + + +void cFireChargeEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) +{ + Destroy(); + Explode((int)floor(a_HitPos.x), (int)floor(a_HitPos.y), (int)floor(a_HitPos.z)); + + // TODO: Some entities are immune to hits + a_EntityHit.StartBurning(5 * 20); // 5 seconds of burning +} diff --git a/src/Entities/FireChargeEntity.h b/src/Entities/FireChargeEntity.h new file mode 100644 index 000000000..3924c337c --- /dev/null +++ b/src/Entities/FireChargeEntity.h @@ -0,0 +1,36 @@ +// +// FireChargeEntity.h +// + +#pragma once + +#include "ProjectileEntity.h" + + + + + +// tolua_begin + +class cFireChargeEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cFireChargeEntity); + + cFireChargeEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); + +protected: + + void Explode(int a_BlockX, int a_BlockY, int a_BlockZ); + + // cProjectileEntity overrides: + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; + virtual void OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) override; + +} ; // tolua_export diff --git a/src/Entities/FireworkEntity.cpp b/src/Entities/FireworkEntity.cpp new file mode 100644 index 000000000..403a53c84 --- /dev/null +++ b/src/Entities/FireworkEntity.cpp @@ -0,0 +1,73 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "FireworkEntity.h" +#include "../World.h" +#include "../Chunk.h" + + + + + +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) +{ +} + + + + + +void cFireworkEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) +{ + 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)) + { + goto setspeed; + } + + if (m_IsInGround) + { + if (a_Chunk.GetBlock(RelX, POSY_TOINT + 1, RelZ) == E_BLOCK_AIR) + { + m_IsInGround = false; + } + else + { + return; + } + } + else + { + if (a_Chunk.GetBlock(RelX, POSY_TOINT + 1, RelZ) != E_BLOCK_AIR) + { + OnHitSolidBlock(GetPosition(), BLOCK_FACE_YM); + return; + } + } + +setspeed: + AddSpeedY(1); + AddPosition(GetSpeed() * (a_Dt / 1000)); +} + + + + + +void cFireworkEntity::Tick(float a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + + if (m_ExplodeTimer == m_FireworkItem.m_FireworkItem.m_FlightTimeInTicks) + { + m_World->BroadcastEntityStatus(*this, esFireworkExploding); + Destroy(); + } + + m_ExplodeTimer++; +} diff --git a/src/Entities/FireworkEntity.h b/src/Entities/FireworkEntity.h new file mode 100644 index 000000000..c62ca9402 --- /dev/null +++ b/src/Entities/FireworkEntity.h @@ -0,0 +1,40 @@ +// +// FireworkEntity.h +// + +#pragma once + +#include "ProjectileEntity.h" + + + + + +// tolua_begin + +class cFireworkEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cFireworkEntity); + + 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 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_export diff --git a/src/Entities/GhastFireballEntity.cpp b/src/Entities/GhastFireballEntity.cpp new file mode 100644 index 000000000..9e4cb387e --- /dev/null +++ b/src/Entities/GhastFireballEntity.cpp @@ -0,0 +1,44 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "GhastFireballEntity.h" +#include "../World.h" + + + + + +cGhastFireballEntity::cGhastFireballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : + super(pkGhastFireball, a_Creator, a_X, a_Y, a_Z, 1, 1) +{ + SetSpeed(a_Speed); + SetGravity(0); +} + + + + + +void cGhastFireballEntity::Explode(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + m_World->DoExplosionAt(1, a_BlockX, a_BlockY, a_BlockZ, true, esGhastFireball, this); +} + + + + + +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)); +} + + + + + +void cGhastFireballEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) +{ + Destroy(); + Explode((int)floor(a_HitPos.x), (int)floor(a_HitPos.y), (int)floor(a_HitPos.z)); +} diff --git a/src/Entities/GhastFireballEntity.h b/src/Entities/GhastFireballEntity.h new file mode 100644 index 000000000..9e4572c78 --- /dev/null +++ b/src/Entities/GhastFireballEntity.h @@ -0,0 +1,38 @@ +// +// GhastFireballEntity.h +// + +#pragma once + +#include "ProjectileEntity.h" + + + + + +// tolua_begin + +class cGhastFireballEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cGhastFireballEntity); + + cGhastFireballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); + +protected: + + void Explode(int a_BlockX, int a_BlockY, int a_BlockZ); + + // cProjectileEntity overrides: + 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 + +} ; // tolua_export diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp index 7f38aa35a..7bd440d6d 100644 --- a/src/Entities/Minecart.cpp +++ b/src/Entities/Minecart.cpp @@ -132,7 +132,7 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) return; } - int PosY = (int)floor(GetPosY()); + int PosY = POSY_TOINT; if ((PosY <= 0) || (PosY >= cChunkDef::Height)) { // Outside the world, just process normal falling physics @@ -141,8 +141,8 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) return; } - int RelPosX = (int)floor(GetPosX()) - a_Chunk.GetPosX() * cChunkDef::Width; - int RelPosZ = (int)floor(GetPosZ()) - a_Chunk.GetPosZ() * cChunkDef::Width; + int RelPosX = POSX_TOINT - a_Chunk.GetPosX() * cChunkDef::Width; + int RelPosZ = POSZ_TOINT - a_Chunk.GetPosZ() * cChunkDef::Width; cChunk * Chunk = a_Chunk.GetRelNeighborChunkAdjustCoords(RelPosX, RelPosZ); if (Chunk == NULL) { @@ -195,7 +195,7 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) super::HandlePhysics(a_Dt, *Chunk); } - if (m_bIsOnDetectorRail && !Vector3i((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())).Equals(m_DetectorRailPosition)) + if (m_bIsOnDetectorRail && !Vector3i(POSX_TOINT, POSY_TOINT, POSZ_TOINT).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; @@ -203,7 +203,7 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) else if (WasDetectorRail) { m_bIsOnDetectorRail = true; - m_DetectorRailPosition = Vector3i((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); + m_DetectorRailPosition = Vector3i(POSX_TOINT, POSY_TOINT, POSZ_TOINT); } // Broadcast positioning changes to client @@ -719,11 +719,11 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) { if (GetSpeedZ() > 0) { - BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)ceil(GetPosZ())); + BLOCKTYPE Block = m_World->GetBlock(POSX_TOINT, POSY_TOINT, (int)ceil(GetPosZ())); 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); + cBoundingBox bbBlock(Vector3d(POSX_TOINT, POSY_TOINT, (int)ceil(GetPosZ())), 0.5, 1); cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight()); if (bbBlock.DoesIntersect(bbMinecart)) @@ -736,10 +736,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) } else if (GetSpeedZ() < 0) { - BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) - 1); + BLOCKTYPE Block = m_World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT - 1); if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block)) { - cBoundingBox bbBlock(Vector3d((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) - 1), 0.5, 1); + cBoundingBox bbBlock(Vector3d(POSX_TOINT, POSY_TOINT, POSZ_TOINT - 1), 0.5, 1); cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ() - 1), GetWidth() / 2, GetHeight()); if (bbBlock.DoesIntersect(bbMinecart)) @@ -756,10 +756,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) { if (GetSpeedX() > 0) { - BLOCKTYPE Block = m_World->GetBlock((int)ceil(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); + BLOCKTYPE Block = m_World->GetBlock((int)ceil(GetPosX()), POSY_TOINT, POSZ_TOINT); if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block)) { - cBoundingBox bbBlock(Vector3d((int)ceil(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())), 0.5, 1); + cBoundingBox bbBlock(Vector3d((int)ceil(GetPosX()), POSY_TOINT, POSZ_TOINT), 0.5, 1); cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight()); if (bbBlock.DoesIntersect(bbMinecart)) @@ -772,10 +772,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) } else if (GetSpeedX() < 0) { - BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()) - 1, (int)floor(GetPosY()), (int)floor(GetPosZ())); + BLOCKTYPE Block = m_World->GetBlock(POSX_TOINT - 1, POSY_TOINT, POSZ_TOINT); if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block)) { - cBoundingBox bbBlock(Vector3d((int)floor(GetPosX()) - 1, (int)floor(GetPosY()), (int)floor(GetPosZ())), 0.5, 1); + cBoundingBox bbBlock(Vector3d(POSX_TOINT - 1, POSY_TOINT, POSZ_TOINT), 0.5, 1); cBoundingBox bbMinecart(Vector3d(GetPosX() - 1, floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight()); if (bbBlock.DoesIntersect(bbMinecart)) @@ -793,10 +793,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) case E_META_RAIL_CURVED_ZP_XM: case E_META_RAIL_CURVED_ZP_XP: { - BLOCKTYPE BlockXM = m_World->GetBlock((int)floor(GetPosX()) - 1, (int)floor(GetPosY()), (int)floor(GetPosZ())); - BLOCKTYPE BlockXP = m_World->GetBlock((int)floor(GetPosX()) + 1, (int)floor(GetPosY()), (int)floor(GetPosZ())); - 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); + BLOCKTYPE BlockXM = m_World->GetBlock(POSX_TOINT - 1, POSY_TOINT, POSZ_TOINT); + BLOCKTYPE BlockXP = m_World->GetBlock(POSX_TOINT + 1, POSY_TOINT, POSZ_TOINT); + BLOCKTYPE BlockZM = m_World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT - 1); + BLOCKTYPE BlockZP = m_World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT + 1); if ( (!IsBlockRail(BlockXM) && cBlockInfo::IsSolid(BlockXM)) || (!IsBlockRail(BlockXP) && cBlockInfo::IsSolid(BlockXP)) || @@ -805,7 +805,7 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) ) { SetSpeed(0, 0, 0); - SetPosition((int)floor(GetPosX()) + 0.5, GetPosY(), (int)floor(GetPosZ()) + 0.5); + SetPosition(POSX_TOINT + 0.5, GetPosY(), POSZ_TOINT + 0.5); return true; } break; @@ -822,7 +822,7 @@ 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); + cChunkDef::BlockToChunk(POSX_TOINT, POSZ_TOINT, ChunkX, ChunkZ); m_World->ForEachEntityInChunk(ChunkX, ChunkZ, MinecartCollisionCallback); if (!MinecartCollisionCallback.FoundIntersection()) @@ -902,18 +902,21 @@ bool cMinecart::TestEntityCollision(NIBBLETYPE a_RailMeta) -void cMinecart::DoTakeDamage(TakeDamageInfo & TDI) +bool cMinecart::DoTakeDamage(TakeDamageInfo & TDI) { if ((TDI.Attacker != NULL) && TDI.Attacker->IsPlayer() && ((cPlayer *)TDI.Attacker)->IsGameModeCreative()) { Destroy(); TDI.FinalDamage = GetMaxHealth(); // Instant hit for creative - super::DoTakeDamage(TDI); - return; // No drops for creative + SetInvulnerableTicks(0); + return super::DoTakeDamage(TDI); // No drops for creative } m_LastDamage = TDI.FinalDamage; - super::DoTakeDamage(TDI); + if (!super::DoTakeDamage(TDI)) + { + return false; + } m_World->BroadcastEntityMetadata(*this); @@ -952,12 +955,13 @@ void cMinecart::DoTakeDamage(TakeDamageInfo & TDI) default: { ASSERT(!"Unhandled minecart type when spawning pickup!"); - return; + return true; } } m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ()); } + return true; } diff --git a/src/Entities/Minecart.h b/src/Entities/Minecart.h index ebdb576e0..1e60f091c 100644 --- a/src/Entities/Minecart.h +++ b/src/Entities/Minecart.h @@ -36,7 +36,7 @@ public: // cEntity overrides: virtual void SpawnOn(cClientHandle & a_ClientHandle) override; virtual void HandlePhysics(float a_Dt, cChunk & a_Chunk) override; - virtual void DoTakeDamage(TakeDamageInfo & TDI) override; + virtual bool DoTakeDamage(TakeDamageInfo & TDI) override; virtual void Destroyed() override; int LastDamage(void) const { return m_LastDamage; } diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 7fc89b62b..497b41683 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -98,45 +98,44 @@ void cPickup::Tick(float a_Dt, cChunk & a_Chunk) if (!m_bCollected) { - int BlockY = (int) floor(GetPosY()); + int BlockY = POSY_TOINT; + int BlockX = POSX_TOINT; + int BlockZ = POSZ_TOINT; + if ((BlockY >= 0) && (BlockY < cChunkDef::Height)) // Don't do anything except for falling when outside the world { - int BlockX = (int) floor(GetPosX()); - int BlockZ = (int) floor(GetPosZ()); // Position might have changed due to physics. So we have to make sure we have the correct chunk. - cChunk * CurrentChunk = a_Chunk.GetNeighborChunk(BlockX, BlockZ); - if (CurrentChunk != NULL) // Make sure the chunk is loaded - { - int RelBlockX = BlockX - (CurrentChunk->GetPosX() * cChunkDef::Width); - int RelBlockZ = BlockZ - (CurrentChunk->GetPosZ() * cChunkDef::Width); + GET_AND_VERIFY_CURRENT_CHUNK(CurrentChunk, BlockX, BlockZ) + + int RelBlockX = BlockX - (CurrentChunk->GetPosX() * cChunkDef::Width); + int RelBlockZ = BlockZ - (CurrentChunk->GetPosZ() * cChunkDef::Width); - // If the pickup is on the bottommost block position, make it think the void is made of air: (#131) - BLOCKTYPE BlockBelow = (BlockY > 0) ? CurrentChunk->GetBlock(RelBlockX, BlockY - 1, RelBlockZ) : E_BLOCK_AIR; - BLOCKTYPE BlockIn = CurrentChunk->GetBlock(RelBlockX, BlockY, RelBlockZ); - - if ( - IsBlockLava(BlockBelow) || (BlockBelow == E_BLOCK_FIRE) || - IsBlockLava(BlockIn) || (BlockIn == E_BLOCK_FIRE) - ) + // If the pickup is on the bottommost block position, make it think the void is made of air: (#131) + BLOCKTYPE BlockBelow = (BlockY > 0) ? CurrentChunk->GetBlock(RelBlockX, BlockY - 1, RelBlockZ) : E_BLOCK_AIR; + BLOCKTYPE BlockIn = CurrentChunk->GetBlock(RelBlockX, BlockY, RelBlockZ); + + if ( + IsBlockLava(BlockBelow) || (BlockBelow == E_BLOCK_FIRE) || + IsBlockLava(BlockIn) || (BlockIn == E_BLOCK_FIRE) + ) + { + m_bCollected = true; + m_Timer = 0; // We have to reset the timer. + m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick. + if (m_Timer > 500.f) { - m_bCollected = true; - m_Timer = 0; // We have to reset the timer. - m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick. - if (m_Timer > 500.f) - { - Destroy(true); - return; - } + Destroy(true); + return; } + } - if (!IsDestroyed()) // Don't try to combine if someone has tried to combine me + 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()) { - 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); - } + m_World->BroadcastEntityMetadata(*this); } } } @@ -156,7 +155,7 @@ void cPickup::Tick(float a_Dt, cChunk & a_Chunk) return; } - if (GetPosY() < -8) // Out of this world and no more visible! + if (GetPosY() < VOID_BOUNDARY) // Out of this world and no more visible! { Destroy(true); return; diff --git a/src/Entities/Pickup.h b/src/Entities/Pickup.h index 74b917bce..2dcbecaaf 100644 --- a/src/Entities/Pickup.h +++ b/src/Entities/Pickup.h @@ -49,9 +49,6 @@ public: 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 */ float m_Timer; diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 7f2e5b4c2..6ac11c270 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -76,18 +76,16 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) cTimer t1; m_LastPlayerListTime = t1.GetNowTime(); - - m_TimeLastTeleportPacket = 0; m_PlayerName = a_PlayerName; - m_bDirtyPosition = true; // So chunks are streamed to player at spawn if (!LoadFromDisk()) { m_Inventory.Clear(); - SetPosX(cRoot::Get()->GetDefaultWorld()->GetSpawnX()); - SetPosY(cRoot::Get()->GetDefaultWorld()->GetSpawnY()); - SetPosZ(cRoot::Get()->GetDefaultWorld()->GetSpawnZ()); + cWorld * DefaultWorld = cRoot::Get()->GetDefaultWorld(); + SetPosX(DefaultWorld->GetSpawnX()); + SetPosY(DefaultWorld->GetSpawnY()); + SetPosZ(DefaultWorld->GetSpawnZ()); LOGD("Player \"%s\" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f}", a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ() @@ -208,25 +206,22 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) m_BowCharge += 1; } - //handle updating experience + // Handle updating experience if (m_bDirtyExperience) { SendExperience(); } - if (m_bDirtyPosition) + if (GetPosition() != m_LastPos) // Change in position from last tick? { // Apply food exhaustion from movement: ApplyFoodExhaustionFromMovement(); cRoot::Get()->GetPluginManager()->CallHookPlayerMoving(*this); - BroadcastMovementUpdate(m_ClientHandle); m_ClientHandle->StreamChunks(); } - else - { - BroadcastMovementUpdate(m_ClientHandle); - } + + BroadcastMovementUpdate(m_ClientHandle); if (m_Health > 0) // make sure player is alive { @@ -437,7 +432,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) cWorld * World = GetWorld(); if ((GetPosY() >= 0) && (GetPosY() < cChunkDef::Height)) { - BLOCKTYPE BlockType = World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); + BLOCKTYPE BlockType = World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT); if (BlockType != E_BLOCK_AIR) { m_bTouchGround = true; @@ -466,7 +461,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) TakeDamage(dtFalling, NULL, Damage, Damage, 0); // Fall particles - GetWorld()->BroadcastSoundParticleEffect(2006, (int)floor(GetPosX()), (int)GetPosY() - 1, (int)floor(GetPosZ()), Damage /* Used as particle effect speed modifier */); + GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, (int)GetPosY() - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */); } m_LastGroundHeight = (float)GetPosY(); @@ -590,7 +585,7 @@ void cPlayer::FinishEating(void) m_EatingFinishTick = -1; // Send the packets: - m_ClientHandle->SendEntityStatus(*this, ENTITY_STATUS_EATING_ACCEPTED); + m_ClientHandle->SendEntityStatus(*this, esPlayerEatingAccepted); m_World->BroadcastEntityAnimation(*this, 0); m_World->BroadcastEntityMetadata(*this); @@ -807,14 +802,14 @@ void cPlayer::SetFlying(bool a_IsFlying) -void cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) +bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) { if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtPlugin)) { if (IsGameModeCreative()) { // No damage / health in creative mode if not void or plugin damage - return; + return false; } } @@ -827,17 +822,19 @@ void cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) if (!m_Team->AllowsFriendlyFire()) { // Friendly fire is disabled - return; + return false; } } } - super::DoTakeDamage(a_TDI); - - // Any kind of damage adds food exhaustion - AddFoodExhaustion(0.3f); - - SendHealth(); + if (super::DoTakeDamage(a_TDI)) + { + // Any kind of damage adds food exhaustion + AddFoodExhaustion(0.3f); + SendHealth(); + return true; + } + return false; } @@ -896,6 +893,7 @@ void cPlayer::KilledBy(cEntity * a_Killer) void cPlayer::Respawn(void) { m_Health = GetMaxHealth(); + SetInvulnerableTicks(20); // Reset food level: m_FoodLevel = MAX_FOOD_LEVEL; @@ -1165,9 +1163,9 @@ Vector3d cPlayer::GetThrowSpeed(double a_SpeedCoeff) const -void cPlayer::ForceSetSpeed(Vector3d a_Direction) +void cPlayer::ForceSetSpeed(const Vector3d & a_Speed) { - SetSpeed(a_Direction); + SetSpeed(a_Speed); m_ClientHandle->SendEntityVelocity(*this); } @@ -1519,22 +1517,16 @@ void cPlayer::LoadPermissionsFromDisk() cIniFile IniFile; if (IniFile.ReadFile("users.ini")) { - std::string Groups = IniFile.GetValue(m_PlayerName, "Groups", ""); - if (!Groups.empty()) + AString Groups = IniFile.GetValueSet(m_PlayerName, "Groups", "Default"); + AStringVector Split = StringSplitAndTrim(Groups, ","); + + for (AStringVector::const_iterator itr = Split.begin(), end = Split.end(); itr != end; ++itr) { - AStringVector Split = StringSplitAndTrim(Groups, ","); - for (AStringVector::const_iterator itr = Split.begin(), end = Split.end(); itr != end; ++itr) + if (!cRoot::Get()->GetGroupManager()->ExistsGroup(*itr)) { - 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); + LOGWARNING("The group %s for player %s was not found!", itr->c_str(), m_PlayerName.c_str()); } - } - else - { - AddToGroup("Default"); + AddToGroup(*itr); } AString Color = IniFile.GetValue(m_PlayerName, "Color", "-"); @@ -1546,8 +1538,10 @@ void cPlayer::LoadPermissionsFromDisk() else { cGroupManager::GenerateDefaultUsersIni(IniFile); + IniFile.AddValue("Groups", m_PlayerName, "Default"); AddToGroup("Default"); } + IniFile.WriteFile("users.ini"); ResolvePermissions(); } @@ -1596,10 +1590,7 @@ bool cPlayer::LoadFromDisk() SetPosX(JSON_PlayerPosition[(unsigned int)0].asDouble()); SetPosY(JSON_PlayerPosition[(unsigned int)1].asDouble()); SetPosZ(JSON_PlayerPosition[(unsigned int)2].asDouble()); - m_LastPosX = GetPosX(); - m_LastPosY = GetPosY(); - m_LastPosZ = GetPosZ(); - m_LastFoodPos = GetPosition(); + m_LastPos = GetPosition(); } Json::Value & JSON_PlayerRotation = root["rotation"]; @@ -1860,17 +1851,16 @@ void cPlayer::ApplyFoodExhaustionFromMovement() { return; } - - // Calculate the distance travelled, update the last pos: - Vector3d Movement(GetPosition() - m_LastFoodPos); - Movement.y = 0; // Only take XZ movement into account - m_LastFoodPos = GetPosition(); - + // If riding anything, apply no food exhaustion if (m_AttachedTo != NULL) { return; } + + // Calculate the distance travelled, update the last pos: + Vector3d Movement(GetPosition() - m_LastPos); + Movement.y = 0; // Only take XZ movement into account // Apply the exhaustion based on distance travelled: double BaseExhaustion = Movement.Length(); @@ -1899,9 +1889,9 @@ void cPlayer::ApplyFoodExhaustionFromMovement() void cPlayer::Detach() { super::Detach(); - int PosX = (int)floor(GetPosX()); - int PosY = (int)floor(GetPosY()); - int PosZ = (int)floor(GetPosZ()); + int PosX = POSX_TOINT; + int PosY = POSY_TOINT; + int PosZ = POSZ_TOINT; // Search for a position within an area to teleport player after detachment // Position must be solid land, and occupied by a nonsolid block diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 05377a117..6fc7e2875 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -181,7 +181,7 @@ public: void LoginSetGameMode(eGameMode a_GameMode); /** Forces the player to move in the given direction. */ - void ForceSetSpeed(Vector3d a_Direction); // tolua_export + void ForceSetSpeed(const Vector3d & a_Speed); // tolua_export /** Tries to move to a new position, with attachment-related checks (y == -999) */ void MoveTo(const Vector3d & a_NewPos); // tolua_export @@ -423,9 +423,6 @@ protected: /** Number of ticks remaining for the foodpoisoning effect; zero if not foodpoisoned */ int m_FoodPoisonedTicksRemaining; - /** Last position that has been recorded for food-related processing: */ - Vector3d m_LastFoodPos; - float m_LastJumpHeight; float m_LastGroundHeight; bool m_bTouchGround; @@ -498,7 +495,7 @@ protected: virtual void Destroyed(void); /** Filters out damage for creative mode/friendly fire */ - virtual void DoTakeDamage(TakeDamageInfo & TDI) override; + virtual bool DoTakeDamage(TakeDamageInfo & TDI) override; /** Stops players from burning in creative mode */ virtual void TickBurning(cChunk & a_Chunk) override; diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp index e86bb48bd..3e48d310c 100644 --- a/src/Entities/ProjectileEntity.cpp +++ b/src/Entities/ProjectileEntity.cpp @@ -4,15 +4,24 @@ // 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" #include "../LineBlockTracer.h" #include "../BoundingBox.h" #include "../ChunkMap.h" #include "../Chunk.h" +#include "ArrowEntity.h" +#include "ThrownEggEntity.h" +#include "ThrownEnderPearlEntity.h" +#include "ExpBottleEntity.h" +#include "ThrownSnowballEntity.h" +#include "FireChargeEntity.h" +#include "FireworkEntity.h" +#include "GhastFireballEntity.h" + @@ -401,495 +410,3 @@ void cProjectileEntity::CollectedBy(cPlayer * a_Dest) // Overriden in arrow UNUSED(a_Dest); } - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cArrowEntity: - -cArrowEntity::cArrowEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : - super(pkArrow, a_Creator, a_X, a_Y, a_Z, 0.5, 0.5), - m_PickupState(psNoPickup), - m_DamageCoeff(2), - m_IsCritical(false), - m_Timer(0), - m_HitGroundTimer(0), - m_bIsCollected(false), - m_HitBlockPos(Vector3i(0, 0, 0)) -{ - SetSpeed(a_Speed); - SetMass(0.1); - SetYawFromSpeed(); - SetPitchFromSpeed(); - LOGD("Created arrow %d with speed {%.02f, %.02f, %.02f} and rot {%.02f, %.02f}", - m_UniqueID, GetSpeedX(), GetSpeedY(), GetSpeedZ(), - GetYaw(), GetPitch() - ); -} - - - - - -cArrowEntity::cArrowEntity(cPlayer & a_Player, double a_Force) : - super(pkArrow, &a_Player, a_Player.GetThrowStartPos(), a_Player.GetThrowSpeed(a_Force * 1.5 * 20), 0.5, 0.5), - m_PickupState(psInSurvivalOrCreative), - m_DamageCoeff(2), - m_IsCritical((a_Force >= 1)), - m_Timer(0), - m_HitGroundTimer(0), - m_bIsCollected(false), - m_HitBlockPos(0, 0, 0) -{ -} - - - - - -bool cArrowEntity::CanPickup(const cPlayer & a_Player) const -{ - switch (m_PickupState) - { - case psNoPickup: return false; - case psInSurvivalOrCreative: return (a_Player.IsGameModeSurvival() || a_Player.IsGameModeCreative()); - case psInCreative: return a_Player.IsGameModeCreative(); - } - ASSERT(!"Unhandled pickup state"); - return false; -} - - - - - -void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) -{ - if (a_HitFace == BLOCK_FACE_NONE) { return; } - - super::OnHitSolidBlock(a_HitPos, a_HitFace); - int a_X = (int)a_HitPos.x, a_Y = (int)a_HitPos.y, a_Z = (int)a_HitPos.z; - - switch (a_HitFace) - { - case BLOCK_FACE_XM: // Strangely, bounding boxes / block tracers return the actual block for these two directions, so AddFace not needed - case BLOCK_FACE_YM: - { - break; - } - default: AddFaceDirection(a_X, a_Y, a_Z, a_HitFace, true); - } - - m_HitBlockPos = Vector3i(a_X, a_Y, a_Z); - - // Broadcast arrow hit sound - m_World->BroadcastSoundEffect("random.bowhit", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); -} - - - - - -void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) -{ - if (!a_EntityHit.IsMob() && !a_EntityHit.IsMinecart() && !a_EntityHit.IsPlayer() && !a_EntityHit.IsBoat()) - { - // Not an entity that interacts with an arrow - return; - } - - int Damage = (int)(GetSpeed().Length() / 20 * m_DamageCoeff + 0.5); - if (m_IsCritical) - { - Damage += m_World->GetTickRandomNumber(Damage / 2 + 2); - } - a_EntityHit.TakeDamage(dtRangedAttack, this, Damage, 1); - - // Broadcast successful hit sound - m_World->BroadcastSoundEffect("random.successful_hit", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); - - Destroy(); -} - - - - - -void cArrowEntity::CollectedBy(cPlayer * a_Dest) -{ - if ((m_IsInGround) && (!m_bIsCollected) && (CanPickup(*a_Dest))) - { - int NumAdded = a_Dest->GetInventory().AddItem(E_ITEM_ARROW); - if (NumAdded > 0) // Only play effects if there was space in inventory - { - m_World->BroadcastCollectPickup((const cPickup &)*this, *a_Dest); - // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;) - m_World->BroadcastSoundEffect("random.pop", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); - m_bIsCollected = true; - } - } -} - - - - - -void cArrowEntity::Tick(float a_Dt, cChunk & a_Chunk) -{ - super::Tick(a_Dt, a_Chunk); - m_Timer += a_Dt; - - if (m_bIsCollected) - { - if (m_Timer > 500.f) // 0.5 seconds - { - Destroy(); - return; - } - } - else if (m_Timer > 1000 * 60 * 5) // 5 minutes - { - Destroy(); - return; - } - - if (m_IsInGround) - { - // When an arrow hits, the client doesn't think its in the ground and keeps on moving, IF BroadcastMovementUpdate() and TeleportEntity was called during flight, AT ALL - // Fix is to simply not sync with the client and send a teleport to confirm pos after arrow has stabilised (around 1 sec after landing) - // We can afford to do this because xoft's algorithm for trajectory is near perfect, so things are pretty close anyway without sync - // Besides, this seems to be what the vanilla server does, note how arrows teleport half a second after they hit to the server position - - if (m_HitGroundTimer != -1) // Sent a teleport already, don't do again - { - if (m_HitGroundTimer > 1000.f) // Send after a second, could be less, but just in case - { - m_World->BroadcastTeleportEntity(*this); - m_HitGroundTimer = -1; - } - else - { - m_HitGroundTimer += a_Dt; - } - } - - int RelPosX = m_HitBlockPos.x - a_Chunk.GetPosX() * cChunkDef::Width; - int RelPosZ = m_HitBlockPos.z - a_Chunk.GetPosZ() * cChunkDef::Width; - cChunk * Chunk = a_Chunk.GetRelNeighborChunkAdjustCoords(RelPosX, RelPosZ); - - if (Chunk == NULL) - { - // Inside an unloaded chunk, abort - return; - } - - if (Chunk->GetBlock(RelPosX, m_HitBlockPos.y, RelPosZ) == E_BLOCK_AIR) // Block attached to was destroyed? - { - m_IsInGround = false; // Yes, begin simulating physics again - } - } -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cThrownEggEntity: - -cThrownEggEntity::cThrownEggEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : - super(pkEgg, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) -{ - SetSpeed(a_Speed); -} - - - - - -void cThrownEggEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) -{ - if (m_World->GetTickRandomNumber(7) == 1) - { - m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); - } - else if (m_World->GetTickRandomNumber(32) == 1) - { - m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); - m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); - m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); - m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); - } - Destroy(); -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cThrownEnderPearlEntity : - -cThrownEnderPearlEntity::cThrownEnderPearlEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : - super(pkEnderPearl, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) -{ - SetSpeed(a_Speed); -} - - - - - -void cThrownEnderPearlEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) -{ - // Teleport the creator here, make them take 5 damage: - if (m_Creator != NULL) - { - // TODO: The coords might need some tweaking based on the block face - m_Creator->TeleportToCoords(a_HitPos.x + 0.5, a_HitPos.y + 1.7, a_HitPos.z + 0.5); - m_Creator->TakeDamage(dtEnderPearl, this, 5, 0); - } - - Destroy(); -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cThrownSnowballEntity : - -cThrownSnowballEntity::cThrownSnowballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : - super(pkSnowball, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) -{ - SetSpeed(a_Speed); -} - - - - - -void cThrownSnowballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) -{ - Destroy(); -} - - - - - -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 : - -cExpBottleEntity::cExpBottleEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : -super(pkExpBottle, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) -{ - SetSpeed(a_Speed); -} - - - - - -void cExpBottleEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) -{ - // 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(); -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cFireworkEntity : - -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) -{ -} - - - - - -void cFireworkEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) -{ - 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)) - { - goto setspeed; - } - - if (m_IsInGround) - { - if (a_Chunk.GetBlock(RelX, POSY_TOINT + 1, RelZ) == E_BLOCK_AIR) - { - m_IsInGround = false; - } - else - { - return; - } - } - else - { - if (a_Chunk.GetBlock(RelX, POSY_TOINT + 1, RelZ) != E_BLOCK_AIR) - { - OnHitSolidBlock(GetPosition(), BLOCK_FACE_YM); - return; - } - } - -setspeed: - AddSpeedY(1); - AddPosition(GetSpeed() * (a_Dt / 1000)); -} - - - - - -void cFireworkEntity::Tick(float a_Dt, cChunk & a_Chunk) -{ - super::Tick(a_Dt, a_Chunk); - - if (m_ExplodeTimer == m_FireworkItem.m_FireworkItem.m_FlightTimeInTicks) - { - m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_FIREWORK_EXPLODE); - Destroy(); - } - - m_ExplodeTimer++; -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cGhastFireballEntity : - -cGhastFireballEntity::cGhastFireballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : - super(pkGhastFireball, a_Creator, a_X, a_Y, a_Z, 1, 1) -{ - SetSpeed(a_Speed); - SetGravity(0); -} - - - - - -void cGhastFireballEntity::Explode(int a_BlockX, int a_BlockY, int a_BlockZ) -{ - m_World->DoExplosionAt(1, a_BlockX, a_BlockY, a_BlockZ, true, esGhastFireball, this); -} - - - - - -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)); -} - - - - - -void cGhastFireballEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) -{ - Destroy(); - Explode((int)floor(a_HitPos.x), (int)floor(a_HitPos.y), (int)floor(a_HitPos.z)); -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cFireChargeEntity : - -cFireChargeEntity::cFireChargeEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : - super(pkFireCharge, a_Creator, a_X, a_Y, a_Z, 0.3125, 0.3125) -{ - SetSpeed(a_Speed); - SetGravity(0); -} - - - - - -void cFireChargeEntity::Explode(int a_BlockX, int a_BlockY, int a_BlockZ) -{ - if (m_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ) == E_BLOCK_AIR) - { - m_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_FIRE, 1); - } -} - - - - - -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)); -} - - - - - -void cFireChargeEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) -{ - Destroy(); - Explode((int)floor(a_HitPos.x), (int)floor(a_HitPos.y), (int)floor(a_HitPos.z)); - - // TODO: Some entities are immune to hits - a_EntityHit.StartBurning(5 * 20); // 5 seconds of burning -} - - - - diff --git a/src/Entities/ProjectileEntity.h b/src/Entities/ProjectileEntity.h index efb7ae783..ae06b072f 100644 --- a/src/Entities/ProjectileEntity.h +++ b/src/Entities/ProjectileEntity.h @@ -94,300 +94,4 @@ protected: virtual void HandlePhysics(float a_Dt, cChunk & a_Chunk) override; virtual void SpawnOn(cClientHandle & a_Client) override; - // tolua_begin -} ; - - - - - -class cArrowEntity : - public cProjectileEntity -{ - typedef cProjectileEntity super; - -public: - /// Determines when the arrow can be picked up (depending on player gamemode). Corresponds to the MCA file "pickup" field - enum ePickupState - { - psNoPickup = 0, - psInSurvivalOrCreative = 1, - psInCreative = 2, - } ; - - // tolua_end - - CLASS_PROTODEF(cArrowEntity); - - /// Creates a new arrow with psNoPickup state and default damage modifier coeff - cArrowEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); - - /// Creates a new arrow as shot by a player, initializes it from the player object - cArrowEntity(cPlayer & a_Player, double a_Force); - - // tolua_begin - - /// Returns whether the arrow can be picked up by players - ePickupState GetPickupState(void) const { return m_PickupState; } - - /// Sets a new pickup state - void SetPickupState(ePickupState a_PickupState) { m_PickupState = a_PickupState; } - - /// Returns the damage modifier coeff. - double GetDamageCoeff(void) const { return m_DamageCoeff; } - - /// Sets the damage modifier coeff - void SetDamageCoeff(double a_DamageCoeff) { m_DamageCoeff = a_DamageCoeff; } - - /// Returns true if the specified player can pick the arrow up - bool CanPickup(const cPlayer & a_Player) const; - - /// Returns true if the arrow is set as critical - bool IsCritical(void) const { return m_IsCritical; } - - /// Sets the IsCritical flag - void SetIsCritical(bool a_IsCritical) { m_IsCritical = a_IsCritical; } - - // tolua_end - -protected: - - /// Determines when the arrow can be picked up by players - ePickupState m_PickupState; - - /// The coefficient applied to the damage that the arrow will deal, based on the bow enchantment. 2.0 for normal arrow - double m_DamageCoeff; - - /// If true, the arrow deals more damage - bool m_IsCritical; - - /// Timer for pickup collection animation or five minute timeout - float m_Timer; - - /// Timer for client arrow position confirmation via TeleportEntity - float m_HitGroundTimer; - - /// If true, the arrow is in the process of being collected - don't go to anyone else - bool m_bIsCollected; - - /// Stores the block position that arrow is lodged into, sets m_IsInGround to false if it becomes air - Vector3i m_HitBlockPos; - - // cProjectileEntity overrides: - 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; - - // tolua_begin -} ; - - - - - -class cThrownEggEntity : - public cProjectileEntity -{ - typedef cProjectileEntity super; - -public: - - // tolua_end - - CLASS_PROTODEF(cThrownEggEntity); - - cThrownEggEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); - -protected: - - // tolua_end - - // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; - - // tolua_begin - -} ; - - - - - -class cThrownEnderPearlEntity : - public cProjectileEntity -{ - typedef cProjectileEntity super; - -public: - - // tolua_end - - CLASS_PROTODEF(cThrownEnderPearlEntity); - - cThrownEnderPearlEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); - -protected: - - // tolua_end - - // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; - - // tolua_begin - -} ; - - - - - -class cThrownSnowballEntity : - public cProjectileEntity -{ - typedef cProjectileEntity super; - -public: - - // tolua_end - - CLASS_PROTODEF(cThrownSnowballEntity); - - cThrownSnowballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); - -protected: - - // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; - virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override; - - // tolua_begin - -} ; - - - - - -class cExpBottleEntity : - public cProjectileEntity -{ - typedef cProjectileEntity super; - -public: - - // tolua_end - - CLASS_PROTODEF(cExpBottleEntity); - - cExpBottleEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); - -protected: - - // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; - - // tolua_begin - -}; - - - - - -class cFireworkEntity : - public cProjectileEntity -{ - typedef cProjectileEntity super; - -public: - - // tolua_end - - CLASS_PROTODEF(cFireworkEntity); - - 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 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 - -}; - - - - - -class cGhastFireballEntity : - public cProjectileEntity -{ - typedef cProjectileEntity super; - -public: - - // tolua_end - - CLASS_PROTODEF(cGhastFireballEntity); - - cGhastFireballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); - -protected: - - void Explode(int a_BlockX, int a_BlockY, int a_BlockZ); - - // cProjectileEntity overrides: - 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 - - // tolua_begin - -} ; - - - - - -class cFireChargeEntity : - public cProjectileEntity -{ - typedef cProjectileEntity super; - -public: - - // tolua_end - - CLASS_PROTODEF(cFireChargeEntity); - - cFireChargeEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); - -protected: - - void Explode(int a_BlockX, int a_BlockY, int a_BlockZ); - - // cProjectileEntity overrides: - virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; - virtual void OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) override; - - // tolua_begin - -} ; - - - - -// tolua_end - - - +} ; // tolua_export diff --git a/src/Entities/TNTEntity.cpp b/src/Entities/TNTEntity.cpp index 02f31f5bb..fd9a4e7ac 100644 --- a/src/Entities/TNTEntity.cpp +++ b/src/Entities/TNTEntity.cpp @@ -30,8 +30,6 @@ cTNTEntity::cTNTEntity(const Vector3d & a_Pos, int a_FuseTicks) : void cTNTEntity::SpawnOn(cClientHandle & a_ClientHandle) { a_ClientHandle.SendSpawnObject(*this, 50, 1, 0, 0); // 50 means TNT - m_bDirtyPosition = false; - m_bDirtySpeed = false; m_bDirtyOrientation = false; m_bDirtyHead = false; } diff --git a/src/Entities/ThrownEggEntity.cpp b/src/Entities/ThrownEggEntity.cpp new file mode 100644 index 000000000..224019091 --- /dev/null +++ b/src/Entities/ThrownEggEntity.cpp @@ -0,0 +1,59 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "ThrownEggEntity.h" +#include "../World.h" + + + + + +cThrownEggEntity::cThrownEggEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : + super(pkEgg, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) +{ + SetSpeed(a_Speed); +} + + + + + +void cThrownEggEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) +{ + TrySpawnChicken(a_HitPos); + + Destroy(); +} + + + + + +void cThrownEggEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) +{ + int TotalDamage = 0; + // TODO: If entity is Ender Crystal, destroy it + + TrySpawnChicken(a_HitPos); + a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1); + + Destroy(true); +} + + + + + +void cThrownEggEntity::TrySpawnChicken(const Vector3d & a_HitPos) +{ + if (m_World->GetTickRandomNumber(7) == 1) + { + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + } + else if (m_World->GetTickRandomNumber(32) == 1) + { + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + } +} diff --git a/src/Entities/ThrownEggEntity.h b/src/Entities/ThrownEggEntity.h new file mode 100644 index 000000000..5ba8f051b --- /dev/null +++ b/src/Entities/ThrownEggEntity.h @@ -0,0 +1,37 @@ +// +// ThrownEggEntity.h +// + +#pragma once + +#include "ProjectileEntity.h" + + + + + +// tolua_begin + +class cThrownEggEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cThrownEggEntity); + + cThrownEggEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); + +protected: + + // cProjectileEntity overrides: + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; + virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override; + + // Randomly decides whether to spawn a chicken where the egg lands. + void TrySpawnChicken(const Vector3d & a_HitPos); + +} ; // tolua_export diff --git a/src/Entities/ThrownEnderPearlEntity.cpp b/src/Entities/ThrownEnderPearlEntity.cpp new file mode 100644 index 000000000..c37161145 --- /dev/null +++ b/src/Entities/ThrownEnderPearlEntity.cpp @@ -0,0 +1,54 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "ThrownEnderPearlEntity.h" + + + + + +cThrownEnderPearlEntity::cThrownEnderPearlEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : + super(pkEnderPearl, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) +{ + SetSpeed(a_Speed); +} + + + + + +void cThrownEnderPearlEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) +{ + // TODO: Tweak a_HitPos based on block face. + TeleportCreator(a_HitPos); + + Destroy(); +} + + + + + +void cThrownEnderPearlEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) +{ + int TotalDamage = 0; + // TODO: If entity is Ender Crystal, destroy it + + TeleportCreator(a_HitPos); + a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1); + + Destroy(true); +} + + + + + +void cThrownEnderPearlEntity::TeleportCreator(const Vector3d & a_HitPos) +{ + // Teleport the creator here, make them take 5 damage: + if (m_Creator != NULL) + { + m_Creator->TeleportToCoords(a_HitPos.x + 0.5, a_HitPos.y + 1.7, a_HitPos.z + 0.5); + m_Creator->TakeDamage(dtEnderPearl, this, 5, 0); + } +} diff --git a/src/Entities/ThrownEnderPearlEntity.h b/src/Entities/ThrownEnderPearlEntity.h new file mode 100644 index 000000000..ddee5babe --- /dev/null +++ b/src/Entities/ThrownEnderPearlEntity.h @@ -0,0 +1,37 @@ +// +// ThrownEnderPearlEntity.h +// + +#pragma once + +#include "ProjectileEntity.h" + + + + + +// tolua_begin + +class cThrownEnderPearlEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cThrownEnderPearlEntity); + + cThrownEnderPearlEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); + +protected: + + // cProjectileEntity overrides: + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; + virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override; + + // Teleports the creator where the ender pearl lands. + void TeleportCreator(const Vector3d & a_HitPos); + +} ; // tolua_export diff --git a/src/Entities/ThrownSnowballEntity.cpp b/src/Entities/ThrownSnowballEntity.cpp new file mode 100644 index 000000000..427f630f7 --- /dev/null +++ b/src/Entities/ThrownSnowballEntity.cpp @@ -0,0 +1,48 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "ThrownSnowballEntity.h" +#include "../World.h" + + + + + +cThrownSnowballEntity::cThrownSnowballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : + super(pkSnowball, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) +{ + SetSpeed(a_Speed); +} + + + + + +void cThrownSnowballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) +{ + Destroy(); +} + + + + + +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; + } + } + // TODO: If entity is Ender Crystal, destroy it + a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1); + + Destroy(true); +} diff --git a/src/Entities/ThrownSnowballEntity.h b/src/Entities/ThrownSnowballEntity.h new file mode 100644 index 000000000..a09512e37 --- /dev/null +++ b/src/Entities/ThrownSnowballEntity.h @@ -0,0 +1,34 @@ +// +// ThrownSnowballEntity.h +// + +#pragma once + +#include "ProjectileEntity.h" + + + + + +// tolua_begin + +class cThrownSnowballEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cThrownSnowballEntity); + + cThrownSnowballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); + +protected: + + // cProjectileEntity overrides: + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; + virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override; + +} ; // tolua_export |