#include "Mob.h"

#include <cmath>

#include "player/Player.h"
#include "SharedFlags.h"
#include "../entity/EntityEvent.h"
#include "../level/Level.h"
#include "../level/material/Material.h"
#include "../level/tile/Tile.h"
#include "../../util/Random.h"
#include "../../util/PerfTimer.h"

#include "../../nbt/CompoundTag.h"

#include "../../network/RakNetInstance.h"
#include "../../network/packet/MoveEntityPacket.h"
#include "ai/control/MoveControl.h"
#include "ai/control/JumpControl.h"
#include "ai/PathNavigation.h"
#include "ai/Sensing.h"
#include "ai/goal/GoalSelector.h"
#include "../../network/packet/SetEntityMotionPacket.h"
#include "../item/ArmorItem.h"


Mob::Mob(Level* level)
:	super(level),
	invulnerableDuration(20),
	//hasHair(false),
	textureName("mob/char.png"),
	capeTextureName(""),
	allowAlpha(true),
	modelName(""),
	bobStrength(1),
	deathScore(0),
	renderOffset(0),
	//interpolateOnly(false),
	lookingAtId(0),
	jumping(false),
	defaultLookAngle(0.0f),
	runSpeed(0.7f),
	walkingSpeed(0.1f),
	flyingSpeed(0.02f),
	run(0), oRun(0),
	animStep(0), animStepO(0),
	lx(0), ly(0), lz(0), lxr(0), lyr(0),
	lSteps(0),
	walkAnimSpeed(0), walkAnimSpeedO(0), walkAnimPos(0),
	attackAnim(0), oAttackAnim(0),

	autoSendPosRot(true),
	sentX(0), sentY(0), sentZ(0),
	sentRotX(0), sentRotY(0),
	sentXd(0), sentYd(0), sentZd(0),
	arrowCount(0),
	removeArrowTime(0),
	speed(0),
	moveControl(NULL),
	jumpControl(NULL),
	navigation(NULL),
	goalSelector(NULL),
	targetSelector(NULL),
	sensing(NULL),
	swinging(false),
	swingTime(0),
	lastHurt(0),
	dmgSpill(0),
	bypassArmor(false)
{
	entityData.define(SharedFlagsInformation::DATA_SHARED_FLAGS_ID, (SynchedEntityData::TypeChar) 0);
	entityData.define(DATA_AIR_SUPPLY_ID, (SynchedEntityData::TypeShort) TOTAL_AIR_SUPPLY);

	_init();
	health = getMaxHealth();

	blocksBuilding = true;
	ambientSoundTime = -level->random.nextInt(100);

	rotA = (float) (Mth::random() + 1) * 0.01f;
	setPos(x, y, z);
	timeOffs = (float) Mth::random() * 12398;
	yRot = (float) (Mth::random() * Mth::PI * 2);

	this->footSize = 0.5f;

	// Initialize cape inertia positions
	xCape = x;
	yCape = y;
	zCape = z;

	xc = xCape;
	yc = yCape;
	zc = zCape;
}

Mob::~Mob() {
	//LOGI("destroying an entity! %p\n", this);
}

float Mob::getWalkingSpeedModifier() {
	float speed = 0.7f; // is 1.0f in desktop Minecraft

	//if (hasEffect(MobEffect.movementSpeed)) {
	//	speed *= 1.0f + .2f * (getEffect(MobEffect.movementSpeed).getAmplifier() + 1);
	//}
	//if (hasEffect(MobEffect.movementSlowdown)) {
	//	speed *= 1.0f - .15f * (getEffect(MobEffect.movementSlowdown).getAmplifier() + 1);
	//}
	return speed;
}

bool Mob::canSee( Entity* target )
{
	return !level->clip(Vec3(x, y + getHeadHeight(), z), Vec3(target->x, target->y + target->getHeadHeight(), target->z)).isHit();
}

std::string Mob::getTexture()
{
	return textureName;
}

void Mob::setTextureName(const std::string& name)
{
	textureName = name;
}

std::string Mob::getCapeTexture()
{
	return capeTextureName;
}

void Mob::setCapeTextureName(const std::string& name)
{
	capeTextureName = name;
}

bool Mob::isPickable()
{
	return !removed;
}

bool Mob::isPushable()
{
	return !removed;
}

float Mob::getHeadHeight()
{
	return bbHeight * 0.85f;
}

