diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index aeb217b7a..594e281e6 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -3724,6 +3724,46 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") }, Notes = "Returns true if the entity is invisible", }, + IsInFire = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if any part of the entity is in a fire block", + }, + IsInLava = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if any part of the entity is in a lava block", + }, + IsInWater = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if any part of the entity is in a water block", + }, + IsHeadInWater = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if the entity's head is in a water block", + }, IsItemFrame = { Returns = @@ -3872,7 +3912,7 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") Type = "boolean", }, }, - Notes = "Returns true if the mob or player is submerged in water (head is in a water block). Note, this function is only updated with mobs or players.", + Notes = "Returns true if the entity's head is in a water block Currently deprecated in favour of IsHeadInWater()", }, IsSwimming = { @@ -3882,7 +3922,7 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") Type = "boolean", }, }, - Notes = "Returns true if the mob or player is swimming in water (feet are in a water block). Note, this function is only updated with mobs or players.", + Notes = "Returns true if any part of the entity is in a water block. Note, this function is only updated with mobs or players. Currently deprecated in favour of IsInWater()", }, IsTicking = { diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 4e7d7c8ef..fbb49d127 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -3954,6 +3954,56 @@ static int tolua_cCompositeChat_UnderlineUrls(lua_State * tolua_S) +static int tolua_cEntity_IsSubmerged(lua_State * tolua_S) +{ + // Check the params: + cLuaState L(tolua_S); + if (!L.CheckParamSelf("cEntity")) + { + return 0; + } + + // Get the params: + cEntity * self = nullptr; + L.GetStackValue(1, self); + + // API function deprecated: + LOGWARNING("cEntity:IsSubmerged() is deprecated. Use cEntity:IsHeadInWater() instead."); + cLuaState::LogStackTrace(tolua_S); + + L.Push(self->IsHeadInWater()); + return 1; +} + + + + + +static int tolua_cEntity_IsSwimming(lua_State * tolua_S) +{ + // Check the params: + cLuaState L(tolua_S); + if (!L.CheckParamSelf("cEntity")) + { + return 0; + } + + // Get the params: + cEntity * self = nullptr; + L.GetStackValue(1, self); + + // API function deprecated + LOGWARNING("cEntity:IsSwimming() is deprecated. Use cEntity:IsInWater() instead."); + cLuaState::LogStackTrace(tolua_S); + + L.Push(self->IsInWater()); + return 1; +} + + + + + static int tolua_cEntity_GetPosition(lua_State * tolua_S) { cLuaState L(tolua_S); @@ -4073,6 +4123,8 @@ void cManualBindings::Bind(lua_State * tolua_S) tolua_beginmodule(tolua_S, "cEntity"); tolua_constant(tolua_S, "INVALID_ID", cEntity::INVALID_ID); + tolua_function(tolua_S, "IsSubmerged", tolua_cEntity_IsSubmerged); + tolua_function(tolua_S, "IsSwimming", tolua_cEntity_IsSwimming); tolua_function(tolua_S, "GetPosition", tolua_cEntity_GetPosition); tolua_function(tolua_S, "GetSpeed", tolua_cEntity_GetSpeed); tolua_endmodule(tolua_S); @@ -4238,7 +4290,3 @@ void cManualBindings::Bind(lua_State * tolua_S) tolua_endmodule(tolua_S); } - - - - diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp index 4d104bd26..2dc329f30 100644 --- a/src/Entities/ArrowEntity.cpp +++ b/src/Entities/ArrowEntity.cpp @@ -123,7 +123,7 @@ void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, Vector3d a_HitPos) double KnockbackAmount = 11 + 10 * PunchLevel; a_EntityHit.TakeDamage(dtRangedAttack, GetCreatorUniqueID(), Damage, KnockbackAmount); - if (IsOnFire() && !a_EntityHit.IsSubmerged() && !a_EntityHit.IsSwimming()) + if (IsOnFire() && !a_EntityHit.IsInWater()) { a_EntityHit.StartBurning(100); } @@ -218,7 +218,3 @@ void cArrowEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } } } - - - - diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 646566ae6..ea20fc8da 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -54,8 +54,10 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d m_TicksSinceLastFireDamage(0), m_TicksLeftBurning(0), m_TicksSinceLastVoidDamage(0), - m_IsSwimming(false), - m_IsSubmerged(false), + m_IsInFire(false), + m_IsInLava(false), + m_IsInWater(false), + m_IsHeadInWater(false), m_AirLevel(MAX_AIR_LEVEL), m_AirTickTimer(DROWNING_TICKS), m_TicksAlive(0), @@ -477,11 +479,11 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) { BurnTicks += 4 * (FireAspectLevel - 1); } - if (!IsMob() && !IsSubmerged() && !IsSwimming()) + if (!IsMob() && !IsInWater()) { StartBurning(BurnTicks * 20); } - else if (IsMob() && !IsSubmerged() && !IsSwimming()) + else if (IsMob() && !IsInWater()) { cMonster * Monster = reinterpret_cast(this); switch (Monster->GetMobType()) @@ -831,6 +833,7 @@ void cEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_InvulnerableTicks--; } + // Non-players are destroyed as soon as they fall out of the world: if ((GetPosY() < 0) && (!IsPlayer())) { Destroy(); @@ -848,11 +851,16 @@ void cEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) return; } - // Position changed -> super::Tick() called + // Position changed -> super::Tick() called: GET_AND_VERIFY_CURRENT_CHUNK(NextChunk, POSX_TOINT, POSZ_TOINT) + // Set swim states (water, lava, and fire): + SetSwimState(*NextChunk); + + // Handle catching on fire and burning: TickBurning(*NextChunk); + // Damage players if they are in the void if (GetPosY() < VOID_BOUNDARY) { TickInVoid(*NextChunk); @@ -862,6 +870,7 @@ void cEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_TicksSinceLastVoidDamage = 0; } + // Handle cactus damage or destruction: if ( IsMob() || IsPickup() || IsExpOrb() || (IsPlayer() && !((reinterpret_cast(this))->IsGameModeCreative() || (reinterpret_cast(this))->IsGameModeSpectator())) @@ -869,12 +878,10 @@ void cEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { DetectCacti(); } + + // Handle drowning: if (IsMob() || IsPlayer()) { - // Set swimming state - SetSwimState(*NextChunk); - - // Handle drowning HandleAir(); } @@ -1192,60 +1199,13 @@ void cEntity::TickBurning(cChunk & a_Chunk) m_TicksLeftBurning--; } - // Update the burning times, based on surroundings: - int MinRelX = FloorC(GetPosX() - m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width; - int MaxRelX = FloorC(GetPosX() + m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width; - int MinRelZ = FloorC(GetPosZ() - m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width; - int MaxRelZ = FloorC(GetPosZ() + m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width; - int MinY = Clamp(POSY_TOINT, 0, cChunkDef::Height - 1); - int MaxY = Clamp(FloorC(GetPosY() + m_Height), 0, cChunkDef::Height - 1); - bool HasWater = false; - bool HasLava = false; - bool HasFire = false; - - for (int x = MinRelX; x <= MaxRelX; x++) - { - for (int z = MinRelZ; z <= MaxRelZ; z++) - { - int RelX = x; - int RelZ = z; - - for (int y = MinY; y <= MaxY; y++) - { - BLOCKTYPE Block; - a_Chunk.UnboundedRelGetBlockType(RelX, y, RelZ, Block); - - switch (Block) - { - case E_BLOCK_FIRE: - { - HasFire = true; - break; - } - case E_BLOCK_LAVA: - case E_BLOCK_STATIONARY_LAVA: - { - HasLava = true; - break; - } - case E_BLOCK_STATIONARY_WATER: - case E_BLOCK_WATER: - { - HasWater = true; - break; - } - } // switch (BlockType) - } // for y - } // for z - } // for x - - if (HasWater) + if (IsInWater()) { // Extinguish the fire m_TicksLeftBurning = 0; } - if (HasLava) + if (IsInLava()) { // Burn: m_TicksLeftBurning = BURN_TICKS; @@ -1266,7 +1226,7 @@ void cEntity::TickBurning(cChunk & a_Chunk) m_TicksSinceLastLavaDamage = 0; } - if (HasFire) + if (IsInFire()) { // Burn: m_TicksLeftBurning = BURN_TICKS; @@ -1275,7 +1235,7 @@ void cEntity::TickBurning(cChunk & a_Chunk) m_TicksSinceLastFireDamage++; if (m_TicksSinceLastFireDamage >= FIRE_TICKS_PER_DAMAGE) { - if (!IsFireproof() && !HasLava) + if (!IsFireproof() && !IsInLava()) { TakeDamage(dtFireContact, nullptr, FIRE_DAMAGE, 0); } @@ -1644,37 +1604,70 @@ bool cEntity::MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn) void cEntity::SetSwimState(cChunk & a_Chunk) { + m_IsInFire = false; + m_IsInLava = false; + m_IsInWater = false; + m_IsHeadInWater = false; + int RelY = FloorC(GetPosY() + 0.1); int HeadRelY = CeilC(GetPosY() + GetHeight()) - 1; ASSERT(RelY <= HeadRelY); if ((RelY < 0) || (HeadRelY >= cChunkDef::Height)) { - m_IsSwimming = false; - m_IsSubmerged = false; return; } - BLOCKTYPE BlockIn; + int MinRelX = FloorC(GetPosX() - m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width; + int MaxRelX = FloorC(GetPosX() + m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width; + int MinRelZ = FloorC(GetPosZ() - m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width; + int MaxRelZ = FloorC(GetPosZ() + m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width; + int MinY = Clamp(POSY_TOINT, 0, cChunkDef::Height - 1); + int MaxY = Clamp(FloorC(GetPosY() + m_Height), 0, cChunkDef::Height - 1); + + for (int x = MinRelX; x <= MaxRelX; x++) + { + for (int z = MinRelZ; z <= MaxRelZ; z++) + { + for (int y = MinY; y <= MaxY; y++) + { + BLOCKTYPE Block; + if (!a_Chunk.UnboundedRelGetBlockType(x, y, z, Block)) + { + LOGD("SetSwimState failure: RelX = %d, RelY = %d, RelZ = %d, Pos = %.02f, %.02f}", + x, y, z, GetPosX(), GetPosZ() + ); + continue; + } + + if (Block == E_BLOCK_FIRE) + { + m_IsInFire = true; + } + else if (IsBlockLava(Block)) + { + m_IsInLava = true; + } + else if (IsBlockWater(Block)) + { + m_IsInWater = true; + } + } // for y + } // for z + } // for x + + // Check if the entity's head is in water. int RelX = POSX_TOINT - a_Chunk.GetPosX() * cChunkDef::Width; int RelZ = POSZ_TOINT - a_Chunk.GetPosZ() * cChunkDef::Width; - - // Check if the player is swimming: - if (!a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn)) + int HeadHeight = CeilC(GetPosY() + GetHeight()) - 1; + BLOCKTYPE BlockIn; + if (!a_Chunk.UnboundedRelGetBlockType(RelX, HeadHeight, RelZ, BlockIn)) { - // This sometimes happens on Linux machines - // Ref.: https://forum.cuberite.org/thread-1244.html - LOGD("SetSwimState failure: RelX = %d, RelZ = %d, Pos = %.02f, %.02f}", - RelX, RelY, GetPosX(), GetPosZ() + LOGD("SetSwimState failure: RelX = %d, RelY = %d, RelZ = %d, Pos = %.02f, %.02f}", + RelX, HeadHeight, RelZ, GetPosX(), GetPosZ() ); - m_IsSwimming = false; - m_IsSubmerged = false; return; } - m_IsSwimming = IsBlockWater(BlockIn); - - // Check if the player is submerged: - VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, HeadRelY, RelZ, BlockIn)); - m_IsSubmerged = IsBlockWater(BlockIn); + m_IsHeadInWater = IsBlockWater(BlockIn); } @@ -1710,7 +1703,7 @@ void cEntity::HandleAir(void) int RespirationLevel = static_cast(GetEquippedHelmet().m_Enchantments.GetLevel(cEnchantments::enchRespiration)); - if (IsSubmerged()) + if (IsHeadInWater()) { if (!IsPlayer()) // Players control themselves { diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index fae296ab4..5b993ac96 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -481,11 +481,17 @@ public: virtual bool IsRclking (void) const {return false; } virtual bool IsInvisible(void) const { return false; } - /** Returns whether the player is swimming or not */ - virtual bool IsSwimming(void) const{ return m_IsSwimming; } + /** Returns true if any part of the entity is in a fire block */ + virtual bool IsInFire(void) const { return m_IsInFire; } - /** Return whether the player is under water or not */ - virtual bool IsSubmerged(void) const{ return m_IsSubmerged; } + /** Returns true if any part of the entity is in a lava block */ + virtual bool IsInLava(void) const { return m_IsInLava; } + + /** Returns true if any part of the entity is in a water block */ + virtual bool IsInWater(void) const { return m_IsInWater; } + + /** Returns true if any part of the entity is in a water block */ + virtual bool IsHeadInWater(void) const { return m_IsHeadInWater; } /** Gets remaining air of a monster */ int GetAirLevel(void) const { return m_AirLevel; } @@ -619,8 +625,17 @@ protected: /** Time, in ticks, since the last damage dealt by the void. Reset to zero when moving out of the void. */ int m_TicksSinceLastVoidDamage; - /** If an entity is currently swimming in or submerged under water */ - bool m_IsSwimming, m_IsSubmerged; + /** If any part of the entity is in a fire block */ + bool m_IsInFire; + + /** If any part of the entity is in a lava block */ + bool m_IsInLava; + + /** If any part of the entity is in a water block */ + bool m_IsInWater; + + /** If the entity's head is in a water block */ + bool m_IsHeadInWater; /** Air level of a mobile */ int m_AirLevel; @@ -647,7 +662,8 @@ protected: /** Called in each tick to handle air-related processing i.e. drowning */ virtual void HandleAir(void); - /** Called once per tick to set IsSwimming and IsSubmerged */ + /** Called once per tick to set m_IsInFire, m_IsInLava, m_IsInWater and + m_IsHeadInWater */ virtual void SetSwimState(cChunk & a_Chunk); private: @@ -693,7 +709,3 @@ private: cMonsterList m_LeashedMobs; } ; // tolua_export - - - - diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp index b5769f430..27c04d4b8 100644 --- a/src/Entities/Pawn.cpp +++ b/src/Entities/Pawn.cpp @@ -156,7 +156,7 @@ bool cPawn::IsInvisible() const void cPawn::HandleAir(void) { - if (IsSubmerged() && HasEntityEffect(cEntityEffect::effWaterBreathing)) + if (IsHeadInWater() && HasEntityEffect(cEntityEffect::effWaterBreathing)) { // Prevent the oxygen from decreasing return; @@ -486,6 +486,3 @@ cEntityEffect * cPawn::GetEntityEffect(cEntityEffect::eType a_EffectType) auto itr = m_EntityEffects.find(a_EffectType); return (itr != m_EntityEffects.end()) ? itr->second.get() : nullptr; } - - - diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 26fe58252..be6f3ebe3 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -147,17 +147,8 @@ void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) // Position might have changed due to physics. So we have to make sure we have the correct chunk. 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) - ) + // Destroy the pickup if it is on fire: + if (IsOnFire()) { m_bCollected = true; m_Timer = std::chrono::milliseconds(0); // We have to reset the timer. @@ -261,7 +252,3 @@ bool cPickup::CollectedBy(cPlayer & a_Dest) // LOG("Pickup %d cannot be collected by \"%s\", because there's no space in the inventory.", a_Dest->GetName().c_str(), m_UniqueID); return false; } - - - - diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 073d00909..7b6719f55 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -92,9 +92,6 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) : { ASSERT(a_PlayerName.length() <= 16); // Otherwise this player could crash many clients... - m_IsSwimming = false; - m_IsSubmerged = false; - m_InventoryWindow = new cInventoryWindow(*this); m_CurrentWindow = m_InventoryWindow; m_InventoryWindow->OpenedByPlayer(*this); @@ -2503,12 +2500,7 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIs m_Stats.AddValue(statDistClimbed, FloorC(a_DeltaPos.y * 100 + 0.5)); } } - else if (IsSubmerged()) - { - m_Stats.AddValue(statDistDove, Value); - AddFoodExhaustion(0.00015 * static_cast(Value)); - } - else if (IsSwimming()) + else if (IsInWater()) { m_Stats.AddValue(statDistSwum, Value); AddFoodExhaustion(0.00015 * static_cast(Value)); diff --git a/src/Mobs/Enderman.cpp b/src/Mobs/Enderman.cpp index b7013affd..01e13713d 100644 --- a/src/Mobs/Enderman.cpp +++ b/src/Mobs/Enderman.cpp @@ -193,10 +193,10 @@ void cEnderman::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) return; } - // Take damage when touching water, drowning damage seems to be most appropriate + // Take damage when wet, drowning damage seems to be most appropriate if ( cChunkDef::IsValidHeight(POSY_TOINT) && - (GetWorld()->IsWeatherWetAtXYZ(GetPosition().Floor()) || IsSwimming()) + (GetWorld()->IsWeatherWetAtXYZ(GetPosition().Floor()) || IsInWater()) ) { EventLosePlayer(); diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index db1150f67..03b809275 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -194,7 +194,7 @@ void cMonster::MoveToWayPoint(cChunk & a_Chunk) { if ( (IsOnGround() && (GetSpeed().SqrLength() <= 0.5)) || // If walking on the ground, we need to slow down first, otherwise we miss the jump - IsSwimming() + IsInWater() ) { m_bOnGround = false; @@ -221,7 +221,7 @@ void cMonster::MoveToWayPoint(cChunk & a_Chunk) { Distance *= 2.5f; } - else if (IsSwimming()) + else if (IsInWater()) { Distance *= 1.3f; } diff --git a/src/Mobs/Squid.cpp b/src/Mobs/Squid.cpp index 8ae688a1b..224ec6a06 100644 --- a/src/Mobs/Squid.cpp +++ b/src/Mobs/Squid.cpp @@ -47,7 +47,7 @@ void cSquid::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) return; } - if (!IsSubmerged()) + if (!IsHeadInWater()) { if (m_AirLevel <= 0) { @@ -79,6 +79,3 @@ void cSquid::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) super::Tick(a_Dt, a_Chunk); } - - -