Cuberite
A lightweight, fast and extensible game server for Minecraft
ProjectileEntity.cpp
Go to the documentation of this file.
1 
2 // ProjectileEntity.cpp
3 
4 // Implements the cProjectileEntity class representing the common base class for projectiles, as well as individual projectile types
5 
6 #include "Globals.h"
7 
8 #include "../Bindings/PluginManager.h"
9 #include "ProjectileEntity.h"
10 #include "../BlockInfo.h"
11 #include "../ClientHandle.h"
12 #include "../LineBlockTracer.h"
13 #include "../BoundingBox.h"
14 #include "../ChunkMap.h"
15 #include "../Chunk.h"
16 
17 #include "ArrowEntity.h"
18 #include "ThrownEggEntity.h"
19 #include "ThrownEnderPearlEntity.h"
20 #include "ExpBottleEntity.h"
21 #include "ThrownSnowballEntity.h"
22 #include "FireChargeEntity.h"
23 #include "FireworkEntity.h"
24 #include "GhastFireballEntity.h"
25 #include "WitherSkullEntity.h"
26 #include "SplashPotionEntity.h"
27 #include "Player.h"
28 
29 
30 
31 
32 
34 // cProjectileTracerCallback:
35 
37  public cBlockTracer::cCallbacks
38 {
39 public:
41  m_Projectile(a_Projectile),
42  m_SlowdownCoeff(0.99) // Default slowdown when not in water
43  {
44  }
45 
46  double GetSlowdownCoeff(void) const { return m_SlowdownCoeff; }
47 
48 protected:
51 
52  // cCallbacks overrides:
53  virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
54  {
55  /*
56  // DEBUG:
57  FLOGD("Hit block {0}:{1} at {2} face {3}, {4} ({5})",
58  a_BlockType, a_BlockMeta,
59  Vector3i{a_BlockX, a_BlockY, a_BlockZ}, a_EntryFace,
60  cBlockInfo::IsSolid(a_BlockType) ? "solid" : "non-solid",
61  ItemToString(cItem(a_BlockType, 1, a_BlockMeta))
62  );
63  */
64 
65  if (cBlockInfo::IsSolid(a_BlockType))
66  {
67  // The projectile hit a solid block, calculate the exact hit coords:
68  cBoundingBox bb(a_BlockPos, a_BlockPos + Vector3i(1, 1, 1)); // Bounding box of the block hit
69  const Vector3d LineStart = m_Projectile->GetPosition(); // Start point for the imaginary line that goes through the block hit
70  const Vector3d LineEnd = LineStart + m_Projectile->GetSpeed(); // End point for the imaginary line that goes through the block hit
71  double LineCoeff = 0; // Used to calculate where along the line an intersection with the bounding box occurs
72  eBlockFace Face; // Face hit
73 
74  if (bb.CalcLineIntersection(LineStart, LineEnd, LineCoeff, Face))
75  {
76  Vector3d Intersection = LineStart + m_Projectile->GetSpeed() * LineCoeff; // Point where projectile goes into the hit block
77 
78  if (cPluginManager::Get()->CallHookProjectileHitBlock(*m_Projectile, a_BlockPos, Face, Intersection))
79  {
80  return false;
81  }
82 
83  m_Projectile->OnHitSolidBlock(Intersection, Face);
84  return true;
85  }
86  else
87  {
88  LOGD("WEIRD! block tracer reports a hit, but BBox tracer doesn't. Ignoring the hit.");
89  }
90  }
91 
92  // Convey some special effects from special blocks:
93  switch (a_BlockType)
94  {
95  case E_BLOCK_LAVA:
97  {
99  m_SlowdownCoeff = std::min(m_SlowdownCoeff, 0.9); // Slow down to 0.9* the speed each tick when moving through lava
100  break;
101  }
102  case E_BLOCK_WATER:
104  {
106  m_SlowdownCoeff = std::min(m_SlowdownCoeff, 0.8); // Slow down to 0.8* the speed each tick when moving through water
107  break;
108  }
109  } // switch (a_BlockType)
110 
111  // Continue tracing
112  return false;
113  }
114 } ;
115 
116 
117 
118 
119 
121 // cProjectileEntityCollisionCallback:
122 
124 {
125 public:
126  cProjectileEntityCollisionCallback(cProjectileEntity * a_Projectile, const Vector3d & a_Pos, const Vector3d & a_NextPos) :
127  m_Projectile(a_Projectile),
128  m_Pos(a_Pos),
129  m_NextPos(a_NextPos),
130  m_MinCoeff(1),
131  m_HitEntity(nullptr)
132  {
133  }
134 
135 
136  bool operator () (cEntity & a_Entity)
137  {
138  if (
139  (&a_Entity == m_Projectile) || // Do not check collisions with self
140  (a_Entity.GetUniqueID() == m_Projectile->GetCreatorUniqueID()) // Do not check whoever shot the projectile
141  )
142  {
143  // Don't check creator only for the first 5 ticks so that projectiles can collide with the creator
144  if (m_Projectile->GetTicksAlive() <= 5)
145  {
146  return false;
147  }
148  }
149 
150  auto EntBox = a_Entity.GetBoundingBox();
151 
152  // Instead of colliding the bounding box with another bounding box in motion, we collide an enlarged bounding box with a hairline.
153  // The results should be good enough for our purposes
154  double LineCoeff;
156  EntBox.Expand(m_Projectile->GetWidth() / 2, m_Projectile->GetHeight() / 2, m_Projectile->GetWidth() / 2);
157  if (!EntBox.CalcLineIntersection(m_Pos, m_NextPos, LineCoeff, Face))
158  {
159  // No intersection whatsoever
160  return false;
161  }
162 
163  if (
164  !a_Entity.IsMob() &&
165  !a_Entity.IsMinecart() &&
166  (
167  !a_Entity.IsPlayer() ||
168  static_cast<cPlayer &>(a_Entity).IsGameModeSpectator()
169  ) &&
170  !a_Entity.IsBoat() &&
171  !a_Entity.IsEnderCrystal()
172  )
173  {
174  // Not an entity that interacts with a projectile
175  return false;
176  }
177 
178  if (cPluginManager::Get()->CallHookProjectileHitEntity(*m_Projectile, a_Entity))
179  {
180  // A plugin disagreed.
181  return false;
182  }
183 
184  if (LineCoeff < m_MinCoeff)
185  {
186  // The entity is closer than anything we've stored so far, replace it as the potential victim
187  m_MinCoeff = LineCoeff;
188  m_HitEntity = &a_Entity;
189  }
190 
191  // Don't break the enumeration, we want all the entities
192  return false;
193  }
194 
196  cEntity * GetHitEntity(void) const { return m_HitEntity; }
197 
199  double GetMinCoeff(void) const { return m_MinCoeff; }
200 
202  bool HasHit(void) const { return (m_MinCoeff < 1); }
203 
204 protected:
206  const Vector3d & m_Pos;
208  double m_MinCoeff; // The coefficient of the nearest hit on the Pos line
209 
210  // Although it's bad(tm) to store entity ptrs from a callback, we can afford it here, because the entire callback
211  // is processed inside the tick thread, so the entities won't be removed in between the calls and the final processing
212  cEntity * m_HitEntity; // The nearest hit entity
213 } ;
214 
215 
216 
217 
218 
220 // cProjectileEntity:
221 
222 cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, Vector3d a_Pos, float a_Width, float a_Height):
223  Super(etProjectile, a_Pos, a_Width, a_Height),
224  m_ProjectileKind(a_Kind),
225  m_CreatorData(
226  ((a_Creator != nullptr) ? a_Creator->GetUniqueID() : cEntity::INVALID_ID),
227  ((a_Creator != nullptr) ? (a_Creator->IsPlayer() ? static_cast<cPlayer *>(a_Creator)->GetName() : "") : ""),
228  ((a_Creator != nullptr) ? a_Creator->GetEquippedWeapon().m_Enchantments : cEnchantments())
229  ),
230  m_IsInGround(false)
231 {
232  SetGravity(-12.0f);
233  SetAirDrag(0.01f);
234 }
235 
236 
237 
238 
239 
240 cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, Vector3d a_Pos, Vector3d a_Speed, float a_Width, float a_Height):
241  cProjectileEntity(a_Kind, a_Creator, a_Pos, a_Width, a_Height)
242 {
243  SetSpeed(a_Speed);
244  SetYawFromSpeed();
246 }
247 
248 
249 
250 
251 
252 std::unique_ptr<cProjectileEntity> cProjectileEntity::Create(
253  eKind a_Kind,
254  cEntity * a_Creator,
255  Vector3d a_Pos,
256  const cItem * a_Item,
257  const Vector3d * a_Speed
258 )
259 {
260  Vector3d Speed;
261  if (a_Speed != nullptr)
262  {
263  Speed = *a_Speed;
264  }
265 
266  switch (a_Kind)
267  {
268  case pkArrow: return std::make_unique<cArrowEntity> (a_Creator, a_Pos, Speed);
269  case pkEgg: return std::make_unique<cThrownEggEntity> (a_Creator, a_Pos, Speed);
270  case pkEnderPearl: return std::make_unique<cThrownEnderPearlEntity>(a_Creator, a_Pos, Speed);
271  case pkSnowball: return std::make_unique<cThrownSnowballEntity> (a_Creator, a_Pos, Speed);
272  case pkGhastFireball: return std::make_unique<cGhastFireballEntity> (a_Creator, a_Pos, Speed);
273  case pkFireCharge: return std::make_unique<cFireChargeEntity> (a_Creator, a_Pos, Speed);
274  case pkExpBottle: return std::make_unique<cExpBottleEntity> (a_Creator, a_Pos, Speed);
275  case pkSplashPotion: return std::make_unique<cSplashPotionEntity> (a_Creator, a_Pos, Speed, *a_Item);
276  case pkWitherSkull: return std::make_unique<cWitherSkullEntity> (a_Creator, a_Pos, Speed);
277  case pkFirework:
278  {
279  ASSERT(a_Item != nullptr);
280  if (a_Item->m_FireworkItem.m_Colours.empty())
281  {
282  return nullptr;
283  }
284 
285  return std::make_unique<cFireworkEntity>(a_Creator, a_Pos, *a_Item);
286  }
287  }
288 
289  LOGWARNING("%s: Unknown projectile kind: %d", __FUNCTION__, a_Kind);
290  return nullptr;
291 }
292 
293 
294 
295 
296 
298 {
299  // Set the position based on what face was hit:
300  SetPosition(a_HitPos);
301  SetSpeed(0, 0, 0);
302 
303  // DEBUG:
304  FLOGD("Projectile {0}: pos {1:.02f}, hit solid block at face {2}",
305  m_UniqueID, a_HitPos, a_HitFace
306  );
307 
308  m_IsInGround = true;
309 }
310 
311 
312 
313 
314 
315 void cProjectileEntity::OnHitEntity(cEntity & a_EntityHit, Vector3d a_HitPos)
316 {
317  UNUSED(a_HitPos);
318 
319  // If we were created by a player and we hit a pawn, notify attacking player's wolves
320  if (a_EntityHit.IsPawn() && (GetCreatorName() != ""))
321  {
322  auto EntityHit = static_cast<cPawn *>(&a_EntityHit);
324  {
325  static_cast<cPlayer&>(a_Hitter).NotifyNearbyWolves(EntityHit, true);
326  return true;
327  }
328  );
329  }
330 }
331 
332 
333 
334 
335 
337 {
338  switch (m_ProjectileKind)
339  {
340  case pkArrow: return "Arrow";
341  case pkSnowball: return "Snowball";
342  case pkEgg: return "Egg";
343  case pkGhastFireball: return "Fireball";
344  case pkFireCharge: return "SmallFireball";
345  case pkEnderPearl: return "ThrownEnderpearl";
346  case pkExpBottle: return "ThrownExpBottle";
347  case pkSplashPotion: return "SplashPotion";
348  case pkWitherSkull: return "WitherSkull";
349  case pkFirework: return "Firework";
350  }
351  UNREACHABLE("Unsupported projectile kind");
352 }
353 
354 
355 
356 
357 
358 void cProjectileEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
359 {
360  Super::Tick(a_Dt, a_Chunk);
361  if (!IsTicking())
362  {
363  // The base class tick destroyed us
364  return;
365  }
367 }
368 
369 
370 
371 
372 
373 void cProjectileEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
374 {
375  if (m_IsInGround)
376  {
377  // Already-grounded projectiles don't move at all
378  return;
379  }
380 
381  auto DtSec = std::chrono::duration_cast<std::chrono::duration<double>>(a_Dt);
382 
383  const Vector3d DeltaSpeed = GetSpeed() * DtSec.count();
384  const Vector3d Pos = GetPosition();
385  const Vector3d NextPos = Pos + DeltaSpeed;
386 
387  // Test for entity collisions:
388  cProjectileEntityCollisionCallback EntityCollisionCallback(this, Pos, NextPos);
389  a_Chunk.ForEachEntity(EntityCollisionCallback);
390  if (EntityCollisionCallback.HasHit())
391  {
392  // An entity was hit:
393  Vector3d HitPos = Pos + (NextPos - Pos) * EntityCollisionCallback.GetMinCoeff();
394 
395  // DEBUG:
396  FLOGD("Projectile {0} has hit an entity {1} ({2}) at {3:.02f} (coeff {4:.03f})",
397  m_UniqueID,
398  EntityCollisionCallback.GetHitEntity()->GetUniqueID(),
399  EntityCollisionCallback.GetHitEntity()->GetClass(),
400  HitPos,
401  EntityCollisionCallback.GetMinCoeff()
402  );
403 
404  OnHitEntity(*(EntityCollisionCallback.GetHitEntity()), HitPos);
405  if (!IsTicking())
406  {
407  return; // We were destroyed by an override of OnHitEntity
408  }
409  }
410  // TODO: Test the entities in the neighboring chunks, too
411 
412  // Trace the tick's worth of movement as a line:
413  cProjectileTracerCallback TracerCallback(this);
414  if (!cLineBlockTracer::Trace(*m_World, TracerCallback, Pos, NextPos))
415  {
416  // Something has been hit, abort all other processing
417  return;
418  }
419  // The tracer also checks the blocks for slowdown blocks - water and lava - and stores it for later in its SlowdownCoeff
420 
421  // Update the position:
422  SetPosition(NextPos);
423 
424  // Add slowdown and gravity effect to the speed:
425  Vector3d NewSpeed(GetSpeed());
426  NewSpeed.y += m_Gravity * DtSec.count();
427  NewSpeed -= NewSpeed * (m_AirDrag * 20.0f) * DtSec.count();
428  SetSpeed(NewSpeed);
429  SetYawFromSpeed();
431 
432  /*
433  FLOGD("Projectile {0}: pos {1:.02f}, speed {2:.02f}, rot {{{3:.02f}, {4:.02f}}}",
434  m_UniqueID, GetPos(), GetSpeed(), GetYaw(), GetPitch()
435  );
436  */
437 }
438 
439 
440 
441 
442 
444 {
445  a_Client.SendSpawnEntity(*this);
446  a_Client.SendEntityMetadata(*this);
447 }
448 
449 
450 
451 
452 
454 {
455  // Overriden in arrow
456  UNUSED(a_Dest);
457 }
458 
@ E_BLOCK_WATER
Definition: BlockType.h:18
@ E_BLOCK_STATIONARY_LAVA
Definition: BlockType.h:21
@ E_BLOCK_STATIONARY_WATER
Definition: BlockType.h:19
@ E_BLOCK_LAVA
Definition: BlockType.h:20
unsigned char NIBBLETYPE
The datatype used by nibbledata (meta, light, skylight)
Definition: ChunkDef.h:44
unsigned char BLOCKTYPE
The datatype used by blockdata.
Definition: ChunkDef.h:41
eBlockFace
Block face constants, used in PlayerDigging and PlayerBlockPlacement packets and bbox collision calc.
Definition: Defines.h:38
#define UNREACHABLE(x)
Definition: Globals.h:288
#define ASSERT(x)
Definition: Globals.h:276
#define UNUSED
Definition: Globals.h:72
#define FLOGD
Definition: LoggerSimple.h:91
void LOGWARNING(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:67
#define LOGD
Definition: LoggerSimple.h:83
std::string AString
Definition: StringUtils.h:11
Vector3< int > Vector3i
Definition: Vector3.h:487
static cPluginManager * Get(void)
Returns the instance of the Plugin Manager (there is only ever one)
static bool IsSolid(BLOCKTYPE Block)
Is this block solid (player cannot walk through)?
Definition: BlockInfo.cpp:892
Represents two sets of coords, minimum and maximum for each direction.
Definition: BoundingBox.h:24
bool CalcLineIntersection(Vector3d a_LinePoint1, Vector3d a_LinePoint2, double &a_LineCoeff, eBlockFace &a_Face) const
Returns true if this bounding box is intersected by the line specified by its two points Also calcula...
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
void SendSpawnEntity(const cEntity &a_Entity)
void SendEntityMetadata(const cEntity &a_Entity)
Class that stores item enchantments or stored-enchantments The enchantments may be serialized to a st...
Definition: Enchantments.h:42
Definition: Entity.h:76
const Vector3d & GetSpeed(void) const
Exported in ManualBindings.
Definition: Entity.h:300
bool IsPlayer(void) const
Definition: Entity.h:160
cBoundingBox GetBoundingBox() const
Definition: Entity.h:211
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk &a_Chunk)
Definition: Entity.cpp:909
float m_AirDrag
Stores the air drag that is applied to the entity every tick, measured in speed ratio per tick Acts a...
Definition: Entity.h:614
bool IsEnderCrystal(void) const
Definition: Entity.h:159
void SetPitchFromSpeed(void)
Sets the pitch to match the speed vector (entity gies "face-forward")
Definition: Entity.cpp:387
virtual const char * GetClass(void) const
Returns the topmost class name for the object.
Definition: Entity.cpp:84
bool IsTicking(void) const
Returns true if the entity is valid and ticking.
Definition: Entity.cpp:2259
float m_Gravity
Stores gravity that is applied to an entity every tick For realistic effects, this should be negative...
Definition: Entity.h:608
UInt32 m_UniqueID
The ID of the entity that is guaranteed to be unique within a single run of the server.
Definition: Entity.h:582
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
void SetPosition(double a_PosX, double a_PosY, double a_PosZ)
Definition: Entity.h:218
float GetWidth(void) const
Definition: Entity.h:205
cWorld * m_World
Definition: Entity.h:624
bool IsMinecart(void) const
Definition: Entity.h:165
UInt32 GetUniqueID(void) const
Definition: Entity.h:253
bool IsPawn(void) const
Definition: Entity.h:163
bool IsBoat(void) const
Definition: Entity.h:166
float GetHeight(void) const
Definition: Entity.h:193
void SetAirDrag(float a_AirDrag)
Definition: Entity.h:286
void StopBurning(void)
Stops the entity from burning, resets all burning timers.
Definition: Entity.cpp:1925
long int GetTicksAlive(void) const
Gets number of ticks this entity has been alive for.
Definition: Entity.h:510
const Vector3d & GetPosition(void) const
Exported in ManualBindings.
Definition: Entity.h:297
void SetYawFromSpeed(void)
Sets the rotation to match the speed vector (entity goes "face-forward")
Definition: Entity.cpp:371
virtual void BroadcastMovementUpdate(const cClientHandle *a_Exclude=nullptr)
Updates clients of changes in the entity.
Definition: Entity.cpp:1966
void StartBurning(int a_TicksLeftBurning)
Puts the entity on fire for the specified amount of ticks.
Definition: Entity.cpp:1908
bool IsMob(void) const
Definition: Entity.h:162
Definition: Pawn.h:17
Definition: Player.h:29
bool IsGameModeSpectator(void) const
Returns true if the player is in Spectator mode, either explicitly, or by inheriting from current wor...
Definition: Player.cpp:1052
cProjectileEntity * m_Projectile
virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
cProjectileTracerCallback(cProjectileEntity *a_Projectile)
double GetSlowdownCoeff(void) const
cProjectileEntityCollisionCallback(cProjectileEntity *a_Projectile, const Vector3d &a_Pos, const Vector3d &a_NextPos)
cEntity * GetHitEntity(void) const
Returns the nearest entity that was hit, after the enumeration has been completed.
bool HasHit(void) const
Returns true if the callback has encountered a true hit.
double GetMinCoeff(void) const
Returns the line coeff where the hit was encountered, after the enumeration has been completed.
UInt32 GetCreatorUniqueID(void) const
Returns the unique ID of the entity who created this projectile May return an ID <0.
eKind
The kind of the projectile.
eKind m_ProjectileKind
The type of projectile I am.
cProjectileEntity(eKind a_Kind, cEntity *a_Creator, Vector3d a_Pos, float a_Width, float a_Height)
virtual void HandlePhysics(std::chrono::milliseconds a_Dt, cChunk &a_Chunk) override
Handles the physics of the entity - updates position based on speed, updates speed based on environme...
virtual void OnHitSolidBlock(Vector3d a_HitPos, eBlockFace a_HitFace)
Called by the physics blocktracer when the entity hits a solid block, the hit position and the face h...
static std::unique_ptr< cProjectileEntity > Create(eKind a_Kind, cEntity *a_Creator, Vector3d a_Pos, const cItem *a_Item, const Vector3d *a_Speed=nullptr)
Creates a new instance of the specified projectile entity.
AString GetCreatorName(void) const
Returns the name of the player that created the projectile Will be empty for non-player creators.
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk &a_Chunk) override
bool m_IsInGround
True if the projectile has hit the ground and is stuck there.
virtual void CollectedBy(cPlayer &a_Dest)
Called by Chunk when the projectile is eligible for player collection.
AString GetMCAClassName(void) const
Returns the string that is used as the entity type (class name) in MCA files.
virtual void OnHitEntity(cEntity &a_EntityHit, Vector3d a_HitPos)
Called by the physics blocktracer when the entity hits another entity.
virtual void SpawnOn(cClientHandle &a_Client) final override
Descendants override this function to send a command to the specified client to spawn the entity on t...
Definition: Item.h:37
cFireworkItem m_FireworkItem
Definition: Item.h:201
bool Trace(Vector3d a_Start, Vector3d a_End)
Traces one line between Start and End; returns true if the entire line was traced (until OnNoMoreHits...
T y
Definition: Vector3.h:17
bool DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback a_Callback)
Calls the callback if the entity with the specified ID is found, with the entity object as the callba...
Definition: World.cpp:2464
std::vector< int > m_Colours