diff options
Diffstat (limited to 'Minecraft.World/EntityHorse.cpp')
| -rw-r--r-- | Minecraft.World/EntityHorse.cpp | 1841 |
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 |
