diff options
Diffstat (limited to '')
-rw-r--r-- | src/Entities/ArrowEntity.cpp | 38 | ||||
-rw-r--r-- | src/Entities/ArrowEntity.h | 5 | ||||
-rw-r--r-- | src/Entities/Boat.cpp | 2 | ||||
-rw-r--r-- | src/Entities/EnderCrystal.cpp | 8 | ||||
-rw-r--r-- | src/Entities/Entity.cpp | 420 | ||||
-rw-r--r-- | src/Entities/Entity.h | 42 | ||||
-rw-r--r-- | src/Entities/EntityEffect.cpp | 99 | ||||
-rw-r--r-- | src/Entities/EntityEffect.h | 10 | ||||
-rw-r--r-- | src/Entities/ExpBottleEntity.cpp | 19 | ||||
-rw-r--r-- | src/Entities/ExpBottleEntity.h | 5 | ||||
-rw-r--r-- | src/Entities/HangingEntity.cpp | 27 | ||||
-rw-r--r-- | src/Entities/HangingEntity.h | 2 | ||||
-rw-r--r-- | src/Entities/ItemFrame.cpp | 5 | ||||
-rw-r--r-- | src/Entities/Minecart.cpp | 220 | ||||
-rw-r--r-- | src/Entities/Minecart.h | 41 | ||||
-rw-r--r-- | src/Entities/Pawn.cpp | 9 | ||||
-rw-r--r-- | src/Entities/Pickup.cpp | 8 | ||||
-rw-r--r-- | src/Entities/Player.cpp | 490 | ||||
-rw-r--r-- | src/Entities/Player.h | 106 | ||||
-rw-r--r-- | src/Entities/ProjectileEntity.cpp | 5 | ||||
-rw-r--r-- | src/Entities/ProjectileEntity.h | 6 | ||||
-rw-r--r-- | src/Entities/ThrownEggEntity.cpp | 10 | ||||
-rw-r--r-- | src/Entities/ThrownSnowballEntity.cpp | 4 |
23 files changed, 1066 insertions, 515 deletions
diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp index 913519c4c..c265c5043 100644 --- a/src/Entities/ArrowEntity.cpp +++ b/src/Entities/ArrowEntity.cpp @@ -3,7 +3,6 @@ #include "Player.h" #include "ArrowEntity.h" #include "../Chunk.h" -#include "FastRandom.h" @@ -90,6 +89,13 @@ void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFa // Broadcast arrow hit sound m_World->BroadcastSoundEffect("random.bowhit", (double)X, (double)Y, (double)Z, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + + if ((m_World->GetBlock(Hit) == E_BLOCK_TNT) && IsOnFire()) + { + m_World->SetBlock(X, Y, Z, E_BLOCK_AIR, 0); + m_World->SpawnPrimedTNT(X, Y, Z); + } + } @@ -103,8 +109,36 @@ void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) { Damage += m_World->GetTickRandomNumber(Damage / 2 + 2); } - a_EntityHit.TakeDamage(dtRangedAttack, this, Damage, 1); + + int PowerLevel = m_CreatorData.m_Enchantments.GetLevel(cEnchantments::enchPower); + if (PowerLevel > 0) + { + int ExtraDamage = (int)ceil(0.25 * (PowerLevel + 1)); + Damage += ExtraDamage; + } + + int KnockbackAmount = 1; + int PunchLevel = m_CreatorData.m_Enchantments.GetLevel(cEnchantments::enchPunch); + if (PunchLevel > 0) + { + Vector3d LookVector = GetLookVector(); + Vector3f FinalSpeed = Vector3f(0, 0, 0); + switch (PunchLevel) + { + case 1: FinalSpeed = LookVector * Vector3d(5, 0.3, 5); break; + case 2: FinalSpeed = LookVector * Vector3d(8, 0.3, 8); break; + default: break; + } + a_EntityHit.SetSpeed(FinalSpeed); + } + + a_EntityHit.TakeDamage(dtRangedAttack, this, Damage, KnockbackAmount); + if (IsOnFire() && !a_EntityHit.IsSubmerged() && !a_EntityHit.IsSwimming()) + { + a_EntityHit.StartBurning(100); + } + // Broadcast successful hit sound GetWorld()->BroadcastSoundEffect("random.successful_hit", GetPosX(), GetPosY(), GetPosZ(), 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); diff --git a/src/Entities/ArrowEntity.h b/src/Entities/ArrowEntity.h index 4bfcb1f6d..a1e7a17e7 100644 --- a/src/Entities/ArrowEntity.h +++ b/src/Entities/ArrowEntity.h @@ -10,6 +10,7 @@ + // tolua_begin class cArrowEntity : @@ -46,7 +47,7 @@ public: /// Returns the damage modifier coeff. double GetDamageCoeff(void) const { return m_DamageCoeff; } - + /// Sets the damage modifier coeff void SetDamageCoeff(double a_DamageCoeff) { m_DamageCoeff = a_DamageCoeff; } @@ -89,7 +90,7 @@ protected: /// If true, the arrow is in the process of being collected - don't go to anyone else bool m_bIsCollected; - + /// Stores the block position that arrow is lodged into, sets m_IsInGround to false if it becomes air Vector3i m_HitBlockPos; diff --git a/src/Entities/Boat.cpp b/src/Entities/Boat.cpp index 8ff8866a1..328a70846 100644 --- a/src/Entities/Boat.cpp +++ b/src/Entities/Boat.cpp @@ -62,6 +62,8 @@ bool cBoat::DoTakeDamage(TakeDamageInfo & TDI) void cBoat::OnRightClicked(cPlayer & a_Player) { + super::OnRightClicked(a_Player); + if (m_Attachee != NULL) { if (m_Attachee->GetUniqueID() == a_Player.GetUniqueID()) diff --git a/src/Entities/EnderCrystal.cpp b/src/Entities/EnderCrystal.cpp index bf86a6c42..30df2c110 100644 --- a/src/Entities/EnderCrystal.cpp +++ b/src/Entities/EnderCrystal.cpp @@ -3,8 +3,8 @@ #include "EnderCrystal.h" #include "ClientHandle.h" -#include "Player.h" #include "../Chunk.h" +#include "../World.h" @@ -32,9 +32,6 @@ void cEnderCrystal::SpawnOn(cClientHandle & a_ClientHandle) void cEnderCrystal::Tick(float a_Dt, cChunk & a_Chunk) { UNUSED(a_Dt); - - a_Chunk.SetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT, E_BLOCK_FIRE, 0); - // No further processing (physics e.t.c.) is needed } @@ -49,6 +46,9 @@ void cEnderCrystal::KilledBy(TakeDamageInfo & a_TDI) m_World->DoExplosionAt(6.0, GetPosX(), GetPosY(), GetPosZ(), true, esEnderCrystal, this); Destroy(); + + m_World->SetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT, E_BLOCK_BEDROCK, 0); + m_World->SetBlock(POSX_TOINT, POSY_TOINT + 1, POSZ_TOINT, E_BLOCK_FIRE, 0); } diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index da578013d..da85dec50 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -3,7 +3,6 @@ #include "Entity.h" #include "../World.h" -#include "../Server.h" #include "../Root.h" #include "../Matrix4.h" #include "../ClientHandle.h" @@ -13,6 +12,7 @@ #include "../Tracer.h" #include "Player.h" #include "Items/ItemHandler.h" +#include "../FastRandom.h" @@ -134,7 +134,7 @@ const char * cEntity::GetParentClass(void) const bool cEntity::Initialize(cWorld & a_World) { - if (cPluginManager::Get()->CallHookSpawningEntity(a_World, *this)) + if (cPluginManager::Get()->CallHookSpawningEntity(a_World, *this) && !IsPlayer()) { return false; } @@ -244,9 +244,9 @@ void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_R Vector3d Heading(0, 0, 0); if (a_Attacker != NULL) { - Heading = a_Attacker->GetLookVector() * (a_Attacker->IsSprinting() ? 10 : 8); + Heading = a_Attacker->GetLookVector() * (a_Attacker->IsSprinting() ? 16 : 11); + Heading.y = 1.6; } - Heading.y = 2; TDI.Knockback = Heading * a_KnockbackAmount; DoTakeDamage(TDI); @@ -259,7 +259,7 @@ void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_R void cEntity::SetYawFromSpeed(void) { const double EPS = 0.0000001; - if ((abs(m_Speed.x) < EPS) && (abs(m_Speed.z) < EPS)) + if ((std::abs(m_Speed.x) < EPS) && (std::abs(m_Speed.z) < EPS)) { // atan2() may overflow or is undefined, pick any number SetYaw(0); @@ -276,7 +276,7 @@ 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 - if ((abs(xz) < EPS) && (abs(m_Speed.y) < EPS)) + if ((std::abs(xz) < EPS) && (std::abs(m_Speed.y) < EPS)) { // atan2() may overflow or is undefined, pick any number SetPitch(0); @@ -316,6 +316,106 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) // IsOnGround() only is false if the player is moving downwards // TODO: Better damage increase, and check for enchantments (and use magic critical instead of plain) + const cEnchantments & Enchantments = Player->GetEquippedItem().m_Enchantments; + + int SharpnessLevel = Enchantments.GetLevel(cEnchantments::enchSharpness); + int SmiteLevel = Enchantments.GetLevel(cEnchantments::enchSmite); + int BaneOfArthropodsLevel = Enchantments.GetLevel(cEnchantments::enchBaneOfArthropods); + + if (SharpnessLevel > 0) + { + a_TDI.FinalDamage += (int)ceil(1.25 * SharpnessLevel); + } + else if (SmiteLevel > 0) + { + if (IsMob()) + { + cMonster * Monster = (cMonster *)this; + switch (Monster->GetMobType()) + { + case mtSkeleton: + case mtZombie: + case mtWither: + case mtZombiePigman: + { + a_TDI.FinalDamage += (int)ceil(2.5 * SmiteLevel); + break; + } + default: break; + } + } + } + else if (BaneOfArthropodsLevel > 0) + { + if (IsMob()) + { + cMonster * Monster = (cMonster *)this; + switch (Monster->GetMobType()) + { + case mtSpider: + case mtCaveSpider: + case mtSilverfish: + { + a_TDI.RawDamage += (int)ceil(2.5 * BaneOfArthropodsLevel); + // TODO: Add slowness effect + + break; + }; + default: break; + } + } + } + + int FireAspectLevel = Enchantments.GetLevel(cEnchantments::enchFireAspect); + if (FireAspectLevel > 0) + { + int BurnTicks = 3; + + if (FireAspectLevel > 1) + { + BurnTicks += 4 * (FireAspectLevel - 1); + } + if (!IsMob() && !IsSubmerged() && !IsSwimming()) + { + StartBurning(BurnTicks * 20); + } + else if (IsMob() && !IsSubmerged() && !IsSwimming()) + { + cMonster * Monster = (cMonster *)this; + switch (Monster->GetMobType()) + { + case mtGhast: + case mtZombiePigman: + case mtMagmaCube: + { + break; + }; + default: StartBurning(BurnTicks * 20); + } + } + } + + int ThornsLevel = 0; + const cItem ArmorItems[] = { GetEquippedHelmet(), GetEquippedChestplate(), GetEquippedLeggings(), GetEquippedBoots() }; + for (size_t i = 0; i < ARRAYCOUNT(ArmorItems); i++) + { + const cItem & Item = ArmorItems[i]; + ThornsLevel = std::max(ThornsLevel, Item.m_Enchantments.GetLevel(cEnchantments::enchThorns)); + } + + if (ThornsLevel > 0) + { + int Chance = ThornsLevel * 15; + + cFastRandom Random; + int RandomValue = Random.GenerateRandomInteger(0, 100); + + if (RandomValue <= Chance) + { + a_TDI.Attacker->TakeDamage(dtAttack, this, 0, Random.GenerateRandomInteger(1, 4), 0); + } + } + if (!Player->IsOnGround()) { if ((a_TDI.DamageType == dtAttack) || (a_TDI.DamageType == dtArrowAttack)) @@ -328,13 +428,123 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) Player->GetStatManager().AddValue(statDamageDealt, (StatValue)floor(a_TDI.FinalDamage * 10 + 0.5)); } + if (IsPlayer()) + { + double TotalEPF = 0.0; + double EPFProtection = 0.00; + double EPFFireProtection = 0.00; + double EPFBlastProtection = 0.00; + double EPFProjectileProtection = 0.00; + double EPFFeatherFalling = 0.00; + + const cItem ArmorItems[] = { GetEquippedHelmet(), GetEquippedChestplate(), GetEquippedLeggings(), GetEquippedBoots() }; + for (size_t i = 0; i < ARRAYCOUNT(ArmorItems); i++) + { + const cItem & Item = ArmorItems[i]; + int Level = Item.m_Enchantments.GetLevel(cEnchantments::enchProtection); + if (Level > 0) + { + EPFProtection += (6 + Level * Level) * 0.75 / 3; + } + + Level = Item.m_Enchantments.GetLevel(cEnchantments::enchFireProtection); + if (Level > 0) + { + EPFFireProtection += (6 + Level * Level) * 1.25 / 3; + } + + Level = Item.m_Enchantments.GetLevel(cEnchantments::enchFeatherFalling); + if (Level > 0) + { + EPFFeatherFalling += (6 + Level * Level) * 2.5 / 3; + } + + Level = Item.m_Enchantments.GetLevel(cEnchantments::enchBlastProtection); + if (Level > 0) + { + EPFBlastProtection += (6 + Level * Level) * 1.5 / 3; + } + + Level = Item.m_Enchantments.GetLevel(cEnchantments::enchProjectileProtection); + if (Level > 0) + { + EPFProjectileProtection += (6 + Level * Level) * 1.5 / 3; + } + + } + + TotalEPF = EPFProtection + EPFFireProtection + EPFFeatherFalling + EPFBlastProtection + EPFProjectileProtection; + + EPFProtection = EPFProtection / TotalEPF; + EPFFireProtection = EPFFireProtection / TotalEPF; + EPFFeatherFalling = EPFFeatherFalling / TotalEPF; + EPFBlastProtection = EPFBlastProtection / TotalEPF; + EPFProjectileProtection = EPFProjectileProtection / TotalEPF; + + if (TotalEPF > 25) + { + TotalEPF = 25; + } + + cFastRandom Random; + float RandomValue = Random.GenerateRandomInteger(50, 100) * 0.01f; + + TotalEPF = ceil(TotalEPF * RandomValue); + + if (TotalEPF > 20) + { + TotalEPF = 20; + } + + EPFProtection = TotalEPF * EPFProtection; + EPFFireProtection = TotalEPF * EPFFireProtection; + EPFFeatherFalling = TotalEPF * EPFFeatherFalling; + EPFBlastProtection = TotalEPF * EPFBlastProtection; + EPFProjectileProtection = TotalEPF * EPFProjectileProtection; + + int RemovedDamage = 0; + + if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtAdmin)) + { + RemovedDamage += (int)ceil(EPFProtection * 0.04 * a_TDI.FinalDamage); + } + + if ((a_TDI.DamageType == dtFalling) || (a_TDI.DamageType == dtFall) || (a_TDI.DamageType == dtEnderPearl)) + { + RemovedDamage += (int)ceil(EPFFeatherFalling * 0.04 * a_TDI.FinalDamage); + } + + if (a_TDI.DamageType == dtBurning) + { + RemovedDamage += (int)ceil(EPFFireProtection * 0.04 * a_TDI.FinalDamage); + } + + if (a_TDI.DamageType == dtExplosion) + { + RemovedDamage += (int)ceil(EPFBlastProtection * 0.04 * a_TDI.FinalDamage); + } + + if (a_TDI.DamageType == dtProjectile) + { + RemovedDamage += (int)ceil(EPFBlastProtection * 0.04 * a_TDI.FinalDamage); + } + + if (a_TDI.FinalDamage < RemovedDamage) + { + RemovedDamage = 0; + } + + a_TDI.FinalDamage -= RemovedDamage; + } + m_Health -= (short)a_TDI.FinalDamage; // TODO: Apply damage to armor m_Health = std::max(m_Health, 0); - if ((IsMob() || IsPlayer()) && (a_TDI.Attacker != NULL)) // Knockback for only players and mobs + // Add knockback: + if ((IsMob() || IsPlayer()) && (a_TDI.Attacker != NULL)) { int KnockbackLevel = a_TDI.Attacker->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchKnockback); // More common enchantment if (KnockbackLevel < 1) @@ -543,10 +753,7 @@ void cEntity::KilledBy(TakeDamageInfo & a_TDI) void cEntity::Heal(int a_HitPoints) { m_Health += a_HitPoints; - if (m_Health > m_MaxHealth) - { - m_Health = m_MaxHealth; - } + m_Health = std::min(m_Health, m_MaxHealth); } @@ -555,7 +762,7 @@ void cEntity::Heal(int a_HitPoints) void cEntity::SetHealth(int a_Health) { - m_Health = std::max(0, std::min(m_MaxHealth, a_Health)); + m_Health = Clamp(a_Health, 0, m_MaxHealth); } @@ -720,12 +927,13 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) float fallspeed; if (IsBlockWater(BlockIn)) { - fallspeed = m_Gravity * a_Dt / 3; // Fall 3x slower in water. + fallspeed = m_Gravity * a_Dt / 3; // Fall 3x slower in water + ApplyFriction(NextSpeed, 0.7, a_Dt); } else if (BlockIn == E_BLOCK_COBWEB) { NextSpeed.y *= 0.05; // Reduce overall falling speed - fallspeed = 0; // No falling. + fallspeed = 0; // No falling } else { @@ -736,20 +944,7 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) } else { - // Friction - if (NextSpeed.SqrLength() > 0.0004f) - { - NextSpeed.x *= 0.7f / (1 + a_Dt); - if (fabs(NextSpeed.x) < 0.05) - { - NextSpeed.x = 0; - } - NextSpeed.z *= 0.7f / (1 + a_Dt); - if (fabs(NextSpeed.z) < 0.05) - { - NextSpeed.z = 0; - } - } + ApplyFriction(NextSpeed, 0.7, a_Dt); } // Adjust X and Z speed for COBWEB temporary. This speed modification should be handled inside block handlers since we @@ -820,7 +1015,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.f) // Hit BLOCK_FACE_YP, we are on the ground { m_bOnGround = true; } @@ -855,6 +1050,27 @@ void cEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) +void cEntity::ApplyFriction(Vector3d & a_Speed, double a_SlowdownMultiplier, float a_Dt) +{ + if (a_Speed.SqrLength() > 0.0004f) + { + a_Speed.x *= a_SlowdownMultiplier / (1 + a_Dt); + if (fabs(a_Speed.x) < 0.05) + { + a_Speed.x = 0; + } + a_Speed.z *= a_SlowdownMultiplier / (1 + a_Dt); + if (fabs(a_Speed.z) < 0.05) + { + a_Speed.z = 0; + } + } +} + + + + + void cEntity::TickBurning(cChunk & a_Chunk) { // Remember the current burning state: @@ -1068,42 +1284,38 @@ bool cEntity::DetectPortal() } m_PortalCooldownData.m_TicksDelayed = 0; - switch (GetWorld()->GetDimension()) + if (GetWorld()->GetDimension() == dimNether) { - case dimNether: + if (GetWorld()->GetLinkedOverworldName().empty()) { - if (GetWorld()->GetLinkedOverworldName().empty()) - { - return false; - } + return false; + } - m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn + m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn - if (IsPlayer()) - { - ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimOverworld); // Send a respawn packet before world is loaded/generated so the client isn't left in limbo - } - - return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false); + if (IsPlayer()) + { + ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimOverworld); // Send a respawn packet before world is loaded/generated so the client isn't left in limbo } - case dimOverworld: + + return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false); + } + else + { + if (GetWorld()->GetNetherWorldName().empty()) { - if (GetWorld()->GetNetherWorldName().empty()) - { - return false; - } - - m_PortalCooldownData.m_ShouldPreventTeleportation = true; - - if (IsPlayer()) - { - ((cPlayer *)this)->AwardAchievement(achEnterPortal); - ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimNether); - } - - return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetNetherWorldName(), dimNether, GetWorld()->GetName()), false); + return false; } - default: return false; + + m_PortalCooldownData.m_ShouldPreventTeleportation = true; + + if (IsPlayer()) + { + ((cPlayer *)this)->AwardAchievement(achEnterPortal); + ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimNether); + } + + return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetNetherWorldName(), dimNether, GetWorld()->GetName()), false); } } case E_BLOCK_END_PORTAL: @@ -1113,45 +1325,43 @@ bool cEntity::DetectPortal() return false; } - switch (GetWorld()->GetDimension()) + if (GetWorld()->GetDimension() == dimEnd) { - case dimEnd: + + if (GetWorld()->GetLinkedOverworldName().empty()) { - if (GetWorld()->GetLinkedOverworldName().empty()) - { - return false; - } - - m_PortalCooldownData.m_ShouldPreventTeleportation = true; + return false; + } - if (IsPlayer()) - { - cPlayer * Player = (cPlayer *)this; - Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z); - Player->GetClientHandle()->SendRespawn(dimOverworld); - } + m_PortalCooldownData.m_ShouldPreventTeleportation = true; - return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false); - } - case dimOverworld: + if (IsPlayer()) { - if (GetWorld()->GetEndWorldName().empty()) - { - return false; - } + cPlayer * Player = (cPlayer *)this; + Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z); + Player->GetClientHandle()->SendRespawn(dimOverworld); + } - m_PortalCooldownData.m_ShouldPreventTeleportation = true; + return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false); + } + else + { + if (GetWorld()->GetEndWorldName().empty()) + { + return false; + } - if (IsPlayer()) - { - ((cPlayer *)this)->AwardAchievement(achEnterTheEnd); - ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimEnd); - } + m_PortalCooldownData.m_ShouldPreventTeleportation = true; - return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetEndWorldName(), dimEnd, GetWorld()->GetName()), false); + if (IsPlayer()) + { + ((cPlayer *)this)->AwardAchievement(achEnterTheEnd); + ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimEnd); } - default: return false; + + return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetEndWorldName(), dimEnd, GetWorld()->GetName()), false); } + } default: break; } @@ -1263,6 +1473,8 @@ void cEntity::HandleAir(void) // See if the entity is /submerged/ water (block above is water) // Get the type of block the entity is standing in: + int RespirationLevel = GetEquippedHelmet().m_Enchantments.GetLevel(cEnchantments::enchRespiration); + if (IsSubmerged()) { if (!IsPlayer()) // Players control themselves @@ -1270,10 +1482,15 @@ void cEntity::HandleAir(void) SetSpeedY(1); // Float in the water } - // Either reduce air level or damage player - if (m_AirLevel < 1) + if (RespirationLevel > 0) + { + ((cPawn *)this)->AddEntityEffect(cEntityEffect::effNightVision, 200, 5, 0); + } + + if (m_AirLevel <= 0) { - if (m_AirTickTimer < 1) + // Runs the air tick timer to check whether the player should be damaged + if (m_AirTickTimer <= 0) { // Damage player TakeDamage(dtDrowning, NULL, 1, 1, 0); @@ -1296,6 +1513,12 @@ void cEntity::HandleAir(void) // Set the air back to maximum m_AirLevel = MAX_AIR_LEVEL; m_AirTickTimer = DROWNING_TICKS; + + if (RespirationLevel > 0) + { + m_AirTickTimer = DROWNING_TICKS + (RespirationLevel * 15 * 20); + } + } } @@ -1558,17 +1781,10 @@ void cEntity::SetHeight(double a_Height) void cEntity::SetMass(double a_Mass) { - if (a_Mass > 0) - { - m_Mass = 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 - // less than 1g as long as is NOT equal or less than zero. - m_Mass = 0.001; - } + // 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 = std::max(a_Mass, 0.001); } @@ -1744,7 +1960,7 @@ void cEntity::SteerVehicle(float a_Forward, float a_Sideways) { return; } - if ((a_Forward != 0) || (a_Sideways != 0)) + if ((a_Forward != 0.f) || (a_Sideways != 0.f)) { m_AttachedTo->HandleSpeedFromAttachee(a_Forward, a_Sideways); } diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index e66194ca2..f0577aba2 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -27,9 +27,9 @@ return super::GetClass(); \ } -#define POSX_TOINT (int)floor(GetPosX()) -#define POSY_TOINT (int)floor(GetPosY()) -#define POSZ_TOINT (int)floor(GetPosZ()) +#define POSX_TOINT FloorC(GetPosX()) +#define POSY_TOINT FloorC(GetPosY()) +#define POSZ_TOINT FloorC(GetPosZ()) #define POS_TOINT Vector3i(POSXTOINT, POSYTOINT, POSZTOINT) #define GET_AND_VERIFY_CURRENT_CHUNK(ChunkVarName, X, Z) cChunk * ChunkVarName = a_Chunk.GetNeighborChunk(X, Z); if ((ChunkVarName == NULL) || !ChunkVarName->IsValid()) { return; } @@ -128,20 +128,20 @@ public: esFireworkExploding = 17, } ; - enum - { - FIRE_TICKS_PER_DAMAGE = 10, ///< How many ticks to wait between damaging an entity when it stands in fire - FIRE_DAMAGE = 1, ///< How much damage to deal when standing in fire - LAVA_TICKS_PER_DAMAGE = 10, ///< How many ticks to wait between damaging an entity when it stands in lava - LAVA_DAMAGE = 5, ///< How much damage to deal when standing in lava - BURN_TICKS_PER_DAMAGE = 20, ///< How many ticks to wait between damaging an entity when it is burning - BURN_DAMAGE = 1, ///< How much damage to deal when the entity is burning - BURN_TICKS = 200, ///< How long to keep an entity burning after it has stood in lava / fire - MAX_AIR_LEVEL = 300, ///< Maximum air an entity can have - DROWNING_TICKS = 20, ///< Number of ticks per heart of damage - VOID_BOUNDARY = -46, ///< At what position Y to begin applying void damage - FALL_DAMAGE_HEIGHT = 4 ///< At what position Y fall damage is applied - } ; + static const int FIRE_TICKS_PER_DAMAGE = 10; ///< Ticks to wait between damaging an entity when it stands in fire + static const int FIRE_DAMAGE = 1; ///< Damage to deal when standing in fire + static const int LAVA_TICKS_PER_DAMAGE = 10; ///< Ticks to wait between damaging an entity when it stands in lava + static const int LAVA_DAMAGE = 5; ///< Damage to deal when standing in lava + static const int BURN_TICKS_PER_DAMAGE = 20; ///< Ticks to wait between damaging an entity when it is burning + static const int BURN_DAMAGE = 1; ///< Damage to deal when the entity is burning + + static const int BURN_TICKS = 200; ///< Ticks to keep an entity burning after it has stood in lava / fire + + static const int MAX_AIR_LEVEL = 300; ///< Maximum air an entity can have + static const int DROWNING_TICKS = 20; ///< Number of ticks per heart of damage + + static const int VOID_BOUNDARY = -46; ///< Y position to begin applying void damage + static const int FALL_DAMAGE_HEIGHT = 4; ///< Y difference after which fall damage is applied cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height); virtual ~cEntity(); @@ -447,7 +447,7 @@ public: // tolua_end /// Called when the specified player right-clicks this entity - virtual void OnRightClicked(cPlayer &) {} + virtual void OnRightClicked(cPlayer & a_Player) {} /// 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) @@ -535,6 +535,12 @@ protected: virtual void Destroyed(void) {} // Called after the entity has been destroyed + /** Applies friction to an entity + @param a_Speed The speed vector to apply changes to + @param a_SlowdownMultiplier The factor to reduce the speed by + */ + static void ApplyFriction(Vector3d & a_Speed, double a_SlowdownMultiplier, float a_Dt); + /** 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 index fdcbe822e..b1ddaa30e 100644 --- a/src/Entities/EntityEffect.cpp +++ b/src/Entities/EntityEffect.cpp @@ -34,14 +34,14 @@ cEntityEffect::eType cEntityEffect::GetPotionEffectType(short a_ItemDamage) case 0x08: return cEntityEffect::effWeakness; case 0x09: return cEntityEffect::effStrength; case 0x0a: return cEntityEffect::effSlowness; + case 0x0b: return cEntityEffect::effJumpBoost; case 0x0c: return cEntityEffect::effInstantDamage; case 0x0d: return cEntityEffect::effWaterBreathing; case 0x0e: return cEntityEffect::effInvisibility; - + // No effect potions case 0x00: case 0x07: - case 0x0b: // Will be potion of leaping in 1.8 case 0x0f: { break; @@ -96,6 +96,7 @@ int cEntityEffect::GetPotionEffectDuration(short a_ItemDamage) base = 1800; break; } + default: break; } // If potion is level II, half the duration. If not, stays the same @@ -170,7 +171,7 @@ cEntityEffect::cEntityEffect(const cEntityEffect & a_OtherEffect): -cEntityEffect & cEntityEffect::operator=(cEntityEffect a_OtherEffect) +cEntityEffect & cEntityEffect::operator =(cEntityEffect a_OtherEffect) { std::swap(m_Ticks, a_OtherEffect.m_Ticks); std::swap(m_Duration, a_OtherEffect.m_Duration); @@ -233,6 +234,92 @@ void cEntityEffect::OnTick(cPawn & a_Target) //////////////////////////////////////////////////////////////////////////////// +// cEntityEffectSpeed: + +void cEntityEffectSpeed::OnActivate(cPawn & a_Target) +{ + if (a_Target.IsMob()) + { + cMonster * Mob = (cMonster*) &a_Target; + Mob->SetRelativeWalkSpeed(Mob->GetRelativeWalkSpeed() + 0.2 * m_Intensity); + } + else if (a_Target.IsPlayer()) + { + cPlayer * Player = (cPlayer*) &a_Target; + Player->SetNormalMaxSpeed(Player->GetNormalMaxSpeed() + 0.2 * m_Intensity); + Player->SetSprintingMaxSpeed(Player->GetSprintingMaxSpeed() + 0.26 * m_Intensity); + Player->SetFlyingMaxSpeed(Player->GetFlyingMaxSpeed() + 0.2 * m_Intensity); + } +} + + + + + +void cEntityEffectSpeed::OnDeactivate(cPawn & a_Target) +{ + if (a_Target.IsMob()) + { + cMonster * Mob = (cMonster*) &a_Target; + Mob->SetRelativeWalkSpeed(Mob->GetRelativeWalkSpeed() - 0.2 * m_Intensity); + } + else if (a_Target.IsPlayer()) + { + cPlayer * Player = (cPlayer*) &a_Target; + Player->SetNormalMaxSpeed(Player->GetNormalMaxSpeed() - 0.2 * m_Intensity); + Player->SetSprintingMaxSpeed(Player->GetSprintingMaxSpeed() - 0.26 * m_Intensity); + Player->SetFlyingMaxSpeed(Player->GetFlyingMaxSpeed() - 0.2 * m_Intensity); + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cEntityEffectSlowness: + +void cEntityEffectSlowness::OnActivate(cPawn & a_Target) +{ + if (a_Target.IsMob()) + { + cMonster * Mob = (cMonster*) &a_Target; + Mob->SetRelativeWalkSpeed(Mob->GetRelativeWalkSpeed() - 0.15 * m_Intensity); + } + else if (a_Target.IsPlayer()) + { + cPlayer * Player = (cPlayer*) &a_Target; + Player->SetNormalMaxSpeed(Player->GetNormalMaxSpeed() - 0.15 * m_Intensity); + Player->SetSprintingMaxSpeed(Player->GetSprintingMaxSpeed() - 0.195 * m_Intensity); + Player->SetFlyingMaxSpeed(Player->GetFlyingMaxSpeed() - 0.15 * m_Intensity); + } +} + + + + + +void cEntityEffectSlowness::OnDeactivate(cPawn & a_Target) +{ + if (a_Target.IsMob()) + { + cMonster * Mob = (cMonster*) &a_Target; + Mob->SetRelativeWalkSpeed(Mob->GetRelativeWalkSpeed() + 0.15 * m_Intensity); + } + else if (a_Target.IsPlayer()) + { + cPlayer * Player = (cPlayer*) &a_Target; + Player->SetNormalMaxSpeed(Player->GetNormalMaxSpeed() + 0.15 * m_Intensity); + Player->SetSprintingMaxSpeed(Player->GetSprintingMaxSpeed() + 0.195 * m_Intensity); + Player->SetFlyingMaxSpeed(Player->GetFlyingMaxSpeed() + 0.15 * m_Intensity); + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// // cEntityEffectInstantHealth: void cEntityEffectInstantHealth::OnActivate(cPawn & a_Target) @@ -309,7 +396,7 @@ void cEntityEffectHunger::OnTick(cPawn & 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 + Target.AddFoodExhaustion(0.025 * ((double)GetIntensity() + 1.0)); // 0.5 per second = 0.025 per tick } } @@ -349,8 +436,8 @@ void cEntityEffectPoison::OnTick(cPawn & a_Target) // Doesn't effect undead mobs, spiders if ( Target.IsUndead() || - (Target.GetMobType() == cMonster::mtSpider) || - (Target.GetMobType() == cMonster::mtCaveSpider) + (Target.GetMobType() == mtSpider) || + (Target.GetMobType() == mtCaveSpider) ) { return; diff --git a/src/Entities/EntityEffect.h b/src/Entities/EntityEffect.h index f9c1e4eb2..7cf9cd3d5 100644 --- a/src/Entities/EntityEffect.h +++ b/src/Entities/EntityEffect.h @@ -71,7 +71,7 @@ public: /** Creates an entity effect by copying another @param a_OtherEffect The other effect to copy */ - cEntityEffect & operator=(cEntityEffect a_OtherEffect); + cEntityEffect & operator =(cEntityEffect a_OtherEffect); virtual ~cEntityEffect(void) {} @@ -137,6 +137,10 @@ public: super(a_Duration, a_Intensity, a_DistanceModifier) { } + + virtual void OnActivate(cPawn & a_Target) override; + + virtual void OnDeactivate(cPawn & a_Target) override; }; @@ -152,6 +156,10 @@ public: super(a_Duration, a_Intensity, a_DistanceModifier) { } + + virtual void OnActivate(cPawn & a_Target) override; + + virtual void OnDeactivate(cPawn & a_Target) override; }; diff --git a/src/Entities/ExpBottleEntity.cpp b/src/Entities/ExpBottleEntity.cpp index 202dde942..ee142a5a2 100644 --- a/src/Entities/ExpBottleEntity.cpp +++ b/src/Entities/ExpBottleEntity.cpp @@ -19,9 +19,26 @@ cExpBottleEntity::cExpBottleEntity(cEntity * a_Creator, double a_X, double a_Y, void cExpBottleEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) { + Break(a_HitPos); +} + + + + + +void cExpBottleEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos) +{ + Break(a_HitPos); +} + + + + + +void cExpBottleEntity::Break(const Vector3d &a_HitPos) +{ // Spawn an experience orb with a reward between 3 and 11. m_World->BroadcastSoundParticleEffect(2002, POSX_TOINT, POSY_TOINT, POSZ_TOINT, 0); m_World->SpawnExperienceOrb(GetPosX(), GetPosY(), GetPosZ(), 3 + m_World->GetTickRandomNumber(8)); - Destroy(); } diff --git a/src/Entities/ExpBottleEntity.h b/src/Entities/ExpBottleEntity.h index d62a84469..d36110f97 100644 --- a/src/Entities/ExpBottleEntity.h +++ b/src/Entities/ExpBottleEntity.h @@ -29,5 +29,10 @@ 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; + + /** Breaks the bottle, fires its particle effects and sounds + @param a_HitPos The position where the bottle will break */ + void Break(const Vector3d &a_HitPos); }; // tolua_export diff --git a/src/Entities/HangingEntity.cpp b/src/Entities/HangingEntity.cpp index 32d2b226d..3276bc4a0 100644 --- a/src/Entities/HangingEntity.cpp +++ b/src/Entities/HangingEntity.cpp @@ -21,18 +21,39 @@ cHangingEntity::cHangingEntity(eEntityType a_EntityType, eBlockFace a_BlockFace, +void cHangingEntity::SetDirection(eBlockFace a_BlockFace) +{ + if ((a_BlockFace < 2) || (a_BlockFace > 5)) + { + ASSERT(!"Tried to set a bad direction!"); + return; + } + + m_BlockFace = a_BlockFace; +} + + + + + void cHangingEntity::SpawnOn(cClientHandle & a_ClientHandle) { int Dir = 0; - + // 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: Dir = 0; break; 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; + default: + { + LOGINFO("Invalid face (%d) in a cHangingEntity at {%d, %d, %d}, adjusting to BLOCK_FACE_XP.", + m_BlockFace, (int)GetPosX(), (int)GetPosY(), (int)GetPosZ() + ); + Dir = 3; + } } if ((Dir == 0) || (Dir == 2)) // Probably a client bug, but two directions are flipped and contrary to the norm, so we do -180 diff --git a/src/Entities/HangingEntity.h b/src/Entities/HangingEntity.h index 3593f9ede..1cc0034e1 100644 --- a/src/Entities/HangingEntity.h +++ b/src/Entities/HangingEntity.h @@ -24,7 +24,7 @@ public: eBlockFace GetDirection() const { return m_BlockFace; } // tolua_export /** Set the orientation from the hanging entity */ - void SetDirection(eBlockFace a_BlockFace) { m_BlockFace = a_BlockFace; } // tolua_export + void SetDirection(eBlockFace a_BlockFace); // tolua_export /** Returns the X coord. */ int GetTileX() const { return POSX_TOINT; } // tolua_export diff --git a/src/Entities/ItemFrame.cpp b/src/Entities/ItemFrame.cpp index f0b0c8c65..f512324eb 100644 --- a/src/Entities/ItemFrame.cpp +++ b/src/Entities/ItemFrame.cpp @@ -22,11 +22,13 @@ cItemFrame::cItemFrame(eBlockFace a_BlockFace, double a_X, double a_Y, double a_ void cItemFrame::OnRightClicked(cPlayer & a_Player) { + super::OnRightClicked(a_Player); + if (!m_Item.IsEmpty()) { // Item not empty, rotate, clipping values to zero to three inclusive m_Rotation++; - if (m_Rotation >= 4) + if (m_Rotation >= 8) { m_Rotation = 0; } @@ -55,7 +57,6 @@ void cItemFrame::KilledBy(TakeDamageInfo & a_TDI) { if (m_Item.IsEmpty()) { - SetHealth(0); super::KilledBy(a_TDI); Destroy(); return; diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp index d4eadc5d5..f45e7bb69 100644 --- a/src/Entities/Minecart.cpp +++ b/src/Entities/Minecart.cpp @@ -7,12 +7,12 @@ #include "Globals.h" #include "Minecart.h" -#include "../World.h" #include "../ClientHandle.h" #include "../Chunk.h" #include "Player.h" #include "../BoundingBox.h" +#define NO_SPEED 0.0 #define MAX_SPEED 8 #define MAX_SPEED_NEGATIVE -MAX_SPEED @@ -220,7 +220,7 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) 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() != NO_SPEED) // Don't do anything if cart is stationary { if (GetSpeedZ() > 0) { @@ -239,13 +239,13 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) { SetYaw(180); SetPosY(floor(GetPosY()) + 0.55); - SetSpeedY(0); - SetSpeedZ(0); + SetSpeedY(NO_SPEED); + SetSpeedZ(NO_SPEED); bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); if (EntCol || BlckCol) return; - if (GetSpeedX() != 0) + if (GetSpeedX() != NO_SPEED) { if (GetSpeedX() > 0) { @@ -305,9 +305,9 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) case E_META_RAIL_ASCEND_XM: // ASCEND EAST { SetYaw(180); - SetSpeedZ(0); + SetSpeedZ(NO_SPEED); - if (GetSpeedX() >= 0) + if (GetSpeedX() >= NO_SPEED) { if (GetSpeedX() <= MAX_SPEED) { @@ -424,9 +424,9 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); if (EntCol || BlckCol) return; - if (GetSpeedZ() != 0) + if (GetSpeedZ() != NO_SPEED) { - if (GetSpeedZ() > 0) + if (GetSpeedZ() > NO_SPEED) { AddSpeedZ(AccelDecelSpeed); } @@ -441,15 +441,15 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) { SetYaw(180); SetPosY(floor(GetPosY()) + 0.55); - SetSpeedY(0); - SetSpeedZ(0); + SetSpeedY(NO_SPEED); + SetSpeedZ(NO_SPEED); bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); if (EntCol || BlckCol) return; - if (GetSpeedX() != 0) + if (GetSpeedX() != NO_SPEED) { - if (GetSpeedX() > 0) + if (GetSpeedX() > NO_SPEED) { AddSpeedX(AccelDecelSpeed); } @@ -463,9 +463,9 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) case E_META_RAIL_ASCEND_XM: // ASCEND EAST { SetYaw(180); - SetSpeedZ(0); + SetSpeedZ(NO_SPEED); - if (GetSpeedX() >= 0) + if (GetSpeedX() >= NO_SPEED) { if (GetSpeedX() <= MAX_SPEED) { @@ -483,9 +483,9 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) case E_META_RAIL_ASCEND_XP: // ASCEND WEST { SetYaw(180); - SetSpeedZ(0); + SetSpeedZ(NO_SPEED); - if (GetSpeedX() > 0) + if (GetSpeedX() > NO_SPEED) { AddSpeedX(AccelDecelSpeed); SetSpeedY(GetSpeedX()); @@ -503,9 +503,9 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) case E_META_RAIL_ASCEND_ZM: // ASCEND NORTH { SetYaw(270); - SetSpeedX(0); + SetSpeedX(NO_SPEED); - if (GetSpeedZ() >= 0) + if (GetSpeedZ() >= NO_SPEED) { if (GetSpeedZ() <= MAX_SPEED) { @@ -523,9 +523,9 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) case E_META_RAIL_ASCEND_ZP: // ASCEND SOUTH { SetYaw(270); - SetSpeedX(0); + SetSpeedX(NO_SPEED); - if (GetSpeedZ() > 0) + if (GetSpeedZ() > NO_SPEED) { AddSpeedZ(AccelDecelSpeed); SetSpeedY(GetSpeedZ()); @@ -576,7 +576,7 @@ void cMinecart::SnapToRail(NIBBLETYPE a_RailMeta) case E_META_RAIL_ASCEND_XP: case E_META_RAIL_XM_XP: { - SetSpeedZ(0); + SetSpeedZ(NO_SPEED); SetPosZ(floor(GetPosZ()) + 0.5); break; } @@ -584,7 +584,7 @@ void cMinecart::SnapToRail(NIBBLETYPE a_RailMeta) case E_META_RAIL_ASCEND_ZP: case E_META_RAIL_ZM_ZP: { - SetSpeedX(0); + SetSpeedX(NO_SPEED); SetPosX(floor(GetPosX()) + 0.5); break; } @@ -593,12 +593,12 @@ void cMinecart::SnapToRail(NIBBLETYPE a_RailMeta) { if (GetPosZ() > floor(GetPosZ()) + 0.5) { - if (GetSpeedZ() > 0) + if (GetSpeedZ() > NO_SPEED) { SetSpeedX(-GetSpeedZ() * 0.7); } - SetSpeedZ(0); + SetSpeedZ(NO_SPEED); SetPosZ(floor(GetPosZ()) + 0.5); } else if (GetPosX() > floor(GetPosX()) + 0.5) @@ -608,82 +608,82 @@ void cMinecart::SnapToRail(NIBBLETYPE a_RailMeta) SetSpeedZ(-GetSpeedX() * 0.7); } - SetSpeedX(0); + SetSpeedX(NO_SPEED); SetPosX(floor(GetPosX()) + 0.5); } - SetSpeedY(0); + SetSpeedY(NO_SPEED); break; } case E_META_RAIL_CURVED_ZM_XP: { if (GetPosZ() > floor(GetPosZ()) + 0.5) { - if (GetSpeedZ() > 0) + if (GetSpeedZ() > NO_SPEED) { SetSpeedX(GetSpeedZ() * 0.7); } - SetSpeedZ(0); + SetSpeedZ(NO_SPEED); SetPosZ(floor(GetPosZ()) + 0.5); } else if (GetPosX() < floor(GetPosX()) + 0.5) { - if (GetSpeedX() < 0) + if (GetSpeedX() < NO_SPEED) { SetSpeedZ(GetSpeedX() * 0.7); } - SetSpeedX(0); + SetSpeedX(NO_SPEED); SetPosX(floor(GetPosX()) + 0.5); } - SetSpeedY(0); + SetSpeedY(NO_SPEED); break; } case E_META_RAIL_CURVED_ZP_XM: { if (GetPosZ() < floor(GetPosZ()) + 0.5) { - if (GetSpeedZ() < 0) + if (GetSpeedZ() < NO_SPEED) { SetSpeedX(GetSpeedZ() * 0.7); } - SetSpeedZ(0); + SetSpeedZ(NO_SPEED); SetPosZ(floor(GetPosZ()) + 0.5); } else if (GetPosX() > floor(GetPosX()) + 0.5) { - if (GetSpeedX() > 0) + if (GetSpeedX() > NO_SPEED) { SetSpeedZ(GetSpeedX() * 0.7); } - SetSpeedX(0); + SetSpeedX(NO_SPEED); SetPosX(floor(GetPosX()) + 0.5); } - SetSpeedY(0); + SetSpeedY(NO_SPEED); break; } case E_META_RAIL_CURVED_ZP_XP: { if (GetPosZ() < floor(GetPosZ()) + 0.5) { - if (GetSpeedZ() < 0) + if (GetSpeedZ() < NO_SPEED) { SetSpeedX(-GetSpeedZ() * 0.7); } - SetSpeedZ(0); + SetSpeedZ(NO_SPEED); SetPosZ(floor(GetPosZ()) + 0.5); } else if (GetPosX() < floor(GetPosX()) + 0.5) { - if (GetSpeedX() < 0) + if (GetSpeedX() < NO_SPEED) { SetSpeedZ(-GetSpeedX() * 0.7); } - SetSpeedX(0); + SetSpeedX(NO_SPEED); SetPosX(floor(GetPosX()) + 0.5); } SetSpeedY(0); @@ -871,11 +871,101 @@ bool cMinecart::TestEntityCollision(NIBBLETYPE a_RailMeta) return true; } case E_META_RAIL_CURVED_ZM_XM: + case E_META_RAIL_CURVED_ZP_XP: + { + Vector3d Distance = MinecartCollisionCallback.GetCollidedEntityPosition() - Vector3d(GetPosX(), 0, GetPosZ()); + + // Prevent division by small numbers + if (std::abs(Distance.z) < 0.001) + { + Distance.z = 0.001; + } + + /* Check to which side the minecart is to be pushed. + Let's consider a z-x-coordinate system where the minecart is the center (0/0). + The minecart moves along the line x = -z, the perpendicular line to this is x = z. + In order to decide to which side the minecart is to be pushed, it must be checked on what side of the perpendicular line the pushing entity is located. */ + if ( + ((Distance.z > 0) && ((Distance.x / Distance.z) >= 1)) || + ((Distance.z < 0) && ((Distance.x / Distance.z) <= 1)) + ) + { + // Moving -X +Z + if ((-GetSpeedX() * 0.4 / sqrt(2.0)) < 0.01) + { + // ~ SpeedX >= 0 Immobile or not moving in the "right" direction. Give it a bump! + AddSpeedX(-4 / sqrt(2.0)); + AddSpeedZ(4 / sqrt(2.0)); + } + else + { + // ~ SpeedX < 0 Moving in the "right" direction. Only accelerate it a bit. + SetSpeedX(GetSpeedX() * 0.4 / sqrt(2.0)); + SetSpeedZ(GetSpeedZ() * 0.4 / sqrt(2.0)); + } + } + else if ((GetSpeedX() * 0.4 / sqrt(2.0)) < 0.01) + { + // Moving +X -Z + // ~ SpeedX <= 0 Immobile or not moving in the "right" direction + AddSpeedX(4 / sqrt(2.0)); + AddSpeedZ(-4 / sqrt(2.0)); + } + else + { + // ~ SpeedX > 0 Moving in the "right" direction + SetSpeedX(GetSpeedX() * 0.4 / sqrt(2.0)); + SetSpeedZ(GetSpeedZ() * 0.4 / sqrt(2.0)); + } + break; + } case E_META_RAIL_CURVED_ZM_XP: case E_META_RAIL_CURVED_ZP_XM: - case E_META_RAIL_CURVED_ZP_XP: { - // TODO - simply can't be bothered right now + Vector3d Distance = MinecartCollisionCallback.GetCollidedEntityPosition() - Vector3d(GetPosX(), 0, GetPosZ()); + + // Prevent division by small numbers + if (std::abs(Distance.z) < 0.001) + { + Distance.z = 0.001; + } + + /* Check to which side the minecart is to be pushed. + Let's consider a z-x-coordinate system where the minecart is the center (0/0). + The minecart moves along the line x = z, the perpendicular line to this is x = -z. + In order to decide to which side the minecart is to be pushed, it must be checked on what side of the perpendicular line the pushing entity is located. */ + if ( + ((Distance.z > 0) && ((Distance.x / Distance.z) <= -1)) || + ((Distance.z < 0) && ((Distance.x / Distance.z) >= -1)) + ) + { + // Moving +X +Z + if ((GetSpeedX() * 0.4) < 0.01) + { + // ~ SpeedX <= 0 Immobile or not moving in the "right" direction + AddSpeedX(4 / sqrt(2.0)); + AddSpeedZ(4 / sqrt(2.0)); + } + else + { + // ~ SpeedX > 0 Moving in the "right" direction + SetSpeedX(GetSpeedX() * 0.4 / sqrt(2.0)); + SetSpeedZ(GetSpeedZ() * 0.4 / sqrt(2.0)); + } + } + else if ((-GetSpeedX() * 0.4) < 0.01) + { + // Moving -X -Z + // ~ SpeedX >= 0 Immobile or not moving in the "right" direction + AddSpeedX(-4 / sqrt(2.0)); + AddSpeedZ(-4 / sqrt(2.0)); + } + else + { + // ~ SpeedX < 0 Moving in the "right" direction + SetSpeedX(GetSpeedX() * 0.4 / sqrt(2.0)); + SetSpeedZ(GetSpeedZ() * 0.4 / sqrt(2.0)); + } break; } default: break; @@ -982,6 +1072,8 @@ cRideableMinecart::cRideableMinecart(double a_X, double a_Y, double a_Z, const c void cRideableMinecart::OnRightClicked(cPlayer & a_Player) { + super::OnRightClicked(a_Player); + if (m_Attachee != NULL) { if (m_Attachee->GetUniqueID() == a_Player.GetUniqueID()) @@ -1013,29 +1105,55 @@ void cRideableMinecart::OnRightClicked(cPlayer & a_Player) // cMinecartWithChest: cMinecartWithChest::cMinecartWithChest(double a_X, double a_Y, double a_Z) : - super(mpChest, a_X, a_Y, a_Z) + super(mpChest, a_X, a_Y, a_Z), + cEntityWindowOwner(this), + m_Contents(ContentsWidth, ContentsHeight) { + m_Contents.AddListener(*this); } -void cMinecartWithChest::SetSlot(size_t a_Idx, const cItem & a_Item) +void cMinecartWithChest::OnRightClicked(cPlayer & a_Player) { - ASSERT(a_Idx < ARRAYCOUNT(m_Items)); - - m_Items[a_Idx] = a_Item; + // If the window is not created, open it anew: + cWindow * Window = GetWindow(); + if (Window == NULL) + { + OpenNewWindow(); + Window = GetWindow(); + } + + // Open the window for the player: + if (Window != NULL) + { + if (a_Player.GetWindow() != Window) + { + a_Player.OpenWindow(Window); + } + } } -void cMinecartWithChest::OnRightClicked(cPlayer & a_Player) +void cMinecartWithChest::OpenNewWindow() +{ + OpenWindow(new cMinecartWithChestWindow(this)); +} + + + + + +void cMinecartWithChest::Destroyed() { - // Show the chest UI window to the player - // TODO + cItems Pickups; + m_Contents.CopyToItems(Pickups); + GetWorld()->SpawnItemPickups(Pickups, GetPosX(), GetPosY() + 1, GetPosZ(), 4); } diff --git a/src/Entities/Minecart.h b/src/Entities/Minecart.h index 410d3c77d..6b6ad36b5 100644 --- a/src/Entities/Minecart.h +++ b/src/Entities/Minecart.h @@ -10,6 +10,8 @@ #pragma once #include "Entity.h" +#include "World.h" +#include "../UI/WindowOwner.h" @@ -108,27 +110,46 @@ protected: class cMinecartWithChest : - public cMinecart + public cMinecart, + public cItemGrid::cListener, + public cEntityWindowOwner { typedef cMinecart super; public: CLASS_PROTODEF(cMinecartWithChest) - /// Number of item slots in the chest - static const int NumSlots = 9 * 3; - cMinecartWithChest(double a_X, double a_Y, double a_Z); + + enum + { + ContentsHeight = 3, + ContentsWidth = 9, + }; - const cItem & GetSlot(int a_Idx) const { return m_Items[a_Idx]; } - cItem & GetSlot(int a_Idx) { return m_Items[a_Idx]; } - - void SetSlot(size_t a_Idx, const cItem & a_Item); + const cItem & GetSlot(int a_Idx) const { return m_Contents.GetSlot(a_Idx); } + void SetSlot(size_t a_Idx, const cItem & a_Item) { m_Contents.SetSlot(a_Idx, a_Item); } protected: + cItemGrid m_Contents; + void OpenNewWindow(void); + virtual void Destroyed() override; - /// The chest contents: - cItem m_Items[NumSlots]; + // cItemGrid::cListener overrides: + virtual void OnSlotChanged(cItemGrid * a_Grid, int a_SlotNum) + { + UNUSED(a_SlotNum); + ASSERT(a_Grid == &m_Contents); + if (m_World != NULL) + { + if (GetWindow() != NULL) + { + GetWindow()->BroadcastWholeWindow(); + } + + m_World->MarkChunkDirty(GetChunkX(), GetChunkZ()); + } + } // cEntity overrides: virtual void OnRightClicked(cPlayer & a_Player) override; diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp index fe6c24a7a..fc8ca3d47 100644 --- a/src/Entities/Pawn.cpp +++ b/src/Entities/Pawn.cpp @@ -9,9 +9,9 @@ -cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height): - super(a_EntityType, 0, 0, 0, a_Width, a_Height), - m_EntityEffects(tEffectMap()) +cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height) : + super(a_EntityType, 0, 0, 0, a_Width, a_Height) + , m_EntityEffects(tEffectMap()) { } @@ -111,3 +111,6 @@ void cPawn::ClearEntityEffects() RemoveEntityEffect(EffectType); } } + + + diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index aab534f41..87b5bed07 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -150,10 +150,14 @@ void cPickup::Tick(float a_Dt, cChunk & a_Chunk) } } - if (!IsDestroyed() && (m_Item.m_ItemCount < m_Item.GetMaxStackSize())) // Don't combine into an already full pickup + // Try to combine the pickup with adjacent same-item pickups: + if (!IsDestroyed() && (m_Item.m_ItemCount < m_Item.GetMaxStackSize())) // Don't combine if already full { + // By using a_Chunk's ForEachEntity() instead of cWorld's, pickups don't combine across chunk boundaries. + // That is a small price to pay for not having to traverse the entire world for each entity. + // The speedup in the tick thread is quite considerable. cPickupCombiningCallback PickupCombiningCallback(GetPosition(), this); - m_World->ForEachEntity(PickupCombiningCallback); // Not ForEachEntityInChunk, otherwise pickups don't combine across chunk boundaries + a_Chunk.ForEachEntity(PickupCombiningCallback); if (PickupCombiningCallback.FoundMatchingPickup()) { m_World->BroadcastEntityMetadata(*this); diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index cf3322968..f58a0a016 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -10,13 +10,12 @@ #include "../Bindings/PluginManager.h" #include "../BlockEntities/BlockEntity.h" #include "../BlockEntities/EnderChestEntity.h" -#include "../GroupManager.h" -#include "../Group.h" #include "../Root.h" #include "../OSSupport/Timer.h" #include "../Chunk.h" #include "../Items/ItemHandler.h" #include "../Vector3.h" +#include "../FastRandom.h" #include "../WorldStorage/StatSerializer.h" #include "../CompositeChat.h" @@ -33,6 +32,15 @@ +const int cPlayer::MAX_HEALTH = 20; + +const int cPlayer::MAX_FOOD_LEVEL = 20; + +/** Number of ticks it takes to eat an item */ +const int cPlayer::EATING_TICKS = 30; + + + cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) : @@ -50,7 +58,6 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) : m_EnderChestContents(9, 3), m_CurrentWindow(NULL), m_InventoryWindow(NULL), - m_Color('-'), m_GameMode(eGameMode_NotSet), m_IP(""), m_ClientHandle(a_Client), @@ -74,7 +81,8 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) : m_Team(NULL), m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL), m_bIsTeleporting(false), - m_UUID((a_Client != NULL) ? a_Client->GetUUID() : "") + m_UUID((a_Client != NULL) ? a_Client->GetUUID() : ""), + m_CustomName("") { m_InventoryWindow = new cInventoryWindow(*this); m_CurrentWindow = m_InventoryWindow; @@ -216,16 +224,24 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) SendExperience(); } + bool CanMove = true; if (!GetPosition().EqualsEps(m_LastPos, 0.01)) // Non negligible change in position from last tick? { // Apply food exhaustion from movement: ApplyFoodExhaustionFromMovement(); - cRoot::Get()->GetPluginManager()->CallHookPlayerMoving(*this); + if (cRoot::Get()->GetPluginManager()->CallHookPlayerMoving(*this, m_LastPos, GetPosition())) + { + CanMove = false; + TeleportToCoords(m_LastPos.x, m_LastPos.y, m_LastPos.z); + } m_ClientHandle->StreamChunks(); } - BroadcastMovementUpdate(m_ClientHandle); + if (CanMove) + { + BroadcastMovementUpdate(m_ClientHandle); + } if (m_Health > 0) // make sure player is alive { @@ -251,7 +267,7 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) cTimer t1; if (m_LastPlayerListTime + PLAYER_LIST_TIME_MS <= t1.GetNowTime()) { - m_World->SendPlayerList(this); + m_World->BroadcastPlayerListUpdatePing(*this); m_LastPlayerListTime = t1.GetNowTime(); } @@ -436,6 +452,11 @@ void cPlayer::CancelChargingBow(void) void cPlayer::SetTouchGround(bool a_bTouchGround) { + if (IsGameModeSpectator()) // You can fly through the ground in Spectator + { + return; + } + m_bTouchGround = a_bTouchGround; if (!m_bTouchGround) @@ -509,7 +530,7 @@ void cPlayer::Heal(int a_Health) void cPlayer::SetFoodLevel(int a_FoodLevel) { - int FoodLevel = std::max(0, std::min(a_FoodLevel, (int)MAX_FOOD_LEVEL)); + int FoodLevel = Clamp(a_FoodLevel, 0, MAX_FOOD_LEVEL); if (cRoot::Get()->GetPluginManager()->CallHookPlayerFoodLevelChange(*this, FoodLevel)) { @@ -527,7 +548,7 @@ void cPlayer::SetFoodLevel(int a_FoodLevel) void cPlayer::SetFoodSaturationLevel(double a_FoodSaturationLevel) { - m_FoodSaturationLevel = std::max(0.0, std::min(a_FoodSaturationLevel, (double)m_FoodLevel)); + m_FoodSaturationLevel = Clamp(a_FoodSaturationLevel, 0.0, (double) m_FoodLevel); } @@ -545,7 +566,7 @@ void cPlayer::SetFoodTickTimer(int a_FoodTickTimer) void cPlayer::SetFoodExhaustionLevel(double a_FoodExhaustionLevel) { - m_FoodExhaustionLevel = std::max(0.0, std::min(a_FoodExhaustionLevel, 4.0)); + m_FoodExhaustionLevel = Clamp(a_FoodExhaustionLevel, 0.0, 40.0); } @@ -568,9 +589,12 @@ bool cPlayer::Feed(int a_Food, double a_Saturation) -void cPlayer::FoodPoison(int a_NumTicks) +void cPlayer::AddFoodExhaustion(double a_Exhaustion) { - AddEntityEffect(cEntityEffect::effHunger, a_NumTicks, 0, 1); + if (!(IsGameModeCreative() || IsGameModeSpectator())) + { + m_FoodExhaustionLevel = std::min(m_FoodExhaustionLevel + a_Exhaustion, 40.0); + } } @@ -598,7 +622,6 @@ void cPlayer::FinishEating(void) // Send the packets: m_ClientHandle->SendEntityStatus(*this, esPlayerEatingAccepted); - m_World->BroadcastEntityAnimation(*this, 0); m_World->BroadcastEntityMetadata(*this); // consume the item: @@ -610,15 +633,6 @@ void cPlayer::FinishEating(void) return; } ItemHandler->OnFoodEaten(m_World, this, &Item); - - GetInventory().RemoveOneEquippedItem(); - - // 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); - } } @@ -628,7 +642,6 @@ void cPlayer::FinishEating(void) void cPlayer::AbortEating(void) { m_EatingFinishTick = -1; - m_World->BroadcastEntityAnimation(*this, 0); m_World->BroadcastEntityMetadata(*this); } @@ -697,16 +710,13 @@ double cPlayer::GetMaxSpeed(void) const { return m_FlyingMaxSpeed; } + else if (m_IsSprinting) + { + return m_SprintingMaxSpeed; + } else { - if (m_IsSprinting) - { - return m_SprintingMaxSpeed; - } - else - { - return m_NormalMaxSpeed; - } + return m_NormalMaxSpeed; } } @@ -800,6 +810,29 @@ void cPlayer::SetCanFly(bool a_CanFly) +void cPlayer::SetCustomName(const AString & a_CustomName) +{ + if (m_CustomName == a_CustomName) + { + return; + } + + m_World->BroadcastPlayerListRemovePlayer(*this); + + m_CustomName = a_CustomName; + if (m_CustomName.length() > 16) + { + m_CustomName = m_CustomName.substr(0, 16); + } + + m_World->BroadcastPlayerListAddPlayer(*this); + m_World->BroadcastSpawnEntity(*this, GetClientHandle()); +} + + + + + void cPlayer::SetFlying(bool a_IsFlying) { if (a_IsFlying == m_IsFlying) @@ -819,9 +852,9 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) { if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtPlugin)) { - if (IsGameModeCreative()) + if (IsGameModeCreative() || IsGameModeSpectator()) { - // No damage / health in creative mode if not void or plugin damage + // No damage / health in creative or spectator mode if not void or plugin damage return false; } } @@ -877,12 +910,12 @@ void cPlayer::KilledBy(TakeDamageInfo & a_TDI) Pickups.Add(cItem(E_ITEM_RED_APPLE)); } - m_Stats.AddValue(statItemsDropped, Pickups.Size()); + m_Stats.AddValue(statItemsDropped, (StatValue)Pickups.Size()); m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10); SaveToDisk(); // Save it, yeah the world is a tough place ! - if (a_TDI.Attacker == NULL) + if ((a_TDI.Attacker == NULL) && m_World->ShouldBroadcastDeathMessages()) { AString DamageText; switch (a_TDI.DamageType) @@ -908,6 +941,10 @@ void cPlayer::KilledBy(TakeDamageInfo & a_TDI) } GetWorld()->BroadcastChatDeath(Printf("%s %s", GetName().c_str(), DamageText.c_str())); } + else if (a_TDI.Attacker == NULL) // && !m_World->ShouldBroadcastDeathMessages() by fallthrough + { + // no-op + } else if (a_TDI.Attacker->IsPlayer()) { cPlayer * Killer = (cPlayer *)a_TDI.Attacker; @@ -1035,6 +1072,14 @@ bool cPlayer::IsGameModeAdventure(void) const +bool cPlayer::IsGameModeSpectator(void) const +{ + return (m_GameMode == gmSpectator) || // Either the player is explicitly in Spectator + ((m_GameMode == gmNotSet) && m_World->IsGameModeSpectator()); // or they inherit from the world and the world is Adventure +} + + + void cPlayer::SetTeam(cTeam * a_Team) { @@ -1150,11 +1195,13 @@ void cPlayer::SetGameMode(eGameMode a_GameMode) m_GameMode = a_GameMode; m_ClientHandle->SendGameMode(a_GameMode); - if (!IsGameModeCreative()) + if (!(IsGameModeCreative() || IsGameModeSpectator())) { SetFlying(false); SetCanFly(false); } + + m_World->BroadcastPlayerListUpdateGameMode(*this); } @@ -1200,11 +1247,13 @@ unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach) } else { - // First time, announce it - cCompositeChat Msg; - Msg.SetMessageType(mtSuccess); - Msg.AddShowAchievementPart(GetName(), cStatInfo::GetName(a_Ach)); - m_World->BroadcastChat(Msg); + if (m_World->ShouldBroadcastAchievementMessages()) + { + cCompositeChat Msg; + Msg.SetMessageType(mtSuccess); + Msg.AddShowAchievementPart(GetName(), cStatInfo::GetName(a_Ach)); + m_World->BroadcastChat(Msg); + } // Increment the statistic StatValue New = m_Stats.AddValue(a_Ach); @@ -1330,6 +1379,7 @@ void cPlayer::MoveTo( const Vector3d & a_NewPos) void cPlayer::SetVisible(bool a_bVisible) { + // Need to Check if the player or other players are in gamemode spectator, but will break compatibility if (a_bVisible && !m_bVisible) // Make visible { m_bVisible = true; @@ -1346,48 +1396,6 @@ void cPlayer::SetVisible(bool a_bVisible) -void cPlayer::AddToGroup( const AString & a_GroupName) -{ - cGroup* Group = cRoot::Get()->GetGroupManager()->GetGroup( a_GroupName); - m_Groups.push_back( Group); - LOGD("Added %s to group %s", GetName().c_str(), a_GroupName.c_str()); - ResolveGroups(); - ResolvePermissions(); -} - - - - - -void cPlayer::RemoveFromGroup( const AString & a_GroupName) -{ - bool bRemoved = false; - for (GroupList::iterator itr = m_Groups.begin(); itr != m_Groups.end(); ++itr) - { - if ((*itr)->GetName().compare(a_GroupName) == 0) - { - m_Groups.erase( itr); - bRemoved = true; - break; - } - } - - if (bRemoved) - { - LOGD("Removed %s from group %s", GetName().c_str(), a_GroupName.c_str()); - ResolveGroups(); - ResolvePermissions(); - } - else - { - LOGWARN("Tried to remove %s from group %s but was not in that group", GetName().c_str(), a_GroupName.c_str()); - } -} - - - - - bool cPlayer::HasPermission(const AString & a_Permission) { if (a_Permission.empty()) @@ -1396,33 +1404,18 @@ bool cPlayer::HasPermission(const AString & a_Permission) return true; } - AStringVector Split = StringSplit( a_Permission, "."); - PermissionMap Possibilities = m_ResolvedPermissions; - // Now search the namespaces - while (Possibilities.begin() != Possibilities.end()) + AStringVector Split = StringSplit(a_Permission, "."); + + // Iterate over all granted permissions; if any matches, then return success: + for (AStringVectorVector::const_iterator itr = m_SplitPermissions.begin(), end = m_SplitPermissions.end(); itr != end; ++itr) { - PermissionMap::iterator itr = Possibilities.begin(); - if (itr->second) + if (PermissionMatches(Split, *itr)) { - AStringVector OtherSplit = StringSplit( itr->first, "."); - if (OtherSplit.size() <= Split.size()) - { - unsigned int i; - for (i = 0; i < OtherSplit.size(); ++i) - { - if (OtherSplit[i].compare( Split[i]) != 0) - { - if (OtherSplit[i].compare("*") == 0) return true; // WildCard man!! WildCard! - break; - } - } - if (i == Split.size()) return true; - } + return true; } - Possibilities.erase( itr); - } + } // for itr - m_SplitPermissions[] - // Nothing that matched :( + // No granted permission matches return false; } @@ -1430,13 +1423,34 @@ bool cPlayer::HasPermission(const AString & a_Permission) -bool cPlayer::IsInGroup( const AString & a_Group) +bool cPlayer::PermissionMatches(const AStringVector & a_Permission, const AStringVector & a_Template) { - for (GroupList::iterator itr = m_ResolvedGroups.begin(); itr != m_ResolvedGroups.end(); ++itr) + // Check the sub-items if they are the same or there's a wildcard: + size_t lenP = a_Permission.size(); + size_t lenT = a_Template.size(); + size_t minLen = std::min(lenP, lenT); + for (size_t i = 0; i < minLen; i++) { - if (a_Group.compare( (*itr)->GetName().c_str()) == 0) + if (a_Template[i] == "*") + { + // Has matched so far and now there's a wildcard in the template, so the permission matches: return true; + } + if (a_Permission[i] != a_Template[i]) + { + // Found a mismatch + return false; + } + } + + // So far all the sub-items have matched + // If the sub-item count is the same, then the permission matches: + if (lenP == lenT) + { + return true; } + + // There are more sub-items in either the permission or the template, not a match: return false; } @@ -1444,87 +1458,38 @@ bool cPlayer::IsInGroup( const AString & a_Group) -void cPlayer::ResolvePermissions() +AString cPlayer::GetColor(void) const { - 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) + if (m_MsgNameColorCode.empty() || (m_MsgNameColorCode == "-")) { - m_ResolvedPermissions[ itr->first ] = itr->second; + // Color has not been assigned, return an empty string: + return AString(); } - for (GroupList::iterator GroupItr = m_ResolvedGroups.begin(); GroupItr != m_ResolvedGroups.end(); ++GroupItr) - { - const cGroup::PermissionMap & Permissions = (*GroupItr)->GetPermissions(); - for (cGroup::PermissionMap::const_iterator itr = Permissions.begin(); itr != Permissions.end(); ++itr) - { - m_ResolvedPermissions[ itr->first ] = itr->second; - } - } + // Return the color, including the delimiter: + return cChatColor::Delimiter + m_MsgNameColorCode; } -void cPlayer::ResolveGroups() +AString cPlayer::GetPlayerListName(void) const { - // Clear resolved groups first - m_ResolvedGroups.clear(); + const AString & Color = GetColor(); - // 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 - GroupList ToIterate; - for (GroupList::iterator GroupItr = m_Groups.begin(); GroupItr != m_Groups.end(); ++GroupItr) + if (HasCustomName()) { - ToIterate.push_back( *GroupItr); + return m_CustomName; } - while (ToIterate.begin() != ToIterate.end()) + else if ((GetName().length() <= 14) && !Color.empty()) { - cGroup* CurrentGroup = *ToIterate.begin(); - if (AllGroups.find( CurrentGroup) != AllGroups.end()) - { - LOGWARNING("ERROR: Player \"%s\" is in the group multiple times (\"%s\"). Please fix your settings in users.ini!", - GetName().c_str(), CurrentGroup->GetName().c_str() - ); - } - else - { - AllGroups[ CurrentGroup ] = true; - 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) - { - if (AllGroups.find( *itr) != AllGroups.end()) - { - LOGERROR("ERROR: Player %s is in the same group multiple times due to inheritance (%s). FIX IT!", GetName().c_str(), (*itr)->GetName().c_str()); - continue; - } - ToIterate.push_back( *itr); - } - } - ToIterate.erase( ToIterate.begin()); - } -} - - - - - -AString cPlayer::GetColor(void) const -{ - if (m_Color != '-') - { - return cChatColor::Delimiter + m_Color; + return Printf("%s%s", Color.c_str(), GetName().c_str()); } - - if (m_Groups.size() < 1) + else { - return cChatColor::White; + return GetName(); } - - return (*m_Groups.begin())->GetColor(); } @@ -1597,7 +1562,12 @@ void cPlayer::TossPickup(const cItem & a_Item) void cPlayer::TossItems(const cItems & a_Items) { - m_Stats.AddValue(statItemsDropped, a_Items.Size()); + if (IsGameModeSpectator()) // Players can't toss items in spectator + { + return; + } + + m_Stats.AddValue(statItemsDropped, (StatValue)a_Items.Size()); double vX = 0, vY = 0, vZ = 0; EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); @@ -1640,48 +1610,9 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) -void cPlayer::LoadPermissionsFromDisk() -{ - m_Groups.clear(); - m_Permissions.clear(); - - cIniFile IniFile; - if (IniFile.ReadFile("users.ini")) - { - AString Groups = IniFile.GetValueSet(GetName(), "Groups", "Default"); - AStringVector Split = StringSplitAndTrim(Groups, ","); - - for (AStringVector::const_iterator itr = Split.begin(), end = Split.end(); itr != end; ++itr) - { - if (!cRoot::Get()->GetGroupManager()->ExistsGroup(*itr)) - { - LOGWARNING("The group %s for player %s was not found!", itr->c_str(), GetName().c_str()); - } - AddToGroup(*itr); - } - - AString Color = IniFile.GetValue(GetName(), "Color", "-"); - if (!Color.empty()) - { - m_Color = Color[0]; - } - } - else - { - cGroupManager::GenerateDefaultUsersIni(IniFile); - IniFile.AddValue("Groups", GetName(), "Default"); - AddToGroup("Default"); - } - IniFile.WriteFile("users.ini"); - ResolvePermissions(); -} - - - - bool cPlayer::LoadFromDisk(cWorldPtr & a_World) { - LoadPermissionsFromDisk(); + LoadRank(); // Load from the UUID file: if (LoadFromFile(GetUUIDFileName(m_UUID), a_World)) @@ -1691,8 +1622,10 @@ bool cPlayer::LoadFromDisk(cWorldPtr & a_World) // Load from the offline UUID file, if allowed: AString OfflineUUID = cClientHandle::GenerateOfflineUUID(GetName()); + const char * OfflineUsage = " (unused)"; if (cRoot::Get()->GetServer()->ShouldLoadOfflinePlayerData()) { + OfflineUsage = ""; if (LoadFromFile(GetUUIDFileName(OfflineUUID), a_World)) { return true; @@ -1715,8 +1648,8 @@ bool cPlayer::LoadFromDisk(cWorldPtr & a_World) } // 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() + LOG("Player data file not found for %s (%s, offline %s%s), will be reset to defaults.", + GetName().c_str(), m_UUID.c_str(), OfflineUUID.c_str(), OfflineUsage ); if (a_World == NULL) @@ -1795,7 +1728,11 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents); m_LoadedWorldName = root.get("world", "world").asString(); - a_World = cRoot::Get()->GetWorld(GetLoadedWorldName(), true); + a_World = cRoot::Get()->GetWorld(GetLoadedWorldName(), false); + if (a_World == NULL) + { + a_World = cRoot::Get()->GetDefaultWorld(); + } m_LastBedPos.x = root.get("SpawnX", a_World->GetSpawnX()).asInt(); m_LastBedPos.y = root.get("SpawnY", a_World->GetSpawnY()).asInt(); @@ -1819,6 +1756,7 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) bool cPlayer::SaveToDisk() { + cFile::CreateFolder(FILE_IO_PREFIX + AString("players/")); // Create the "players" folder, if it doesn't exist yet (#1268) cFile::CreateFolder(FILE_IO_PREFIX + AString("players/") + m_UUID.substr(0, 2)); // create the JSON data @@ -1913,31 +1851,33 @@ bool cPlayer::SaveToDisk() -cPlayer::StringList cPlayer::GetResolvedPermissions() +void cPlayer::UseEquippedItem(int a_Amount) { - StringList Permissions; + if (IsGameModeCreative() || IsGameModeSpectator()) // No damage in creative or spectator + { + return; + } - const PermissionMap& ResolvedPermissions = m_ResolvedPermissions; - for (PermissionMap::const_iterator itr = ResolvedPermissions.begin(); itr != ResolvedPermissions.end(); ++itr) + // If the item has an unbreaking enchantment, give it a random chance of not breaking: + cItem Item = GetEquippedItem(); + int UnbreakingLevel = Item.m_Enchantments.GetLevel(cEnchantments::enchUnbreaking); + if (UnbreakingLevel > 0) { - if (itr->second) + int chance; + if (ItemCategory::IsArmor(Item.m_ItemType)) { - Permissions.push_back( itr->first); + chance = 60 + (40 / (UnbreakingLevel + 1)); + } + else + { + chance = 100 / (UnbreakingLevel + 1); } - } - - return Permissions; -} - - - - -void cPlayer::UseEquippedItem(int a_Amount) -{ - if (IsGameModeCreative()) // No damage in creative - { - return; + cFastRandom Random; + if (Random.NextInt(101) <= chance) + { + return; + } } if (GetInventory().DamageEquippedItem(a_Amount)) @@ -1977,19 +1917,34 @@ void cPlayer::HandleFood(void) return; } + // Apply food exhaustion that has accumulated: + if (m_FoodExhaustionLevel > 4.0) + { + m_FoodExhaustionLevel -= 4.0; + + if (m_FoodSaturationLevel > 0.0) + { + m_FoodSaturationLevel = std::max(m_FoodSaturationLevel - 1.0, 0.0); + } + else + { + SetFoodLevel(m_FoodLevel - 1); + } + } + // Heal or damage, based on the food level, using the m_FoodTickTimer: - if ((m_FoodLevel > 17) || (m_FoodLevel <= 0)) + if ((m_FoodLevel >= 18) || (m_FoodLevel <= 0)) { m_FoodTickTimer++; if (m_FoodTickTimer >= 80) { m_FoodTickTimer = 0; - if ((m_FoodLevel > 17) && (GetHealth() < GetMaxHealth())) + if ((m_FoodLevel >= 18) && (GetHealth() < GetMaxHealth())) { // Regenerate health from food, incur 3 pts of food exhaustion: Heal(1); - m_FoodExhaustionLevel += 3.0; + AddFoodExhaustion(3.0); } else if ((m_FoodLevel <= 0) && (m_Health > 1)) { @@ -1998,20 +1953,9 @@ void cPlayer::HandleFood(void) } } } - - // Apply food exhaustion that has accumulated: - if (m_FoodExhaustionLevel >= 4.0) + else { - m_FoodExhaustionLevel -= 4.0; - - if (m_FoodSaturationLevel >= 1.0) - { - m_FoodSaturationLevel -= 1.0; - } - else - { - SetFoodLevel(m_FoodLevel - 1); - } + m_FoodTickTimer = 0; } } @@ -2086,14 +2030,17 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos) else if (IsSubmerged()) { m_Stats.AddValue(statDistDove, Value); + AddFoodExhaustion(0.00015 * (double)Value); } else if (IsSwimming()) { m_Stats.AddValue(statDistSwum, Value); + AddFoodExhaustion(0.00015 * (double)Value); } else if (IsOnGround()) { m_Stats.AddValue(statDistWalked, Value); + AddFoodExhaustion((m_IsSprinting ? 0.001 : 0.0001) * (double)Value); } else { @@ -2114,8 +2061,8 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos) cMonster * Monster = (cMonster *)m_AttachedTo; switch (Monster->GetMobType()) { - case cMonster::mtPig: m_Stats.AddValue(statDistPig, Value); break; - case cMonster::mtHorse: m_Stats.AddValue(statDistHorse, Value); break; + case mtPig: m_Stats.AddValue(statDistPig, Value); break; + case mtHorse: m_Stats.AddValue(statDistHorse, Value); break; default: break; } break; @@ -2184,6 +2131,36 @@ void cPlayer::ApplyFoodExhaustionFromMovement() +void cPlayer::LoadRank(void) +{ + // Load the values from cRankManager: + cRankManager & RankMgr = cRoot::Get()->GetRankManager(); + m_Rank = RankMgr.GetPlayerRankName(m_UUID); + if (m_Rank.empty()) + { + m_Rank = RankMgr.GetDefaultRank(); + } + else + { + // Update the name: + RankMgr.UpdatePlayerName(m_UUID, m_PlayerName); + } + m_Permissions = RankMgr.GetPlayerPermissions(m_UUID); + RankMgr.GetRankVisuals(m_Rank, m_MsgPrefix, m_MsgSuffix, m_MsgNameColorCode); + + // Break up the individual permissions on each dot, into m_SplitPermissions: + m_SplitPermissions.clear(); + m_SplitPermissions.reserve(m_Permissions.size()); + for (AStringVector::const_iterator itr = m_Permissions.begin(), end = m_Permissions.end(); itr != end; ++itr) + { + m_SplitPermissions.push_back(StringSplit(*itr, ".")); + } // for itr - m_Permissions[] +} + + + + + void cPlayer::Detach() { super::Detach(); @@ -2216,12 +2193,13 @@ void cPlayer::Detach() AString cPlayer::GetUUIDFileName(const AString & a_UUID) { - ASSERT(a_UUID.size() == 36); + AString UUID = cMojangAPI::MakeUUIDDashed(a_UUID); + ASSERT(UUID.length() == 36); AString res("players/"); - res.append(a_UUID, 0, 2); + res.append(UUID, 0, 2); res.push_back('/'); - res.append(a_UUID, 2, AString::npos); + res.append(UUID, 2, AString::npos); res.append(".json"); return res; } diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 65c1e33a8..22d6a2ae2 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -13,7 +13,6 @@ -class cGroup; class cWindow; class cClientHandle; class cTeam; @@ -29,12 +28,13 @@ class cPlayer : typedef cPawn super; public: - enum - { - MAX_HEALTH = 20, - MAX_FOOD_LEVEL = 20, - EATING_TICKS = 30, ///< Number of ticks it takes to eat an item - } ; + static const int MAX_HEALTH; + + static const int MAX_FOOD_LEVEL; + + /** Number of ticks it takes to eat an item */ + static const int EATING_TICKS; + // tolua_end CLASS_PROTODEF(cPlayer) @@ -171,6 +171,9 @@ public: /** Returns true if the player is in Adventure mode, either explicitly, or by inheriting from current world */ bool IsGameModeAdventure(void) const; + /** Returns true if the player is in Spectator mode, either explicitly, or by inheriting from current world */ + bool IsGameModeSpectator(void) const; + AString GetIP(void) const { return m_IP; } // tolua_export /** Returns the associated team, NULL if none */ @@ -179,11 +182,11 @@ public: /** Sets the player team, NULL if none */ void SetTeam(cTeam * a_Team); + // tolua_end + /** Forces the player to query the scoreboard for his team */ cTeam * UpdateTeam(void); - // tolua_end - /** Return the associated statistic and achievement manager. */ cStatManager & GetStatManager() { return m_Stats; } @@ -235,26 +238,25 @@ public: // tolua_end - typedef std::list< cGroup* > GroupList; - typedef std::list< std::string > StringList; + bool HasPermission(const AString & a_Permission); // tolua_export - /** Adds a player to existing group or creates a new group when it doesn't exist */ - 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 - - 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 + /** Returns true iff a_Permission matches the a_Template. + A match is defined by either being exactly the same, or each sub-item matches until there's a wildcard in a_Template. + Ie. {"a", "b", "c"} matches {"a", "b", "*"} but doesn't match {"a", "b"} */ + static bool PermissionMatches(const AStringVector & a_Permission, const AStringVector & a_Template); // Exported in ManualBindings with AString params + + /** Returns all the permissions that the player has assigned to them. */ + const AStringVector & GetPermissions(void) { return m_Permissions; } // Exported in ManualBindings.cpp // tolua_begin - /** Returns the full color code to use for this player, based on their primary group or set in m_Color. - The returned value includes the cChatColor::Delimiter. */ + /** Returns the full color code to use for this player, based on their rank. + The returned value either is empty, or includes the cChatColor::Delimiter. */ AString GetColor(void) const; + /** Returns the name that is used in the playerlist. */ + AString GetPlayerListName(void) const; + /** tosses the item in the selected hotbar slot */ void TossEquippedItem(char a_Amount = 1); @@ -284,13 +286,7 @@ public: bool Feed(int a_Food, double a_Saturation); /** Adds the specified exhaustion to m_FoodExhaustion. Expects only positive values. */ - void AddFoodExhaustion(double a_Exhaustion) - { - m_FoodExhaustionLevel += a_Exhaustion; - } - - /** Starts the food poisoning for the specified amount of ticks */ - void FoodPoison(int a_NumTicks); + void AddFoodExhaustion(double a_Exhaustion); /** Returns true if the player is currently in the process of eating the currently equipped item */ bool IsEating(void) const { return (m_EatingFinishTick >= 0); } @@ -352,8 +348,6 @@ public: */ bool LoadFromFile(const AString & a_FileName, cWorldPtr & a_World); - void LoadPermissionsFromDisk(void); // tolua_export - const AString & GetLoadedWorldName() { return m_LoadedWorldName; } void UseEquippedItem(int a_Amount = 1); @@ -410,6 +404,16 @@ public: /** If true the player can fly even when he's not in creative. */ void SetCanFly(bool a_CanFly); + /** Returns true if the player has a custom name. */ + bool HasCustomName(void) const { return !m_CustomName.empty(); } + + /** Returns the custom name of this player. If the player hasn't a custom name, it will return an empty string. */ + const AString & GetCustomName(void) const { return m_CustomName; } + + /** Sets the custom name of this player. If you want to disable the custom name, simply set an empty string. + The custom name will be used in the tab-list, in the player nametag and in the tab-completion. */ + void SetCustomName(const AString & a_CustomName); + /** Gets the last position that the player slept in This is initialised to the world spawn point if the player has not slept in a bed as of yet */ @@ -417,12 +421,24 @@ public: /** Sets the player's bed (home) position */ void SetBedPos(const Vector3i & a_Pos) { m_LastBedPos = a_Pos; } + + // tolua_end /** Update movement-related statistics. */ void UpdateMovementStats(const Vector3d & a_DeltaPos); + + // tolua_begin /** Returns wheter the player can fly or not. */ virtual bool CanFly(void) const { return m_CanFly; } + + /** Returns the UUID (short format) that has been read from the client, or empty string if not available. */ + const AString & GetUUID(void) const { return m_UUID; } + + /** (Re)loads the rank and permissions from the cRankManager. + Expects the m_UUID member to be valid. + Loads the m_Rank, m_Permissions, m_MsgPrefix, m_MsgSuffix and m_MsgNameColorCode members. */ + void LoadRank(void); // tolua_end @@ -434,12 +450,22 @@ public: virtual void Detach(void); protected: - typedef std::map< std::string, bool > PermissionMap; - PermissionMap m_ResolvedPermissions; - PermissionMap m_Permissions; - GroupList m_ResolvedGroups; - GroupList m_Groups; + typedef std::vector<std::vector<AString> > AStringVectorVector; + + /** The name of the rank assigned to this player. */ + AString m_Rank; + + /** All the permissions that this player has, based on their rank. */ + AStringVector m_Permissions; + + /** All the permissions that this player has, based on their rank, split into individual dot-delimited parts. + This is used mainly by the HasPermission() function to optimize the lookup. */ + AStringVectorVector m_SplitPermissions; + + // Message visuals: + AString m_MsgPrefix, m_MsgSuffix; + AString m_MsgNameColorCode; AString m_PlayerName; AString m_LoadedWorldName; @@ -484,8 +510,6 @@ protected: /** The player's last saved bed position */ Vector3i m_LastBedPos; - char m_Color; - eGameMode m_GameMode; AString m_IP; @@ -554,10 +578,12 @@ protected: */ bool m_bIsTeleporting; - /** The UUID of the player, as read from the ClientHandle. + /** The short UUID (no dashes) of the player, as read from the ClientHandle. If no ClientHandle is given, the UUID is initialized to empty. */ AString m_UUID; + AString m_CustomName; + /** 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; diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp index 43023ec28..acc9bd674 100644 --- a/src/Entities/ProjectileEntity.cpp +++ b/src/Entities/ProjectileEntity.cpp @@ -222,7 +222,8 @@ cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, double a m_ProjectileKind(a_Kind), m_CreatorData( ((a_Creator != NULL) ? a_Creator->GetUniqueID() : -1), - ((a_Creator != NULL) ? (a_Creator->IsPlayer() ? ((cPlayer *)a_Creator)->GetName() : "") : "") + ((a_Creator != NULL) ? (a_Creator->IsPlayer() ? ((cPlayer *)a_Creator)->GetName() : "") : ""), + ((a_Creator != NULL) ? a_Creator->GetEquippedWeapon().m_Enchantments : cEnchantments()) ), m_IsInGround(false) { @@ -235,7 +236,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_CreatorData(a_Creator->GetUniqueID(), a_Creator->IsPlayer() ? ((cPlayer *)a_Creator)->GetName() : ""), + m_CreatorData(a_Creator->GetUniqueID(), a_Creator->IsPlayer() ? ((cPlayer *)a_Creator)->GetName() : "", a_Creator->GetEquippedWeapon().m_Enchantments), m_IsInGround(false) { SetSpeed(a_Speed); diff --git a/src/Entities/ProjectileEntity.h b/src/Entities/ProjectileEntity.h index 0ebc32f36..990136a32 100644 --- a/src/Entities/ProjectileEntity.h +++ b/src/Entities/ProjectileEntity.h @@ -94,14 +94,16 @@ protected: */ struct CreatorData { - CreatorData(int a_UniqueID, const AString & a_Name) : + CreatorData(int a_UniqueID, const AString & a_Name, const cEnchantments & a_Enchantments) : m_UniqueID(a_UniqueID), - m_Name(a_Name) + m_Name(a_Name), + m_Enchantments(a_Enchantments) { } const int m_UniqueID; AString m_Name; + cEnchantments m_Enchantments; }; /** The type of projectile I am */ diff --git a/src/Entities/ThrownEggEntity.cpp b/src/Entities/ThrownEggEntity.cpp index 456083108..5ae85bee8 100644 --- a/src/Entities/ThrownEggEntity.cpp +++ b/src/Entities/ThrownEggEntity.cpp @@ -48,13 +48,13 @@ void cThrownEggEntity::TrySpawnChicken(const Vector3d & a_HitPos) { if (m_World->GetTickRandomNumber(7) == 1) { - m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, mtChicken); } else if (m_World->GetTickRandomNumber(32) == 1) { - m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); - m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); - m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); - m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, cMonster::mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, mtChicken); + m_World->SpawnMob(a_HitPos.x, a_HitPos.y, a_HitPos.z, mtChicken); } } diff --git a/src/Entities/ThrownSnowballEntity.cpp b/src/Entities/ThrownSnowballEntity.cpp index d94e75898..496397100 100644 --- a/src/Entities/ThrownSnowballEntity.cpp +++ b/src/Entities/ThrownSnowballEntity.cpp @@ -32,8 +32,8 @@ void cThrownSnowballEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & int TotalDamage = 0; if (a_EntityHit.IsMob()) { - cMonster::eType MobType = ((cMonster &) a_EntityHit).GetMobType(); - if (MobType == cMonster::mtBlaze) + eMonsterType MobType = ((cMonster &) a_EntityHit).GetMobType(); + if (MobType == mtBlaze) { TotalDamage = 3; } |