aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.World/EntityHorse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Minecraft.World/EntityHorse.cpp')
-rw-r--r--Minecraft.World/EntityHorse.cpp1841
1 files changed, 1841 insertions, 0 deletions
diff --git a/Minecraft.World/EntityHorse.cpp b/Minecraft.World/EntityHorse.cpp
new file mode 100644
index 00000000..2d67d21b
--- /dev/null
+++ b/Minecraft.World/EntityHorse.cpp
@@ -0,0 +1,1841 @@
+#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->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_VAR(it,list->begin()); it != list->end(); ++it)
+ {
+ shared_ptr<Entity> horse = *it;
+ 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"_" + _toString<int>(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());
+} \ No newline at end of file