float Mob::getVoicePitch() {
	if (isBaby()) {
		return (random.nextFloat() - random.nextFloat()) * 0.2f + 1.5f;
	}
	return (random.nextFloat() - random.nextFloat()) * 0.2f + 1.0f;
}

int Mob::getAmbientSoundInterval()
{
	return 8 * SharedConstants::TicksPerSecond;
}

void Mob::playAmbientSound()
{
	const char* ambient = getAmbientSound();
	if (ambient != NULL) {
		level->playSound(this, ambient, getSoundVolume(), getVoicePitch());
	}
}

void Mob::baseTick()
{
	oAttackAnim = attackAnim;
	super::baseTick();

	TIMER_PUSH("mobBaseTick");

	if (((ambientSoundTime++ & 15) == 0) && random.nextInt(2000) < ambientSoundTime) {
	    ambientSoundTime = -getAmbientSoundInterval();
	    playAmbientSound();
	}

	if (isAlive() && isInWall()) {
		hurt(NULL, 1);
	}

	//if (fireImmune || level.isOnline) onFire = 0;

	if (isAlive() && isUnderLiquid(Material::water) && !isWaterMob()) {
		airSupply--;
		if (airSupply == -20) {
			airSupply = 0;
			for (int i = 0; i < 8; i++) {
				float xo = random.nextFloat() - random.nextFloat();
				float yo = random.nextFloat() - random.nextFloat();
				float zo = random.nextFloat() - random.nextFloat();
				level->addParticle(PARTICLETYPE(bubble), x + xo, y + yo, z + zo, xd, yd, zd);
			}
			hurt(NULL, 2);
		}
		//onFire = 0;
	} else {
		airSupply = airCapacity;
	}

	oTilt = tilt;
	if (attackTime > 0) attackTime--;
	if (hurtTime > 0) hurtTime--;
	if (invulnerableTime > 0) invulnerableTime--;

	if (health <= 0) {
	    deathTime++;
	    if (deathTime > SharedConstants::TicksPerSecond) {
	        beforeRemove();
	        remove();
	        for (int i = 0; i < 20; i++) {
	            float xa = (2.0f * random.nextFloat() - 1.0f) * (2.0f * random.nextFloat() - 1.0f) * 0.02f;
				float ya = (2.0f * random.nextFloat() - 1.0f) * (2.0f * random.nextFloat() - 1.0f) * 0.02f;
				float za = (2.0f * random.nextFloat() - 1.0f) * (2.0f * random.nextFloat() - 1.0f) * 0.02f;
	            level->addParticle(PARTICLETYPE(explode), x + random.nextFloat() * bbWidth * 2 - bbWidth, y + random.nextFloat() * bbHeight, z + random.nextFloat() * bbWidth * 2 - bbWidth, xa, ya, za);
	        }
	    }
	}

	animStepO = animStep;

	yBodyRotO = yBodyRot;
	yRotO = yRot;
	xRotO = xRot;

	TIMER_POP();

	if (!level->isClientSide) {
		if (autoSendPosRot) {
			if (autoSendPosRot && (std::abs(x - sentX) > .1f || std::abs(y - sentY) > .05f || std::abs(z - sentZ) > .1f || std::abs(sentRotX - xRot) > 1 || std::abs(sentRotY - yRot) > 1)) {
				MoveEntityPacket_PosRot packet(this);
				level->raknetInstance->send(packet);
				sentX = x;
				sentY = y;
				sentZ = z;
				sentRotX = xRot;
				sentRotY = yRot;
			}
		}

		float ddx = std::abs(xd - sentXd);
		float ddy = std::abs(yd - sentYd);
		float ddz = std::abs(zd - sentZd);
		const float max = 0.02f;
		const float diff = ddx*ddx + ddy*ddy + ddz*ddz;
		if (diff > max*max || (diff > 0 && xd == 0 && yd == 0 && zd == 0)) {
			sentXd = xd;
			sentYd = yd;
			sentZd = zd;
			SetEntityMotionPacket packet(this);
			level->raknetInstance->send(packet);
			//LOGI("Motion-packet: %d\n", entityId);
		}
	}
}

