Files
mcpe/source/world/entity/Mob.cpp

882 lines
15 KiB
C++

/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#include "Mob.hpp"
#include "world/level/Level.hpp"
Mob::Mob(Level* pLevel) : Entity(pLevel)
{
field_DC = 10;
field_E8 = 0.0f;
field_EC = 0.0f;
field_F0 = 0;
field_F4 = 0.0f;
field_F8 = 0.0f;
m_health = 10;
field_100 = 20;
field_104 = 0;
field_108 = 0;
field_10C = 0.0f;
field_110 = 0;
field_114 = 0;
field_118 = 0.0f;
field_11C = 0.0f;
field_120 = 0;
field_124 = 0;
field_128 = 0.0f;
field_12C = 0.0f;
field_130 = 0.0f;
field_AFC = 0;
field_B00 = 0.0f;
field_B04 = 0.0f;
field_B08 = 0.0f;
field_B0C = 0;
field_B10 = 0;
field_B14 = 0.7f;
field_B48 = 0;
field_B4C = 0.0f;
field_B50 = 0.0f;
field_B54 = 0.0f;
field_B58 = 0.0f;
field_B5C = 0.0f;
field_B60 = 1.0f;
field_B64 = 0;
field_B68 = 1;
field_B69 = 0;
field_B6C = 0;
field_B70 = 0.0f;
field_B74 = 0.0f;
field_B78 = 0.0f;
field_B7C = 0.0f;
field_B80 = 0.0f;
field_B84 = 0;
m_pEntLookedAt = nullptr;
m_class = "";
m_renderType = RENDER_MODEL;
field_34 = 1;
field_E4 = (Mth::random() + 1.0f) * 0.01f;
setPos(m_pos.x, m_pos.y, m_pos.z);
field_E0 = Mth::random() * 12398.0f;
m_yaw = float(Mth::random() * M_PI);
field_A8 = 0.5f;
}
Mob::~Mob()
{
}
void Mob::reset()
{
Entity::reset();
// TODO what fields to reset?
m_health = 10;
}
void Mob::lerpTo(float x, float y, float z, float yaw, float pitch, int i)
{
field_B70 = x;
field_B74 = y + field_84;
field_B78 = z;
field_B7C = yaw;
field_B80 = pitch;
field_B6C = i;
}
void Mob::tick()
{
superTick();
if (field_B6C > 0)
{
float xPos = m_pos.x + (field_B70 - m_pos.x) / field_B6C;
float yPos = m_pos.y + (field_B74 - m_pos.y) / field_B6C;
float zPos = m_pos.z + (field_B78 - m_pos.z) / field_B6C;
float ang = field_B7C - m_yaw;
while (ang < -180.0f) ang += 360.0f;
while (ang >= 180.0f) ang -= 360.0f;
m_yaw += ang / float(field_B6C);
m_pitch += (field_B80 - m_pitch) / float(field_B6C);
setPos(xPos, yPos, zPos);
setRot(m_yaw, m_pitch);
field_B6C--;
}
aiStep();
updateWalkAnim();
//@TODO: untangle this variable mess
float deltaX, deltaZ, dist, x1, x2, x3, x4, x5, x6, x7, field_E8_2, field_E8_new, v36;
bool angleOOB = false;
deltaX = m_pos.x - field_3C.x;
deltaZ = m_pos.z - field_3C.z;
dist = Mth::sqrt(deltaZ * deltaZ + deltaX * deltaX);
field_E8_2 = field_E8;
x1 = field_E8_2;
field_B4C = field_B50;
if (dist > 0.05f)
{
x2 = dist * 3.0f;
v36 = Mth::atan2(deltaZ, deltaX);
x3 = 1.0f;
x1 = ((v36 * 180.0f) / float(M_PI)) - 90.0f;
field_E8_2 = this->field_E8;
}
else
{
x3 = 0.0f;
x2 = 0.0f;
}
x4 = field_F8;
if (x4 <= 0.0f)
x4 = m_yaw;
else
x4 = x1 = m_yaw;
if (!field_7C)
x3 = 0.0f;
field_B50 += (x3 - field_B50) * 0.3f;
x5 = x1 - field_E8_2;
while (x5 < -180.0f)
x5 += 360.0f;
while (x5 >= 180.0f)
x5 -= 360.0f;
field_E8_new = field_E8_2 + (float)(x5 * 0.3);
this->field_E8 = field_E8_new;
x6 = x4 - field_E8_new;
while (x6 < -180.0f)
x6 += 360.0f;
while (x6 >= 180.0f)
x6 -= 360.0f;
angleOOB = x6 < -90.0f || x6 >= 90.0f;
if (x6 < -75.0f)
{
x7 = x4 + 75.0f;
x6 = -75.0f;
field_E8 = x4 + 75.0f;
goto LABEL_30;
}
if (x6 >= 75.0f)
{
x7 = x4 - 75.0f;
x6 = 75.0f;
field_E8 = x4 - 75.0f;
LABEL_30:
field_E8 = x7 + (x6 * 0.2f);
goto LABEL_31;
}
x7 = x4 - x6;
field_E8 = x4 - x6;
if (x6 * x6 > 2500.0f)
goto LABEL_30;
LABEL_31:
if (angleOOB)
x2 = -x2;
while (x4 - field_5C < -180.0f)
field_5C -= 360.0f;
while (x4 - field_5C >= 180.0f)
field_5C += 360.0f;
while (field_E8 - field_EC < -180.0f)
field_EC -= 360.0f;
while (field_E8 - field_EC >= 180.0f)
field_EC += 360.0f;
while (m_pitch - field_60 < -180.0f)
field_60 -= 360.0f;
while (m_pitch - field_60 >= 180.0f)
field_60 += 360.0f;
field_B54 += x2;
}
void Mob::baseTick()
{
field_F4 = field_F8;
Entity::baseTick();
if (isAlive() && isInWall())
hurt(nullptr, 1);
if (isAlive() && isUnderLiquid(Material::water) && !isWaterMob())
{
field_BC--;
if (field_BC == -20)
{
field_BC = 0;
for (int i = 0; i < 8; i++)
{
m_pLevel->addParticle("bubble",
m_pos.x + m_random.nextFloat() - m_random.nextFloat(),
m_pos.y + m_random.nextFloat() - m_random.nextFloat(),
m_pos.z + m_random.nextFloat() - m_random.nextFloat(),
m_vel.x, m_vel.y, m_vel.z
);
}
hurt(nullptr, 2);
}
}
else
{
field_BC = field_D0;
}
field_118 = field_11C;
if (field_114 > 0) field_114--;
if (field_104 > 0) field_104--;
if (field_B8 > 0) field_B8--;
if (m_health <= 0)
{
field_110++;
if (field_110 > 20)
{
beforeRemove();
remove();
for (int i = 0; i < 20; i++)
{
m_pLevel->addParticle("explode",
m_pos.x + 2 * field_88 * m_random.nextFloat() - field_88,
m_pos.y + field_8C * m_random.nextFloat(),
m_pos.z + 2 * field_88 * m_random.nextFloat() - field_88,
0.02f * (m_random.nextFloat() * 2 - 1) * (m_random.nextFloat() * 2 - 1),
0.02f * (m_random.nextFloat() * 2 - 1) * (m_random.nextFloat() * 2 - 1),
0.02f * (m_random.nextFloat() * 2 - 1) * (m_random.nextFloat() * 2 - 1)
);
}
}
}
field_B58 = field_B54;
field_EC = field_E8;
field_5C = m_yaw;
field_60 = m_pitch;
}
float Mob::getHeadHeight()
{
return 0.85f * field_8C;
}
bool Mob::isPickable()
{
return !m_bRemoved;
}
bool Mob::isPushable()
{
return !m_bRemoved;
}
bool Mob::isShootable()
{
return true;
}
bool Mob::isAlive()
{
if (m_bRemoved)
return false;
return m_health >= 0;
}
bool Mob::hurt(Entity *pAttacker, int damage)
{
if (m_pLevel->m_bIsMultiplayer)
return false;
field_AFC = m_pLevel->m_bIsMultiplayer;
if (m_health <= 0)
return false;
field_12C = 1.5f;
if (float(field_B8) <= float(field_DC) * 0.5f)
{
field_100 = m_health;
field_B8 = field_DC;
field_B84 = damage;
actuallyHurt(damage);
field_108 = 10;
field_104 = 10;
// not in 0.1
markHurt();
if (pAttacker)
{
float xd = pAttacker->m_pos.x - m_pos.x;
float zd = pAttacker->m_pos.z - m_pos.z;
while (zd * zd + xd * xd < 0.0001f)
{
xd = 0.01f * (Mth::random() - Mth::random());
zd = 0.01f * (Mth::random() - Mth::random());
}
float ang = atan2f(zd, xd);
v020_field_104 = ang * (180.0f / float(M_PI)) - m_yaw;
knockback(pAttacker, damage, xd, zd);
}
}
else
{
if (field_B84 >= damage)
return 0;
actuallyHurt(damage - field_B84);
field_B84 = damage;
}
field_10C = 0;
if (m_health <= 0)
die(pAttacker);
return true;
}
void Mob::animateHurt()
{
field_108 = 10;
field_10C = 0;
field_104 = 10;
}
void Mob::setSize(float rad, float height)
{
Entity::setSize(rad, height);
}
void Mob::outOfWorld()
{
hurt(nullptr, 4);
}
void Mob::causeFallDamage(float level)
{
int x = int(ceilf(level - 3));
if (x > 0)
{
// from 0.2.0
std::string fallSound;
if (x > 4)
fallSound = "damage.fallbig";
else
fallSound = "damage.fallsmall";
m_pLevel->playSound(this, fallSound, 1.0f, 1.0f);
hurt(nullptr, x);
//@HUH: useless call to getTile? or could this be a return value of some sort
//Entity::causeFallDamage returns nothing though, so....
m_pLevel->getTile(Mth::floor(m_pos.x), Mth::floor(m_pos.y - 0.2f - field_84), Mth::floor(m_pos.z));
}
}
void Mob::knockback(Entity* pEnt, int a, float x, float z)
{
float power = Mth::invSqrt(x * x + z * z);
m_vel.x = m_vel.x * 0.5f - x * power * 0.4f;
m_vel.y = m_vel.y * 0.5f + 0.4f;
m_vel.z = m_vel.z * 0.5f - z * power * 0.4f;
//@BUG: maybe this is meant to behave differently.
// Unless the player is falling, the Y vel will always be 0.4.
if (m_vel.y > 0.4f)
m_vel.y = 0.4f;
}
bool Mob::onLadder()
{
int tileX = Mth::floor(m_pos.x);
int tileZ = Mth::floor(m_pos.z);
int tileY = Mth::floor(m_hitbox.min.y);
//@INFO: Pre Beta 1.5 stair behaviour
return
m_pLevel->getTile(tileX, tileY + 0, tileZ) == Tile::ladder->m_ID ||
m_pLevel->getTile(tileX, tileY + 1, tileZ) == Tile::ladder->m_ID;
}
void Mob::spawnAnim()
{
}
void Mob::playAmbientSound()
{
}
int Mob::getAmbientSoundInterval()
{
return 80;
}
void Mob::heal(int health)
{
if (m_health <= 0)
return;
m_health += health;
if (m_health > C_MAX_MOB_HEALTH)
m_health = C_MAX_MOB_HEALTH;
field_B8 = field_DC / 2;
}
HitResult Mob::pick(float f1, float f2)
{
Vec3 pos = getPos(f2);
Vec3 view = getViewVector(f2);
Vec3 limit = pos + view * f1;
return m_pLevel->clip(pos, limit);
}
void Mob::travel(float a2, float a3)
{
float x1, x2, dragFactor, oldYPos = m_pos.y;
if (isInWater())
{
moveRelative(a2, a3, 0.02f);
move(m_vel.x, m_vel.y, m_vel.z);
x1 = 0.8f;
goto label_3;
}
if (isInLava())
{
moveRelative(a2, a3, 0.02f);
move(m_vel.x, m_vel.y, m_vel.z);
x1 = 0.5f;
label_3:
m_vel.y = m_vel.y * x1 - 0.02f;
m_vel.x *= x1;
m_vel.z *= x1;
if (field_7D)
{
if (isFree(m_vel.x, m_vel.y + 0.6f - m_pos.y + oldYPos, m_vel.z))
m_vel.y = 0.3f;
}
return;
}
if (!field_7C)
{
x2 = 0.02f;
}
else
{
float _x1;
int tileX = Mth::floor(m_pos.x);
int tileY = Mth::floor(m_hitbox.min.y);
int tileZ = Mth::floor(m_pos.z);
TileID tile = m_pLevel->getTile(tileX, tileY - 1, tileZ);
if (tile <= 0)
_x1 = 0.546f;
else
_x1 = Tile::tiles[tile]->m_acceleration * 0.91f;
assert(_x1 != 0.0f);
x2 = (0.16277f / (_x1 * _x1 * _x1)) * 0.1f;
}
moveRelative(a2, a3, x2);
if (!field_7C)
{
dragFactor = 0.91f;
}
else
{
//@HUH: repeated code. Could be an inlined function?
int tileX = Mth::floor(m_pos.x);
int tileY = Mth::floor(m_hitbox.min.y);
int tileZ = Mth::floor(m_pos.z);
TileID tile = m_pLevel->getTile(tileX, tileY - 1, tileZ);
if (tile <= 0)
dragFactor = 0.546f;
else
dragFactor = Tile::tiles[tile]->m_acceleration * 0.91f;
}
if (onLadder())
{
m_distanceFallen = 0.0f;
if (m_vel.y < -0.15f)
m_vel.y = -0.15f;
if (isSneaking() && m_vel.y < 0)
m_vel.y = 0;
}
move(m_vel.x, m_vel.y, m_vel.z);
if (field_7D && onLadder())
m_vel.y = 0.2f;
m_vel.y = (m_vel.y - 0.08f) * 0.98f; // gravity?
// drag
m_vel.x *= dragFactor;
m_vel.z *= dragFactor;
}
void Mob::die(Entity* pCulprit)
{
if (pCulprit && field_B48 > 0)
pCulprit->awardKillScore(pCulprit, field_B48);
field_B69 = true;
if (!m_pLevel->m_bIsMultiplayer)
dropDeathLoot();
}
bool Mob::canSee(Entity* pEnt)
{
Vec3 v1 = m_pos;
v1.y += getHeadHeight();
Vec3 v2 = pEnt->m_pos;
v2.y += pEnt->getHeadHeight();
return m_pLevel->clip(v1, v2).m_hitType == HitResult::NONE;
}
void Mob::updateWalkAnim()
{
field_128 = field_12C;
float diffX = m_pos.x - field_3C.x;
float diffZ = m_pos.z - field_3C.z;
float spd = 4.0f * Mth::sqrt(diffX * diffX + diffZ * diffZ);
if (spd > 1.0f)
spd = 1.0f;
field_12C += (spd - field_12C) * 0.4f;
field_130 += field_12C;
}
void Mob::aiStep()
{
if (isImmobile())
{
field_B0C = 0;
field_B00 = 0.0f;
field_B04 = 0.0f;
field_B08 = 0.0f;
}
else if (!field_F0)
{
updateAi();
}
bool bIsInWater = isInWater(), bIsInLava = isInLava();
if (field_B0C)
{
if (bIsInWater || bIsInLava)
m_vel.y += 0.04f;
else if (field_7C)
jumpFromGround();
}
field_B00 *= 0.98f;
field_B04 *= 0.98f;
field_B08 *= 0.9f;
travel(field_B00, field_B04);
AABB aabb = m_hitbox;
aabb.grow(0.2f, 0.2f, 0.2f);
EntityVector* pEnts = m_pLevel->getEntities(this, aabb);
for (EntityVector::iterator it = pEnts->begin(); it != pEnts->end(); it++)
{
Entity* pEnt = *it;
if (pEnt->isPushable())
pEnt->push(this);
}
}
bool Mob::isWaterMob()
{
return false;
}
void Mob::superTick()
{
Entity::tick();
}
void Mob::lookAt(Entity* pEnt, float a3, float a4)
{
float diffX = pEnt->m_pos.x - m_pos.x;
float diffZ = pEnt->m_pos.z - m_pos.z;
float x1 = atan2f(diffZ, diffX);
float x2 = atan2f((pEnt->m_hitbox.min.y + pEnt->m_hitbox.max.y) / 2 - m_pos.y - getHeadHeight(), Mth::sqrt(diffX * diffX + diffZ * diffZ));
m_pitch = -rotlerp(m_pitch, x2 * 180.0f / float(M_PI), a4);
m_yaw = rotlerp(m_yaw, x1 * 180.0f / float(M_PI) - 90.0f, a3);
}
bool Mob::isLookingAtAnEntity()
{
return m_pEntLookedAt != nullptr;
}
Entity* Mob::getLookingAt()
{
return m_pEntLookedAt;
}
void Mob::beforeRemove()
{
}
bool Mob::canSpawn()
{
return m_pLevel->getCubes(this, m_hitbox)->empty();
}
float Mob::getAttackAnim(float f)
{
float x = field_F8 - field_F4;
if (x < 0.0f)
x += 1.0f;
return field_F4 + (x * f);
}
Vec3 Mob::getPos(float f)
{
if (f == 1.0f)
return m_pos;
return Vec3(
Lerp(field_3C.x, m_pos.x, f),
Lerp(field_3C.y, m_pos.y, f),
Lerp(field_3C.z, m_pos.z, f)
);
}
Vec3 Mob::getLookAngle(float f)
{
return getViewVector(1.0f);
}
Vec3 Mob::getViewVector(float f)
{
constexpr float C_180_OVER_PI = 0.017453f;
constexpr float C_PI = 3.1416f;
if (f == 1.0)
{
float x1 = Mth::cos(-(m_yaw * C_180_OVER_PI) - C_PI);
float x2 = Mth::sin(-(m_yaw * C_180_OVER_PI) - C_PI);
float x3 = -Mth::cos(-(m_pitch * C_180_OVER_PI));
return Vec3(x1 * x3, Mth::sin(-(m_pitch * C_180_OVER_PI)), x2 * x3);
}
float x1 = field_60 + (m_pitch - field_60) * f;
float x2 = -((field_5C + (m_yaw - field_5C) * f) * C_180_OVER_PI) - C_PI;
float x3 = Mth::cos(x2);
float x4 = Mth::sin(x2);
float x5 = -(x1 * C_180_OVER_PI);
float x6 = -Mth::cos(x5);
return Vec3(x4 * x6, Mth::sin(x5), x3 * x6);
}
int Mob::getMaxSpawnClusterSize()
{
return 4;
}
void Mob::actuallyHurt(int damage)
{
#ifdef TEST_SURVIVAL_MODE
m_health -= damage;
#endif
}
bool Mob::removeWhenFarAway()
{
return false;
}
int Mob::getDeathLoot()
{
return 0;
}
void Mob::dropDeathLoot()
{
}
bool Mob::isImmobile()
{
return m_health <= 0;
}
void Mob::jumpFromGround()
{
m_vel.y = 0.42f;
}
void Mob::updateAi()
{
field_AFC++;
Entity* nearestPlayer = m_pLevel->getNearestPlayer(this, -1.0f);
// if we need to remove ourselves when far away, and there's a player around
// (if there's no players, we're on a headless server)
if (removeWhenFarAway() && nearestPlayer)
{
float distSqr = nearestPlayer->distanceToSqr_inline(m_pos.x, m_pos.y, m_pos.z);
if (distSqr > 16384.0f)
remove();
if (field_AFC > 600)
{
if (m_random.nextInt(800) == 0)
{
if (distSqr >= 1024.0f)
remove();
else
field_AFC = 0;
}
}
}
field_B00 = 0.0f;
field_B04 = 0.0f;
if (m_random.nextFloat() < 0.02f)
{
Entity* nearestPlayer = m_pLevel->getNearestPlayer(this, 8.0f);
if (nearestPlayer)
{
m_pEntLookedAt = nearestPlayer;
field_120 = m_random.nextInt(20) + 10;
}
}
if (m_pEntLookedAt)
{
lookAt(m_pEntLookedAt, 10.0f, getMaxHeadXRot());
// gaze timer
field_120--;
// if the entity was removed, or we're too far away, or our gaze timer is up
if (field_120 < 0 || m_pEntLookedAt->m_bRemoved || m_pEntLookedAt->distanceToSqr(this) > 64.0f)
// stop staring
m_pEntLookedAt = nullptr;
}
else
{
if (m_random.nextFloat() < 0.05f)
field_B08 = (m_random.nextFloat() - 0.5f) * 20.0f;
m_yaw += field_B08;
m_pitch = field_B10;
}
if (isInWater() || isInLava())
{
field_B0C = m_random.nextInt() < 0.8f;
}
}
int Mob::getMaxHeadXRot()
{
return 10;
}
float Mob::getSoundVolume()
{
return 1.0f;
}
std::string Mob::getAmbientSound()
{
return "";
}
std::string Mob::getHurtSound()
{
return "random.hurt";
}
std::string Mob::getDeathSound()
{
return "random.hurt";
}
void Mob::defineSynchedData()
{
}
float Mob::rotlerp(float a2, float a3, float a4)
{
float ang = a3 - a2;
while (ang < -180.0f) ang += 360.0f;
while (ang >= 180.0f) ang -= 360.0f;
if (ang > a4)
ang = a4;
if (ang < -a4)
ang = -a4;
return a2 + ang;
}