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