summaryrefslogtreecommitdiffstats
path: root/src/Entities
diff options
context:
space:
mode:
authorAlexander Harkness <bearbin@gmail.com>2014-10-11 19:32:21 +0200
committerAlexander Harkness <bearbin@gmail.com>2014-10-11 19:32:21 +0200
commit93833069a80fe4aec33a95148df39ad40671ddaf (patch)
tree2e6a335a60e618b5fe456ca8a586a2c5448c8c87 /src/Entities
parentReverted submodule changes. (diff)
parentMerge pull request #1528 from kjanku1/master (diff)
downloadcuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar
cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar.gz
cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar.bz2
cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar.lz
cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar.xz
cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar.zst
cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.zip
Diffstat (limited to 'src/Entities')
-rw-r--r--src/Entities/ArrowEntity.cpp38
-rw-r--r--src/Entities/ArrowEntity.h5
-rw-r--r--src/Entities/Boat.cpp2
-rw-r--r--src/Entities/EnderCrystal.cpp8
-rw-r--r--src/Entities/Entity.cpp420
-rw-r--r--src/Entities/Entity.h42
-rw-r--r--src/Entities/EntityEffect.cpp99
-rw-r--r--src/Entities/EntityEffect.h10
-rw-r--r--src/Entities/ExpBottleEntity.cpp19
-rw-r--r--src/Entities/ExpBottleEntity.h5
-rw-r--r--src/Entities/HangingEntity.cpp27
-rw-r--r--src/Entities/HangingEntity.h2
-rw-r--r--src/Entities/ItemFrame.cpp5
-rw-r--r--src/Entities/Minecart.cpp220
-rw-r--r--src/Entities/Minecart.h41
-rw-r--r--src/Entities/Pawn.cpp9
-rw-r--r--src/Entities/Pickup.cpp8
-rw-r--r--src/Entities/Player.cpp490
-rw-r--r--src/Entities/Player.h106
-rw-r--r--src/Entities/ProjectileEntity.cpp5
-rw-r--r--src/Entities/ProjectileEntity.h6
-rw-r--r--src/Entities/ThrownEggEntity.cpp10
-rw-r--r--src/Entities/ThrownSnowballEntity.cpp4
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;
}