diff options
author | Tiger Wang <ziwei.tiger@outlook.com> | 2022-07-11 00:55:11 +0200 |
---|---|---|
committer | Tiger Wang <ziwei.tiger@outlook.com> | 2022-07-19 23:33:09 +0200 |
commit | cd97aa83307ce33068082dc57deee8d922fec432 (patch) | |
tree | 7ea02bf81e102e80df32d527a085c3ecd3f66d44 /src | |
parent | ClientHandle: fix incorrect position floor (diff) | |
download | cuberite-Pickups.tar cuberite-Pickups.tar.gz cuberite-Pickups.tar.bz2 cuberite-Pickups.tar.lz cuberite-Pickups.tar.xz cuberite-Pickups.tar.zst cuberite-Pickups.zip |
Diffstat (limited to '')
-rw-r--r-- | src/BlockEntities/DropSpenserEntity.cpp | 41 | ||||
-rw-r--r-- | src/BlockEntities/JukeboxEntity.cpp | 2 | ||||
-rw-r--r-- | src/Blocks/BroadcastInterface.h | 3 | ||||
-rw-r--r-- | src/BoundingBox.cpp | 16 | ||||
-rw-r--r-- | src/BoundingBox.h | 17 | ||||
-rw-r--r-- | src/Broadcaster.cpp | 2 | ||||
-rw-r--r-- | src/ChunkMap.cpp | 5 | ||||
-rw-r--r-- | src/ClientHandle.cpp | 2 | ||||
-rw-r--r-- | src/ClientHandle.h | 2 | ||||
-rw-r--r-- | src/Entities/Entity.h | 2 | ||||
-rw-r--r-- | src/Entities/ItemFrame.cpp | 2 | ||||
-rw-r--r-- | src/Entities/Pickup.cpp | 407 | ||||
-rw-r--r-- | src/Entities/Pickup.h | 52 | ||||
-rw-r--r-- | src/Entities/Player.cpp | 41 | ||||
-rw-r--r-- | src/Entities/Player.h | 3 | ||||
-rw-r--r-- | src/Mobs/Chicken.cpp | 4 | ||||
-rw-r--r-- | src/Mobs/Monster.cpp | 4 | ||||
-rw-r--r-- | src/Mobs/Mooshroom.cpp | 4 | ||||
-rw-r--r-- | src/Protocol/Protocol.h | 2 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_11.cpp | 2 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_11.h | 2 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_8.cpp | 2 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_8.h | 2 | ||||
-rw-r--r-- | src/UI/SlotArea.cpp | 7 | ||||
-rw-r--r-- | src/World.cpp | 70 | ||||
-rw-r--r-- | src/World.h | 21 | ||||
-rw-r--r-- | src/WorldStorage/WSSAnvil.cpp | 2 |
27 files changed, 394 insertions, 325 deletions
diff --git a/src/BlockEntities/DropSpenserEntity.cpp b/src/BlockEntities/DropSpenserEntity.cpp index b067e1081..7fe89fffc 100644 --- a/src/BlockEntities/DropSpenserEntity.cpp +++ b/src/BlockEntities/DropSpenserEntity.cpp @@ -197,30 +197,25 @@ void cDropSpenserEntity::DropFromSlot(cChunk & a_Chunk, int a_SlotNum) auto Meta = a_Chunk.GetMeta(GetRelPos()); AddDropSpenserDir(dispCoord, Meta); - cItems Pickups; - Pickups.push_back(m_Contents.RemoveOneItem(a_SlotNum)); - - const int PickupSpeed = GetRandomProvider().RandInt(2, 6); // At least 2, at most 6 - int PickupSpeedX = 0, PickupSpeedY = 0, PickupSpeedZ = 0; - switch (Meta & E_META_DROPSPENSER_FACING_MASK) + const auto PickupSpeed = [Meta]() -> Vector3i { - case E_META_DROPSPENSER_FACING_YP: PickupSpeedY = PickupSpeed; break; - case E_META_DROPSPENSER_FACING_YM: PickupSpeedY = -PickupSpeed; break; - case E_META_DROPSPENSER_FACING_XM: PickupSpeedX = -PickupSpeed; break; - case E_META_DROPSPENSER_FACING_XP: PickupSpeedX = PickupSpeed; break; - case E_META_DROPSPENSER_FACING_ZM: PickupSpeedZ = -PickupSpeed; break; - case E_META_DROPSPENSER_FACING_ZP: PickupSpeedZ = PickupSpeed; break; - } - - double MicroX, MicroY, MicroZ; - MicroX = dispCoord.x + 0.5; - MicroY = dispCoord.y + 0.4; // Slightly less than half, to accomodate actual texture hole on DropSpenser - MicroZ = dispCoord.z + 0.5; + const int PickupSpeed = GetRandomProvider().RandInt(2, 6); // At least 2, at most 6. + switch (Meta & E_META_DROPSPENSER_FACING_MASK) + { + case E_META_DROPSPENSER_FACING_YP: return { 0, PickupSpeed, 0 }; + case E_META_DROPSPENSER_FACING_YM: return { 0, -PickupSpeed, 0 }; + case E_META_DROPSPENSER_FACING_XM: return { -PickupSpeed, 0, 0 }; + case E_META_DROPSPENSER_FACING_XP: return { PickupSpeed, 0, 0 }; + case E_META_DROPSPENSER_FACING_ZM: return { 0, 0, -PickupSpeed }; + case E_META_DROPSPENSER_FACING_ZP: return { 0, 0, PickupSpeed }; + } + UNREACHABLE("Unsupported DropSpenser direction"); + }(); + // Where to spawn the pickup. + // Y offset is slightly less than half, to accomodate actual texture hole on DropSpenser. + const auto HolePosition = Vector3d(0.5, 0.4, 0.5) + dispCoord; - m_World->SpawnItemPickups(Pickups, MicroX, MicroY, MicroZ, PickupSpeedX, PickupSpeedY, PickupSpeedZ); + auto Pickup = m_Contents.RemoveOneItem(a_SlotNum); + m_World->SpawnItemPickup(HolePosition, std::move(Pickup), PickupSpeed, 0_tick); // Spawn pickup with no collection delay. } - - - - diff --git a/src/BlockEntities/JukeboxEntity.cpp b/src/BlockEntities/JukeboxEntity.cpp index 065f9cb46..895e59a8b 100644 --- a/src/BlockEntities/JukeboxEntity.cpp +++ b/src/BlockEntities/JukeboxEntity.cpp @@ -115,7 +115,7 @@ bool cJukeboxEntity::EjectRecord(void) return false; } - m_World->SpawnItemPickups(cItem(static_cast<short>(m_Record)), Vector3d(0.5, 0.5, 0.5) + m_Pos, 10); + m_World->SpawnItemPickup(m_Pos.addedY(1), cItem(static_cast<short>(m_Record)), 10); m_World->SetBlockMeta(m_Pos, E_META_JUKEBOX_OFF); m_World->BroadcastSoundParticleEffect(EffectID::SFX_RANDOM_PLAY_MUSIC_DISC, GetPos(), 0); diff --git a/src/Blocks/BroadcastInterface.h b/src/Blocks/BroadcastInterface.h index 6c52a5156..b06318ada 100644 --- a/src/Blocks/BroadcastInterface.h +++ b/src/Blocks/BroadcastInterface.h @@ -7,6 +7,7 @@ // fwd: class cClientHandle; class cCompositeChat; +class cPawn; class cPlayer; class cWorld; enum class EffectID : Int32; @@ -33,7 +34,7 @@ public: virtual void BroadcastChatFatal (const AString & a_Message, const cClientHandle * a_Exclude = nullptr) = 0; virtual void BroadcastChatDeath (const AString & a_Message, const cClientHandle * a_Exclude = nullptr) = 0; virtual void BroadcastChat (const cCompositeChat & a_Message, const cClientHandle * a_Exclude = nullptr) = 0; - virtual void BroadcastCollectEntity (const cEntity & a_Collected, const cEntity & a_Collector, unsigned a_Count, const cClientHandle * a_Exclude = nullptr) = 0; + virtual void BroadcastCollectEntity (const cEntity & a_Collected, const cPawn & a_Collector, unsigned a_Count, const cClientHandle * a_Exclude = nullptr) = 0; virtual void BroadcastDestroyEntity (const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr) = 0; virtual void BroadcastDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle) = 0; virtual void BroadcastEntityEffect (const cEntity & a_Entity, int a_EffectID, int a_Amplifier, int a_Duration, const cClientHandle * a_Exclude = nullptr) = 0; diff --git a/src/BoundingBox.cpp b/src/BoundingBox.cpp index 1356e165c..649850267 100644 --- a/src/BoundingBox.cpp +++ b/src/BoundingBox.cpp @@ -101,7 +101,7 @@ void cBoundingBox::Expand(double a_ExpandX, double a_ExpandY, double a_ExpandZ) -bool cBoundingBox::DoesIntersect(const cBoundingBox & a_Other) +bool cBoundingBox::DoesIntersect(const cBoundingBox & a_Other) const { return ( ((a_Other.m_Min.x <= m_Max.x) && (a_Other.m_Max.x >= m_Min.x)) && // X coords intersect @@ -114,7 +114,7 @@ bool cBoundingBox::DoesIntersect(const cBoundingBox & a_Other) -cBoundingBox cBoundingBox::Union(const cBoundingBox & a_Other) +cBoundingBox cBoundingBox::Union(const cBoundingBox & a_Other) const { return cBoundingBox( std::min(m_Min.x, a_Other.m_Min.x), @@ -130,7 +130,7 @@ cBoundingBox cBoundingBox::Union(const cBoundingBox & a_Other) -bool cBoundingBox::IsInside(Vector3d a_Point) +bool cBoundingBox::IsInside(Vector3d a_Point) const { return IsInside(m_Min, m_Max, a_Point); } @@ -139,7 +139,7 @@ bool cBoundingBox::IsInside(Vector3d a_Point) -bool cBoundingBox::IsInside(double a_X, double a_Y, double a_Z) +bool cBoundingBox::IsInside(double a_X, double a_Y, double a_Z) const { return IsInside(m_Min, m_Max, a_X, a_Y, a_Z); } @@ -148,7 +148,7 @@ bool cBoundingBox::IsInside(double a_X, double a_Y, double a_Z) -bool cBoundingBox::IsInside(cBoundingBox & a_Other) +bool cBoundingBox::IsInside(cBoundingBox & a_Other) const { // If both a_Other's coords are inside this, then the entire a_Other is inside return (IsInside(a_Other.m_Min) && IsInside(a_Other.m_Max)); @@ -158,7 +158,7 @@ bool cBoundingBox::IsInside(cBoundingBox & a_Other) -bool cBoundingBox::IsInside(Vector3d a_Min, Vector3d a_Max) +bool cBoundingBox::IsInside(Vector3d a_Min, Vector3d a_Max) const { // If both coords are inside this, then the entire a_Other is inside return (IsInside(a_Min) && IsInside(a_Max)); @@ -287,7 +287,3 @@ bool cBoundingBox::Intersect(const cBoundingBox & a_Other, cBoundingBox & a_Inte a_Intersection.m_Max.z = std::min(m_Max.z, a_Other.m_Max.z); return (a_Intersection.m_Min.z < a_Intersection.m_Max.z); } - - - - diff --git a/src/BoundingBox.h b/src/BoundingBox.h index e62711658..8ac5e1097 100644 --- a/src/BoundingBox.h +++ b/src/BoundingBox.h @@ -23,6 +23,7 @@ the boxes are considered non-intersecting. */ class cBoundingBox { public: + cBoundingBox(double a_MinX, double a_MaxX, double a_MinY, double a_MaxY, double a_MinZ, double a_MaxZ); cBoundingBox(Vector3d a_Min, Vector3d a_Max); cBoundingBox(Vector3d a_Pos, double a_Radius, double a_Height); @@ -46,22 +47,22 @@ public: void Expand(double a_ExpandX, double a_ExpandY, double a_ExpandZ); /** Returns true if the two bounding boxes intersect */ - bool DoesIntersect(const cBoundingBox & a_Other); + bool DoesIntersect(const cBoundingBox & a_Other) const; /** Returns the union of the two bounding boxes */ - cBoundingBox Union(const cBoundingBox & a_Other); + cBoundingBox Union(const cBoundingBox & a_Other) const; /** Returns true if the point is inside the bounding box */ - bool IsInside(Vector3d a_Point); + bool IsInside(Vector3d a_Point) const; /** Returns true if the point is inside the bounding box */ - bool IsInside(double a_X, double a_Y, double a_Z); + bool IsInside(double a_X, double a_Y, double a_Z) const; /** Returns true if a_Other is inside this bounding box */ - bool IsInside(cBoundingBox & a_Other); + bool IsInside(cBoundingBox & a_Other) const; /** Returns true if a boundingbox specified by a_Min and a_Max is inside this bounding box */ - bool IsInside(Vector3d a_Min, Vector3d a_Max); + bool IsInside(Vector3d a_Min, Vector3d a_Max) const; /** Returns true if the specified point is inside the bounding box specified by its min / max corners */ static bool IsInside(Vector3d a_Min, Vector3d a_Max, Vector3d a_Point); @@ -107,7 +108,3 @@ protected: Vector3d m_Max; } ; // tolua_export - - - - diff --git a/src/Broadcaster.cpp b/src/Broadcaster.cpp index 9369b8062..4aa7cd0fc 100644 --- a/src/Broadcaster.cpp +++ b/src/Broadcaster.cpp @@ -215,7 +215,7 @@ void cWorld::BroadcastChat(const cCompositeChat & a_Message, const cClientHandle -void cWorld::BroadcastCollectEntity(const cEntity & a_Collected, const cEntity & a_Collector, unsigned a_Count, const cClientHandle * a_Exclude) +void cWorld::BroadcastCollectEntity(const cEntity & a_Collected, const cPawn & a_Collector, unsigned a_Count, const cClientHandle * a_Exclude) { ForClientsWithEntity(a_Collected, *this, a_Exclude, [&](cClientHandle & a_Client) { diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 82631e6ed..110a1f8f7 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -916,14 +916,15 @@ void cChunkMap::AddEntity(OwnedEntity a_Entity) } const auto EntityPtr = a_Entity.get(); - ASSERT(EntityPtr->GetWorld() == m_World); auto & Chunk = ConstructChunk(a_Entity->GetChunkX(), a_Entity->GetChunkZ()); Chunk.AddEntity(std::move(a_Entity)); - EntityPtr->OnAddToWorld(*m_World); ASSERT(!EntityPtr->IsTicking()); + ASSERT(EntityPtr->GetWorld() == m_World); + EntityPtr->SetIsTicking(true); + EntityPtr->OnAddToWorld(*m_World); cPluginManager::Get()->CallHookSpawnedEntity(*m_World, *EntityPtr); } diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 3f56dfedf..dd8582302 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -2535,7 +2535,7 @@ void cClientHandle::SendChunkData(int a_ChunkX, int a_ChunkZ, const ContiguousBy -void cClientHandle::SendCollectEntity(const cEntity & a_Collected, const cEntity & a_Collector, unsigned a_Count) +void cClientHandle::SendCollectEntity(const cEntity & a_Collected, const cPawn & a_Collector, unsigned a_Count) { m_Protocol->SendCollectEntity(a_Collected, a_Collector, a_Count); } diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 9f3cbb18d..cd49417a5 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -169,7 +169,7 @@ public: // tolua_export void SendChatSystem (const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData = ""); void SendChatSystem (const cCompositeChat & a_Message); void SendChunkData (int a_ChunkX, int a_ChunkZ, ContiguousByteBufferView a_ChunkData); - void SendCollectEntity (const cEntity & a_Collected, const cEntity & a_Collector, unsigned a_Count); // tolua_export + void SendCollectEntity (const cEntity & a_Collected, const cPawn & a_Collector, unsigned a_Count); // tolua_export void SendDestroyEntity (const cEntity & a_Entity); // tolua_export void SendDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle); // tolua_export void SendDisconnect (const AString & a_Reason); diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 2f9ba229b..574918bda 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -208,7 +208,7 @@ public: int GetChunkZ(void) const { return FloorC(m_Position.z / cChunkDef::Width); } // Get the Entity's axis aligned bounding box, with absolute (world-relative) coordinates. - cBoundingBox GetBoundingBox() const { return cBoundingBox(GetPosition(), GetWidth() / 2, GetHeight()); } + cBoundingBox GetBoundingBox() const { return cBoundingBox(m_Position, m_Width / 2, m_Height); } void SetHeadYaw (double a_HeadYaw); void SetMass (double a_Mass); diff --git a/src/Entities/ItemFrame.cpp b/src/Entities/ItemFrame.cpp index 90d3bb049..3e718661f 100644 --- a/src/Entities/ItemFrame.cpp +++ b/src/Entities/ItemFrame.cpp @@ -39,7 +39,7 @@ bool cItemFrame::DoTakeDamage(TakeDamageInfo & a_TDI) const auto FlyOutSpeed = AddFaceDirection(Vector3i(), ProtocolFaceToBlockFace(m_Facing)) * 2; // Spawn the frame's held item: - GetWorld()->SpawnItemPickup(SpawnPosition, m_Item, FlyOutSpeed); + GetWorld()->SpawnItemPickup(SpawnPosition, std::move(m_Item), FlyOutSpeed); } // In any case we have a held item and were hit by a player, so clear it: diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 05d1cd185..699bd5944 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -19,73 +19,94 @@ -class cPickupCombiningCallback +class PickupCombiningCallback { public: - cPickupCombiningCallback(Vector3d a_Position, cPickup * a_Pickup) : + + PickupCombiningCallback(cPickup * a_Pickup) : + m_Pickup(a_Pickup), m_FoundMatchingPickup(false), - m_Position(a_Position), - m_Pickup(a_Pickup) + m_MaxStackSize(a_Pickup->GetItem().GetMaxStackSize()) { } - bool operator () (cEntity & a_Entity) + ~PickupCombiningCallback() { - ASSERT(a_Entity.IsTicking()); - if (!a_Entity.IsPickup() || (a_Entity.GetUniqueID() <= m_Pickup->GetUniqueID()) || !a_Entity.IsOnGround()) + if (m_FoundMatchingPickup) { - return false; + m_Pickup->GetWorld()->BroadcastEntityMetadata(*m_Pickup); } + } + bool operator()(cEntity & a_Entity) + { + ASSERT(a_Entity.IsTicking()); - Vector3d EntityPos = a_Entity.GetPosition(); - double Distance = (EntityPos - m_Position).Length(); + if (!a_Entity.IsPickup() || (&a_Entity == m_Pickup)) + { + return false; + } auto & OtherPickup = static_cast<cPickup &>(a_Entity); - cItem & Item = OtherPickup.GetItem(); - if ((Distance < 1.2) && Item.IsEqual(m_Pickup->GetItem()) && OtherPickup.CanCombine()) - { - short CombineCount = static_cast<short>(Item.m_ItemCount); - if ((CombineCount + static_cast<short>(m_Pickup->GetItem().m_ItemCount)) > static_cast<short>(Item.GetMaxStackSize())) - { - CombineCount = Item.GetMaxStackSize() - m_Pickup->GetItem().m_ItemCount; - } + cItem & OtherItem = OtherPickup.GetItem(); + cItem & Item = m_Pickup->GetItem(); - if (CombineCount <= 0) - { - return false; - } + if (!Item.IsEqual(OtherItem) || !OtherPickup.CanCombine() || OtherPickup.IsCollected()) + { + return false; + } - m_Pickup->GetItem().AddCount(static_cast<char>(CombineCount)); - Item.m_ItemCount -= static_cast<char>(CombineCount); + // The recipient pickup is the one with more items, and vice versa for the donor. + auto [Recipient, Donor] = Item.m_ItemCount > OtherItem.m_ItemCount ? std::make_pair(m_Pickup, &OtherPickup) : std::make_pair(&OtherPickup, m_Pickup); - if (Item.m_ItemCount <= 0) - { - a_Entity.GetWorld()->BroadcastCollectEntity(a_Entity, *m_Pickup, static_cast<unsigned>(CombineCount)); - a_Entity.Destroy(); + // Try to combine, and stop if we're the one who is full: + if (!CombineInto(Recipient->GetItem(), Donor->GetItem(), m_MaxStackSize)) + { + return Recipient == m_Pickup; + } - // Reset the timer - m_Pickup->SetAge(0); - } - else - { - a_Entity.GetWorld()->BroadcastEntityMetadata(a_Entity); - } - m_FoundMatchingPickup = true; + if (Donor->GetItem().m_ItemCount == 0) + { + Donor->Destroy(); + m_Pickup = Recipient; } + else + { + OtherPickup.GetWorld()->BroadcastEntityMetadata(OtherPickup); + } + + m_FoundMatchingPickup = true; return false; } - inline bool FoundMatchingPickup() + static bool CombineInto(cItem & a_Recipient, cItem & a_Donor, const char a_MaxStackSize) { - return m_FoundMatchingPickup; + // Check for plugin shenanigans: + if (a_Recipient.m_ItemCount > a_MaxStackSize) + { + return false; + } + + const auto Move = std::min(a_Donor.m_ItemCount, static_cast<char>(a_MaxStackSize - a_Recipient.m_ItemCount)); + + // Stop if recipient full: + if (Move == 0) + { + return false; + } + + a_Donor.m_ItemCount -= Move; + a_Recipient.m_ItemCount += Move; + return true; } -protected: - bool m_FoundMatchingPickup; +private: - Vector3d m_Position; cPickup * m_Pickup; + + bool m_FoundMatchingPickup; + + char m_MaxStackSize; }; @@ -95,14 +116,12 @@ protected: //////////////////////////////////////////////////////////////////////////////// // cPickup: -cPickup::cPickup(Vector3d a_Pos, const cItem & a_Item, bool IsPlayerCreated, Vector3f a_Speed, int a_LifetimeTicks, bool a_CanCombine): - Super(etPickup, a_Pos, 0.25f, 0.25f), - m_Timer(0), - m_Item(a_Item), - m_bCollected(false), - m_bIsPlayerCreated(IsPlayerCreated), - m_bCanCombine(a_CanCombine), - m_Lifetime(cTickTime(a_LifetimeTicks)) +cPickup::cPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cTickTime a_CollectionDelay, cTickTime a_Lifetime) : + Super(etPickup, a_Position, 0.25f, 0.25f), + m_Item(std::move(a_Item)), + m_RemainingCollectionDelay(a_CollectionDelay), + m_RemainingLifetime(a_Lifetime), + m_IsCollected(false) { SetGravity(-16.0f); SetAirDrag(0.02f); @@ -115,127 +134,28 @@ cPickup::cPickup(Vector3d a_Pos, const cItem & a_Item, bool IsPlayerCreated, Vec -void cPickup::SpawnOn(cClientHandle & a_Client) -{ - a_Client.SendSpawnEntity(*this); - a_Client.SendEntityMetadata(*this); -} - - - - - -void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - Super::Tick(a_Dt, a_Chunk); - if (!IsTicking()) - { - // The base class tick destroyed us - return; - } - BroadcastMovementUpdate(); // Notify clients of position - - m_Timer += a_Dt; - - if (!m_bCollected) - { - int BlockY = POSY_TOINT; - int BlockX = POSX_TOINT; - int BlockZ = POSZ_TOINT; - - if ((BlockY >= 0) && (BlockY < cChunkDef::Height)) // Don't do anything except for falling when outside the world - { - // 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); - - // 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. - m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick. - if (m_Timer > std::chrono::milliseconds(500)) - { - Destroy(); - return; - } - } - - // Try to combine the pickup with adjacent same-item pickups: - if ((m_Item.m_ItemCount < m_Item.GetMaxStackSize()) && IsOnGround() && CanCombine()) // Don't combine if already full or not on ground - { - // By using a_Chunk's ForEachEntity() instead of cWorld's, pickups don't combine across chunk boundaries. - // That is a small price to pay for not having to traverse the entire world for each entity. - // The speedup in the tick thread is quite considerable. - cPickupCombiningCallback PickupCombiningCallback(GetPosition(), this); - a_Chunk.ForEachEntity(PickupCombiningCallback); - if (PickupCombiningCallback.FoundMatchingPickup()) - { - m_World->BroadcastEntityMetadata(*this); - } - } - } - } - else - { - if (m_Timer > std::chrono::milliseconds(500)) // 0.5 second - { - Destroy(); - return; - } - } - - if (m_Timer > m_Lifetime) - { - Destroy(); - return; - } -} - - - - - -bool cPickup::DoTakeDamage(TakeDamageInfo & a_TDI) -{ - if (a_TDI.DamageType == dtCactusContact) - { - Destroy(); - return true; - } - - return Super::DoTakeDamage(a_TDI); -} - - - - - bool cPickup::CollectedBy(cEntity & a_Dest) { - if (m_bCollected) + if (m_IsCollected) { - // LOG("Pickup %d cannot be collected by \"%s\", because it has already been collected.", m_UniqueID, a_Dest->GetName().c_str()); - return false; // It's already collected! + // It's already collected! + return false; } - // This type of entity can't pickup items if (!a_Dest.IsPawn()) { + // Pawns can't pick up items: return false; } - // Two seconds if player created the pickup (vomiting), half a second if anything else - if (m_Timer < (m_bIsPlayerCreated ? std::chrono::seconds(2) : std::chrono::milliseconds(500))) + if (m_RemainingCollectionDelay > m_RemainingCollectionDelay.zero()) { - // LOG("Pickup %d cannot be collected by \"%s\", because it is not old enough.", m_UniqueID, a_Dest->GetName().c_str()); - return false; // Not old enough + // Not old enough to be collected! + return false; } - // Checking for villagers - if (!a_Dest.IsPlayer() && a_Dest.IsMob()) + if (a_Dest.IsMob()) { - auto & Mob = static_cast<cMonster &>(a_Dest); if (Mob.GetMobType() == mtVillager) { @@ -249,21 +169,9 @@ bool cPickup::CollectedBy(cEntity & a_Dest) char NumAdded = Villager.GetInventory().AddItem(m_Item); if (NumAdded > 0) { - m_Item.m_ItemCount -= NumAdded; - m_World->BroadcastCollectEntity(*this, a_Dest, static_cast<unsigned>(NumAdded)); - - // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;) - m_World->BroadcastSoundEffect("entity.item.pickup", GetPosition(), 0.3f, (1.2f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)); - if (m_Item.m_ItemCount <= 0) - { - // All of the pickup has been collected, schedule the pickup for destroying - m_bCollected = true; - } - m_Timer = std::chrono::milliseconds(0); + OnCollectedBy(Mob, NumAdded); return true; } - // Pickup cannot be collected because the entity has not enough space - return false; } } else if (a_Dest.IsPlayer()) @@ -278,7 +186,7 @@ bool cPickup::CollectedBy(cEntity & a_Dest) if (cRoot::Get()->GetPluginManager()->CallHookCollectingPickup(Player, *this)) { - // LOG("Pickup %d cannot be collected by \"%s\", because a plugin has said no.", m_UniqueID, a_Dest->GetName().c_str()); + // Collection refused because a plugin has said no: return false; } @@ -295,21 +203,156 @@ bool cPickup::CollectedBy(cEntity & a_Dest) default: break; } - m_Item.m_ItemCount -= NumAdded; - m_World->BroadcastCollectEntity(*this, a_Dest, static_cast<unsigned>(NumAdded)); - - // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;) - m_World->BroadcastSoundEffect("entity.item.pickup", GetPosition(), 0.3f, (1.2f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)); - if (m_Item.m_ItemCount <= 0) - { - // All of the pickup has been collected, schedule the pickup for destroying - m_bCollected = true; - } - m_Timer = std::chrono::milliseconds(0); + OnCollectedBy(Player, NumAdded); return true; } } - // LOG("Pickup %d cannot be collected by \"%s\", because there's no space in the inventory.", a_Dest->GetName().c_str(), m_UniqueID); + // Either destination cannot collect, or no space in inventory: return false; } + + + + + +bool cPickup::TryCombineWithQueuedEntity(cEntity & a_Entity, const cBoundingBox & a_CombineBounds, cItem & a_Item, const char a_MaxStackSize) +{ + if (!a_Entity.IsPickup()) + { + return false; + } + + auto & Pickup = static_cast<cPickup &>(a_Entity); + auto & RecipientItem = Pickup.GetItem(); + + if (!RecipientItem.IsEqual(a_Item) || !a_CombineBounds.DoesIntersect(Pickup.GetBoundingBox())) + { + return false; + } + + PickupCombiningCallback::CombineInto(RecipientItem, a_Item, a_MaxStackSize); + + return a_Item.m_ItemCount == 0; +} + + + + + +void cPickup::OnCollectedBy(cPawn & a_Collector, char a_CollectionCount) +{ + m_Item.m_ItemCount -= a_CollectionCount; + + if (m_Item.m_ItemCount <= 0) + { + // All of the pickup has been collected, schedule the pickup for destroying: + m_IsCollected = true; + + // Play the collection animation (only when fully collected): + m_World->BroadcastCollectEntity(*this, a_Collector, static_cast<unsigned>(a_CollectionCount)); + + // Wait 0.5s for collection animation to play: + m_RemainingLifetime = std::chrono::milliseconds(500); + } + else + { + // Our count changed, so try to combine again: + TryCombineWithPickupsInWorld(); + } + + // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;) + m_World->BroadcastSoundEffect("entity.item.pickup", GetPosition(), 0.3f, (1.2f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)); +} + + + + + +void cPickup::TryCombineWithPickupsInWorld() +{ + if (!m_IsCombinable) + { + return; + } + + PickupCombiningCallback PickupCombiningCallback(this); + m_World->ForEachEntityInBox({ GetPosition(), 0.25 / 2, 0.25 }, PickupCombiningCallback); +} + + + + + +bool cPickup::DoTakeDamage(TakeDamageInfo & a_TDI) +{ + if (a_TDI.DamageType == dtCactusContact) + { + Destroy(); + return true; + } + + return Super::DoTakeDamage(a_TDI); +} + + + + + +void cPickup::OnAddToWorld(cWorld & a_World) +{ + Super::OnAddToWorld(a_World); + + // Say when going through a portal, try to combine: + TryCombineWithPickupsInWorld(); +} + + + + + +void cPickup::SpawnOn(cClientHandle & a_Client) +{ + a_Client.SendSpawnEntity(*this); + a_Client.SendEntityMetadata(*this); +} + + + + + +void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + Super::Tick(a_Dt, a_Chunk); + + if (!IsTicking()) + { + // The base class tick destroyed us: + return; + } + + BroadcastMovementUpdate(); // Notify clients of position. + + m_RemainingCollectionDelay -= a_Dt; + m_RemainingLifetime -= a_Dt; + + if (m_RemainingLifetime <= m_RemainingLifetime.zero()) + { + Destroy(); + return; + } + + if (m_IsCollected) + { + return; + } + + // Don't combine if already full, we haven't moved, or combination is disabled: + if ((m_LastPosition == GetPosition()) || (m_Item.m_ItemCount >= m_Item.GetMaxStackSize())) + { + return; + } + + // Try to combine the pickup with adjacent same-item pickups: + TryCombineWithPickupsInWorld(); +} diff --git a/src/Entities/Pickup.h b/src/Entities/Pickup.h index c995055ff..b03ae0846 100644 --- a/src/Entities/Pickup.h +++ b/src/Entities/Pickup.h @@ -8,7 +8,7 @@ -class cPlayer; +class cPawn; @@ -26,57 +26,61 @@ public: // tolua_export CLASS_PROTODEF(cPickup) - cPickup(Vector3d a_Pos, const cItem & a_Item, bool IsPlayerCreated, Vector3f a_Speed = Vector3f(), int a_LifetimeTicks = 6000, bool a_CanCombine = true); + cPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cTickTime a_CollectionDelay, cTickTime a_Lifetime); cItem & GetItem(void) {return m_Item; } // tolua_export const cItem & GetItem(void) const {return m_Item; } - virtual void SpawnOn(cClientHandle & a_ClientHandle) override; - bool CollectedBy(cEntity & a_Dest); // tolua_export - virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - - virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override; - - virtual bool DoesPreventBlockPlacement(void) const override { return false; } + static bool TryCombineWithQueuedEntity(cEntity & a_Entity, const cBoundingBox & a_CombineBounds, cItem & a_Item, const char a_MaxStackSize); /** Returns whether this pickup is allowed to combine with other similar pickups */ - bool CanCombine(void) const { return m_bCanCombine; } // tolua_export + bool CanCombine(void) const { return m_IsCombinable; } // tolua_export /** Sets whether this pickup is allowed to combine with other similar pickups */ - void SetCanCombine(bool a_CanCombine) { m_bCanCombine = a_CanCombine; } // tolua_export + void SetCanCombine(bool a_CanCombine) { m_IsCombinable = a_CanCombine; } // tolua_export /** Returns the number of ticks that this entity has existed */ - int GetAge(void) const { return std::chrono::duration_cast<cTickTime>(m_Timer).count(); } // tolua_export + int GetAge(void) const { LOGWARNING("GetAge is deprecated, use GetTicksAlive"); return m_TicksAlive; } // tolua_export /** Set the number of ticks that this entity has existed */ - void SetAge(int a_Age) { m_Timer = cTickTime(a_Age); } // tolua_export + void SetAge(int a_Age) { LOGWARNING("SetAge is deprecated, use SetLifetime"); m_TicksAlive = a_Age; } // tolua_export /** Returns the number of ticks that this pickup should live for */ - int GetLifetime(void) const { return std::chrono::duration_cast<cTickTime>(m_Lifetime).count(); } // tolua_export + int GetLifetime(void) const { return std::chrono::duration_cast<cTickTime>(m_RemainingLifetime).count(); } // tolua_export /** Set the number of ticks that this pickup should live for */ - void SetLifetime(int a_Lifetime) { m_Lifetime = cTickTime(a_Lifetime); } // tolua_export + void SetLifetime(int a_Lifetime) { m_RemainingLifetime = cTickTime(a_Lifetime); } // tolua_export /** Returns true if the pickup has already been collected */ - bool IsCollected(void) const { return m_bCollected; } // tolua_export + bool IsCollected(void) const { return m_IsCollected; } // tolua_export /** Returns true if created by player (i.e. vomiting), used for determining picking-up delay time */ - bool IsPlayerCreated(void) const { return m_bIsPlayerCreated; } // tolua_export + bool IsPlayerCreated(void) const { LOGWARNING("IsPlayerCreated is deprecated"); return false; } // tolua_export private: - /** The number of ticks that the entity has existed / timer between collect and destroy; in msec */ - std::chrono::milliseconds m_Timer; + void OnCollectedBy(cPawn & a_Collector, char a_CollectionCount); - cItem m_Item; + void TryCombineWithPickupsInWorld(); - bool m_bCollected; + // cEntity overrides: + virtual bool DoesPreventBlockPlacement(void) const override { return false; } + virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override; + virtual void OnAddToWorld(cWorld & a_World) override; + virtual void SpawnOn(cClientHandle & a_ClientHandle) override; + virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + + cItem m_Item; - bool m_bIsPlayerCreated; + /** How much time left until the pickup can be picked up. + Two seconds if player created the pickup (vomiting), half a second if anything else, but plugin-customisable. */ + std::chrono::milliseconds m_RemainingCollectionDelay; - bool m_bCanCombine; + /** You have thirty seconds to live. - Medic TF2 */ + std::chrono::milliseconds m_RemainingLifetime; - std::chrono::milliseconds m_Lifetime; + bool m_IsCollected; + bool m_IsCombinable; }; // tolua_export diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 04a7f9be0..43632f551 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -17,6 +17,7 @@ #include "../Items/ItemHandler.h" #include "../FastRandom.h" #include "../ClientHandle.h" +#include "Entities/Pickup.h" #include "../WorldStorage/StatisticsSerializer.h" #include "../CompositeChat.h" @@ -475,24 +476,6 @@ bool cPlayer::IsStanding() const -void cPlayer::TossItems(const cItems & a_Items) -{ - if (IsGameModeSpectator()) // Players can't toss items in spectator - { - return; - } - - m_Stats.Custom[CustomStatistic::Drop] += static_cast<StatisticsManager::StatValue>(a_Items.Size()); - - const auto Speed = (GetLookVector() + Vector3d(0, 0.2, 0)) * 6; // A dash of height and a dollop of speed - const auto Position = GetEyePosition() - Vector3d(0, 0.2, 0); // Correct for eye-height weirdness - m_World->SpawnItemPickups(a_Items, Position, Speed, true); // 'true' because created by player -} - - - - - void cPlayer::SetIsInBed(const bool a_GoToBed) { if (a_GoToBed && IsStanding()) @@ -1719,7 +1702,6 @@ void cPlayer::SetDraggingItem(const cItem & a_Item) void cPlayer::TossEquippedItem(char a_Amount) { - cItems Drops; cItem DroppedItem(GetInventory().GetEquippedItem()); if (!DroppedItem.IsEmpty()) { @@ -1732,10 +1714,8 @@ void cPlayer::TossEquippedItem(char a_Amount) GetInventory().GetHotbarGrid().ChangeSlotCount(GetInventory().GetEquippedSlotNum() /* Returns hotbar subslot, which HotbarGrid takes */, -a_Amount); DroppedItem.m_ItemCount = NewAmount; - Drops.push_back(DroppedItem); + TossPickup(DroppedItem); } - - TossItems(Drops); } @@ -1763,13 +1743,12 @@ void cPlayer::ReplaceOneEquippedItemTossRest(const cItem & a_Item) void cPlayer::TossHeldItem(char a_Amount) { - cItems Drops; cItem & Item = GetDraggingItem(); if (!Item.IsEmpty()) { char OriginalItemAmount = Item.m_ItemCount; Item.m_ItemCount = std::min(OriginalItemAmount, a_Amount); - Drops.push_back(Item); + TossPickup(Item); if (OriginalItemAmount > a_Amount) { @@ -1780,8 +1759,6 @@ void cPlayer::TossHeldItem(char a_Amount) Item.Empty(); } } - - TossItems(Drops); } @@ -1790,10 +1767,16 @@ void cPlayer::TossHeldItem(char a_Amount) void cPlayer::TossPickup(const cItem & a_Item) { - cItems Drops; - Drops.push_back(a_Item); + if (IsGameModeSpectator()) // Players can't toss items in spectator + { + return; + } + + m_Stats.Custom[CustomStatistic::Drop] += 1U; - TossItems(Drops); + const auto Speed = (GetLookVector() + Vector3d(0, 0.2, 0)) * 6; // A dash of height and a dollop of speed. + const auto Position = GetEyePosition() - Vector3d(0, 0.2, 0); // Eye position, corrected for eye-height weirdness. + m_World->SpawnItemPickup(Position, cItem(a_Item), Speed, 40_tick); // 2s delay before vomited pickups can be collected. } diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 87b3accda..03b6504ef 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -394,9 +394,6 @@ public: /** Returns true if a player is standing normally, that is, in a neutral pose. */ bool IsStanding() const; - /** Tosses a list of items. */ - void TossItems(const cItems & a_Items); - /** Sets a player's in-bed state. We can't be sure plugins will keep this value updated, so no exporting. If value is false (not in bed), will update players of the fact that they have been ejected from the bed. */ diff --git a/src/Mobs/Chicken.cpp b/src/Mobs/Chicken.cpp index cc9609450..07929b3f4 100644 --- a/src/Mobs/Chicken.cpp +++ b/src/Mobs/Chicken.cpp @@ -38,10 +38,8 @@ void cChicken::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_EggDropTimer == 12000 ) { - cItems Drops; m_EggDropTimer = 0; - Drops.emplace_back(E_ITEM_EGG, static_cast<char>(1)); - m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ(), 10); + m_World->SpawnItemPickup(GetPosition(), cItem(E_ITEM_EGG)); } else { diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index e05264f9f..8e0d3a179 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -1514,12 +1514,12 @@ void cMonster::AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned auto MaxStackSize = static_cast<unsigned int>(cItem(a_Item).GetMaxStackSize()); while (Count > MaxStackSize) { - a_Drops.emplace_back(a_Item, MaxStackSize, a_ItemHealth); + a_Drops.emplace_back(a_Item, static_cast<char>(MaxStackSize), a_ItemHealth); Count -= MaxStackSize; } if (Count > 0) { - a_Drops.emplace_back(a_Item, Count, a_ItemHealth); + a_Drops.emplace_back(a_Item, static_cast<char>(Count), a_ItemHealth); } } diff --git a/src/Mobs/Mooshroom.cpp b/src/Mobs/Mooshroom.cpp index 35eb7f3d1..ca96da081 100644 --- a/src/Mobs/Mooshroom.cpp +++ b/src/Mobs/Mooshroom.cpp @@ -64,9 +64,7 @@ void cMooshroom::OnRightClicked(cPlayer & a_Player) a_Player.UseEquippedItem(); } - cItems Drops; - Drops.emplace_back(E_BLOCK_RED_MUSHROOM, static_cast<char>(5), static_cast<char>(0)); - m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ(), 10); + m_World->SpawnItemPickup(GetPosition(), cItem(E_BLOCK_RED_MUSHROOM, static_cast<char>(5))); m_World->SpawnMob(GetPosX(), GetPosY(), GetPosZ(), mtCow, false); Destroy(); } break; diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index f1d85f014..cbaffb052 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -387,7 +387,7 @@ public: virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) = 0; virtual void SendChatRaw (const AString & a_MessageRaw, eChatType a_Type) = 0; virtual void SendChunkData (ContiguousByteBufferView a_ChunkData) = 0; - virtual void SendCollectEntity (const cEntity & a_Collected, const cEntity & a_Collector, unsigned a_Count) = 0; + virtual void SendCollectEntity (const cEntity & a_Collected, const cPawn & a_Collector, unsigned a_Count) = 0; virtual void SendDestroyEntity (const cEntity & a_Entity) = 0; virtual void SendDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle) = 0; virtual void SendDisconnect (const AString & a_Reason) = 0; diff --git a/src/Protocol/Protocol_1_11.cpp b/src/Protocol/Protocol_1_11.cpp index 73951fb70..cc41ebcd6 100644 --- a/src/Protocol/Protocol_1_11.cpp +++ b/src/Protocol/Protocol_1_11.cpp @@ -319,7 +319,7 @@ namespace Metadata_1_11 //////////////////////////////////////////////////////////////////////////////// // cProtocol_1_11_0: -void cProtocol_1_11_0::SendCollectEntity(const cEntity & a_Collected, const cEntity & a_Collector, unsigned a_Count) +void cProtocol_1_11_0::SendCollectEntity(const cEntity & a_Collected, const cPawn & a_Collector, unsigned a_Count) { ASSERT(m_State == 3); // In game mode? diff --git a/src/Protocol/Protocol_1_11.h b/src/Protocol/Protocol_1_11.h index 5bf03a9dd..1e583b31b 100644 --- a/src/Protocol/Protocol_1_11.h +++ b/src/Protocol/Protocol_1_11.h @@ -32,7 +32,7 @@ public: protected: - virtual void SendCollectEntity (const cEntity & a_Collected, const cEntity & a_Collector, unsigned a_Count) override; + virtual void SendCollectEntity (const cEntity & a_Collected, const cPawn & a_Collector, unsigned a_Count) override; virtual void SendEntityAnimation (const cEntity & a_Entity, EntityAnimation a_Animation) override; virtual void SendHideTitle (void) override; virtual void SendResetTitle (void) override; diff --git a/src/Protocol/Protocol_1_8.cpp b/src/Protocol/Protocol_1_8.cpp index cbb13e68e..e175223b6 100644 --- a/src/Protocol/Protocol_1_8.cpp +++ b/src/Protocol/Protocol_1_8.cpp @@ -372,7 +372,7 @@ void cProtocol_1_8_0::SendChunkData(const ContiguousByteBufferView a_ChunkData) -void cProtocol_1_8_0::SendCollectEntity(const cEntity & a_Collected, const cEntity & a_Collector, unsigned a_Count) +void cProtocol_1_8_0::SendCollectEntity(const cEntity & a_Collected, const cPawn & a_Collector, unsigned a_Count) { UNUSED(a_Count); ASSERT(m_State == 3); // In game mode? diff --git a/src/Protocol/Protocol_1_8.h b/src/Protocol/Protocol_1_8.h index eaa8813be..eec8dd014 100644 --- a/src/Protocol/Protocol_1_8.h +++ b/src/Protocol/Protocol_1_8.h @@ -56,7 +56,7 @@ public: virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) override; virtual void SendChatRaw (const AString & a_MessageRaw, eChatType a_Type) override; virtual void SendChunkData (ContiguousByteBufferView a_ChunkData) override; - virtual void SendCollectEntity (const cEntity & a_Collected, const cEntity & a_Collector, unsigned a_Count) override; + virtual void SendCollectEntity (const cEntity & a_Collected, const cPawn & a_Collector, unsigned a_Count) override; virtual void SendDestroyEntity (const cEntity & a_Entity) override; virtual void SendDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle) override; virtual void SendDisconnect (const AString & a_Reason) override; diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp index bf328154d..edd2a4835 100644 --- a/src/UI/SlotArea.cpp +++ b/src/UI/SlotArea.cpp @@ -2742,18 +2742,15 @@ void cSlotAreaTemporary::TossItems(cPlayer & a_Player, int a_Begin, int a_End) return; } - cItems Drops; for (int i = a_Begin; i < a_End; i++) { cItem & Item = itr->second[static_cast<size_t>(i)]; if (!Item.IsEmpty()) { - Drops.push_back(Item); + a_Player.TossPickup(Item); + Item.Empty(); } - Item.Empty(); } // for i - itr->second[] - - a_Player.TossItems(Drops); } diff --git a/src/World.cpp b/src/World.cpp index 57ba656e8..35f41d389 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1817,9 +1817,7 @@ void cWorld::SpawnItemPickups(const cItems & a_Pickups, Vector3d a_Pos, double a float SpeedY = static_cast<float>(a_FlyAwaySpeed * Random.RandInt(40, 50)); float SpeedZ = static_cast<float>(a_FlyAwaySpeed * Random.RandInt(-10, 10)); - auto Pickup = std::make_unique<cPickup>(a_Pos, *itr, a_IsPlayerCreated, Vector3f{SpeedX, SpeedY, SpeedZ}); - auto PickupPtr = Pickup.get(); - PickupPtr->Initialize(std::move(Pickup), *this); + SpawnItemPickup(a_Pos, cItem(*itr), { SpeedX, SpeedY, SpeedZ }); } } @@ -1836,9 +1834,7 @@ void cWorld::SpawnItemPickups(const cItems & a_Pickups, Vector3d a_Pos, Vector3d continue; } - auto pickup = std::make_unique<cPickup>(a_Pos, *itr, a_IsPlayerCreated, a_Speed); - auto pickupPtr = pickup.get(); - pickupPtr->Initialize(std::move(pickup), *this); + SpawnItemPickup(a_Pos, cItem(*itr), a_Speed); } } @@ -1846,15 +1842,56 @@ void cWorld::SpawnItemPickups(const cItems & a_Pickups, Vector3d a_Pos, Vector3d -UInt32 cWorld::SpawnItemPickup(Vector3d a_Pos, const cItem & a_Item, Vector3f a_Speed, int a_LifetimeTicks, bool a_CanCombine) +UInt32 cWorld::SpawnItemPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cTickTime a_CollectionDelay, cTickTime a_Lifetime, bool a_CanCombine) { - auto pickup = std::make_unique<cPickup>(a_Pos, a_Item, false, a_Speed, a_LifetimeTicks, a_CanCombine); - auto pickupPtr = pickup.get(); - if (!pickupPtr->Initialize(std::move(pickup), *this)) + auto Pickup = std::make_unique<cPickup>(a_Position, std::move(a_Item), a_Speed, a_CollectionDelay, a_Lifetime); + auto PickupPtr = Pickup.get(); + + if (!PickupPtr->Initialize(std::move(Pickup), *this)) { return cEntity::INVALID_ID; } - return pickupPtr->GetUniqueID(); + + return PickupPtr->GetUniqueID(); +} + + + + + +UInt32 cWorld::SpawnItemPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cBoundingBox a_CombineBounds, cTickTime a_CollectionDelay, cTickTime a_Lifetime) +{ + const auto MaxStackSize = a_Item.GetMaxStackSize(); + + for (const auto & [Entity, World] : m_EntitiesToAdd) + { + if (cPickup::TryCombineWithQueuedEntity(*Entity, a_CombineBounds, a_Item, MaxStackSize)) + { + return Entity->GetUniqueID(); + } + } + + return SpawnItemPickup(a_Position, std::move(a_Item), a_Speed, a_CollectionDelay, a_Lifetime, true); +} + + + + + +UInt32 cWorld::SpawnItemPickup(Vector3d a_Position, cItem && a_Item, double a_SpeedMultiplier, cTickTime a_CollectionDelay, cTickTime a_Lifetime, bool a_CanCombine) +{ + auto & Random = GetRandomProvider(); + const auto InitialSpeed = Vector3d(Random.RandReal(-2.0, 2.0), 4.0, Random.RandReal(-2.0, 2.0)) * a_SpeedMultiplier; + return SpawnItemPickup(a_Position, std::move(a_Item), InitialSpeed, a_CollectionDelay, a_Lifetime, a_CanCombine); +} + + + + + +UInt32 cWorld::SpawnItemPickup(Vector3d a_Pos, const cItem & a_Item, Vector3f a_Speed, int a_LifetimeTicks, bool a_CanCombine) +{ + return SpawnItemPickup(a_Pos, cItem(a_Item), a_Speed, 0_tick, cTickTime(a_LifetimeTicks), a_CanCombine); } @@ -2075,12 +2112,19 @@ bool cWorld::DigBlock(Vector3i a_BlockPos, const cEntity * a_Digger) bool cWorld::DropBlockAsPickups(Vector3i a_BlockPos, const cEntity * a_Digger, const cItem * a_Tool) { - auto pickups = PickupsFromBlock(a_BlockPos, a_Digger, a_Tool); + auto Pickups = PickupsFromBlock(a_BlockPos, a_Digger, a_Tool); + if (!DigBlock(a_BlockPos, a_Digger)) { return false; } - SpawnItemPickups(pickups, Vector3d(0.5, 0.5, 0.5) + a_BlockPos, 10); + + auto & Random = GetRandomProvider(); + for (auto & Item : Pickups) + { + SpawnItemPickup(Vector3d(Random.RandReal(0.25, 0.75), Random.RandReal(0.25, 0.75), Random.RandReal(0.25, 0.75)) + a_BlockPos, std::move(Item)); + } + return true; } diff --git a/src/World.h b/src/World.h index ea995ebdc..8852b9ac8 100644 --- a/src/World.h +++ b/src/World.h @@ -29,6 +29,7 @@ class cFluidSimulator; class cSandSimulator; class cRedstoneSimulator; class cItem; +class cPawn; class cPlayer; class cClientHandle; class cEntity; @@ -167,7 +168,7 @@ public: virtual void BroadcastChat (const cCompositeChat & a_Message, const cClientHandle * a_Exclude = nullptr) override; // tolua_end - virtual void BroadcastCollectEntity (const cEntity & a_Collected, const cEntity & a_Collector, unsigned a_Count, const cClientHandle * a_Exclude = nullptr) override; + virtual void BroadcastCollectEntity (const cEntity & a_Collected, const cPawn & a_Collector, unsigned a_Count, const cClientHandle * a_Exclude = nullptr) override; virtual void BroadcastDestroyEntity (const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr) override; virtual void BroadcastDetachEntity (const cEntity & a_Entity, const cEntity & a_PreviousVehicle) override; virtual void BroadcastEntityEffect (const cEntity & a_Entity, int a_EffectID, int a_Amplifier, int a_Duration, const cClientHandle * a_Exclude = nullptr) override; @@ -434,6 +435,20 @@ public: return SpawnItemPickups(a_Pickups, {a_BlockX, a_BlockY, a_BlockZ}, {a_SpeedX, a_SpeedY, a_SpeedZ}, a_IsPlayerCreated); } + // tolua_end + + /** Spawns a pickup containing the specified item with the specified speed. */ + UInt32 SpawnItemPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cTickTime a_CollectionDelay = 10_tick, cTickTime a_Lifetime = 6000_tick, bool a_CanCombine = true); + + /** With a pickup containing the specified item, either attempt to combine it with another pickup within the given bounds in the entity spawn queue, or spawn a new pickup with the specified speed. + This variant is useful for avoiding heavy server load when spawning many pickups at once within a single tick, for example during explosions, by pre-emptively combining them. */ + UInt32 SpawnItemPickup(Vector3d a_Position, cItem && a_Item, Vector3d a_Speed, cBoundingBox a_CombineBounds, cTickTime a_CollectionDelay = 10_tick, cTickTime a_Lifetime = 6000_tick); + + /** Spawns a pickup containing the specified item with random initial speed. */ + UInt32 SpawnItemPickup(Vector3d a_Position, cItem && a_Item, double a_SpeedMultiplier = 1.0, cTickTime a_CollectionDelay = 10_tick, cTickTime a_Lifetime = 6000_tick, bool a_CanCombine = true); + + // tolua_begin + /** Spawns a single pickup containing the specified item. */ UInt32 SpawnItemPickup(Vector3d a_Pos, const cItem & a_Item, Vector3f a_Speed, int a_LifetimeTicks = 6000, bool a_CanCombine = true); @@ -561,9 +576,9 @@ public: } /** Digs the specified block, and spawns the appropriate pickups for it. + The pickup is spawned in a random position at most 0.25 away from the centre of the block along each axis. a_Digger is an optional entity causing the digging, usually the player. - a_Tool is an optional item used to dig up the block, used by the handlers (empty hand vs shears produce different pickups from leaves). - An empty hand is assumed if a_Tool is nullptr. + a_Tool is an optional item used to dig up the block, used by the handlers (empty hand vs shears produce different pickups from leaves). An empty hand is assumed if a_Tool is nullptr. Returns true on success, false if the chunk is not loaded. */ bool DropBlockAsPickups(Vector3i a_BlockPos, const cEntity * a_Digger = nullptr, const cItem * a_Tool = nullptr); diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index dfefb74d3..fa7f8a349 100644 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -1942,7 +1942,7 @@ void cWSSAnvil::LoadPickupFromNBT(cEntityList & a_Entities, const cParsedNBT & a return; } - auto Pickup = std::make_unique<cPickup>(Vector3d(), Item, false); // Pickup delay doesn't matter, just say false + auto Pickup = std::make_unique<cPickup>(Vector3d(), std::move(Item), Vector3d(), 0_tick, 0_tick); // Pickup delay loaded later if (!LoadEntityBaseFromNBT(*Pickup.get(), a_NBT, a_TagIdx)) { return; |