summaryrefslogtreecommitdiffstats
path: root/src/Entities
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Entities/Entity.cpp11
-rw-r--r--src/Entities/Pawn.cpp184
-rw-r--r--src/Entities/Pawn.h8
-rw-r--r--src/Entities/Player.cpp97
-rw-r--r--src/Entities/Player.h2
5 files changed, 193 insertions, 109 deletions
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp
index f44dbe27c..4c84df1f4 100644
--- a/src/Entities/Entity.cpp
+++ b/src/Entities/Entity.cpp
@@ -1029,16 +1029,17 @@ void cEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
NextSpeed.z = 0.0f;
}
- if (Tracer.HitNormal.y == 1.0f) // Hit BLOCK_FACE_YP, we are on the ground
- {
- m_bOnGround = true;
- }
-
// 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;
+
+ if (Tracer.HitNormal.y == 1.0f) // Hit BLOCK_FACE_YP, we are on the ground
+ {
+ m_bOnGround = true;
+ NextPos.y = FloorC(NextPos.y); // we clamp the height to 0 cos otherwise we'll constantly be slightly above the block
+ }
}
else
{
diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp
index ca2d413df..6b404f7e0 100644
--- a/src/Entities/Pawn.cpp
+++ b/src/Entities/Pawn.cpp
@@ -2,17 +2,22 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Pawn.h"
+#include "Player.h"
#include "../World.h"
#include "../Bindings/PluginManager.h"
#include "BoundingBox.h"
+#include "../Blocks/BlockHandler.h"
+#include "EffectID.h"
cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height) :
- super(a_EntityType, 0, 0, 0, a_Width, a_Height)
- , m_EntityEffects(tEffectMap())
+ super(a_EntityType, 0, 0, 0, a_Width, a_Height),
+ m_EntityEffects(tEffectMap()),
+ m_LastGroundHeight(0),
+ m_bTouchGround(false)
{
SetGravity(-32.0f);
SetAirDrag(0.02f);
@@ -79,8 +84,10 @@ void cPawn::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
} Callback(this);
m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), GetWidth(), GetHeight()), Callback);
-
+
super::Tick(a_Dt, a_Chunk);
+
+ HandleFalling();
}
@@ -185,3 +192,174 @@ void cPawn::ClearEntityEffects()
+
+
+void cPawn::HandleFalling(void)
+{
+ /* Not pretty looking, and is more suited to wherever server-sided collision detection is implemented.
+ The following condition sets on-ground-ness if
+ The player isn't swimming or flying (client hardcoded conditions) and
+ they're on a block (Y is exact) - ensure any they could be standing on (including on the edges) is solid or
+ they're on a slab (Y significand is 0.5) - ditto with slab check
+ they're on a snow layer (Y divisible by 0.125) - ditto with snow layer check
+ */
+
+ static const auto HalfWidth = GetWidth() / 2;
+ static const auto EPS = 0.0001;
+
+ /* Since swimming is decided in a tick and is asynchronous to this, we have to check for dampeners ourselves.
+ The behaviour as of 1.8.9 is the following:
+ - Landing in water alleviates all fall damage
+ - Passing through any liquid (water + lava) and cobwebs "slows" the player down,
+ i.e. resets the fall distance to that block, but only after checking for fall damage
+ (this means that plummeting into lava will still kill the player via fall damage, although cobwebs
+ will slow players down enough to have multiple updates that keep them alive)
+ - Slime blocks reverse falling velocity, unless it's a crouching player, in which case they act as standard blocks.
+ They also reset the topmost point of the damage calculation with each bounce,
+ so if the block is removed while the player is bouncing or crouches after a bounce, the last bounce's zenith is considered as fall damage.
+
+ With this in mind, we first check the block at the player's feet, then the one below that (because fences),
+ and decide which behaviour we want to go with.
+ */
+ BLOCKTYPE BlockAtFoot = (cChunkDef::IsValidHeight(POSY_TOINT)) ? GetWorld()->GetBlock(POS_TOINT) : E_BLOCK_AIR;
+
+ /* We initialize these with what the foot is really IN, because for sampling we will move down with the epsilon above */
+ bool IsFootInWater = IsBlockWater(BlockAtFoot);
+ bool IsFootInLiquid = IsFootInWater || IsBlockLava(BlockAtFoot) || (BlockAtFoot == E_BLOCK_COBWEB); // okay so cobweb is not _technically_ a liquid...
+ bool IsFootOnSlimeBlock = false;
+
+ /* The "cross" we sample around to account for the player width/girth */
+ static const struct
+ {
+ int x, z;
+ } CrossSampleCoords[] =
+ {
+ { 0, 0 },
+ { 1, 0 },
+ { -1, 0 },
+ { 0, 1 },
+ { 0, -1 },
+ };
+
+ /* The blocks we're interested in relative to the player to account for larger than 1 blocks.
+ This can be extended to do additional checks in case there are blocks that are represented as one block
+ in memory but have a hitbox larger than 1 (like fences) */
+ static const struct
+ {
+ int x, y, z;
+ } BlockSampleOffsets[] =
+ {
+ { 0, 0, 0 },
+ { 0, -1, 0 },
+ };
+
+ /* Here's the rough outline of how this mechanism works:
+ We take the player's pointlike position (sole of feet), and expand it into a crosslike shape.
+ If any of the five points hit a block, we consider the player to be "on" (or "in") the ground. */
+ bool OnGround = false;
+ for (size_t i = 0; i < ARRAYCOUNT(CrossSampleCoords); i++)
+ {
+ /* We calculate from the player's position, one of the cross-offsets above, and we move it down slightly so it's beyond inaccuracy.
+ The added advantage of this method is that if the player is simply standing on the floor,
+ the point will move into the next block, and the floor() will retrieve that instead of air. */
+ Vector3d CrossTestPosition = GetPosition() + Vector3d(CrossSampleCoords[i].x * HalfWidth, -EPS, CrossSampleCoords[i].z * HalfWidth);
+
+ /* We go through the blocks that we consider "relevant" */
+ for (size_t j = 0; j < ARRAYCOUNT(BlockSampleOffsets); j++)
+ {
+ Vector3i BlockTestPosition = CrossTestPosition.Floor() + Vector3i(BlockSampleOffsets[j].x, BlockSampleOffsets[j].y, BlockSampleOffsets[j].z);
+
+ if (!cChunkDef::IsValidHeight(BlockTestPosition.y))
+ {
+ continue;
+ }
+
+ BLOCKTYPE Block = GetWorld()->GetBlock(BlockTestPosition);
+ NIBBLETYPE BlockMeta = GetWorld()->GetBlockMeta(BlockTestPosition);
+
+ /* we do the cross-shaped sampling to check for water / liquids, but only on our level because water blocks are never bigger than unit voxels */
+ if (j == 0)
+ {
+ IsFootInWater |= IsBlockWater(Block);
+ IsFootInLiquid |= IsFootInWater || IsBlockLava(Block) || (Block == E_BLOCK_COBWEB); // okay so cobweb is not _technically_ a liquid...
+ IsFootOnSlimeBlock |= (Block == E_BLOCK_SLIME_BLOCK);
+ }
+
+ /* If the block is solid, and the blockhandler confirms the block to be inside, we're officially on the ground. */
+ if ((cBlockInfo::IsSolid(Block)) && (cBlockInfo::GetHandler(Block)->IsInsideBlock(CrossTestPosition - BlockTestPosition, Block, BlockMeta)))
+ {
+ OnGround = true;
+ }
+ }
+ }
+
+ // Update the ground height to have the highest position of the player (i.e. jumping up adds to the eventual fall damage)
+ if (!OnGround)
+ {
+ m_LastGroundHeight = std::max(m_LastGroundHeight, GetPosY());
+ }
+
+ /* So here's the use of the rules above: */
+ /* 1. Falling in water absorbs all fall damage */
+ bool FallDamageAbsorbed = IsFootInWater;
+
+ /* 2. Falling in liquid (lava, water, cobweb) or hitting a slime block resets the "fall zenith".
+ Note: Even though the pawn bounces back with no damage after hitting the slime block,
+ the "fall zenith" will continue to increase back up when flying upwards - which is good */
+ bool FallDistanceReset = IsFootOnSlimeBlock || IsFootInLiquid;
+ bool IsFlying = false;
+ bool ShouldBounceOnSlime = true;
+
+ if (GetEntityType() == eEntityType::etPlayer)
+ {
+ cPlayer * Player = static_cast<cPlayer *>(this);
+ IsFlying = Player->IsFlying();
+
+ /* 3. If the player is flying or climbing, absorb fall damage */
+ FallDamageAbsorbed |= IsFlying || Player->IsClimbing();
+
+ /* 4. If the player is about to bounce on a slime block and is not crouching, absorb all fall damage */
+ ShouldBounceOnSlime = !Player->IsCrouched();
+ FallDamageAbsorbed |= (IsFootOnSlimeBlock && ShouldBounceOnSlime);
+ }
+ else
+ {
+ /* 5. Bouncing on a slime block absorbs all fall damage */
+ FallDamageAbsorbed |= IsFootOnSlimeBlock;
+ }
+
+ /* If the player is not crouching or is not a player, shoot them back up.
+ NOTE: this will only work in some cases; should be done in HandlePhysics() */
+ if (IsFootOnSlimeBlock && ShouldBounceOnSlime)
+ {
+ SetSpeedY(-GetSpeedY());
+ }
+
+ if (!IsFlying && OnGround)
+ {
+ auto Damage = static_cast<int>(m_LastGroundHeight - GetPosY() - 3.0);
+ if ((Damage > 0) && !FallDamageAbsorbed)
+ {
+ TakeDamage(dtFalling, nullptr, Damage, Damage, 0);
+
+ // Fall particles
+ int ParticleSize = static_cast<int>((std::min(15, Damage) - 1.f) * ((50.f - 20.f) / (15.f - 1.f)) + 20.f);
+ GetWorld()->BroadcastSoundParticleEffect(EffectID::PARTICLE_FALL_PARTICLES, POSX_TOINT, POSY_TOINT - 1, POSZ_TOINT, ParticleSize);
+ }
+
+ m_bTouchGround = true;
+ m_LastGroundHeight = GetPosY();
+ }
+ else
+ {
+ m_bTouchGround = false;
+ }
+
+ /* Note: it is currently possible to fall through lava and still die from fall damage
+ because of the client skipping an update about the lava block. This can only be resolved by
+ somehow integrating these above checks into the tracer in HandlePhysics. */
+ if (FallDistanceReset)
+ {
+ m_LastGroundHeight = GetPosY();
+ }
+}
diff --git a/src/Entities/Pawn.h b/src/Entities/Pawn.h
index 67878f699..0ceb1073e 100644
--- a/src/Entities/Pawn.h
+++ b/src/Entities/Pawn.h
@@ -25,7 +25,8 @@ public:
virtual bool IsFireproof(void) const override;
virtual void HandleAir(void) override;
-
+ virtual void HandleFalling(void);
+
// tolua_begin
/** Applies an entity effect
@@ -49,12 +50,15 @@ public:
/** Removes all currently applied entity effects (used when drinking milk) */
void ClearEntityEffects(void);
-
+
// tolua_end
protected:
typedef std::map<cEntityEffect::eType, cEntityEffect *> tEffectMap;
tEffectMap m_EntityEffects;
+
+ double m_LastGroundHeight;
+ bool m_bTouchGround;
} ; // tolua_export
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index 3bea60af7..33ded6ab9 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -55,8 +55,6 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) :
m_FoodSaturationLevel(5.0),
m_FoodTickTimer(0),
m_FoodExhaustionLevel(0.0),
- m_LastGroundHeight(0),
- m_bTouchGround(false),
m_Stance(0.0),
m_Inventory(*this),
m_EnderChestContents(9, 3),
@@ -451,101 +449,6 @@ void cPlayer::SetTouchGround(bool a_bTouchGround)
return;
}
- /* Not pretty looking, and is more suited to wherever server-sided collision detection is implemented.
- The following condition sets on-ground-ness if
- The player isn't swimming or flying (client hardcoded conditions) and
- they're on a block (Y is exact) - ensure any they could be standing on (including on the edges) is solid or
- they're on a slab (Y significand is 0.5) - ditto with slab check
- they're on a snow layer (Y divisible by 0.125) - ditto with snow layer check
- */
-
- static const auto HalfWidth = GetWidth() / 2;
- static const auto EPS = 0.0001;
-
- /* Since swimming is decided in a tick and is asynchronous to this, we have to check for dampeners ourselves.
- The behaviour as of 1.8.8 is the following:
- - Landing in water alleviates all fall damage
- - Passing through any liquid (water + lava) and cobwebs "slows" the player down,
- i.e. resets the fall distance to that block, but only after checking for fall damage
- (this means that plummeting into lava will still kill the player via fall damage, although cobwebs
- will slow players down enough to have multiple updates that keep them alive)
-
- With this in mind, we first check the block at the player's feet, and decide which behaviour we want to go with.
- */
- BLOCKTYPE BlockAtFoot = (cChunkDef::IsValidHeight(GetPosY())) ? GetWorld()->GetBlock(POS_TOINT) : E_BLOCK_AIR;
- bool IsFootInWater = IsBlockWater(BlockAtFoot);
- bool IsFootInLiquid = IsFootInWater || IsBlockLava(BlockAtFoot) || (BlockAtFoot == E_BLOCK_COBWEB); // okay so cobweb is not _technically_ a liquid...
-
- if (
- !IsFlying() &&
- (
- (
- (cChunkDef::IsValidHeight(GetPosY()) && ((GetPosY() - POSY_TOINT) <= EPS)) &&
- (
- cBlockInfo::IsSolid(GetWorld()->GetBlock((GetPosition() + Vector3d(0, -1, 0)).Floor())) ||
- cBlockInfo::IsSolid(GetWorld()->GetBlock((GetPosition() + Vector3d(HalfWidth, -1, 0)).Floor())) ||
- cBlockInfo::IsSolid(GetWorld()->GetBlock((GetPosition() + Vector3d(-HalfWidth, -1, 0)).Floor())) ||
- cBlockInfo::IsSolid(GetWorld()->GetBlock((GetPosition() + Vector3d(0, -1, HalfWidth)).Floor())) ||
- cBlockInfo::IsSolid(GetWorld()->GetBlock((GetPosition() + Vector3d(0, -1, -HalfWidth)).Floor()))
- )
- ) ||
- (
- (cChunkDef::IsValidHeight(GetPosY()) && (GetPosY() >= POSY_TOINT) && ((GetPosY() - (POSY_TOINT + 0.5)) <= EPS)) &&
- (
- cBlockSlabHandler::IsAnySlabType(GetWorld()->GetBlock((GetPosition() + Vector3d(0, 0, 0)).Floor())) ||
- cBlockSlabHandler::IsAnySlabType(GetWorld()->GetBlock((GetPosition() + Vector3d(HalfWidth, 0, 0)).Floor())) ||
- cBlockSlabHandler::IsAnySlabType(GetWorld()->GetBlock((GetPosition() + Vector3d(-HalfWidth, 0, 0)).Floor())) ||
- cBlockSlabHandler::IsAnySlabType(GetWorld()->GetBlock((GetPosition() + Vector3d(0, 0, HalfWidth)).Floor())) ||
- cBlockSlabHandler::IsAnySlabType(GetWorld()->GetBlock((GetPosition() + Vector3d(0, 0, -HalfWidth)).Floor()))
- )
- ) ||
- (
- (cChunkDef::IsValidHeight(GetPosY()) && (fmod(GetPosY(), 0.125) <= EPS)) &&
- (
- (GetWorld()->GetBlock((GetPosition() + Vector3d(0, 0, 0)).Floor()) == E_BLOCK_SNOW) ||
- (GetWorld()->GetBlock((GetPosition() + Vector3d(HalfWidth, 0, 0)).Floor()) == E_BLOCK_SNOW) ||
- (GetWorld()->GetBlock((GetPosition() + Vector3d(-HalfWidth, 0, 0)).Floor()) == E_BLOCK_SNOW) ||
- (GetWorld()->GetBlock((GetPosition() + Vector3d(0, 0, HalfWidth)).Floor()) == E_BLOCK_SNOW) ||
- (GetWorld()->GetBlock((GetPosition() + Vector3d(0, 0, -HalfWidth)).Floor()) == E_BLOCK_SNOW)
- )
- )
- )
- )
- {
- auto Damage = static_cast<int>(m_LastGroundHeight - GetPosY() - 3.0);
- if ((Damage > 0) && !IsFootInWater)
- {
- // cPlayer makes sure damage isn't applied in creative, no need to check here
- TakeDamage(dtFalling, nullptr, Damage, Damage, 0);
-
- // Fall particles
- Damage = std::min(15, Damage);
- GetClientHandle()->SendParticleEffect(
- "blockdust",
- GetPosition(),
- { 0, 0, 0 },
- (Damage - 1.f) * ((0.3f - 0.1f) / (15.f - 1.f)) + 0.1f, // Map damage (1 - 15) to particle speed (0.1 - 0.3)
- static_cast<int>((Damage - 1.f) * ((50.f - 20.f) / (15.f - 1.f)) + 20.f), // Map damage (1 - 15) to particle quantity (20 - 50)
- { { GetWorld()->GetBlock(POS_TOINT - Vector3i(0, 1, 0)), 0 } }
- );
- }
-
- m_bTouchGround = true;
- m_LastGroundHeight = GetPosY();
- }
- else
- {
- m_bTouchGround = false;
- }
-
- /* Note: it is currently possible to fall through lava and still die from fall damage
- because of the client skipping an update about the lava block. This can only be resolved by
- interpolating between positions. */
- if (IsFlying() || IsFootInLiquid || IsClimbing())
- {
- m_LastGroundHeight = GetPosY();
- }
-
UNUSED(a_bTouchGround);
/* Unfortunately whatever the reason, there are still desyncs in on-ground status between the client and server. For example:
1. Walking off a ledge (whatever height)
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index 179eb02b7..bff9599f7 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -550,8 +550,6 @@ protected:
/** A "buffer" which adds up hunger before it is substracted from m_FoodSaturationLevel or m_FoodLevel. Each action adds a little */
double m_FoodExhaustionLevel;
- double m_LastGroundHeight;
- bool m_bTouchGround;
double m_Stance;
/** Stores the player's inventory, consisting of crafting grid, hotbar, and main slots */