Cuberite
A lightweight, fast and extensible game server for Minecraft
Pickup.cpp
Go to the documentation of this file.
1 
2 #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
3 
4 #ifndef _WIN32
5 #include <cstdlib>
6 #endif
7 
8 #include "Pickup.h"
9 #include "Player.h"
10 #include "../Mobs/Villager.h"
11 #include "../ClientHandle.h"
12 #include "../World.h"
13 #include "../Server.h"
14 #include "../Bindings/PluginManager.h"
15 #include "../Registries/Items.h"
16 #include "../Root.h"
17 #include "../Chunk.h"
18 
19 
20 
21 
23 {
24 public:
25  cPickupCombiningCallback(Vector3d a_Position, cPickup * a_Pickup) :
26  m_FoundMatchingPickup(false),
27  m_Position(a_Position),
28  m_Pickup(a_Pickup)
29  {
30  }
31 
32  bool operator () (cEntity & a_Entity)
33  {
34  ASSERT(a_Entity.IsTicking());
35  if (!a_Entity.IsPickup() || (a_Entity.GetUniqueID() <= m_Pickup->GetUniqueID()) || !a_Entity.IsOnGround())
36  {
37  return false;
38  }
39 
40 
41  Vector3d EntityPos = a_Entity.GetPosition();
42  double Distance = (EntityPos - m_Position).Length();
43 
44  auto & OtherPickup = static_cast<cPickup &>(a_Entity);
45  cItem & Item = OtherPickup.GetItem();
46  if ((Distance < 1.2) && Item.IsEqual(m_Pickup->GetItem()) && OtherPickup.CanCombine())
47  {
48  short CombineCount = static_cast<short>(Item.m_ItemCount);
49  if ((CombineCount + static_cast<short>(m_Pickup->GetItem().m_ItemCount)) > static_cast<short>(Item.GetMaxStackSize()))
50  {
51  CombineCount = Item.GetMaxStackSize() - m_Pickup->GetItem().m_ItemCount;
52  }
53 
54  if (CombineCount <= 0)
55  {
56  return false;
57  }
58 
59  m_Pickup->GetItem().AddCount(static_cast<char>(CombineCount));
60  Item.m_ItemCount -= static_cast<char>(CombineCount);
61 
62  if (Item.m_ItemCount <= 0)
63  {
64  a_Entity.GetWorld()->BroadcastCollectEntity(a_Entity, *m_Pickup, static_cast<unsigned>(CombineCount));
65  a_Entity.Destroy();
66 
67  // Reset the timer
68  m_Pickup->SetAge(0);
69  }
70  else
71  {
72  a_Entity.GetWorld()->BroadcastEntityMetadata(a_Entity);
73  }
74  m_FoundMatchingPickup = true;
75  }
76  return false;
77  }
78 
79  inline bool FoundMatchingPickup()
80  {
81  return m_FoundMatchingPickup;
82  }
83 
84 protected:
86 
89 };
90 
91 
92 
93 
94 
96 // cPickup:
97 
98 cPickup::cPickup(Vector3d a_Pos, const cItem & a_Item, bool IsPlayerCreated, Vector3f a_Speed, int a_LifetimeTicks, bool a_CanCombine):
99  Super(etPickup, a_Pos, 0.25f, 0.25f),
100  m_Timer(0),
101  m_Item(a_Item),
102  m_bCollected(false),
103  m_bIsPlayerCreated(IsPlayerCreated),
104  m_bCanCombine(a_CanCombine),
105  m_Lifetime(cTickTime(a_LifetimeTicks))
106 {
107  SetGravity(-16.0f);
108  SetAirDrag(0.02f);
109  SetMaxHealth(5);
110  SetHealth(5);
111  SetSpeed(a_Speed);
112 }
113 
114 
115 
116 
117 
119 {
120  a_Client.SendSpawnEntity(*this);
121  a_Client.SendEntityMetadata(*this);
122 }
123 
124 
125 
126 
127 
128 void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
129 {
130  Super::Tick(a_Dt, a_Chunk);
131  if (!IsTicking())
132  {
133  // The base class tick destroyed us
134  return;
135  }
136  BroadcastMovementUpdate(); // Notify clients of position
137 
138  m_Timer += a_Dt;
139 
140  if (!m_bCollected)
141  {
142  int BlockY = POSY_TOINT;
143  int BlockX = POSX_TOINT;
144  int BlockZ = POSZ_TOINT;
145 
146  if ((BlockY >= 0) && (BlockY < cChunkDef::Height)) // Don't do anything except for falling when outside the world
147  {
148  // Position might have changed due to physics. So we have to make sure we have the correct chunk.
149  GET_AND_VERIFY_CURRENT_CHUNK(CurrentChunk, BlockX, BlockZ);
150 
151  // Destroy the pickup if it is on fire:
152  if (IsOnFire())
153  {
154  m_bCollected = true;
155  m_Timer = std::chrono::milliseconds(0); // We have to reset the timer.
156  m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick.
157  if (m_Timer > std::chrono::milliseconds(500))
158  {
159  Destroy();
160  return;
161  }
162  }
163 
164  // Try to combine the pickup with adjacent same-item pickups:
165  if ((m_Item.m_ItemCount < m_Item.GetMaxStackSize()) && IsOnGround() && CanCombine()) // Don't combine if already full or not on ground
166  {
167  // By using a_Chunk's ForEachEntity() instead of cWorld's, pickups don't combine across chunk boundaries.
168  // That is a small price to pay for not having to traverse the entire world for each entity.
169  // The speedup in the tick thread is quite considerable.
170  cPickupCombiningCallback PickupCombiningCallback(GetPosition(), this);
171  a_Chunk.ForEachEntity(PickupCombiningCallback);
172  if (PickupCombiningCallback.FoundMatchingPickup())
173  {
175  }
176  }
177  }
178  }
179  else
180  {
181  if (m_Timer > std::chrono::milliseconds(500)) // 0.5 second
182  {
183  Destroy();
184  return;
185  }
186  }
187 
188  if (m_Timer > m_Lifetime)
189  {
190  Destroy();
191  return;
192  }
193 }
194 
195 
196 
197 
198 
200 {
201  if (a_TDI.DamageType == dtCactusContact)
202  {
203  Destroy();
204  return true;
205  }
206 
207  return Super::DoTakeDamage(a_TDI);
208 }
209 
210 
211 
212 
213 
215 {
216  if (m_bCollected)
217  {
218  // LOG("Pickup %d cannot be collected by \"%s\", because it has already been collected.", m_UniqueID, a_Dest->GetName().c_str());
219  return false; // It's already collected!
220  }
221 
222  // This type of entity can't pickup items
223  if (!a_Dest.IsPawn())
224  {
225  return false;
226  }
227 
228  // Two seconds if player created the pickup (vomiting), half a second if anything else
229  if (m_Timer < (m_bIsPlayerCreated ? std::chrono::seconds(2) : std::chrono::milliseconds(500)))
230  {
231  // LOG("Pickup %d cannot be collected by \"%s\", because it is not old enough.", m_UniqueID, a_Dest->GetName().c_str());
232  return false; // Not old enough
233  }
234 
235  // Checking for villagers
236  if (!a_Dest.IsPlayer() && a_Dest.IsMob())
237  {
238 
239  auto & Mob = static_cast<cMonster &>(a_Dest);
240  if (Mob.GetMobType() == mtVillager)
241  {
242  // Villagers only pickup food
244  {
245  return false;
246  }
247 
248  auto & Villager = static_cast<cVillager &>(Mob);
249  char NumAdded = Villager.GetInventory().AddItem(m_Item);
250  if (NumAdded > 0)
251  {
252  m_Item.m_ItemCount -= NumAdded;
253  m_World->BroadcastCollectEntity(*this, a_Dest, static_cast<unsigned>(NumAdded));
254 
255  // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;)
256  m_World->BroadcastSoundEffect("entity.item.pickup", GetPosition(), 0.3f, (1.2f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
257  if (m_Item.m_ItemCount <= 0)
258  {
259  // All of the pickup has been collected, schedule the pickup for destroying
260  m_bCollected = true;
261  }
262  m_Timer = std::chrono::milliseconds(0);
263  return true;
264  }
265  // Pickup cannot be collected because the entity has not enough space
266  return false;
267  }
268  }
269  else if (a_Dest.IsPlayer())
270  {
271  auto & Player = static_cast<cPlayer &>(a_Dest);
272 
273  // If the player is a spectator, he cannot collect anything
274  if (Player.IsGameModeSpectator())
275  {
276  return false;
277  }
278 
279  if (cRoot::Get()->GetPluginManager()->CallHookCollectingPickup(Player, *this))
280  {
281  // LOG("Pickup %d cannot be collected by \"%s\", because a plugin has said no.", m_UniqueID, a_Dest->GetName().c_str());
282  return false;
283  }
284 
285  char NumAdded = Player.GetInventory().AddItem(m_Item);
286  if (NumAdded > 0)
287  {
288  // Check achievements
289  switch (m_Item.m_ItemType)
290  {
291  case E_BLOCK_LOG: Player.AwardAchievement(CustomStatistic::AchMineWood); break;
292  case E_ITEM_LEATHER: Player.AwardAchievement(CustomStatistic::AchKillCow); break;
293  case E_ITEM_DIAMOND: Player.AwardAchievement(CustomStatistic::AchDiamonds); break;
294  case E_ITEM_BLAZE_ROD: Player.AwardAchievement(CustomStatistic::AchBlazeRod); break;
295  default: break;
296  }
297 
298  m_Item.m_ItemCount -= NumAdded;
299  m_World->BroadcastCollectEntity(*this, a_Dest, static_cast<unsigned>(NumAdded));
300 
301  // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;)
302  m_World->BroadcastSoundEffect("entity.item.pickup", GetPosition(), 0.3f, (1.2f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
303  if (m_Item.m_ItemCount <= 0)
304  {
305  // All of the pickup has been collected, schedule the pickup for destroying
306  m_bCollected = true;
307  }
308  m_Timer = std::chrono::milliseconds(0);
309  return true;
310  }
311  }
312 
313  // LOG("Pickup %d cannot be collected by \"%s\", because there's no space in the inventory.", a_Dest->GetName().c_str(), m_UniqueID);
314  return false;
315 }
@ E_BLOCK_LOG
Definition: BlockType.h:27
@ E_ITEM_DIAMOND
Definition: BlockType.h:308
@ E_ITEM_LEATHER
Definition: BlockType.h:378
@ E_ITEM_BLAZE_ROD
Definition: BlockType.h:414
@ dtCactusContact
Definition: Defines.h:253
#define POSX_TOINT
Definition: Entity.h:31
#define POSZ_TOINT
Definition: Entity.h:33
#define GET_AND_VERIFY_CURRENT_CHUNK(ChunkVarName, X, Z)
Definition: Entity.h:36
#define POSY_TOINT
Definition: Entity.h:32
std::chrono::duration< signed int, std::ratio_multiply< std::chrono::milliseconds::period, std::ratio< 50 > >> cTickTime
Definition: Globals.h:364
#define ASSERT(x)
Definition: Globals.h:276
@ mtVillager
Definition: MonsterTypes.h:71
Item
Definition: Items.h:4
bool IsVillagerFood(short a_ItemType)
Definition: Defines.cpp:623
unsigned char Distance(const BlockState Block)
bool CallHookCollectingPickup(cPlayer &a_Player, cPickup &a_Pickup)
Definition: Chunk.h:36
bool ForEachEntity(cEntityCallback a_Callback) const
Calls the callback for each entity; returns true if all entities processed, false if the callback abo...
Definition: Chunk.cpp:1657
static const int Height
Definition: ChunkDef.h:125
void SendSpawnEntity(const cEntity &a_Entity)
void SendEntityMetadata(const cEntity &a_Entity)
eDamageType DamageType
Definition: Entity.h:61
Definition: Entity.h:76
bool IsPlayer(void) const
Definition: Entity.h:160
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk &a_Chunk)
Definition: Entity.cpp:909
bool IsPickup(void) const
Definition: Entity.h:161
void SetHealth(float a_Health)
Sets the health of this entity; doesn't broadcast any hurt animation.
Definition: Entity.cpp:900
bool IsTicking(void) const
Returns true if the entity is valid and ticking.
Definition: Entity.cpp:2259
void SetGravity(float a_Gravity)
Definition: Entity.h:282
void SetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ)
Sets the speed of the entity, measured in m / sec.
Definition: Entity.cpp:2157
cWorld * m_World
Definition: Entity.h:624
UInt32 GetUniqueID(void) const
Definition: Entity.h:253
void Destroy()
Destroys the entity, schedules it for memory freeing and broadcasts the DestroyEntity packet.
Definition: Entity.cpp:243
virtual bool IsOnFire(void) const
Definition: Entity.h:489
bool IsPawn(void) const
Definition: Entity.h:163
virtual bool IsOnGround(void) const
Returns whether the entity is on ground or not.
Definition: Entity.h:519
void SetAirDrag(float a_AirDrag)
Definition: Entity.h:286
virtual bool DoTakeDamage(TakeDamageInfo &a_TDI)
Makes this entity take damage specified in the a_TDI.
Definition: Entity.cpp:404
void SetMaxHealth(float a_MaxHealth)
Sets the maximum value for the health.
Definition: Entity.cpp:1887
const Vector3d & GetPosition(void) const
Exported in ManualBindings.
Definition: Entity.h:297
virtual void BroadcastMovementUpdate(const cClientHandle *a_Exclude=nullptr)
Updates clients of changes in the entity.
Definition: Entity.cpp:1966
cWorld * GetWorld(void) const
Definition: Entity.h:190
bool IsMob(void) const
Definition: Entity.h:162
cPickupCombiningCallback(Vector3d a_Position, cPickup *a_Pickup)
Definition: Pickup.cpp:25
bool operator()(cEntity &a_Entity)
Definition: Pickup.cpp:32
Definition: Pickup.h:20
std::chrono::milliseconds m_Lifetime
Definition: Pickup.h:81
bool CollectedBy(cEntity &a_Dest)
Definition: Pickup.cpp:214
cPickup(Vector3d a_Pos, const cItem &a_Item, bool IsPlayerCreated, Vector3f a_Speed=Vector3f(), int a_LifetimeTicks=6000, bool a_CanCombine=true)
Definition: Pickup.cpp:98
bool m_bIsPlayerCreated
Definition: Pickup.h:77
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk &a_Chunk) override
Definition: Pickup.cpp:128
bool m_bCollected
Definition: Pickup.h:75
cItem m_Item
Definition: Pickup.h:73
void SetAge(int a_Age)
Set the number of ticks that this entity has existed.
Definition: Pickup.h:54
std::chrono::milliseconds m_Timer
The number of ticks that the entity has existed / timer between collect and destroy; in msec.
Definition: Pickup.h:71
virtual void SpawnOn(cClientHandle &a_ClientHandle) override
Descendants override this function to send a command to the specified client to spawn the entity on t...
Definition: Pickup.cpp:118
cItem & GetItem(void)
Definition: Pickup.h:31
virtual bool DoTakeDamage(TakeDamageInfo &a_TDI) override
Makes this entity take damage specified in the a_TDI.
Definition: Pickup.cpp:199
bool CanCombine(void) const
Returns whether this pickup is allowed to combine with other similar pickups.
Definition: Pickup.h:45
Definition: Player.h:29
cInventory & GetInventory(void)
Definition: Player.h:156
char AddItem(const cItem &a_ItemStack, bool a_AllowNewStacks=true)
Adds as many items out of a_ItemStack as can fit.
Definition: Inventory.cpp:106
Definition: Item.h:37
cItem & AddCount(char a_AmountToAdd)
Adds the specified count to this object and returns the reference to self (useful for chaining)
Definition: Item.cpp:104
char m_ItemCount
Definition: Item.h:164
char GetMaxStackSize(void) const
Returns the maximum amount of stacked items of this type.
Definition: Item.cpp:207
short m_ItemType
Definition: Item.h:163
char AddItem(cItem &a_ItemStack, bool a_AllowNewStacks=true, int a_PrioritySlot=-1)
Adds as many items out of a_ItemStack as can fit.
Definition: ItemGrid.cpp:313
cItemGrid & GetInventory(void)
Returns the villager hidden inventory (8 slots).
Definition: Villager.h:43
static cRoot * Get()
Definition: Root.h:52
cPluginManager * GetPluginManager(void)
Definition: Root.h:111
virtual void BroadcastEntityMetadata(const cEntity &a_Entity, const cClientHandle *a_Exclude=nullptr) override
virtual void BroadcastCollectEntity(const cEntity &a_Collected, const cEntity &a_Collector, unsigned a_Count, const cClientHandle *a_Exclude=nullptr) override
virtual void BroadcastSoundEffect(const AString &a_SoundName, Vector3d a_Position, float a_Volume, float a_Pitch, const cClientHandle *a_Exclude=nullptr) override