Merge pull request #2638 from Gargaj/master
Implement block heights + adapt ground checks
This commit is contained in:
commit
1e5ba8a1bb
@ -399,6 +399,7 @@ g_APIDesc =
|
|||||||
CanBeTerraformed = { Params = "Type", Return = "bool", Notes = "(STATIC) Returns true if the block is suitable to be changed by a generator" },
|
CanBeTerraformed = { Params = "Type", Return = "bool", Notes = "(STATIC) Returns true if the block is suitable to be changed by a generator" },
|
||||||
FullyOccupiesVoxel = { Params = "Type", Return = "bool", Notes = "(STATIC) Returns whether the specified block fully occupies its voxel." },
|
FullyOccupiesVoxel = { Params = "Type", Return = "bool", Notes = "(STATIC) Returns whether the specified block fully occupies its voxel." },
|
||||||
Get = { Params = "Type", Return = "{{cBlockInfo}}", Notes = "(STATIC) Returns the {{cBlockInfo}} structure for the specified type." },
|
Get = { Params = "Type", Return = "{{cBlockInfo}}", Notes = "(STATIC) Returns the {{cBlockInfo}} structure for the specified type." },
|
||||||
|
GetBlockHeight = { Params = "Type", Return = "number", Notes = "(STATIC) Returns the block's hitbox height." },
|
||||||
GetLightValue = { Params = "Type", Return = "number", Notes = "(STATIC) Returns how much light the specified block emits on its own." },
|
GetLightValue = { Params = "Type", Return = "number", Notes = "(STATIC) Returns how much light the specified block emits on its own." },
|
||||||
GetPlaceSound = { Params = "Type", Return = "", Notes = "(STATIC) Returns the name of the sound that is played when placing the block." },
|
GetPlaceSound = { Params = "Type", Return = "", Notes = "(STATIC) Returns the name of the sound that is played when placing the block." },
|
||||||
GetSpreadLightFalloff = { Params = "Type", Return = "number", Notes = "(STATIC) Returns how much light the specified block consumes." },
|
GetSpreadLightFalloff = { Params = "Type", Return = "number", Notes = "(STATIC) Returns how much light the specified block consumes." },
|
||||||
|
@ -584,6 +584,27 @@ void cBlockInfo::Initialize(cBlockInfoArray & a_Info)
|
|||||||
a_Info[E_BLOCK_STONE ].m_CanBeTerraformed = true;
|
a_Info[E_BLOCK_STONE ].m_CanBeTerraformed = true;
|
||||||
|
|
||||||
|
|
||||||
|
// Block heights:
|
||||||
|
a_Info[E_BLOCK_BED ].m_BlockHeight = 0.5625; // 9 pixels
|
||||||
|
a_Info[E_BLOCK_CAKE ].m_BlockHeight = 0.5; // 8 pixels
|
||||||
|
a_Info[E_BLOCK_ENCHANTMENT_TABLE ].m_BlockHeight = 0.75; // 12 pixels
|
||||||
|
a_Info[E_BLOCK_STONE_SLAB ].m_BlockHeight = 0.5;
|
||||||
|
a_Info[E_BLOCK_WOODEN_SLAB ].m_BlockHeight = 0.5;
|
||||||
|
a_Info[E_BLOCK_SNOW ].m_BlockHeight = 0.125; // one layer is 1 / 8 (2 pixels) tall
|
||||||
|
a_Info[E_BLOCK_ACACIA_FENCE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_ACACIA_FENCE_GATE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_BIRCH_FENCE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_BIRCH_FENCE_GATE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_DARK_OAK_FENCE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_DARK_OAK_FENCE_GATE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_FENCE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_OAK_FENCE_GATE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_JUNGLE_FENCE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_JUNGLE_FENCE_GATE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_SPRUCE_FENCE ].m_BlockHeight = 1.5;
|
||||||
|
a_Info[E_BLOCK_SPRUCE_FENCE_GATE ].m_BlockHeight = 1.5;
|
||||||
|
|
||||||
|
|
||||||
// Block place sounds:
|
// Block place sounds:
|
||||||
a_Info[E_BLOCK_STONE ].m_PlaceSound = "dig.stone";
|
a_Info[E_BLOCK_STONE ].m_PlaceSound = "dig.stone";
|
||||||
a_Info[E_BLOCK_GRASS ].m_PlaceSound = "dig.grass";
|
a_Info[E_BLOCK_GRASS ].m_PlaceSound = "dig.grass";
|
||||||
|
@ -61,6 +61,9 @@ public:
|
|||||||
/** Can a finisher change it? */
|
/** Can a finisher change it? */
|
||||||
bool m_CanBeTerraformed;
|
bool m_CanBeTerraformed;
|
||||||
|
|
||||||
|
/** Block height */
|
||||||
|
float m_BlockHeight;
|
||||||
|
|
||||||
/** Sound when placing this block */
|
/** Sound when placing this block */
|
||||||
AString m_PlaceSound;
|
AString m_PlaceSound;
|
||||||
|
|
||||||
@ -80,6 +83,7 @@ public:
|
|||||||
inline static bool IsSolid (BLOCKTYPE a_Type) { return Get(a_Type).m_IsSolid; }
|
inline static bool IsSolid (BLOCKTYPE a_Type) { return Get(a_Type).m_IsSolid; }
|
||||||
inline static bool FullyOccupiesVoxel (BLOCKTYPE a_Type) { return Get(a_Type).m_FullyOccupiesVoxel; }
|
inline static bool FullyOccupiesVoxel (BLOCKTYPE a_Type) { return Get(a_Type).m_FullyOccupiesVoxel; }
|
||||||
inline static bool CanBeTerraformed (BLOCKTYPE a_Type) { return Get(a_Type).m_CanBeTerraformed; }
|
inline static bool CanBeTerraformed (BLOCKTYPE a_Type) { return Get(a_Type).m_CanBeTerraformed; }
|
||||||
|
inline static float GetBlockHeight (BLOCKTYPE a_Type) { return Get(a_Type).m_BlockHeight; }
|
||||||
inline static AString GetPlaceSound (BLOCKTYPE a_Type) { return Get(a_Type).m_PlaceSound; }
|
inline static AString GetPlaceSound (BLOCKTYPE a_Type) { return Get(a_Type).m_PlaceSound; }
|
||||||
|
|
||||||
// tolua_end
|
// tolua_end
|
||||||
@ -101,6 +105,7 @@ protected:
|
|||||||
, m_IsSolid(true)
|
, m_IsSolid(true)
|
||||||
, m_FullyOccupiesVoxel(false)
|
, m_FullyOccupiesVoxel(false)
|
||||||
, m_CanBeTerraformed(false)
|
, m_CanBeTerraformed(false)
|
||||||
|
, m_BlockHeight(1.0)
|
||||||
, m_PlaceSound("")
|
, m_PlaceSound("")
|
||||||
, m_Handler(nullptr)
|
, m_Handler(nullptr)
|
||||||
{}
|
{}
|
||||||
|
@ -553,6 +553,16 @@ bool cBlockHandler::DoesDropOnUnsuitable(void)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* default functionality: only test for height, since we assume full voxels with varying height */
|
||||||
|
bool cBlockHandler::IsInsideBlock(const Vector3d & a_Position, const BLOCKTYPE a_BlockType, const NIBBLETYPE a_BlockMeta)
|
||||||
|
{
|
||||||
|
return a_Position.y < cBlockInfo::GetBlockHeight(a_BlockType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void cBlockHandler::Check(cChunkInterface & a_ChunkInterface, cBlockPluginInterface & a_PluginInterface, int a_RelX, int a_RelY, int a_RelZ, cChunk & a_Chunk)
|
void cBlockHandler::Check(cChunkInterface & a_ChunkInterface, cBlockPluginInterface & a_PluginInterface, int a_RelX, int a_RelY, int a_RelZ, cChunk & a_Chunk)
|
||||||
{
|
{
|
||||||
if (!CanBeAt(a_ChunkInterface, a_RelX, a_RelY, a_RelZ, a_Chunk))
|
if (!CanBeAt(a_ChunkInterface, a_RelX, a_RelY, a_RelZ, a_Chunk))
|
||||||
|
@ -125,6 +125,10 @@ public:
|
|||||||
Default: true */
|
Default: true */
|
||||||
virtual bool DoesDropOnUnsuitable(void);
|
virtual bool DoesDropOnUnsuitable(void);
|
||||||
|
|
||||||
|
/** Tests if a_Position is inside the block where a_Position is relative to the origin of the block
|
||||||
|
Note that this is considered from a "top-down" perspective i.e. empty spaces on the bottom of a block don't matter */
|
||||||
|
virtual bool IsInsideBlock(const Vector3d & a_Position, const BLOCKTYPE a_BlockType, const NIBBLETYPE a_BlockMeta);
|
||||||
|
|
||||||
/** Called when one of the neighbors gets set; equivalent to MC block update.
|
/** Called when one of the neighbors gets set; equivalent to MC block update.
|
||||||
By default drops if position no more suitable (CanBeAt(), DoesDropOnUnsuitable(), Drop()),
|
By default drops if position no more suitable (CanBeAt(), DoesDropOnUnsuitable(), Drop()),
|
||||||
and wakes up all simulators on the block. */
|
and wakes up all simulators on the block. */
|
||||||
|
@ -174,6 +174,15 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool IsInsideBlock(const Vector3d & a_Position, const BLOCKTYPE a_BlockType, const NIBBLETYPE a_BlockMeta) override
|
||||||
|
{
|
||||||
|
if (a_BlockMeta & 0x8) // top half
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return cBlockHandler::IsInsideBlock(a_Position, a_BlockType, a_BlockMeta);
|
||||||
|
}
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,6 +88,11 @@ public:
|
|||||||
UNUSED(a_Meta);
|
UNUSED(a_Meta);
|
||||||
return 14;
|
return 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool IsInsideBlock(const Vector3d & a_Position, const BLOCKTYPE a_BlockType, const NIBBLETYPE a_BlockMeta) override
|
||||||
|
{
|
||||||
|
return a_Position.y < (cBlockInfo::GetBlockHeight(a_BlockType) * (a_BlockMeta & 7));
|
||||||
|
}
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,6 +115,38 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** EXCEPTION a.k.a. why is this removed:
|
||||||
|
This collision-detection is actually more accurate than the client, but since the client itself
|
||||||
|
sends inaccurate / sparse data, it's easier to just err on the side of the client and keep the
|
||||||
|
two in sync by assuming that if a player hit ANY of the stair's bounding cube, it counts as the ground. */
|
||||||
|
#if 0
|
||||||
|
bool IsInsideBlock(const Vector3d & a_Position, const BLOCKTYPE a_BlockType, const NIBBLETYPE a_BlockMeta)
|
||||||
|
{
|
||||||
|
if (a_BlockMeta & 0x4) // upside down
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if ((a_BlockMeta & 0x3) == 0) // tall side is east (+X)
|
||||||
|
{
|
||||||
|
return a_Position.y < ((a_Position.x > 0.5) ? 1.0 : 0.5);
|
||||||
|
}
|
||||||
|
else if ((a_BlockMeta & 0x3) == 1) // tall side is west (-X)
|
||||||
|
{
|
||||||
|
return a_Position.y < ((a_Position.x < 0.5) ? 1.0 : 0.5);
|
||||||
|
}
|
||||||
|
else if ((a_BlockMeta & 0x3) == 2) // tall side is south (+Z)
|
||||||
|
{
|
||||||
|
return a_Position.y < ((a_Position.z > 0.5) ? 1.0 : 0.5);
|
||||||
|
}
|
||||||
|
else if ((a_BlockMeta & 0x3) == 3) // tall side is north (-Z)
|
||||||
|
{
|
||||||
|
return a_Position.y < ((a_Position.z < 0.5) ? 1.0 : 0.5);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1029,16 +1029,17 @@ void cEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
NextSpeed.z = 0.0f;
|
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)
|
// 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.Set(Tracer.RealHit.x, Tracer.RealHit.y, Tracer.RealHit.z);
|
||||||
NextPos.x += Tracer.HitNormal.x * 0.1;
|
NextPos.x += Tracer.HitNormal.x * 0.1;
|
||||||
NextPos.y += Tracer.HitNormal.y * 0.05;
|
NextPos.y += Tracer.HitNormal.y * 0.05;
|
||||||
NextPos.z += Tracer.HitNormal.z * 0.1;
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -2,17 +2,22 @@
|
|||||||
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
||||||
|
|
||||||
#include "Pawn.h"
|
#include "Pawn.h"
|
||||||
|
#include "Player.h"
|
||||||
#include "../World.h"
|
#include "../World.h"
|
||||||
#include "../Bindings/PluginManager.h"
|
#include "../Bindings/PluginManager.h"
|
||||||
#include "BoundingBox.h"
|
#include "BoundingBox.h"
|
||||||
|
#include "../Blocks/BlockHandler.h"
|
||||||
|
#include "EffectID.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height) :
|
cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height) :
|
||||||
super(a_EntityType, 0, 0, 0, a_Width, a_Height)
|
super(a_EntityType, 0, 0, 0, a_Width, a_Height),
|
||||||
, m_EntityEffects(tEffectMap())
|
m_EntityEffects(tEffectMap()),
|
||||||
|
m_LastGroundHeight(0),
|
||||||
|
m_bTouchGround(false)
|
||||||
{
|
{
|
||||||
SetGravity(-32.0f);
|
SetGravity(-32.0f);
|
||||||
SetAirDrag(0.02f);
|
SetAirDrag(0.02f);
|
||||||
@ -81,6 +86,8 @@ void cPawn::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), GetWidth(), GetHeight()), Callback);
|
m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), GetWidth(), GetHeight()), Callback);
|
||||||
|
|
||||||
super::Tick(a_Dt, a_Chunk);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,6 +25,7 @@ public:
|
|||||||
|
|
||||||
virtual bool IsFireproof(void) const override;
|
virtual bool IsFireproof(void) const override;
|
||||||
virtual void HandleAir(void) override;
|
virtual void HandleAir(void) override;
|
||||||
|
virtual void HandleFalling(void);
|
||||||
|
|
||||||
// tolua_begin
|
// tolua_begin
|
||||||
|
|
||||||
@ -55,6 +56,9 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
typedef std::map<cEntityEffect::eType, cEntityEffect *> tEffectMap;
|
typedef std::map<cEntityEffect::eType, cEntityEffect *> tEffectMap;
|
||||||
tEffectMap m_EntityEffects;
|
tEffectMap m_EntityEffects;
|
||||||
|
|
||||||
|
double m_LastGroundHeight;
|
||||||
|
bool m_bTouchGround;
|
||||||
} ; // tolua_export
|
} ; // tolua_export
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,8 +55,6 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) :
|
|||||||
m_FoodSaturationLevel(5.0),
|
m_FoodSaturationLevel(5.0),
|
||||||
m_FoodTickTimer(0),
|
m_FoodTickTimer(0),
|
||||||
m_FoodExhaustionLevel(0.0),
|
m_FoodExhaustionLevel(0.0),
|
||||||
m_LastGroundHeight(0),
|
|
||||||
m_bTouchGround(false),
|
|
||||||
m_Stance(0.0),
|
m_Stance(0.0),
|
||||||
m_Inventory(*this),
|
m_Inventory(*this),
|
||||||
m_EnderChestContents(9, 3),
|
m_EnderChestContents(9, 3),
|
||||||
@ -451,101 +449,6 @@ void cPlayer::SetTouchGround(bool a_bTouchGround)
|
|||||||
return;
|
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);
|
UNUSED(a_bTouchGround);
|
||||||
/* Unfortunately whatever the reason, there are still desyncs in on-ground status between the client and server. For example:
|
/* 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)
|
1. Walking off a ledge (whatever height)
|
||||||
|
@ -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 */
|
/** 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_FoodExhaustionLevel;
|
||||||
|
|
||||||
double m_LastGroundHeight;
|
|
||||||
bool m_bTouchGround;
|
|
||||||
double m_Stance;
|
double m_Stance;
|
||||||
|
|
||||||
/** Stores the player's inventory, consisting of crafting grid, hotbar, and main slots */
|
/** Stores the player's inventory, consisting of crafting grid, hotbar, and main slots */
|
||||||
|
@ -77,7 +77,6 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A
|
|||||||
, m_Target(nullptr)
|
, m_Target(nullptr)
|
||||||
, m_PathFinder(a_Width, a_Height)
|
, m_PathFinder(a_Width, a_Height)
|
||||||
, m_PathfinderActivated(false)
|
, m_PathfinderActivated(false)
|
||||||
, m_LastGroundHeight(POSY_TOINT)
|
|
||||||
, m_JumpCoolDown(0)
|
, m_JumpCoolDown(0)
|
||||||
, m_IdleInterval(0)
|
, m_IdleInterval(0)
|
||||||
, m_DestroyTimer(0)
|
, m_DestroyTimer(0)
|
||||||
@ -298,7 +297,6 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetPitchAndYawFromDestination(a_IsFollowingPath);
|
SetPitchAndYawFromDestination(a_IsFollowingPath);
|
||||||
HandleFalling();
|
|
||||||
|
|
||||||
switch (m_EMState)
|
switch (m_EMState)
|
||||||
{
|
{
|
||||||
@ -397,20 +395,8 @@ void cMonster::SetPitchAndYawFromDestination(bool a_IsFollowingPath)
|
|||||||
|
|
||||||
void cMonster::HandleFalling()
|
void cMonster::HandleFalling()
|
||||||
{
|
{
|
||||||
if (m_bOnGround)
|
m_bTouchGround = IsOnGround();
|
||||||
{
|
super::HandleFalling();
|
||||||
int Damage = (m_LastGroundHeight - POSY_TOINT) - 3;
|
|
||||||
|
|
||||||
if (Damage > 0)
|
|
||||||
{
|
|
||||||
TakeDamage(dtFalling, nullptr, Damage, Damage, 0);
|
|
||||||
|
|
||||||
// Fall particles
|
|
||||||
GetWorld()->BroadcastSoundParticleEffect(EffectID::PARTICLE_FALL_PARTICLES, POSX_TOINT, POSY_TOINT - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_LastGroundHeight = POSY_TOINT;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,6 +56,8 @@ public:
|
|||||||
|
|
||||||
virtual void OnRightClicked(cPlayer & a_Player) override;
|
virtual void OnRightClicked(cPlayer & a_Player) override;
|
||||||
|
|
||||||
|
virtual void HandleFalling(void) override;
|
||||||
|
|
||||||
/** Engage pathfinder and tell it to calculate a path to a given position, and move the mobile accordingly
|
/** Engage pathfinder and tell it to calculate a path to a given position, and move the mobile accordingly
|
||||||
Currently, the mob will only start moving to a new position after the position it is currently going to is reached. */
|
Currently, the mob will only start moving to a new position after the position it is currently going to is reached. */
|
||||||
virtual void MoveToPosition(const Vector3d & a_Position); // tolua_export
|
virtual void MoveToPosition(const Vector3d & a_Position); // tolua_export
|
||||||
@ -203,8 +205,6 @@ protected:
|
|||||||
/** Sets the body yaw and head yaw */
|
/** Sets the body yaw and head yaw */
|
||||||
void SetPitchAndYawFromDestination(bool a_IsFollowingPath);
|
void SetPitchAndYawFromDestination(bool a_IsFollowingPath);
|
||||||
|
|
||||||
virtual void HandleFalling(void);
|
|
||||||
int m_LastGroundHeight;
|
|
||||||
int m_JumpCoolDown;
|
int m_JumpCoolDown;
|
||||||
|
|
||||||
std::chrono::milliseconds m_IdleInterval;
|
std::chrono::milliseconds m_IdleInterval;
|
||||||
|
@ -246,6 +246,15 @@ ColourID cBlockHandler::GetMapBaseColourID(NIBBLETYPE a_Meta)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cBlockHandler::IsInsideBlock(const Vector3d & a_Position, const BLOCKTYPE a_BlockType, const NIBBLETYPE a_BlockMeta)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cBlockEntity * cBlockEntity::CreateByBlockType(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World)
|
cBlockEntity * cBlockEntity::CreateByBlockType(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World)
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
Loading…
Reference in New Issue
Block a user