void Mob::spawnAnim()
{
	/*
	for (int i = 0; i < 20; i++) {
	float xa = random.nextGaussian() * 0.02;
	float ya = random.nextGaussian() * 0.02;
	float za = random.nextGaussian() * 0.02;
	float dd = 10;
	level.addParticle(PARTICLETYPE(explode), x + random.nextFloat() * bbWidth * 2 - bbWidth - xa * dd, y + random.nextFloat() * bbHeight - ya * dd, z + random.nextFloat() * bbWidth * 2 - bbWidth - za
	* dd, xa, ya, za);
	}
	*/
}

void Mob::lerpTo( float x, float y, float z, float yRot, float xRot, int steps )
{
	//heightOffset = 0;
	lx = x;
	ly = y + heightOffset;
	lz = z;
	lyr = yRot;
	lxr = xRot;

	lSteps = steps;
}

void Mob::superTick()
{
	super::tick();
}

void Mob::tick()
{
	xc = xCape;
	yc = yCape;
	zc = zCape;

	super::tick();

	if (arrowCount > 0) {
		if (removeArrowTime <= 0) {
			removeArrowTime = 20 * 3;
		}
		removeArrowTime--;
		if (removeArrowTime <= 0) {
			arrowCount--;
		}
	}

	if (lSteps > 0) {
		float xt = x + (lx - x) / lSteps;
		float yt = y + (ly - y) / lSteps;
		float zt = z + (lz - z) / lSteps;

		float yrd = lyr - yRot;
		while (yrd < -180)
			yrd += 360;
		while (yrd >= 180)
			yrd -= 360;

		yRot += (yrd) / lSteps;
		xRot += (lxr - xRot) / lSteps;

		lSteps--;
		this->setPos(xt, yt, zt);
		this->setRot(yRot, xRot);
	}

	aiStep();

	updateWalkAnim();

	float xd = x - xo;
	float zd = z - zo;

	float sideDist = (float) Mth::sqrt(xd * xd + zd * zd);

	float yBodyRotT = yBodyRot;

	float walkSpeed = 0;
	oRun = run;
	float tRun = 0;
	if (sideDist <= 0.05f) {
		// animStep = 0;
	} else {
		tRun = 1;
		walkSpeed = sideDist * 3;
		yBodyRotT = Mth::atan2(zd, xd) * Mth::RADDEG - 90;
	}
	if (attackAnim > 0) {
		yBodyRotT = yRot;
	}
	if (!onGround) {
		tRun = 0;
	}
	run = run + (tRun - run) * 0.3f;

	/*
	* float yBodyRotD = yRot-yBodyRot; while (yBodyRotD < -180) yBodyRotD
	* += 360; while (yBodyRotD >= 180) yBodyRotD -= 360; yBodyRot +=
	* yBodyRotD * 0.1f;
	*/

	float yBodyRotD = yBodyRotT - yBodyRot;
	while (yBodyRotD < -180)
		yBodyRotD += 360;
	while (yBodyRotD >= 180)
		yBodyRotD -= 360;
	yBodyRot += yBodyRotD * 0.3f;

	float headDiff = yRot - yBodyRot;
	while (headDiff < -180)
		headDiff += 360;
	while (headDiff >= 180)
		headDiff -= 360;
	bool behind = headDiff < -90 || headDiff >= 90;
	if (headDiff < -75) headDiff = -75;
	if (headDiff >= 75) headDiff = +75;
	yBodyRot = yRot - headDiff;
	if (headDiff * headDiff > 50 * 50) {
		yBodyRot += headDiff * 0.2f;
	}

	if (behind) {
		walkSpeed *= -1;
	}
	while (yRot - yRotO < -180)
		yRotO -= 360;
	while (yRot - yRotO >= 180)
		yRotO += 360;

	while (yBodyRot - yBodyRotO < -180)
		yBodyRotO -= 360;
	while (yBodyRot - yBodyRotO >= 180)
		yBodyRotO += 360;

	while (xRot - xRotO < -180)
		xRotO -= 360;
	while (xRot - xRotO >= 180)
		xRotO += 360;
	animStep += walkSpeed;

	// Reduce jitter by using a smaller interpolation factor (more lag, smoother motion)
	double dxCape = x - xCape;
	double dyCape = y - yCape;
	double dzCape = z - zCape;

	const double interp = 0.15; // small value for smoother cape motion
	const double interpY = 0.12; // extra smoothing on vertical movement

	xCape += dxCape * interp;
	yCape += dyCape * interpY;
	zCape += dzCape * interp;
}

