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