Files
MinecraftConsoles/Minecraft.World/EntityHorse.cpp
Loki Rautio 087b7e7abf Revert "Project modernization (#630)"
This code was not tested and breaks in Release builds, reverting to restore
functionality of the nightly. All in-game menus do not work and generating
a world crashes.

This reverts commit a9be52c41a.
2026-03-07 21:12:22 -06:00

1840 lines
39 KiB
C++

#include "stdafx.h"
#include "net.minecraft.world.entity.h"
#include "net.minecraft.world.entity.monster.h"
#include "net.minecraft.world.entity.ai.attributes.h"
#include "net.minecraft.world.entity.ai.goal.h"
#include "net.minecraft.world.entity.ai.navigation.h"
#include "net.minecraft.world.entity.player.h"
#include "net.minecraft.world.entity.monster.h"
#include "net.minecraft.world.effect.h"
#include "net.minecraft.world.damagesource.h"
#include "net.minecraft.world.item.h"
#include "net.minecraft.world.level.h"
#include "net.minecraft.world.level.tile.h"
#include "net.minecraft.world.inventory.h"
#include "net.minecraft.world.phys.h"
#include "..\Minecraft.Client\Textures.h"
#include "..\Minecraft.Client\Minecraft.h"
#include "BasicTypeContainers.h"
#include "EntityHorse.h"
const wstring EntityHorse::TEX_FOLDER = L"mob/horse/";
const EntitySelector *EntityHorse::PARENT_HORSE_SELECTOR = new HorseEntitySelector();
Attribute *EntityHorse::JUMP_STRENGTH = (new RangedAttribute(eAttributeId_HORSE_JUMPSTRENGTH, .7, 0, 2.0))->setSyncable(true);
wstring EntityHorse::ARMOR_TEXTURES[EntityHorse::ARMORS] = {L"", L"armor/horse_armor_iron.png", L"armor/horse_armor_gold.png", L"armor/horse_armor_diamond.png"};
int EntityHorse::ARMOR_TEXTURES_ID[EntityHorse::ARMORS] = {-1, TN_MOB_HORSE_ARMOR_IRON, TN_MOB_HORSE_ARMOR_GOLD, TN_MOB_HORSE_ARMOR_DIAMOND };
wstring EntityHorse::ARMOR_HASHES[EntityHorse::ARMORS] = {L"", L"meo", L"goo", L"dio"};
int EntityHorse::ARMOR_PROTECTION[EntityHorse::ARMORS] = {0, 5, 7, 11};
wstring EntityHorse::VARIANT_TEXTURES[EntityHorse::VARIANTS] = {L"horse_white.png", L"horse_creamy.png", L"horse_chestnut.png", L"horse_brown.png", L"horse_black.png", L"horse_gray.png", L"horse_darkbrown.png"};
int EntityHorse::VARIANT_TEXTURES_ID[EntityHorse::VARIANTS] = {TN_MOB_HORSE_WHITE, TN_MOB_HORSE_CREAMY, TN_MOB_HORSE_CHESTNUT, TN_MOB_HORSE_BROWN, TN_MOB_HORSE_BLACK, TN_MOB_HORSE_GRAY, TN_MOB_HORSE_DARKBROWN};
wstring EntityHorse::VARIANT_HASHES[EntityHorse::VARIANTS] = {L"hwh", L"hcr", L"hch", L"hbr", L"hbl", L"hgr", L"hdb"};
wstring EntityHorse::MARKING_TEXTURES[EntityHorse::MARKINGS] = {L"", L"horse_markings_white.png", L"horse_markings_whitefield.png", L"horse_markings_whitedots.png", L"horse_markings_blackdots.png"};
int EntityHorse::MARKING_TEXTURES_ID[EntityHorse::MARKINGS] = {-1, TN_MOB_HORSE_MARKINGS_WHITE, TN_MOB_HORSE_MARKINGS_WHITEFIELD, TN_MOB_HORSE_MARKINGS_WHITEDOTS, TN_MOB_HORSE_MARKINGS_BLACKDOTS};
wstring EntityHorse::MARKING_HASHES[EntityHorse::MARKINGS] = {L"", L"wo_", L"wmo", L"wdo", L"bdo"};
bool HorseEntitySelector::matches(shared_ptr<Entity> entity) const
{
return entity->instanceof(eTYPE_HORSE) && dynamic_pointer_cast<EntityHorse>(entity)->isBred();
}
EntityHorse::EntityHorse(Level *level) : Animal(level)
{
// 4J Stu - This function call had to be moved here from the Entity ctor to ensure that the derived version of the function is called
this->defineSynchedData();
registerAttributes();
setHealth(getMaxHealth());
countEating = 0;
mouthCounter = 0;
standCounter = 0;
tailCounter = 0;
sprintCounter = 0;
isEntityJumping = false;
inventory = nullptr;
hasReproduced = false;
temper = 0;
playerJumpPendingScale = 0.0f;
allowStandSliding = false;
eatAnim = eatAnimO = 0.0f;
standAnim = standAnimO = 0.0f;
mouthAnim = mouthAnimO = 0.0f;
gallopSoundCounter = 0;
layerTextureHashName = L"";
layerTextureLayers = intArray(3);
for(unsigned int i = 0; i < 3; ++i)
{
layerTextureLayers[i] = -1;
}
setSize(1.4f, 1.6f);
fireImmune = false;
setChestedHorse(false);
getNavigation()->setAvoidWater(true);
goalSelector.addGoal(0, new FloatGoal(this));
goalSelector.addGoal(1, new PanicGoal(this, 1.2));
goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2));
goalSelector.addGoal(2, new BreedGoal(this, 1.0));
goalSelector.addGoal(4, new FollowParentGoal(this, 1.0));
goalSelector.addGoal(6, new RandomStrollGoal(this, .7));
goalSelector.addGoal(7, new LookAtPlayerGoal(this, typeid(Player), 6));
goalSelector.addGoal(8, new RandomLookAroundGoal(this));
createInventory();
}
EntityHorse::~EntityHorse()
{
delete [] layerTextureLayers.data;
}
void EntityHorse::defineSynchedData()
{
Animal::defineSynchedData();
entityData->define(DATA_ID_HORSE_FLAGS, 0);
entityData->define(DATA_ID_TYPE, (byte) 0);
entityData->define(DATA_ID_TYPE_VARIANT, 0);
entityData->define(DATA_ID_OWNER_NAME, L"");
entityData->define(DATA_ID_ARMOR, 0);
}
void EntityHorse::setType(int i)
{
entityData->set(DATA_ID_TYPE, (byte) i);
clearLayeredTextureInfo();
}
int EntityHorse::getType()
{
return entityData->getByte(DATA_ID_TYPE);
}
void EntityHorse::setVariant(int i)
{
entityData->set(DATA_ID_TYPE_VARIANT, i);
clearLayeredTextureInfo();
}
int EntityHorse::getVariant()
{
return entityData->getInteger(DATA_ID_TYPE_VARIANT);
}
wstring EntityHorse::getAName()
{
if (hasCustomName()) return getCustomName();
#ifdef _DEBUG
int type = getType();
switch (type)
{
default:
case TYPE_HORSE:
return L"entity.horse.name";
case TYPE_DONKEY:
return L"entity.donkey.name";
case TYPE_MULE:
return L"entity.mule.name";
case TYPE_SKELETON:
return L"entity.skeletonhorse.name";
case TYPE_UNDEAD:
return L"entity.zombiehorse.name";
}
#else
return L"";
#endif
}
bool EntityHorse::getHorseFlag(int flag)
{
return (entityData->getInteger(DATA_ID_HORSE_FLAGS) & flag) != 0;
}
void EntityHorse::setHorseFlag(int flag, bool value)
{
int current = entityData->getInteger(DATA_ID_HORSE_FLAGS);
if (value)
{
entityData->set(DATA_ID_HORSE_FLAGS, current | flag);
}
else
{
entityData->set(DATA_ID_HORSE_FLAGS, current & ~flag);
}
}
bool EntityHorse::isAdult()
{
return !isBaby();
}
bool EntityHorse::isTamed()
{
return getHorseFlag(FLAG_TAME);
}
bool EntityHorse::isRidable()
{
return isAdult();
}
wstring EntityHorse::getOwnerName()
{
return entityData->getString(DATA_ID_OWNER_NAME);
}
void EntityHorse::setOwner(const wstring &par1Str)
{
entityData->set(DATA_ID_OWNER_NAME, par1Str);
}
float EntityHorse::getFoalScale()
{
int age = getAge();
if (age >= 0)
{
return 1.0f;
}
return .5f + (float) (BABY_START_AGE - age) / (float) BABY_START_AGE * .5f;
}
void EntityHorse::updateSize(bool isBaby)
{
if (isBaby)
{
internalSetSize(getFoalScale());
}
else
{
internalSetSize(1.0f);
}
}
bool EntityHorse::getIsJumping()
{
return isEntityJumping;
}
void EntityHorse::setTamed(bool flag)
{
setHorseFlag(FLAG_TAME, flag);
}
void EntityHorse::setIsJumping(bool flag)
{
isEntityJumping = flag;
}
bool EntityHorse::canBeLeashed()
{
return !isUndead() && Animal::canBeLeashed();
}
void EntityHorse::onLeashDistance(float distanceToLeashHolder)
{
if (distanceToLeashHolder > 6 && isEating())
{
setEating(false);
}
}
bool EntityHorse::isChestedHorse()
{
return getHorseFlag(FLAG_CHESTED);
}
int EntityHorse::getArmorType()
{
return entityData->getInteger(DATA_ID_ARMOR);
}
int EntityHorse::getArmorTypeForItem(shared_ptr<ItemInstance> armorItem)
{
if (armorItem == NULL)
{
return ARMOR_NONE;
}
if (armorItem->id == Item::horseArmorMetal_Id)
{
return ARMOR_IRON;
}
else if (armorItem->id == Item::horseArmorGold_Id)
{
return ARMOR_GOLD;
}
else if (armorItem->id == Item::horseArmorDiamond_Id)
{
return ARMOR_DIAMOND;
}
return ARMOR_NONE;
}
bool EntityHorse::isEating()
{
return getHorseFlag(FLAG_EATING);
}
bool EntityHorse::isStanding()
{
return getHorseFlag(FLAG_STANDING);
}
bool EntityHorse::isBred()
{
return getHorseFlag(FLAG_BRED);
}
bool EntityHorse::getHasReproduced()
{
return hasReproduced;
}
void EntityHorse::setArmorType(int i)
{
entityData->set(DATA_ID_ARMOR, i);
clearLayeredTextureInfo();
}
void EntityHorse::setBred(bool flag)
{
setHorseFlag(FLAG_BRED, flag);
}
void EntityHorse::setChestedHorse(bool flag)
{
setHorseFlag(FLAG_CHESTED, flag);
}
void EntityHorse::setReproduced(bool flag)
{
hasReproduced = flag;
}
void EntityHorse::setSaddled(bool flag)
{
setHorseFlag(FLAG_SADDLE, flag);
}
int EntityHorse::getTemper()
{
return temper;
}
void EntityHorse::setTemper(int temper)
{
this->temper = temper;
}
int EntityHorse::modifyTemper(int amount)
{
int temper = Mth::clamp(getTemper() + amount, 0, getMaxTemper());
setTemper(temper);
return temper;
}
bool EntityHorse::hurt(DamageSource *damagesource, float dmg)
{
// 4J: Protect owned horses from untrusted players
if (isTamed())
{
shared_ptr<Entity> entity = damagesource->getDirectEntity();
if (entity != NULL && entity->instanceof(eTYPE_PLAYER))
{
shared_ptr<Player> attacker = dynamic_pointer_cast<Player>(entity);
attacker->canHarmPlayer(getOwnerName());
}
}
shared_ptr<Entity> attacker = damagesource->getEntity();
if (rider.lock() != NULL && (rider.lock() == (attacker) ))
{
return false;
}
return Animal::hurt(damagesource, dmg);
}
int EntityHorse::getArmorValue()
{
return ARMOR_PROTECTION[getArmorType()];
}
bool EntityHorse::isPushable()
{
return rider.lock() == NULL;
}
// TODO: [EB]: Explain why this is being done - what side effect does getBiome have?
bool EntityHorse::checkSpawningBiome()
{
int x = Mth::floor(this->x);
int z = Mth::floor(this->z);
level->getBiome(x, z);
return true;
}
/**
* Drops a chest block if the horse is bagged
*/
void EntityHorse::dropBags()
{
if (level->isClientSide || !isChestedHorse())
{
return;
}
spawnAtLocation(Tile::chest_Id, 1);
setChestedHorse(false);
}
void EntityHorse::eatingHorse()
{
openMouth();
level->playEntitySound(shared_from_this(), eSoundType_EATING, 1.0f, 1.0f + (random->nextFloat() - random->nextFloat()) * 0.2f);
}
/**
* Changed to adjust fall damage for riders
*/
void EntityHorse::causeFallDamage(float fallDistance)
{
if (fallDistance > 1)
{
playSound(eSoundType_MOB_HORSE_LAND, .4f, 1);
}
int dmg = Mth::ceil(fallDistance * .5f - 3.0f);
if (dmg <= 0) return;
hurt(DamageSource::fall, dmg);
if (rider.lock() != NULL)
{
rider.lock()->hurt(DamageSource::fall, dmg);
}
int id = level->getTile(Mth::floor(x), Mth::floor(y - 0.2 - yRotO), Mth::floor(z));
if (id > 0)
{
const Tile::SoundType *stepsound = Tile::tiles[id]->soundType;
level->playEntitySound(shared_from_this(), stepsound->getStepSound(), stepsound->getVolume() * 0.5f, stepsound->getPitch() * 0.75f);
}
}
/**
* Different inventory sizes depending on the kind of horse
*
* @return
*/
int EntityHorse::getInventorySize()
{
int type = getType();
if (isChestedHorse() && (type == TYPE_DONKEY || type == TYPE_MULE))
{
return INV_BASE_COUNT + INV_DONKEY_CHEST_COUNT;
}
return INV_BASE_COUNT;
}
void EntityHorse::createInventory()
{
shared_ptr<AnimalChest> old = inventory;
inventory = shared_ptr<AnimalChest>( new AnimalChest(L"HorseChest", getInventorySize()) );
inventory->setCustomName(getAName());
if (old != NULL)
{
old->removeListener(this);
int max = min(old->getContainerSize(), inventory->getContainerSize());
for (int slot = 0; slot < max; slot++)
{
shared_ptr<ItemInstance> item = old->getItem(slot);
if (item != NULL)
{
inventory->setItem(slot, item->copy());
}
}
old = nullptr;
}
inventory->addListener(this);
updateEquipment();
}
void EntityHorse::updateEquipment()
{
if (level && !level->isClientSide)
{
setSaddled(inventory->getItem(INV_SLOT_SADDLE) != NULL);
if (canWearArmor())
{
setArmorType(getArmorTypeForItem(inventory->getItem(INV_SLOT_ARMOR)));
}
}
}
void EntityHorse::containerChanged()
{
int armorType = getArmorType();
bool saddled = isSaddled();
updateEquipment();
if (tickCount > 20)
{
if (armorType == ARMOR_NONE && armorType != getArmorType())
{
playSound(eSoundType_MOB_HORSE_ARMOR, .5f, 1);
}
if (!saddled && isSaddled())
{
playSound(eSoundType_MOB_HORSE_LEATHER, .5f, 1);
}
}
}
bool EntityHorse::canSpawn()
{
checkSpawningBiome();
return Animal::canSpawn();
}
shared_ptr<EntityHorse> EntityHorse::getClosestMommy(shared_ptr<Entity> baby, double searchRadius)
{
double closestDistance = Double::MAX_VALUE;
shared_ptr<Entity> mommy = nullptr;
vector<shared_ptr<Entity> > *list = level->getEntities(baby, baby->bb->expand(searchRadius, searchRadius, searchRadius), PARENT_HORSE_SELECTOR);
for( auto& horse : *list )
{
double distanceSquared = horse->distanceToSqr(baby->x, baby->y, baby->z);
if (distanceSquared < closestDistance)
{
mommy = horse;
closestDistance = distanceSquared;
}
}
delete list;
return dynamic_pointer_cast<EntityHorse>(mommy);
}
double EntityHorse::getCustomJump()
{
return getAttribute(JUMP_STRENGTH)->getValue();
}
int EntityHorse::getDeathSound()
{
openMouth();
int type = getType();
if (type == TYPE_UNDEAD)
{
return eSoundType_MOB_HORSE_ZOMBIE_DEATH; //"mob.horse.zombie.death";
}
if (type == TYPE_SKELETON)
{
return eSoundType_MOB_HORSE_SKELETON_DEATH; //"mob.horse.skeleton.death";
}
if (type == TYPE_DONKEY || type == TYPE_MULE)
{
return eSoundType_MOB_HORSE_DONKEY_DEATH; //"mob.horse.donkey.death";
}
return eSoundType_MOB_HORSE_DEATH; //"mob.horse.death";
}
int EntityHorse::getDeathLoot()
{
bool flag = random->nextInt(4) == 0;
int type = getType();
if (type == TYPE_SKELETON)
{
return Item::bone_Id;
}
if (type == TYPE_UNDEAD)
{
if (flag)
{
return 0;
}
return Item::rotten_flesh_Id;
}
return Item::leather_Id;
}
int EntityHorse::getHurtSound()
{
openMouth();
{
if (random->nextInt(3) == 0)
{
stand();
}
}
int type = getType();
if (type == TYPE_UNDEAD)
{
return eSoundType_MOB_HORSE_ZOMBIE_HIT; //"mob.horse.zombie.hit";
}
if (type == TYPE_SKELETON)
{
return eSoundType_MOB_HORSE_SKELETON_HIT; //"mob.horse.skeleton.hit";
}
if (type == TYPE_DONKEY || type == TYPE_MULE)
{
return eSoundType_MOB_HORSE_DONKEY_HIT; //"mob.horse.donkey.hit";
}
return eSoundType_MOB_HORSE_HIT; //"mob.horse.hit";
}
bool EntityHorse::isSaddled()
{
return getHorseFlag(FLAG_SADDLE);
}
int EntityHorse::getAmbientSound()
{
openMouth();
if (random->nextInt(10) == 0 && !isImmobile())
{
stand();
}
int type = getType();
if (type == TYPE_UNDEAD)
{
return eSoundType_MOB_HORSE_ZOMBIE_IDLE; //"mob.horse.zombie.idle";
}
if (type == TYPE_SKELETON)
{
return eSoundType_MOB_HORSE_SKELETON_IDLE; //"mob.horse.skeleton.idle";
}
if (type == TYPE_DONKEY || type == TYPE_MULE)
{
return eSoundType_MOB_HORSE_DONKEY_IDLE; //"mob.horse.donkey.idle";
}
return eSoundType_MOB_HORSE_IDLE; //"mob.horse.idle";
}
/**
* sound played when an untamed mount buckles rider
*/
int EntityHorse::getMadSound()
{
openMouth();
stand();
int type = getType();
if (type == TYPE_UNDEAD || type == TYPE_SKELETON)
{
return -1;
}
if (type == TYPE_DONKEY || type == TYPE_MULE)
{
return eSoundType_MOB_HORSE_DONKEY_ANGRY; //"mob.horse.donkey.angry";
}
return eSoundType_MOB_HORSE_ANGRY; //"mob.horse.angry";
}
void EntityHorse::playStepSound(int xt, int yt, int zt, int t)
{
const Tile::SoundType *soundType = Tile::tiles[t]->soundType;
if (level->getTile(xt, yt + 1, zt) == Tile::topSnow_Id)
{
soundType = Tile::topSnow->soundType;
}
if (!Tile::tiles[t]->material->isLiquid())
{
int type = getType();
if (rider.lock() != NULL && type != TYPE_DONKEY && type != TYPE_MULE)
{
gallopSoundCounter++;
if (gallopSoundCounter > 5 && gallopSoundCounter % 3 == 0)
{
playSound(eSoundType_MOB_HORSE_GALLOP, soundType->getVolume() * 0.15f, soundType->getPitch());
if (type == TYPE_HORSE && random->nextInt(10) == 0)
{
playSound(eSoundType_MOB_HORSE_BREATHE, soundType->getVolume() * 0.6f, soundType->getPitch());
}
}
else if (gallopSoundCounter <= 5)
{
playSound(eSoundType_MOB_HORSE_WOOD, soundType->getVolume() * 0.15f, soundType->getPitch());
}
}
else if (soundType == Tile::SOUND_WOOD)
{
playSound(eSoundType_MOB_HORSE_SOFT, soundType->getVolume() * 0.15f, soundType->getPitch());
}
else
{
playSound(eSoundType_MOB_HORSE_WOOD, soundType->getVolume() * 0.15f, soundType->getPitch());
}
}
}
void EntityHorse::registerAttributes()
{
Animal::registerAttributes();
getAttributes()->registerAttribute(JUMP_STRENGTH);
getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(53);
getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.225f);
}
int EntityHorse::getMaxSpawnClusterSize()
{
return 6;
}
/**
* How difficult is the creature to be tamed? the Higher the number, the
* more difficult
*/
int EntityHorse::getMaxTemper()
{
return 100;
}
float EntityHorse::getSoundVolume()
{
return 0.8f;
}
int EntityHorse::getAmbientSoundInterval()
{
return 400;
}
bool EntityHorse::hasLayeredTextures()
{
return getType() == TYPE_HORSE || getArmorType() > 0;
}
void EntityHorse::clearLayeredTextureInfo()
{
layerTextureHashName = L"";
}
void EntityHorse::rebuildLayeredTextureInfo()
{
layerTextureHashName = L"horse/";
layerTextureLayers[0] = -1;
layerTextureLayers[1] = -1;
layerTextureLayers[2] = -1;
int type = getType();
int variant = getVariant();
int armorIndex = 2;
if (type == TYPE_HORSE)
{
int skin = variant & 0xFF;
int markings = (variant & 0xFF00) >> 8;
layerTextureLayers[0] = VARIANT_TEXTURES_ID[skin];
layerTextureHashName += VARIANT_HASHES[skin];
layerTextureLayers[1] = MARKING_TEXTURES_ID[markings];
layerTextureHashName += MARKING_HASHES[markings];
if(layerTextureLayers[1] == -1)
{
armorIndex = 1;
}
}
else
{
layerTextureLayers[0] = -1;
layerTextureHashName += L"_" + std::to_wstring(type) + L"_";
armorIndex = 1;
}
int armor = getArmorType();
layerTextureLayers[armorIndex] = ARMOR_TEXTURES_ID[armor];
layerTextureHashName += ARMOR_HASHES[armor];
}
wstring EntityHorse::getLayeredTextureHashName()
{
if (layerTextureHashName.empty())
{
rebuildLayeredTextureInfo();
}
return layerTextureHashName;
}
intArray EntityHorse::getLayeredTextureLayers()
{
if (layerTextureHashName.empty())
{
rebuildLayeredTextureInfo();
}
return layerTextureLayers;
}
void EntityHorse::openInventory(shared_ptr<Player> player)
{
if (!level->isClientSide && (rider.lock() == NULL || rider.lock() == player) && isTamed())
{
inventory->setCustomName(getAName());
player->openHorseInventory(dynamic_pointer_cast<EntityHorse>(shared_from_this()), inventory);
}
}
bool EntityHorse::mobInteract(shared_ptr<Player> player)
{
shared_ptr<ItemInstance> itemstack = player->inventory->getSelected();
if (itemstack != NULL && itemstack->id == Item::spawnEgg_Id)
{
return Animal::mobInteract(player);
}
if (!isTamed())
{
if (isUndead())
{
return false;
}
}
if (isTamed() && isAdult() && player->isSneaking())
{
openInventory(player);
return true;
}
if (isRidable() && rider.lock() != NULL)
{
return Animal::mobInteract(player);
}
// consumables
if (itemstack != NULL)
{
bool itemUsed = false;
if (canWearArmor())
{
int armorType = -1;
if (itemstack->id == Item::horseArmorMetal_Id)
{
armorType = ARMOR_IRON;
}
else if (itemstack->id == Item::horseArmorGold_Id)
{
armorType = ARMOR_GOLD;
}
else if (itemstack->id == Item::horseArmorDiamond_Id)
{
armorType = ARMOR_DIAMOND;
}
if (armorType >= 0)
{
if (!isTamed())
{
makeMad();
return true;
}
openInventory(player);
return true;
}
}
if (!itemUsed && !isUndead())
{
float _heal = 0;
int _ageUp = 0;
int temper = 0;
if (itemstack->id == Item::wheat_Id)
{
_heal = 2;
_ageUp = 60;
temper = 3;
}
else if (itemstack->id == Item::sugar_Id)
{
_heal = 1;
_ageUp = 30;
temper = 3;
}
else if (itemstack->id == Item::bread_Id)
{
_heal = 7;
_ageUp = 180;
temper = 3;
}
else if (itemstack->id == Tile::hayBlock_Id)
{
_heal = 20;
_ageUp = 180;
}
else if (itemstack->id == Item::apple_Id)
{
_heal = 3;
_ageUp = 60;
temper = 3;
}
else if (itemstack->id == Item::carrotGolden_Id)
{
_heal = 4;
_ageUp = 60;
temper = 5;
if (isTamed() && getAge() == 0)
{
itemUsed = true;
setInLove();
}
}
else if (itemstack->id == Item::apple_gold_Id)
{
_heal = 10;
_ageUp = 240;
temper = 10;
if (isTamed() && getAge() == 0)
{
itemUsed = true;
setInLove();
}
}
if (getHealth() < getMaxHealth() && _heal > 0)
{
heal(_heal);
itemUsed = true;
}
if (!isAdult() && _ageUp > 0)
{
ageUp(_ageUp);
itemUsed = true;
}
if (temper > 0 && (itemUsed || !isTamed()) && temper < getMaxTemper())
{
itemUsed = true;
modifyTemper(temper);
}
if (itemUsed)
{
eatingHorse();
}
}
if (!isTamed() && !itemUsed)
{
if (itemstack != NULL && itemstack->interactEnemy(player, dynamic_pointer_cast<LivingEntity>(shared_from_this())))
{
return true;
}
makeMad();
return true;
}
if (!itemUsed && canWearBags() && !isChestedHorse())
{
if (itemstack->id == Tile::chest_Id)
{
setChestedHorse(true);
playSound(eSoundType_MOB_CHICKENPLOP, 1.0f, (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f);
itemUsed = true;
createInventory();
}
}
if (!itemUsed && isRidable() && !isSaddled())
{
if (itemstack->id == Item::saddle_Id)
{
openInventory(player);
return true;
}
}
if (itemUsed)
{
if (!player->abilities.instabuild)
{
if (--itemstack->count == 0)
{
player->inventory->setItem(player->inventory->selected, nullptr);
}
}
return true;
}
}
if (isRidable() && rider.lock() == NULL)
{
// for name tag items and such, we must call the item's interaction
// method before riding
if (itemstack != NULL && itemstack->interactEnemy(player, dynamic_pointer_cast<LivingEntity>(shared_from_this())))
{
return true;
}
doPlayerRide(player);
app.DebugPrintf("<EntityHorse::mobInteract> Horse speed: %f\n", (float) (getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->getValue()));
return true;
}
else
{
return Animal::mobInteract(player);
}
}
void EntityHorse::doPlayerRide(shared_ptr<Player> player)
{
player->yRot = yRot;
player->xRot = xRot;
setEating(false);
setStanding(false);
if (!level->isClientSide)
{
player->ride(shared_from_this());
}
}
/**
* Can this horse be trapped in an amulet?
*/
bool EntityHorse::isAmuletHorse()
{
return getType() == TYPE_SKELETON;
}
/**
* Can wear regular armor
*/
bool EntityHorse::canWearArmor()
{
return getType() == TYPE_HORSE;
}
/**
* able to carry bags
*
* @return
*/
bool EntityHorse::canWearBags()
{
int type = getType();
return type == TYPE_MULE || type == TYPE_DONKEY;
}
bool EntityHorse::isImmobile()
{
if (rider.lock() != NULL && isSaddled())
{
return true;
}
return isEating() || isStanding();
}
/**
* Rare horse that can be transformed into Nightmares or Bathorses or give
* ghost horses on dead
*/
bool EntityHorse::isPureBreed()
{
return getType() > 10 && getType() < 21;
}
/**
* Is this an Undead Horse?
*
* @return
*/
bool EntityHorse::isUndead()
{
int type = getType();
return type == TYPE_UNDEAD || type == TYPE_SKELETON;
}
bool EntityHorse::isSterile()
{
return isUndead() || getType() == TYPE_MULE;
}
bool EntityHorse::isFood(shared_ptr<ItemInstance> itemInstance)
{
// horses have their own food behaviors in mobInterract
return false;
}
void EntityHorse::moveTail()
{
tailCounter = 1;
}
int EntityHorse::nameYOffset()
{
if (isAdult())
{
return -80;
}
else
{
return (int) (-5 - getFoalScale() * 80.0f);
}
}
void EntityHorse::die(DamageSource *damagesource)
{
Animal::die(damagesource);
if (!level->isClientSide)
{
dropMyStuff();
}
}
void EntityHorse::aiStep()
{
if (random->nextInt(200) == 0)
{
moveTail();
}
Animal::aiStep();
if (!level->isClientSide)
{
if (random->nextInt(900) == 0 && deathTime == 0)
{
heal(1);
}
if (!isEating() && rider.lock() == NULL && random->nextInt(300) == 0)
{
if (level->getTile(Mth::floor(x), Mth::floor(y) - 1, Mth::floor(z)) == Tile::grass_Id)
{
setEating(true);
}
}
if (isEating() && ++countEating > 50)
{
countEating = 0;
setEating(false);
}
if (isBred() && !isAdult() && !isEating())
{
shared_ptr<EntityHorse> mommy = getClosestMommy(shared_from_this(), 16);
if (mommy != NULL && distanceToSqr(mommy) > 4.0)
{
Path *pathentity = level->findPath(shared_from_this(), mommy, 16.0f, true, false, false, true);
setPath(pathentity);
}
}
}
}
void EntityHorse::tick()
{
Animal::tick();
// if client-side data values have changed, rebuild texture info
if (level->isClientSide && entityData->isDirty())
{
entityData->clearDirty();
clearLayeredTextureInfo();
}
if (mouthCounter > 0 && ++mouthCounter > 30)
{
mouthCounter = 0;
setHorseFlag(FLAG_OPEN_MOUTH, false);
}
if (!level->isClientSide)
{
if (standCounter > 0 && ++standCounter > 20)
{
standCounter = 0;
setStanding(false);
}
}
if (tailCounter > 0 && ++tailCounter > 8)
{
tailCounter = 0;
}
if (sprintCounter > 0)
{
++sprintCounter;
if (sprintCounter > 300)
{
sprintCounter = 0;
}
}
eatAnimO = eatAnim;
if (isEating())
{
eatAnim += (1.0f - eatAnim) * .4f + .05f;
if (eatAnim > 1)
{
eatAnim = 1;
}
}
else
{
eatAnim += (.0f - eatAnim) * .4f - .05f;
if (eatAnim < 0)
{
eatAnim = 0;
}
}
standAnimO = standAnim;
if (isStanding())
{
// standing is incompatible with eating, so lock eat anim
eatAnimO = eatAnim = 0;
standAnim += (1.0f - standAnim) * .4f + .05f;
if (standAnim > 1)
{
standAnim = 1;
}
}
else
{
allowStandSliding = false;
// the animation falling back to ground is slower in the beginning
standAnim += (.8f * standAnim * standAnim * standAnim - standAnim) * .6f - .05f;
if (standAnim < 0)
{
standAnim = 0;
}
}
mouthAnimO = mouthAnim;
if (getHorseFlag(FLAG_OPEN_MOUTH))
{
mouthAnim += (1.0f - mouthAnim) * .7f + .05f;
if (mouthAnim > 1)
{
mouthAnim = 1;
}
}
else
{
mouthAnim += (.0f - mouthAnim) * .7f - .05f;
if (mouthAnim < 0)
{
mouthAnim = 0;
}
}
}
void EntityHorse::openMouth()
{
if (!level->isClientSide)
{
mouthCounter = 1;
setHorseFlag(FLAG_OPEN_MOUTH, true);
}
}
bool EntityHorse::isReadyForParenting()
{
return rider.lock() == NULL && riding == NULL && isTamed() && isAdult() && !isSterile() && getHealth() >= getMaxHealth();
}
bool EntityHorse::renderName()
{
return hasCustomName() && rider.lock() == NULL;
}
bool EntityHorse::rideableEntity()
{
return true;
}
void EntityHorse::setUsingItemFlag(bool flag)
{
setHorseFlag(FLAG_EATING, flag);
}
void EntityHorse::setEating(bool state)
{
setUsingItemFlag(state);
}
void EntityHorse::setStanding(bool state)
{
if (state)
{
setEating(false);
}
setHorseFlag(FLAG_STANDING, state);
}
void EntityHorse::stand()
{
if (!level->isClientSide)
{
standCounter = 1;
setStanding(true);
}
}
void EntityHorse::makeMad()
{
stand();
int ambient = getMadSound();
playSound(ambient, getSoundVolume(), getVoicePitch());
}
void EntityHorse::dropMyStuff()
{
dropInventory(shared_from_this(), inventory);
dropBags();
}
void EntityHorse::dropInventory(shared_ptr<Entity> entity, shared_ptr<AnimalChest> animalchest)
{
if (animalchest == NULL || level->isClientSide) return;
for (int i = 0; i < animalchest->getContainerSize(); i++)
{
shared_ptr<ItemInstance> itemstack = animalchest->getItem(i);
if (itemstack == NULL)
{
continue;
}
spawnAtLocation(itemstack, 0);
}
}
bool EntityHorse::tameWithName(shared_ptr<Player> player)
{
setOwner(player->getName());
setTamed(true);
return true;
}
/**
* Overridden method to add control to mounts, should be moved to
* EntityLiving
*/
void EntityHorse::travel(float xa, float ya)
{
// If the entity is not ridden by Player, then execute the normal
// Entityliving code
if (rider.lock() == NULL || !isSaddled())
{
footSize = .5f;
flyingSpeed = .02f;
Animal::travel(xa, ya);
return;
}
yRotO = yRot = rider.lock()->yRot;
xRot = rider.lock()->xRot * 0.5f;
setRot(yRot, xRot);
yHeadRot = yBodyRot = yRot;
shared_ptr<LivingEntity> livingRider = dynamic_pointer_cast<LivingEntity>(rider.lock());
xa = livingRider->xxa * .5f;
ya = livingRider->yya;
// move much slower backwards
if (ya <= 0)
{
ya *= .25f;
gallopSoundCounter = 0;
}
if (onGround && playerJumpPendingScale == 0 && isStanding() && !allowStandSliding)
{
xa = 0;
ya = 0;
}
if (playerJumpPendingScale > 0 && !getIsJumping() && onGround)
{
yd = getCustomJump() * playerJumpPendingScale;
if (hasEffect(MobEffect::jump))
{
yd += (getEffect(MobEffect::jump)->getAmplifier() + 1) * .1f;
}
setIsJumping(true);
hasImpulse = true;
if (ya > 0)
{
float sin = Mth::sin(yRot * PI / 180);
float cos = Mth::cos(yRot * PI / 180);
xd += -0.4f * sin * playerJumpPendingScale;
zd += 0.4f * cos * playerJumpPendingScale;
playSound(eSoundType_MOB_HORSE_JUMP, .4f, 1);
}
playerJumpPendingScale = 0;
}
footSize = 1;
flyingSpeed = getSpeed() * .1f;
if (!level->isClientSide)
{
setSpeed((float) (getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->getValue()));
Animal::travel(xa, ya);
}
if (onGround)
{
// blood - fixes jump bug
playerJumpPendingScale = 0;
setIsJumping(false);
}
walkAnimSpeedO = walkAnimSpeed;
double dx = x - xo;
double dz = z - zo;
float wst = Mth::sqrt(dx * dx + dz * dz) * 4.0f;
if (wst > 1.0f)
{
wst = 1.0f;
}
walkAnimSpeed += (wst - walkAnimSpeed) * 0.4f;
walkAnimPos += walkAnimSpeed;
}
void EntityHorse::addAdditonalSaveData(CompoundTag *tag)
{
Animal::addAdditonalSaveData(tag);
tag->putBoolean(L"EatingHaystack", isEating());
tag->putBoolean(L"ChestedHorse", isChestedHorse());
tag->putBoolean(L"HasReproduced", getHasReproduced());
tag->putBoolean(L"Bred", isBred());
tag->putInt(L"Type", getType());
tag->putInt(L"Variant", getVariant());
tag->putInt(L"Temper", getTemper());
tag->putBoolean(L"Tame", isTamed());
tag->putString(L"OwnerName", getOwnerName());
if (isChestedHorse())
{
ListTag<CompoundTag> *listTag = new ListTag<CompoundTag>();
for (int i = INV_BASE_COUNT; i < inventory->getContainerSize(); i++)
{
shared_ptr<ItemInstance> stack = inventory->getItem(i);
if (stack != NULL)
{
CompoundTag *compoundTag = new CompoundTag();
compoundTag->putByte(L"Slot", (byte) i);
stack->save(compoundTag);
listTag->add(compoundTag);
}
}
tag->put(L"Items", listTag);
}
if (inventory->getItem(INV_SLOT_ARMOR) != NULL)
{
tag->put(L"ArmorItem", inventory->getItem(INV_SLOT_ARMOR)->save(new CompoundTag(L"ArmorItem")));
}
if (inventory->getItem(INV_SLOT_SADDLE) != NULL)
{
tag->put(L"SaddleItem", inventory->getItem(INV_SLOT_SADDLE)->save(new CompoundTag(L"SaddleItem")));
}
}
void EntityHorse::readAdditionalSaveData(CompoundTag *tag)
{
Animal::readAdditionalSaveData(tag);
setEating(tag->getBoolean(L"EatingHaystack"));
setBred(tag->getBoolean(L"Bred"));
setChestedHorse(tag->getBoolean(L"ChestedHorse"));
setReproduced(tag->getBoolean(L"HasReproduced"));
setType(tag->getInt(L"Type"));
setVariant(tag->getInt(L"Variant"));
setTemper(tag->getInt(L"Temper"));
setTamed(tag->getBoolean(L"Tame"));
if (tag->contains(L"OwnerName"))
{
setOwner(tag->getString(L"OwnerName"));
}
// 4J: This is for handling old save data, not needed on console
/*AttributeInstance *oldSpeedAttribute = getAttributes()->getInstance(SharedMonsterAttributes::MOVEMENT_SPEED);
if (oldSpeedAttribute != NULL)
{
getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(oldSpeedAttribute->getBaseValue() * 0.25f);
}*/
if (isChestedHorse())
{
ListTag<CompoundTag> *nbttaglist = (ListTag<CompoundTag> *) tag->getList(L"Items");
createInventory();
for (int i = 0; i < nbttaglist->size(); i++)
{
CompoundTag *compoundTag = nbttaglist->get(i);
int slot = compoundTag->getByte(L"Slot") & 0xFF;
if (slot >= INV_BASE_COUNT && slot < inventory->getContainerSize())
{
inventory->setItem(slot, ItemInstance::fromTag(compoundTag));
}
}
}
if (tag->contains(L"ArmorItem"))
{
shared_ptr<ItemInstance> armor = ItemInstance::fromTag(tag->getCompound(L"ArmorItem"));
if (armor != NULL && isHorseArmor(armor->id))
{
inventory->setItem(INV_SLOT_ARMOR, armor);
}
}
if (tag->contains(L"SaddleItem"))
{
shared_ptr<ItemInstance> saddleItem = ItemInstance::fromTag(tag->getCompound(L"SaddleItem"));
if (saddleItem != NULL && saddleItem->id == Item::saddle_Id)
{
inventory->setItem(INV_SLOT_SADDLE, saddleItem);
}
}
else if (tag->getBoolean(L"Saddle"))
{
inventory->setItem(INV_SLOT_SADDLE, shared_ptr<ItemInstance>( new ItemInstance(Item::saddle)));
}
updateEquipment();
}
bool EntityHorse::canMate(shared_ptr<Animal> partner)
{
if (partner == shared_from_this()) return false;
if (partner->GetType() != GetType()) return false;
shared_ptr<EntityHorse> horsePartner = dynamic_pointer_cast<EntityHorse>(partner);
if (!isReadyForParenting() || !horsePartner->isReadyForParenting())
{
return false;
}
int type = getType();
int pType = horsePartner->getType();
return type == pType || (type == TYPE_HORSE && pType == TYPE_DONKEY) || (type == TYPE_DONKEY && pType == TYPE_HORSE);
}
shared_ptr<AgableMob> EntityHorse::getBreedOffspring(shared_ptr<AgableMob> partner)
{
shared_ptr<EntityHorse> horsePartner = dynamic_pointer_cast<EntityHorse>(partner);
shared_ptr<EntityHorse> baby = shared_ptr<EntityHorse>( new EntityHorse(level) );
int type = getType();
int partnerType = horsePartner->getType();
int babyType = TYPE_HORSE;
if (type == partnerType)
{
babyType = type;
}
else if (type == TYPE_HORSE && partnerType == TYPE_DONKEY || type == TYPE_DONKEY && partnerType == TYPE_HORSE)
{
babyType = TYPE_MULE;
}
// select skin and marking colors
if (babyType == TYPE_HORSE)
{
int skinResult;
int selectSkin = random->nextInt(9);
if (selectSkin < 4)
{
skinResult = getVariant() & 0xff;
}
else if (selectSkin < 8)
{
skinResult = horsePartner->getVariant() & 0xff;
}
else
{
skinResult = random->nextInt(VARIANTS);
}
int selectMarking = random->nextInt(5);
if (selectMarking < 4)
{
skinResult |= getVariant() & 0xff00;
}
else if (selectMarking < 8)
{
skinResult |= horsePartner->getVariant() & 0xff00;
}
else
{
skinResult |= (random->nextInt(MARKINGS) << 8) & 0xff00;
}
baby->setVariant(skinResult);
}
baby->setType(babyType);
// generate stats from parents
double maxHealth = getAttribute(SharedMonsterAttributes::MAX_HEALTH)->getBaseValue() + partner->getAttribute(SharedMonsterAttributes::MAX_HEALTH)->getBaseValue() + generateRandomMaxHealth();
baby->getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(maxHealth / 3.0f);
double jumpStrength = getAttribute(JUMP_STRENGTH)->getBaseValue() + partner->getAttribute(JUMP_STRENGTH)->getBaseValue() + generateRandomJumpStrength();
baby->getAttribute(JUMP_STRENGTH)->setBaseValue(jumpStrength / 3.0f);
double speed = getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->getBaseValue() + partner->getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->getBaseValue() + generateRandomSpeed();
baby->getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(speed / 3.0f);
return baby;
}
MobGroupData *EntityHorse::finalizeMobSpawn(MobGroupData *groupData, int extraData /*= 0*/) // 4J Added extraData param
{
groupData = Animal::finalizeMobSpawn(groupData);
int type = 0;
int variant = 0;
if ( dynamic_cast<HorseGroupData *>(groupData) != NULL )
{
type = ((HorseGroupData *) groupData)->horseType;
variant = ((HorseGroupData *) groupData)->horseVariant & 0xff | (random->nextInt(MARKINGS) << 8);
}
else
{
if(extraData != 0)
{
type = extraData - 1;
}
else if (random->nextInt(10) == 0)
{
type = TYPE_DONKEY;
}
else
{
type = TYPE_HORSE;
}
if(type == TYPE_HORSE)
{
int skin = random->nextInt(VARIANTS);
int mark = random->nextInt(MARKINGS);
variant = skin | (mark << 8);
}
groupData = new HorseGroupData(type, variant);
}
setType(type);
setVariant(variant);
if (random->nextInt(5) == 0)
{
setAge(AgableMob::BABY_START_AGE);
}
if (type == TYPE_SKELETON || type == TYPE_UNDEAD)
{
getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(15);
getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.2f);
}
else
{
getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(generateRandomMaxHealth());
if (type == TYPE_HORSE)
{
getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(generateRandomSpeed());
}
else
{
getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.175f);
}
}
if (type == TYPE_MULE || type == TYPE_DONKEY)
{
getAttribute(JUMP_STRENGTH)->setBaseValue(.5f);
}
else
{
getAttribute(JUMP_STRENGTH)->setBaseValue(generateRandomJumpStrength());
}
setHealth(getMaxHealth());
return groupData;
}
float EntityHorse::getEatAnim(float a)
{
return eatAnimO + (eatAnim - eatAnimO) * a;
}
float EntityHorse::getStandAnim(float a)
{
return standAnimO + (standAnim - standAnimO) * a;
}
float EntityHorse::getMouthAnim(float a)
{
return mouthAnimO + (mouthAnim - mouthAnimO) * a;
}
bool EntityHorse::useNewAi()
{
return true;
}
void EntityHorse::onPlayerJump(int jumpAmount)
{
if (isSaddled())
{
if (jumpAmount < 0)
{
jumpAmount = 0;
}
else
{
allowStandSliding = true;
stand();
}
if (jumpAmount >= 90)
{
playerJumpPendingScale = 1.0f;
}
else
{
playerJumpPendingScale = .4f + .4f * (float) jumpAmount / 90.0f;
}
}
}
void EntityHorse::spawnTamingParticles(bool success)
{
ePARTICLE_TYPE particle = success ? eParticleType_heart : eParticleType_smoke;
for (int i = 0; i < 7; i++)
{
double xa = random->nextGaussian() * 0.02;
double ya = random->nextGaussian() * 0.02;
double za = random->nextGaussian() * 0.02;
level->addParticle(particle, x + random->nextFloat() * bbWidth * 2 - bbWidth, y + .5f + random->nextFloat() * bbHeight, z + random->nextFloat() * bbWidth * 2 - bbWidth, xa, ya, za);
}
}
void EntityHorse::handleEntityEvent(byte id)
{
if (id == EntityEvent::TAMING_SUCCEEDED)
{
spawnTamingParticles(true);
}
else if (id == EntityEvent::TAMING_FAILED)
{
spawnTamingParticles(false);
}
else
{
Animal::handleEntityEvent(id);
}
}
void EntityHorse::positionRider()
{
Animal::positionRider();
if (standAnimO > 0)
{
float sin = Mth::sin(yBodyRot * PI / 180);
float cos = Mth::cos(yBodyRot * PI / 180);
float dist = .7f * standAnimO;
float height = .15f * standAnimO;
rider.lock()->setPos(x + dist * sin, y + getRideHeight() + rider.lock()->getRidingHeight() + height, z - dist * cos);
if ( rider.lock()->instanceof(eTYPE_LIVINGENTITY) )
{
shared_ptr<LivingEntity> livingRider = dynamic_pointer_cast<LivingEntity>(rider.lock());
livingRider->yBodyRot = yBodyRot;
}
}
}
// Health is between 15 and 30
float EntityHorse::generateRandomMaxHealth()
{
return 15.0f + random->nextInt(8) + random->nextInt(9);
}
double EntityHorse::generateRandomJumpStrength()
{
return .4f + random->nextDouble() * .2 + random->nextDouble() * .2 + random->nextDouble() * .2;
}
double EntityHorse::generateRandomSpeed()
{
double speed = (0.45f + random->nextDouble() * .3 + random->nextDouble() * .3 + random->nextDouble() * .3) * 0.25f;
app.DebugPrintf("<EntityHorse::generateRandomSpeed> Speed: %f\n", speed);
return speed;
}
EntityHorse::HorseGroupData::HorseGroupData(int type, int variant)
{
horseType = type;
horseVariant = variant;
}
bool EntityHorse::isHorseArmor(int itemId)
{
return itemId == Item::horseArmorMetal_Id || itemId == Item::horseArmorGold_Id || itemId == Item::horseArmorDiamond_Id;
}
bool EntityHorse::onLadder()
{
// prevent horses from climbing ladders
return false;
}
shared_ptr<Player> EntityHorse::getOwner()
{
return level->getPlayerByUUID(getOwnerName());
}