Pickups: improve coalescing implementation

+ Add ability to coalesce before spawning in to world.
* Adapt coalescing for pickup entities already in the world to be more like Vanilla.
This commit is contained in:
Tiger Wang 2022-07-10 23:55:11 +01:00
parent b8071f1449
commit cd97aa8330
27 changed files with 416 additions and 347 deletions

View File

@ -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;
}
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");
}();
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;
// 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.
}

View File

@ -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);

View File

@ -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;

View File

@ -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);
}

View File

@ -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

View File

@ -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)
{

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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:

View File

@ -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()
{
if (m_FoundMatchingPickup)
{
m_Pickup->GetWorld()->BroadcastEntityMetadata(*m_Pickup);
}
}
bool operator()(cEntity & a_Entity)
{
ASSERT(a_Entity.IsTicking());
if (!a_Entity.IsPickup() || (a_Entity.GetUniqueID() <= m_Pickup->GetUniqueID()) || !a_Entity.IsOnGround())
if (!a_Entity.IsPickup() || (&a_Entity == m_Pickup))
{
return false;
}
Vector3d EntityPos = a_Entity.GetPosition();
double Distance = (EntityPos - m_Position).Length();
auto & OtherPickup = static_cast<cPickup &>(a_Entity);
cItem & Item = OtherPickup.GetItem();
if ((Distance < 1.2) && Item.IsEqual(m_Pickup->GetItem()) && OtherPickup.CanCombine())
cItem & OtherItem = OtherPickup.GetItem();
cItem & Item = m_Pickup->GetItem();
if (!Item.IsEqual(OtherItem) || !OtherPickup.CanCombine() || OtherPickup.IsCollected())
{
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;
}
if (CombineCount <= 0)
{
return false;
}
m_Pickup->GetItem().AddCount(static_cast<char>(CombineCount));
Item.m_ItemCount -= static_cast<char>(CombineCount);
if (Item.m_ItemCount <= 0)
{
a_Entity.GetWorld()->BroadcastCollectEntity(a_Entity, *m_Pickup, static_cast<unsigned>(CombineCount));
a_Entity.Destroy();
// Reset the timer
m_Pickup->SetAge(0);
}
else
{
a_Entity.GetWorld()->BroadcastEntityMetadata(a_Entity);
}
m_FoundMatchingPickup = true;
return false;
}
// 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);
// 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;
}
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:
private:
cPickup * m_Pickup;
bool m_FoundMatchingPickup;
Vector3d m_Position;
cPickup * m_Pickup;
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,81 +134,150 @@ cPickup::cPickup(Vector3d a_Pos, const cItem & a_Item, bool IsPlayerCreated, Vec
void cPickup::SpawnOn(cClientHandle & a_Client)
bool cPickup::CollectedBy(cEntity & a_Dest)
{
a_Client.SendSpawnEntity(*this);
a_Client.SendEntityMetadata(*this);
if (m_IsCollected)
{
// It's already collected!
return false;
}
if (!a_Dest.IsPawn())
{
// Pawns can't pick up items:
return false;
}
if (m_RemainingCollectionDelay > m_RemainingCollectionDelay.zero())
{
// Not old enough to be collected!
return false;
}
if (a_Dest.IsMob())
{
auto & Mob = static_cast<cMonster &>(a_Dest);
if (Mob.GetMobType() == mtVillager)
{
// Villagers only pickup food
if (!ItemCategory::IsVillagerFood(m_Item.m_ItemType))
{
return false;
}
auto & Villager = static_cast<cVillager &>(Mob);
char NumAdded = Villager.GetInventory().AddItem(m_Item);
if (NumAdded > 0)
{
OnCollectedBy(Mob, NumAdded);
return true;
}
}
}
else if (a_Dest.IsPlayer())
{
auto & Player = static_cast<cPlayer &>(a_Dest);
// If the player is a spectator, he cannot collect anything
if (Player.IsGameModeSpectator())
{
return false;
}
if (cRoot::Get()->GetPluginManager()->CallHookCollectingPickup(Player, *this))
{
// Collection refused because a plugin has said no:
return false;
}
char NumAdded = Player.GetInventory().AddItem(m_Item);
if (NumAdded > 0)
{
// Check achievements
switch (m_Item.m_ItemType)
{
case E_BLOCK_LOG: Player.AwardAchievement(CustomStatistic::AchMineWood); break;
case E_ITEM_LEATHER: Player.AwardAchievement(CustomStatistic::AchKillCow); break;
case E_ITEM_DIAMOND: Player.AwardAchievement(CustomStatistic::AchDiamonds); break;
case E_ITEM_BLAZE_ROD: Player.AwardAchievement(CustomStatistic::AchBlazeRod); break;
default: break;
}
OnCollectedBy(Player, NumAdded);
return true;
}
}
// Either destination cannot collect, or no space in inventory:
return false;
}
void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
bool cPickup::TryCombineWithQueuedEntity(cEntity & a_Entity, const cBoundingBox & a_CombineBounds, cItem & a_Item, const char a_MaxStackSize)
{
Super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
if (!a_Entity.IsPickup())
{
// The base class tick destroyed us
return;
return false;
}
BroadcastMovementUpdate(); // Notify clients of position
m_Timer += a_Dt;
auto & Pickup = static_cast<cPickup &>(a_Entity);
auto & RecipientItem = Pickup.GetItem();
if (!m_bCollected)
if (!RecipientItem.IsEqual(a_Item) || !a_CombineBounds.DoesIntersect(Pickup.GetBoundingBox()))
{
int BlockY = POSY_TOINT;
int BlockX = POSX_TOINT;
int BlockZ = POSZ_TOINT;
return false;
}
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);
PickupCombiningCallback::CombineInto(RecipientItem, a_Item, a_MaxStackSize);
// 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;
}
}
return a_Item.m_ItemCount == 0;
}
// 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);
}
}
}
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
{
if (m_Timer > std::chrono::milliseconds(500)) // 0.5 second
{
Destroy();
return;
}
// Our count changed, so try to combine again:
TryCombineWithPickupsInWorld();
}
if (m_Timer > m_Lifetime)
// 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)
{
Destroy();
return;
}
PickupCombiningCallback PickupCombiningCallback(this);
m_World->ForEachEntityInBox({ GetPosition(), 0.25 / 2, 0.25 }, PickupCombiningCallback);
}
@ -211,105 +299,60 @@ bool cPickup::DoTakeDamage(TakeDamageInfo & a_TDI)
bool cPickup::CollectedBy(cEntity & a_Dest)
void cPickup::OnAddToWorld(cWorld & a_World)
{
if (m_bCollected)
{
// 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!
}
Super::OnAddToWorld(a_World);
// This type of entity can't pickup items
if (!a_Dest.IsPawn())
{
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)))
{
// 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
}
// Checking for villagers
if (!a_Dest.IsPlayer() && a_Dest.IsMob())
{
auto & Mob = static_cast<cMonster &>(a_Dest);
if (Mob.GetMobType() == mtVillager)
{
// Villagers only pickup food
if (!ItemCategory::IsVillagerFood(m_Item.m_ItemType))
{
return false;
}
auto & Villager = static_cast<cVillager &>(Mob);
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);
return true;
}
// Pickup cannot be collected because the entity has not enough space
return false;
}
}
else if (a_Dest.IsPlayer())
{
auto & Player = static_cast<cPlayer &>(a_Dest);
// If the player is a spectator, he cannot collect anything
if (Player.IsGameModeSpectator())
{
return false;
}
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());
return false;
}
char NumAdded = Player.GetInventory().AddItem(m_Item);
if (NumAdded > 0)
{
// Check achievements
switch (m_Item.m_ItemType)
{
case E_BLOCK_LOG: Player.AwardAchievement(CustomStatistic::AchMineWood); break;
case E_ITEM_LEATHER: Player.AwardAchievement(CustomStatistic::AchKillCow); break;
case E_ITEM_DIAMOND: Player.AwardAchievement(CustomStatistic::AchDiamonds); break;
case E_ITEM_BLAZE_ROD: Player.AwardAchievement(CustomStatistic::AchBlazeRod); break;
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);
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);
return false;
// 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();
}

View File

@ -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);
void TryCombineWithPickupsInWorld();
// 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_bCollected;
/** 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_bIsPlayerCreated;
/** You have thirty seconds to live. - Medic TF2 */
std::chrono::milliseconds m_RemainingLifetime;
bool m_bCanCombine;
std::chrono::milliseconds m_Lifetime;
bool m_IsCollected;
bool m_IsCombinable;
}; // tolua_export

View File

@ -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;
}
TossItems(Drops);
m_Stats.Custom[CustomStatistic::Drop] += 1U;
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.
}

View File

@ -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. */

View File

@ -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
{

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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?

View File

@ -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;

View File

@ -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?

View File

@ -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;

View File

@ -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);
}

View File

@ -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_Position, cItem && a_Item, Vector3d a_Speed, cTickTime a_CollectionDelay, cTickTime a_Lifetime, bool a_CanCombine)
{
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();
}
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)
{
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))
{
return cEntity::INVALID_ID;
}
return pickupPtr->GetUniqueID();
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;
}

View File

@ -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);

View File

@ -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;