void Mob::setSize( float w, float h )
{
	super::setSize(w, h);
}

void Mob::heal( int heal )
{
	if (health <= 0) return;
	health += heal;
	if (health > 20) health = 20;
	invulnerableTime = invulnerableDuration / 2;
}

bool Mob::hurt( Entity* source, int dmg )
{
	if (level->isClientSide) return false;
	noActionTime = 0;
	if (health <= 0) return false;

	this->walkAnimSpeed = 1.5f;

	bool sound = true;
	if (invulnerableTime > invulnerableDuration / 2.0f) {
		if (dmg <= lastHurt) return false;
		actuallyHurt(dmg - lastHurt);
		lastHurt = dmg;
		sound = false;
	} else {
		lastHurt = dmg;
		lastHealth = health;
		invulnerableTime = invulnerableDuration;
		actuallyHurt(dmg);
		hurtTime = hurtDuration = 10;
		hurtDuration = 10;
	}

	hurtDir = 0;

	if (sound) {
		level->broadcastEntityEvent(this, EntityEvent::HURT);
		markHurt();
		if (source != NULL) {
			float xd = source->x - x;
			float zd = source->z - z;
			while (xd * xd + zd * zd < 0.0001) {
				xd = (Mth::random() - Mth::random()) * 0.01f;
				zd = (Mth::random() - Mth::random()) * 0.01f;
			}
			hurtDir = (float) (std::atan2(zd, xd) * Mth::RADDEG) - yRot;
			knockback(source, dmg, xd, zd);
		} else {
			hurtDir = (int) (Mth::random() * 2.0f) * 180.0f;
		}
	}

	if (health <= 0) {
		if (sound) level->playSound(this, getDeathSound(), getSoundVolume(), getVoicePitch());
		die(source);
	} else {
		if (sound) level->playSound(this, getHurtSound(), getSoundVolume(), getVoicePitch());
	}

	return true;
}

void Mob::actuallyHurt( int dmg )
{
    if (!bypassArmor) {
	    dmg = getDamageAfterArmorAbsorb(dmg);
    }
	health -= dmg;
}

float Mob::getSoundVolume()
{
	return 1;
}

const char* Mob::getAmbientSound()
{
	return NULL;
}

std::string Mob::getHurtSound()
{
	return "random.hurt";
}

std::string Mob::getDeathSound()
{
	return "random.hurt";
}

void Mob::knockback( Entity* source, int dmg, float xd, float zd )
{
	float dd = Mth::invSqrt(xd * xd + zd * zd);
	float pow = 0.4f;

	this->xd *= 0.5f;
	this->yd *= 0.5f;
	this->zd *= 0.5f;

	this->xd -= xd * dd * pow;
	this->yd += 0.4f;
	this->zd -= zd * dd * pow;

	if (this->yd > 0.4f) this->yd = 0.4f;
}

void Mob::die( Entity* source )
{
	if (deathScore > 0 && source != NULL) source->awardKillScore(this, deathScore);

	if (!level->isClientSide) {
		if (!isBaby()) {
			dropDeathLoot();
		}
		level->broadcastEntityEvent(this, EntityEvent::DEATH);
	}
}

void Mob::dropDeathLoot()
{
	int loot = getDeathLoot();
	if (loot > 0) {
		int count = random.nextInt(3);
		for (int i = 0; i < count; i++)
			spawnAtLocation(loot, 1);
	}
}

int Mob::getDeathLoot()
{
	return 0;
}

void Mob::reset() {
	super::reset();
	this->_init();
}

void Mob::_init() {
	yBodyRot = 0;
	yBodyRotO = 0;
	rotOffs = 0;
	fallTime = 0;
	lastHurt = 0;
	deathTime = 0;
	attackTime = 0;
	tilt = 0;
	oTilt = 0;
	hurtDuration = 0;
	hurtTime = 0;
	hurtDir = 0;
	lookTime = 0;
	noActionTime = 0;
	xxa = 0; yya = 0;
	yRotA = 0;

	health = 10;
	lastHealth = 20;
}

