Files
cuberite-2a/src/Entities/Pickup.cpp
T

269 lines
6.6 KiB
C++
Raw Normal View History

#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#ifndef _WIN32
#include <cstdlib>
#endif
#include "Pickup.h"
2014-01-24 21:55:04 +00:00
#include "Player.h"
2013-08-19 11:39:13 +02:00
#include "../ClientHandle.h"
#include "../World.h"
#include "../Server.h"
#include "../Bindings/PluginManager.h"
2013-08-19 11:39:13 +02:00
#include "../Root.h"
#include "../Chunk.h"
2017-09-11 22:20:49 +01:00
class cPickupCombiningCallback
2014-01-25 15:19:56 +00:00
{
public:
cPickupCombiningCallback(Vector3d a_Position, cPickup * a_Pickup) :
2014-02-05 09:43:49 -08:00
m_FoundMatchingPickup(false),
2014-01-25 15:19:56 +00:00
m_Position(a_Position),
2014-02-05 09:43:49 -08:00
m_Pickup(a_Pickup)
2014-01-25 15:19:56 +00:00
{
}
2017-09-11 22:20:49 +01:00
bool operator () (cEntity & a_Entity)
2014-01-25 15:19:56 +00:00
{
2017-09-11 22:20:49 +01:00
ASSERT(a_Entity.IsTicking());
if (!a_Entity.IsPickup() || (a_Entity.GetUniqueID() <= m_Pickup->GetUniqueID()) || !a_Entity.IsOnGround())
2014-01-25 15:19:56 +00:00
{
return false;
}
2017-09-11 22:20:49 +01:00
Vector3d EntityPos = a_Entity.GetPosition();
2014-01-25 15:19:56 +00:00
double Distance = (EntityPos - m_Position).Length();
2017-09-11 22:20:49 +01:00
auto & OtherPickup = static_cast<cPickup &>(a_Entity);
cItem & Item = OtherPickup.GetItem();
if ((Distance < 1.2) && Item.IsEqual(m_Pickup->GetItem()) && OtherPickup.CanCombine())
2014-01-25 15:19:56 +00:00
{
short CombineCount = Item.m_ItemCount;
2014-06-24 16:19:22 +02:00
if ((CombineCount + m_Pickup->GetItem().m_ItemCount) > Item.GetMaxStackSize())
{
CombineCount = Item.GetMaxStackSize() - m_Pickup->GetItem().m_ItemCount;
}
if (CombineCount <= 0)
{
return false;
}
2015-05-24 12:56:56 +01:00
m_Pickup->GetItem().AddCount(static_cast<char>(CombineCount));
2014-06-24 16:19:22 +02:00
Item.m_ItemCount -= CombineCount;
if (Item.m_ItemCount <= 0)
{
2020-05-04 09:10:47 +01:00
a_Entity.GetWorld()->BroadcastCollectEntity(a_Entity, *m_Pickup, static_cast<unsigned>(CombineCount));
2017-09-11 22:20:49 +01:00
a_Entity.Destroy();
// Reset the timer
m_Pickup->SetAge(0);
}
2014-06-24 16:19:22 +02:00
else
{
2017-09-11 22:20:49 +01:00
a_Entity.GetWorld()->BroadcastEntityMetadata(a_Entity);
2014-06-24 16:19:22 +02:00
}
2014-01-25 15:19:56 +00:00
m_FoundMatchingPickup = true;
}
return false;
}
inline bool FoundMatchingPickup()
{
return m_FoundMatchingPickup;
}
protected:
bool m_FoundMatchingPickup;
Vector3d m_Position;
cPickup * m_Pickup;
};
////////////////////////////////////////////////////////////////////////////////
// cPickup:
cPickup::cPickup(Vector3d a_Pos, const cItem & a_Item, bool IsPlayerCreated, Vector3f a_Speed, int a_LifetimeTicks, bool a_CanCombine):
2021-04-06 16:09:16 +01:00
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))
{
SetGravity(-16.0f);
2015-03-31 11:40:31 -04:00
SetAirDrag(0.02f);
2013-11-10 22:20:25 +00:00
SetMaxHealth(5);
SetHealth(5);
SetSpeed(a_Speed);
}
void cPickup::SpawnOn(cClientHandle & a_Client)
{
2020-04-20 20:46:04 +01:00
a_Client.SendSpawnEntity(*this);
a_Client.SendEntityMetadata(*this);
}
2015-01-11 21:12:26 +00:00
void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
2020-04-13 18:38:06 +02:00
Super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
2014-07-17 22:15:34 +02:00
BroadcastMovementUpdate(); // Notify clients of position
2015-01-16 13:49:22 +00:00
m_Timer += a_Dt;
2016-02-05 23:45:45 +02:00
if (!m_bCollected)
{
2014-04-12 13:16:48 +01:00
int BlockY = POSY_TOINT;
int BlockX = POSX_TOINT;
int BlockZ = POSZ_TOINT;
2013-08-24 21:34:42 +02:00
if ((BlockY >= 0) && (BlockY < cChunkDef::Height)) // Don't do anything except for falling when outside the world
{
2013-08-24 21:34:42 +02:00
// Position might have changed due to physics. So we have to make sure we have the correct chunk.
2019-08-11 11:39:43 +02:00
GET_AND_VERIFY_CURRENT_CHUNK(CurrentChunk, BlockX, BlockZ);
2016-02-05 23:45:45 +02:00
2018-01-14 18:44:45 +00:00
// Destroy the pickup if it is on fire:
if (IsOnFire())
2014-04-12 13:16:48 +01:00
{
m_bCollected = true;
2015-01-16 13:49:22 +00:00
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))
{
2020-03-05 12:52:34 +02:00
Destroy();
2014-04-12 13:16:48 +01:00
return;
}
2014-04-12 13:16:48 +01:00
}
2014-01-25 15:19:56 +00:00
2014-09-01 21:43:03 +02:00
// 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
2014-04-12 13:16:48 +01:00
{
2014-09-01 21:43:03 +02:00
// 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.
2014-04-12 13:16:48 +01:00
cPickupCombiningCallback PickupCombiningCallback(GetPosition(), this);
2014-09-01 21:43:03 +02:00
a_Chunk.ForEachEntity(PickupCombiningCallback);
2014-04-12 13:16:48 +01:00
if (PickupCombiningCallback.FoundMatchingPickup())
2014-01-25 15:19:56 +00:00
{
2014-04-12 13:16:48 +01:00
m_World->BroadcastEntityMetadata(*this);
2014-01-25 15:19:56 +00:00
}
}
}
}
else
{
2015-01-16 13:49:22 +00:00
if (m_Timer > std::chrono::milliseconds(500)) // 0.5 second
{
2020-03-05 12:52:34 +02:00
Destroy();
return;
}
}
if (m_Timer > m_Lifetime)
{
2020-03-05 12:52:34 +02:00
Destroy();
return;
}
}
bool cPickup::DoTakeDamage(TakeDamageInfo & a_TDI)
{
if (a_TDI.DamageType == dtCactusContact)
{
2020-03-05 12:52:34 +02:00
Destroy();
return true;
}
2020-04-13 18:38:06 +02:00
return Super::DoTakeDamage(a_TDI);
}
bool cPickup::CollectedBy(cPlayer & a_Dest)
{
2012-08-24 09:49:00 +00:00
if (m_bCollected)
{
2013-05-17 14:30:18 +00:00
// LOG("Pickup %d cannot be collected by \"%s\", because it has already been collected.", m_UniqueID, a_Dest->GetName().c_str());
2014-07-17 22:15:34 +02:00
return false; // It's already collected!
2012-08-24 09:49:00 +00:00
}
2016-02-05 23:45:45 +02:00
// Two seconds if player created the pickup (vomiting), half a second if anything else
2015-01-16 13:49:22 +00:00
if (m_Timer < (m_bIsPlayerCreated ? std::chrono::seconds(2) : std::chrono::milliseconds(500)))
2012-08-24 09:49:00 +00:00
{
2013-05-17 14:30:18 +00:00
// LOG("Pickup %d cannot be collected by \"%s\", because it is not old enough.", m_UniqueID, a_Dest->GetName().c_str());
2014-07-17 22:15:34 +02:00
return false; // Not old enough
2012-08-24 09:49:00 +00:00
}
2016-10-12 14:38:45 +02:00
// If the player is a spectator, he cannot collect anything
if (a_Dest.IsGameModeSpectator())
{
return false;
}
if (cRoot::Get()->GetPluginManager()->CallHookCollectingPickup(a_Dest, *this))
2012-08-24 09:49:00 +00:00
{
2013-05-17 14:30:18 +00:00
// LOG("Pickup %d cannot be collected by \"%s\", because a plugin has said no.", m_UniqueID, a_Dest->GetName().c_str());
2012-08-24 09:49:00 +00:00
return false;
}
int NumAdded = a_Dest.GetInventory().AddItem(m_Item);
if (NumAdded > 0)
{
// Check achievements
switch (m_Item.m_ItemType)
{
case E_BLOCK_LOG: a_Dest.AwardAchievement(Statistic::AchMineWood); break;
case E_ITEM_LEATHER: a_Dest.AwardAchievement(Statistic::AchKillCow); break;
case E_ITEM_DIAMOND: a_Dest.AwardAchievement(Statistic::AchDiamonds); break;
case E_ITEM_BLAZE_ROD: a_Dest.AwardAchievement(Statistic::AchBlazeRod); break;
default: break;
}
m_Item.m_ItemCount -= NumAdded;
2020-05-04 09:10:47 +01:00
m_World->BroadcastCollectEntity(*this, a_Dest, static_cast<unsigned>(NumAdded));
2015-05-18 14:30:16 +01:00
2013-11-10 20:48:12 +00:00
// Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;)
2020-04-03 18:49:12 +03:00
m_World->BroadcastSoundEffect("entity.item.pickup", GetPosition(), 0.3f, (1.2f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
2014-06-24 17:50:38 +02:00
if (m_Item.m_ItemCount <= 0)
{
// All of the pickup has been collected, schedule the pickup for destroying
m_bCollected = true;
2012-10-24 12:48:25 +00:00
}
2015-01-16 13:49:22 +00:00
m_Timer = std::chrono::milliseconds(0);
return true;
}
2013-05-17 14:30:18 +00:00
// 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;
}