mirror of
https://github.com/cuberite/cuberite.git
synced 2025-01-07 03:16:55 +08:00
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:
parent
b8071f1449
commit
cd97aa8330
@ -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.
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
}
|
||||
|
||||
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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?
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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?
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
21
src/World.h
21
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);
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user