From b691c43c44ff180d10e7d4a9afc83b98551ff586 Mon Sep 17 00:00:00 2001 From: daoge_cmd <3523206925@qq.com> Date: Sun, 1 Mar 2026 12:16:08 +0800 Subject: Initial commit --- Minecraft.World/Villager.cpp | 782 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 782 insertions(+) create mode 100644 Minecraft.World/Villager.cpp (limited to 'Minecraft.World/Villager.cpp') diff --git a/Minecraft.World/Villager.cpp b/Minecraft.World/Villager.cpp new file mode 100644 index 00000000..328c0c70 --- /dev/null +++ b/Minecraft.World/Villager.cpp @@ -0,0 +1,782 @@ +#include "stdafx.h" +#include "com.mojang.nbt.h" +#include "net.minecraft.world.entity.ai.goal.h" +#include "net.minecraft.world.entity.ai.navigation.h" +#include "net.minecraft.world.entity.ai.village.h" +#include "net.minecraft.world.entity.monster.h" +#include "net.minecraft.world.entity.player.h" +#include "net.minecraft.world.effect.h" +#include "net.minecraft.world.entity.h" +#include "net.minecraft.world.damagesource.h" +#include "net.minecraft.world.item.h" +#include "net.minecraft.world.item.enchantment.h" +#include "net.minecraft.world.item.trading.h" +#include "net.minecraft.world.level.tile.h" +#include "net.minecraft.world.level.h" +#include "..\Minecraft.Client\Textures.h" +#include "Villager.h" + +unordered_map > Villager::MIN_MAX_VALUES; +unordered_map > Villager::MIN_MAX_PRICES; + +void Villager::_init(int profession) +{ + // 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(); + + // 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 + health = getMaxHealth(); + + setProfession(profession); + setSize(.6f, 1.8f); + + runSpeed = 0.5f; + + villageUpdateInterval = 0; + inLove = false; + chasing = false; + village = weak_ptr(); + + tradingPlayer = weak_ptr(); + offers = NULL; + updateMerchantTimer = 0; + addRecipeOnUpdate = false; + riches = 0; + lastPlayerTradeName = L""; + rewardPlayersOnFirstVillage = false; + baseRecipeChanceMod = 0.0f; + + getNavigation()->setCanOpenDoors(true); + getNavigation()->setAvoidWater(true); + + goalSelector.addGoal(0, new FloatGoal(this)); + goalSelector.addGoal(1, new AvoidPlayerGoal(this, typeid(Zombie), 8, 0.3f, 0.35f)); + goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); + goalSelector.addGoal(1, new LookAtTradingPlayerGoal(this)); + goalSelector.addGoal(2, new MoveIndoorsGoal(this)); + goalSelector.addGoal(3, new RestrictOpenDoorGoal(this)); + goalSelector.addGoal(4, new OpenDoorGoal(this, true)); + goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 0.3f)); + goalSelector.addGoal(6, new MakeLoveGoal(this)); + goalSelector.addGoal(7, new TakeFlowerGoal(this)); + goalSelector.addGoal(8, new PlayGoal(this, 0.32f)); + goalSelector.addGoal(9, new InteractGoal(this, typeid(Player), 3, 1.f)); + goalSelector.addGoal(9, new InteractGoal(this, typeid(Villager), 5, 0.02f)); + goalSelector.addGoal(9, new RandomStrollGoal(this, 0.3f)); + goalSelector.addGoal(10, new LookAtPlayerGoal(this, typeid(Mob), 8)); +} + +Villager::Villager(Level *level) : AgableMob(level) +{ + _init(0); +} + +Villager::Villager(Level *level, int profession) : AgableMob(level) +{ + _init(profession); +} + +Villager::~Villager() +{ + delete offers; +} + +bool Villager::useNewAi() +{ + return true; +} + +void Villager::serverAiMobStep() +{ + if (--villageUpdateInterval <= 0) + { + level->villages->queryUpdateAround(Mth::floor(x), Mth::floor(y), Mth::floor(z)); + villageUpdateInterval = 70 + random->nextInt(50); + + shared_ptr _village = level->villages->getClosestVillage(Mth::floor(x), Mth::floor(y), Mth::floor(z), Villages::MaxDoorDist); + village = _village; + if (_village == NULL) clearRestriction(); + else + { + Pos *center = _village->getCenter(); + restrictTo(center->x, center->y, center->z, (int)((float)_village->getRadius() * 0.6f)); + if (rewardPlayersOnFirstVillage) + { + rewardPlayersOnFirstVillage = false; + _village->rewardAllPlayers(5); + } + } + } + + if (!isTrading() && updateMerchantTimer > 0) + { + updateMerchantTimer--; + if (updateMerchantTimer <= 0) + { + if (addRecipeOnUpdate) + { + // improve max uses for all obsolete recipes + if (offers->size() > 0) + { + //for (MerchantRecipe recipe : offers) + for(AUTO_VAR(it, offers->begin()); it != offers->end(); ++it) + { + MerchantRecipe *recipe = *it; + if (recipe->isDeprecated()) + { + recipe->increaseMaxUses(random->nextInt(6) + random->nextInt(6) + 2); + } + } + } + addOffers(1); + addRecipeOnUpdate = false; + + if (village.lock() != NULL && !lastPlayerTradeName.empty()) + { + level->broadcastEntityEvent(shared_from_this(), EntityEvent::VILLAGER_HAPPY); + village.lock()->modifyStanding(lastPlayerTradeName, 1); + } + } + addEffect(new MobEffectInstance(MobEffect::regeneration->id, SharedConstants::TICKS_PER_SECOND * 10, 0)); + } + } + + AgableMob::serverAiMobStep(); +} + +bool Villager::interact(shared_ptr player) +{ + // [EB]: Truly dislike this code but I don't see another easy way + shared_ptr item = player->inventory->getSelected(); + bool holdingSpawnEgg = item != NULL && item->id == Item::monsterPlacer_Id; + + if (!holdingSpawnEgg && isAlive() && !isTrading() && !isBaby()) + { + if (!level->isClientSide) + { + // note: stop() logic is controlled by trading ai goal + setTradingPlayer(player); + player->openTrading(dynamic_pointer_cast(shared_from_this())); + } + return true; + } + return AgableMob::interact(player); +} + +void Villager::defineSynchedData() +{ + AgableMob::defineSynchedData(); + entityData->define(DATA_PROFESSION_ID, 0); +} + +int Villager::getMaxHealth() +{ + return 20; +} + +void Villager::addAdditonalSaveData(CompoundTag *tag) +{ + AgableMob::addAdditonalSaveData(tag); + tag->putInt(L"Profession", getProfession()); + tag->putInt(L"Riches", riches); + if (offers != NULL) + { + tag->putCompound(L"Offers", offers->createTag()); + } +} + +void Villager::readAdditionalSaveData(CompoundTag *tag) +{ + AgableMob::readAdditionalSaveData(tag); + setProfession(tag->getInt(L"Profession")); + riches = tag->getInt(L"Riches"); + if (tag->contains(L"Offers")) + { + CompoundTag *compound = tag->getCompound(L"Offers"); + delete offers; + offers = new MerchantRecipeList(compound); + } +} + +int Villager::getTexture() +{ + // 4J Made switch + switch(getProfession()) + { + case PROFESSION_FARMER: + return TN_MOB_VILLAGER_FARMER; // 4J was "/mob/villager/farmer.png"; + break; + case PROFESSION_LIBRARIAN: + return TN_MOB_VILLAGER_LIBRARIAN; // 4J was "/mob/villager/librarian.png"; + break; + case PROFESSION_PRIEST: + return TN_MOB_VILLAGER_PRIEST; // 4J was "/mob/villager/priest.png"; + break; + case PROFESSION_SMITH: + return TN_MOB_VILLAGER_SMITH; // 4J was "/mob/villager/smith.png"; + break; + case PROFESSION_BUTCHER: + return TN_MOB_VILLAGER_BUTCHER; // 4J was "/mob/villager/butcher.png"; + break; + //default: + // return TN_MOB_VILLAGER_VILLAGER; // 4J was "/mob/villager/villager.png"; + // break; + } + + return AgableMob::getTexture(); +} + +bool Villager::removeWhenFarAway() +{ + return false; +} + +int Villager::getAmbientSound() +{ + if(isTrading()) + { + return eSoundType_MOB_VILLAGER_HAGGLE; + } + return eSoundType_MOB_VILLAGER_IDLE; +} + +int Villager::getHurtSound() +{ + return eSoundType_MOB_VILLAGER_HIT; +} + +int Villager::getDeathSound() +{ + return eSoundType_MOB_VILLAGER_DEATH; +} + +void Villager::setProfession(int profession) +{ + entityData->set(DATA_PROFESSION_ID, profession); +} + +int Villager::getProfession() +{ + return entityData->getInteger(DATA_PROFESSION_ID); +} + +bool Villager::isInLove() +{ + return inLove; +} + +void Villager::setInLove(bool inLove) +{ + this->inLove = inLove; +} + +void Villager::setChasing(bool chasing) +{ + this->chasing = chasing; +} + +bool Villager::isChasing() +{ + return chasing; +} + +void Villager::setLastHurtByMob(shared_ptr mob) +{ + AgableMob::setLastHurtByMob(mob); + shared_ptr _village = village.lock(); + if (_village != NULL && mob != NULL) + { + _village->addAggressor(mob); + + shared_ptr player = dynamic_pointer_cast(mob); + if (player) + { + int amount = -1; + if (isBaby()) + { + amount = -3; + } + _village->modifyStanding(player->getName(), amount); + if (isAlive()) + { + level->broadcastEntityEvent(shared_from_this(), EntityEvent::VILLAGER_ANGRY); + } + } + } +} + +void Villager::die(DamageSource *source) +{ + shared_ptr _village = village.lock(); + if (_village != NULL) + { + shared_ptr sourceEntity = source->getEntity(); + if (sourceEntity != NULL) + { + if ((sourceEntity->GetType() & eTYPE_PLAYER) == eTYPE_PLAYER) + { + shared_ptr player = dynamic_pointer_cast(sourceEntity); + _village->modifyStanding(player->getName(), -2); + } + else if ((sourceEntity->GetType() & eTYPE_ENEMY) == eTYPE_ENEMY) + { + _village->resetNoBreedTimer(); + } + } + else if (sourceEntity == NULL) + { + // if the villager was killed by the world (such as lava or falling), blame + // the nearest player by not reproducing for a while + shared_ptr nearestPlayer = level->getNearestPlayer(shared_from_this(), 16.0f); + if (nearestPlayer != NULL) + { + _village->resetNoBreedTimer(); + } + } + } + + AgableMob::die(source); +} + +void Villager::setTradingPlayer(shared_ptr player) +{ + tradingPlayer = weak_ptr(player); +} + +shared_ptr Villager::getTradingPlayer() +{ + return tradingPlayer.lock(); +} + +bool Villager::isTrading() +{ + return tradingPlayer.lock() != NULL; +} + +void Villager::notifyTrade(MerchantRecipe *activeRecipe) +{ + activeRecipe->increaseUses(); + ambientSoundTime = -getAmbientSoundInterval(); + playSound(eSoundType_MOB_VILLAGER_YES, getSoundVolume(), getVoicePitch()); + + // when the player buys the latest item, we improve the merchant a little while later + if (activeRecipe->isSame(offers->at(offers->size() - 1))) + { + updateMerchantTimer = SharedConstants::TICKS_PER_SECOND * 2; + addRecipeOnUpdate = true; + if (tradingPlayer.lock() != NULL) + { + lastPlayerTradeName = tradingPlayer.lock()->getName(); + } + else + { + lastPlayerTradeName = L""; + } + } + + if (activeRecipe->getBuyAItem()->id == Item::emerald_Id) + { + riches += activeRecipe->getBuyAItem()->count; + } +} + +void Villager::notifyTradeUpdated(shared_ptr item) +{ + if (!level->isClientSide && (ambientSoundTime > (-getAmbientSoundInterval() + SharedConstants::TICKS_PER_SECOND))) + { + ambientSoundTime = -getAmbientSoundInterval(); + if (item != NULL) + { + playSound(eSoundType_MOB_VILLAGER_YES, getSoundVolume(), getVoicePitch()); + } + else + { + playSound(eSoundType_MOB_VILLAGER_NO, getSoundVolume(), getVoicePitch()); + } + } +} + +MerchantRecipeList *Villager::getOffers(shared_ptr forPlayer) +{ + if (offers == NULL) + { + addOffers(1); + } + return offers; +} + +float Villager::getRecipeChance(float baseChance) +{ + float newChance = baseChance + baseRecipeChanceMod; + if (newChance > .9f) + { + return .9f - (newChance - .9f); + } + return newChance; +} + +void Villager::addOffers(int addCount) +{ + MerchantRecipeList *newOffers = new MerchantRecipeList(); + switch (getProfession()) + { + case PROFESSION_FARMER: + addItemForTradeIn(newOffers, Item::wheat_Id, random, getRecipeChance(.9f)); + addItemForTradeIn(newOffers, Tile::cloth_Id, random, getRecipeChance(.5f)); + addItemForTradeIn(newOffers, Item::chicken_raw_Id, random, getRecipeChance(.5f)); + addItemForTradeIn(newOffers, Item::fish_cooked_Id, random, getRecipeChance(.4f)); + addItemForPurchase(newOffers, Item::bread_Id, random, getRecipeChance(.9f)); + addItemForPurchase(newOffers, Item::melon_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::apple_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::cookie_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::shears_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::flintAndSteel_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::chicken_cooked_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::arrow_Id, random, getRecipeChance(.5f)); + if (random->nextFloat() < .5f) + { + newOffers->push_back(new MerchantRecipe(shared_ptr( new ItemInstance(Tile::gravel, 10) ), shared_ptr( new ItemInstance(Item::emerald) ), shared_ptr( new ItemInstance(Item::flint_Id, 2 + random->nextInt(2), 0)))); + } + break; + case PROFESSION_BUTCHER: + addItemForTradeIn(newOffers, Item::coal_Id, random, getRecipeChance(.7f)); + addItemForTradeIn(newOffers, Item::porkChop_raw_Id, random, getRecipeChance(.5f)); + addItemForTradeIn(newOffers, Item::beef_raw_Id, random, getRecipeChance(.5f)); + addItemForPurchase(newOffers, Item::saddle_Id, random, getRecipeChance(.1f)); + addItemForPurchase(newOffers, Item::chestplate_cloth_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::boots_cloth_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::helmet_cloth_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::leggings_cloth_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::porkChop_cooked_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::beef_cooked_Id, random, getRecipeChance(.3f)); + break; + case PROFESSION_SMITH: + addItemForTradeIn(newOffers, Item::coal_Id, random, getRecipeChance(.7f)); + addItemForTradeIn(newOffers, Item::ironIngot_Id, random, getRecipeChance(.5f)); + addItemForTradeIn(newOffers, Item::goldIngot_Id, random, getRecipeChance(.5f)); + addItemForTradeIn(newOffers, Item::diamond_Id, random, getRecipeChance(.5f)); + + addItemForPurchase(newOffers, Item::sword_iron_Id, random, getRecipeChance(.5f)); + addItemForPurchase(newOffers, Item::sword_diamond_Id, random, getRecipeChance(.5f)); + addItemForPurchase(newOffers, Item::hatchet_iron_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::hatchet_diamond_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::pickAxe_iron_Id, random, getRecipeChance(.5f)); + addItemForPurchase(newOffers, Item::pickAxe_diamond_Id, random, getRecipeChance(.5f)); + addItemForPurchase(newOffers, Item::shovel_iron_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::shovel_diamond_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::hoe_iron_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::hoe_diamond_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::boots_iron_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::boots_diamond_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::helmet_iron_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::helmet_diamond_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::chestplate_iron_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::chestplate_diamond_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::leggings_iron_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::leggings_diamond_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::boots_chain_Id, random, getRecipeChance(.1f)); + addItemForPurchase(newOffers, Item::helmet_chain_Id, random, getRecipeChance(.1f)); + addItemForPurchase(newOffers, Item::chestplate_chain_Id, random, getRecipeChance(.1f)); + addItemForPurchase(newOffers, Item::leggings_chain_Id, random, getRecipeChance(.1f)); + break; + case PROFESSION_LIBRARIAN: + addItemForTradeIn(newOffers, Item::paper_Id, random, getRecipeChance(.8f)); + addItemForTradeIn(newOffers, Item::book_Id, random, getRecipeChance(.8f)); + //addItemForTradeIn(newOffers, Item::writtenBook_Id, random, getRecipeChance(0.3f)); + addItemForPurchase(newOffers, Tile::bookshelf_Id, random, getRecipeChance(.8f)); + addItemForPurchase(newOffers, Tile::glass_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::compass_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::clock_Id, random, getRecipeChance(.2f)); + + if (random->nextFloat() < getRecipeChance(0.07f)) + { + Enchantment *enchantment = Enchantment::validEnchantments[random->nextInt(Enchantment::validEnchantments.size())]; + int level = Mth::nextInt(random, enchantment->getMinLevel(), enchantment->getMaxLevel()); + shared_ptr book = Item::enchantedBook->createForEnchantment(new EnchantmentInstance(enchantment, level)); + int cost = 2 + random->nextInt(5 + (level * 10)) + 3 * level; + + newOffers->push_back(new MerchantRecipe(shared_ptr(new ItemInstance(Item::book)), shared_ptr(new ItemInstance(Item::emerald, cost)), book)); + } + break; + case PROFESSION_PRIEST: + addItemForPurchase(newOffers, Item::eyeOfEnder_Id, random, getRecipeChance(.3f)); + addItemForPurchase(newOffers, Item::expBottle_Id, random, getRecipeChance(.2f)); + addItemForPurchase(newOffers, Item::redStone_Id, random, getRecipeChance(.4f)); + addItemForPurchase(newOffers, Tile::lightGem_Id, random, getRecipeChance(.3f)); + { + int enchantItems[] = { + Item::sword_iron_Id, Item::sword_diamond_Id, Item::chestplate_iron_Id, Item::chestplate_diamond_Id, Item::hatchet_iron_Id, Item::hatchet_diamond_Id, Item::pickAxe_iron_Id, + Item::pickAxe_diamond_Id + }; + for (unsigned int i = 0; i < 8; ++i) + { + int id = enchantItems[i]; + if (random->nextFloat() < getRecipeChance(.05f)) + { + newOffers->push_back(new MerchantRecipe(shared_ptr(new ItemInstance(id, 1, 0)), + shared_ptr(new ItemInstance(Item::emerald, 2 + random->nextInt(3), 0)), + EnchantmentHelper::enchantItem(random, shared_ptr(new ItemInstance(id, 1, 0)), 5 + random->nextInt(15)))); + } + } + } + break; + } + + if (newOffers->empty()) + { + addItemForTradeIn(newOffers, Item::goldIngot_Id, random, 1.0f); + } + + // shuffle the list to make it more interesting + std::random_shuffle(newOffers->begin(), newOffers->end()); + + if (offers == NULL) + { + offers = new MerchantRecipeList(); + } + for (int i = 0; i < addCount && i < newOffers->size(); i++) + { + if( offers->addIfNewOrBetter(newOffers->at(i))) + { + // 4J Added so we can delete newOffers + newOffers->erase(newOffers->begin() + i); + } + } + delete newOffers; +} + +void Villager::overrideOffers(MerchantRecipeList *recipeList) +{ +} + + +void Villager::staticCtor() +{ + MIN_MAX_VALUES[Item::coal_Id] = pair(16, 24); + MIN_MAX_VALUES[Item::ironIngot_Id] = pair(8, 10); + MIN_MAX_VALUES[Item::goldIngot_Id] = pair(8, 10); + MIN_MAX_VALUES[Item::diamond_Id] = pair(4, 6); + MIN_MAX_VALUES[Item::paper_Id] = pair(24, 36); + MIN_MAX_VALUES[Item::book_Id] = pair(11, 13); + //MIN_MAX_VALUES.insert(Item::writtenBook_Id, pair(1, 1)); + MIN_MAX_VALUES[Item::enderPearl_Id] = pair(3, 4); + MIN_MAX_VALUES[Item::eyeOfEnder_Id] = pair(2, 3); + MIN_MAX_VALUES[Item::porkChop_raw_Id] = pair(14, 18); + MIN_MAX_VALUES[Item::beef_raw_Id] = pair(14, 18); + MIN_MAX_VALUES[Item::chicken_raw_Id] = pair(14, 18); + MIN_MAX_VALUES[Item::fish_cooked_Id] = pair(9, 13); + MIN_MAX_VALUES[Item::seeds_wheat_Id] = pair(34, 48); + MIN_MAX_VALUES[Item::seeds_melon_Id] = pair(30, 38); + MIN_MAX_VALUES[Item::seeds_pumpkin_Id] = pair(30, 38); + MIN_MAX_VALUES[Item::wheat_Id] = pair(18, 22); + MIN_MAX_VALUES[Tile::cloth_Id] = pair(14, 22); + MIN_MAX_VALUES[Item::rotten_flesh_Id] = pair(36, 64); + + MIN_MAX_PRICES[Item::flintAndSteel_Id] = pair(3, 4); + MIN_MAX_PRICES[Item::shears_Id] = pair(3, 4); + MIN_MAX_PRICES[Item::sword_iron_Id] = pair(7, 11); + MIN_MAX_PRICES[Item::sword_diamond_Id] = pair(12, 14); + MIN_MAX_PRICES[Item::hatchet_iron_Id] = pair(6, 8); + MIN_MAX_PRICES[Item::hatchet_diamond_Id] = pair(9, 12); + MIN_MAX_PRICES[Item::pickAxe_iron_Id] = pair(7, 9); + MIN_MAX_PRICES[Item::pickAxe_diamond_Id] = pair(10, 12); + MIN_MAX_PRICES[Item::shovel_iron_Id] = pair(4, 6); + MIN_MAX_PRICES[Item::shovel_diamond_Id] = pair(7, 8); + MIN_MAX_PRICES[Item::hoe_iron_Id] = pair(4, 6); + MIN_MAX_PRICES[Item::hoe_diamond_Id] = pair(7, 8); + MIN_MAX_PRICES[Item::boots_iron_Id] = pair(4, 6); + MIN_MAX_PRICES[Item::boots_diamond_Id] = pair(7, 8); + MIN_MAX_PRICES[Item::helmet_iron_Id] = pair(4, 6); + MIN_MAX_PRICES[Item::helmet_diamond_Id] = pair(7, 8); + MIN_MAX_PRICES[Item::chestplate_iron_Id] = pair(10, 14); + MIN_MAX_PRICES[Item::chestplate_diamond_Id] = pair(16, 19); + MIN_MAX_PRICES[Item::leggings_iron_Id] = pair(8, 10); + MIN_MAX_PRICES[Item::leggings_diamond_Id] = pair(11, 14); + MIN_MAX_PRICES[Item::boots_chain_Id] = pair(5, 7); + MIN_MAX_PRICES[Item::helmet_chain_Id] = pair(5, 7); + MIN_MAX_PRICES[Item::chestplate_chain_Id] = pair(11, 15); + MIN_MAX_PRICES[Item::leggings_chain_Id] = pair(9, 11); + MIN_MAX_PRICES[Item::bread_Id] = pair(-4, -2); + MIN_MAX_PRICES[Item::melon_Id] = pair(-8, -4); + MIN_MAX_PRICES[Item::apple_Id] = pair(-8, -4); + MIN_MAX_PRICES[Item::cookie_Id] = pair(-10, -7); + MIN_MAX_PRICES[Tile::glass_Id] = pair(-5, -3); + MIN_MAX_PRICES[Tile::bookshelf_Id] = pair(3, 4); + MIN_MAX_PRICES[Item::chestplate_cloth_Id] = pair(4, 5); + MIN_MAX_PRICES[Item::boots_cloth_Id] = pair(2, 4); + MIN_MAX_PRICES[Item::helmet_cloth_Id] = pair(2, 4); + MIN_MAX_PRICES[Item::leggings_cloth_Id] = pair(2, 4); + MIN_MAX_PRICES[Item::saddle_Id] = pair(6, 8); + MIN_MAX_PRICES[Item::expBottle_Id] = pair(-4, -1); + MIN_MAX_PRICES[Item::redStone_Id] = pair(-4, -1); + MIN_MAX_PRICES[Item::compass_Id] = pair(10, 12); + MIN_MAX_PRICES[Item::clock_Id] = pair(10, 12); + MIN_MAX_PRICES[Tile::lightGem_Id] = pair(-3, -1); + MIN_MAX_PRICES[Item::porkChop_cooked_Id] = pair(-7, -5); + MIN_MAX_PRICES[Item::beef_cooked_Id] = pair(-7, -5); + MIN_MAX_PRICES[Item::chicken_cooked_Id] = pair(-8, -6); + MIN_MAX_PRICES[Item::eyeOfEnder_Id] = pair(7, 11); + MIN_MAX_PRICES[Item::arrow_Id] = pair(-12, -8); +} + +/** +* Adds a merchant recipe that trades items for a single ruby. +* +* @param list +* @param itemId +* @param random +* @param likelyHood +*/ +void Villager::addItemForTradeIn(MerchantRecipeList *list, int itemId, Random *random, float likelyHood) +{ + if (random->nextFloat() < likelyHood) + { + list->push_back(new MerchantRecipe(getItemTradeInValue(itemId, random), Item::emerald)); + } +} + +shared_ptr Villager::getItemTradeInValue(int itemId, Random *random) +{ + return shared_ptr(new ItemInstance(itemId, getTradeInValue(itemId, random), 0)); +} + +int Villager::getTradeInValue(int itemId, Random *random) +{ + AUTO_VAR(it,MIN_MAX_VALUES.find(itemId)); + if (it == MIN_MAX_VALUES.end()) + { + return 1; + } + pair minMax = it->second; + if (minMax.first >= minMax.second) + { + return minMax.first; + } + return minMax.first + random->nextInt(minMax.second - minMax.first); +} + +/** +* Adds a merchant recipe that trades rubies for an item. If the cost is +* negative, one ruby will give several of that item. +* +* @param list +* @param itemId +* @param random +* @param likelyHood +*/ +void Villager::addItemForPurchase(MerchantRecipeList *list, int itemId, Random *random, float likelyHood) +{ + if (random->nextFloat() < likelyHood) + { + int purchaseCost = getPurchaseCost(itemId, random); + shared_ptr rubyItem; + shared_ptr resultItem; + if (purchaseCost < 0) + { + rubyItem = shared_ptr( new ItemInstance(Item::emerald_Id, 1, 0) ); + resultItem = shared_ptr( new ItemInstance(itemId, -purchaseCost, 0) ); + } + else + { + rubyItem = shared_ptr( new ItemInstance(Item::emerald_Id, purchaseCost, 0) ); + resultItem = shared_ptr( new ItemInstance(itemId, 1, 0) ); + } + list->push_back(new MerchantRecipe(rubyItem, resultItem)); + } +} + +int Villager::getPurchaseCost(int itemId, Random *random) +{ + AUTO_VAR(it,MIN_MAX_PRICES.find(itemId)); + if (it == MIN_MAX_PRICES.end()) + { + return 1; + } + pair minMax = it->second; + if (minMax.first >= minMax.second) + { + return minMax.first; + } + return minMax.first + random->nextInt(minMax.second - minMax.first); +} + +void Villager::handleEntityEvent(byte id) +{ + if (id == EntityEvent::LOVE_HEARTS) + { + addParticlesAroundSelf(eParticleType_heart); + } + else if (id == EntityEvent::VILLAGER_ANGRY) + { + addParticlesAroundSelf(eParticleType_angryVillager); + } + else if (id == EntityEvent::VILLAGER_HAPPY) + { + addParticlesAroundSelf(eParticleType_happyVillager); + } + else + { + AgableMob::handleEntityEvent(id); + } +} + +void Villager::addParticlesAroundSelf(ePARTICLE_TYPE particle) +{ + for (int i = 0; i < 5; 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 + 1.0f + random->nextFloat() * bbHeight, z + random->nextFloat() * bbWidth * 2 - bbWidth, xa, ya, za); + } +} + +void Villager::finalizeMobSpawn() +{ + setProfession(level->random->nextInt(Villager::PROFESSION_MAX)); +} + +void Villager::setRewardPlayersInVillage() +{ + rewardPlayersOnFirstVillage = true; +} + +shared_ptr Villager::getBreedOffspring(shared_ptr target) +{ + // 4J - added limit to villagers that can be bred + if(level->canCreateMore(GetType(), Level::eSpawnType_Breed) ) + { + shared_ptr villager = shared_ptr(new Villager(level)); + villager->finalizeMobSpawn(); + return villager; + } + else + { + return nullptr; + } +} + +int Villager::getDisplayName() +{ + int name = IDS_VILLAGER; + switch(getProfession()) + { + case PROFESSION_FARMER: + name = IDS_VILLAGER_FARMER; + break; + case PROFESSION_LIBRARIAN: + name = IDS_VILLAGER_LIBRARIAN; + break; + case PROFESSION_PRIEST: + name = IDS_VILLAGER_PRIEST; + break; + case PROFESSION_SMITH: + name = IDS_VILLAGER_SMITH; + break; + case PROFESSION_BUTCHER: + name = IDS_VILLAGER_BUTCHER; + break; + }; + return name; +} -- cgit v1.2.3