diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index ab3f2c65b..67689a2a5 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -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" }, 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." }, + 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." }, 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." }, diff --git a/src/BlockInfo.cpp b/src/BlockInfo.cpp index 54f11158e..cff5f953a 100644 --- a/src/BlockInfo.cpp +++ b/src/BlockInfo.cpp @@ -584,6 +584,27 @@ void cBlockInfo::Initialize(cBlockInfoArray & a_Info) 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: a_Info[E_BLOCK_STONE ].m_PlaceSound = "dig.stone"; a_Info[E_BLOCK_GRASS ].m_PlaceSound = "dig.grass"; diff --git a/src/BlockInfo.h b/src/BlockInfo.h index 4709f2357..9f562e531 100644 --- a/src/BlockInfo.h +++ b/src/BlockInfo.h @@ -61,6 +61,9 @@ public: /** Can a finisher change it? */ bool m_CanBeTerraformed; + /** Block height */ + float m_BlockHeight; + /** Sound when placing this block */ AString m_PlaceSound; @@ -80,6 +83,7 @@ public: 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 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; } // tolua_end @@ -101,6 +105,7 @@ protected: , m_IsSolid(true) , m_FullyOccupiesVoxel(false) , m_CanBeTerraformed(false) + , m_BlockHeight(1.0) , m_PlaceSound("") , m_Handler(nullptr) {} diff --git a/src/Blocks/BlockHandler.cpp b/src/Blocks/BlockHandler.cpp index bb479db53..0ad0e2242 100644 --- a/src/Blocks/BlockHandler.cpp +++ b/src/Blocks/BlockHandler.cpp @@ -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) { if (!CanBeAt(a_ChunkInterface, a_RelX, a_RelY, a_RelZ, a_Chunk)) diff --git a/src/Blocks/BlockHandler.h b/src/Blocks/BlockHandler.h index 4a7b76019..41fbc5140 100644 --- a/src/Blocks/BlockHandler.h +++ b/src/Blocks/BlockHandler.h @@ -124,7 +124,11 @@ public: /** Returns if this block drops if it gets destroyed by an unsuitable situation. Default: true */ 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. By default drops if position no more suitable (CanBeAt(), DoesDropOnUnsuitable(), Drop()), and wakes up all simulators on the block. */ diff --git a/src/Blocks/BlockSlab.h b/src/Blocks/BlockSlab.h index 19b25595d..79d440cf6 100644 --- a/src/Blocks/BlockSlab.h +++ b/src/Blocks/BlockSlab.h @@ -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); + } } ; diff --git a/src/Blocks/BlockSnow.h b/src/Blocks/BlockSnow.h index 3fab0b8ef..c49aa7161 100644 --- a/src/Blocks/BlockSnow.h +++ b/src/Blocks/BlockSnow.h @@ -88,6 +88,11 @@ public: UNUSED(a_Meta); 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)); + } } ; diff --git a/src/Blocks/BlockStairs.h b/src/Blocks/BlockStairs.h index 8a23fd064..3d7dd1442 100644 --- a/src/Blocks/BlockStairs.h +++ b/src/Blocks/BlockStairs.h @@ -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 + } ; 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(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(m_LastGroundHeight - GetPosY() - 3.0); + if ((Damage > 0) && !FallDamageAbsorbed) + { + TakeDamage(dtFalling, nullptr, Damage, Damage, 0); + + // Fall particles + int ParticleSize = static_cast((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 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(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((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 */ diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 2e365e987..fa36285ba 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -77,7 +77,6 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_Target(nullptr) , m_PathFinder(a_Width, a_Height) , m_PathfinderActivated(false) - , m_LastGroundHeight(POSY_TOINT) , m_JumpCoolDown(0) , m_IdleInterval(0) , m_DestroyTimer(0) @@ -298,7 +297,6 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } SetPitchAndYawFromDestination(a_IsFollowingPath); - HandleFalling(); switch (m_EMState) { @@ -397,20 +395,8 @@ void cMonster::SetPitchAndYawFromDestination(bool a_IsFollowingPath) void cMonster::HandleFalling() { - if (m_bOnGround) - { - 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; - } + m_bTouchGround = IsOnGround(); + super::HandleFalling(); } diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 1e1012f57..98b67f343 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -56,6 +56,8 @@ public: 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 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 @@ -203,8 +205,6 @@ protected: /** Sets the body yaw and head yaw */ void SetPitchAndYawFromDestination(bool a_IsFollowingPath); - virtual void HandleFalling(void); - int m_LastGroundHeight; int m_JumpCoolDown; std::chrono::milliseconds m_IdleInterval; diff --git a/tests/LoadablePieces/Stubs.cpp b/tests/LoadablePieces/Stubs.cpp index 26ee06769..ce30b76dd 100644 --- a/tests/LoadablePieces/Stubs.cpp +++ b/tests/LoadablePieces/Stubs.cpp @@ -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) { return nullptr;