1
0
Fork 0
cuberite-2a/src/Entities/Entity.cpp

2302 lines
50 KiB
C++
Raw Normal View History

2014-04-24 21:03:47 +00:00
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Entity.h"
#include "../World.h"
#include "../Root.h"
2014-03-11 16:32:33 +00:00
#include "../Matrix4.h"
#include "../ClientHandle.h"
#include "../Chunk.h"
#include "../Simulator/FluidSimulator.h"
#include "../Bindings/PluginManager.h"
#include "../LineBlockTracer.h"
2013-12-22 20:04:17 +00:00
#include "Player.h"
2014-07-23 14:32:09 +00:00
#include "Items/ItemHandler.h"
#include "../FastRandom.h"
#include "../NetherPortalScanner.h"
#include "../BoundingBox.h"
static UInt32 GetNextUniqueID(void)
{
static std::atomic<UInt32> counter(1);
return counter.fetch_add(1);
}
////////////////////////////////////////////////////////////////////////////////
// cEntity:
2015-03-21 14:18:17 +00:00
cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height):
m_UniqueID(GetNextUniqueID()),
2015-03-21 14:18:17 +00:00
m_Health(1),
m_MaxHealth(1),
m_AttachedTo(nullptr),
m_Attachee(nullptr),
m_bDirtyHead(true),
m_bDirtyOrientation(true),
m_bHasSentNoSpeed(true),
m_bOnGround(false),
m_Gravity(-9.81f),
m_AirDrag(0.02f),
2015-07-21 20:25:37 +00:00
m_LastPosition(a_X, a_Y, a_Z),
2015-03-21 14:18:17 +00:00
m_EntityType(a_EntityType),
m_World(nullptr),
m_IsWorldChangeScheduled(false),
2015-03-21 14:18:17 +00:00
m_IsFireproof(false),
m_TicksSinceLastBurnDamage(0),
m_TicksSinceLastLavaDamage(0),
m_TicksSinceLastFireDamage(0),
m_TicksLeftBurning(0),
m_TicksSinceLastVoidDamage(0),
m_IsInFire(false),
m_IsInLava(false),
m_IsInWater(false),
m_IsHeadInWater(false),
m_AirLevel(MAX_AIR_LEVEL),
m_AirTickTimer(DROWNING_TICKS),
2015-03-21 14:18:17 +00:00
m_TicksAlive(0),
m_IsTicking(false),
m_ParentChunk(nullptr),
2015-03-21 14:18:17 +00:00
m_HeadYaw(0.0),
m_Rot(0.0, 0.0, 0.0),
2015-07-21 20:25:37 +00:00
m_Position(a_X, a_Y, a_Z),
m_LastSentPosition(a_X, a_Y, a_Z),
2015-03-21 14:18:17 +00:00
m_WaterSpeed(0, 0, 0),
m_Mass (0.001), // Default 1g
m_Width(a_Width),
m_Height(a_Height),
m_InvulnerableTicks(0)
{
}
cEntity::~cEntity()
{
// Before deleting, the entity needs to have been removed from the world, if ever added
2014-10-20 20:55:07 +00:00
ASSERT((m_World == nullptr) || !m_World->HasEntity(m_UniqueID));
ASSERT(!IsTicking());
/*
// DEBUG:
2014-07-17 20:50:58 +00:00
LOGD("Deleting entity %d at pos {%.2f, %.2f, %.2f} ~ [%d, %d]; ptr %p",
m_UniqueID,
m_Pos.x, m_Pos.y, m_Pos.z,
(int)(m_Pos.x / cChunkDef::Width), (int)(m_Pos.z / cChunkDef::Width),
this
);
*/
2014-10-20 20:55:07 +00:00
if (m_AttachedTo != nullptr)
{
Detach();
}
2014-10-20 20:55:07 +00:00
if (m_Attachee != nullptr)
{
m_Attachee->Detach();
}
}
const char * cEntity::GetClass(void) const
{
return "cEntity";
}
const char * cEntity::GetClassStatic(void)
{
return "cEntity";
}
const char * cEntity::GetParentClass(void) const
{
return "";
}
bool cEntity::Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld)
{
if (cPluginManager::Get()->CallHookSpawningEntity(a_EntityWorld, *this))
{
return false;
}
/*
// DEBUG:
LOGD("Initializing entity #%d (%s) at {%.02f, %.02f, %.02f}",
m_UniqueID, GetClass(), m_Pos.x, m_Pos.y, m_Pos.z
);
*/
ASSERT(m_World == nullptr);
ASSERT(GetParentChunk() == nullptr);
a_EntityWorld.AddEntity(std::move(a_Self));
ASSERT(m_World != nullptr);
cPluginManager::Get()->CallHookSpawnedEntity(a_EntityWorld, *this);
// Spawn the entity on the clients:
a_EntityWorld.BroadcastSpawnEntity(*this);
2017-08-21 08:46:41 +00:00
// If has any mob leashed broadcast every leashed entity to this
if (HasAnyMobLeashed())
{
for (auto LeashedMob : m_LeashedMobs)
{
m_World->BroadcastLeashEntity(*LeashedMob, *this);
}
}
return true;
}
void cEntity::WrapHeadYaw(void)
{
2013-12-08 12:08:56 +00:00
m_HeadYaw = NormalizeAngleDegrees(m_HeadYaw);
}
void cEntity::WrapRotation(void)
{
2013-12-08 12:08:56 +00:00
m_Rot.x = NormalizeAngleDegrees(m_Rot.x);
2013-12-08 17:54:04 +00:00
m_Rot.y = NormalizeAngleDegrees(m_Rot.y);
}
void cEntity::WrapSpeed(void)
{
m_Speed.x = Clamp(m_Speed.x, -78.0, 78.0);
m_Speed.y = Clamp(m_Speed.y, -78.0, 78.0);
m_Speed.z = Clamp(m_Speed.z, -78.0, 78.0);
}
void cEntity::SetParentChunk(cChunk * a_Chunk)
{
m_ParentChunk = a_Chunk;
}
void cEntity::Destroy(bool a_ShouldBroadcast)
{
SetIsTicking(false);
2017-08-21 08:46:41 +00:00
// Unleash leashed mobs
while (!m_LeashedMobs.empty())
{
m_LeashedMobs.front()->Unleash(true, true);
}
if (a_ShouldBroadcast)
{
m_World->BroadcastDestroyEntity(*this);
}
auto ParentChunkCoords = cChunkDef::BlockToChunk(GetPosition());
m_World->QueueTask([this, ParentChunkCoords](cWorld & a_World)
{
LOGD("Destroying entity #%i (%s) from chunk (%d, %d)",
this->GetUniqueID(), this->GetClass(),
ParentChunkCoords.m_ChunkX, ParentChunkCoords.m_ChunkZ
);
UNUSED(ParentChunkCoords); // Non Debug mode only
// Make sure that RemoveEntity returned a valid smart pointer
// Also, not storing the returned pointer means automatic destruction
VERIFY(a_World.RemoveEntity(*this));
});
Destroyed();
}
void cEntity::DestroyNoScheduling(bool a_ShouldBroadcast)
{
SetIsTicking(false);
if (a_ShouldBroadcast)
{
m_World->BroadcastDestroyEntity(*this);
}
Destroyed();
}
void cEntity::TakeDamage(cEntity & a_Attacker)
{
int RawDamage = a_Attacker.GetRawDamageAgainst(*this);
TakeDamage(dtAttack, &a_Attacker, RawDamage, a_Attacker.GetKnockbackAmountAgainst(*this));
}
void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_RawDamage, double a_KnockbackAmount)
{
int ArmorCover = GetArmorCoverAgainst(a_Attacker, a_DamageType, a_RawDamage);
int EnchantmentCover = GetEnchantmentCoverAgainst(a_Attacker, a_DamageType, a_RawDamage);
int FinalDamage = a_RawDamage - ArmorCover - EnchantmentCover;
if ((FinalDamage == 0) && (a_RawDamage > 0))
{
// Nobody's invincible
FinalDamage = 1;
}
ApplyArmorDamage(ArmorCover);
cEntity::TakeDamage(a_DamageType, a_Attacker, a_RawDamage, FinalDamage, a_KnockbackAmount);
}
void cEntity::TakeDamage(eDamageType a_DamageType, UInt32 a_AttackerID, int a_RawDamage, double a_KnockbackAmount)
{
m_World->DoWithEntityByID(a_AttackerID, [=](cEntity & a_Attacker)
{
cPawn * Attacker;
if (a_Attacker.IsPawn())
{
Attacker = static_cast<cPawn*>(&a_Attacker);
}
else
{
Attacker = nullptr;
}
TakeDamage(a_DamageType, Attacker, a_RawDamage, a_KnockbackAmount);
return true;
}
);
}
void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_RawDamage, int a_FinalDamage, double a_KnockbackAmount)
{
TakeDamageInfo TDI;
TDI.DamageType = a_DamageType;
if ((a_Attacker != nullptr) && a_Attacker->IsPawn())
{
TDI.Attacker = a_Attacker;
}
else
{
TDI.Attacker = nullptr;
}
TDI.RawDamage = a_RawDamage;
TDI.FinalDamage = a_FinalDamage;
2013-12-22 20:04:17 +00:00
Vector3d Heading(0, 0, 0);
2014-10-20 20:55:07 +00:00
if (a_Attacker != nullptr)
2013-12-22 20:04:17 +00:00
{
Heading = a_Attacker->GetLookVector();
2013-12-22 20:04:17 +00:00
}
TDI.Knockback = Heading * a_KnockbackAmount;
DoTakeDamage(TDI);
}
2014-01-16 19:00:49 +00:00
void cEntity::SetYawFromSpeed(void)
{
const double EPS = 0.0000001;
2014-09-22 07:22:36 +00:00
if ((std::abs(m_Speed.x) < EPS) && (std::abs(m_Speed.z) < EPS))
{
// atan2() may overflow or is undefined, pick any number
2014-01-16 19:00:49 +00:00
SetYaw(0);
return;
}
2014-10-20 20:55:07 +00:00
SetYaw(atan2(m_Speed.x, m_Speed.z) * 180 / M_PI);
}
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
2014-09-22 07:22:36 +00:00
if ((std::abs(xz) < EPS) && (std::abs(m_Speed.y) < EPS))
{
// atan2() may overflow or is undefined, pick any number
SetPitch(0);
return;
}
2014-10-20 20:55:07 +00:00
SetPitch(atan2(m_Speed.y, xz) * 180 / M_PI);
}
2014-04-25 22:32:30 +00:00
bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI)
{
if (m_Health <= 0)
{
// Can't take damage if already dead
2014-04-25 22:32:30 +00:00
return false;
}
if (m_InvulnerableTicks > 0)
{
// Entity is invulnerable
return false;
}
2014-07-23 14:32:09 +00:00
if (cRoot::Get()->GetPluginManager()->CallHookTakeDamage(*this, a_TDI))
{
return false;
}
2014-10-20 20:55:07 +00:00
if ((a_TDI.Attacker != nullptr) && (a_TDI.Attacker->IsPlayer()))
2013-12-22 20:04:17 +00:00
{
cPlayer * Player = static_cast<cPlayer *>(a_TDI.Attacker);
2014-05-12 18:38:52 +00:00
2014-07-23 14:32:09 +00:00
Player->GetEquippedItem().GetHandler()->OnEntityAttack(Player, this);
// TODO: Better damage increase, and check for enchantments (and use magic critical instead of plain)
2018-01-17 21:12:24 +00:00
// IsOnGround() only is false if the player is moving downwards
// Ref: https://minecraft.gamepedia.com/Damage#Critical_Hits
if (!Player->IsOnGround())
{
if ((a_TDI.DamageType == dtAttack) || (a_TDI.DamageType == dtArrowAttack))
{
a_TDI.FinalDamage *= 1.5; // 150% damage
m_World->BroadcastEntityAnimation(*this, 4); // Critical hit
}
}
const cEnchantments & Enchantments = Player->GetEquippedItem().m_Enchantments;
int SharpnessLevel = static_cast<int>(Enchantments.GetLevel(cEnchantments::enchSharpness));
int SmiteLevel = static_cast<int>(Enchantments.GetLevel(cEnchantments::enchSmite));
int BaneOfArthropodsLevel = static_cast<int>(Enchantments.GetLevel(cEnchantments::enchBaneOfArthropods));
if (SharpnessLevel > 0)
{
a_TDI.FinalDamage += static_cast<int>(ceil(1.25 * SharpnessLevel));
}
else if (SmiteLevel > 0)
{
if (IsMob())
{
cMonster * Monster = static_cast<cMonster *>(this);
switch (Monster->GetMobType())
{
case mtSkeleton:
case mtZombie:
case mtWither:
case mtZombiePigman:
{
a_TDI.FinalDamage += static_cast<int>(ceil(2.5 * SmiteLevel));
break;
}
2014-09-22 22:23:56 +00:00
default: break;
}
}
}
else if (BaneOfArthropodsLevel > 0)
2013-12-22 20:04:17 +00:00
{
if (IsMob())
{
cMonster * Monster = static_cast<cMonster *>(this);
switch (Monster->GetMobType())
{
case mtSpider:
case mtCaveSpider:
case mtSilverfish:
{
2018-01-17 21:12:24 +00:00
a_TDI.FinalDamage += static_cast<int>(ceil(2.5 * BaneOfArthropodsLevel));
// The duration of the effect is a random value between 1 and 1.5 seconds at level I,
// increasing the max duration by 0.5 seconds each level
// Ref: https://minecraft.gamepedia.com/Enchanting#Bane_of_Arthropods
int Duration = 20 + GetRandomProvider().RandInt(BaneOfArthropodsLevel * 10); // Duration in ticks
Monster->AddEntityEffect(cEntityEffect::effSlowness, Duration, 4);
break;
}
default: break;
}
}
}
int FireAspectLevel = static_cast<int>(Enchantments.GetLevel(cEnchantments::enchFireAspect));
if (FireAspectLevel > 0)
{
int BurnTicks = 3;
if (FireAspectLevel > 1)
{
BurnTicks += 4 * (FireAspectLevel - 1);
}
if (!IsMob() && !IsInWater())
{
StartBurning(BurnTicks * 20);
}
else if (IsMob() && !IsInWater())
{
cMonster * Monster = static_cast<cMonster *>(this);
switch (Monster->GetMobType())
{
case mtGhast:
case mtZombiePigman:
case mtMagmaCube:
2014-09-03 08:29:10 +00:00
{
break;
}
2014-08-19 14:47:33 +00:00
default: StartBurning(BurnTicks * 20);
}
}
}
unsigned 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 = static_cast<int>(ThornsLevel * 15);
2017-06-13 19:35:30 +00:00
auto & Random = GetRandomProvider();
2017-06-13 19:35:30 +00:00
if (Random.RandBool(Chance / 100.0))
{
2017-06-13 19:35:30 +00:00
a_TDI.Attacker->TakeDamage(dtAttack, this, 0, Random.RandInt(1, 4), 0);
}
}
Player->GetStatManager().AddValue(statDamageDealt, static_cast<StatValue>(floor(a_TDI.FinalDamage * 10 + 0.5)));
2013-12-22 20:04:17 +00:00
}
m_Health -= static_cast<float>(a_TDI.FinalDamage);
m_Health = std::max(m_Health, 0.0f);
// Add knockback:
2014-10-20 20:55:07 +00:00
if ((IsMob() || IsPlayer()) && (a_TDI.Attacker != nullptr))
{
AddSpeed(a_TDI.Knockback);
}
2013-12-22 20:04:17 +00:00
m_World->BroadcastEntityStatus(*this, esGenericHurt);
2014-04-25 22:32:30 +00:00
m_InvulnerableTicks = 10;
if (m_Health <= 0)
{
2014-07-04 09:55:09 +00:00
KilledBy(a_TDI);
2014-10-20 20:55:07 +00:00
if (a_TDI.Attacker != nullptr)
{
a_TDI.Attacker->Killed(this);
}
}
2014-04-25 22:32:30 +00:00
return true;
}
int cEntity::GetRawDamageAgainst(const cEntity & a_Receiver)
{
// Returns the hitpoints that this pawn can deal to a_Receiver using its equipped items
2017-08-24 09:19:40 +00:00
// Ref: https://minecraft.gamepedia.com/Damage#Dealing_damage as of 2012_12_20
switch (this->GetEquippedWeapon().m_ItemType)
{
case E_ITEM_WOODEN_SWORD: return 4;
case E_ITEM_GOLD_SWORD: return 4;
case E_ITEM_STONE_SWORD: return 5;
case E_ITEM_IRON_SWORD: return 6;
case E_ITEM_DIAMOND_SWORD: return 7;
case E_ITEM_WOODEN_AXE: return 3;
case E_ITEM_GOLD_AXE: return 3;
case E_ITEM_STONE_AXE: return 4;
case E_ITEM_IRON_AXE: return 5;
case E_ITEM_DIAMOND_AXE: return 6;
case E_ITEM_WOODEN_PICKAXE: return 2;
case E_ITEM_GOLD_PICKAXE: return 2;
case E_ITEM_STONE_PICKAXE: return 3;
case E_ITEM_IRON_PICKAXE: return 4;
case E_ITEM_DIAMOND_PICKAXE: return 5;
case E_ITEM_WOODEN_SHOVEL: return 1;
case E_ITEM_GOLD_SHOVEL: return 1;
case E_ITEM_STONE_SHOVEL: return 2;
case E_ITEM_IRON_SHOVEL: return 3;
case E_ITEM_DIAMOND_SHOVEL: return 4;
}
// All other equipped items give a damage of 1:
return 1;
}
void cEntity::ApplyArmorDamage(int DamageBlocked)
{
// cEntities don't necessarily have armor to damage.
return;
}
bool cEntity::ArmorCoversAgainst(eDamageType a_DamageType)
{
2017-08-24 09:19:40 +00:00
// Ref.: https://minecraft.gamepedia.com/Armor#Effects as of 2012_12_20
switch (a_DamageType)
{
case dtOnFire:
case dtSuffocating:
case dtDrowning: // TODO: This one could be a special case - in various MC versions (PC vs XBox) it is and isn't armor-protected
case dtEnderPearl:
case dtStarving:
case dtInVoid:
case dtPoisoning:
case dtWithering:
case dtPotionOfHarming:
case dtFalling:
case dtLightning:
2014-02-02 12:47:17 +00:00
case dtPlugin:
{
return false;
}
case dtAttack:
case dtArrowAttack:
case dtCactusContact:
case dtLavaContact:
case dtFireContact:
case dtExplosion:
{
return true;
}
}
UNREACHABLE("Unsupported damage type");
}
int cEntity::GetEnchantmentCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_Damage)
{
int TotalEPF = 0;
const cItem ArmorItems[] = { GetEquippedHelmet(), GetEquippedChestplate(), GetEquippedLeggings(), GetEquippedBoots() };
for (size_t i = 0; i < ARRAYCOUNT(ArmorItems); i++)
{
const cItem & Item = ArmorItems[i];
if ((a_DamageType != dtInVoid) && (a_DamageType != dtAdmin) && (a_DamageType != dtStarving))
{
TotalEPF += static_cast<int>(Item.m_Enchantments.GetLevel(cEnchantments::enchProtection)) * 1;
}
if ((a_DamageType == dtBurning) || (a_DamageType == dtFireContact) || (a_DamageType == dtLavaContact))
{
TotalEPF += static_cast<int>(Item.m_Enchantments.GetLevel(cEnchantments::enchFireProtection)) * 2;
}
if ((a_DamageType == dtFalling) || (a_DamageType == dtEnderPearl))
{
TotalEPF += static_cast<int>(Item.m_Enchantments.GetLevel(cEnchantments::enchFeatherFalling)) * 3;
}
if (a_DamageType == dtExplosion)
{
TotalEPF += static_cast<int>(Item.m_Enchantments.GetLevel(cEnchantments::enchBlastProtection)) * 2;
}
// Note: Also blocks against fire charges, etc.
if (a_DamageType == dtProjectile)
{
TotalEPF += static_cast<int>(Item.m_Enchantments.GetLevel(cEnchantments::enchProjectileProtection)) * 2;
}
}
int CappedEPF = std::min(20, TotalEPF);
return static_cast<int>(a_Damage * CappedEPF / 25.0);
}
float cEntity::GetEnchantmentBlastKnockbackReduction()
{
UInt32 MaxLevel = 0;
const cItem ArmorItems[] = { GetEquippedHelmet(), GetEquippedChestplate(), GetEquippedLeggings(), GetEquippedBoots() };
for (auto & Item : ArmorItems)
{
UInt32 Level = Item.m_Enchantments.GetLevel(cEnchantments::enchBlastProtection);
if (Level > MaxLevel)
{
// Get max blast protection
MaxLevel = Level;
}
}
// Max blast protect level is 4, each level provide 15% knock back reduction
MaxLevel = std::min<UInt32>(MaxLevel, 4);
return MaxLevel * 0.15f;
}
int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_Damage)
{
// Returns the hitpoints out of a_RawDamage that the currently equipped armor would cover
// Filter out damage types that are not protected by armor:
if (!ArmorCoversAgainst(a_DamageType))
{
return 0;
}
// Add up all armor points:
2017-08-24 09:19:40 +00:00
// Ref.: https://minecraft.gamepedia.com/Armor#Defense_points
int ArmorValue = 0;
int Toughness = 0;
switch (GetEquippedHelmet().m_ItemType)
{
case E_ITEM_LEATHER_CAP: ArmorValue += 1; break;
case E_ITEM_GOLD_HELMET: ArmorValue += 2; break;
case E_ITEM_CHAIN_HELMET: ArmorValue += 2; break;
case E_ITEM_IRON_HELMET: ArmorValue += 2; break;
case E_ITEM_DIAMOND_HELMET: ArmorValue += 3; Toughness += 2; break;
}
switch (GetEquippedChestplate().m_ItemType)
{
case E_ITEM_LEATHER_TUNIC: ArmorValue += 3; break;
case E_ITEM_GOLD_CHESTPLATE: ArmorValue += 5; break;
case E_ITEM_CHAIN_CHESTPLATE: ArmorValue += 5; break;
case E_ITEM_IRON_CHESTPLATE: ArmorValue += 6; break;
case E_ITEM_DIAMOND_CHESTPLATE: ArmorValue += 8; Toughness += 2; break;
}
switch (GetEquippedLeggings().m_ItemType)
{
case E_ITEM_LEATHER_PANTS: ArmorValue += 2; break;
case E_ITEM_GOLD_LEGGINGS: ArmorValue += 3; break;
case E_ITEM_CHAIN_LEGGINGS: ArmorValue += 4; break;
case E_ITEM_IRON_LEGGINGS: ArmorValue += 5; break;
case E_ITEM_DIAMOND_LEGGINGS: ArmorValue += 6; Toughness += 2; break;
}
switch (GetEquippedBoots().m_ItemType)
{
case E_ITEM_LEATHER_BOOTS: ArmorValue += 1; break;
case E_ITEM_GOLD_BOOTS: ArmorValue += 1; break;
case E_ITEM_CHAIN_BOOTS: ArmorValue += 1; break;
case E_ITEM_IRON_BOOTS: ArmorValue += 2; break;
case E_ITEM_DIAMOND_BOOTS: ArmorValue += 3; Toughness += 2; break;
}
// TODO: Special armor cases, such as wool, saddles, dog's collar
2017-08-24 09:19:40 +00:00
// Ref.: https://minecraft.gamepedia.com/Armor#Mob_armor as of 2012_12_20
double Reduction = std::max(ArmorValue / 5.0, ArmorValue - a_Damage / (2 + Toughness / 4.0));
return static_cast<int>(a_Damage * std::min(20.0, Reduction) / 25.0);
}
double cEntity::GetKnockbackAmountAgainst(const cEntity & a_Receiver)
{
// Returns the knockback amount that the currently equipped items would cause to a_Receiver on a hit
double Knockback = 11;
// If we're sprinting, bump up the knockback
if (IsSprinting())
{
Knockback = 16;
}
// Check for knockback enchantments (punch only applies to shot arrows)
unsigned int KnockbackLevel = GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchKnockback);
Knockback += 10 * KnockbackLevel;
return Knockback;
}
2014-07-04 09:55:09 +00:00
void cEntity::KilledBy(TakeDamageInfo & a_TDI)
{
m_Health = 0;
cRoot::Get()->GetPluginManager()->CallHookKilling(*this, a_TDI.Attacker, a_TDI);
if (m_Health > 0)
{
// Plugin wants to 'unkill' the pawn. Abort
return;
}
2015-06-02 15:06:18 +00:00
// If the victim is a player the hook is handled by the cPlayer class
if (!IsPlayer())
{
AString emptystring = AString("");
cRoot::Get()->GetPluginManager()->CallHookKilled(*this, a_TDI, emptystring);
}
2014-02-02 12:47:17 +00:00
// Drop loot:
cItems Drops;
2014-07-04 09:55:09 +00:00
GetDrops(Drops, a_TDI.Attacker);
m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ());
m_World->BroadcastEntityStatus(*this, esGenericDead);
}
void cEntity::Heal(int a_HitPoints)
{
m_Health += a_HitPoints;
2014-08-03 05:35:29 +00:00
m_Health = std::min(m_Health, m_MaxHealth);
}
void cEntity::SetHealth(float a_Health)
{
m_Health = Clamp(a_Health, 0.0f, m_MaxHealth);
}
void cEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
ASSERT(IsTicking());
ASSERT(GetWorld() != nullptr);
m_TicksAlive++;
2014-04-25 22:32:30 +00:00
if (m_InvulnerableTicks > 0)
{
m_InvulnerableTicks--;
}
// Non-players are destroyed as soon as they fall out of the world:
2016-04-18 18:58:57 +00:00
if ((GetPosY() < 0) && (!IsPlayer()))
{
Destroy();
return;
}
2014-10-20 20:55:07 +00:00
if (m_AttachedTo != nullptr)
{
2015-07-21 20:25:37 +00:00
SetPosition(m_AttachedTo->GetPosition());
}
else
{
if (!a_Chunk.IsValid())
{
return;
}
// Position changed -> super::Tick() called:
GET_AND_VERIFY_CURRENT_CHUNK(NextChunk, POSX_TOINT, POSZ_TOINT)
// Set swim states (water, lava, and fire):
SetSwimState(*NextChunk);
// Handle catching on fire and burning:
TickBurning(*NextChunk);
// Damage players if they are in the void
if (GetPosY() < VOID_BOUNDARY)
{
TickInVoid(*NextChunk);
}
else
{
m_TicksSinceLastVoidDamage = 0;
}
// Handle cactus damage or destruction:
if (
IsMob() || IsPickup() ||
(IsPlayer() && !((static_cast<cPlayer *>(this))->IsGameModeCreative() || (static_cast<cPlayer *>(this))->IsGameModeSpectator()))
)
{
DetectCacti();
}
// Handle drowning:
if (IsMob() || IsPlayer())
{
HandleAir();
2014-07-29 15:36:24 +00:00
}
if (!DetectPortal()) // Our chunk is invalid if we have moved to another world
{
// None of the above functions changed position, we remain in the chunk of NextChunk
HandlePhysics(a_Dt, *NextChunk);
}
}
}
void cEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
int BlockX = POSX_TOINT;
int BlockY = POSY_TOINT;
int BlockZ = POSZ_TOINT;
// Position changed -> super::HandlePhysics() called
GET_AND_VERIFY_CURRENT_CHUNK(NextChunk, BlockX, BlockZ)
// TODO Add collision detection with entities.
auto DtSec = std::chrono::duration_cast<std::chrono::duration<double>>(a_Dt);
Vector3d NextPos = Vector3d(GetPosX(), GetPosY(), GetPosZ());
Vector3d NextSpeed = Vector3d(GetSpeedX(), GetSpeedY(), GetSpeedZ());
if ((BlockY >= cChunkDef::Height) || (BlockY < 0))
{
2014-07-17 20:59:02 +00:00
// Outside of the world
AddSpeedY(m_Gravity * DtSec.count());
AddPosition(GetSpeed() * DtSec.count());
return;
}
int RelBlockX = BlockX - (NextChunk->GetPosX() * cChunkDef::Width);
int RelBlockZ = BlockZ - (NextChunk->GetPosZ() * cChunkDef::Width);
BLOCKTYPE BlockIn = NextChunk->GetBlock( RelBlockX, BlockY, RelBlockZ);
BLOCKTYPE BlockBelow = (BlockY > 0) ? NextChunk->GetBlock(RelBlockX, BlockY - 1, RelBlockZ) : E_BLOCK_AIR;
if (!cBlockInfo::IsSolid(BlockIn)) // Making sure we are not inside a solid block
{
if (m_bOnGround) // check if it's still on the ground
{
if (!cBlockInfo::IsSolid(BlockBelow)) // Check if block below is air or water.
{
m_bOnGround = false;
}
}
}
else
{
// Push out entity.
BLOCKTYPE GotBlock;
2013-09-04 16:52:15 +00:00
static const struct
{
int x, y, z;
} gCrossCoords[] =
{
{ 1, 0, 0},
{-1, 0, 0},
{ 0, 0, 1},
{ 0, 0, -1},
} ;
bool IsNoAirSurrounding = true;
for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
{
if (!NextChunk->UnboundedRelGetBlockType(RelBlockX + gCrossCoords[i].x, BlockY, RelBlockZ + gCrossCoords[i].z, GotBlock))
{
// The pickup is too close to an unloaded chunk, bail out of any physics handling
return;
}
if (!cBlockInfo::IsSolid(GotBlock))
{
NextPos.x += gCrossCoords[i].x;
NextPos.z += gCrossCoords[i].z;
IsNoAirSurrounding = false;
break;
}
} // for i - gCrossCoords[]
if (IsNoAirSurrounding)
{
NextPos.y += 0.5;
}
2013-09-13 18:54:50 +00:00
m_bHasSentNoSpeed = false; // this unlocks movement sending to client in BroadcastMovementUpdate function
m_bOnGround = true;
/*
// DEBUG:
LOGD("Entity #%d (%s) is inside a block at {%d, %d, %d}",
m_UniqueID, GetClass(), BlockX, BlockY, BlockZ
);
*/
}
if (!m_bOnGround)
{
2015-01-18 10:02:17 +00:00
double fallspeed;
if (IsBlockWater(BlockIn))
{
fallspeed = m_Gravity * DtSec.count() / 3; // Fall 3x slower in water
2015-01-18 10:02:17 +00:00
ApplyFriction(NextSpeed, 0.7, static_cast<float>(DtSec.count()));
}
else if (BlockIn == E_BLOCK_COBWEB)
{
NextSpeed.y *= 0.05; // Reduce overall falling speed
2014-09-13 21:49:05 +00:00
fallspeed = 0; // No falling
}
else
{
// Normal gravity
fallspeed = m_Gravity * DtSec.count();
NextSpeed -= NextSpeed * (m_AirDrag * 20.0f) * DtSec.count();
}
2015-01-18 10:02:17 +00:00
NextSpeed.y += static_cast<float>(fallspeed);
// A real boat floats
if (IsBoat())
{
// Find top water block and sit there
int NextBlockY = BlockY;
BLOCKTYPE NextBlock = NextChunk->GetBlock(RelBlockX, NextBlockY, RelBlockZ);
while (IsBlockWater(NextBlock))
{
NextBlock = NextChunk->GetBlock(RelBlockX, ++NextBlockY, RelBlockZ);
}
NextPos.y = NextBlockY - 0.5;
NextSpeed.y = 0;
}
}
else
{
2015-01-18 10:02:17 +00:00
ApplyFriction(NextSpeed, 0.7, static_cast<float>(DtSec.count()));
}
// Adjust X and Z speed for COBWEB temporary. This speed modification should be handled inside block handlers since we
// might have different speed modifiers according to terrain.
if (BlockIn == E_BLOCK_COBWEB)
{
NextSpeed.x *= 0.25;
NextSpeed.z *= 0.25;
}
// Get water direction
Direction WaterDir = m_World->GetWaterSimulator()->GetFlowingDirection(BlockX, BlockY, BlockZ);
m_WaterSpeed *= 0.9; // Reduce speed each tick
switch (WaterDir)
{
case X_PLUS:
{
m_WaterSpeed.x = 0.2f;
m_bOnGround = false;
break;
}
case X_MINUS:
{
m_WaterSpeed.x = -0.2f;
m_bOnGround = false;
break;
}
case Z_PLUS:
{
m_WaterSpeed.z = 0.2f;
m_bOnGround = false;
break;
}
case Z_MINUS:
{
m_WaterSpeed.z = -0.2f;
m_bOnGround = false;
break;
}
default:
{
break;
}
}
if (fabs(m_WaterSpeed.x) < 0.05)
{
m_WaterSpeed.x = 0;
}
if (fabs(m_WaterSpeed.z) < 0.05)
{
m_WaterSpeed.z = 0;
}
NextSpeed += m_WaterSpeed;
if (NextSpeed.SqrLength() > 0.0f)
{
Vector3d HitCoords;
Vector3i HitBlockCoords;
eBlockFace HitBlockFace;
Vector3d wantNextPos = NextPos + NextSpeed * DtSec.count();
auto isHit = cLineBlockTracer::FirstSolidHitTrace(*GetWorld(), NextPos, wantNextPos, HitCoords, HitBlockCoords, HitBlockFace);
if (isHit)
{
// Set our position to where the block was hit, minus a bit:
// TODO: The real entity's m_Width should be taken into account here
NextPos = HitCoords - NextSpeed.NormalizeCopy() * 0.1;
if (HitBlockFace == BLOCK_FACE_YP)
{
// We hit the ground, adjust the position to the top of the block:
m_bOnGround = true;
NextPos.y = HitBlockCoords.y + 1;
}
// Avoid movement in the direction of the blockface that has been hit:
switch (HitBlockFace)
{
case BLOCK_FACE_XM:
case BLOCK_FACE_XP:
{
NextSpeed.x = 0;
break;
}
case BLOCK_FACE_YM:
case BLOCK_FACE_YP:
{
NextSpeed.y = 0;
break;
}
case BLOCK_FACE_ZM:
case BLOCK_FACE_ZP:
{
NextSpeed.z = 0;
break;
}
default:
2015-11-10 13:02:07 +00:00
{
break;
2015-11-10 13:02:07 +00:00
}
}
}
else
{
// We didn't hit anything, so move:
NextPos += (NextSpeed * DtSec.count());
}
}
SetPosition(NextPos);
SetSpeed(NextSpeed);
}
2014-09-13 21:49:05 +00:00
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)
{
// If we're about to change worlds, then we can't accurately determine whether we're in lava (#3939)
if (m_IsWorldChangeScheduled)
{
return;
}
// Remember the current burning state:
bool HasBeenBurning = (m_TicksLeftBurning > 0);
2014-04-18 11:59:14 +00:00
// Fireproof entities burn out on the next tick
if (IsFireproof())
{
m_TicksLeftBurning = 0;
}
// Fire is extinguished by rain
if (GetWorld()->IsWeatherWetAtXYZ(GetPosition().Floor()))
2014-04-18 11:59:14 +00:00
{
m_TicksLeftBurning = 0;
2014-04-18 11:59:14 +00:00
}
// Do the burning damage:
if (m_TicksLeftBurning > 0)
{
m_TicksSinceLastBurnDamage++;
if (m_TicksSinceLastBurnDamage >= BURN_TICKS_PER_DAMAGE)
{
if (!IsFireproof())
{
2014-10-20 20:55:07 +00:00
TakeDamage(dtOnFire, nullptr, BURN_DAMAGE, 0);
}
m_TicksSinceLastBurnDamage = 0;
}
m_TicksLeftBurning--;
}
if (IsInWater())
{
// Extinguish the fire
m_TicksLeftBurning = 0;
}
if (IsInLava())
{
// Burn:
m_TicksLeftBurning = BURN_TICKS;
// Periodically damage:
m_TicksSinceLastLavaDamage++;
if (m_TicksSinceLastLavaDamage >= LAVA_TICKS_PER_DAMAGE)
{
if (!IsFireproof())
{
2014-10-20 20:55:07 +00:00
TakeDamage(dtLavaContact, nullptr, LAVA_DAMAGE, 0);
}
m_TicksSinceLastLavaDamage = 0;
}
}
else
{
m_TicksSinceLastLavaDamage = 0;
}
if (IsInFire())
{
// Burn:
m_TicksLeftBurning = BURN_TICKS;
// Periodically damage:
m_TicksSinceLastFireDamage++;
if (m_TicksSinceLastFireDamage >= FIRE_TICKS_PER_DAMAGE)
{
if (!IsFireproof() && !IsInLava())
{
2014-10-20 20:55:07 +00:00
TakeDamage(dtFireContact, nullptr, FIRE_DAMAGE, 0);
}
m_TicksSinceLastFireDamage = 0;
}
}
else
{
m_TicksSinceLastFireDamage = 0;
}
// If just started / finished burning, notify descendants:
if ((m_TicksLeftBurning > 0) && !HasBeenBurning)
{
OnStartedBurning();
}
else if ((m_TicksLeftBurning <= 0) && HasBeenBurning)
{
OnFinishedBurning();
}
}
2013-09-10 22:01:02 +00:00
void cEntity::TickInVoid(cChunk & a_Chunk)
{
if (m_TicksSinceLastVoidDamage == 20)
{
2014-10-20 20:55:07 +00:00
TakeDamage(dtInVoid, nullptr, 2, 0);
2013-09-10 22:01:02 +00:00
m_TicksSinceLastVoidDamage = 0;
}
else
{
m_TicksSinceLastVoidDamage++;
}
}
2014-05-23 10:33:30 +00:00
void cEntity::DetectCacti(void)
{
int MinX = FloorC(GetPosX() - m_Width / 2);
int MaxX = FloorC(GetPosX() + m_Width / 2);
int MinZ = FloorC(GetPosZ() - m_Width / 2);
int MaxZ = FloorC(GetPosZ() + m_Width / 2);
int MinY = Clamp(POSY_TOINT, 0, cChunkDef::Height - 1);
int MaxY = Clamp(FloorC(GetPosY() + m_Height), 0, cChunkDef::Height - 1);
for (int x = MinX; x <= MaxX; x++)
{
for (int z = MinZ; z <= MaxZ; z++)
{
for (int y = MinY; y <= MaxY; y++)
{
if (GetWorld()->GetBlock(x, y, z) == E_BLOCK_CACTUS)
{
TakeDamage(dtCactusContact, nullptr, 1, 0);
return;
}
} // for y
} // for z
} // for x
}
void cEntity::ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_SetPortalCooldown, bool a_ShouldSendRespawn)
{
m_NewWorld = a_World;
m_NewWorldPosition = a_NewPosition;
m_IsWorldChangeScheduled = true;
m_WorldChangeSetPortalCooldown = a_SetPortalCooldown;
m_WorldChangeSendRespawn = a_ShouldSendRespawn;
}
bool cEntity::DetectPortal()
2014-05-31 21:28:51 +00:00
{
// If somebody scheduled a world change with ScheduleMoveToWorld, change worlds now.
if (m_IsWorldChangeScheduled)
{
m_IsWorldChangeScheduled = false;
if (m_WorldChangeSetPortalCooldown)
{
// Delay the portal check.
m_PortalCooldownData.m_TicksDelayed = 0;
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
}
MoveToWorld(m_NewWorld, m_WorldChangeSendRespawn, m_NewWorldPosition);
return true;
}
if (GetWorld()->GetDimension() == dimOverworld)
2014-06-04 19:00:55 +00:00
{
if (GetWorld()->GetLinkedNetherWorldName().empty() && GetWorld()->GetLinkedEndWorldName().empty())
2014-07-21 21:49:06 +00:00
{
2014-07-23 20:12:59 +00:00
// Teleportation to either dimension not enabled, don't bother proceeding
return false;
2014-07-21 21:49:06 +00:00
}
}
else if (GetWorld()->GetLinkedOverworldName().empty())
{
2014-07-23 20:12:59 +00:00
// Overworld teleportation disabled, abort
return false;
2014-06-04 19:00:55 +00:00
}
2014-05-31 21:28:51 +00:00
int X = POSX_TOINT, Y = POSY_TOINT, Z = POSZ_TOINT;
if ((Y > 0) && (Y < cChunkDef::Height))
{
switch (GetWorld()->GetBlock(X, Y, Z))
{
case E_BLOCK_NETHER_PORTAL:
{
2014-07-21 21:49:06 +00:00
if (m_PortalCooldownData.m_ShouldPreventTeleportation)
2014-06-04 19:00:55 +00:00
{
2014-07-23 20:12:59 +00:00
// Just exited a portal, don't teleport again
return false;
2014-06-04 19:00:55 +00:00
}
if (IsPlayer() && !(static_cast<cPlayer *>(this))->IsGameModeCreative() && (m_PortalCooldownData.m_TicksDelayed != 80))
2014-06-12 14:21:07 +00:00
{
2014-07-23 20:12:59 +00:00
// Delay teleportation for four seconds if the entity is a non-creative player
2014-07-21 21:49:06 +00:00
m_PortalCooldownData.m_TicksDelayed++;
return false;
2014-06-12 14:21:07 +00:00
}
2014-07-21 21:49:06 +00:00
m_PortalCooldownData.m_TicksDelayed = 0;
2014-06-12 14:21:07 +00:00
// Nether portal in the nether
if (GetWorld()->GetDimension() == dimNether)
2014-05-31 21:28:51 +00:00
{
if (GetWorld()->GetLinkedOverworldName().empty())
2014-06-12 14:21:07 +00:00
{
return false;
2014-06-12 14:21:07 +00:00
}
cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn
if (IsPlayer())
2014-05-31 21:28:51 +00:00
{
// Send a respawn packet before world is loaded / generated so the client isn't left in limbo
(static_cast<cPlayer *>(this))->GetClientHandle()->SendRespawn(DestionationDim);
2014-05-31 21:28:51 +00:00
}
Vector3d TargetPos = GetPosition();
TargetPos.x *= 8.0;
TargetPos.z *= 8.0;
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
LOGD("Jumping %s -> %s", DimensionToString(dimNether).c_str(), DimensionToString(DestionationDim).c_str());
new cNetherPortalScanner(this, TargetWorld, TargetPos, cChunkDef::Height);
return true;
}
// Nether portal in the overworld
else
{
if (GetWorld()->GetLinkedNetherWorldName().empty())
{
return false;
}
cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName());
eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
if (IsPlayer())
{
if (DestionationDim == dimNether)
{
static_cast<cPlayer *>(this)->AwardAchievement(achEnterPortal);
}
static_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(DestionationDim);
}
Vector3d TargetPos = GetPosition();
TargetPos.x /= 8.0;
TargetPos.z /= 8.0;
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(DestionationDim).c_str());
new cNetherPortalScanner(this, TargetWorld, TargetPos, (cChunkDef::Height / 2));
return true;
2014-05-31 21:28:51 +00:00
}
}
case E_BLOCK_END_PORTAL:
{
2014-07-21 21:49:06 +00:00
if (m_PortalCooldownData.m_ShouldPreventTeleportation)
2014-06-04 19:00:55 +00:00
{
return false;
2014-06-04 19:00:55 +00:00
}
// End portal in the end
if (GetWorld()->GetDimension() == dimEnd)
2014-05-31 21:28:51 +00:00
{
if (GetWorld()->GetLinkedOverworldName().empty())
2014-05-31 21:28:51 +00:00
{
return false;
}
cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
2014-06-12 14:21:07 +00:00
if (IsPlayer())
2014-05-31 21:28:51 +00:00
{
cPlayer * Player = static_cast<cPlayer *>(this);
if (Player->GetBedWorld() == DestinationWorld)
{
Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z);
}
else
{
Player->TeleportToCoords(DestinationWorld->GetSpawnX(), DestinationWorld->GetSpawnY(), DestinationWorld->GetSpawnZ());
}
Player->GetClientHandle()->SendRespawn(DestionationDim);
}
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
LOGD("Jumping %s -> %s", DimensionToString(dimEnd).c_str(), DimensionToString(DestionationDim).c_str());
return MoveToWorld(TargetWorld, false);
}
// End portal in the overworld
else
{
if (GetWorld()->GetLinkedEndWorldName().empty())
{
return false;
}
cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName());
eDimension DestionationDim = DestinationWorld->GetDimension();
2014-06-12 14:21:07 +00:00
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
2014-06-12 14:21:07 +00:00
if (IsPlayer())
{
if (DestionationDim == dimEnd)
{
static_cast<cPlayer *>(this)->AwardAchievement(achEnterTheEnd);
}
static_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(DestionationDim);
2014-05-31 21:28:51 +00:00
}
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(DestionationDim).c_str());
return MoveToWorld(TargetWorld, false);
2014-05-31 21:28:51 +00:00
}
2014-05-31 21:28:51 +00:00
}
default: break;
}
}
2014-06-12 14:21:07 +00:00
// Allow portals to work again
2014-07-21 21:49:06 +00:00
m_PortalCooldownData.m_ShouldPreventTeleportation = false;
2014-07-23 20:12:59 +00:00
m_PortalCooldownData.m_TicksDelayed = 0;
return false;
2014-05-31 21:28:51 +00:00
}
bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition)
2014-05-31 21:28:51 +00:00
{
2014-06-12 14:21:07 +00:00
UNUSED(a_ShouldSendRespawn);
2014-10-20 20:55:07 +00:00
ASSERT(a_World != nullptr);
2014-06-12 14:21:07 +00:00
2014-07-21 21:49:06 +00:00
if (GetWorld() == a_World)
2014-05-31 21:28:51 +00:00
{
2014-06-12 14:21:07 +00:00
// Don't move to same world
2014-05-31 21:28:51 +00:00
return false;
}
2015-05-21 10:27:54 +00:00
// Ask the plugins if the entity is allowed to changing the world
if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World))
{
2015-05-21 10:27:54 +00:00
// A Plugin doesn't allow the entity to changing the world
return false;
}
// Stop ticking, in preperation for detaching from this world.
SetIsTicking(false);
// Tell others we are gone
2014-05-31 21:28:51 +00:00
GetWorld()->BroadcastDestroyEntity(*this);
// Take note of old chunk coords
auto OldChunkCoords = cChunkDef::BlockToChunk(GetPosition());
// Set position to the new position
ResetPosition(a_NewPosition);
// Stop all mobs from targeting this entity
// Stop this entity from targeting other mobs
if (this->IsMob())
{
cMonster * Monster = static_cast<cMonster*>(this);
Monster->SetTarget(nullptr);
Monster->StopEveryoneFromTargetingMe();
}
// Queue add to new world and removal from the old one
cWorld * OldWorld = GetWorld();
SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value
OldWorld->QueueTask([this, OldChunkCoords, a_World](cWorld & a_OldWorld)
{
LOGD("Warping entity #%i (%s) from world \"%s\" to \"%s\". Source chunk: (%d, %d) ",
this->GetUniqueID(), this->GetClass(),
a_OldWorld.GetName().c_str(), a_World->GetName().c_str(),
OldChunkCoords.m_ChunkX, OldChunkCoords.m_ChunkZ
);
UNUSED(OldChunkCoords); // Non Debug mode only
a_World->AddEntity(a_OldWorld.RemoveEntity(*this));
cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, a_OldWorld);
});
2014-05-31 21:28:51 +00:00
return true;
}
2016-03-30 08:38:45 +00:00
bool cEntity::MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition)
{
return DoMoveToWorld(a_World, a_ShouldSendRespawn, a_NewPosition);
}
bool cEntity::MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn)
{
return MoveToWorld(a_World, a_ShouldSendRespawn, Vector3d(a_World->GetSpawnX(), a_World->GetSpawnY(), a_World->GetSpawnZ()));
}
2014-07-21 21:49:06 +00:00
bool cEntity::MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn)
{
cWorld * World = cRoot::Get()->GetWorld(a_WorldName);
2014-10-20 20:55:07 +00:00
if (World == nullptr)
2014-07-21 21:49:06 +00:00
{
LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName.c_str());
return false;
}
2016-03-30 08:38:45 +00:00
return DoMoveToWorld(World, a_ShouldSendRespawn, Vector3d(World->GetSpawnX(), World->GetSpawnY(), World->GetSpawnZ()));
2014-07-21 21:49:06 +00:00
}
void cEntity::SetSwimState(cChunk & a_Chunk)
{
m_IsInFire = false;
m_IsInLava = false;
m_IsInWater = false;
m_IsHeadInWater = false;
int RelY = FloorC(GetPosY() + 0.1);
int HeadRelY = CeilC(GetPosY() + GetHeight()) - 1;
ASSERT(RelY <= HeadRelY);
if ((RelY < 0) || (HeadRelY >= cChunkDef::Height))
{
return;
}
int MinRelX = FloorC(GetPosX() - m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width;
int MaxRelX = FloorC(GetPosX() + m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width;
int MinRelZ = FloorC(GetPosZ() - m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width;
int MaxRelZ = FloorC(GetPosZ() + m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width;
int MinY = Clamp(POSY_TOINT, 0, cChunkDef::Height - 1);
int MaxY = Clamp(FloorC(GetPosY() + m_Height), 0, cChunkDef::Height - 1);
for (int x = MinRelX; x <= MaxRelX; x++)
{
for (int z = MinRelZ; z <= MaxRelZ; z++)
{
for (int y = MinY; y <= MaxY; y++)
{
BLOCKTYPE Block;
if (!a_Chunk.UnboundedRelGetBlockType(x, y, z, Block))
{
LOGD("SetSwimState failure: RelX = %d, RelY = %d, RelZ = %d, Pos = %.02f, %.02f}",
x, y, z, GetPosX(), GetPosZ()
);
continue;
}
if (Block == E_BLOCK_FIRE)
{
m_IsInFire = true;
}
else if (IsBlockLava(Block))
{
m_IsInLava = true;
}
else if (IsBlockWater(Block))
{
m_IsInWater = true;
}
} // for y
} // for z
} // for x
// Check if the entity's head is in water.
int RelX = POSX_TOINT - a_Chunk.GetPosX() * cChunkDef::Width;
int RelZ = POSZ_TOINT - a_Chunk.GetPosZ() * cChunkDef::Width;
int HeadHeight = CeilC(GetPosY() + GetHeight()) - 1;
BLOCKTYPE BlockIn;
if (!a_Chunk.UnboundedRelGetBlockType(RelX, HeadHeight, RelZ, BlockIn))
{
LOGD("SetSwimState failure: RelX = %d, RelY = %d, RelZ = %d, Pos = %.02f, %.02f}",
RelX, HeadHeight, RelZ, GetPosX(), GetPosZ()
2014-04-24 21:03:47 +00:00
);
return;
}
m_IsHeadInWater = IsBlockWater(BlockIn);
}
void cEntity::SetIsTicking(bool a_IsTicking)
{
m_IsTicking = a_IsTicking;
ASSERT(!(m_IsTicking && (m_ParentChunk == nullptr))); // We shouldn't be ticking if we have no parent chunk
}
void cEntity::DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ)
{
m_Speed.Set(a_SpeedX, a_SpeedY, a_SpeedZ);
WrapSpeed();
}
void cEntity::HandleAir(void)
{
2017-08-24 09:19:40 +00:00
// Ref.: https://minecraft.gamepedia.com/Chunk_format
// See if the entity is /submerged/ water (head is in water)
// Get the type of block the entity is standing in:
int RespirationLevel = static_cast<int>(GetEquippedHelmet().m_Enchantments.GetLevel(cEnchantments::enchRespiration));
if (IsHeadInWater())
{
if (!IsPlayer()) // Players control themselves
{
SetSpeedY(1); // Float in the water
}
if (RespirationLevel > 0)
{
static_cast<cPawn *>(this)->AddEntityEffect(cEntityEffect::effNightVision, 200, 5, 0);
}
2014-08-03 05:35:29 +00:00
if (m_AirLevel <= 0)
{
2014-08-03 09:20:48 +00:00
// Runs the air tick timer to check whether the player should be damaged
2014-08-03 05:35:29 +00:00
if (m_AirTickTimer <= 0)
{
2014-07-17 20:50:58 +00:00
// Damage player
2014-10-20 20:55:07 +00:00
TakeDamage(dtDrowning, nullptr, 1, 1, 0);
// Reset timer
m_AirTickTimer = DROWNING_TICKS;
}
else
{
m_AirTickTimer--;
}
}
else
{
// Reduce air supply
m_AirLevel--;
}
}
else
{
// 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);
}
}
}
void cEntity::ResetPosition(Vector3d a_NewPos)
{
SetPosition(a_NewPos);
m_LastSentPosition = GetPosition();
}
void cEntity::OnStartedBurning(void)
{
// Broadcast the change:
m_World->BroadcastEntityMetadata(*this);
}
void cEntity::OnFinishedBurning(void)
{
// Broadcast the change:
m_World->BroadcastEntityMetadata(*this);
}
void cEntity::SetMaxHealth(float a_MaxHealth)
{
m_MaxHealth = a_MaxHealth;
// Reset health, if too high:
m_Health = std::min(m_Health, a_MaxHealth);
}
void cEntity::SetIsFireproof(bool a_IsFireproof)
2014-04-22 22:59:31 +00:00
{
m_IsFireproof = a_IsFireproof;
}
void cEntity::StartBurning(int a_TicksLeftBurning)
{
if (m_TicksLeftBurning > 0)
{
// Already burning, top up the ticks left burning and bail out:
m_TicksLeftBurning = std::max(m_TicksLeftBurning, a_TicksLeftBurning);
return;
}
m_TicksLeftBurning = a_TicksLeftBurning;
OnStartedBurning();
}
void cEntity::StopBurning(void)
{
bool HasBeenBurning = (m_TicksLeftBurning > 0);
m_TicksLeftBurning = 0;
m_TicksSinceLastBurnDamage = 0;
m_TicksSinceLastFireDamage = 0;
m_TicksSinceLastLavaDamage = 0;
// Notify if the entity has stopped burning
if (HasBeenBurning)
{
OnFinishedBurning();
}
}
void cEntity::TeleportToEntity(cEntity & a_Entity)
{
TeleportToCoords(a_Entity.GetPosX(), a_Entity.GetPosY(), a_Entity.GetPosZ());
}
void cEntity::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
{
// ask the plugins to allow teleport to the new position.
2015-07-21 20:25:37 +00:00
if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, Vector3d(a_PosX, a_PosY, a_PosZ)))
{
ResetPosition({a_PosX, a_PosY, a_PosZ});
m_World->BroadcastTeleportEntity(*this);
}
}
void cEntity::BroadcastMovementUpdate(const cClientHandle * a_Exclude)
{
// Process packet sending every two ticks
if (GetWorld()->GetWorldAge() % 2 == 0)
{
double SpeedSqr = GetSpeed().SqrLength();
if (SpeedSqr == 0.0)
{
// Speed is zero, send this to clients once only as well as an absolute position
if (!m_bHasSentNoSpeed)
{
m_World->BroadcastEntityVelocity(*this, a_Exclude);
m_World->BroadcastTeleportEntity(*this, a_Exclude);
m_bHasSentNoSpeed = true;
}
}
else
{
// Movin'
m_World->BroadcastEntityVelocity(*this, a_Exclude);
m_bHasSentNoSpeed = false;
}
// Only send movement if speed is not 0 and 'no speed' was sent to client
if (!m_bHasSentNoSpeed || IsPlayer())
{
// TODO: Pickups move disgracefully if relative move packets are sent as opposed to just velocity. Have a system to send relmove only when SetPosXXX() is called with a large difference in position
Vector3i Diff = (GetPosition() * 32.0).Floor() - (m_LastSentPosition * 32.0).Floor();
if (Diff.HasNonZeroLength()) // Have we moved?
{
if ((abs(Diff.x) <= 127) && (abs(Diff.y) <= 127) && (abs(Diff.z) <= 127)) // Limitations of a Byte
{
// Difference within Byte limitations, use a relative move packet
if (m_bDirtyOrientation)
{
m_World->BroadcastEntityRelMoveLook(*this, Vector3<Int8>(Diff), a_Exclude);
m_bDirtyOrientation = false;
}
else
{
m_World->BroadcastEntityRelMove(*this, Vector3<Int8>(Diff), a_Exclude);
}
// Clients seem to store two positions, one for the velocity packet and one for the teleport / relmove packet
// The latter is only changed with a relmove / teleport, and m_LastSentPosition stores this position
m_LastSentPosition = GetPosition();
}
else
{
// Too big a movement, do a teleport
m_World->BroadcastTeleportEntity(*this, a_Exclude);
m_LastSentPosition = GetPosition(); // See above
m_bDirtyOrientation = false;
}
}
}
if (m_bDirtyHead)
{
m_World->BroadcastEntityHeadLook(*this, a_Exclude);
m_bDirtyHead = false;
}
if (m_bDirtyOrientation)
{
// Send individual update in case above (sending with rel-move packet) wasn't done
GetWorld()->BroadcastEntityLook(*this, a_Exclude);
m_bDirtyOrientation = false;
}
}
}
cEntity * cEntity::GetAttached()
{
return m_AttachedTo;
}
void cEntity::AttachTo(cEntity * a_AttachTo)
{
if (m_AttachedTo == a_AttachTo)
{
// Already attached to that entity, nothing to do here
return;
}
2014-10-20 20:55:07 +00:00
if (m_AttachedTo != nullptr)
{
// Detach from any previous entity:
Detach();
}
1.9 / 1.9.2 / 1.9.3 / 1.9.4 protocol support (#3135) * Semistable update to 15w31a I'm going through snapshots in a sequential order since it should make things easier, and since protocol version history is written. * Update to 15w34b protocol Also, fix an issue with the Entity Equipment packet from the past version. Clients are able to connect and do stuff! * Partially update to 15w35e Chunk data doesn't work, but the client joins. I'm waiting to do chunk data because chunk data has an incomplete format until 15w36d. * Add '/blk' debug command This command lets one see what block they are looking at, and makes figuring out what's supposed to be where in a highly broken chunk possible. * Fix CRLF normalization in CheckBasicStyle.lua Normally, this doesn't cause an issue, but when running from cygwin, it detects the CR as whitespace and creates thousands of violations for every single line. Lua, when run on windows, will normalize automatically, but when run via cygwin, it won't. The bug was simply that gsub was returning a replaced version, but not changing the parameter, so the replaced version was ignored. * Update to 15w40b This includes chunk serialization. Fully functional chunk serialization for 1.9. I'm not completely happy with the chunk serialization as-is (correct use of palettes would be great), but cuberite also doesn't skip sending empty chunks so this performance optimization should probably come later. The creation of a full buffer is suboptimal, but it's the easiest way to implement this code. * Write long-by-long rather than creating a buffer This is a bit faster and should be equivalent. However, the code still doesn't look too good. * Update to 15w41a protocol This includes the new set passengers packet, which works off of the ridden entity, not the rider. That means, among other things, that information about the previously ridden vehicle is needed when detaching. So a new method with that info was added. * Update to 15w45a * 15w51b protocol * Update to 1.9.0 protocol Closes #3067. There are still a few things that need to be worked out (picking up items, effects, particles, and most importantly inventory), but in general this should work. I'll make a few more changes tomorrow to get the rest of the protocol set up, along with 1.9.1/1.9.2 (which did make a few changes). Chunks, however, _are_ working, along with most other parts of the game (placing/breaking blocks). * Fix item pickup packet not working That was a silly mistake, but at least it was an easy one. * 1.9.2 protocol support * Fix version info found in server list ping Thus, the client reports that it can connect rather than saying that the server is out of date. This required creating separate classes for 1.9.1 and 1.9.2, unfortunately. * Fix build errors generated by clang These didn't happen in MSVC. * Add protocol19x.cpp and protocol19x.h to CMakeLists * Ignore warnings in protocol19x that are ignored in protocol18x * Document BLOCK_FACE and DIG_STATUS constants * Fix BLOCK_FACE links and add separate section for DIG_STATUS * Fix bat animation and object spawning The causes of both of these are explained in #3135, but the gist is that both were typos. * Implement Use Item packet This means that buckets, bows, fishing rods, and several other similar items now work when not looking at a block. * Handle DIG_STATUS_SWAP_ITEM_IN_HAND * Add support for spawn eggs and potions The items are transformed from the 1.9 version to the 1.8 version when reading and transformed back when sending. * Remove spammy potion debug logging * Fix wolf collar color metadata The wrong type was being used, causing several clientside issues (including the screen going black). * Fix 1.9 chunk sending in the nether The nether and the end don't send skylight. * Fix clang build errors * Fix water bottles becoming mundane potions This happened because the can become splash potion bit got set incorrectly. Water bottles and mundane potions are only differentiated by the fact that water bottles have a metadata of 0, so setting that bit made it a mundane potion. Also add missing break statements to the read item NBT switch, which would otherwise break items with custom names and also cause incorrect "Unimplemented NBT data when parsing!" logging. * Copy Protocol18x as Protocol19x Aditionally, method and class names have been swapped to clean up other diffs. This commit is only added to make the following diffs more readable; it doesn't make any other changes (beyond class names). * Make thrown potions use the correct appearence This was caused by potions now using metadata. * Add missing api doc for cSplashPotionEntity::GetItem * Fix compile error in SplashPotionEntity.cpp * Fix fix of cSplashPotionEntity API doc * Temporarilly disable fall damage particles These were causing issues in 1.9 due to the changed effect ID. * Properly send a kick packet when connecting with an invalid version This means that the client no longer waits on the server screen with no indication whatsoever. However, right now the server list ping isn't implemented for unknown versions, so it'll only load "Old" on the ping. I also added a GetVarIntSize method to cByteBuffer. This helps clean up part of the code here (and I think it could clean up other parts), but it may make sense for it to be moved elsewhere (or declared in a different way). * Handle server list pings from unrecognized versions This isn't the cleanest way of writing it (it feels odd to use ProtocolRecognizer to send packets, and the addition of m_InPingForUnrecognizedVersion feels like the wrong technique), but it works and I can't think of a better way (apart from creating a full separate protocol class to handle only the ping... which would be worse). * Use cPacketizer for the disconnect packet This also should fix clang build errors. * Add 1.9.3 / 1.9.4 support * Fix incorrect indentation in APIDesc
2016-05-14 19:12:42 +00:00
// Update state information
m_AttachedTo = a_AttachTo;
a_AttachTo->m_Attachee = this;
1.9 / 1.9.2 / 1.9.3 / 1.9.4 protocol support (#3135) * Semistable update to 15w31a I'm going through snapshots in a sequential order since it should make things easier, and since protocol version history is written. * Update to 15w34b protocol Also, fix an issue with the Entity Equipment packet from the past version. Clients are able to connect and do stuff! * Partially update to 15w35e Chunk data doesn't work, but the client joins. I'm waiting to do chunk data because chunk data has an incomplete format until 15w36d. * Add '/blk' debug command This command lets one see what block they are looking at, and makes figuring out what's supposed to be where in a highly broken chunk possible. * Fix CRLF normalization in CheckBasicStyle.lua Normally, this doesn't cause an issue, but when running from cygwin, it detects the CR as whitespace and creates thousands of violations for every single line. Lua, when run on windows, will normalize automatically, but when run via cygwin, it won't. The bug was simply that gsub was returning a replaced version, but not changing the parameter, so the replaced version was ignored. * Update to 15w40b This includes chunk serialization. Fully functional chunk serialization for 1.9. I'm not completely happy with the chunk serialization as-is (correct use of palettes would be great), but cuberite also doesn't skip sending empty chunks so this performance optimization should probably come later. The creation of a full buffer is suboptimal, but it's the easiest way to implement this code. * Write long-by-long rather than creating a buffer This is a bit faster and should be equivalent. However, the code still doesn't look too good. * Update to 15w41a protocol This includes the new set passengers packet, which works off of the ridden entity, not the rider. That means, among other things, that information about the previously ridden vehicle is needed when detaching. So a new method with that info was added. * Update to 15w45a * 15w51b protocol * Update to 1.9.0 protocol Closes #3067. There are still a few things that need to be worked out (picking up items, effects, particles, and most importantly inventory), but in general this should work. I'll make a few more changes tomorrow to get the rest of the protocol set up, along with 1.9.1/1.9.2 (which did make a few changes). Chunks, however, _are_ working, along with most other parts of the game (placing/breaking blocks). * Fix item pickup packet not working That was a silly mistake, but at least it was an easy one. * 1.9.2 protocol support * Fix version info found in server list ping Thus, the client reports that it can connect rather than saying that the server is out of date. This required creating separate classes for 1.9.1 and 1.9.2, unfortunately. * Fix build errors generated by clang These didn't happen in MSVC. * Add protocol19x.cpp and protocol19x.h to CMakeLists * Ignore warnings in protocol19x that are ignored in protocol18x * Document BLOCK_FACE and DIG_STATUS constants * Fix BLOCK_FACE links and add separate section for DIG_STATUS * Fix bat animation and object spawning The causes of both of these are explained in #3135, but the gist is that both were typos. * Implement Use Item packet This means that buckets, bows, fishing rods, and several other similar items now work when not looking at a block. * Handle DIG_STATUS_SWAP_ITEM_IN_HAND * Add support for spawn eggs and potions The items are transformed from the 1.9 version to the 1.8 version when reading and transformed back when sending. * Remove spammy potion debug logging * Fix wolf collar color metadata The wrong type was being used, causing several clientside issues (including the screen going black). * Fix 1.9 chunk sending in the nether The nether and the end don't send skylight. * Fix clang build errors * Fix water bottles becoming mundane potions This happened because the can become splash potion bit got set incorrectly. Water bottles and mundane potions are only differentiated by the fact that water bottles have a metadata of 0, so setting that bit made it a mundane potion. Also add missing break statements to the read item NBT switch, which would otherwise break items with custom names and also cause incorrect "Unimplemented NBT data when parsing!" logging. * Copy Protocol18x as Protocol19x Aditionally, method and class names have been swapped to clean up other diffs. This commit is only added to make the following diffs more readable; it doesn't make any other changes (beyond class names). * Make thrown potions use the correct appearence This was caused by potions now using metadata. * Add missing api doc for cSplashPotionEntity::GetItem * Fix compile error in SplashPotionEntity.cpp * Fix fix of cSplashPotionEntity API doc * Temporarilly disable fall damage particles These were causing issues in 1.9 due to the changed effect ID. * Properly send a kick packet when connecting with an invalid version This means that the client no longer waits on the server screen with no indication whatsoever. However, right now the server list ping isn't implemented for unknown versions, so it'll only load "Old" on the ping. I also added a GetVarIntSize method to cByteBuffer. This helps clean up part of the code here (and I think it could clean up other parts), but it may make sense for it to be moved elsewhere (or declared in a different way). * Handle server list pings from unrecognized versions This isn't the cleanest way of writing it (it feels odd to use ProtocolRecognizer to send packets, and the addition of m_InPingForUnrecognizedVersion feels like the wrong technique), but it works and I can't think of a better way (apart from creating a full separate protocol class to handle only the ping... which would be worse). * Use cPacketizer for the disconnect packet This also should fix clang build errors. * Add 1.9.3 / 1.9.4 support * Fix incorrect indentation in APIDesc
2016-05-14 19:12:42 +00:00
if (a_AttachTo != nullptr)
{
m_World->BroadcastAttachEntity(*this, *a_AttachTo);
}
}
void cEntity::Detach(void)
{
2014-10-20 20:55:07 +00:00
if (m_AttachedTo == nullptr)
{
1.9 / 1.9.2 / 1.9.3 / 1.9.4 protocol support (#3135) * Semistable update to 15w31a I'm going through snapshots in a sequential order since it should make things easier, and since protocol version history is written. * Update to 15w34b protocol Also, fix an issue with the Entity Equipment packet from the past version. Clients are able to connect and do stuff! * Partially update to 15w35e Chunk data doesn't work, but the client joins. I'm waiting to do chunk data because chunk data has an incomplete format until 15w36d. * Add '/blk' debug command This command lets one see what block they are looking at, and makes figuring out what's supposed to be where in a highly broken chunk possible. * Fix CRLF normalization in CheckBasicStyle.lua Normally, this doesn't cause an issue, but when running from cygwin, it detects the CR as whitespace and creates thousands of violations for every single line. Lua, when run on windows, will normalize automatically, but when run via cygwin, it won't. The bug was simply that gsub was returning a replaced version, but not changing the parameter, so the replaced version was ignored. * Update to 15w40b This includes chunk serialization. Fully functional chunk serialization for 1.9. I'm not completely happy with the chunk serialization as-is (correct use of palettes would be great), but cuberite also doesn't skip sending empty chunks so this performance optimization should probably come later. The creation of a full buffer is suboptimal, but it's the easiest way to implement this code. * Write long-by-long rather than creating a buffer This is a bit faster and should be equivalent. However, the code still doesn't look too good. * Update to 15w41a protocol This includes the new set passengers packet, which works off of the ridden entity, not the rider. That means, among other things, that information about the previously ridden vehicle is needed when detaching. So a new method with that info was added. * Update to 15w45a * 15w51b protocol * Update to 1.9.0 protocol Closes #3067. There are still a few things that need to be worked out (picking up items, effects, particles, and most importantly inventory), but in general this should work. I'll make a few more changes tomorrow to get the rest of the protocol set up, along with 1.9.1/1.9.2 (which did make a few changes). Chunks, however, _are_ working, along with most other parts of the game (placing/breaking blocks). * Fix item pickup packet not working That was a silly mistake, but at least it was an easy one. * 1.9.2 protocol support * Fix version info found in server list ping Thus, the client reports that it can connect rather than saying that the server is out of date. This required creating separate classes for 1.9.1 and 1.9.2, unfortunately. * Fix build errors generated by clang These didn't happen in MSVC. * Add protocol19x.cpp and protocol19x.h to CMakeLists * Ignore warnings in protocol19x that are ignored in protocol18x * Document BLOCK_FACE and DIG_STATUS constants * Fix BLOCK_FACE links and add separate section for DIG_STATUS * Fix bat animation and object spawning The causes of both of these are explained in #3135, but the gist is that both were typos. * Implement Use Item packet This means that buckets, bows, fishing rods, and several other similar items now work when not looking at a block. * Handle DIG_STATUS_SWAP_ITEM_IN_HAND * Add support for spawn eggs and potions The items are transformed from the 1.9 version to the 1.8 version when reading and transformed back when sending. * Remove spammy potion debug logging * Fix wolf collar color metadata The wrong type was being used, causing several clientside issues (including the screen going black). * Fix 1.9 chunk sending in the nether The nether and the end don't send skylight. * Fix clang build errors * Fix water bottles becoming mundane potions This happened because the can become splash potion bit got set incorrectly. Water bottles and mundane potions are only differentiated by the fact that water bottles have a metadata of 0, so setting that bit made it a mundane potion. Also add missing break statements to the read item NBT switch, which would otherwise break items with custom names and also cause incorrect "Unimplemented NBT data when parsing!" logging. * Copy Protocol18x as Protocol19x Aditionally, method and class names have been swapped to clean up other diffs. This commit is only added to make the following diffs more readable; it doesn't make any other changes (beyond class names). * Make thrown potions use the correct appearence This was caused by potions now using metadata. * Add missing api doc for cSplashPotionEntity::GetItem * Fix compile error in SplashPotionEntity.cpp * Fix fix of cSplashPotionEntity API doc * Temporarilly disable fall damage particles These were causing issues in 1.9 due to the changed effect ID. * Properly send a kick packet when connecting with an invalid version This means that the client no longer waits on the server screen with no indication whatsoever. However, right now the server list ping isn't implemented for unknown versions, so it'll only load "Old" on the ping. I also added a GetVarIntSize method to cByteBuffer. This helps clean up part of the code here (and I think it could clean up other parts), but it may make sense for it to be moved elsewhere (or declared in a different way). * Handle server list pings from unrecognized versions This isn't the cleanest way of writing it (it feels odd to use ProtocolRecognizer to send packets, and the addition of m_InPingForUnrecognizedVersion feels like the wrong technique), but it works and I can't think of a better way (apart from creating a full separate protocol class to handle only the ping... which would be worse). * Use cPacketizer for the disconnect packet This also should fix clang build errors. * Add 1.9.3 / 1.9.4 support * Fix incorrect indentation in APIDesc
2016-05-14 19:12:42 +00:00
// Already not attached to any entity, our work is done
return;
}
1.9 / 1.9.2 / 1.9.3 / 1.9.4 protocol support (#3135) * Semistable update to 15w31a I'm going through snapshots in a sequential order since it should make things easier, and since protocol version history is written. * Update to 15w34b protocol Also, fix an issue with the Entity Equipment packet from the past version. Clients are able to connect and do stuff! * Partially update to 15w35e Chunk data doesn't work, but the client joins. I'm waiting to do chunk data because chunk data has an incomplete format until 15w36d. * Add '/blk' debug command This command lets one see what block they are looking at, and makes figuring out what's supposed to be where in a highly broken chunk possible. * Fix CRLF normalization in CheckBasicStyle.lua Normally, this doesn't cause an issue, but when running from cygwin, it detects the CR as whitespace and creates thousands of violations for every single line. Lua, when run on windows, will normalize automatically, but when run via cygwin, it won't. The bug was simply that gsub was returning a replaced version, but not changing the parameter, so the replaced version was ignored. * Update to 15w40b This includes chunk serialization. Fully functional chunk serialization for 1.9. I'm not completely happy with the chunk serialization as-is (correct use of palettes would be great), but cuberite also doesn't skip sending empty chunks so this performance optimization should probably come later. The creation of a full buffer is suboptimal, but it's the easiest way to implement this code. * Write long-by-long rather than creating a buffer This is a bit faster and should be equivalent. However, the code still doesn't look too good. * Update to 15w41a protocol This includes the new set passengers packet, which works off of the ridden entity, not the rider. That means, among other things, that information about the previously ridden vehicle is needed when detaching. So a new method with that info was added. * Update to 15w45a * 15w51b protocol * Update to 1.9.0 protocol Closes #3067. There are still a few things that need to be worked out (picking up items, effects, particles, and most importantly inventory), but in general this should work. I'll make a few more changes tomorrow to get the rest of the protocol set up, along with 1.9.1/1.9.2 (which did make a few changes). Chunks, however, _are_ working, along with most other parts of the game (placing/breaking blocks). * Fix item pickup packet not working That was a silly mistake, but at least it was an easy one. * 1.9.2 protocol support * Fix version info found in server list ping Thus, the client reports that it can connect rather than saying that the server is out of date. This required creating separate classes for 1.9.1 and 1.9.2, unfortunately. * Fix build errors generated by clang These didn't happen in MSVC. * Add protocol19x.cpp and protocol19x.h to CMakeLists * Ignore warnings in protocol19x that are ignored in protocol18x * Document BLOCK_FACE and DIG_STATUS constants * Fix BLOCK_FACE links and add separate section for DIG_STATUS * Fix bat animation and object spawning The causes of both of these are explained in #3135, but the gist is that both were typos. * Implement Use Item packet This means that buckets, bows, fishing rods, and several other similar items now work when not looking at a block. * Handle DIG_STATUS_SWAP_ITEM_IN_HAND * Add support for spawn eggs and potions The items are transformed from the 1.9 version to the 1.8 version when reading and transformed back when sending. * Remove spammy potion debug logging * Fix wolf collar color metadata The wrong type was being used, causing several clientside issues (including the screen going black). * Fix 1.9 chunk sending in the nether The nether and the end don't send skylight. * Fix clang build errors * Fix water bottles becoming mundane potions This happened because the can become splash potion bit got set incorrectly. Water bottles and mundane potions are only differentiated by the fact that water bottles have a metadata of 0, so setting that bit made it a mundane potion. Also add missing break statements to the read item NBT switch, which would otherwise break items with custom names and also cause incorrect "Unimplemented NBT data when parsing!" logging. * Copy Protocol18x as Protocol19x Aditionally, method and class names have been swapped to clean up other diffs. This commit is only added to make the following diffs more readable; it doesn't make any other changes (beyond class names). * Make thrown potions use the correct appearence This was caused by potions now using metadata. * Add missing api doc for cSplashPotionEntity::GetItem * Fix compile error in SplashPotionEntity.cpp * Fix fix of cSplashPotionEntity API doc * Temporarilly disable fall damage particles These were causing issues in 1.9 due to the changed effect ID. * Properly send a kick packet when connecting with an invalid version This means that the client no longer waits on the server screen with no indication whatsoever. However, right now the server list ping isn't implemented for unknown versions, so it'll only load "Old" on the ping. I also added a GetVarIntSize method to cByteBuffer. This helps clean up part of the code here (and I think it could clean up other parts), but it may make sense for it to be moved elsewhere (or declared in a different way). * Handle server list pings from unrecognized versions This isn't the cleanest way of writing it (it feels odd to use ProtocolRecognizer to send packets, and the addition of m_InPingForUnrecognizedVersion feels like the wrong technique), but it works and I can't think of a better way (apart from creating a full separate protocol class to handle only the ping... which would be worse). * Use cPacketizer for the disconnect packet This also should fix clang build errors. * Add 1.9.3 / 1.9.4 support * Fix incorrect indentation in APIDesc
2016-05-14 19:12:42 +00:00
m_World->BroadcastDetachEntity(*this, *m_AttachedTo);
2014-10-20 20:55:07 +00:00
m_AttachedTo->m_Attachee = nullptr;
m_AttachedTo = nullptr;
}
bool cEntity::IsA(const char * a_ClassName) const
{
return ((a_ClassName != nullptr) && (strcmp(a_ClassName, "cEntity") == 0));
}
bool cEntity::IsAttachedTo(const cEntity * a_Entity) const
{
if ((m_AttachedTo != nullptr) && (a_Entity->GetUniqueID() == m_AttachedTo->GetUniqueID()))
{
return true;
}
return false;
}
void cEntity::SetHeadYaw(double a_HeadYaw)
{
m_HeadYaw = a_HeadYaw;
m_bDirtyHead = true;
WrapHeadYaw();
}
void cEntity::SetHeight(double a_Height)
{
m_Height = a_Height;
}
void cEntity::SetMass(double a_Mass)
{
2014-08-03 05:35:29 +00:00
// 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);
}
void cEntity::SetYaw(double a_Yaw)
{
m_Rot.x = a_Yaw;
m_bDirtyOrientation = true;
WrapRotation();
}
void cEntity::SetPitch(double a_Pitch)
{
m_Rot.y = a_Pitch;
m_bDirtyOrientation = true;
WrapRotation();
}
void cEntity::SetRoll(double a_Roll)
{
m_Rot.z = a_Roll;
m_bDirtyOrientation = true;
}
void cEntity::SetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ)
{
DoSetSpeed(a_SpeedX, a_SpeedY, a_SpeedZ);
}
void cEntity::SetSpeedX(double a_SpeedX)
{
SetSpeed(a_SpeedX, m_Speed.y, m_Speed.z);
}
void cEntity::SetSpeedY(double a_SpeedY)
{
SetSpeed(m_Speed.x, a_SpeedY, m_Speed.z);
}
void cEntity::SetSpeedZ(double a_SpeedZ)
{
SetSpeed(m_Speed.x, m_Speed.y, a_SpeedZ);
}
void cEntity::SetWidth(double a_Width)
{
m_Width = a_Width;
}
void cEntity::AddSpeed(double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeedZ)
{
DoSetSpeed(m_Speed.x + a_AddSpeedX, m_Speed.y + a_AddSpeedY, m_Speed.z + a_AddSpeedZ);
}
void cEntity::AddSpeedX(double a_AddSpeedX)
{
AddSpeed(a_AddSpeedX, 0, 0);
}
void cEntity::AddSpeedY(double a_AddSpeedY)
{
AddSpeed(0, a_AddSpeedY, 0);
}
void cEntity::AddSpeedZ(double a_AddSpeedZ)
{
AddSpeed(0, 0, a_AddSpeedZ);
}
void cEntity::HandleSpeedFromAttachee(float a_Forward, float a_Sideways)
{
Vector3d LookVector = m_Attachee->GetLookVector();
double AddSpeedX = LookVector.x * a_Forward + LookVector.z * a_Sideways;
double AddSpeedZ = LookVector.z * a_Forward - LookVector.x * a_Sideways;
SetSpeed(AddSpeedX, 0, AddSpeedZ);
BroadcastMovementUpdate();
}
2013-09-05 22:04:49 +00:00
void cEntity::SteerVehicle(float a_Forward, float a_Sideways)
{
2014-10-20 20:55:07 +00:00
if (m_AttachedTo == nullptr)
2013-09-05 22:04:49 +00:00
{
return;
}
if ((a_Forward != 0.0f) || (a_Sideways != 0.0f))
2013-09-05 22:04:49 +00:00
{
m_AttachedTo->HandleSpeedFromAttachee(a_Forward, a_Sideways);
2013-09-05 22:04:49 +00:00
}
}
bool cEntity::IsTicking(void) const
{
ASSERT(!(m_IsTicking && (m_ParentChunk == nullptr))); // We shouldn't be ticking if we have no parent chunk
return m_IsTicking;
}
////////////////////////////////////////////////////////////////////////////////
// Get look vector (this is NOT a rotation!)
Vector3d cEntity::GetLookVector(void) const
{
Matrix4d m;
m.Init(Vector3d(), 0, m_Rot.x, -m_Rot.y);
Vector3d Look = m.Transform(Vector3d(0, 0, 1));
return Look;
}
////////////////////////////////////////////////////////////////////////////////
// Set position
2015-07-21 20:25:37 +00:00
void cEntity::SetPosition(const Vector3d & a_Position)
{
2015-07-21 20:25:37 +00:00
m_LastPosition = m_Position;
m_Position = a_Position;
}
2017-08-21 08:46:41 +00:00
void cEntity::AddLeashedMob(cMonster * a_Monster)
{
// Not there already
ASSERT(std::find(m_LeashedMobs.begin(), m_LeashedMobs.end(), a_Monster) == m_LeashedMobs.end());
m_LeashedMobs.push_back(a_Monster);
}
2017-08-21 08:46:41 +00:00
void cEntity::RemoveLeashedMob(cMonster * a_Monster)
{
ASSERT(a_Monster->GetLeashedTo() == this);
// Must exists
ASSERT(std::find(m_LeashedMobs.begin(), m_LeashedMobs.end(), a_Monster) != m_LeashedMobs.end());
m_LeashedMobs.remove(a_Monster);
}
float cEntity::GetExplosionExposureRate(Vector3d a_ExplosionPosition, float a_ExlosionPower)
{
double EntitySize = m_Width * m_Width * m_Height;
if (EntitySize <= 0)
{
// Handle entity with invalid size
return 0;
}
cBoundingBox EntityBox(GetPosition(), m_Width / 2, m_Height);
cBoundingBox ExplosionBox(a_ExplosionPosition, a_ExlosionPower * 2.0);
cBoundingBox IntersectionBox(EntityBox);
bool Overlap = EntityBox.Intersect(ExplosionBox, IntersectionBox);
if (Overlap)
{
Vector3d Diff = IntersectionBox.GetMax() - IntersectionBox.GetMin();
double OverlapSize = Diff.x * Diff.y * Diff.z;
return static_cast<float>(OverlapSize / EntitySize);
}
else
{
return 0;
}
}