diff options
Diffstat (limited to 'src/Entities')
-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 |
6 files changed, 267 insertions, 240 deletions
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. */ |