void Mob::causeFallDamage( float distance )
{
	int dmg = (int) ceil((distance - 3));

	if (dmg > 0) {
		level->playSound(this, (dmg > 4)?"damage.fallbig":"damage.fallsmall", 0.75f, 1);

		hurt(NULL, dmg);

		int t = level->getTile(Mth::floor(x), Mth::floor(y - 0.2f - this->heightOffset), Mth::floor(z));
		if (t > 0) {
			const Tile::SoundType* soundType = Tile::tiles[t]->soundType;
			level->playSound(this, soundType->getStepSound(), soundType->getVolume() * 0.5f, soundType->getPitch() * 0.75f);
		}
	}
}

void Mob::travel( float xa, float ya )
{
	if (isInWater()) {
		float yo = y;
		moveRelative(xa, ya, 0.02f);
		move(xd, yd, zd);

		xd *= 0.80f;
		yd *= 0.80f;
		zd *= 0.80f;
		yd -= 0.02f;

		if (horizontalCollision && isFree(xd, yd + 0.6f - y + yo, zd)) {
			yd = 0.3f;
		}
	} else if (isInLava()) {
		float yo = y;
		moveRelative(xa, ya, 0.02f);
		move(xd, yd, zd);
		xd *= 0.50f;
		yd *= 0.50f;
		zd *= 0.50f;
		yd -= 0.02f;

		if (horizontalCollision && isFree(xd, yd + 0.6f - y + yo, zd)) {
			yd = 0.3f;
		}
	} else {
		float friction = 0.91f;
		if (onGround) {
			friction = 0.6f * 0.91f;
			int t = level->getTile(Mth::floor(x), Mth::floor(bb.y0 - 0.5f), Mth::floor(z));
			if (t > 0) {
				friction = Tile::tiles[t]->friction * 0.91f;
			}
		}

		float friction2 = (0.6f * 0.6f * 0.91f * 0.91f * 0.6f * 0.91f) / (friction * friction * friction);
		moveRelative(xa, ya, (onGround ? walkingSpeed * friction2 : flyingSpeed));

		friction = 0.91f;
		if (onGround) {
			friction = 0.6f * 0.91f;
			int t = level->getTile(Mth::floor(x), Mth::floor(bb.y0 - 0.5f), Mth::floor(z));
			if (t > 0) {
				friction = Tile::tiles[t]->friction * 0.91f;
			}
		}
		//@todo: make it easier to climb ladders
		if (onLadder()) {
			this->fallDistance = 0;
			if (yd < -0.15) yd = -0.15f;
			if (isSneaking() && yd < 0) yd = 0;
		}

		move(xd, yd, zd);

		//@todo: make it easier to climb ladders
		if (horizontalCollision && onLadder()) {
			yd = 0.2f;
		}

		yd -= 0.08f;
		yd *= 0.98f;
		xd *= friction;
		zd *= friction;
	}

}

void Mob::updateWalkAnim()
{
	walkAnimSpeedO = walkAnimSpeed;
	float xxd = x - xo;
	float zzd = z - zo;
	float wst = Mth::sqrt(xxd * xxd + zzd * zzd) * 4;
	if (wst > 1) wst = 1;
	walkAnimSpeed += (wst - walkAnimSpeed) * 0.4f;
	walkAnimPos += walkAnimSpeed;
}

bool Mob::onLadder()
{
	int xt = Mth::floor(x);
	int yt = Mth::floor(bb.y0);
	int zt = Mth::floor(z);
	return level->getTile(xt, yt, zt) == Tile::ladder->id || level->getTile(xt, yt + 1, zt) == Tile::ladder->id;
}

bool Mob::isShootable()
{
	return true;
}

void Mob::addAdditonalSaveData( CompoundTag* entityTag )
{
	entityTag->putShort("Health", (short) health);
	entityTag->putShort("HurtTime", (short) hurtTime);
	entityTag->putShort("DeathTime", (short) deathTime);
	entityTag->putShort("AttackTime", (short) attackTime);

	//if (isPlayer()) LOGI("Saving %d, %d, %d, %d\n", health, hurtTime, deathTime, attackTime);
}

void Mob::readAdditionalSaveData( CompoundTag* tag )
{
	health = tag->getShort("Health");
	hurtTime = tag->getShort("HurtTime");
	deathTime = tag->getShort("DeathTime");
	attackTime = tag->getShort("AttackTime");

	//if (isPlayer()) LOGI("Reading %d, %d, %d, %d\n", health, hurtTime, deathTime, attackTime);
}

void Mob::animateHurt()
{
	hurtTime = hurtDuration = 10;
	hurtDir = 0;
}

