diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index a185b8f69..d6bb057f4 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -1715,17 +1715,6 @@ void cEntity::SetIsTicking(bool a_IsTicking) -void cEntity::DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) -{ - m_Speed.Set(a_SpeedX, a_SpeedY, a_SpeedZ); - - WrapSpeed(); -} - - - - - void cEntity::HandleAir(void) { // Ref.: https://minecraft.gamepedia.com/Chunk_format @@ -2095,7 +2084,8 @@ void cEntity::SetRoll(double a_Roll) void cEntity::SetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) { - DoSetSpeed(a_SpeedX, a_SpeedY, a_SpeedZ); + m_Speed.Set(a_SpeedX, a_SpeedY, a_SpeedZ); + WrapSpeed(); } @@ -2140,7 +2130,7 @@ void cEntity::SetWidth(double a_Width) void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeedZ) { - DoSetSpeed(m_Speed.x + a_AddSpeedX, m_Speed.y + a_AddSpeedY, m_Speed.z + a_AddSpeedZ); + SetSpeed(m_Speed.x + a_AddSpeedX, m_Speed.y + a_AddSpeedY, m_Speed.z + a_AddSpeedZ); } @@ -2280,34 +2270,3 @@ void cEntity::BroadcastLeashedMobs() } } } - - - - - -float cEntity::GetExplosionExposureRate(Vector3d a_ExplosionPosition, float a_ExlosionPower) -{ - double EntitySize = m_Width * m_Width * m_Height; - if (EntitySize <= 0) - { - // Handle entity with invalid size - return 0; - } - - auto EntityBox = GetBoundingBox(); - cBoundingBox ExplosionBox(a_ExplosionPosition, a_ExlosionPower * 2.0); - cBoundingBox IntersectionBox(EntityBox); - - bool Overlap = EntityBox.Intersect(ExplosionBox, IntersectionBox); - if (Overlap) - { - Vector3d Diff = IntersectionBox.GetMax() - IntersectionBox.GetMin(); - double OverlapSize = Diff.x * Diff.y * Diff.z; - - return static_cast(OverlapSize / EntitySize); - } - else - { - return 0; - } -} diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 143993dad..9fe7f16f5 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -591,12 +591,6 @@ public: /** Returs whether the entity has any mob leashed to it. */ bool HasAnyMobLeashed() const { return m_LeashedMobs.size() > 0; } - /** a lightweight calculation approach to get explosion exposure rate - @param a_ExplosionPosition explosion position - @param a_ExlosionPower explosion power - @return exposure rate */ - virtual float GetExplosionExposureRate(Vector3d a_ExplosionPosition, float a_ExlosionPower); - protected: @@ -705,11 +699,6 @@ protected: /** The number of ticks this entity has been alive for */ long int m_TicksAlive; - - /** Does the actual speed-setting. The default implementation just sets the member variable value; - overrides can provide further processing, such as forcing players to move at the given speed. */ - virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ); - /** Handles the moving of this entity between worlds. Should handle degenerate cases such as moving to the same world. */ void DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo); diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp index 3ddcea6ee..96eebe2fd 100644 --- a/src/Entities/Minecart.cpp +++ b/src/Entities/Minecart.cpp @@ -216,6 +216,9 @@ void cMinecart::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_DetectorRailPosition = Vector3i(POSX_TOINT, POSY_TOINT, POSZ_TOINT); } + // Enforce speed limit: + m_Speed.Clamp(MAX_SPEED_NEGATIVE, MAX_SPEED); + // Broadcast positioning changes to client BroadcastMovementUpdate(); } @@ -1268,40 +1271,6 @@ void cMinecart::ApplyAcceleration(Vector3d a_ForwardDirection, double a_Accelera -void cMinecart::DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) -{ - if (a_SpeedX > MAX_SPEED) - { - a_SpeedX = MAX_SPEED; - } - else if (a_SpeedX < MAX_SPEED_NEGATIVE) - { - a_SpeedX = MAX_SPEED_NEGATIVE; - } - if (a_SpeedY > MAX_SPEED) - { - a_SpeedY = MAX_SPEED; - } - else if (a_SpeedY < MAX_SPEED_NEGATIVE) - { - a_SpeedY = MAX_SPEED_NEGATIVE; - } - if (a_SpeedZ > MAX_SPEED) - { - a_SpeedZ = MAX_SPEED; - } - else if (a_SpeedZ < MAX_SPEED_NEGATIVE) - { - a_SpeedZ = MAX_SPEED_NEGATIVE; - } - - Super::DoSetSpeed(a_SpeedX, a_SpeedY, a_SpeedZ); -} - - - - - //////////////////////////////////////////////////////////////////////////////// // cRideableMinecart: diff --git a/src/Entities/Minecart.h b/src/Entities/Minecart.h index 73011b7b5..a98014799 100644 --- a/src/Entities/Minecart.h +++ b/src/Entities/Minecart.h @@ -56,9 +56,6 @@ protected: /** Applies an acceleration to the minecart parallel to a_ForwardDirection but without allowing backward speed. */ void ApplyAcceleration(Vector3d a_ForwardDirection, double a_Acceleration); - // Overwrite to enforce speed limit - virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) override; - cMinecart(ePayload a_Payload, Vector3d a_Pos); /** Handles physics on normal rails diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index d20795643..e3994e88c 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -179,47 +179,6 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client) : -void cPlayer::AddKnownItem(const cItem & a_Item) -{ - if (a_Item.m_ItemType < 0) - { - return; - } - - auto Response = m_KnownItems.insert(a_Item.CopyOne()); - if (!Response.second) - { - // The item was already known, bail out: - return; - } - - // Process the recipes that got unlocked by this newly-known item: - auto Recipes = cRoot::Get()->GetCraftingRecipes()->FindNewRecipesForItem(a_Item, m_KnownItems); - for (const auto & RecipeId : Recipes) - { - AddKnownRecipe(RecipeId); - } -} - - - - - -void cPlayer::AddKnownRecipe(UInt32 a_RecipeId) -{ - auto Response = m_KnownRecipes.insert(a_RecipeId); - if (!Response.second) - { - // The recipe was already known, bail out: - return; - } - m_ClientHandle->SendUnlockRecipe(a_RecipeId); -} - - - - - cPlayer::~cPlayer(void) { LOGD("Deleting cPlayer \"%s\" at %p, ID %d", GetName().c_str(), static_cast(this), GetUniqueID()); @@ -238,302 +197,6 @@ cPlayer::~cPlayer(void) -void cPlayer::OnAddToWorld(cWorld & a_World) -{ - Super::OnAddToWorld(a_World); - - // Update world name tracking: - m_CurrentWorldName = m_World->GetName(); - - // Fix to stop the player falling through the world, until we get serversided collision detection: - FreezeInternal(GetPosition(), false); - - // Set capabilities based on new world: - SetCapabilities(); - - // Send contents of the inventory window: - m_ClientHandle->SendWholeInventory(*m_CurrentWindow); - - // Send health (the respawn packet, which understandably resets health, is also used for world travel...): - m_ClientHandle->SendHealth(); - - // Send experience, similar story with the respawn packet: - m_ClientHandle->SendExperience(); - - // Send hotbar active slot (also reset by respawn): - m_ClientHandle->SendHeldItemChange(m_Inventory.GetEquippedSlotNum()); - - // Update player team: - UpdateTeam(); - - // Send scoreboard data: - m_World->GetScoreBoard().SendTo(*m_ClientHandle); - - // Update the view distance: - m_ClientHandle->SetViewDistance(m_ClientHandle->GetRequestedViewDistance()); - - // Send current weather of target world: - m_ClientHandle->SendWeather(a_World.GetWeather()); - - // Send time: - m_ClientHandle->SendTimeUpdate(a_World.GetWorldAge(), a_World.GetTimeOfDay(), a_World.IsDaylightCycleEnabled()); - - // Finally, deliver the notification hook: - cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*this); -} - - - - - -void cPlayer::OnRemoveFromWorld(cWorld & a_World) -{ - Super::OnRemoveFromWorld(a_World); - - // Remove any references to this player pointer by windows in the old world: - CloseWindow(false); - - // Remove the client handle from the world: - m_World->RemoveClientFromChunks(m_ClientHandle.get()); - - if (m_ClientHandle->IsDestroyed()) // Note: checking IsWorldChangeScheduled not appropriate here since we can disconnecting while having a scheduled warp - { - // Disconnecting, do the necessary cleanup. - // This isn't in the destructor to avoid crashing accessing destroyed objects during shutdown. - - if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this)) - { - cRoot::Get()->BroadcastChatLeave(Printf("%s has left the game", GetName().c_str())); - LOGINFO("Player %s has left the game", GetName().c_str()); - } - - // Remove ourself from everyone's lists: - cRoot::Get()->BroadcastPlayerListsRemovePlayer(*this); - - // Atomically decrement player count (in world thread): - cRoot::Get()->GetServer()->PlayerDestroyed(); - - // We're just disconnecting. The remaining code deals with going through portals, so bail: - return; - } - - const auto DestinationDimension = m_WorldChangeInfo.m_NewWorld->GetDimension(); - - // Award relevant achievements: - if (DestinationDimension == dimEnd) - { - AwardAchievement(Statistic::AchTheEnd); - } - else if (DestinationDimension == dimNether) - { - AwardAchievement(Statistic::AchPortal); - } - - // Clear sent chunk lists from the clienthandle: - m_ClientHandle->RemoveFromWorld(); - - // The clienthandle caches the coords of the chunk we're standing at. Invalidate this. - m_ClientHandle->InvalidateCachedSentChunk(); - - // Clientside warp start: - m_ClientHandle->SendRespawn(DestinationDimension, false); -} - - - - - -void cPlayer::SpawnOn(cClientHandle & a_Client) -{ - if (!m_bVisible || (m_ClientHandle.get() == (&a_Client))) - { - return; - } - - LOGD("Spawing %s on %s", GetName().c_str(), a_Client.GetUsername().c_str()); - - a_Client.SendPlayerSpawn(*this); - a_Client.SendEntityHeadLook(*this); - a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem()); - a_Client.SendEntityEquipment(*this, 1, m_Inventory.GetEquippedBoots()); - a_Client.SendEntityEquipment(*this, 2, m_Inventory.GetEquippedLeggings()); - a_Client.SendEntityEquipment(*this, 3, m_Inventory.GetEquippedChestplate()); - a_Client.SendEntityEquipment(*this, 4, m_Inventory.GetEquippedHelmet()); -} - - - - - -void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - m_ClientHandle->Tick(a_Dt.count()); - - if (m_ClientHandle->IsDestroyed()) - { - Destroy(); - return; - } - - if (!m_ClientHandle->IsPlaying()) - { - // We're not yet in the game, ignore everything: - return; - } - - m_Stats.AddValue(Statistic::PlayOneMinute); - m_Stats.AddValue(Statistic::TimeSinceDeath); - - if (IsCrouched()) - { - m_Stats.AddValue(Statistic::SneakTime); - } - - // Handle the player detach, when the player is in spectator mode - if ( - (IsGameModeSpectator()) && - (m_AttachedTo != nullptr) && - ( - (m_AttachedTo->IsDestroyed()) || // Watching entity destruction - (m_AttachedTo->GetHealth() <= 0) || // Watching entity dead - (IsCrouched()) // Or the player wants to be detached - ) - ) - { - Detach(); - } - - if (!a_Chunk.IsValid()) - { - // Players are ticked even if the parent chunk is invalid. - // We've processed as much as we can, bail: - return; - } - - ASSERT((GetParentChunk() != nullptr) && (GetParentChunk()->IsValid())); - ASSERT(a_Chunk.IsValid()); - - // Handle a frozen player: - TickFreezeCode(); - - if ( - m_IsFrozen || // Don't do Tick updates if frozen - IsWorldChangeScheduled() // If we're about to change worlds (e.g. respawn), abort processing all world interactions (GH #3939) - ) - { - return; - } - - Super::Tick(a_Dt, a_Chunk); - - // Handle charging the bow: - if (m_IsChargingBow) - { - m_BowCharge += 1; - } - - BroadcastMovementUpdate(m_ClientHandle.get()); - - if (m_Health > 0) // make sure player is alive - { - m_World->CollectPickupsByPlayer(*this); - - if ((m_EatingFinishTick >= 0) && (m_EatingFinishTick <= m_World->GetWorldAge())) - { - FinishEating(); - } - - HandleFood(); - } - - if (m_IsFishing) - { - HandleFloater(); - } - - // Update items (e.g. Maps) - m_Inventory.UpdateItems(); - - // Send Player List (Once per m_LastPlayerListTime/1000 ms) - if (m_LastPlayerListTime + PLAYER_LIST_TIME_MS <= std::chrono::steady_clock::now()) - { - m_World->BroadcastPlayerListUpdatePing(*this); - m_LastPlayerListTime = std::chrono::steady_clock::now(); - } - - if (m_TicksUntilNextSave == 0) - { - SaveToDisk(); - m_TicksUntilNextSave = PLAYER_INVENTORY_SAVE_INTERVAL; - } - else - { - m_TicksUntilNextSave--; - } -} - - - - - -void cPlayer::TickFreezeCode() -{ - if (m_IsFrozen) - { - if ((!m_IsManuallyFrozen) && (GetClientHandle()->IsPlayerChunkSent())) - { - // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded and sent - Unfreeze(); - - // Pull the player out of any solids that might have loaded on them. - PREPARE_REL_AND_CHUNK(GetPosition(), *(GetParentChunk())); - if (RelSuccess) - { - int NewY = Rel.y; - if (NewY < 0) - { - NewY = 0; - } - while (NewY < cChunkDef::Height - 2) - { - // If we find a position with enough space for the player - if ( - !cBlockInfo::IsSolid(Chunk->GetBlock(Rel.x, NewY, Rel.z)) && - !cBlockInfo::IsSolid(Chunk->GetBlock(Rel.x, NewY + 1, Rel.z)) - ) - { - // If the found position is not the same as the original - if (NewY != Rel.y) - { - SetPosition(GetPosition().x, NewY, GetPosition().z); - GetClientHandle()->SendPlayerPosition(); - } - break; - } - ++NewY; - } - } - } - else if (GetWorld()->GetWorldAge() % 100 == 0) - { - // Despite the client side freeze, the player may be able to move a little by - // Jumping or canceling flight. Re-freeze every now and then - FreezeInternal(GetPosition(), m_IsManuallyFrozen); - } - } - else - { - if (!GetClientHandle()->IsPlayerChunkSent() || (!GetParentChunk()->IsValid())) - { - FreezeInternal(GetPosition(), false); - } - } -} - - - - - int cPlayer::CalcLevelFromXp(int a_XpTotal) { // level 0 to 15 @@ -1092,69 +755,6 @@ void cPlayer::SetFlying(bool a_IsFlying) -void cPlayer::ApplyArmorDamage(int a_DamageBlocked) -{ - short ArmorDamage = static_cast(std::max(a_DamageBlocked / 4, 1)); - - for (int i = 0; i < 4; i++) - { - UseItem(cInventory::invArmorOffset + i, ArmorDamage); - } -} - - - - - -bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) -{ - if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtPlugin)) - { - if (IsGameModeCreative() || IsGameModeSpectator()) - { - // No damage / health in creative or spectator mode if not void or plugin damage - return false; - } - } - - if ((a_TDI.Attacker != nullptr) && (a_TDI.Attacker->IsPlayer())) - { - cPlayer * Attacker = static_cast(a_TDI.Attacker); - - if ((m_Team != nullptr) && (m_Team == Attacker->m_Team)) - { - if (!m_Team->AllowsFriendlyFire()) - { - // Friendly fire is disabled - return false; - } - } - } - - if (Super::DoTakeDamage(a_TDI)) - { - // Any kind of damage adds food exhaustion - AddFoodExhaustion(0.3f); - m_ClientHandle->SendHealth(); - - // Tell the wolves - if (a_TDI.Attacker != nullptr) - { - if (a_TDI.Attacker->IsPawn()) - { - NotifyNearbyWolves(static_cast(a_TDI.Attacker), true); - } - } - m_Stats.AddValue(Statistic::DamageTaken, FloorC(a_TDI.FinalDamage * 10 + 0.5)); - return true; - } - return false; -} - - - - - void cPlayer::NotifyNearbyWolves(cPawn * a_Opponent, bool a_IsPlayerInvolved) { ASSERT(a_Opponent != nullptr); @@ -1888,22 +1488,6 @@ void cPlayer::ForceSetSpeed(const Vector3d & a_Speed) -void cPlayer::DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) -{ - if (m_IsFrozen) - { - // Do not set speed to a frozen client - return; - } - Super::DoSetSpeed(a_SpeedX, a_SpeedY, a_SpeedZ); - // Send the speed to the client so he actualy moves - m_ClientHandle->SendEntityVelocity(*this); -} - - - - - void cPlayer::SetVisible(bool a_bVisible) { // Need to Check if the player or other players are in gamemode spectator, but will break compatibility @@ -3211,15 +2795,436 @@ bool cPlayer::CanInstantlyMine(BLOCKTYPE a_Block) -float cPlayer::GetExplosionExposureRate(Vector3d a_ExplosionPosition, float a_ExlosionPower) +void cPlayer::AddKnownItem(const cItem & a_Item) +{ + if (a_Item.m_ItemType < 0) + { + return; + } + + auto Response = m_KnownItems.insert(a_Item.CopyOne()); + if (!Response.second) + { + // The item was already known, bail out: + return; + } + + // Process the recipes that got unlocked by this newly-known item: + auto Recipes = cRoot::Get()->GetCraftingRecipes()->FindNewRecipesForItem(a_Item, m_KnownItems); + for (const auto & RecipeId : Recipes) + { + AddKnownRecipe(RecipeId); + } +} + + + + + +void cPlayer::AddKnownRecipe(UInt32 a_RecipeId) +{ + auto Response = m_KnownRecipes.insert(a_RecipeId); + if (!Response.second) + { + // The recipe was already known, bail out: + return; + } + m_ClientHandle->SendUnlockRecipe(a_RecipeId); +} + + + + + +void cPlayer::TickFreezeCode() +{ + if (m_IsFrozen) + { + if ((!m_IsManuallyFrozen) && (GetClientHandle()->IsPlayerChunkSent())) + { + // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded and sent + Unfreeze(); + + // Pull the player out of any solids that might have loaded on them. + PREPARE_REL_AND_CHUNK(GetPosition(), *(GetParentChunk())); + if (RelSuccess) + { + int NewY = Rel.y; + if (NewY < 0) + { + NewY = 0; + } + while (NewY < cChunkDef::Height - 2) + { + // If we find a position with enough space for the player + if ( + !cBlockInfo::IsSolid(Chunk->GetBlock(Rel.x, NewY, Rel.z)) && + !cBlockInfo::IsSolid(Chunk->GetBlock(Rel.x, NewY + 1, Rel.z)) + ) + { + // If the found position is not the same as the original + if (NewY != Rel.y) + { + SetPosition(GetPosition().x, NewY, GetPosition().z); + GetClientHandle()->SendPlayerPosition(); + } + break; + } + ++NewY; + } + } + } + else if (GetWorld()->GetWorldAge() % 100 == 0) + { + // Despite the client side freeze, the player may be able to move a little by + // Jumping or canceling flight. Re-freeze every now and then + FreezeInternal(GetPosition(), m_IsManuallyFrozen); + } + } + else + { + if (!GetClientHandle()->IsPlayerChunkSent() || (!GetParentChunk()->IsValid())) + { + FreezeInternal(GetPosition(), false); + } + } +} + + + + + +void cPlayer::ApplyArmorDamage(int a_DamageBlocked) +{ + short ArmorDamage = static_cast(std::max(a_DamageBlocked / 4, 1)); + + for (int i = 0; i < 4; i++) + { + UseItem(cInventory::invArmorOffset + i, ArmorDamage); + } +} + + + + + +void cPlayer::BroadcastMovementUpdate(const cClientHandle * a_Exclude) +{ + if (!m_IsFrozen && m_Speed.SqrLength() > 0.001) + { + // If the player is not frozen, has a non-zero speed, + // send the speed to the client so he is forced to move so: + m_ClientHandle->SendEntityVelocity(*this); + } + + // Since we do no physics processing for players, speed will otherwise never decrease: + m_Speed.Set(0, 0, 0); + + Super::BroadcastMovementUpdate(a_Exclude); +} + + + + + +bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) +{ + // Filters out damage for creative mode / friendly fire. + + if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtPlugin)) + { + if (IsGameModeCreative() || IsGameModeSpectator()) + { + // No damage / health in creative or spectator mode if not void or plugin damage + return false; + } + } + + if ((a_TDI.Attacker != nullptr) && (a_TDI.Attacker->IsPlayer())) + { + cPlayer * Attacker = static_cast(a_TDI.Attacker); + + if ((m_Team != nullptr) && (m_Team == Attacker->m_Team)) + { + if (!m_Team->AllowsFriendlyFire()) + { + // Friendly fire is disabled + return false; + } + } + } + + if (Super::DoTakeDamage(a_TDI)) + { + // Any kind of damage adds food exhaustion + AddFoodExhaustion(0.3f); + m_ClientHandle->SendHealth(); + + // Tell the wolves + if (a_TDI.Attacker != nullptr) + { + if (a_TDI.Attacker->IsPawn()) + { + NotifyNearbyWolves(static_cast(a_TDI.Attacker), true); + } + } + m_Stats.AddValue(Statistic::DamageTaken, FloorC(a_TDI.FinalDamage * 10 + 0.5)); + return true; + } + return false; +} + + + + + +float cPlayer::GetEnchantmentBlastKnockbackReduction() { if ( IsGameModeSpectator() || (IsGameModeCreative() && !IsOnGround()) ) { - return 0; // No impact from explosion + return 1; // No impact from explosion } - return Super::GetExplosionExposureRate(a_ExplosionPosition, a_ExlosionPower) / 30.0f; + return Super::GetEnchantmentBlastKnockbackReduction(); +} + + + + + +void cPlayer::OnAddToWorld(cWorld & a_World) +{ + Super::OnAddToWorld(a_World); + + // Update world name tracking: + m_CurrentWorldName = m_World->GetName(); + + // Fix to stop the player falling through the world, until we get serversided collision detection: + FreezeInternal(GetPosition(), false); + + // Set capabilities based on new world: + SetCapabilities(); + + // Send contents of the inventory window: + m_ClientHandle->SendWholeInventory(*m_CurrentWindow); + + // Send health (the respawn packet, which understandably resets health, is also used for world travel...): + m_ClientHandle->SendHealth(); + + // Send experience, similar story with the respawn packet: + m_ClientHandle->SendExperience(); + + // Send hotbar active slot (also reset by respawn): + m_ClientHandle->SendHeldItemChange(m_Inventory.GetEquippedSlotNum()); + + // Update player team: + UpdateTeam(); + + // Send scoreboard data: + m_World->GetScoreBoard().SendTo(*m_ClientHandle); + + // Update the view distance: + m_ClientHandle->SetViewDistance(m_ClientHandle->GetRequestedViewDistance()); + + // Send current weather of target world: + m_ClientHandle->SendWeather(a_World.GetWeather()); + + // Send time: + m_ClientHandle->SendTimeUpdate(a_World.GetWorldAge(), a_World.GetTimeOfDay(), a_World.IsDaylightCycleEnabled()); + + // Finally, deliver the notification hook: + cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*this); +} + + + + + +void cPlayer::OnRemoveFromWorld(cWorld & a_World) +{ + Super::OnRemoveFromWorld(a_World); + + // Remove any references to this player pointer by windows in the old world: + CloseWindow(false); + + // Remove the client handle from the world: + m_World->RemoveClientFromChunks(m_ClientHandle.get()); + + if (m_ClientHandle->IsDestroyed()) // Note: checking IsWorldChangeScheduled not appropriate here since we can disconnecting while having a scheduled warp + { + // Disconnecting, do the necessary cleanup. + // This isn't in the destructor to avoid crashing accessing destroyed objects during shutdown. + + if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this)) + { + cRoot::Get()->BroadcastChatLeave(Printf("%s has left the game", GetName().c_str())); + LOGINFO("Player %s has left the game", GetName().c_str()); + } + + // Remove ourself from everyone's lists: + cRoot::Get()->BroadcastPlayerListsRemovePlayer(*this); + + // Atomically decrement player count (in world thread): + cRoot::Get()->GetServer()->PlayerDestroyed(); + + // We're just disconnecting. The remaining code deals with going through portals, so bail: + return; + } + + const auto DestinationDimension = m_WorldChangeInfo.m_NewWorld->GetDimension(); + + // Award relevant achievements: + if (DestinationDimension == dimEnd) + { + AwardAchievement(Statistic::AchTheEnd); + } + else if (DestinationDimension == dimNether) + { + AwardAchievement(Statistic::AchPortal); + } + + // Clear sent chunk lists from the clienthandle: + m_ClientHandle->RemoveFromWorld(); + + // The clienthandle caches the coords of the chunk we're standing at. Invalidate this. + m_ClientHandle->InvalidateCachedSentChunk(); + + // Clientside warp start: + m_ClientHandle->SendRespawn(DestinationDimension, false); +} + + + + + +void cPlayer::SpawnOn(cClientHandle & a_Client) +{ + if (!m_bVisible || (m_ClientHandle.get() == (&a_Client))) + { + return; + } + + LOGD("Spawing %s on %s", GetName().c_str(), a_Client.GetUsername().c_str()); + + a_Client.SendPlayerSpawn(*this); + a_Client.SendEntityHeadLook(*this); + a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem()); + a_Client.SendEntityEquipment(*this, 1, m_Inventory.GetEquippedBoots()); + a_Client.SendEntityEquipment(*this, 2, m_Inventory.GetEquippedLeggings()); + a_Client.SendEntityEquipment(*this, 3, m_Inventory.GetEquippedChestplate()); + a_Client.SendEntityEquipment(*this, 4, m_Inventory.GetEquippedHelmet()); +} + + + + + +void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + m_ClientHandle->Tick(a_Dt.count()); + + if (m_ClientHandle->IsDestroyed()) + { + Destroy(); + return; + } + + if (!m_ClientHandle->IsPlaying()) + { + // We're not yet in the game, ignore everything: + return; + } + + m_Stats.AddValue(Statistic::PlayOneMinute); + m_Stats.AddValue(Statistic::TimeSinceDeath); + + if (IsCrouched()) + { + m_Stats.AddValue(Statistic::SneakTime); + } + + // Handle the player detach, when the player is in spectator mode + if ( + (IsGameModeSpectator()) && + (m_AttachedTo != nullptr) && + ( + (m_AttachedTo->IsDestroyed()) || // Watching entity destruction + (m_AttachedTo->GetHealth() <= 0) || // Watching entity dead + (IsCrouched()) // Or the player wants to be detached + ) + ) + { + Detach(); + } + + if (!a_Chunk.IsValid()) + { + // Players are ticked even if the parent chunk is invalid. + // We've processed as much as we can, bail: + return; + } + + ASSERT((GetParentChunk() != nullptr) && (GetParentChunk()->IsValid())); + ASSERT(a_Chunk.IsValid()); + + // Handle a frozen player: + TickFreezeCode(); + + if ( + m_IsFrozen || // Don't do Tick updates if frozen + IsWorldChangeScheduled() // If we're about to change worlds (e.g. respawn), abort processing all world interactions (GH #3939) + ) + { + return; + } + + Super::Tick(a_Dt, a_Chunk); + + // Handle charging the bow: + if (m_IsChargingBow) + { + m_BowCharge += 1; + } + + BroadcastMovementUpdate(m_ClientHandle.get()); + + if (m_Health > 0) // make sure player is alive + { + m_World->CollectPickupsByPlayer(*this); + + if ((m_EatingFinishTick >= 0) && (m_EatingFinishTick <= m_World->GetWorldAge())) + { + FinishEating(); + } + + HandleFood(); + } + + if (m_IsFishing) + { + HandleFloater(); + } + + // Update items (e.g. Maps) + m_Inventory.UpdateItems(); + + // Send Player List (Once per m_LastPlayerListTime/1000 ms) + if (m_LastPlayerListTime + PLAYER_LIST_TIME_MS <= std::chrono::steady_clock::now()) + { + m_World->BroadcastPlayerListUpdatePing(*this); + m_LastPlayerListTime = std::chrono::steady_clock::now(); + } + + if (m_TicksUntilNextSave == 0) + { + SaveToDisk(); + m_TicksUntilNextSave = PLAYER_INVENTORY_SAVE_INTERVAL; + } + else + { + m_TicksUntilNextSave--; + } } diff --git a/src/Entities/Player.h b/src/Entities/Player.h index ba3c345ed..f7d54340e 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -47,42 +47,9 @@ public: CLASS_PROTODEF(cPlayer) - cPlayer(const cClientHandlePtr & a_Client); - virtual ~cPlayer() override; - virtual void OnAddToWorld(cWorld & a_World) override; - virtual void OnRemoveFromWorld(cWorld & a_World) override; - - virtual void SpawnOn(cClientHandle & a_Client) override; - - virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - - void TickFreezeCode(); - - virtual void HandlePhysics(std::chrono::milliseconds a_Dt, cChunk &) override { UNUSED(a_Dt); } - - /** Returns the currently equipped weapon; empty item if none */ - virtual cItem GetEquippedWeapon(void) const override { return m_Inventory.GetEquippedItem(); } - - /** Returns the currently equipped helmet; empty item if none */ - virtual cItem GetEquippedHelmet(void) const override { return m_Inventory.GetEquippedHelmet(); } - - /** Returns the currently equipped chestplate; empty item if none */ - virtual cItem GetEquippedChestplate(void) const override { return m_Inventory.GetEquippedChestplate(); } - - /** Returns the currently equipped leggings; empty item if none */ - virtual cItem GetEquippedLeggings(void) const override { return m_Inventory.GetEquippedLeggings(); } - - /** Returns the currently equipped boots; empty item if none */ - virtual cItem GetEquippedBoots(void) const override { return m_Inventory.GetEquippedBoots(); } - - /** Returns the currently offhand equipped item; empty item if none */ - virtual cItem GetOffHandEquipedItem(void) const override { return m_Inventory.GetShieldSlot(); } - - virtual void ApplyArmorDamage(int DamageBlocked) override; - // tolua_begin /** Sets the experience total @@ -598,14 +565,19 @@ public: Source: https://minecraft.gamepedia.com/Breaking#Instant_breaking */ bool CanInstantlyMine(BLOCKTYPE a_Block); - /** get player explosion exposure rate */ - virtual float GetExplosionExposureRate(Vector3d a_ExplosionPosition, float a_ExlosionPower) override; - /** Adds an Item to the list of known items. If the item is already known, does nothing. */ void AddKnownItem(const cItem & a_Item); -protected: + // cEntity overrides: + virtual cItem GetEquippedWeapon(void) const override { return m_Inventory.GetEquippedItem(); } + virtual cItem GetEquippedHelmet(void) const override { return m_Inventory.GetEquippedHelmet(); } + virtual cItem GetEquippedChestplate(void) const override { return m_Inventory.GetEquippedChestplate(); } + virtual cItem GetEquippedLeggings(void) const override { return m_Inventory.GetEquippedLeggings(); } + virtual cItem GetEquippedBoots(void) const override { return m_Inventory.GetEquippedBoots(); } + virtual cItem GetOffHandEquipedItem(void) const override { return m_Inventory.GetShieldSlot(); } + +private: typedef std::vector > AStringVectorVector; @@ -766,12 +738,6 @@ protected: /** List of known items as Ids */ std::set m_KnownItems; - /** Sets the speed and sends it to the client, so that they are forced to move so. */ - virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) override; - - /** Filters out damage for creative mode / friendly fire */ - virtual bool DoTakeDamage(TakeDamageInfo & TDI) override; - /** Called in each tick to handle food-related processing */ void HandleFood(void); @@ -782,8 +748,6 @@ protected: This can be used both for online and offline UUIDs. */ AString GetUUIDFileName(const cUUID & a_UUID); -private: - /** Pins the player to a_Location until Unfreeze() is called. If ManuallyFrozen is false, the player will unfreeze when the chunk is loaded. */ void FreezeInternal(const Vector3d & a_Location, bool a_ManuallyFrozen); @@ -807,4 +771,17 @@ private: If the recipe is already known, does nothing. */ void AddKnownRecipe(UInt32 RecipeId); + void TickFreezeCode(); + + // cEntity overrides: + virtual void ApplyArmorDamage(int DamageBlocked) override; + virtual void BroadcastMovementUpdate(const cClientHandle * a_Exclude = nullptr) override; + virtual bool DoTakeDamage(TakeDamageInfo & TDI) override; + virtual float GetEnchantmentBlastKnockbackReduction() override; + virtual void HandlePhysics(std::chrono::milliseconds a_Dt, cChunk &) override { UNUSED(a_Dt); } + virtual void OnAddToWorld(cWorld & a_World) override; + virtual void OnRemoveFromWorld(cWorld & a_World) override; + virtual void SpawnOn(cClientHandle & a_Client) override; + virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + } ; // tolua_export diff --git a/src/Physics/Explodinator.cpp b/src/Physics/Explodinator.cpp index e12b9bd60..8954f66f5 100644 --- a/src/Physics/Explodinator.cpp +++ b/src/Physics/Explodinator.cpp @@ -96,11 +96,11 @@ namespace Explodinator if (Entity.IsPawn()) { const auto ReducedImpact = Impact - Impact * Entity.GetEnchantmentBlastKnockbackReduction(); - Entity.SetSpeed(Direction.NormalizeCopy() * KnockbackFactor * ReducedImpact); + Entity.AddSpeed(Direction.NormalizeCopy() * KnockbackFactor * ReducedImpact); } else { - Entity.SetSpeed(Direction.NormalizeCopy() * KnockbackFactor * Impact); + Entity.AddSpeed(Direction.NormalizeCopy() * KnockbackFactor * Impact); } // Continue iteration: