summaryrefslogtreecommitdiffstats
path: root/src/Entities
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Entities/ArrowEntity.cpp64
-rw-r--r--src/Entities/ArrowEntity.h6
-rw-r--r--src/Entities/Entity.cpp17
-rw-r--r--src/Entities/Entity.h4
-rw-r--r--src/Entities/ExpOrb.h2
-rw-r--r--src/Entities/Pickup.cpp2
-rw-r--r--src/Entities/Player.cpp305
-rw-r--r--src/Entities/Player.h45
-rw-r--r--src/Entities/ProjectileEntity.cpp71
-rw-r--r--src/Entities/ProjectileEntity.h34
-rw-r--r--src/Entities/ThrownEggEntity.cpp7
-rw-r--r--src/Entities/ThrownEggEntity.h21
-rw-r--r--src/Entities/ThrownEnderPearlEntity.cpp40
-rw-r--r--src/Entities/ThrownEnderPearlEntity.h25
-rw-r--r--src/Entities/ThrownSnowballEntity.cpp7
-rw-r--r--src/Entities/ThrownSnowballEntity.h21
16 files changed, 445 insertions, 226 deletions
diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp
index 4727f67a9..d59088e72 100644
--- a/src/Entities/ArrowEntity.cpp
+++ b/src/Entities/ArrowEntity.cpp
@@ -72,26 +72,24 @@ bool cArrowEntity::CanPickup(const cPlayer & a_Player) const
void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace)
-{
- if (a_HitFace == BLOCK_FACE_NONE) { return; }
-
- super::OnHitSolidBlock(a_HitPos, a_HitFace);
- int a_X = (int)a_HitPos.x, a_Y = (int)a_HitPos.y, a_Z = (int)a_HitPos.z;
-
- switch (a_HitFace)
+{
+ if (GetSpeed().EqualsEps(Vector3d(0, 0, 0), 0.0000001))
{
- case BLOCK_FACE_XM: // Strangely, bounding boxes / block tracers return the actual block for these two directions, so AddFace not needed
- case BLOCK_FACE_YM:
- {
- break;
- }
- default: AddFaceDirection(a_X, a_Y, a_Z, a_HitFace, true);
+ SetSpeed(GetLookVector().NormalizeCopy() * 0.1); // Ensure that no division by zero happens later
}
-
- m_HitBlockPos = Vector3i(a_X, a_Y, a_Z);
+
+ Vector3d Hit = a_HitPos;
+ Vector3d SinkMovement = (GetSpeed() / 800);
+ Hit += (SinkMovement * 0.01) / SinkMovement.Length(); // Make arrow sink into block a centimetre so it lodges (but not to far so it goes black clientside)
+
+ super::OnHitSolidBlock(Hit, a_HitFace);
+ Vector3i BlockHit = Hit.Floor();
+
+ int X = BlockHit.x, Y = BlockHit.y, Z = BlockHit.z;
+ m_HitBlockPos = Vector3i(X, Y, Z);
// Broadcast arrow hit sound
- m_World->BroadcastSoundEffect("random.bowhit", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
+ m_World->BroadcastSoundEffect("random.bowhit", X * 8, Y * 8, Z * 8, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
}
@@ -99,13 +97,7 @@ void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFa
void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos)
-{
- if (!a_EntityHit.IsMob() && !a_EntityHit.IsMinecart() && !a_EntityHit.IsPlayer() && !a_EntityHit.IsBoat())
- {
- // Not an entity that interacts with an arrow
- return;
- }
-
+{
int Damage = (int)(GetSpeed().Length() / 20 * m_DamageCoeff + 0.5);
if (m_IsCritical)
{
@@ -114,14 +106,7 @@ void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos)
a_EntityHit.TakeDamage(dtRangedAttack, this, Damage, 1);
// Broadcast successful hit sound
- m_World->BroadcastSoundEffect(
- "random.successful_hit",
- (int)std::floor(GetPosX() * 8.0),
- (int)std::floor(GetPosY() * 8.0),
- (int)std::floor(GetPosZ() * 8.0),
- 0.5f,
- 0.75f + ((float)((GetUniqueID() * 23) % 32)) / 64.0f
- );
+ GetWorld()->BroadcastSoundEffect("random.successful_hit", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
Destroy();
}
@@ -144,21 +129,10 @@ void cArrowEntity::CollectedBy(cPlayer * a_Dest)
return;
}
}
-
- // TODO: BroadcastCollectPickup needs a cPickup, which we don't have
- // m_World->BroadcastCollectPickup(*this, *a_Dest);
+ GetWorld()->BroadcastCollectEntity(*this, *a_Dest);
+ GetWorld()->BroadcastSoundEffect("random.pop", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
m_bIsCollected = true;
-
- cFastRandom Random;
- m_World->BroadcastSoundEffect(
- "random.pop",
- (int)std::floor(GetPosX() * 8.0),
- (int)std::floor(GetPosY() * 8),
- (int)std::floor(GetPosZ() * 8),
- 0.2F,
- ((Random.NextFloat(1.0F) - Random.NextFloat(1.0F)) * 0.7F + 1.0F) * 2.0F
- );
}
}
@@ -194,7 +168,7 @@ void cArrowEntity::Tick(float a_Dt, cChunk & a_Chunk)
if (!m_HasTeleported) // Sent a teleport already, don't do again
{
- if (m_HitGroundTimer > 1000.f) // Send after a second, could be less, but just in case
+ if (m_HitGroundTimer > 500.f) // Send after half a second, could be less, but just in case
{
m_World->BroadcastTeleportEntity(*this);
m_HasTeleported = true;
diff --git a/src/Entities/ArrowEntity.h b/src/Entities/ArrowEntity.h
index 1fe3032ee..76cb24449 100644
--- a/src/Entities/ArrowEntity.h
+++ b/src/Entities/ArrowEntity.h
@@ -58,8 +58,14 @@ public:
/// Sets the IsCritical flag
void SetIsCritical(bool a_IsCritical) { m_IsCritical = a_IsCritical; }
+
+ /** Gets the block arrow is in */
+ Vector3i GetBlockHit(void) const { return m_HitBlockPos; }
// tolua_end
+
+ /** Sets the block arrow is in. To be used by the MCA loader only! */
+ void SetBlockHit(const Vector3i & a_BlockHit) { m_HitBlockPos = a_BlockHit; }
protected:
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp
index ee7ce06ac..26823924f 100644
--- a/src/Entities/Entity.cpp
+++ b/src/Entities/Entity.cpp
@@ -1090,7 +1090,10 @@ void cEntity::HandleAir(void)
if (IsSubmerged())
{
- SetSpeedY(1); // Float in the water
+ if (!IsPlayer()) // Players control themselves
+ {
+ SetSpeedY(1); // Float in the water
+ }
// Either reduce air level or damage player
if (m_AirLevel < 1)
@@ -1476,8 +1479,7 @@ void cEntity::SetWidth(double a_Width)
void cEntity::AddPosX(double a_AddPosX)
{
- m_Pos.x += a_AddPosX;
-
+ m_Pos.x += a_AddPosX;
}
@@ -1485,8 +1487,7 @@ void cEntity::AddPosX(double a_AddPosX)
void cEntity::AddPosY(double a_AddPosY)
{
- m_Pos.y += a_AddPosY;
-
+ m_Pos.y += a_AddPosY;
}
@@ -1494,8 +1495,7 @@ void cEntity::AddPosY(double a_AddPosY)
void cEntity::AddPosZ(double a_AddPosZ)
{
- m_Pos.z += a_AddPosZ;
-
+ m_Pos.z += a_AddPosZ;
}
@@ -1505,8 +1505,7 @@ void cEntity::AddPosition(double a_AddPosX, double a_AddPosY, double a_AddPosZ)
{
m_Pos.x += a_AddPosX;
m_Pos.y += a_AddPosY;
- m_Pos.z += a_AddPosZ;
-
+ m_Pos.z += a_AddPosZ;
}
diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h
index 2df66e353..f4080f8aa 100644
--- a/src/Entities/Entity.h
+++ b/src/Entities/Entity.h
@@ -237,9 +237,9 @@ public:
void AddPosY (double a_AddPosY);
void AddPosZ (double a_AddPosZ);
void AddPosition(double a_AddPosX, double a_AddPosY, double a_AddPosZ);
- void AddPosition(const Vector3d & a_AddPos) { AddPosition(a_AddPos.x,a_AddPos.y,a_AddPos.z);}
+ void AddPosition(const Vector3d & a_AddPos) { AddPosition(a_AddPos.x, a_AddPos.y, a_AddPos.z); }
void AddSpeed (double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeedZ);
- void AddSpeed (const Vector3d & a_AddSpeed) { AddSpeed(a_AddSpeed.x,a_AddSpeed.y,a_AddSpeed.z);}
+ void AddSpeed (const Vector3d & a_AddSpeed) { AddSpeed(a_AddSpeed.x, a_AddSpeed.y, a_AddSpeed.z); }
void AddSpeedX (double a_AddSpeedX);
void AddSpeedY (double a_AddSpeedY);
void AddSpeedZ (double a_AddSpeedZ);
diff --git a/src/Entities/ExpOrb.h b/src/Entities/ExpOrb.h
index e76274ac9..2cd4ef31f 100644
--- a/src/Entities/ExpOrb.h
+++ b/src/Entities/ExpOrb.h
@@ -11,7 +11,7 @@
class cExpOrb :
public cEntity
{
- typedef cExpOrb super;
+ typedef cEntity super;
public:
// tolua_end
diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp
index 10b6bbd5c..24fa591da 100644
--- a/src/Entities/Pickup.cpp
+++ b/src/Entities/Pickup.cpp
@@ -224,7 +224,7 @@ bool cPickup::CollectedBy(cPlayer * a_Dest)
}
m_Item.m_ItemCount -= NumAdded;
- m_World->BroadcastCollectPickup(*this, *a_Dest);
+ m_World->BroadcastCollectEntity(*this, *a_Dest);
// Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;)
m_World->BroadcastSoundEffect("random.pop",(int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
if (m_Item.m_ItemCount <= 0)
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index 7b4fd219d..944ed643e 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -8,6 +8,7 @@
#include "../World.h"
#include "../Bindings/PluginManager.h"
#include "../BlockEntities/BlockEntity.h"
+#include "../BlockEntities/EnderChestEntity.h"
#include "../GroupManager.h"
#include "../Group.h"
#include "../Root.h"
@@ -33,50 +34,48 @@
-cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName)
- : super(etPlayer, 0.6, 1.8)
- , m_bVisible(true)
- , m_FoodLevel(MAX_FOOD_LEVEL)
- , m_FoodSaturationLevel(5)
- , m_FoodTickTimer(0)
- , m_FoodExhaustionLevel(0)
- , m_FoodPoisonedTicksRemaining(0)
- , m_LastJumpHeight(0)
- , m_LastGroundHeight(0)
- , m_bTouchGround(false)
- , m_Stance(0.0)
- , m_Inventory(*this)
- , m_CurrentWindow(NULL)
- , m_InventoryWindow(NULL)
- , m_Color('-')
- , m_GameMode(eGameMode_NotSet)
- , m_IP("")
- , m_ClientHandle(a_Client)
- , m_NormalMaxSpeed(1.0)
- , m_SprintingMaxSpeed(1.3)
- , m_FlyingMaxSpeed(1.0)
- , m_IsCrouched(false)
- , m_IsSprinting(false)
- , m_IsFlying(false)
- , m_IsSwimming(false)
- , m_IsSubmerged(false)
- , m_IsFishing(false)
- , m_CanFly(false)
- , m_EatingFinishTick(-1)
- , m_LifetimeTotalXp(0)
- , m_CurrentXp(0)
- , m_bDirtyExperience(false)
- , m_IsChargingBow(false)
- , m_BowCharge(0)
- , m_FloaterID(-1)
- , m_Team(NULL)
- , m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL)
-{
- LOGD("Created a player object for \"%s\" @ \"%s\" at %p, ID %d",
- a_PlayerName.c_str(), a_Client->GetIPString().c_str(),
- this, GetUniqueID()
- );
-
+cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) :
+ super(etPlayer, 0.6, 1.8),
+ m_bVisible(true),
+ m_FoodLevel(MAX_FOOD_LEVEL),
+ m_FoodSaturationLevel(5.0),
+ m_FoodTickTimer(0),
+ m_FoodExhaustionLevel(0.0),
+ m_FoodPoisonedTicksRemaining(0),
+ m_LastJumpHeight(0),
+ m_LastGroundHeight(0),
+ m_bTouchGround(false),
+ m_Stance(0.0),
+ m_Inventory(*this),
+ m_EnderChestContents(9, 3),
+ m_CurrentWindow(NULL),
+ m_InventoryWindow(NULL),
+ m_Color('-'),
+ m_GameMode(eGameMode_NotSet),
+ m_IP(""),
+ m_ClientHandle(a_Client),
+ m_NormalMaxSpeed(1.0),
+ m_SprintingMaxSpeed(1.3),
+ m_FlyingMaxSpeed(1.0),
+ m_IsCrouched(false),
+ m_IsSprinting(false),
+ m_IsFlying(false),
+ m_IsSwimming(false),
+ m_IsSubmerged(false),
+ m_IsFishing(false),
+ m_CanFly(false),
+ m_EatingFinishTick(-1),
+ m_LifetimeTotalXp(0),
+ m_CurrentXp(0),
+ m_bDirtyExperience(false),
+ m_IsChargingBow(false),
+ m_BowCharge(0),
+ m_FloaterID(-1),
+ m_Team(NULL),
+ m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL),
+ m_bIsTeleporting(false),
+ m_UUID((a_Client != NULL) ? a_Client->GetUUID() : "")
+{
m_InventoryWindow = new cInventoryWindow(*this);
m_CurrentWindow = m_InventoryWindow;
m_InventoryWindow->OpenedByPlayer(*this);
@@ -225,7 +224,7 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk)
SendExperience();
}
- if (GetPosition() != m_LastPos) // Change in position from last tick?
+ if (!GetPosition().EqualsEps(m_LastPos, 0.01)) // Non negligible change in position from last tick?
{
// Apply food exhaustion from movement:
ApplyFoodExhaustionFromMovement();
@@ -521,7 +520,15 @@ void cPlayer::Heal(int a_Health)
void cPlayer::SetFoodLevel(int a_FoodLevel)
{
- m_FoodLevel = std::max(0, std::min(a_FoodLevel, (int)MAX_FOOD_LEVEL));
+ int FoodLevel = std::max(0, std::min(a_FoodLevel, (int)MAX_FOOD_LEVEL));
+
+ if (cRoot::Get()->GetPluginManager()->CallHookPlayerFoodLevelChange(*this, FoodLevel))
+ {
+ m_FoodSaturationLevel = 5.0;
+ return;
+ }
+
+ m_FoodLevel = FoodLevel;
SendHealth();
}
@@ -571,11 +578,9 @@ bool cPlayer::Feed(int a_Food, double a_Saturation)
{
return false;
}
-
- m_FoodLevel = std::min(a_Food + m_FoodLevel, (int)MAX_FOOD_LEVEL);
- m_FoodSaturationLevel = std::min(m_FoodSaturationLevel + a_Saturation, (double)m_FoodLevel);
-
- SendHealth();
+
+ SetFoodSaturationLevel(m_FoodSaturationLevel + a_Saturation);
+ SetFoodLevel(m_FoodLevel + a_Food);
return true;
}
@@ -969,14 +974,15 @@ void cPlayer::Respawn(void)
// Reset food level:
m_FoodLevel = MAX_FOOD_LEVEL;
- m_FoodSaturationLevel = 5;
+ m_FoodSaturationLevel = 5.0;
+ m_FoodExhaustionLevel = 0.0;
// Reset Experience
m_CurrentXp = 0;
m_LifetimeTotalXp = 0;
// ToDo: send score to client? How?
- m_ClientHandle->SendRespawn(*m_World);
+ m_ClientHandle->SendRespawn(*m_World, true);
// Extinguish the fire:
StopBurning();
@@ -1226,6 +1232,7 @@ void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
SetPosition(a_PosX, a_PosY, a_PosZ);
m_LastGroundHeight = (float)a_PosY;
m_LastJumpHeight = (float)a_PosY;
+ m_bIsTeleporting = true;
m_World->BroadcastTeleportEntity(*this, GetClientHandle());
m_ClientHandle->SendPlayerMoveLook();
@@ -1679,53 +1686,98 @@ void cPlayer::LoadPermissionsFromDisk()
-bool cPlayer::LoadFromDisk()
+
+bool cPlayer::LoadFromDisk(void)
{
LoadPermissionsFromDisk();
- AString SourceFile;
- Printf(SourceFile, "players/%s.json", GetName().c_str() );
+ // Load from the UUID file:
+ if (LoadFromFile(GetUUIDFileName(m_UUID)))
+ {
+ return true;
+ }
+
+ // Load from the offline UUID file, if allowed:
+ AString OfflineUUID = cClientHandle::GenerateOfflineUUID(GetName());
+ if (cRoot::Get()->GetServer()->ShouldLoadOfflinePlayerData())
+ {
+ if (LoadFromFile(GetUUIDFileName(OfflineUUID)))
+ {
+ return true;
+ }
+ }
+
+ // Load from the old-style name-based file, if allowed:
+ if (cRoot::Get()->GetServer()->ShouldLoadNamedPlayerData())
+ {
+ AString OldStyleFileName = Printf("players/%s.json", GetName().c_str());
+ if (LoadFromFile(OldStyleFileName))
+ {
+ // Save in new format and remove the old file
+ if (SaveToDisk())
+ {
+ cFile::Delete(OldStyleFileName);
+ }
+ return true;
+ }
+ }
+
+ // None of the files loaded successfully
+ LOG("Player data file not found for %s (%s, offline %s), will be reset to defaults.",
+ GetName().c_str(), m_UUID.c_str(), OfflineUUID.c_str()
+ );
+ return false;
+}
+
+
+
+
+bool cPlayer::LoadFromFile(const AString & a_FileName)
+{
+ // Load the data from the file:
cFile f;
- if (!f.Open(SourceFile, cFile::fmRead))
+ if (!f.Open(a_FileName, cFile::fmRead))
{
// This is a new player whom we haven't seen yet, bail out, let them have the defaults
return false;
}
-
AString buffer;
if (f.ReadRestOfFile(buffer) != f.GetSize())
{
- LOGWARNING("Cannot read player data from file \"%s\"", SourceFile.c_str());
+ LOGWARNING("Cannot read player data from file \"%s\"", a_FileName.c_str());
return false;
}
- f.Close(); //cool kids play nice
+ f.Close();
+ // Parse the JSON format:
Json::Value root;
Json::Reader reader;
if (!reader.parse(buffer, root, false))
{
- LOGWARNING("Cannot parse player data in file \"%s\", player will be reset", SourceFile.c_str());
+ LOGWARNING("Cannot parse player data in file \"%s\"", a_FileName.c_str());
+ return false;
}
+ // Load the player data:
Json::Value & JSON_PlayerPosition = root["position"];
if (JSON_PlayerPosition.size() == 3)
{
- SetPosX(JSON_PlayerPosition[(unsigned int)0].asDouble());
- SetPosY(JSON_PlayerPosition[(unsigned int)1].asDouble());
- SetPosZ(JSON_PlayerPosition[(unsigned int)2].asDouble());
+ SetPosX(JSON_PlayerPosition[(unsigned)0].asDouble());
+ SetPosY(JSON_PlayerPosition[(unsigned)1].asDouble());
+ SetPosZ(JSON_PlayerPosition[(unsigned)2].asDouble());
m_LastPos = GetPosition();
}
Json::Value & JSON_PlayerRotation = root["rotation"];
if (JSON_PlayerRotation.size() == 3)
{
- SetYaw ((float)JSON_PlayerRotation[(unsigned int)0].asDouble());
- SetPitch ((float)JSON_PlayerRotation[(unsigned int)1].asDouble());
- SetRoll ((float)JSON_PlayerRotation[(unsigned int)2].asDouble());
+ SetYaw ((float)JSON_PlayerRotation[(unsigned)0].asDouble());
+ SetPitch ((float)JSON_PlayerRotation[(unsigned)1].asDouble());
+ SetRoll ((float)JSON_PlayerRotation[(unsigned)2].asDouble());
}
- m_Health = root.get("health", 0).asInt();
+ m_Health = root.get("health", 0).asInt();
m_AirLevel = root.get("air", MAX_AIR_LEVEL).asInt();
m_FoodLevel = root.get("food", MAX_FOOD_LEVEL).asInt();
m_FoodSaturationLevel = root.get("foodSaturation", MAX_FOOD_LEVEL).asDouble();
@@ -1743,6 +1795,7 @@ bool cPlayer::LoadFromDisk()
}
m_Inventory.LoadFromJson(root["inventory"]);
+ cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents);
m_LoadedWorldName = root.get("world", "world").asString();
@@ -1751,8 +1804,8 @@ bool cPlayer::LoadFromDisk()
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), GetName(), &m_Stats);
StatSerializer.Load();
- LOGD("Player \"%s\" was read from file, spawning at {%.2f, %.2f, %.2f} in world \"%s\"",
- GetName().c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str()
+ LOGD("Player \"%s\" was read from file \"%s\", spawning at {%.2f, %.2f, %.2f} in world \"%s\"",
+ GetName().c_str(), a_FileName.c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str()
);
return true;
@@ -1765,6 +1818,7 @@ bool cPlayer::LoadFromDisk()
bool cPlayer::SaveToDisk()
{
cFile::CreateFolder(FILE_IO_PREFIX + AString("players"));
+ cFile::CreateFolder(FILE_IO_PREFIX + AString("players/") + m_UUID.substr(0, 2));
// create the JSON data
Json::Value JSON_PlayerPosition;
@@ -1780,45 +1834,61 @@ bool cPlayer::SaveToDisk()
Json::Value JSON_Inventory;
m_Inventory.SaveToJson(JSON_Inventory);
+ Json::Value JSON_EnderChestInventory;
+ cEnderChestEntity::SaveToJson(JSON_EnderChestInventory, m_EnderChestContents);
+
Json::Value root;
- root["position"] = JSON_PlayerPosition;
- root["rotation"] = JSON_PlayerRotation;
- root["inventory"] = JSON_Inventory;
- root["health"] = m_Health;
- root["xpTotal"] = m_LifetimeTotalXp;
- root["xpCurrent"] = m_CurrentXp;
- root["air"] = m_AirLevel;
- root["food"] = m_FoodLevel;
- root["foodSaturation"] = m_FoodSaturationLevel;
- root["foodTickTimer"] = m_FoodTickTimer;
- root["foodExhaustion"] = m_FoodExhaustionLevel;
- root["world"] = GetWorld()->GetName();
- root["isflying"] = IsFlying();
-
- if (m_GameMode == GetWorld()->GetGameMode())
- {
- root["gamemode"] = (int) eGameMode_NotSet;
+ root["position"] = JSON_PlayerPosition;
+ root["rotation"] = JSON_PlayerRotation;
+ root["inventory"] = JSON_Inventory;
+ root["enderchestinventory"] = JSON_EnderChestInventory;
+ root["health"] = m_Health;
+ root["xpTotal"] = m_LifetimeTotalXp;
+ root["xpCurrent"] = m_CurrentXp;
+ root["air"] = m_AirLevel;
+ root["food"] = m_FoodLevel;
+ root["foodSaturation"] = m_FoodSaturationLevel;
+ root["foodTickTimer"] = m_FoodTickTimer;
+ root["foodExhaustion"] = m_FoodExhaustionLevel;
+ root["isflying"] = IsFlying();
+ root["lastknownname"] = GetName();
+ if (m_World != NULL)
+ {
+ root["world"] = m_World->GetName();
+ if (m_GameMode == m_World->GetGameMode())
+ {
+ root["gamemode"] = (int) eGameMode_NotSet;
+ }
+ else
+ {
+ root["gamemode"] = (int) m_GameMode;
+ }
}
else
{
- root["gamemode"] = (int) m_GameMode;
+ // This happens if the player is saved to new format after loading from the old format
+ root["world"] = m_LoadedWorldName;
+ root["gamemode"] = (int) eGameMode_NotSet;
}
Json::StyledWriter writer;
std::string JsonData = writer.write(root);
- AString SourceFile;
- Printf(SourceFile, "players/%s.json", GetName().c_str() );
+ AString SourceFile = GetUUIDFileName(m_UUID);
cFile f;
if (!f.Open(SourceFile, cFile::fmWrite))
{
- LOGERROR("ERROR WRITING PLAYER \"%s\" TO FILE \"%s\" - cannot open file", GetName().c_str(), SourceFile.c_str());
+ LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot open file. Player will lose their progress.",
+ GetName().c_str(), SourceFile.c_str()
+ );
return false;
}
if (f.Write(JsonData.c_str(), JsonData.size()) != (int)JsonData.size())
{
- LOGERROR("ERROR WRITING PLAYER JSON TO FILE \"%s\"", SourceFile.c_str());
+ LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot save data. Player will lose their progress. ",
+ GetName().c_str(), SourceFile.c_str()
+ );
return false;
}
@@ -1827,7 +1897,7 @@ bool cPlayer::SaveToDisk()
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), GetName(), &m_Stats);
if (!StatSerializer.Save())
{
- LOGERROR("Could not save stats for player %s", GetName().c_str());
+ LOGWARNING("Could not save stats for player %s", GetName().c_str());
return false;
}
@@ -1895,16 +1965,13 @@ void cPlayer::TickBurning(cChunk & a_Chunk)
void cPlayer::HandleFood(void)
{
// Ref.: http://www.minecraftwiki.net/wiki/Hunger
-
+
if (IsGameModeCreative())
{
// Hunger is disabled for Creative
return;
}
-
- // Remember the food level before processing, for later comparison
- int LastFoodLevel = m_FoodLevel;
-
+
// Heal or damage, based on the food level, using the m_FoodTickTimer:
if ((m_FoodLevel > 17) || (m_FoodLevel <= 0))
{
@@ -1913,11 +1980,11 @@ void cPlayer::HandleFood(void)
{
m_FoodTickTimer = 0;
- if (m_FoodLevel >= 17)
+ if ((m_FoodLevel > 17) && (GetHealth() < GetMaxHealth()))
{
// Regenerate health from food, incur 3 pts of food exhaustion:
Heal(1);
- m_FoodExhaustionLevel += 3;
+ m_FoodExhaustionLevel += 3.0;
}
else if ((m_FoodLevel <= 0) && (m_Health > 1))
{
@@ -1926,7 +1993,7 @@ void cPlayer::HandleFood(void)
}
}
}
-
+
// Apply food poisoning food exhaustion:
if (m_FoodPoisonedTicksRemaining > 0)
{
@@ -1939,24 +2006,19 @@ void cPlayer::HandleFood(void)
}
// Apply food exhaustion that has accumulated:
- if (m_FoodExhaustionLevel >= 4)
+ if (m_FoodExhaustionLevel >= 4.0)
{
- m_FoodExhaustionLevel -= 4;
+ m_FoodExhaustionLevel -= 4.0;
- if (m_FoodSaturationLevel >= 1)
+ if (m_FoodSaturationLevel >= 1.0)
{
- m_FoodSaturationLevel -= 1;
+ m_FoodSaturationLevel -= 1.0;
}
else
{
- m_FoodLevel = std::max(m_FoodLevel - 1, 0);
+ SetFoodLevel(m_FoodLevel - 1);
}
}
-
- if (m_FoodLevel != LastFoodLevel)
- {
- SendHealth();
- }
}
@@ -2079,6 +2141,11 @@ void cPlayer::ApplyFoodExhaustionFromMovement()
{
return;
}
+ if (m_bIsTeleporting)
+ {
+ m_bIsTeleporting = false;
+ return;
+ }
// If riding anything, apply no food exhaustion
if (m_AttachedTo != NULL)
@@ -2143,3 +2210,19 @@ void cPlayer::Detach()
+
+AString cPlayer::GetUUIDFileName(const AString & a_UUID)
+{
+ ASSERT(a_UUID.size() == 36);
+
+ AString res("players/");
+ res.append(a_UUID, 0, 2);
+ res.push_back('/');
+ res.append(a_UUID, 2, AString::npos);
+ res.append(".json");
+ return res;
+}
+
+
+
+
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index 2f7957f16..8f9b46e0f 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -41,6 +41,7 @@ public:
cPlayer(cClientHandle * a_Client, const AString & a_PlayerName);
+
virtual ~cPlayer();
virtual void SpawnOn(cClientHandle & a_Client) override;
@@ -124,6 +125,9 @@ public:
inline double GetStance(void) const { return GetPosY() + 1.62; } // tolua_export // TODO: Proper stance when crouching etc.
inline cInventory & GetInventory(void) { return m_Inventory; } // tolua_export
inline const cInventory & GetInventory(void) const { return m_Inventory; }
+
+ /** Gets the contents of the player's associated enderchest */
+ cItemGrid & GetEnderChestContents(void) { return m_EnderChestContents; }
inline const cItem & GetEquippedItem(void) const { return GetInventory().GetEquippedItem(); } // tolua_export
@@ -334,7 +338,15 @@ public:
bool MoveToWorld(const char * a_WorldName); // tolua_export
bool SaveToDisk(void);
+
+ /** Loads the player data from the disk file.
+ Returns true on success, false on failure. */
bool LoadFromDisk(void);
+
+ /** Loads the player data from the specified file.
+ Returns true on success, false on failure. */
+ bool LoadFromFile(const AString & a_FileName);
+
void LoadPermissionsFromDisk(void); // tolua_export
const AString & GetLoadedWorldName() { return m_LoadedWorldName; }
@@ -449,7 +461,13 @@ protected:
float m_LastGroundHeight;
bool m_bTouchGround;
double m_Stance;
+
+ /** Stores the player's inventory, consisting of crafting grid, hotbar, and main slots */
cInventory m_Inventory;
+
+ /** An item grid that stores the player specific enderchest contents */
+ cItemGrid m_EnderChestContents;
+
cWindow * m_CurrentWindow;
cWindow * m_InventoryWindow;
@@ -510,6 +528,22 @@ protected:
cStatManager m_Stats;
+ /** Flag representing whether the player is currently in a bed
+ Set by a right click on unoccupied bed, unset by a time fast forward or teleport */
+ bool m_bIsInBed;
+
+ /** How long till the player's inventory will be saved
+ Default save interval is #defined in PLAYER_INVENTORY_SAVE_INTERVAL */
+ unsigned int m_TicksUntilNextSave;
+
+ /** Flag used by food handling system to determine whether a teleport has just happened
+ Will not apply food penalties if found to be true; will set to false after processing
+ */
+ bool m_bIsTeleporting;
+
+ /** The UUID of the player, as read from the ClientHandle.
+ If no ClientHandle is given, the UUID is initialized to empty. */
+ AString m_UUID;
/** Sets the speed and sends it to the client, so that they are forced to move so. */
@@ -538,14 +572,9 @@ protected:
/** Adds food exhaustion based on the difference between Pos and LastPos, sprinting status and swimming (in water block) */
void ApplyFoodExhaustionFromMovement();
- /** Flag representing whether the player is currently in a bed
- Set by a right click on unoccupied bed, unset by a time fast forward or teleport */
- bool m_bIsInBed;
-
- /** How long till the player's inventory will be saved
- Default save interval is #defined in PLAYER_INVENTORY_SAVE_INTERVAL */
- unsigned int m_TicksUntilNextSave;
-
+ /** Returns the filename for the player data based on the UUID given.
+ This can be used both for online and offline UUIDs. */
+ AString GetUUIDFileName(const AString & a_UUID);
} ; // tolua_export
diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp
index 95c494569..0bb34019e 100644
--- a/src/Entities/ProjectileEntity.cpp
+++ b/src/Entities/ProjectileEntity.cpp
@@ -21,6 +21,7 @@
#include "FireChargeEntity.h"
#include "FireworkEntity.h"
#include "GhastFireballEntity.h"
+#include "Player.h"
@@ -67,16 +68,17 @@ protected:
if (cBlockInfo::IsSolid(a_BlockType))
{
- // The projectile hit a solid block
- // Calculate the exact hit coords:
- cBoundingBox bb(a_BlockX, a_BlockX + 1, a_BlockY, a_BlockY + 1, a_BlockZ, a_BlockZ + 1);
- Vector3d Line1 = m_Projectile->GetPosition();
- Vector3d Line2 = Line1 + m_Projectile->GetSpeed();
- double LineCoeff = 0;
- eBlockFace Face;
- if (bb.CalcLineIntersection(Line1, Line2, LineCoeff, Face))
+ // The projectile hit a solid block, calculate the exact hit coords:
+ cBoundingBox bb(a_BlockX, a_BlockX + 1, a_BlockY, a_BlockY + 1, a_BlockZ, a_BlockZ + 1); // Bounding box of the block hit
+ const Vector3d LineStart = m_Projectile->GetPosition(); // Start point for the imaginary line that goes through the block hit
+ const Vector3d LineEnd = LineStart + m_Projectile->GetSpeed(); // End point for the imaginary line that goes through the block hit
+ double LineCoeff = 0; // Used to calculate where along the line an intersection with the bounding box occurs
+ eBlockFace Face; // Face hit
+
+ if (bb.CalcLineIntersection(LineStart, LineEnd, LineCoeff, Face))
{
- Vector3d Intersection = Line1 + m_Projectile->GetSpeed() * LineCoeff;
+ Vector3d Intersection = LineStart + m_Projectile->GetSpeed() * LineCoeff; // Point where projectile goes into the hit block
+
if (cPluginManager::Get()->CallHookProjectileHitBlock(*m_Projectile, a_BlockX, a_BlockY, a_BlockZ, Face, &Intersection))
{
return false;
@@ -140,7 +142,7 @@ public:
{
if (
(a_Entity == m_Projectile) || // Do not check collisions with self
- (a_Entity == m_Projectile->GetCreator()) // Do not check whoever shot the projectile
+ (a_Entity->GetUniqueID() == m_Projectile->GetCreatorUniqueID()) // Do not check whoever shot the projectile
)
{
// TODO: Don't check creator only for the first 5 ticks
@@ -161,7 +163,12 @@ public:
return false;
}
- // TODO: Some entities don't interact with the projectiles (pickups, falling blocks)
+ if (!a_Entity->IsMob() && !a_Entity->IsMinecart() && !a_Entity->IsPlayer() && !a_Entity->IsBoat())
+ {
+ // Not an entity that interacts with a projectile
+ return false;
+ }
+
if (cPluginManager::Get()->CallHookProjectileHitEntity(*m_Projectile, *a_Entity))
{
// A plugin disagreed.
@@ -209,7 +216,10 @@ protected:
cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, double a_Width, double a_Height) :
super(etProjectile, a_X, a_Y, a_Z, a_Width, a_Height),
m_ProjectileKind(a_Kind),
- m_Creator(a_Creator),
+ m_CreatorData(
+ ((a_Creator != NULL) ? a_Creator->GetUniqueID() : -1),
+ ((a_Creator != NULL) ? (a_Creator->IsPlayer() ? ((cPlayer *)a_Creator)->GetName() : "") : "")
+ ),
m_IsInGround(false)
{
}
@@ -221,7 +231,7 @@ cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, double a
cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, const Vector3d & a_Pos, const Vector3d & a_Speed, double a_Width, double a_Height) :
super(etProjectile, a_Pos.x, a_Pos.y, a_Pos.z, a_Width, a_Height),
m_ProjectileKind(a_Kind),
- m_Creator(a_Creator),
+ m_CreatorData(a_Creator->GetUniqueID(), a_Creator->IsPlayer() ? ((cPlayer *)a_Creator)->GetName() : ""),
m_IsInGround(false)
{
SetSpeed(a_Speed);
@@ -298,7 +308,7 @@ AString cProjectileEntity::GetMCAClassName(void) const
case pkEgg: return "Egg";
case pkGhastFireball: return "Fireball";
case pkFireCharge: return "SmallFireball";
- case pkEnderPearl: return "ThrownEnderPearl";
+ case pkEnderPearl: return "ThrownEnderpearl";
case pkExpBottle: return "ThrownExpBottle";
case pkSplashPotion: return "ThrownPotion";
case pkWitherSkull: return "WitherSkull";
@@ -316,8 +326,9 @@ AString cProjectileEntity::GetMCAClassName(void) const
void cProjectileEntity::Tick(float a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
-
- if (GetProjectileKind() != pkArrow) // See cArrow::Tick
+
+ // TODO: see BroadcastMovementUpdate; RelativeMove packet jerkiness affects projectiles too (cause of sympton described in cArrowEntity::Tick())
+ if (GetProjectileKind() != pkArrow)
{
BroadcastMovementUpdate();
}
@@ -335,19 +346,10 @@ void cProjectileEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk)
return;
}
- Vector3d PerTickSpeed = GetSpeed() / 20;
- Vector3d Pos = GetPosition();
-
- // Trace the tick's worth of movement as a line:
- Vector3d NextPos = Pos + PerTickSpeed;
- cProjectileTracerCallback TracerCallback(this);
- if (!cLineBlockTracer::Trace(*m_World, TracerCallback, Pos, NextPos))
- {
- // Something has been hit, abort all other processing
- return;
- }
- // The tracer also checks the blocks for slowdown blocks - water and lava - and stores it for later in its SlowdownCoeff
-
+ const Vector3d PerTickSpeed = GetSpeed() / 20;
+ const Vector3d Pos = GetPosition();
+ const Vector3d NextPos = Pos + PerTickSpeed;
+
// Test for entity collisions:
cProjectileEntityCollisionCallback EntityCollisionCallback(this, Pos, NextPos);
a_Chunk.ForEachEntity(EntityCollisionCallback);
@@ -364,10 +366,19 @@ void cProjectileEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk)
HitPos.x, HitPos.y, HitPos.z,
EntityCollisionCallback.GetMinCoeff()
);
-
+
OnHitEntity(*(EntityCollisionCallback.GetHitEntity()), HitPos);
}
// TODO: Test the entities in the neighboring chunks, too
+
+ // Trace the tick's worth of movement as a line:
+ cProjectileTracerCallback TracerCallback(this);
+ if (!cLineBlockTracer::Trace(*m_World, TracerCallback, Pos, NextPos))
+ {
+ // Something has been hit, abort all other processing
+ return;
+ }
+ // The tracer also checks the blocks for slowdown blocks - water and lava - and stores it for later in its SlowdownCoeff
// Update the position:
SetPosition(NextPos);
diff --git a/src/Entities/ProjectileEntity.h b/src/Entities/ProjectileEntity.h
index ae06b072f..7b38169e2 100644
--- a/src/Entities/ProjectileEntity.h
+++ b/src/Entities/ProjectileEntity.h
@@ -66,8 +66,15 @@ public:
/// Returns the kind of the projectile (fast class identification)
eKind GetProjectileKind(void) const { return m_ProjectileKind; }
- /// Returns the entity who created this projectile; may be NULL
- cEntity * GetCreator(void) { return m_Creator; }
+ /** Returns the unique ID of the entity who created this projectile
+ May return an ID <0
+ */
+ int GetCreatorUniqueID(void) { return m_CreatorData.m_UniqueID; }
+
+ /** Returns the name of the player that created the projectile
+ Will be empty for non-player creators
+ */
+ AString GetCreatorName(void) const { return m_CreatorData.m_Name; }
/// Returns the string that is used as the entity type (class name) in MCA files
AString GetMCAClassName(void) const;
@@ -81,10 +88,29 @@ public:
void SetIsInGround(bool a_IsInGround) { m_IsInGround = a_IsInGround; }
protected:
+
+ /** A structure that stores the Entity ID and Playername of the projectile's creator
+ Used to migitate invalid pointers caused by the creator being destroyed
+ */
+ struct CreatorData
+ {
+ CreatorData(int a_UniqueID, const AString & a_Name) :
+ m_UniqueID(a_UniqueID),
+ m_Name(a_Name)
+ {
+ }
+
+ const int m_UniqueID;
+ AString m_Name;
+ };
+
+ /** The type of projectile I am */
eKind m_ProjectileKind;
- /// The entity who has created this projectile; may be NULL (e. g. for dispensers)
- cEntity * m_Creator;
+ /** The structure for containing the entity ID and name who has created this projectile
+ The ID and/or name may be NULL (e.g. for dispensers/mobs)
+ */
+ CreatorData m_CreatorData;
/// True if the projectile has hit the ground and is stuck there
bool m_IsInGround;
diff --git a/src/Entities/ThrownEggEntity.cpp b/src/Entities/ThrownEggEntity.cpp
index 224019091..456083108 100644
--- a/src/Entities/ThrownEggEntity.cpp
+++ b/src/Entities/ThrownEggEntity.cpp
@@ -8,7 +8,8 @@
cThrownEggEntity::cThrownEggEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) :
- super(pkEgg, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25)
+ super(pkEgg, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25),
+ m_DestroyTimer(-1)
{
SetSpeed(a_Speed);
}
@@ -21,7 +22,7 @@ void cThrownEggEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_H
{
TrySpawnChicken(a_HitPos);
- Destroy();
+ m_DestroyTimer = 2;
}
@@ -36,7 +37,7 @@ void cThrownEggEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_Hit
TrySpawnChicken(a_HitPos);
a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1);
- Destroy(true);
+ m_DestroyTimer = 5;
}
diff --git a/src/Entities/ThrownEggEntity.h b/src/Entities/ThrownEggEntity.h
index 5ba8f051b..dc72c279f 100644
--- a/src/Entities/ThrownEggEntity.h
+++ b/src/Entities/ThrownEggEntity.h
@@ -30,8 +30,29 @@ protected:
// cProjectileEntity overrides:
virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override;
virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override;
+ virtual void Tick (float a_Dt, cChunk & a_Chunk) override
+ {
+ if (m_DestroyTimer > 0)
+ {
+ m_DestroyTimer--;
+ if (m_DestroyTimer == 0)
+ {
+ Destroy();
+ return;
+ }
+ }
+ else
+ {
+ super::Tick(a_Dt, a_Chunk);
+ }
+ }
// Randomly decides whether to spawn a chicken where the egg lands.
void TrySpawnChicken(const Vector3d & a_HitPos);
+
+private:
+
+ /** Time in ticks to wait for the hit animation to begin before destroying */
+ int m_DestroyTimer;
} ; // tolua_export
diff --git a/src/Entities/ThrownEnderPearlEntity.cpp b/src/Entities/ThrownEnderPearlEntity.cpp
index c37161145..c7407e6ae 100644
--- a/src/Entities/ThrownEnderPearlEntity.cpp
+++ b/src/Entities/ThrownEnderPearlEntity.cpp
@@ -1,13 +1,15 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "ThrownEnderPearlEntity.h"
+#include "Player.h"
cThrownEnderPearlEntity::cThrownEnderPearlEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) :
- super(pkEnderPearl, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25)
+ super(pkEnderPearl, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25),
+ m_DestroyTimer(-1)
{
SetSpeed(a_Speed);
}
@@ -21,7 +23,7 @@ void cThrownEnderPearlEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockF
// TODO: Tweak a_HitPos based on block face.
TeleportCreator(a_HitPos);
- Destroy();
+ m_DestroyTimer = 2;
}
@@ -36,7 +38,7 @@ void cThrownEnderPearlEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d
TeleportCreator(a_HitPos);
a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1);
- Destroy(true);
+ m_DestroyTimer = 5;
}
@@ -45,10 +47,34 @@ void cThrownEnderPearlEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d
void cThrownEnderPearlEntity::TeleportCreator(const Vector3d & a_HitPos)
{
- // Teleport the creator here, make them take 5 damage:
- if (m_Creator != NULL)
+ if (m_CreatorData.m_Name.empty())
{
- m_Creator->TeleportToCoords(a_HitPos.x + 0.5, a_HitPos.y + 1.7, a_HitPos.z + 0.5);
- m_Creator->TakeDamage(dtEnderPearl, this, 5, 0);
+ return;
}
+
+ class cProjectileCreatorCallbackForPlayers : public cPlayerListCallback
+ {
+ public:
+ cProjectileCreatorCallbackForPlayers(cEntity * a_Attacker, Vector3i a_HitPos) :
+ m_Attacker(a_Attacker),
+ m_HitPos(a_HitPos)
+ {
+ }
+
+ virtual bool Item(cPlayer * a_Entity) override
+ {
+ // Teleport the creator here, make them take 5 damage:
+ a_Entity->TeleportToCoords(m_HitPos.x, m_HitPos.y + 0.2, m_HitPos.z);
+ a_Entity->TakeDamage(dtEnderPearl, m_Attacker, 5, 0);
+ return true;
+ }
+
+ private:
+
+ cEntity * m_Attacker;
+ Vector3i m_HitPos;
+ };
+
+ cProjectileCreatorCallbackForPlayers PCCFP(this, a_HitPos);
+ GetWorld()->FindAndDoWithPlayer(m_CreatorData.m_Name, PCCFP);
}
diff --git a/src/Entities/ThrownEnderPearlEntity.h b/src/Entities/ThrownEnderPearlEntity.h
index ddee5babe..1cea5f7d9 100644
--- a/src/Entities/ThrownEnderPearlEntity.h
+++ b/src/Entities/ThrownEnderPearlEntity.h
@@ -30,8 +30,29 @@ protected:
// cProjectileEntity overrides:
virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override;
virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override;
-
- // Teleports the creator where the ender pearl lands.
+ virtual void Tick (float a_Dt, cChunk & a_Chunk) override
+ {
+ if (m_DestroyTimer > 0)
+ {
+ m_DestroyTimer--;
+ if (m_DestroyTimer == 0)
+ {
+ Destroy();
+ return;
+ }
+ }
+ else
+ {
+ super::Tick(a_Dt, a_Chunk);
+ }
+ }
+
+ /** Teleports the creator where the ender pearl lands */
void TeleportCreator(const Vector3d & a_HitPos);
+
+private:
+
+ /** Time in ticks to wait for the hit animation to begin before destroying */
+ int m_DestroyTimer;
} ; // tolua_export
diff --git a/src/Entities/ThrownSnowballEntity.cpp b/src/Entities/ThrownSnowballEntity.cpp
index cefc3433c..d94e75898 100644
--- a/src/Entities/ThrownSnowballEntity.cpp
+++ b/src/Entities/ThrownSnowballEntity.cpp
@@ -8,7 +8,8 @@
cThrownSnowballEntity::cThrownSnowballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) :
- super(pkSnowball, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25)
+ super(pkSnowball, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25),
+ m_DestroyTimer(-1)
{
SetSpeed(a_Speed);
}
@@ -19,7 +20,7 @@ cThrownSnowballEntity::cThrownSnowballEntity(cEntity * a_Creator, double a_X, do
void cThrownSnowballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace)
{
- Destroy();
+ m_DestroyTimer = 2;
}
@@ -40,5 +41,5 @@ void cThrownSnowballEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d &
// TODO: If entity is Ender Crystal, destroy it
a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1);
- Destroy(true);
+ m_DestroyTimer = 5;
}
diff --git a/src/Entities/ThrownSnowballEntity.h b/src/Entities/ThrownSnowballEntity.h
index a09512e37..9a8770379 100644
--- a/src/Entities/ThrownSnowballEntity.h
+++ b/src/Entities/ThrownSnowballEntity.h
@@ -30,5 +30,26 @@ protected:
// cProjectileEntity overrides:
virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override;
virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override;
+ virtual void Tick (float a_Dt, cChunk & a_Chunk) override
+ {
+ if (m_DestroyTimer > 0)
+ {
+ m_DestroyTimer--;
+ if (m_DestroyTimer == 0)
+ {
+ Destroy();
+ return;
+ }
+ }
+ else
+ {
+ super::Tick(a_Dt, a_Chunk);
+ }
+ }
+
+private:
+
+ /** Time in ticks to wait for the hit animation to begin before destroying */
+ int m_DestroyTimer;
} ; // tolua_export