bool Mob::isAlive()
{
	return !removed && health > 0;
}

bool Mob::isWaterMob()
{
	return false;
}

void Mob::aiStep()
{
	//@todo? 30 lines of code here in java version
	TIMER_PUSH("ai");
	if (isImmobile()) {
		jumping = false;
		xxa = 0;
		yya = 0;
		yRotA = 0;
	} else {
		if (!interpolateOnly()) {
			if (useNewAi()) {
				TIMER_PUSH("newAi");
				newServerAiStep();
				TIMER_POP();
			}
			else {
				TIMER_PUSH("oldAi");
				updateAi();
				TIMER_POP();
			}
		}
	}
	TIMER_POP_PUSH("move");

	bool inWater = isInWater();
	bool inLava = isInLava();

	if (jumping) {
		if (inWater) {
			yd += 0.04f;
		} else if (inLava) {
			yd += 0.04f;
		} else if (onGround) {
			jumpFromGround();
		}
	}

	xxa *= 0.98f;
	yya *= 0.98f;
	yRotA *= 0.9f;

	float normalSpeed = walkingSpeed;
	float normalAirControllSpeed = flyingSpeed;
	walkingSpeed *= getWalkingSpeedModifier();
	flyingSpeed  *= getWalkingSpeedModifier();
	travel(xxa, yya);
	walkingSpeed = normalSpeed;
	flyingSpeed = normalAirControllSpeed;

	TIMER_POP_PUSH("push");
	Player* player = NULL;
	if(isPlayer()) {
		player = (Player*)this;
	}
	if(player == NULL || !player->isSleeping())  {
		EntityList& entities = level->getEntities(this, this->bb.grow(0.2f, 0, 0.2f));
		for (unsigned int i = 0; i < entities.size(); i++) {
			Entity* e = entities[i];
			if(e->isPlayer()) {
				Player* p = (Player*)e;
				if(p->isSleeping())
					continue;
			}
			if (e->isPushable()) e->push(this);
		}
	}
	TIMER_POP();
}

/*protected*/
void Mob::updateAi() {
    noActionTime++;

	Player* player = level->getNearestPlayer(this, -1);
	checkDespawn(player);

    xxa = 0;
    yya = 0;

    float lookDistance = 8;
    if (random.nextFloat() < 0.02f) {
        player = level->getNearestPlayer(this, lookDistance);
        if (player != NULL) {
            lookingAtId = player->entityId;
            lookTime = 10 + random.nextInt(20);
        } else {
            yRotA = (random.nextFloat() - 0.5f) * 20;
        }
    }

    if (lookingAtId != 0) {
		Entity* lookingAt = level->getEntity(lookingAtId);
		lookingAtId = 0;
		if (lookingAt != NULL) {
			lookAt(lookingAt, 10, (float)getMaxHeadXRot());
			if (lookTime-- <= 0 || lookingAt->removed || lookingAt->distanceToSqr(this) > lookDistance * lookDistance) {
	            lookingAtId = 0;
		    } else {
				lookingAtId = lookingAt->entityId;
			}
		}
    } else {
        if (random.nextFloat() < 0.05f) {
            yRotA = (random.nextFloat() - 0.5f) * 20;
        }
        yRot += yRotA;
        xRot = defaultLookAngle;
    }

    bool inWater = isInWater();
    bool inLava = isInLava();
    if (inWater || inLava) jumping = random.nextFloat() < 0.8f;
}

void Mob::newServerAiStep() {
	noActionTime++;

	TIMER_PUSH("checkDespawn");
	checkDespawn();
	jumping = false;

	TIMER_POP_PUSH("sensing");
	if (sensing) sensing->tick();

	if (random.nextInt(5) == 0) {
		TIMER_POP_PUSH("targetSelector");
		//LOGI("tg1\n");
		if (targetSelector) targetSelector->tick();
		//LOGI("tg2\n");

		TIMER_POP_PUSH("goalSelector");
		//LOGI("gs1\n");
		if (goalSelector) goalSelector->tick();
		//LOGI("gs2\n");
	}

	TIMER_POP_PUSH("navigation");
	if (navigation) navigation->tick();

	//TIMER_POP_PUSH("mob tick");
	//serverAiMobStep();

	TIMER_POP_PUSH("controls");
	if (moveControl) moveControl->tick(); //@todo; we shouldn't be here if not
	if (jumpControl) jumpControl->tick();

	TIMER_POP();
}

