diff options
Diffstat (limited to 'src/Entities')
-rw-r--r-- | src/Entities/ArrowEntity.cpp | 50 | ||||
-rw-r--r-- | src/Entities/ArrowEntity.h | 6 | ||||
-rw-r--r-- | src/Entities/Entity.cpp | 10 | ||||
-rw-r--r-- | src/Entities/ExpOrb.cpp | 2 | ||||
-rw-r--r-- | src/Entities/ExpOrb.h | 2 | ||||
-rw-r--r-- | src/Entities/Floater.cpp | 2 | ||||
-rw-r--r-- | src/Entities/Pickup.cpp | 37 | ||||
-rw-r--r-- | src/Entities/Player.cpp | 313 | ||||
-rw-r--r-- | src/Entities/Player.h | 47 | ||||
-rw-r--r-- | src/Entities/ProjectileEntity.cpp | 33 | ||||
-rw-r--r-- | src/Entities/ProjectileEntity.h | 36 | ||||
-rw-r--r-- | src/Entities/ThrownEggEntity.cpp | 2 | ||||
-rw-r--r-- | src/Entities/ThrownEggEntity.h | 5 | ||||
-rw-r--r-- | src/Entities/ThrownEnderPearlEntity.cpp | 35 | ||||
-rw-r--r-- | src/Entities/ThrownEnderPearlEntity.h | 5 | ||||
-rw-r--r-- | src/Entities/ThrownSnowballEntity.cpp | 2 | ||||
-rw-r--r-- | src/Entities/ThrownSnowballEntity.h | 5 |
17 files changed, 411 insertions, 181 deletions
diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp index db9dc781a..d5e41bd46 100644 --- a/src/Entities/ArrowEntity.cpp +++ b/src/Entities/ArrowEntity.cpp @@ -3,6 +3,7 @@ #include "Player.h" #include "ArrowEntity.h" #include "../Chunk.h" +#include "FastRandom.h" @@ -24,9 +25,9 @@ cArrowEntity::cArrowEntity(cEntity * a_Creator, double a_X, double a_Y, double a SetYawFromSpeed(); SetPitchFromSpeed(); LOGD("Created arrow %d with speed {%.02f, %.02f, %.02f} and rot {%.02f, %.02f}", - m_UniqueID, GetSpeedX(), GetSpeedY(), GetSpeedZ(), - GetYaw(), GetPitch() - ); + m_UniqueID, GetSpeedX(), GetSpeedY(), GetSpeedZ(), + GetYaw(), GetPitch() + ); } @@ -44,6 +45,10 @@ cArrowEntity::cArrowEntity(cPlayer & a_Player, double a_Force) : m_bIsCollected(false), m_HitBlockPos(0, 0, 0) { + if (a_Player.IsGameModeCreative()) + { + m_PickupState = psInCreative; + } } @@ -67,17 +72,24 @@ bool cArrowEntity::CanPickup(const cPlayer & a_Player) const void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) -{ +{ + if (GetSpeed().EqualsEps(Vector3d(0, 0, 0), 0.0000001)) + { + SetSpeed(GetLookVector().NormalizeCopy() * 0.1); // Ensure that no division by zero happens later + } + Vector3d Hit = a_HitPos; - Hit += GetSpeed() / 700; // Make arrow sink into block a little + 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); - int X = (int)floor(Hit.x), Y = (int)floor(Hit.y), Z = (int)floor(Hit.z); - + 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", X * 8, Y * 8, Z * 8, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_World->BroadcastSoundEffect("random.bowhit", (double)X, (double)Y, (double)Z, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); } @@ -94,7 +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)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + GetWorld()->BroadcastSoundEffect("random.successful_hit", GetPosX(), GetPosY(), GetPosZ(), 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); Destroy(); } @@ -105,16 +117,22 @@ void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) void cArrowEntity::CollectedBy(cPlayer * a_Dest) { - if ((m_IsInGround) && (!m_bIsCollected) && (CanPickup(*a_Dest))) + if (m_IsInGround && !m_bIsCollected && CanPickup(*a_Dest)) { - int NumAdded = a_Dest->GetInventory().AddItem(E_ITEM_ARROW); - if (NumAdded > 0) // Only play effects if there was space in inventory + // Do not add the arrow to the inventory when the player is in creative: + if (!a_Dest->IsGameModeCreative()) { - m_World->BroadcastCollectPickup((const cPickup &)*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)); - m_bIsCollected = true; + int NumAdded = a_Dest->GetInventory().AddItem(E_ITEM_ARROW); + if (NumAdded == 0) + { + // No space in the inventory + return; + } } + + GetWorld()->BroadcastCollectEntity(*this, *a_Dest); + GetWorld()->BroadcastSoundEffect("random.pop", GetPosX(), GetPosY(), GetPosZ(), 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_bIsCollected = 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 f4e89367b..26823924f 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -255,7 +255,8 @@ void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_R void cEntity::SetYawFromSpeed(void) { - if ((abs(m_Speed.x) < std::numeric_limits<double>::epsilon()) && (abs(m_Speed.z) < std::numeric_limits<double>::epsilon())) + const double EPS = 0.0000001; + if ((abs(m_Speed.x) < EPS) && (abs(m_Speed.z) < EPS)) { // atan2() may overflow or is undefined, pick any number SetYaw(0); @@ -1089,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) @@ -1235,7 +1239,7 @@ void cEntity::BroadcastMovementUpdate(const cClientHandle * a_Exclude) if (GetWorld()->GetWorldAge() % 2 == 0) { double SpeedSqr = GetSpeed().SqrLength(); - if (SpeedSqr < std::numeric_limits<double>::epsilon()) + if (SpeedSqr == 0.0) { // Speed is zero, send this to clients once only as well as an absolute position if (!m_bHasSentNoSpeed) diff --git a/src/Entities/ExpOrb.cpp b/src/Entities/ExpOrb.cpp index 10f79aedc..e437c24ef 100644 --- a/src/Entities/ExpOrb.cpp +++ b/src/Entities/ExpOrb.cpp @@ -56,7 +56,7 @@ void cExpOrb::Tick(float a_Dt, cChunk & a_Chunk) LOGD("Player %s picked up an ExpOrb. His reward is %i", a_ClosestPlayer->GetName().c_str(), m_Reward); a_ClosestPlayer->DeltaExperience(m_Reward); - m_World->BroadcastSoundEffect("random.orb", (int)(GetPosX() * 8), (int)(GetPosY() * 8), (int)(GetPosZ() * 8), 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_World->BroadcastSoundEffect("random.orb", GetPosX(), GetPosY(), GetPosZ(), 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); Destroy(); } 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/Floater.cpp b/src/Entities/Floater.cpp index b910c3769..d49893020 100644 --- a/src/Entities/Floater.cpp +++ b/src/Entities/Floater.cpp @@ -134,7 +134,7 @@ void cFloater::Tick(float a_Dt, cChunk & a_Chunk) { if (m_CountDownTime <= 0) { - m_World->BroadcastSoundEffect("random.splash", (int) floor(GetPosX() * 8), (int) floor(GetPosY() * 8), (int) floor(GetPosZ() * 8), 1, 1); + m_World->BroadcastSoundEffect("random.splash", GetPosX(), GetPosY(), GetPosZ(), 1, 1); SetPosY(GetPosY() - 1); m_CanPickupItem = true; m_PickupCountDown = 20; diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 0fd006485..bae1485d4 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -30,7 +30,7 @@ public: virtual bool Item(cEntity * a_Entity) override { - if (!a_Entity->IsPickup() || (a_Entity->GetUniqueID() == m_Pickup->GetUniqueID()) || a_Entity->IsDestroyed()) + if (!a_Entity->IsPickup() || (a_Entity->GetUniqueID() <= m_Pickup->GetUniqueID()) || a_Entity->IsDestroyed()) { return false; } @@ -38,10 +38,31 @@ public: Vector3d EntityPos = a_Entity->GetPosition(); double Distance = (EntityPos - m_Position).Length(); - if ((Distance < 1.2) && ((cPickup *)a_Entity)->GetItem().IsEqual(m_Pickup->GetItem())) + cItem & Item = ((cPickup *)a_Entity)->GetItem(); + if ((Distance < 1.2) && Item.IsEqual(m_Pickup->GetItem())) { - m_Pickup->GetItem().AddCount(((cPickup *)a_Entity)->GetItem().m_ItemCount); - a_Entity->Destroy(); + short CombineCount = Item.m_ItemCount; + if ((CombineCount + m_Pickup->GetItem().m_ItemCount) > Item.GetMaxStackSize()) + { + CombineCount = Item.GetMaxStackSize() - m_Pickup->GetItem().m_ItemCount; + } + + if (CombineCount <= 0) + { + return false; + } + + m_Pickup->GetItem().AddCount((char)CombineCount); + Item.m_ItemCount -= CombineCount; + + if (Item.m_ItemCount <= 0) + { + a_Entity->Destroy(); + } + else + { + a_Entity->GetWorld()->BroadcastEntityMetadata(*a_Entity); + } m_FoundMatchingPickup = true; } return false; @@ -129,7 +150,7 @@ void cPickup::Tick(float a_Dt, cChunk & a_Chunk) } } - if (!IsDestroyed()) // Don't try to combine if someone has tried to combine me + if (!IsDestroyed() && (m_Item.m_ItemCount < m_Item.GetMaxStackSize())) // Don't combine into an already full pickup { cPickupCombiningCallback PickupCombiningCallback(GetPosition(), this); m_World->ForEachEntity(PickupCombiningCallback); // Not ForEachEntityInChunk, otherwise pickups don't combine across chunk boundaries @@ -203,10 +224,10 @@ 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) + m_World->BroadcastSoundEffect("random.pop", GetPosX(), GetPosY(), GetPosZ(), 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + if (m_Item.m_ItemCount <= 0) { // All of the pickup has been collected, schedule the pickup for destroying m_bCollected = true; diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index e1e03fded..b1b7fc74e 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -1,4 +1,4 @@ - + #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Player.h" @@ -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(); @@ -412,6 +411,7 @@ void cPlayer::StartChargingBow(void) LOGD("Player \"%s\" started charging their bow", GetName().c_str()); m_IsChargingBow = true; m_BowCharge = 0; + m_World->BroadcastEntityMetadata(*this, m_ClientHandle); } @@ -424,6 +424,8 @@ int cPlayer::FinishChargingBow(void) int res = m_BowCharge; m_IsChargingBow = false; m_BowCharge = 0; + m_World->BroadcastEntityMetadata(*this, m_ClientHandle); + return res; } @@ -436,6 +438,7 @@ void cPlayer::CancelChargingBow(void) LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", GetName().c_str(), m_BowCharge); m_IsChargingBow = false; m_BowCharge = 0; + m_World->BroadcastEntityMetadata(*this, m_ClientHandle); } @@ -517,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(); } @@ -567,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; } @@ -965,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(); @@ -1222,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(); @@ -1675,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(); @@ -1739,6 +1795,7 @@ bool cPlayer::LoadFromDisk() } m_Inventory.LoadFromJson(root["inventory"]); + cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents); m_LoadedWorldName = root.get("world", "world").asString(); @@ -1747,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; @@ -1761,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; @@ -1776,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; } @@ -1823,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; } @@ -1863,7 +1937,7 @@ void cPlayer::UseEquippedItem(void) if (GetInventory().DamageEquippedItem()) { - m_World->BroadcastSoundEffect("random.break", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_World->BroadcastSoundEffect("random.break", GetPosX(), GetPosY(), GetPosZ(), 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); } } @@ -1891,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)) { @@ -1909,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)) { @@ -1922,7 +1993,7 @@ void cPlayer::HandleFood(void) } } } - + // Apply food poisoning food exhaustion: if (m_FoodPoisonedTicksRemaining > 0) { @@ -1935,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(); - } } @@ -2075,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) @@ -2139,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 b2142a18b..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; } @@ -404,7 +416,7 @@ public: // cEntity overrides: virtual bool IsCrouched (void) const { return m_IsCrouched; } virtual bool IsSprinting(void) const { return m_IsSprinting; } - virtual bool IsRclking (void) const { return IsEating(); } + virtual bool IsRclking (void) const { return IsEating() || IsChargingBow(); } virtual void Detach(void); @@ -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 d45f1d212..b5ef5c90a 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" @@ -68,15 +69,15 @@ 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); // 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 + 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 = LineStart + m_Projectile->GetSpeed() * LineCoeff; // Point where projectile goes into the hit block + 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)) { @@ -141,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 @@ -215,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) { } @@ -227,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); @@ -239,7 +243,7 @@ cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, const Ve -cProjectileEntity * cProjectileEntity::Create(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, const cItem & a_Item, const Vector3d * a_Speed) +cProjectileEntity * cProjectileEntity::Create(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, const cItem * a_Item, const Vector3d * a_Speed) { Vector3d Speed; if (a_Speed != NULL) @@ -258,12 +262,13 @@ cProjectileEntity * cProjectileEntity::Create(eKind a_Kind, cEntity * a_Creator, case pkExpBottle: return new cExpBottleEntity (a_Creator, a_X, a_Y, a_Z, Speed); case pkFirework: { - if (a_Item.m_FireworkItem.m_Colours.empty()) + ASSERT(a_Item != NULL); + if (a_Item->m_FireworkItem.m_Colours.empty()) { return NULL; } - return new cFireworkEntity(a_Creator, a_X, a_Y, a_Z, a_Item); + return new cFireworkEntity(a_Creator, a_X, a_Y, a_Z, *a_Item); } } @@ -304,7 +309,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"; @@ -361,7 +366,7 @@ void cProjectileEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) EntityCollisionCallback.GetHitEntity()->GetClass(), HitPos.x, HitPos.y, HitPos.z, EntityCollisionCallback.GetMinCoeff() - ); + ); OnHitEntity(*(EntityCollisionCallback.GetHitEntity()), HitPos); } diff --git a/src/Entities/ProjectileEntity.h b/src/Entities/ProjectileEntity.h index ae06b072f..14cee1272 100644 --- a/src/Entities/ProjectileEntity.h +++ b/src/Entities/ProjectileEntity.h @@ -46,7 +46,7 @@ public: cProjectileEntity(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, double a_Width, double a_Height); cProjectileEntity(eKind a_Kind, cEntity * a_Creator, const Vector3d & a_Pos, const Vector3d & a_Speed, double a_Width, double a_Height); - static cProjectileEntity * Create(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, const cItem & a_Item, const Vector3d * a_Speed = NULL); + static cProjectileEntity * Create(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, const cItem * a_Item, const Vector3d * a_Speed = NULL); /// Called by the physics blocktracer when the entity hits a solid block, the hit position and the face hit (BLOCK_FACE_) is given virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace); @@ -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 d7eed41e3..456083108 100644 --- a/src/Entities/ThrownEggEntity.cpp +++ b/src/Entities/ThrownEggEntity.cpp @@ -22,7 +22,7 @@ void cThrownEggEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_H { TrySpawnChicken(a_HitPos); - m_DestroyTimer = 5; + m_DestroyTimer = 2; } diff --git a/src/Entities/ThrownEggEntity.h b/src/Entities/ThrownEggEntity.h index 894665428..dc72c279f 100644 --- a/src/Entities/ThrownEggEntity.h +++ b/src/Entities/ThrownEggEntity.h @@ -41,7 +41,10 @@ protected: return; } } - else { super::Tick(a_Dt, a_Chunk); } + else + { + super::Tick(a_Dt, a_Chunk); + } } // Randomly decides whether to spawn a chicken where the egg lands. diff --git a/src/Entities/ThrownEnderPearlEntity.cpp b/src/Entities/ThrownEnderPearlEntity.cpp index a3ee23389..c7407e6ae 100644 --- a/src/Entities/ThrownEnderPearlEntity.cpp +++ b/src/Entities/ThrownEnderPearlEntity.cpp @@ -1,6 +1,7 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "ThrownEnderPearlEntity.h" +#include "Player.h" @@ -22,7 +23,7 @@ void cThrownEnderPearlEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockF // TODO: Tweak a_HitPos based on block face. TeleportCreator(a_HitPos); - m_DestroyTimer = 5; + m_DestroyTimer = 2; } @@ -46,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, a_HitPos.y + 0.2, a_HitPos.z); - 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 bfd9bd70d..1cea5f7d9 100644 --- a/src/Entities/ThrownEnderPearlEntity.h +++ b/src/Entities/ThrownEnderPearlEntity.h @@ -41,7 +41,10 @@ protected: return; } } - else { super::Tick(a_Dt, a_Chunk); } + else + { + super::Tick(a_Dt, a_Chunk); + } } /** Teleports the creator where the ender pearl lands */ diff --git a/src/Entities/ThrownSnowballEntity.cpp b/src/Entities/ThrownSnowballEntity.cpp index b82cd56db..d94e75898 100644 --- a/src/Entities/ThrownSnowballEntity.cpp +++ b/src/Entities/ThrownSnowballEntity.cpp @@ -20,7 +20,7 @@ cThrownSnowballEntity::cThrownSnowballEntity(cEntity * a_Creator, double a_X, do void cThrownSnowballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) { - m_DestroyTimer = 5; + m_DestroyTimer = 2; } diff --git a/src/Entities/ThrownSnowballEntity.h b/src/Entities/ThrownSnowballEntity.h index e30971f0a..9a8770379 100644 --- a/src/Entities/ThrownSnowballEntity.h +++ b/src/Entities/ThrownSnowballEntity.h @@ -41,7 +41,10 @@ protected: return; } } - else { super::Tick(a_Dt, a_Chunk); } + else + { + super::Tick(a_Dt, a_Chunk); + } } private: |