#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; }