bool Mob::isImmobile()
{
	return health <= 0;
}

void Mob::jumpFromGround()
{
	yd = 0.42f;
}

bool Mob::removeWhenFarAway()
{
	return true;
}

int Mob::getMaxHeadXRot()
{
	return 10;
}

void Mob::lookAt( Entity* e, float yMax, float xMax )
{
	float xd = e->x - x;
	float yd;
	float zd = e->z - z;

	if (false) { //if (e->isMob()) { //@todo?
		Mob* mob = (Mob*) e;
		yd = (y + getHeadHeight()) - (mob->y + mob->getHeadHeight());
	} else {
		yd = (e->bb.y0 + e->bb.y1) / 2 - (y + getHeadHeight());
	}

	float sd = Mth::sqrt(xd * xd + zd * zd);

	float yRotD = (float) (atan2(zd, xd) * 180 / Mth::PI) - 90;
	float xRotD = (float) (atan2(yd, sd) * 180 / Mth::PI);
	xRot = -rotlerp(xRot, xRotD, xMax);
	yRot = rotlerp(yRot, yRotD, yMax);
}

bool Mob::isLookingAtAnEntity()
{
	return lookingAtId != 0;
}

//Entity* Mob::getLookingAt()
//{
//	return lookingAt;
//}

float Mob::rotlerp( float a, float b, float max )
{
	float diff = b - a;
	while (diff < -180)
		diff += 360;
	while (diff >= 180)
		diff -= 360;
	if (diff > max) {
		diff = max;
	}
	if (diff < -max) {
		diff = -max;
	}
	return a + diff;
}

void Mob::beforeRemove()
{
}

bool Mob::canSpawn()
{
	return level->isUnobstructed(bb) && level->getCubes(this, bb).size() == 0 && !level->containsAnyLiquid(bb);
}

void Mob::outOfWorld()
{
	int oldHealth = health;
	hurt(NULL, 4);
	if (health >= oldHealth)
		actuallyHurt(4);
}

float Mob::getAttackAnim( float a )
{
	float diff = attackAnim - oAttackAnim;
	if (diff < 0) diff += 1;
	return oAttackAnim + diff * a;
}

Vec3 Mob::getPos( float a )
{
	if (a == 1) {
		return Vec3(x, y, z);
	}
	float x = xo + (this->x - xo) * a;
	float y = yo + (this->y - yo) * a;
	float z = zo + (this->z - zo) * a;

	return Vec3(x, y, z);
}

Vec3 Mob::getLookAngle()
{
	return getViewVector(1);
}

Vec3 Mob::getViewVector( float a )
{
	if (a == 1) {
		float yCos = (float) Mth::cos(-yRot * Mth::DEGRAD - Mth::PI);
		float ySin = (float) Mth::sin(-yRot * Mth::DEGRAD - Mth::PI);
		float xCos = (float) -Mth::cos(-xRot * Mth::DEGRAD);
		float xSin = (float) Mth::sin(-xRot * Mth::DEGRAD);

		return Vec3(ySin * xCos, xSin, yCos * xCos); // newTemp
	}
	float xRot = xRotO + (this->xRot - xRotO) * a;
	float yRot = yRotO + (this->yRot - yRotO) * a;

	float yCos = (float) Mth::cos(-yRot * Mth::DEGRAD - Mth::PI);
	float ySin = (float) Mth::sin(-yRot * Mth::DEGRAD - Mth::PI);
	float xCos = (float) -Mth::cos(-xRot * Mth::DEGRAD);
	float xSin = (float) Mth::sin(-xRot * Mth::DEGRAD);

	return Vec3(ySin * xCos, xSin, yCos * xCos); // newTemp
}

HitResult Mob::pick( float range, float a )
{
	Vec3 from = getPos(a);
	return level->clip(from, from + getViewVector(a) * range);
}

int Mob::getMaxSpawnClusterSize()
{
	return 4;
}

bool Mob::interpolateOnly()
{
	return level->isClientSide;
}

//ItemInstance getCarriedItem() {
//    return NULL;
//}

