diff options
author | Tiger Wang <ziwei.tiger@hotmail.co.uk> | 2014-07-18 21:10:51 +0200 |
---|---|---|
committer | Tiger Wang <ziwei.tiger@hotmail.co.uk> | 2014-07-18 21:10:51 +0200 |
commit | 37140ae57835680d2c4fb3aa365082622d3a150e (patch) | |
tree | 89a636c74375b3c9f0fe77b20ecbadc693bd0989 /src/Entities | |
parent | Merge branch 'master' of https://github.com/mc-server/MCServer into portals (diff) | |
parent | Monster fixes (diff) | |
download | cuberite-37140ae57835680d2c4fb3aa365082622d3a150e.tar cuberite-37140ae57835680d2c4fb3aa365082622d3a150e.tar.gz cuberite-37140ae57835680d2c4fb3aa365082622d3a150e.tar.bz2 cuberite-37140ae57835680d2c4fb3aa365082622d3a150e.tar.lz cuberite-37140ae57835680d2c4fb3aa365082622d3a150e.tar.xz cuberite-37140ae57835680d2c4fb3aa365082622d3a150e.tar.zst cuberite-37140ae57835680d2c4fb3aa365082622d3a150e.zip |
Diffstat (limited to 'src/Entities')
43 files changed, 1917 insertions, 506 deletions
diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp index 8d2569125..913519c4c 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; + } } @@ -68,25 +73,23 @@ 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() / 1000); + Hit += SinkMovement * (0.0005 / 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", (double)X, (double)Y, (double)Z, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); } @@ -95,12 +98,6 @@ 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) { @@ -109,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(); } @@ -120,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; } } @@ -163,9 +166,9 @@ void cArrowEntity::Tick(float a_Dt, cChunk & a_Chunk) // We can afford to do this because xoft's algorithm for trajectory is near perfect, so things are pretty close anyway without sync // Besides, this seems to be what the vanilla server does, note how arrows teleport half a second after they hit to the server position - if (!m_HasTeleported) // Sent a teleport already, don't do again + 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; @@ -186,9 +189,9 @@ void cArrowEntity::Tick(float a_Dt, cChunk & a_Chunk) return; } - if (Chunk->GetBlock(RelPosX, m_HitBlockPos.y, RelPosZ) == E_BLOCK_AIR) // Block attached to was destroyed? + if (Chunk->GetBlock(RelPosX, m_HitBlockPos.y, RelPosZ) == E_BLOCK_AIR) // Block attached to was destroyed? { - m_IsInGround = false; // Yes, begin simulating physics again + m_IsInGround = false; // Yes, begin simulating physics again } } } diff --git a/src/Entities/ArrowEntity.h b/src/Entities/ArrowEntity.h index 1fe3032ee..ad93dba6c 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: @@ -93,4 +99,4 @@ protected: virtual void CollectedBy(cPlayer * a_Player) override; virtual void Tick(float a_Dt, cChunk & a_Chunk) override; -}; // tolua_export +}; // tolua_export diff --git a/src/Entities/Boat.cpp b/src/Entities/Boat.cpp index 31bfe3dc3..8ff8866a1 100644 --- a/src/Entities/Boat.cpp +++ b/src/Entities/Boat.cpp @@ -94,7 +94,7 @@ void cBoat::Tick(float a_Dt, cChunk & a_Chunk) super::Tick(a_Dt, a_Chunk); BroadcastMovementUpdate(); - SetSpeed(GetSpeed() * 0.97); // Slowly decrease the speed + SetSpeed(GetSpeed() * 0.97); // Slowly decrease the speed if ((POSY_TOINT < 0) || (POSY_TOINT > cChunkDef::Height)) { @@ -104,7 +104,7 @@ void cBoat::Tick(float a_Dt, cChunk & a_Chunk) if (IsBlockWater(m_World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT))) { if (GetSpeedY() < 2) - { + { AddSpeedY(0.2); } } diff --git a/src/Entities/Effects.h b/src/Entities/Effects.h deleted file mode 100644 index baf3302fb..000000000 --- a/src/Entities/Effects.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -// tolua_begin -enum ENUM_ENTITY_EFFECT -{ - E_EFFECT_SPEED = 1, - E_EFFECT_SLOWNESS = 2, - E_EFFECT_HASTE = 3, - E_EFFECT_MINING_FATIGUE = 4, - E_EFFECT_STENGTH = 5, - E_EFFECT_INSTANT_HEALTH = 6, - E_EFFECT_INSTANT_DAMAGE = 7, - E_EFFECT_JUMP_BOOST = 8, - E_EFFECT_NAUSEA = 9, - E_EFFECT_REGENERATION = 10, - E_EFFECT_RESISTANCE = 11, - E_EFFECT_FIRE_RESISTANCE = 12, - E_EFFECT_WATER_BREATHING = 13, - E_EFFECT_INVISIBILITY = 14, - E_EFFECT_BLINDNESS = 15, - E_EFFECT_NIGHT_VISION = 16, - E_EFFECT_HUNGER = 17, - E_EFFECT_WEAKNESS = 18, - E_EFFECT_POISON = 19, - E_EFFECT_WITHER = 20, - E_EFFECT_HEALTH_BOOST = 21, - E_EFFECT_ABSORPTION = 22, - E_EFFECT_SATURATION = 23, -} ; -// tolua_end diff --git a/src/Entities/EnderCrystal.cpp b/src/Entities/EnderCrystal.cpp index a640b236c..bf86a6c42 100644 --- a/src/Entities/EnderCrystal.cpp +++ b/src/Entities/EnderCrystal.cpp @@ -42,9 +42,9 @@ void cEnderCrystal::Tick(float a_Dt, cChunk & a_Chunk) -void cEnderCrystal::KilledBy(cEntity * a_Killer) +void cEnderCrystal::KilledBy(TakeDamageInfo & a_TDI) { - super::KilledBy(a_Killer); + super::KilledBy(a_TDI); m_World->DoExplosionAt(6.0, GetPosX(), GetPosY(), GetPosZ(), true, esEnderCrystal, this); diff --git a/src/Entities/EnderCrystal.h b/src/Entities/EnderCrystal.h index 5b86df987..30211de13 100644 --- a/src/Entities/EnderCrystal.h +++ b/src/Entities/EnderCrystal.h @@ -24,7 +24,7 @@ private: // cEntity overrides: virtual void SpawnOn(cClientHandle & a_ClientHandle) override; virtual void Tick(float a_Dt, cChunk & a_Chunk) override; - virtual void KilledBy(cEntity * a_Killer) override; + virtual void KilledBy(TakeDamageInfo & a_TDI) override; }; // tolua_export diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index d4edc9144..e5e4cf4cb 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -75,7 +75,7 @@ cEntity::~cEntity() /* // DEBUG: - LOGD("Deleting entity %d at pos {%.2f, %.2f, %.2f} ~ [%d, %d]; ptr %p", + LOGD("Deleting entity %d at pos {%.2f, %.2f, %.2f} ~ [%d, %d]; ptr %p", m_UniqueID, m_Pos.x, m_Pos.y, m_Pos.z, (int)(m_Pos.x / cChunkDef::Width), (int)(m_Pos.z / cChunkDef::Width), @@ -273,7 +273,7 @@ void cEntity::SetYawFromSpeed(void) void cEntity::SetPitchFromSpeed(void) { const double EPS = 0.0000001; - double xz = sqrt(m_Speed.x * m_Speed.x + m_Speed.z * m_Speed.z); // Speed XZ-plane component + double xz = sqrt(m_Speed.x * m_Speed.x + m_Speed.z * m_Speed.z); // Speed XZ-plane component if ((abs(xz) < EPS) && (abs(m_Speed.y) < EPS)) { // atan2() may overflow or is undefined, pick any number @@ -311,10 +311,14 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) cPlayer * Player = (cPlayer *)a_TDI.Attacker; // IsOnGround() only is false if the player is moving downwards - if (!Player->IsOnGround()) // TODO: Better damage increase, and check for enchantments (and use magic critical instead of plain) + // TODO: Better damage increase, and check for enchantments (and use magic critical instead of plain) + if (!Player->IsOnGround()) { - a_TDI.FinalDamage += 2; - m_World->BroadcastEntityAnimation(*this, 4); // Critical hit + if ((a_TDI.DamageType == dtAttack) || (a_TDI.DamageType == dtArrowAttack)) + { + a_TDI.FinalDamage += 2; + m_World->BroadcastEntityAnimation(*this, 4); // Critical hit + } } Player->GetStatManager().AddValue(statDamageDealt, (StatValue)floor(a_TDI.FinalDamage * 10 + 0.5)); @@ -329,38 +333,23 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) m_Health = 0; } - if ((IsMob() || IsPlayer()) && (a_TDI.Attacker != NULL)) // Knockback for only players and mobs + if ((IsMob() || IsPlayer()) && (a_TDI.Attacker != NULL)) // Knockback for only players and mobs { - int KnockbackLevel = 0; - if (a_TDI.Attacker->GetEquippedWeapon().m_ItemType == E_ITEM_BOW) + int KnockbackLevel = a_TDI.Attacker->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchKnockback); // More common enchantment + if (KnockbackLevel < 1) { + // We support punch on swords and vice versa! :) KnockbackLevel = a_TDI.Attacker->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchPunch); } - else - { - KnockbackLevel = a_TDI.Attacker->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchKnockback); - } - Vector3d additionalSpeed(0, 0, 0); + Vector3d AdditionalSpeed(0, 0, 0); switch (KnockbackLevel) { - case 1: - { - additionalSpeed.Set(5, .3, 5); - break; - } - case 2: - { - additionalSpeed.Set(8, .3, 8); - break; - } - default: - { - additionalSpeed.Set(2, .3, 2); - break; - } + case 1: AdditionalSpeed.Set(5, 0.3, 5); break; + case 2: AdditionalSpeed.Set(8, 0.3, 8); break; + default: break; } - AddSpeed(a_TDI.Knockback * additionalSpeed); + AddSpeed(a_TDI.Knockback + AdditionalSpeed); } m_World->BroadcastEntityStatus(*this, esGenericHurt); @@ -369,7 +358,7 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) if (m_Health <= 0) { - KilledBy(a_TDI.Attacker); + KilledBy(a_TDI); if (a_TDI.Attacker != NULL) { @@ -432,6 +421,7 @@ bool cEntity::ArmorCoversAgainst(eDamageType a_DamageType) case dtStarving: case dtInVoid: case dtPoisoning: + case dtWithering: case dtPotionOfHarming: case dtFalling: case dtLightning: @@ -525,11 +515,11 @@ double cEntity::GetKnockbackAmountAgainst(const cEntity & a_Receiver) -void cEntity::KilledBy(cEntity * a_Killer) +void cEntity::KilledBy(TakeDamageInfo & a_TDI) { m_Health = 0; - cRoot::Get()->GetPluginManager()->CallHookKilling(*this, a_Killer); + cRoot::Get()->GetPluginManager()->CallHookKilling(*this, a_TDI.Attacker, a_TDI); if (m_Health > 0) { @@ -539,7 +529,7 @@ void cEntity::KilledBy(cEntity * a_Killer) // Drop loot: cItems Drops; - GetDrops(Drops, a_Killer); + GetDrops(Drops, a_TDI.Attacker); m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ()); m_World->BroadcastEntityStatus(*this, esGenericDead); @@ -652,7 +642,7 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) if ((BlockY >= cChunkDef::Height) || (BlockY < 0)) { - // Outside of the world + // Outside of the world AddSpeedY(m_Gravity * a_Dt); AddPosition(GetSpeed() * a_Dt); return; @@ -764,11 +754,11 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) NextSpeed.x *= 0.25; NextSpeed.z *= 0.25; } - - //Get water direction + + // Get water direction Direction WaterDir = m_World->GetWaterSimulator()->GetFlowingDirection(BlockX, BlockY, BlockZ); - m_WaterSpeed *= 0.9f; //Reduce speed each tick + m_WaterSpeed *= 0.9f; // Reduce speed each tick switch(WaterDir) { @@ -825,7 +815,7 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) if (Tracer.HitNormal.y != 0.f) NextSpeed.y = 0.f; if (Tracer.HitNormal.z != 0.f) NextSpeed.z = 0.f; - if (Tracer.HitNormal.y == 1) // Hit BLOCK_FACE_YP, we are on the ground + if (Tracer.HitNormal.y == 1) // Hit BLOCK_FACE_YP, we are on the ground { m_bOnGround = true; } @@ -870,7 +860,7 @@ void cEntity::TickBurning(cChunk & a_Chunk) if (POSY_TOINT > m_World->GetHeight(POSX_TOINT, POSZ_TOINT)) { m_TicksLeftBurning = 0; - } + } } // Do the burning damage: @@ -1248,14 +1238,17 @@ 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) { if (m_AirTickTimer < 1) { - // Damage player + // Damage player TakeDamage(dtDrowning, NULL, 1, 1, 0); // Reset timer m_AirTickTimer = DROWNING_TICKS; @@ -1416,9 +1409,9 @@ void cEntity::BroadcastMovementUpdate(const cClientHandle * a_Exclude) int DiffY = (int)(floor(GetPosY() * 32.0) - floor(m_LastPos.y * 32.0)); int DiffZ = (int)(floor(GetPosZ() * 32.0) - floor(m_LastPos.z * 32.0)); - if ((DiffX != 0) || (DiffY != 0) || (DiffZ != 0)) // Have we moved? + if ((DiffX != 0) || (DiffY != 0) || (DiffZ != 0)) // Have we moved? { - if ((abs(DiffX) <= 127) && (abs(DiffY) <= 127) && (abs(DiffZ) <= 127)) // Limitations of a Byte + if ((abs(DiffX) <= 127) && (abs(DiffY) <= 127) && (abs(DiffZ) <= 127)) // Limitations of a Byte { // Difference within Byte limitations, use a relative move packet if (m_bDirtyOrientation) @@ -1438,7 +1431,7 @@ void cEntity::BroadcastMovementUpdate(const cClientHandle * a_Exclude) { // Too big a movement, do a teleport m_World->BroadcastTeleportEntity(*this, a_Exclude); - m_LastPos = GetPosition(); // See above + m_LastPos = GetPosition(); // See above m_bDirtyOrientation = false; } } @@ -1547,8 +1540,8 @@ void cEntity::SetMass(double a_Mass) } else { - // Make sure that mass is not zero. 1g is the default because we - // have to choose a number. It's perfectly legal to have a mass + // Make sure that mass is not zero. 1g is the default because we + // have to choose a number. It's perfectly legal to have a mass // less than 1g as long as is NOT equal or less than zero. m_Mass = 0.001; } @@ -1635,7 +1628,6 @@ void cEntity::SetWidth(double a_Width) void cEntity::AddPosX(double a_AddPosX) { m_Pos.x += a_AddPosX; - } @@ -1644,7 +1636,6 @@ void cEntity::AddPosX(double a_AddPosX) void cEntity::AddPosY(double a_AddPosY) { m_Pos.y += a_AddPosY; - } @@ -1653,7 +1644,6 @@ void cEntity::AddPosY(double a_AddPosY) void cEntity::AddPosZ(double a_AddPosZ) { m_Pos.z += a_AddPosZ; - } @@ -1664,7 +1654,6 @@ 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; - } @@ -1674,7 +1663,7 @@ void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeed { m_Speed.x += a_AddSpeedX; m_Speed.y += a_AddSpeedY; - m_Speed.z += a_AddSpeedZ; + m_Speed.z += a_AddSpeedZ; WrapSpeed(); } @@ -1684,7 +1673,7 @@ void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeed void cEntity::AddSpeedX(double a_AddSpeedX) { - m_Speed.x += a_AddSpeedX; + m_Speed.x += a_AddSpeedX; WrapSpeed(); } @@ -1694,7 +1683,7 @@ void cEntity::AddSpeedX(double a_AddSpeedX) void cEntity::AddSpeedY(double a_AddSpeedY) { - m_Speed.y += a_AddSpeedY; + m_Speed.y += a_AddSpeedY; WrapSpeed(); } @@ -1704,7 +1693,7 @@ void cEntity::AddSpeedY(double a_AddSpeedY) void cEntity::AddSpeedZ(double a_AddSpeedZ) { - m_Speed.z += a_AddSpeedZ; + m_Speed.z += a_AddSpeedZ; WrapSpeed(); } @@ -1741,7 +1730,7 @@ void cEntity::SteerVehicle(float a_Forward, float a_Sideways) -////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // Get look vector (this is NOT a rotation!) Vector3d cEntity::GetLookVector(void) const { @@ -1755,11 +1744,11 @@ Vector3d cEntity::GetLookVector(void) const -////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // Set position void cEntity::SetPosition(double a_PosX, double a_PosY, double a_PosZ) { - m_Pos.Set(a_PosX, a_PosY, a_PosZ); + m_Pos.Set(a_PosX, a_PosY, a_PosZ); } @@ -1768,7 +1757,7 @@ void cEntity::SetPosition(double a_PosX, double a_PosY, double a_PosZ) void cEntity::SetPosX(double a_PosX) { - m_Pos.x = a_PosX; + m_Pos.x = a_PosX; } @@ -1777,7 +1766,7 @@ void cEntity::SetPosX(double a_PosX) void cEntity::SetPosY(double a_PosY) { - m_Pos.y = a_PosY; + m_Pos.y = a_PosY; } diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index f06666569..8b6fc05f7 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -158,6 +158,7 @@ public: bool IsPlayer (void) const { return (m_EntityType == etPlayer); } bool IsPickup (void) const { return (m_EntityType == etPickup); } bool IsMob (void) const { return (m_EntityType == etMonster); } + bool IsPawn (void) const { return (IsMob() || IsPlayer()); } bool IsFallingBlock(void) const { return (m_EntityType == etFallingBlock); } bool IsMinecart (void) const { return (m_EntityType == etMinecart); } bool IsBoat (void) const { return (m_EntityType == etBoat); } @@ -237,9 +238,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); @@ -309,13 +310,13 @@ public: virtual cItem GetEquippedBoots(void) const { return cItem(); } /// Called when the health drops below zero. a_Killer may be NULL (environmental damage) - virtual void KilledBy(cEntity * a_Killer); + virtual void KilledBy(TakeDamageInfo & a_TDI); /// Called when the entity kills another entity virtual void Killed(cEntity * a_Victim) {} /// Heals the specified amount of HPs - void Heal(int a_HitPoints); + virtual void Heal(int a_HitPoints); /// Returns the health of this entity int GetHealth(void) const { return m_Health; } @@ -439,7 +440,7 @@ public: virtual void OnRightClicked(cPlayer &) {}; /// Returns the list of drops for this pawn when it is killed. May check a_Killer for special handling (sword of looting etc.). Called from KilledBy(). - virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) + virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) { UNUSED(a_Drops); UNUSED(a_Killer); @@ -499,7 +500,7 @@ protected: /// Whether the entity is capable of taking fire or lava damage. bool m_IsFireproof; - + /// Time, in ticks, since the last damage dealt by being on fire. Valid only if on fire (IsOnFire()) int m_TicksSinceLastBurnDamage; @@ -519,7 +520,7 @@ protected: overrides can provide further processing, such as forcing players to move at the given speed. */ virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ); - virtual void Destroyed(void) {} // Called after the entity has been destroyed + virtual void Destroyed(void) {} // Called after the entity has been destroyed /** Called in each tick to handle air-related processing i.e. drowning */ virtual void HandleAir(void); diff --git a/src/Entities/EntityEffect.cpp b/src/Entities/EntityEffect.cpp new file mode 100644 index 000000000..6c4b13a58 --- /dev/null +++ b/src/Entities/EntityEffect.cpp @@ -0,0 +1,287 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "EntityEffect.h" +#include "../Mobs/Monster.h" +#include "Player.h" + + + + +cEntityEffect::cEntityEffect(): + m_Ticks(0), + m_Duration(0), + m_Intensity(0), + m_DistanceModifier(1) +{ + +} + + + + + +cEntityEffect::cEntityEffect(int a_Duration, short a_Intensity, double a_DistanceModifier): + m_Ticks(0), + m_Duration(a_Duration), + m_Intensity(a_Intensity), + m_DistanceModifier(a_DistanceModifier) +{ + +} + + + + + +cEntityEffect::cEntityEffect(const cEntityEffect & a_OtherEffect): + m_Ticks(a_OtherEffect.m_Ticks), + m_Duration(a_OtherEffect.m_Duration), + m_Intensity(a_OtherEffect.m_Intensity), + m_DistanceModifier(a_OtherEffect.m_DistanceModifier) +{ + +} + + + + + +cEntityEffect & cEntityEffect::operator=(cEntityEffect a_OtherEffect) +{ + std::swap(m_Ticks, a_OtherEffect.m_Ticks); + std::swap(m_Duration, a_OtherEffect.m_Duration); + std::swap(m_Intensity, a_OtherEffect.m_Intensity); + std::swap(m_DistanceModifier, a_OtherEffect.m_DistanceModifier); + return *this; +} + + + + + +cEntityEffect * cEntityEffect::CreateEntityEffect(cEntityEffect::eType a_EffectType, int a_Duration, short a_Intensity, double a_DistanceModifier) +{ + switch (a_EffectType) + { + case cEntityEffect::effNoEffect: return new cEntityEffect (a_Duration, a_Intensity, a_DistanceModifier); + + case cEntityEffect::effAbsorption: return new cEntityEffectAbsorption (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effBlindness: return new cEntityEffectBlindness (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effFireResistance: return new cEntityEffectFireResistance(a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effHaste: return new cEntityEffectHaste (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effHealthBoost: return new cEntityEffectHealthBoost (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effHunger: return new cEntityEffectHunger (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effInstantDamage: return new cEntityEffectInstantDamage (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effInstantHealth: return new cEntityEffectInstantHealth (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effInvisibility: return new cEntityEffectInvisibility (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effJumpBoost: return new cEntityEffectJumpBoost (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effMiningFatigue: return new cEntityEffectMiningFatigue (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effNausea: return new cEntityEffectNausea (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effNightVision: return new cEntityEffectNightVision (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effPoison: return new cEntityEffectPoison (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effRegeneration: return new cEntityEffectRegeneration (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effResistance: return new cEntityEffectResistance (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effSaturation: return new cEntityEffectSaturation (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effSlowness: return new cEntityEffectSlowness (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effSpeed: return new cEntityEffectSpeed (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effStrength: return new cEntityEffectStrength (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effWaterBreathing: return new cEntityEffectWaterBreathing(a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effWeakness: return new cEntityEffectWeakness (a_Duration, a_Intensity, a_DistanceModifier); + case cEntityEffect::effWither: return new cEntityEffectWither (a_Duration, a_Intensity, a_DistanceModifier); + } + + ASSERT(!"Unhandled entity effect type!"); + return NULL; +} + + + + + +void cEntityEffect::OnTick(cPawn & a_Target) +{ + // Reduce the effect's duration + ++m_Ticks; +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cEntityEffectInstantHealth: + +void cEntityEffectInstantHealth::OnActivate(cPawn & a_Target) +{ + // Base amount = 6, doubles for every increase in intensity + int amount = (int)(6 * (1 << m_Intensity) * m_DistanceModifier); + + if (a_Target.IsMob() && ((cMonster &) a_Target).IsUndead()) + { + a_Target.TakeDamage(dtPotionOfHarming, NULL, amount, 0); // TODO: Store attacker in a pointer-safe way, pass to TakeDamage + return; + } + a_Target.Heal(amount); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cEntityEffectInstantDamage: + +void cEntityEffectInstantDamage::OnActivate(cPawn & a_Target) +{ + // Base amount = 6, doubles for every increase in intensity + int amount = (int)(6 * (1 << m_Intensity) * m_DistanceModifier); + + if (a_Target.IsMob() && ((cMonster &) a_Target).IsUndead()) + { + a_Target.Heal(amount); + return; + } + a_Target.TakeDamage(dtPotionOfHarming, NULL, amount, 0); // TODO: Store attacker in a pointer-safe way, pass to TakeDamage +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cEntityEffectRegeneration: + +void cEntityEffectRegeneration::OnTick(cPawn & a_Target) +{ + super::OnTick(a_Target); + + if (a_Target.IsMob() && ((cMonster &) a_Target).IsUndead()) + { + return; + } + + // Regen frequency = 50 ticks, divided by potion level (Regen II = 25 ticks) + int frequency = (int) std::floor(50.0 / (double)(m_Intensity + 1)); + + if ((m_Ticks % frequency) != 0) + { + return; + } + + a_Target.Heal(1); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cEntityEffectHunger: + +void cEntityEffectHunger::OnTick(cPawn & a_Target) +{ + super::OnTick(a_Target); + + if (a_Target.IsPlayer()) + { + cPlayer & Target = (cPlayer &) a_Target; + Target.SetFoodExhaustionLevel(Target.GetFoodExhaustionLevel() + 0.025); // 0.5 per second = 0.025 per tick + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cEntityEffectWeakness: + +void cEntityEffectWeakness::OnTick(cPawn & a_Target) +{ + super::OnTick(a_Target); + + // Damage reduction = 0.5 damage, multiplied by potion level (Weakness II = 1 damage) + // double dmg_reduc = 0.5 * (a_Effect.GetIntensity() + 1); + + // TODO: Implement me! + // TODO: Weakened villager zombies can be turned back to villagers with the god apple +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cEntityEffectPoison: + +void cEntityEffectPoison::OnTick(cPawn & a_Target) +{ + super::OnTick(a_Target); + + if (a_Target.IsMob()) + { + cMonster & Target = (cMonster &) a_Target; + + // Doesn't effect undead mobs, spiders + if ( + Target.IsUndead() || + (Target.GetMobType() == cMonster::mtSpider) || + (Target.GetMobType() == cMonster::mtCaveSpider) + ) + { + return; + } + } + + // Poison frequency = 25 ticks, divided by potion level (Poison II = 12 ticks) + int frequency = (int) std::floor(25.0 / (double)(m_Intensity + 1)); + + if ((m_Ticks % frequency) == 0) + { + // Cannot take poison damage when health is at 1 + if (a_Target.GetHealth() > 1) + { + a_Target.TakeDamage(dtPoisoning, NULL, 1, 0); + } + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cEntityEffectWither: + +void cEntityEffectWither::OnTick(cPawn & a_Target) +{ + super::OnTick(a_Target); + + // Damage frequency = 40 ticks, divided by effect level (Wither II = 20 ticks) + int frequency = (int) std::floor(25.0 / (double)(m_Intensity + 1)); + + if ((m_Ticks % frequency) == 0) + { + a_Target.TakeDamage(dtWither, NULL, 1, 0); + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cEntityEffectSaturation: + +void cEntityEffectSaturation::OnTick(cPawn & a_Target) +{ + if (a_Target.IsPlayer()) + { + cPlayer & Target = (cPlayer &) a_Target; + Target.SetFoodSaturationLevel(Target.GetFoodSaturationLevel() + (1 + m_Intensity)); // Increase saturation 1 per tick, adds 1 for every increase in level + } +} + + + + diff --git a/src/Entities/EntityEffect.h b/src/Entities/EntityEffect.h new file mode 100644 index 000000000..ebd611ff0 --- /dev/null +++ b/src/Entities/EntityEffect.h @@ -0,0 +1,476 @@ +#pragma once + +class cPawn; + +// tolua_begin +class cEntityEffect +{ +public: + + /** All types of entity effects (numbers correspond to protocol / storage types) */ + enum eType + { + effNoEffect = 0, + effSpeed = 1, + effSlowness = 2, + effHaste = 3, + effMiningFatigue = 4, + effStrength = 5, + effInstantHealth = 6, + effInstantDamage = 7, + effJumpBoost = 8, + effNausea = 9, + effRegeneration = 10, + effResistance = 11, + effFireResistance = 12, + effWaterBreathing = 13, + effInvisibility = 14, + effBlindness = 15, + effNightVision = 16, + effHunger = 17, + effWeakness = 18, + effPoison = 19, + effWither = 20, + effHealthBoost = 21, + effAbsorption = 22, + effSaturation = 23, + } ; + + // tolua_end + + /** Creates an empty entity effect */ + cEntityEffect(void); + + /** Creates an entity effect of the specified type + @param a_Duration How long this effect will last, in ticks + @param a_Intensity How strong the effect will be applied + @param a_DistanceModifier The distance modifier for affecting potency, defaults to 1 */ + cEntityEffect(int a_Duration, short a_Intensity, double a_DistanceModifier = 1); + + /** Creates an entity effect by copying another + @param a_OtherEffect The other effect to copy */ + cEntityEffect(const cEntityEffect & a_OtherEffect); + + /** Creates an entity effect by copying another + @param a_OtherEffect The other effect to copy */ + cEntityEffect & operator=(cEntityEffect a_OtherEffect); + + virtual ~cEntityEffect(void) {}; + + /** Creates a pointer to the proper entity effect from the effect type + @warning This function creates raw pointers that must be manually managed. + @param a_EffectType The effect type to create the effect from + @param a_Duration How long this effect will last, in ticks + @param a_Intensity How strong the effect will be applied + @param a_DistanceModifier The distance modifier for affecting potency, defaults to 1 */ + static cEntityEffect * CreateEntityEffect(cEntityEffect::eType a_EffectType, int a_Duration, short a_Intensity, double a_DistanceModifier); + + /** Returns how many ticks this effect has been active for */ + int GetTicks(void) const { return m_Ticks; } + + /** Returns the duration of the effect */ + int GetDuration(void) const { return m_Duration; } + + /** Returns how strong the effect will be applied */ + short GetIntensity(void) const { return m_Intensity; } + + /** Returns the distance modifier for affecting potency */ + double GetDistanceModifier(void) const { return m_DistanceModifier; } + + void SetTicks(int a_Ticks) { m_Ticks = a_Ticks; } + void SetDuration(int a_Duration) { m_Duration = a_Duration; } + void SetIntensity(short a_Intensity) { m_Intensity = a_Intensity; } + void SetDistanceModifier(double a_DistanceModifier) { m_DistanceModifier = a_DistanceModifier; } + + /** Called on each tick. + By default increases the m_Ticks, descendants may override to provide additional processing. */ + virtual void OnTick(cPawn & a_Target); + + /** Called when the effect is first added to an entity */ + virtual void OnActivate(cPawn & a_Target) { } + + /** Called when the effect is removed from an entity */ + virtual void OnDeactivate(cPawn & a_Target) { } + +protected: + /** How many ticks this effect has been active for */ + int m_Ticks; + + /** How long this effect will last, in ticks */ + int m_Duration; + + /** How strong the effect will be applied */ + short m_Intensity; + + /** The distance modifier for affecting potency */ + double m_DistanceModifier; +}; // tolua_export + + + + + +class cEntityEffectSpeed: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectSpeed(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectSlowness: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectSlowness(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectHaste: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectHaste(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectMiningFatigue: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectMiningFatigue(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectStrength: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectStrength(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectInstantHealth: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectInstantHealth(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } + + virtual void OnActivate(cPawn & a_Target) override; +}; + + + + + +class cEntityEffectInstantDamage: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectInstantDamage(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } + + virtual void OnActivate(cPawn & a_Target) override; +}; + + + + + +class cEntityEffectJumpBoost: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectJumpBoost(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectNausea: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectNausea(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectRegeneration: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectRegeneration(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } + + virtual void OnTick(cPawn & a_Target) override; +}; + + + + + +class cEntityEffectResistance: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectResistance(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectFireResistance: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectFireResistance(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectWaterBreathing: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectWaterBreathing(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectInvisibility: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectInvisibility(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectBlindness: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectBlindness(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectNightVision: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectNightVision(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectHunger: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectHunger(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } + + // cEntityEffect overrides: + virtual void OnTick(cPawn & a_Target) override; +}; + + + + + +class cEntityEffectWeakness: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectWeakness(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } + + // cEntityEffect overrides: + virtual void OnTick(cPawn & a_Target) override; +}; + + + + + +class cEntityEffectPoison: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectPoison(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } + + // cEntityEffect overrides: + virtual void OnTick(cPawn & a_Target) override; +}; + + + + + +class cEntityEffectWither: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectWither(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } + + // cEntityEffect overrides: + virtual void OnTick(cPawn & a_Target) override; +}; + + + + + +class cEntityEffectHealthBoost: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectHealthBoost(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectAbsorption: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectAbsorption(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } +}; + + + + + +class cEntityEffectSaturation: + public cEntityEffect +{ + typedef cEntityEffect super; +public: + cEntityEffectSaturation(int a_Duration, short a_Intensity, double a_DistanceModifier = 1): + super(a_Duration, a_Intensity, a_DistanceModifier) + { + } + + virtual void OnTick(cPawn & a_Target) override; +}; + + + + diff --git a/src/Entities/ExpBottleEntity.h b/src/Entities/ExpBottleEntity.h index b2043d8f1..e9536452c 100644 --- a/src/Entities/ExpBottleEntity.h +++ b/src/Entities/ExpBottleEntity.h @@ -30,4 +30,4 @@ protected: // cProjectileEntity overrides: virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; -}; // tolua_export +}; // tolua_export diff --git a/src/Entities/ExpOrb.cpp b/src/Entities/ExpOrb.cpp index 10f79aedc..ed225b8e6 100644 --- a/src/Entities/ExpOrb.cpp +++ b/src/Entities/ExpOrb.cpp @@ -6,7 +6,7 @@ cExpOrb::cExpOrb(double a_X, double a_Y, double a_Z, int a_Reward) - : cEntity(etExpOrb, a_X, a_Y, a_Z, 0.98, 0.98) + : cEntity(etExpOrb, a_X, a_Y, a_Z, 0.98, 0.98) , m_Reward(a_Reward) , m_Timer(0.f) { @@ -19,7 +19,7 @@ cExpOrb::cExpOrb(double a_X, double a_Y, double a_Z, int a_Reward) cExpOrb::cExpOrb(const Vector3d & a_Pos, int a_Reward) - : cEntity(etExpOrb, a_Pos.x, a_Pos.y, a_Pos.z, 0.98, 0.98) + : cEntity(etExpOrb, a_Pos.x, a_Pos.y, a_Pos.z, 0.98, 0.98) , m_Reward(a_Reward) , m_Timer(0.f) { @@ -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/FireChargeEntity.h b/src/Entities/FireChargeEntity.h index 3924c337c..42ecc7d74 100644 --- a/src/Entities/FireChargeEntity.h +++ b/src/Entities/FireChargeEntity.h @@ -33,4 +33,4 @@ protected: virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; virtual void OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) override; -} ; // tolua_export +} ; // tolua_export diff --git a/src/Entities/FireworkEntity.h b/src/Entities/FireworkEntity.h index c62ca9402..7dbdc49e1 100644 --- a/src/Entities/FireworkEntity.h +++ b/src/Entities/FireworkEntity.h @@ -37,4 +37,4 @@ private: int m_ExplodeTimer; cItem m_FireworkItem; -}; // tolua_export +}; // tolua_export diff --git a/src/Entities/Floater.cpp b/src/Entities/Floater.cpp index b910c3769..159429147 100644 --- a/src/Entities/Floater.cpp +++ b/src/Entities/Floater.cpp @@ -11,7 +11,7 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // cFloaterEntityCollisionCallback class cFloaterEntityCollisionCallback : public cEntityCallback @@ -75,7 +75,7 @@ protected: -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // cFloaterCheckEntityExist class cFloaterCheckEntityExist : public cEntityCallback @@ -130,18 +130,18 @@ void cFloater::Tick(float a_Dt, cChunk & a_Chunk) HandlePhysics(a_Dt, a_Chunk); if (IsBlockWater(m_World->GetBlock((int) GetPosX(), (int) GetPosY(), (int) GetPosZ())) && m_World->GetBlockMeta((int) GetPosX(), (int) GetPosY(), (int) GetPosZ()) == 0) { - if ((!m_CanPickupItem) && (m_AttachedMobID == -1)) // Check if you can't already pickup a fish and if the floater isn't attached to a mob. + if ((!m_CanPickupItem) && (m_AttachedMobID == -1)) // Check if you can't already pickup a fish and if the floater isn't attached to a mob. { 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; m_CountDownTime = 100 + m_World->GetTickRandomNumber(800); LOGD("Floater %i can be picked up", GetUniqueID()); } - else if (m_CountDownTime == 20) // Calculate the position where the particles should spawn and start producing them. + else if (m_CountDownTime == 20) // Calculate the position where the particles should spawn and start producing them. { LOGD("Started producing particles for floater %i", GetUniqueID()); m_ParticlePos.Set(GetPosX() + (-4 + m_World->GetTickRandomNumber(8)), GetPosY(), GetPosZ() + (-4 + m_World->GetTickRandomNumber(8))); @@ -156,12 +156,12 @@ void cFloater::Tick(float a_Dt, cChunk & a_Chunk) m_CountDownTime--; if (m_World->GetHeight((int) GetPosX(), (int) GetPosZ()) == (int) GetPosY()) { - if (m_World->IsWeatherWet() && m_World->GetTickRandomNumber(3) == 0) // 25% chance of an extra countdown when being rained on. + if (m_World->IsWeatherWet() && m_World->GetTickRandomNumber(3) == 0) // 25% chance of an extra countdown when being rained on. { m_CountDownTime--; } } - else // if the floater is underground it has a 50% chance of not decreasing the countdown. + else // if the floater is underground it has a 50% chance of not decreasing the countdown. { if (m_World->GetTickRandomNumber(1) == 0) { @@ -190,21 +190,21 @@ void cFloater::Tick(float a_Dt, cChunk & a_Chunk) if (Callback.HasHit()) { AttachTo(Callback.GetHitEntity()); - Callback.GetHitEntity()->TakeDamage(*this); // TODO: the player attacked the mob not the floater. + Callback.GetHitEntity()->TakeDamage(*this); // TODO: the player attacked the mob not the floater. m_AttachedMobID = Callback.GetHitEntity()->GetUniqueID(); } } cFloaterCheckEntityExist EntityCallback; m_World->DoWithEntityByID(m_PlayerID, EntityCallback); - if (!EntityCallback.DoesExist()) // The owner doesn't exist anymore. Destroy the floater entity. + if (!EntityCallback.DoesExist()) // The owner doesn't exist anymore. Destroy the floater entity. { Destroy(true); } if (m_AttachedMobID != -1) { - m_World->DoWithEntityByID(m_AttachedMobID, EntityCallback); // The mob the floater was attached to doesn't exist anymore. + m_World->DoWithEntityByID(m_AttachedMobID, EntityCallback); // The mob the floater was attached to doesn't exist anymore. if (!EntityCallback.DoesExist()) { m_AttachedMobID = -1; diff --git a/src/Entities/Floater.h b/src/Entities/Floater.h index 547d503f1..5d2720b6a 100644 --- a/src/Entities/Floater.h +++ b/src/Entities/Floater.h @@ -14,7 +14,7 @@ class cFloater : typedef cEntity super; public: - //tolua_end + // tolua_end CLASS_PROTODEF(cFloater); @@ -43,4 +43,4 @@ protected: // Entity IDs int m_PlayerID; int m_AttachedMobID; -} ; // tolua_export +} ; // tolua_export diff --git a/src/Entities/GhastFireballEntity.h b/src/Entities/GhastFireballEntity.h index 9e4572c78..fa59fa642 100644 --- a/src/Entities/GhastFireballEntity.h +++ b/src/Entities/GhastFireballEntity.h @@ -35,4 +35,4 @@ protected: // TODO: Deflecting the fireballs by arrow- or sword- hits -} ; // tolua_export +} ; // tolua_export diff --git a/src/Entities/HangingEntity.cpp b/src/Entities/HangingEntity.cpp index 41ac86268..32d2b226d 100644 --- a/src/Entities/HangingEntity.cpp +++ b/src/Entities/HangingEntity.cpp @@ -10,7 +10,7 @@ cHangingEntity::cHangingEntity(eEntityType a_EntityType, eBlockFace a_BlockFace, double a_X, double a_Y, double a_Z) - : cEntity(a_EntityType, a_X, a_Y, a_Z, 0.8, 0.8) + : cEntity(a_EntityType, a_X, a_Y, a_Z, 0.8, 0.8) , m_BlockFace(a_BlockFace) { SetMaxHealth(1); @@ -28,14 +28,14 @@ void cHangingEntity::SpawnOn(cClientHandle & a_ClientHandle) // The client uses different values for item frame directions and block faces. Our constants are for the block faces, so we convert them here to item frame faces switch (m_BlockFace) { - case BLOCK_FACE_ZP: break; // Initialised to zero + case BLOCK_FACE_ZP: break; // Initialised to zero case BLOCK_FACE_ZM: Dir = 2; break; case BLOCK_FACE_XM: Dir = 1; break; case BLOCK_FACE_XP: Dir = 3; break; default: ASSERT(!"Unhandled block face when trying to spawn item frame!"); return; } - if ((Dir == 0) || (Dir == 2)) // Probably a client bug, but two directions are flipped and contrary to the norm, so we do -180 + if ((Dir == 0) || (Dir == 2)) // Probably a client bug, but two directions are flipped and contrary to the norm, so we do -180 { SetYaw((Dir * 90) - 180); } diff --git a/src/Entities/ItemFrame.cpp b/src/Entities/ItemFrame.cpp index 7bc7bda8d..f0b0c8c65 100644 --- a/src/Entities/ItemFrame.cpp +++ b/src/Entities/ItemFrame.cpp @@ -10,7 +10,7 @@ cItemFrame::cItemFrame(eBlockFace a_BlockFace, double a_X, double a_Y, double a_Z) - : cHangingEntity(etItemFrame, a_BlockFace, a_X, a_Y, a_Z) + : cHangingEntity(etItemFrame, a_BlockFace, a_X, a_Y, a_Z) , m_Item(E_BLOCK_AIR) , m_Rotation(0) { @@ -43,7 +43,7 @@ void cItemFrame::OnRightClicked(cPlayer & a_Player) } } - GetWorld()->BroadcastEntityMetadata(*this); // Update clients + GetWorld()->BroadcastEntityMetadata(*this); // Update clients } @@ -51,17 +51,17 @@ void cItemFrame::OnRightClicked(cPlayer & a_Player) -void cItemFrame::KilledBy(cEntity * a_Killer) +void cItemFrame::KilledBy(TakeDamageInfo & a_TDI) { if (m_Item.IsEmpty()) { SetHealth(0); - super::KilledBy(a_Killer); + super::KilledBy(a_TDI); Destroy(); return; } - if ((a_Killer != NULL) && a_Killer->IsPlayer() && !((cPlayer *)a_Killer)->IsGameModeCreative()) + if ((a_TDI.Attacker != NULL) && a_TDI.Attacker->IsPlayer() && !((cPlayer *)a_TDI.Attacker)->IsGameModeCreative()) { cItems Item; Item.push_back(m_Item); diff --git a/src/Entities/ItemFrame.h b/src/Entities/ItemFrame.h index 6577e7d94..9261e52cc 100644 --- a/src/Entities/ItemFrame.h +++ b/src/Entities/ItemFrame.h @@ -35,7 +35,7 @@ public: private: virtual void OnRightClicked(cPlayer & a_Player) override; - virtual void KilledBy(cEntity * a_Killer) override; + virtual void KilledBy(TakeDamageInfo & a_TDI) override; virtual void GetDrops(cItems & a_Items, cEntity * a_Killer) override; cItem m_Item; diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp index 7bd440d6d..03850c8a7 100644 --- a/src/Entities/Minecart.cpp +++ b/src/Entities/Minecart.cpp @@ -117,7 +117,7 @@ void cMinecart::SpawnOn(cClientHandle & a_ClientHandle) return; } } - a_ClientHandle.SendSpawnVehicle(*this, 10, SubType); // 10 = Minecarts, SubType = What type of Minecart + a_ClientHandle.SendSpawnVehicle(*this, 10, SubType); // 10 = Minecarts, SubType = What type of Minecart a_ClientHandle.SendEntityMetadata(*this); } @@ -127,7 +127,7 @@ void cMinecart::SpawnOn(cClientHandle & a_ClientHandle) void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) { - if (IsDestroyed()) // Mainly to stop detector rails triggering again after minecart is dead + if (IsDestroyed()) // Mainly to stop detector rails triggering again after minecart is dead { return; } @@ -156,8 +156,8 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) if (!IsBlockRail(InsideType)) { - Chunk->GetBlockTypeMeta(RelPosX, PosY + 1, RelPosZ, InsideType, InsideMeta); // When an descending minecart hits a flat rail, it goes through the ground; check for this - if (IsBlockRail(InsideType)) AddPosY(1); // Push cart upwards + Chunk->GetBlockTypeMeta(RelPosX, PosY + 1, RelPosZ, InsideType, InsideMeta); // When an descending minecart hits a flat rail, it goes through the ground; check for this + if (IsBlockRail(InsideType)) AddPosY(1); // Push cart upwards } bool WasDetectorRail = false; @@ -186,12 +186,12 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) default: VERIFY(!"Unhandled rail type despite checking if block was rail!"); break; } - AddPosition(GetSpeed() * (a_Dt / 1000)); // Commit changes; as we use our own engine when on rails, this needs to be done, whereas it is normally in Entity.cpp + AddPosition(GetSpeed() * (a_Dt / 1000)); // Commit changes; as we use our own engine when on rails, this needs to be done, whereas it is normally in Entity.cpp } else { // Not on rail, default physics - SetPosY(floor(GetPosY()) + 0.35); // HandlePhysics overrides this if minecart can fall, else, it is to stop ground clipping minecart bottom when off-rail + SetPosY(floor(GetPosY()) + 0.35); // HandlePhysics overrides this if minecart can fall, else, it is to stop ground clipping minecart bottom when off-rail super::HandlePhysics(a_Dt, *Chunk); } @@ -223,18 +223,18 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) switch (a_RailMeta) { - case E_META_RAIL_ZM_ZP: // NORTHSOUTH + case E_META_RAIL_ZM_ZP: // NORTHSOUTH { SetYaw(270); SetPosY(floor(GetPosY()) + 0.55); - SetSpeedY(0); // Don't move vertically as on ground - SetSpeedX(0); // Correct diagonal movement from curved rails + SetSpeedY(0); // Don't move vertically as on ground + SetSpeedX(0); // Correct diagonal movement from curved rails // Execute both the entity and block collision checks bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); if (EntCol || BlckCol) return; - if (GetSpeedZ() != 0) // Don't do anything if cart is stationary + if (GetSpeedZ() != 0) // Don't do anything if cart is stationary { if (GetSpeedZ() > 0) { @@ -249,7 +249,7 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) } break; } - case E_META_RAIL_XM_XP: // EASTWEST + case E_META_RAIL_XM_XP: // EASTWEST { SetYaw(180); SetPosY(floor(GetPosY()) + 0.55); @@ -272,7 +272,7 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) } break; } - case E_META_RAIL_ASCEND_ZM: // ASCEND NORTH + case E_META_RAIL_ASCEND_ZM: // ASCEND NORTH { SetYaw(270); SetSpeedX(0); @@ -280,21 +280,21 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) if (GetSpeedZ() >= 0) { // SpeedZ POSITIVE, going SOUTH - if (GetSpeedZ() <= MAX_SPEED) // Speed limit + if (GetSpeedZ() <= MAX_SPEED) // Speed limit { - AddSpeedZ(0.5); // Speed up - SetSpeedY(-GetSpeedZ()); // Downward movement is negative (0 minus positive numbers is negative) + AddSpeedZ(0.5); // Speed up + SetSpeedY(-GetSpeedZ()); // Downward movement is negative (0 minus positive numbers is negative) } } else { // SpeedZ NEGATIVE, going NORTH - AddSpeedZ(1); // Slow down - SetSpeedY(-GetSpeedZ()); // Upward movement is positive (0 minus negative number is positive number) + AddSpeedZ(1); // Slow down + SetSpeedY(-GetSpeedZ()); // Upward movement is positive (0 minus negative number is positive number) } break; } - case E_META_RAIL_ASCEND_ZP: // ASCEND SOUTH + case E_META_RAIL_ASCEND_ZP: // ASCEND SOUTH { SetYaw(270); SetSpeedX(0); @@ -302,21 +302,21 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) if (GetSpeedZ() > 0) { // SpeedZ POSITIVE, going SOUTH - AddSpeedZ(-1); // Slow down - SetSpeedY(GetSpeedZ()); // Upward movement positive + AddSpeedZ(-1); // Slow down + SetSpeedY(GetSpeedZ()); // Upward movement positive } else { - if (GetSpeedZ() >= MAX_SPEED_NEGATIVE) // Speed limit + if (GetSpeedZ() >= MAX_SPEED_NEGATIVE) // Speed limit { // SpeedZ NEGATIVE, going NORTH - AddSpeedZ(-0.5); // Speed up - SetSpeedY(GetSpeedZ()); // Downward movement negative + AddSpeedZ(-0.5); // Speed up + SetSpeedY(GetSpeedZ()); // Downward movement negative } } break; } - case E_META_RAIL_ASCEND_XM: // ASCEND EAST + case E_META_RAIL_ASCEND_XM: // ASCEND EAST { SetYaw(180); SetSpeedZ(0); @@ -336,7 +336,7 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) } break; } - case E_META_RAIL_ASCEND_XP: // ASCEND WEST + case E_META_RAIL_ASCEND_XP: // ASCEND WEST { SetYaw(180); SetSpeedZ(0); @@ -356,10 +356,10 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) } break; } - case E_META_RAIL_CURVED_ZM_XM: // Ends pointing NORTH and WEST + case E_META_RAIL_CURVED_ZM_XM: // Ends pointing NORTH and WEST { - SetYaw(315); // Set correct rotation server side - SetPosY(floor(GetPosY()) + 0.55); // Levitate dat cart + SetYaw(315); // Set correct rotation server side + SetPosY(floor(GetPosY()) + 0.55); // Levitate dat cart SetSpeedY(0); TestBlockCollision(a_RailMeta); @@ -369,7 +369,7 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) break; } - case E_META_RAIL_CURVED_ZM_XP: // Curved NORTH EAST + case E_META_RAIL_CURVED_ZM_XP: // Curved NORTH EAST { SetYaw(225); SetPosY(floor(GetPosY()) + 0.55); @@ -380,7 +380,7 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) break; } - case E_META_RAIL_CURVED_ZP_XM: // Curved SOUTH WEST + case E_META_RAIL_CURVED_ZP_XM: // Curved SOUTH WEST { SetYaw(135); SetPosY(floor(GetPosY()) + 0.55); @@ -391,7 +391,7 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) break; } - case E_META_RAIL_CURVED_ZP_XP: // Curved SOUTH EAST + case E_META_RAIL_CURVED_ZP_XP: // Curved SOUTH EAST { SetYaw(45); SetPosY(floor(GetPosY()) + 0.55); @@ -404,7 +404,7 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) } default: { - ASSERT(!"Unhandled rail meta!"); // Dun dun DUN! + ASSERT(!"Unhandled rail meta!"); // Dun dun DUN! break; } } @@ -428,7 +428,7 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) switch (a_RailMeta & 0x07) { - case E_META_RAIL_ZM_ZP: // NORTHSOUTH + case E_META_RAIL_ZM_ZP: // NORTHSOUTH { SetYaw(270); SetPosY(floor(GetPosY()) + 0.55); @@ -451,7 +451,7 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) } break; } - case E_META_RAIL_XM_XP: // EASTWEST + case E_META_RAIL_XM_XP: // EASTWEST { SetYaw(180); SetPosY(floor(GetPosY()) + 0.55); @@ -474,7 +474,7 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) } break; } - case E_META_RAIL_ASCEND_XM: // ASCEND EAST + case E_META_RAIL_ASCEND_XM: // ASCEND EAST { SetYaw(180); SetSpeedZ(0); @@ -493,8 +493,8 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) SetSpeedY(-GetSpeedX()); } break; - } - case E_META_RAIL_ASCEND_XP: // ASCEND WEST + } + case E_META_RAIL_ASCEND_XP: // ASCEND WEST { SetYaw(180); SetSpeedZ(0); @@ -513,8 +513,8 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) } } break; - } - case E_META_RAIL_ASCEND_ZM: // ASCEND NORTH + } + case E_META_RAIL_ASCEND_ZM: // ASCEND NORTH { SetYaw(270); SetSpeedX(0); @@ -534,7 +534,7 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) } break; } - case E_META_RAIL_ASCEND_ZP: // ASCEND SOUTH + case E_META_RAIL_ASCEND_ZP: // ASCEND SOUTH { SetYaw(270); SetSpeedX(0); @@ -787,7 +787,7 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) } } break; - } + } case E_META_RAIL_CURVED_ZM_XM: case E_META_RAIL_CURVED_ZM_XP: case E_META_RAIL_CURVED_ZP_XM: @@ -883,7 +883,7 @@ bool cMinecart::TestEntityCollision(NIBBLETYPE a_RailMeta) } } return true; - } + } case E_META_RAIL_CURVED_ZM_XM: case E_META_RAIL_CURVED_ZM_XP: case E_META_RAIL_CURVED_ZP_XM: @@ -907,7 +907,7 @@ bool cMinecart::DoTakeDamage(TakeDamageInfo & TDI) if ((TDI.Attacker != NULL) && TDI.Attacker->IsPlayer() && ((cPlayer *)TDI.Attacker)->IsGameModeCreative()) { Destroy(); - TDI.FinalDamage = GetMaxHealth(); // Instant hit for creative + TDI.FinalDamage = GetMaxHealth(); // Instant hit for creative SetInvulnerableTicks(0); return super::DoTakeDamage(TDI); // No drops for creative } @@ -980,7 +980,7 @@ void cMinecart::Destroyed() -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // cRideableMinecart: cRideableMinecart::cRideableMinecart(double a_X, double a_Y, double a_Z, const cItem & a_Content, int a_Height) : @@ -1023,7 +1023,7 @@ void cRideableMinecart::OnRightClicked(cPlayer & a_Player) -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // cMinecartWithChest: cMinecartWithChest::cMinecartWithChest(double a_X, double a_Y, double a_Z) : @@ -1056,7 +1056,7 @@ void cMinecartWithChest::OnRightClicked(cPlayer & a_Player) -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // cMinecartWithFurnace: cMinecartWithFurnace::cMinecartWithFurnace(double a_X, double a_Y, double a_Z) : @@ -1078,12 +1078,12 @@ void cMinecartWithFurnace::OnRightClicked(cPlayer & a_Player) { a_Player.GetInventory().RemoveOneEquippedItem(); } - if (!m_IsFueled) // We don't want to change the direction by right clicking it. + if (!m_IsFueled) // We don't want to change the direction by right clicking it. { AddSpeed(a_Player.GetLookVector().x, 0, a_Player.GetLookVector().z); } m_IsFueled = true; - m_FueledTimeLeft = m_FueledTimeLeft + 600; // The minecart will be active 600 more ticks. + m_FueledTimeLeft = m_FueledTimeLeft + 600; // The minecart will be active 600 more ticks. m_World->BroadcastEntityMetadata(*this); } } @@ -1118,7 +1118,7 @@ void cMinecartWithFurnace::Tick(float a_Dt, cChunk & a_Chunk) -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // cMinecartWithTNT: cMinecartWithTNT::cMinecartWithTNT(double a_X, double a_Y, double a_Z) : @@ -1132,7 +1132,7 @@ cMinecartWithTNT::cMinecartWithTNT(double a_X, double a_Y, double a_Z) : -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // cMinecartWithHopper: cMinecartWithHopper::cMinecartWithHopper(double a_X, double a_Y, double a_Z) : diff --git a/src/Entities/Minecart.h b/src/Entities/Minecart.h index 1e60f091c..798f844ce 100644 --- a/src/Entities/Minecart.h +++ b/src/Entities/Minecart.h @@ -99,7 +99,7 @@ public: protected: cItem m_Content; - int m_Height; + int m_Height; } ; diff --git a/src/Entities/Painting.h b/src/Entities/Painting.h index c1024bd1b..ce7c6f3de 100644 --- a/src/Entities/Painting.h +++ b/src/Entities/Painting.h @@ -18,17 +18,17 @@ public: CLASS_PROTODEF(cPainting); cPainting(const AString & a_Name, int a_Direction, double a_X, double a_Y, double a_Z); - const AString & GetName(void) const { return m_Name; } // tolua_export - int GetDirection(void) const { return m_Direction; } // tolua_export + const AString & GetName(void) const { return m_Name; } // tolua_export + int GetDirection(void) const { return m_Direction; } // tolua_export private: virtual void SpawnOn(cClientHandle & a_Client) override; virtual void Tick(float a_Dt, cChunk & a_Chunk) override; virtual void GetDrops(cItems & a_Items, cEntity * a_Killer) override; - virtual void KilledBy(cEntity * a_Killer) override + virtual void KilledBy(TakeDamageInfo & a_TDI) override { - super::KilledBy(a_Killer); + super::KilledBy(a_TDI); Destroy(); } diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp index fffefd538..fe6c24a7a 100644 --- a/src/Entities/Pawn.cpp +++ b/src/Entities/Pawn.cpp @@ -2,14 +2,16 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Pawn.h" +#include "../World.h" +#include "../Bindings/PluginManager.h" -cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height) - : cEntity(a_EntityType, 0, 0, 0, a_Width, a_Height) - , m_bBurnable(true) +cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height): + super(a_EntityType, 0, 0, 0, a_Width, a_Height), + m_EntityEffects(tEffectMap()) { } @@ -17,3 +19,95 @@ cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height) +void cPawn::Tick(float a_Dt, cChunk & a_Chunk) +{ + // Iterate through this entity's applied effects + for (tEffectMap::iterator iter = m_EntityEffects.begin(); iter != m_EntityEffects.end();) + { + // Copies values to prevent pesky wrong accesses and erasures + cEntityEffect::eType EffectType = iter->first; + cEntityEffect * Effect = iter->second; + + Effect->OnTick(*this); + + // Iterates (must be called before any possible erasure) + ++iter; + + // Remove effect if duration has elapsed + if (Effect->GetDuration() - Effect->GetTicks() <= 0) + { + RemoveEntityEffect(EffectType); + } + + // TODO: Check for discrepancies between client and server effect values + } + + super::Tick(a_Dt, a_Chunk); +} + + + + + +void cPawn::KilledBy(TakeDamageInfo & a_TDI) +{ + ClearEntityEffects(); + super::KilledBy(a_TDI); +} + + + + + +void cPawn::AddEntityEffect(cEntityEffect::eType a_EffectType, int a_Duration, short a_Intensity, double a_DistanceModifier) +{ + // Check if the plugins allow the addition: + if (cPluginManager::Get()->CallHookEntityAddEffect(*this, a_EffectType, a_Duration, a_Intensity, a_DistanceModifier)) + { + // A plugin disallows the addition, bail out. + return; + } + + // No need to add empty effects: + if (a_EffectType == cEntityEffect::effNoEffect) + { + return; + } + a_Duration = (int)(a_Duration * a_DistanceModifier); + + m_EntityEffects[a_EffectType] = cEntityEffect::CreateEntityEffect(a_EffectType, a_Duration, a_Intensity, a_DistanceModifier); + m_World->BroadcastEntityEffect(*this, a_EffectType, a_Intensity, a_Duration); + m_EntityEffects[a_EffectType]->OnActivate(*this); +} + + + + + +void cPawn::RemoveEntityEffect(cEntityEffect::eType a_EffectType) +{ + m_World->BroadcastRemoveEntityEffect(*this, a_EffectType); + m_EntityEffects[a_EffectType]->OnDeactivate(*this); + delete m_EntityEffects[a_EffectType]; + m_EntityEffects.erase(a_EffectType); +} + + + + + +void cPawn::ClearEntityEffects() +{ + // Iterate through this entity's applied effects + for (tEffectMap::iterator iter = m_EntityEffects.begin(); iter != m_EntityEffects.end();) + { + // Copy values to prevent pesky wrong erasures + cEntityEffect::eType EffectType = iter->first; + + // Iterates (must be called before any possible erasure) + ++iter; + + // Remove effect + RemoveEntityEffect(EffectType); + } +} diff --git a/src/Entities/Pawn.h b/src/Entities/Pawn.h index e76337d86..63c7cfbb6 100644 --- a/src/Entities/Pawn.h +++ b/src/Entities/Pawn.h @@ -2,6 +2,7 @@ #pragma once #include "Entity.h" +#include "EntityEffect.h" @@ -18,9 +19,34 @@ public: CLASS_PROTODEF(cPawn); cPawn(eEntityType a_EntityType, double a_Width, double a_Height); + + virtual void Tick(float a_Dt, cChunk & a_Chunk) override; + virtual void KilledBy(TakeDamageInfo & a_TDI) override; + + // tolua_begin + + /** Applies an entity effect + Checks with plugins if they allow the addition. + @param a_EffectType The entity effect to apply + @param a_EffectDurationTicks The duration of the effect + @param a_EffectIntensity The level of the effect (0 = Potion I, 1 = Potion II, etc) + @param a_DistanceModifier The scalar multiplied to the potion duration, only applies to splash potions) + */ + void AddEntityEffect(cEntityEffect::eType a_EffectType, int a_EffectDurationTicks, short a_EffectIntensity, double a_DistanceModifier = 1); + + /** Removes a currently applied entity effect + @param a_EffectType The entity effect to remove + */ + void RemoveEntityEffect(cEntityEffect::eType a_EffectType); + + /** Removes all currently applied entity effects (used when drinking milk) */ + void ClearEntityEffects(void); + + // tolua_end protected: - bool m_bBurnable; + typedef std::map<cEntityEffect::eType, cEntityEffect *> tEffectMap; + tEffectMap m_EntityEffects; } ; // tolua_export diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 0fd006485..aab534f41 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; @@ -64,7 +85,7 @@ protected: cPickup::cPickup(double a_PosX, double a_PosY, double a_PosZ, const cItem & a_Item, bool IsPlayerCreated, float a_SpeedX /* = 0.f */, float a_SpeedY /* = 0.f */, float a_SpeedZ /* = 0.f */) - : cEntity(etPickup, a_PosX, a_PosY, a_PosZ, 0.2, 0.2) + : cEntity(etPickup, a_PosX, a_PosY, a_PosZ, 0.2, 0.2) , m_Timer(0.f) , m_Item(a_Item) , m_bCollected(false) @@ -92,7 +113,7 @@ void cPickup::SpawnOn(cClientHandle & a_Client) void cPickup::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); - BroadcastMovementUpdate(); //Notify clients of position + BroadcastMovementUpdate(); // Notify clients of position m_Timer += a_Dt; @@ -122,17 +143,17 @@ void cPickup::Tick(float a_Dt, cChunk & a_Chunk) m_bCollected = true; m_Timer = 0; // We have to reset the timer. m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick. - if (m_Timer > 500.f) + if (m_Timer > 500.f) { Destroy(true); return; } } - 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 + m_World->ForEachEntity(PickupCombiningCallback); // Not ForEachEntityInChunk, otherwise pickups don't combine across chunk boundaries if (PickupCombiningCallback.FoundMatchingPickup()) { m_World->BroadcastEntityMetadata(*this); @@ -155,7 +176,7 @@ void cPickup::Tick(float a_Dt, cChunk & a_Chunk) return; } - if (GetPosY() < VOID_BOUNDARY) // Out of this world and no more visible! + if (GetPosY() < VOID_BOUNDARY) // Out of this world and no more visible! { Destroy(true); return; @@ -173,14 +194,14 @@ bool cPickup::CollectedBy(cPlayer * a_Dest) if (m_bCollected) { // LOG("Pickup %d cannot be collected by \"%s\", because it has already been collected.", m_UniqueID, a_Dest->GetName().c_str()); - return false; // It's already collected! + return false; // It's already collected! } // Two seconds if player created the pickup (vomiting), half a second if anything else if (m_Timer < (m_bIsPlayerCreated ? 2000.f : 500.f)) { // LOG("Pickup %d cannot be collected by \"%s\", because it is not old enough.", m_UniqueID, a_Dest->GetName().c_str()); - return false; // Not old enough + return false; // Not old enough } if (cRoot::Get()->GetPluginManager()->CallHookCollectingPickup(a_Dest, *this)) @@ -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/Pickup.h b/src/Entities/Pickup.h index 2dcbecaaf..d7c5d2b26 100644 --- a/src/Entities/Pickup.h +++ b/src/Entities/Pickup.h @@ -27,12 +27,12 @@ public: cPickup(double a_PosX, double a_PosY, double a_PosZ, const cItem & a_Item, bool IsPlayerCreated, float a_SpeedX = 0.f, float a_SpeedY = 0.f, float a_SpeedZ = 0.f); - cItem & GetItem(void) {return m_Item; } // tolua_export + cItem & GetItem(void) {return m_Item; } // tolua_export const cItem & GetItem(void) const {return m_Item; } virtual void SpawnOn(cClientHandle & a_ClientHandle) override; - bool CollectedBy(cPlayer * a_Dest); // tolua_export + bool CollectedBy(cPlayer * a_Dest); // tolua_export virtual void Tick(float a_Dt, cChunk & a_Chunk) override; @@ -46,7 +46,7 @@ public: bool IsCollected(void) const { return m_bCollected; } // tolua_export /** Returns true if created by player (i.e. vomiting), used for determining picking-up delay time */ - bool IsPlayerCreated(void) const { return m_bIsPlayerCreated; } // tolua_export + bool IsPlayerCreated(void) const { return m_bIsPlayerCreated; } // tolua_export private: diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 5a156bd3b..1acc8a962 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -1,13 +1,15 @@ - + #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Player.h" +#include "../ChatColor.h" #include "../Server.h" #include "../UI/Window.h" #include "../UI/WindowOwner.h" #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 +35,47 @@ -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_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); @@ -127,7 +126,7 @@ cPlayer::~cPlayer(void) if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this)) { cRoot::Get()->BroadcastChatLeave(Printf("%s has left the game", GetName().c_str())); - LOGINFO("Player %s has left the game.", GetName().c_str()); + LOGINFO("Player %s has left the game", GetName().c_str()); } LOGD("Deleting cPlayer \"%s\" at %p, ID %d", GetName().c_str(), this, GetUniqueID()); @@ -219,7 +218,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(); @@ -280,19 +279,19 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) short cPlayer::CalcLevelFromXp(short a_XpTotal) { - //level 0 to 15 + // level 0 to 15 if(a_XpTotal <= XP_TO_LEVEL15) { return a_XpTotal / XP_PER_LEVEL_TO15; } - //level 30+ + // level 30+ if(a_XpTotal > XP_TO_LEVEL30) { return (short) (151.5 + sqrt( 22952.25 - (14 * (2220 - a_XpTotal)))) / 7; } - //level 16 to 30 + // level 16 to 30 return (short) ( 29.5 + sqrt( 870.25 - (6 * ( 360 - a_XpTotal )))) / 3; } @@ -302,19 +301,19 @@ short cPlayer::CalcLevelFromXp(short a_XpTotal) short cPlayer::XpForLevel(short a_Level) { - //level 0 to 15 + // level 0 to 15 if(a_Level <= 15) { return a_Level * XP_PER_LEVEL_TO15; } - //level 30+ + // level 30+ if(a_Level >= 31) { return (short) ( (3.5 * a_Level * a_Level) - (151.5 * a_Level) + 2220 ); } - //level 16 to 30 + // level 16 to 30 return (short) ( (1.5 * a_Level * a_Level) - (29.5 * a_Level) + 360 ); } @@ -336,7 +335,7 @@ float cPlayer::GetXpPercentage() short int currentLevel = CalcLevelFromXp(m_CurrentXp); short int currentLevel_XpBase = XpForLevel(currentLevel); - return (float)(m_CurrentXp - currentLevel_XpBase) / + return (float)(m_CurrentXp - currentLevel_XpBase) / (float)(XpForLevel(1+currentLevel) - currentLevel_XpBase); } @@ -349,7 +348,7 @@ bool cPlayer::SetCurrentExperience(short int a_CurrentXp) if(!(a_CurrentXp >= 0) || (a_CurrentXp > (SHRT_MAX - m_LifetimeTotalXp))) { LOGWARNING("Tried to update experiece with an invalid Xp value: %d", a_CurrentXp); - return false; //oops, they gave us a dodgey number + return false; // oops, they gave us a dodgey number } m_CurrentXp = a_CurrentXp; @@ -371,7 +370,7 @@ short cPlayer::DeltaExperience(short a_Xp_delta) // Value was bad, abort and report LOGWARNING("Attempt was made to increment Xp by %d, which overflowed the short datatype. Ignoring.", a_Xp_delta); - return -1; // Should we instead just return the current Xp? + return -1; // Should we instead just return the current Xp? } m_CurrentXp += a_Xp_delta; @@ -388,7 +387,7 @@ short cPlayer::DeltaExperience(short a_Xp_delta) m_LifetimeTotalXp += a_Xp_delta; } - LOGD("Player \"%s\" gained/lost %d experience, total is now: %d", + LOGD("Player \"%s\" gained/lost %d experience, total is now: %d", GetName().c_str(), a_Xp_delta, m_CurrentXp); // Set experience to be updated @@ -406,6 +405,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); } @@ -418,6 +418,8 @@ int cPlayer::FinishChargingBow(void) int res = m_BowCharge; m_IsChargingBow = false; m_BowCharge = 0; + m_World->BroadcastEntityMetadata(*this, m_ClientHandle); + return res; } @@ -430,6 +432,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); } @@ -469,7 +472,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) { float Dist = (float)(m_LastGroundHeight - floor(GetPosY())); - if (Dist >= 2.0) // At least two blocks - TODO: Use m_LastJumpHeight instead of m_LastGroundHeight above + if (Dist >= 2.0) // At least two blocks - TODO: Use m_LastJumpHeight instead of m_LastGroundHeight above { // Increment statistic m_Stats.AddValue(statDistFallen, (StatValue)floor(Dist * 100 + 0.5)); @@ -489,7 +492,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) // Fall particles GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, (int)GetPosY() - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */); - } + } m_LastGroundHeight = (float)GetPosY(); } @@ -511,7 +514,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(); } @@ -546,26 +557,15 @@ void cPlayer::SetFoodExhaustionLevel(double a_FoodExhaustionLevel) -void cPlayer::SetFoodPoisonedTicksRemaining(int a_FoodPoisonedTicksRemaining) -{ - m_FoodPoisonedTicksRemaining = a_FoodPoisonedTicksRemaining; -} - - - - - bool cPlayer::Feed(int a_Food, double a_Saturation) { - if (m_FoodLevel >= MAX_FOOD_LEVEL) + if (IsSatiated()) { 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; } @@ -575,17 +575,7 @@ bool cPlayer::Feed(int a_Food, double a_Saturation) void cPlayer::FoodPoison(int a_NumTicks) { - bool HasBeenFoodPoisoned = (m_FoodPoisonedTicksRemaining > 0); - m_FoodPoisonedTicksRemaining = std::max(m_FoodPoisonedTicksRemaining, a_NumTicks); - if (!HasBeenFoodPoisoned) - { - m_World->BroadcastRemoveEntityEffect(*this, E_EFFECT_HUNGER); - SendHealth(); - } - else - { - m_World->BroadcastEntityEffect(*this, E_EFFECT_HUNGER, 0, 400); // Give the player the "Hunger" effect for 20 seconds. - } + AddEntityEffect(cEntityEffect::effHunger, a_NumTicks, 0, 1); } @@ -628,7 +618,7 @@ void cPlayer::FinishEating(void) GetInventory().RemoveOneEquippedItem(); - //if the food is mushroom soup, return a bowl to the inventory + // if the food is mushroom soup, return a bowl to the inventory if( Item.m_ItemType == E_ITEM_MUSHROOM_SOUP ) { cItem emptyBowl(E_ITEM_BOWL, 1, 0, ""); GetInventory().AddItem(emptyBowl, true, true); @@ -870,16 +860,16 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) -void cPlayer::KilledBy(cEntity * a_Killer) +void cPlayer::KilledBy(TakeDamageInfo & a_TDI) { - super::KilledBy(a_Killer); + super::KilledBy(a_TDI); if (m_Health > 0) { - return; // not dead yet =] + return; // not dead yet =] } - m_bVisible = false; // So new clients don't see the player + m_bVisible = false; // So new clients don't see the player // Puke out all the items cItems Pickups; @@ -896,20 +886,42 @@ void cPlayer::KilledBy(cEntity * a_Killer) m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10); SaveToDisk(); // Save it, yeah the world is a tough place ! - if (a_Killer == NULL) + if (a_TDI.Attacker == NULL) { - GetWorld()->BroadcastChatDeath(Printf("%s was killed by environmental damage", GetName().c_str())); + AString DamageText; + switch (a_TDI.DamageType) + { + case dtRangedAttack: DamageText = "was shot"; break; + case dtLightning: DamageText = "was plasmified by lightining"; break; + case dtFalling: DamageText = (GetWorld()->GetTickRandomNumber(10) % 2 == 0) ? "fell to death" : "hit the ground too hard"; break; + case dtDrowning: DamageText = "drowned"; break; + case dtSuffocating: DamageText = (GetWorld()->GetTickRandomNumber(10) % 2 == 0) ? "git merge'd into a block" : "fused with a block"; break; + case dtStarving: DamageText = "forgot the importance of food"; break; + case dtCactusContact: DamageText = "was impaled on a cactus"; break; + case dtLavaContact: DamageText = "was melted by lava"; break; + case dtPoisoning: DamageText = "died from septicaemia"; break; + case dtWithering: DamageText = "is a husk of their former selves"; break; + case dtOnFire: DamageText = "forgot to stop, drop, and roll"; break; + case dtFireContact: DamageText = "burnt themselves to death"; break; + case dtInVoid: DamageText = "somehow fell out of the world"; break; + case dtPotionOfHarming: DamageText = "was magicked to death"; break; + case dtEnderPearl: DamageText = "misused an ender pearl"; break; + case dtAdmin: DamageText = "was administrator'd"; break; + case dtExplosion: DamageText = "blew up"; break; + default: DamageText = "died, somehow; we've no idea how though"; break; + } + GetWorld()->BroadcastChatDeath(Printf("%s %s", GetName().c_str(), DamageText.c_str())); } - else if (a_Killer->IsPlayer()) + else if (a_TDI.Attacker->IsPlayer()) { - cPlayer * Killer = (cPlayer *)a_Killer; + cPlayer * Killer = (cPlayer *)a_TDI.Attacker; GetWorld()->BroadcastChatDeath(Printf("%s was killed by %s", GetName().c_str(), Killer->GetName().c_str())); } else { - AString KillerClass = a_Killer->GetClass(); - KillerClass.erase(KillerClass.begin()); // Erase the 'c' of the class (e.g. "cWitch" -> "Witch") + AString KillerClass = a_TDI.Attacker->GetClass(); + KillerClass.erase(KillerClass.begin()); // Erase the 'c' of the class (e.g. "cWitch" -> "Witch") GetWorld()->BroadcastChatDeath(Printf("%s was killed by a %s", GetName().c_str(), KillerClass.c_str())); } @@ -959,7 +971,8 @@ 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; @@ -1216,6 +1229,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(); @@ -1260,7 +1274,7 @@ Vector3d cPlayer::GetThrowSpeed(double a_SpeedCoeff) const // TODO: Add a slight random change (+-0.0075 in each direction) return res * a_SpeedCoeff; -} +} @@ -1293,7 +1307,7 @@ void cPlayer::MoveTo( const Vector3d & a_NewPos ) { // When attached to an entity, the client sends position packets with weird coords: // Y = -999 and X, Z = attempting to create speed, usually up to 0.03 - // We cannot test m_AttachedTo, because when deattaching, the server thinks the client is already deattached while + // We cannot test m_AttachedTo, because when deattaching, the server thinks the client is already deattached while // the client may still send more of these nonsensical packets. if (m_AttachedTo != NULL) { @@ -1320,7 +1334,7 @@ void cPlayer::MoveTo( const Vector3d & a_NewPos ) void cPlayer::SetVisible(bool a_bVisible) { - if (a_bVisible && !m_bVisible) // Make visible + if (a_bVisible && !m_bVisible) // Make visible { m_bVisible = true; m_World->BroadcastSpawnEntity(*this); @@ -1328,7 +1342,7 @@ void cPlayer::SetVisible(bool a_bVisible) if (!a_bVisible && m_bVisible) { m_bVisible = false; - m_World->BroadcastDestroyEntity(*this, m_ClientHandle); // Destroy on all clients + m_World->BroadcastDestroyEntity(*this, m_ClientHandle); // Destroy on all clients } } @@ -1402,7 +1416,7 @@ bool cPlayer::HasPermission(const AString & a_Permission) { if( OtherSplit[i].compare( Split[i] ) != 0 ) { - if( OtherSplit[i].compare("*") == 0 ) return true; // WildCard man!! WildCard! + if( OtherSplit[i].compare("*") == 0 ) return true; // WildCard man!! WildCard! break; } } @@ -1436,7 +1450,7 @@ bool cPlayer::IsInGroup( const AString & a_Group ) void cPlayer::ResolvePermissions() { - m_ResolvedPermissions.clear(); // Start with an empty map yo~ + m_ResolvedPermissions.clear(); // Start with an empty map // Copy all player specific permissions into the resolved permissions map for( PermissionMap::iterator itr = m_Permissions.begin(); itr != m_Permissions.end(); ++itr ) @@ -1464,7 +1478,7 @@ void cPlayer::ResolveGroups() m_ResolvedGroups.clear(); // Get a complete resolved list of all groups the player is in - std::map< cGroup*, bool > AllGroups; // Use a map, because it's faster than iterating through a list to find duplicates + std::map< cGroup*, bool > AllGroups; // Use a map, because it's faster than iterating through a list to find duplicates GroupList ToIterate; for( GroupList::iterator GroupItr = m_Groups.begin(); GroupItr != m_Groups.end(); ++GroupItr ) { @@ -1482,7 +1496,7 @@ void cPlayer::ResolveGroups() else { AllGroups[ CurrentGroup ] = true; - m_ResolvedGroups.push_back( CurrentGroup ); // Add group to resolved list + m_ResolvedGroups.push_back( CurrentGroup ); // Add group to resolved list const cGroup::GroupList & Inherits = CurrentGroup->GetInherits(); for( cGroup::GroupList::const_iterator itr = Inherits.begin(); itr != Inherits.end(); ++itr ) { @@ -1530,7 +1544,7 @@ void cPlayer::TossEquippedItem(char a_Amount) char NewAmount = a_Amount; if (NewAmount > GetInventory().GetEquippedItem().m_ItemCount) { - NewAmount = GetInventory().GetEquippedItem().m_ItemCount; // Drop only what's there + NewAmount = GetInventory().GetEquippedItem().m_ItemCount; // Drop only what's there } GetInventory().GetHotbarGrid().ChangeSlotCount(GetInventory().GetEquippedSlotNum() /* Returns hotbar subslot, which HotbarGrid takes */, -a_Amount); @@ -1592,7 +1606,7 @@ void cPlayer::TossItems(const cItems & a_Items) double vX = 0, vY = 0, vZ = 0; EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); vY = -vY * 2 + 1.f; - m_World->SpawnItemPickups(a_Items, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player + m_World->SpawnItemPickups(a_Items, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player } @@ -1681,7 +1695,12 @@ void cPlayer::LoadPermissionsFromDisk() +<<<<<<< HEAD bool cPlayer::LoadFromDisk(cWorldPtr & a_World) +======= + +bool cPlayer::LoadFromDisk(void) +>>>>>>> master { a_World = cRoot::Get()->GetWorld(GetLoadedWorldName()); if (a_World == NULL) @@ -1691,49 +1710,93 @@ bool cPlayer::LoadFromDisk(cWorldPtr & a_World) 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(); @@ -1754,6 +1817,7 @@ bool cPlayer::LoadFromDisk(cWorldPtr & a_World) } m_Inventory.LoadFromJson(root["inventory"]); + cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents); m_LoadedWorldName = root.get("world", "world").asString(); @@ -1762,8 +1826,8 @@ bool cPlayer::LoadFromDisk(cWorldPtr & a_World) 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(), GetLoadedWorldName().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; @@ -1776,6 +1840,7 @@ bool cPlayer::LoadFromDisk(cWorldPtr & a_World) 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; @@ -1791,7 +1856,11 @@ 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; +<<<<<<< HEAD root["position"] = JSON_PlayerPosition; root["rotation"] = JSON_PlayerRotation; root["inventory"] = JSON_Inventory; @@ -1812,27 +1881,59 @@ bool cPlayer::SaveToDisk() 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; + } +>>>>>>> master } 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; } @@ -1841,7 +1942,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; } @@ -1874,14 +1975,14 @@ cPlayer::StringList cPlayer::GetResolvedPermissions() void cPlayer::UseEquippedItem(void) { - if (IsGameModeCreative()) // No damage in creative + if (IsGameModeCreative()) // No damage in creative { return; } 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)); } } @@ -1909,16 +2010,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)) { @@ -1927,11 +2025,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)) { @@ -1940,37 +2038,21 @@ void cPlayer::HandleFood(void) } } } - - // Apply food poisoning food exhaustion: - if (m_FoodPoisonedTicksRemaining > 0) - { - m_FoodPoisonedTicksRemaining--; - m_FoodExhaustionLevel += 0.025; // 0.5 per second = 0.025 per tick - } - else - { - m_World->BroadcastRemoveEntityEffect(*this, E_EFFECT_HUNGER); // Remove the "Hunger" effect. - } // 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(); - } } @@ -2036,7 +2118,7 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos) { if (IsClimbing()) { - if (a_DeltaPos.y > 0.0) // Going up + if (a_DeltaPos.y > 0.0) // Going up { m_Stats.AddValue(statDistClimbed, (StatValue)floor(a_DeltaPos.y * 100 + 0.5)); } @@ -2055,7 +2137,7 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos) } else { - if (Value >= 25) // Ignore small/slow movement + if (Value >= 25) // Ignore small/slow movement { m_Stats.AddValue(statDistFlown, Value); } @@ -2094,11 +2176,25 @@ void cPlayer::ApplyFoodExhaustionFromMovement() return; } + // If we have just teleported, apply no exhaustion + if (m_bIsTeleporting) + { + m_bIsTeleporting = false; + return; + } + // If riding anything, apply no food exhaustion if (m_AttachedTo != NULL) { return; } + + // Process exhaustion every two ticks as that is how frequently m_LastPos is updated + // Otherwise, we apply exhaustion for a 'movement' every tick, one of which is an already processed value + if (GetWorld()->GetWorldAge() % 2 != 0) + { + return; + } // Calculate the distance travelled, update the last pos: Vector3d Movement(GetPosition() - m_LastPos); @@ -2157,3 +2253,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 eff5d0663..f84cc5f55 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; @@ -118,12 +119,15 @@ public: void SetTouchGround( bool a_bTouchGround ); inline void SetStance( const double a_Stance ) { m_Stance = a_Stance; } - double GetEyeHeight(void) const; // tolua_export - Vector3d GetEyePosition(void) const; // tolua_export + double GetEyeHeight(void) const; // tolua_export + Vector3d GetEyePosition(void) const; // tolua_export inline bool IsOnGround(void) const {return m_bTouchGround; } // tolua_export 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 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 @@ -170,7 +174,7 @@ public: AString GetIP(void) const { return m_IP; } // tolua_export /** Returns the associated team, NULL if none */ - cTeam * GetTeam(void) { return m_Team; } // tolua_export + cTeam * GetTeam(void) { return m_Team; } // tolua_export /** Sets the player team, NULL if none */ void SetTeam(cTeam * a_Team); @@ -194,9 +198,9 @@ public: // Sets the current gamemode, doesn't check validity, doesn't send update packets to client void LoginSetGameMode(eGameMode a_GameMode); - /** Forces the player to move in the given direction. + /** Forces the player to move in the given direction. @deprecated Use SetSpeed instead. */ - void ForceSetSpeed(const Vector3d & a_Speed); // tolua_export + void ForceSetSpeed(const Vector3d & a_Speed); // tolua_export /** Tries to move to a new position, with attachment-related checks (y == -999) */ void MoveTo(const Vector3d & a_NewPos); // tolua_export @@ -235,15 +239,15 @@ public: typedef std::list< std::string > StringList; /** Adds a player to existing group or creates a new group when it doesn't exist */ - void AddToGroup( const AString & a_GroupName ); // tolua_export + void AddToGroup( const AString & a_GroupName ); // tolua_export /** Removes a player from the group, resolves permissions and group inheritance (case sensitive) */ - void RemoveFromGroup( const AString & a_GroupName ); // tolua_export + void RemoveFromGroup( const AString & a_GroupName ); // tolua_export - bool HasPermission( const AString & a_Permission ); // tolua_export - const GroupList & GetGroups() { return m_Groups; } // >> EXPORTED IN MANUALBINDINGS << - StringList GetResolvedPermissions(); // >> EXPORTED IN MANUALBINDINGS << - bool IsInGroup( const AString & a_Group ); // tolua_export + bool HasPermission( const AString & a_Permission ); // tolua_export + const GroupList & GetGroups() { return m_Groups; } // >> EXPORTED IN MANUALBINDINGS << + StringList GetResolvedPermissions(); // >> EXPORTED IN MANUALBINDINGS << + bool IsInGroup( const AString & a_Group ); // tolua_export // tolua_begin @@ -261,13 +265,12 @@ public: void TossPickup(const cItem & a_Item); /** Heals the player by the specified amount of HPs (positive only); sends health update */ - void Heal(int a_Health); + virtual void Heal(int a_Health) override; int GetFoodLevel (void) const { return m_FoodLevel; } double GetFoodSaturationLevel (void) const { return m_FoodSaturationLevel; } int GetFoodTickTimer (void) const { return m_FoodTickTimer; } double GetFoodExhaustionLevel (void) const { return m_FoodExhaustionLevel; } - int GetFoodPoisonedTicksRemaining(void) const { return m_FoodPoisonedTicksRemaining; } /** Returns true if the player is satiated, i. e. their foodlevel is at the max and they cannot eat anymore */ bool IsSatiated(void) const { return (m_FoodLevel >= MAX_FOOD_LEVEL); } @@ -276,7 +279,6 @@ public: void SetFoodSaturationLevel (double a_FoodSaturationLevel); void SetFoodTickTimer (int a_FoodTickTimer); void SetFoodExhaustionLevel (double a_FoodExhaustionLevel); - void SetFoodPoisonedTicksRemaining(int a_FoodPoisonedTicksRemaining); /** Adds to FoodLevel and FoodSaturationLevel, returns true if any food has been consumed, false if player "full" */ bool Feed(int a_Food, double a_Saturation); @@ -287,7 +289,7 @@ public: m_FoodExhaustionLevel += a_Exhaustion; } - /** Starts the food poisoning for the specified amount of ticks; if already foodpoisoned, sets FoodPoisonedTicksRemaining to the larger of the two */ + /** Starts the food poisoning for the specified amount of ticks */ void FoodPoison(int a_NumTicks); /** Returns true if the player is currently in the process of eating the currently equipped item */ @@ -320,15 +322,20 @@ public: /** Aborts the current eating operation */ void AbortEating(void); - virtual void KilledBy(cEntity * a_Killer) override; + virtual void KilledBy(TakeDamageInfo & a_TDI) override; virtual void Killed(cEntity * a_Victim) override; - + void Respawn(void); // tolua_export void SetVisible(bool a_bVisible); // tolua_export bool IsVisible(void) const { return m_bVisible; } // tolua_export + void Respawn(void); // tolua_export + + void SetVisible( bool a_bVisible ); // tolua_export + bool IsVisible(void) const { return m_bVisible; } // tolua_export + /** Moves the player to the specified world. Returns true if successful, false on failure (world not found). */ virtual bool MoveToWorld(const AString & a_WorldName, cWorld * a_World = NULL, bool a_ShouldSendRespawn = true) override; // tolua_export @@ -337,11 +344,17 @@ public: bool SaveToDisk(void); typedef cWorld * cWorldPtr; - /** Loads player data from JSON to the object - Takes a (NULL) cWorld pointer which it will assign a value to based on either the loaded world or default world - */ + + /** Loads the player data from the disk file. +Takes a (NULL) cWorld pointer which it will assign a value to based on either the loaded world or default world + Returns true on success, false on failure. */ bool LoadFromDisk(cWorldPtr & a_World); - void LoadPermissionsFromDisk(void); // tolua_export + + /** 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; } @@ -419,7 +432,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); @@ -435,7 +448,7 @@ protected: AString m_LoadedWorldName; /** Xp Level stuff */ - enum + enum { XP_TO_LEVEL15 = 255, XP_PER_LEVEL_TO15 = 17, @@ -452,19 +465,22 @@ protected: double m_FoodSaturationLevel; /** Count-up to the healing or damaging action, based on m_FoodLevel */ - int m_FoodTickTimer; + int m_FoodTickTimer; /** A "buffer" which adds up hunger before it is substracted from m_FoodSaturationLevel or m_FoodLevel. Each action adds a little */ double m_FoodExhaustionLevel; - /** Number of ticks remaining for the foodpoisoning effect; zero if not foodpoisoned */ - int m_FoodPoisonedTicksRemaining; - float m_LastJumpHeight; 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; @@ -528,6 +544,27 @@ protected: cStatManager m_Stats; +<<<<<<< HEAD +======= + /** 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; + + +>>>>>>> master /** Sets the speed and sends it to the client, so that they are forced to move so. */ virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) override; @@ -554,15 +591,10 @@ 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; - -} ; // tolua_export + /** 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..a55c9b895 100644 --- a/src/Entities/ProjectileEntity.cpp +++ b/src/Entities/ProjectileEntity.cpp @@ -21,6 +21,8 @@ #include "FireChargeEntity.h" #include "FireworkEntity.h" #include "GhastFireballEntity.h" +#include "WitherSkullEntity.h" +#include "Player.h" @@ -33,7 +35,7 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // cProjectileTracerCallback: class cProjectileTracerCallback : @@ -67,16 +69,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; @@ -119,7 +122,7 @@ protected: -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // cProjectileEntityCollisionCallback: class cProjectileEntityCollisionCallback : @@ -140,7 +143,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 +164,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. @@ -203,13 +211,16 @@ protected: -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// // cProjectileEntity: 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 +232,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); @@ -233,7 +244,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) @@ -250,14 +261,16 @@ cProjectileEntity * cProjectileEntity::Create(eKind a_Kind, cEntity * a_Creator, case pkGhastFireball: return new cGhastFireballEntity (a_Creator, a_X, a_Y, a_Z, Speed); case pkFireCharge: return new cFireChargeEntity (a_Creator, a_X, a_Y, a_Z, Speed); case pkExpBottle: return new cExpBottleEntity (a_Creator, a_X, a_Y, a_Z, Speed); + case pkWitherSkull: return new cWitherSkullEntity (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); } } @@ -298,9 +311,9 @@ 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 pkSplashPotion: return "SplashPotion"; case pkWitherSkull: return "WitherSkull"; case pkFirework: return "Firework"; case pkFishingFloat: return ""; // Unknown, perhaps MC doesn't save this? @@ -316,8 +329,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 +349,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 +369,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..e6b05714e 100644 --- a/src/Entities/ProjectileEntity.h +++ b/src/Entities/ProjectileEntity.h @@ -46,13 +46,13 @@ 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); /// Called by the physics blocktracer when the entity hits another entity - virtual void OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) + virtual void OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) { UNUSED(a_EntityHit); UNUSED(a_HitPos); @@ -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; @@ -94,4 +120,4 @@ protected: virtual void HandlePhysics(float a_Dt, cChunk & a_Chunk) override; virtual void SpawnOn(cClientHandle & a_Client) override; -} ; // tolua_export +} ; // tolua_export diff --git a/src/Entities/SplashPotionEntity.cpp b/src/Entities/SplashPotionEntity.cpp new file mode 100644 index 000000000..3efb1f25d --- /dev/null +++ b/src/Entities/SplashPotionEntity.cpp @@ -0,0 +1,120 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "SplashPotionEntity.h" +#include "Pawn.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cSplashPotionEntityCallback: + +/** Used to distribute the splashed potion effect among nearby entities */ +class cSplashPotionCallback : + public cEntityCallback +{ +public: + /** Creates the callback. + @param a_HitPos The position where the splash potion has splashed + @param a_EntityEffectType The effect type of the potion + @param a_EntityEffect The effect description */ + cSplashPotionCallback(const Vector3d & a_HitPos, cEntityEffect::eType a_EntityEffectType, const cEntityEffect & a_EntityEffect) : + m_HitPos(a_HitPos), + m_EntityEffectType(a_EntityEffectType), + m_EntityEffect(a_EntityEffect) + { + } + + /** Called by cWorld::ForEachEntity(), adds the stored entity effect to the entity, if it is close enough. */ + virtual bool Item(cEntity * a_Entity) override + { + double SplashDistance = (a_Entity->GetPosition() - m_HitPos).Length(); + if (SplashDistance >= 20) + { + // Too far away + return false; + } + if (!a_Entity->IsPawn()) + { + // Not an entity that can take effects + return false; + } + + // y = -0.25x + 1, where x is the distance from the player. Approximation for potion splash. + // TODO: better equation + double Reduction = -0.25 * SplashDistance + 1.0; + if (Reduction < 0) + { + Reduction = 0; + } + + ((cPawn *) a_Entity)->AddEntityEffect(m_EntityEffectType, m_EntityEffect.GetDuration(), m_EntityEffect.GetIntensity(), Reduction); + return false; + } + +private: + const Vector3d & m_HitPos; + cEntityEffect::eType m_EntityEffectType; + const cEntityEffect & m_EntityEffect; +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cSplashPotionEntity: + +cSplashPotionEntity::cSplashPotionEntity( + cEntity * a_Creator, + double a_X, double a_Y, double a_Z, + const Vector3d & a_Speed, + cEntityEffect::eType a_EntityEffectType, + cEntityEffect a_EntityEffect, + int a_PotionParticleType +) : + super(pkSplashPotion, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25), + m_EntityEffectType(a_EntityEffectType), + m_EntityEffect(a_EntityEffect), + m_PotionParticleType(a_PotionParticleType) +{ + SetSpeed(a_Speed); +} + + + + + +void cSplashPotionEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) +{ + Splash(a_HitPos); + Destroy(); +} + + + + + +void cSplashPotionEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) +{ + a_EntityHit.TakeDamage(dtRangedAttack, this, 0, 1); + Splash(a_HitPos); + Destroy(true); +} + + + + + +void cSplashPotionEntity::Splash(const Vector3d & a_HitPos) +{ + cSplashPotionCallback Callback(a_HitPos, m_EntityEffectType, m_EntityEffect); + m_World->ForEachEntity(Callback); + + m_World->BroadcastSoundParticleEffect(2002, (int)a_HitPos.x, (int)a_HitPos.y, (int)a_HitPos.z, m_PotionParticleType); +} + + + + diff --git a/src/Entities/SplashPotionEntity.h b/src/Entities/SplashPotionEntity.h new file mode 100644 index 000000000..2c78b55d2 --- /dev/null +++ b/src/Entities/SplashPotionEntity.h @@ -0,0 +1,59 @@ +// +// SplashPotionEntity.h +// + +#pragma once + +#include "ProjectileEntity.h" +#include "EntityEffect.h" +#include "../World.h" +#include "Entity.h" + + + + +// tolua_begin + +class cSplashPotionEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cSplashPotionEntity); + + cSplashPotionEntity( + cEntity * a_Creator, + double a_X, double a_Y, double a_Z, + const Vector3d & a_Speed, + cEntityEffect::eType a_EntityEffectType, + cEntityEffect a_EntityEffect, + int a_PotionParticleType + ); + + cEntityEffect::eType GetEntityEffectType (void) const { return m_EntityEffectType; } + cEntityEffect GetEntityEffect (void) const { return m_EntityEffect; } + int GetPotionParticleType(void) const { return m_PotionParticleType; } + + void SetEntityEffectType(cEntityEffect::eType a_EntityEffectType) { m_EntityEffectType = a_EntityEffectType; } + void SetEntityEffect(cEntityEffect a_EntityEffect) { m_EntityEffect = a_EntityEffect; } + void SetPotionParticleType(int a_PotionParticleType) { m_PotionParticleType = a_PotionParticleType; } + +protected: + + cEntityEffect::eType m_EntityEffectType; + cEntityEffect m_EntityEffect; + int m_PotionParticleType; + + + // cProjectileEntity overrides: + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override; + virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override; + + /** Splashes the potion, fires its particle effects and sounds + @param a_HitPos The position where the potion will splash */ + void Splash(const Vector3d & a_HitPos); +} ; // tolua_export diff --git a/src/Entities/TNTEntity.h b/src/Entities/TNTEntity.h index 116f5a8cb..df61b14f5 100644 --- a/src/Entities/TNTEntity.h +++ b/src/Entities/TNTEntity.h @@ -38,7 +38,7 @@ public: protected: int m_FuseTicks; ///< How much ticks is left, while the tnt will explode -}; // tolua_export +}; // tolua_export 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..f93731256 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 +} ; // 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..549d8a3eb 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 +} ; // 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..6f3efdd7e 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 +} ; // tolua_export diff --git a/src/Entities/WitherSkullEntity.cpp b/src/Entities/WitherSkullEntity.cpp new file mode 100644 index 000000000..a7e774bba --- /dev/null +++ b/src/Entities/WitherSkullEntity.cpp @@ -0,0 +1,49 @@ + +// WitherSkullEntity.cpp + +// Implements the cWitherSkullEntity class representing the entity used by both blue and black wither skulls + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "WitherSkullEntity.h" +#include "../World.h" + + + + + +cWitherSkullEntity::cWitherSkullEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : + super(pkWitherSkull, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) +{ + SetSpeed(a_Speed); +} + + + + + +void cWitherSkullEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) +{ + // TODO: Explode + // TODO: Apply wither effect to entities nearby + Destroy(); +} + + + + + +void cWitherSkullEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) +{ + // TODO: If entity is Ender Crystal, destroy it + a_EntityHit.TakeDamage(dtRangedAttack, this, 0, 1); + + // TODO: Explode + // TODO: Apply wither effect to entity and others nearby + + Destroy(true); +} + + + + diff --git a/src/Entities/WitherSkullEntity.h b/src/Entities/WitherSkullEntity.h new file mode 100644 index 000000000..ebc1550e3 --- /dev/null +++ b/src/Entities/WitherSkullEntity.h @@ -0,0 +1,35 @@ + +// WitherSkullEntity.h + +// Declares the cWitherSkullEntity class representing the entity used by both blue and black wither skulls + +#pragma once + +#include "ProjectileEntity.h" + + + + + +// tolua_begin + +class cWitherSkullEntity : +public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cWitherSkullEntity); + + cWitherSkullEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); + +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; + +} ; // tolua_export |