void Mob::handleEntityEvent(char id) {
    if (id == EntityEvent::HURT) {
        this->walkAnimSpeed = 1.5f;

        invulnerableTime = invulnerableDuration;
        hurtTime = hurtDuration = 10;
        hurtDir = 0;

        level->playSound(this, getHurtSound(), getSoundVolume(), getVoicePitch());
        hurt(NULL, 0);
    } else if (id == EntityEvent::DEATH) {
        level->playSound(this, getDeathSound(), getSoundVolume(), getVoicePitch());
        health = 0;
        die(NULL);
    } else {
        super::handleEntityEvent(id);
    }
}

SynchedEntityData* Mob::getEntityData() {
	return &entityData;
}

const SynchedEntityData* Mob::getEntityData() const {
	return &entityData;
}

MoveControl* Mob::getMoveControl() {
	return moveControl;
}

JumpControl* Mob::getJumpControl() {
	return jumpControl;
}

void Mob::setYya( float yya )
{
	this->yya = yya;
}

float Mob::getSpeed() {
	return speed;
}

void Mob::setSpeed( float speed ) {
	this->speed = speed;
	this->yya = speed;
}

void Mob::setJumping(bool jump) {
	jumping = jump;
}

bool Mob::useNewAi() {
	return false;
}

void Mob::checkDespawn() {
	checkDespawn(level->getNearestPlayer(this, -1));
}

void Mob::checkDespawn(Mob* nearestBlocking) {
	if (nearestBlocking != NULL) {
		const bool removeIfFar = removeWhenFarAway();
		float xd = nearestBlocking->x - x;
		float yd = nearestBlocking->y - y;
		float zd = nearestBlocking->z - z;
		float sd = xd * xd + yd * yd + zd * zd;

		if (removeIfFar && sd > 96 * 96) {
			//LOGI("removed some angry mob more than 10 meters away!\n");
			remove();
		}

		if (noActionTime > 30 * SharedConstants::TicksPerSecond && random.nextInt(800) == 0 && removeIfFar && sd > 32 * 32)
			remove();
		else
			noActionTime = 0;
	}
}
bool Mob::getSharedFlag(int flag) {
	return entityData.getFlag<SharedFlagsInformation::SharedFlagsInformationType> (SharedFlagsInformation::DATA_SHARED_FLAGS_ID, flag);
}
void Mob::setSharedFlag(int flag, bool value) {
	if(value) {
		entityData.setFlag<SharedFlagsInformation::SharedFlagsInformationType>(SharedFlagsInformation::DATA_SHARED_FLAGS_ID, flag);
	}
	else {
		entityData.clearFlag<SharedFlagsInformation::SharedFlagsInformationType>(SharedFlagsInformation::DATA_SHARED_FLAGS_ID, flag);
	}
}

void Mob::updateAttackAnim() {
	if (swinging) {
		if (++swingTime >= SWING_DURATION) {
			swingTime = 0;
			swinging = false;
			//LOGI("Swinging! %d/%d (%f)\n", swingTime, SWING_DURATION, attackAnim);
		}
	} else {
		swingTime = 0;
	}

	attackAnim = swingTime / (float) SWING_DURATION;
}

void Mob::swing() {
	if (!swinging || swingTime >= 3 || swingTime < 0) {
		swingTime = -1;
		swinging = true;
	}
}

bool Mob::isSneaking() {
    return getSharedFlag(SharedFlagsInformation::FLAG_SNEAKING);
}

bool Mob::isSleeping() {
    return false;
}

void Mob::setSneaking(bool value) {
    setSharedFlag(SharedFlagsInformation::FLAG_SNEAKING, value);
}

//
// Armor
//
int Mob::getDamageAfterArmorAbsorb(int damage) {
    int absorb = 25 - getArmorValue();
    int v = (damage) * absorb + dmgSpill;
    hurtArmor(damage);
    damage = v / 25;
    dmgSpill = v % 25;
    return damage;
}

static std::vector<ItemInstance*> getEquipmentSlots() {
	return std::vector<ItemInstance*>();
}

int Mob::getArmorValue() {
	int val = 0;
    /*
	const std::vector<ItemInstance*> items = getEquipmentSlots();
	for (unsigned int i = 0; i < items.size(); ++i) {
		ItemInstance* item = items[i];
        if (item != NULL && ItemInstance::isArmorItem(item)) {
            int baseProtection = ((ArmorItem*) item->getItem())->defense;
            val += baseProtection;
        }
    }
    */
    return val;
}

void Mob::hurtArmor(int damage) {}
