the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
1#include "stdafx.h"
2#include "com.mojang.nbt.h"
3#include "net.minecraft.world.entity.ai.attributes.h"
4#include "net.minecraft.world.entity.ai.goal.h"
5#include "net.minecraft.world.entity.ai.navigation.h"
6#include "net.minecraft.world.entity.ai.village.h"
7#include "net.minecraft.world.entity.monster.h"
8#include "net.minecraft.world.entity.player.h"
9#include "net.minecraft.world.effect.h"
10#include "net.minecraft.world.entity.h"
11#include "net.minecraft.world.damagesource.h"
12#include "net.minecraft.world.item.h"
13#include "net.minecraft.world.item.enchantment.h"
14#include "net.minecraft.world.item.trading.h"
15#include "net.minecraft.world.level.tile.h"
16#include "net.minecraft.world.level.h"
17#include "..\Minecraft.Client\Textures.h"
18#include "Villager.h"
19
20unordered_map<int, pair<int,int> > Villager::MIN_MAX_VALUES;
21unordered_map<int, pair<int,int> > Villager::MIN_MAX_PRICES;
22
23void Villager::_init(int profession)
24{
25 // 4J Stu - This function call had to be moved here from the Entity ctor to ensure that
26 // the derived version of the function is called
27 this->defineSynchedData();
28 registerAttributes();
29 setHealth(getMaxHealth());
30
31 setProfession(profession);
32 setSize(.6f, 1.8f);
33
34 villageUpdateInterval = 0;
35 inLove = false;
36 chasing = false;
37 village = weak_ptr<Village>();
38
39 tradingPlayer = weak_ptr<Player>();
40 offers = NULL;
41 updateMerchantTimer = 0;
42 addRecipeOnUpdate = false;
43 riches = 0;
44 lastPlayerTradeName = L"";
45 rewardPlayersOnFirstVillage = false;
46 baseRecipeChanceMod = 0.0f;
47
48 getNavigation()->setCanOpenDoors(true);
49 getNavigation()->setAvoidWater(true);
50
51 goalSelector.addGoal(0, new FloatGoal(this));
52 goalSelector.addGoal(1, new AvoidPlayerGoal(this, typeid(Zombie), 8, 0.6, 0.6));
53 goalSelector.addGoal(1, new TradeWithPlayerGoal(this));
54 goalSelector.addGoal(1, new LookAtTradingPlayerGoal(this));
55 goalSelector.addGoal(2, new MoveIndoorsGoal(this));
56 goalSelector.addGoal(3, new RestrictOpenDoorGoal(this));
57 goalSelector.addGoal(4, new OpenDoorGoal(this, true));
58 goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 0.6));
59 goalSelector.addGoal(6, new MakeLoveGoal(this));
60 goalSelector.addGoal(7, new TakeFlowerGoal(this));
61 goalSelector.addGoal(8, new PlayGoal(this, 0.32));
62 goalSelector.addGoal(9, new InteractGoal(this, typeid(Player), 3, 1.f));
63 goalSelector.addGoal(9, new InteractGoal(this, typeid(Villager), 5, 0.02f));
64 goalSelector.addGoal(9, new RandomStrollGoal(this, 0.6));
65 goalSelector.addGoal(10, new LookAtPlayerGoal(this, typeid(Mob), 8));
66}
67
68Villager::Villager(Level *level) : AgableMob(level)
69{
70 _init(0);
71}
72
73Villager::Villager(Level *level, int profession) : AgableMob(level)
74{
75 _init(profession);
76}
77
78Villager::~Villager()
79{
80 delete offers;
81}
82
83void Villager::registerAttributes()
84{
85 AgableMob::registerAttributes();
86
87 getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.5f);
88}
89
90bool Villager::useNewAi()
91{
92 return true;
93}
94
95void Villager::serverAiMobStep()
96{
97 if (--villageUpdateInterval <= 0)
98 {
99 level->villages->queryUpdateAround(Mth::floor(x), Mth::floor(y), Mth::floor(z));
100 villageUpdateInterval = 70 + random->nextInt(50);
101
102 shared_ptr<Village> _village = level->villages->getClosestVillage(Mth::floor(x), Mth::floor(y), Mth::floor(z), Villages::MaxDoorDist);
103 village = _village;
104 if (_village == NULL) clearRestriction();
105 else
106 {
107 Pos *center = _village->getCenter();
108 restrictTo(center->x, center->y, center->z, (int)((float)_village->getRadius() * 0.6f));
109 if (rewardPlayersOnFirstVillage)
110 {
111 rewardPlayersOnFirstVillage = false;
112 _village->rewardAllPlayers(5);
113 }
114 }
115 }
116
117 if (!isTrading() && updateMerchantTimer > 0)
118 {
119 updateMerchantTimer--;
120 if (updateMerchantTimer <= 0)
121 {
122 if (addRecipeOnUpdate)
123 {
124 // improve max uses for all obsolete recipes
125 if (offers->size() > 0)
126 {
127 //for (MerchantRecipe recipe : offers)
128 for(AUTO_VAR(it, offers->begin()); it != offers->end(); ++it)
129 {
130 MerchantRecipe *recipe = *it;
131 if (recipe->isDeprecated())
132 {
133 recipe->increaseMaxUses(random->nextInt(6) + random->nextInt(6) + 2);
134 }
135 }
136 }
137 addOffers(1);
138 addRecipeOnUpdate = false;
139
140 if (village.lock() != NULL && !lastPlayerTradeName.empty())
141 {
142 level->broadcastEntityEvent(shared_from_this(), EntityEvent::VILLAGER_HAPPY);
143 village.lock()->modifyStanding(lastPlayerTradeName, 1);
144 }
145 }
146 addEffect(new MobEffectInstance(MobEffect::regeneration->id, SharedConstants::TICKS_PER_SECOND * 10, 0));
147 }
148 }
149
150 AgableMob::serverAiMobStep();
151}
152
153bool Villager::mobInteract(shared_ptr<Player> player)
154{
155 // [EB]: Truly dislike this code but I don't see another easy way
156 shared_ptr<ItemInstance> item = player->inventory->getSelected();
157 bool holdingSpawnEgg = item != NULL && item->id == Item::spawnEgg_Id;
158
159 if (!holdingSpawnEgg && isAlive() && !isTrading() && !isBaby())
160 {
161 if (!level->isClientSide)
162 {
163 // note: stop() logic is controlled by trading ai goal
164 setTradingPlayer(player);
165
166 // 4J-JEV: Villagers in PC game don't display professions.
167 player->openTrading(dynamic_pointer_cast<Merchant>(shared_from_this()), getDisplayName() );
168 }
169 return true;
170 }
171 return AgableMob::mobInteract(player);
172}
173
174void Villager::defineSynchedData()
175{
176 AgableMob::defineSynchedData();
177 entityData->define(DATA_PROFESSION_ID, 0);
178}
179
180void Villager::addAdditonalSaveData(CompoundTag *tag)
181{
182 AgableMob::addAdditonalSaveData(tag);
183 tag->putInt(L"Profession", getProfession());
184 tag->putInt(L"Riches", riches);
185 if (offers != NULL)
186 {
187 tag->putCompound(L"Offers", offers->createTag());
188 }
189}
190
191void Villager::readAdditionalSaveData(CompoundTag *tag)
192{
193 AgableMob::readAdditionalSaveData(tag);
194 setProfession(tag->getInt(L"Profession"));
195 riches = tag->getInt(L"Riches");
196 if (tag->contains(L"Offers"))
197 {
198 CompoundTag *compound = tag->getCompound(L"Offers");
199 delete offers;
200 offers = new MerchantRecipeList(compound);
201 }
202}
203
204bool Villager::removeWhenFarAway()
205{
206 return false;
207}
208
209int Villager::getAmbientSound()
210{
211 if(isTrading())
212 {
213 return eSoundType_MOB_VILLAGER_HAGGLE;
214 }
215 return eSoundType_MOB_VILLAGER_IDLE;
216}
217
218int Villager::getHurtSound()
219{
220 return eSoundType_MOB_VILLAGER_HIT;
221}
222
223int Villager::getDeathSound()
224{
225 return eSoundType_MOB_VILLAGER_DEATH;
226}
227
228void Villager::setProfession(int profession)
229{
230 entityData->set(DATA_PROFESSION_ID, profession);
231}
232
233int Villager::getProfession()
234{
235 return entityData->getInteger(DATA_PROFESSION_ID);
236}
237
238bool Villager::isInLove()
239{
240 return inLove;
241}
242
243void Villager::setInLove(bool inLove)
244{
245 this->inLove = inLove;
246}
247
248void Villager::setChasing(bool chasing)
249{
250 this->chasing = chasing;
251}
252
253bool Villager::isChasing()
254{
255 return chasing;
256}
257
258void Villager::setLastHurtByMob(shared_ptr<LivingEntity> mob)
259{
260 AgableMob::setLastHurtByMob(mob);
261 shared_ptr<Village> _village = village.lock();
262 if (_village != NULL && mob != NULL)
263 {
264 _village->addAggressor(mob);
265
266 if ( mob->instanceof(eTYPE_PLAYER) )
267 {
268 int amount = -1;
269 if (isBaby())
270 {
271 amount = -3;
272 }
273 _village->modifyStanding( dynamic_pointer_cast<Player>(mob)->getName(), amount );
274 if (isAlive())
275 {
276 level->broadcastEntityEvent(shared_from_this(), EntityEvent::VILLAGER_ANGRY);
277 }
278 }
279 }
280}
281
282void Villager::die(DamageSource *source)
283{
284 shared_ptr<Village> _village = village.lock();
285 if (_village != NULL)
286 {
287 shared_ptr<Entity> sourceEntity = source->getEntity();
288 if (sourceEntity != NULL)
289 {
290 if ( sourceEntity->instanceof(eTYPE_PLAYER) )
291 {
292 _village->modifyStanding( dynamic_pointer_cast<Player>(sourceEntity)->getName(), -2 );
293 }
294 else if ( sourceEntity->instanceof(eTYPE_ENEMY) )
295 {
296 _village->resetNoBreedTimer();
297 }
298 }
299 else if (sourceEntity == NULL)
300 {
301 // if the villager was killed by the world (such as lava or falling), blame
302 // the nearest player by not reproducing for a while
303 shared_ptr<Player> nearestPlayer = level->getNearestPlayer(shared_from_this(), 16.0f);
304 if (nearestPlayer != NULL)
305 {
306 _village->resetNoBreedTimer();
307 }
308 }
309 }
310
311 AgableMob::die(source);
312}
313
314void Villager::setTradingPlayer(shared_ptr<Player> player)
315{
316 tradingPlayer = weak_ptr<Player>(player);
317}
318
319shared_ptr<Player> Villager::getTradingPlayer()
320{
321 return tradingPlayer.lock();
322}
323
324bool Villager::isTrading()
325{
326 return tradingPlayer.lock() != NULL;
327}
328
329void Villager::notifyTrade(MerchantRecipe *activeRecipe)
330{
331 activeRecipe->increaseUses();
332 ambientSoundTime = -getAmbientSoundInterval();
333 playSound(eSoundType_MOB_VILLAGER_YES, getSoundVolume(), getVoicePitch());
334
335 // when the player buys the latest item, we improve the merchant a little while later
336 if (activeRecipe->isSame(offers->at(offers->size() - 1)))
337 {
338 updateMerchantTimer = SharedConstants::TICKS_PER_SECOND * 2;
339 addRecipeOnUpdate = true;
340 if (tradingPlayer.lock() != NULL)
341 {
342 lastPlayerTradeName = tradingPlayer.lock()->getName();
343 }
344 else
345 {
346 lastPlayerTradeName = L"";
347 }
348 }
349
350 if (activeRecipe->getBuyAItem()->id == Item::emerald_Id)
351 {
352 riches += activeRecipe->getBuyAItem()->count;
353 }
354}
355
356void Villager::notifyTradeUpdated(shared_ptr<ItemInstance> item)
357{
358 if (!level->isClientSide && (ambientSoundTime > (-getAmbientSoundInterval() + SharedConstants::TICKS_PER_SECOND)))
359 {
360 ambientSoundTime = -getAmbientSoundInterval();
361 if (item != NULL)
362 {
363 playSound(eSoundType_MOB_VILLAGER_YES, getSoundVolume(), getVoicePitch());
364 }
365 else
366 {
367 playSound(eSoundType_MOB_VILLAGER_NO, getSoundVolume(), getVoicePitch());
368 }
369 }
370}
371
372MerchantRecipeList *Villager::getOffers(shared_ptr<Player> forPlayer)
373{
374 if (offers == NULL)
375 {
376 addOffers(1);
377 }
378 return offers;
379}
380
381float Villager::getRecipeChance(float baseChance)
382{
383 float newChance = baseChance + baseRecipeChanceMod;
384 if (newChance > .9f)
385 {
386 return .9f - (newChance - .9f);
387 }
388 return newChance;
389}
390
391void Villager::addOffers(int addCount)
392{
393 MerchantRecipeList *newOffers = new MerchantRecipeList();
394 switch (getProfession())
395 {
396 case PROFESSION_FARMER:
397 addItemForTradeIn(newOffers, Item::wheat_Id, random, getRecipeChance(.9f));
398 addItemForTradeIn(newOffers, Tile::wool_Id, random, getRecipeChance(.5f));
399 addItemForTradeIn(newOffers, Item::chicken_raw_Id, random, getRecipeChance(.5f));
400 addItemForTradeIn(newOffers, Item::fish_cooked_Id, random, getRecipeChance(.4f));
401 addItemForPurchase(newOffers, Item::bread_Id, random, getRecipeChance(.9f));
402 addItemForPurchase(newOffers, Item::melon_Id, random, getRecipeChance(.3f));
403 addItemForPurchase(newOffers, Item::apple_Id, random, getRecipeChance(.3f));
404 addItemForPurchase(newOffers, Item::cookie_Id, random, getRecipeChance(.3f));
405 addItemForPurchase(newOffers, Item::shears_Id, random, getRecipeChance(.3f));
406 addItemForPurchase(newOffers, Item::flintAndSteel_Id, random, getRecipeChance(.3f));
407 addItemForPurchase(newOffers, Item::chicken_cooked_Id, random, getRecipeChance(.3f));
408 addItemForPurchase(newOffers, Item::arrow_Id, random, getRecipeChance(.5f));
409 if (random->nextFloat() < getRecipeChance(.5f))
410 {
411 newOffers->push_back(new MerchantRecipe(shared_ptr<ItemInstance>( new ItemInstance(Tile::gravel, 10) ), shared_ptr<ItemInstance>( new ItemInstance(Item::emerald) ), shared_ptr<ItemInstance>( new ItemInstance(Item::flint_Id, 4 + random->nextInt(2), 0))));
412 }
413 break;
414 case PROFESSION_BUTCHER:
415 addItemForTradeIn(newOffers, Item::coal_Id, random, getRecipeChance(.7f));
416 addItemForTradeIn(newOffers, Item::porkChop_raw_Id, random, getRecipeChance(.5f));
417 addItemForTradeIn(newOffers, Item::beef_raw_Id, random, getRecipeChance(.5f));
418 addItemForPurchase(newOffers, Item::saddle_Id, random, getRecipeChance(.1f));
419 addItemForPurchase(newOffers, Item::chestplate_leather_Id, random, getRecipeChance(.3f));
420 addItemForPurchase(newOffers, Item::boots_leather_Id, random, getRecipeChance(.3f));
421 addItemForPurchase(newOffers, Item::helmet_leather_Id, random, getRecipeChance(.3f));
422 addItemForPurchase(newOffers, Item::leggings_leather_Id, random, getRecipeChance(.3f));
423 addItemForPurchase(newOffers, Item::porkChop_cooked_Id, random, getRecipeChance(.3f));
424 addItemForPurchase(newOffers, Item::beef_cooked_Id, random, getRecipeChance(.3f));
425 break;
426 case PROFESSION_SMITH:
427 addItemForTradeIn(newOffers, Item::coal_Id, random, getRecipeChance(.7f));
428 addItemForTradeIn(newOffers, Item::ironIngot_Id, random, getRecipeChance(.5f));
429 addItemForTradeIn(newOffers, Item::goldIngot_Id, random, getRecipeChance(.5f));
430 addItemForTradeIn(newOffers, Item::diamond_Id, random, getRecipeChance(.5f));
431
432 addItemForPurchase(newOffers, Item::sword_iron_Id, random, getRecipeChance(.5f));
433 addItemForPurchase(newOffers, Item::sword_diamond_Id, random, getRecipeChance(.5f));
434 addItemForPurchase(newOffers, Item::hatchet_iron_Id, random, getRecipeChance(.3f));
435 addItemForPurchase(newOffers, Item::hatchet_diamond_Id, random, getRecipeChance(.3f));
436 addItemForPurchase(newOffers, Item::pickAxe_iron_Id, random, getRecipeChance(.5f));
437 addItemForPurchase(newOffers, Item::pickAxe_diamond_Id, random, getRecipeChance(.5f));
438 addItemForPurchase(newOffers, Item::shovel_iron_Id, random, getRecipeChance(.2f));
439 addItemForPurchase(newOffers, Item::shovel_diamond_Id, random, getRecipeChance(.2f));
440 addItemForPurchase(newOffers, Item::hoe_iron_Id, random, getRecipeChance(.2f));
441 addItemForPurchase(newOffers, Item::hoe_diamond_Id, random, getRecipeChance(.2f));
442 addItemForPurchase(newOffers, Item::boots_iron_Id, random, getRecipeChance(.2f));
443 addItemForPurchase(newOffers, Item::boots_diamond_Id, random, getRecipeChance(.2f));
444 addItemForPurchase(newOffers, Item::helmet_iron_Id, random, getRecipeChance(.2f));
445 addItemForPurchase(newOffers, Item::helmet_diamond_Id, random, getRecipeChance(.2f));
446 addItemForPurchase(newOffers, Item::chestplate_iron_Id, random, getRecipeChance(.2f));
447 addItemForPurchase(newOffers, Item::chestplate_diamond_Id, random, getRecipeChance(.2f));
448 addItemForPurchase(newOffers, Item::leggings_iron_Id, random, getRecipeChance(.2f));
449 addItemForPurchase(newOffers, Item::leggings_diamond_Id, random, getRecipeChance(.2f));
450 addItemForPurchase(newOffers, Item::boots_chain_Id, random, getRecipeChance(.1f));
451 addItemForPurchase(newOffers, Item::helmet_chain_Id, random, getRecipeChance(.1f));
452 addItemForPurchase(newOffers, Item::chestplate_chain_Id, random, getRecipeChance(.1f));
453 addItemForPurchase(newOffers, Item::leggings_chain_Id, random, getRecipeChance(.1f));
454 break;
455 case PROFESSION_LIBRARIAN:
456 addItemForTradeIn(newOffers, Item::paper_Id, random, getRecipeChance(.8f));
457 addItemForTradeIn(newOffers, Item::book_Id, random, getRecipeChance(.8f));
458 //addItemForTradeIn(newOffers, Item::writtenBook_Id, random, getRecipeChance(0.3f));
459 addItemForPurchase(newOffers, Tile::bookshelf_Id, random, getRecipeChance(.8f));
460 addItemForPurchase(newOffers, Tile::glass_Id, random, getRecipeChance(.2f));
461 addItemForPurchase(newOffers, Item::compass_Id, random, getRecipeChance(.2f));
462 addItemForPurchase(newOffers, Item::clock_Id, random, getRecipeChance(.2f));
463
464 if (random->nextFloat() < getRecipeChance(0.07f))
465 {
466 Enchantment *enchantment = Enchantment::validEnchantments[random->nextInt(Enchantment::validEnchantments.size())];
467 int level = Mth::nextInt(random, enchantment->getMinLevel(), enchantment->getMaxLevel());
468 shared_ptr<ItemInstance> book = Item::enchantedBook->createForEnchantment(new EnchantmentInstance(enchantment, level));
469 int cost = 2 + random->nextInt(5 + (level * 10)) + 3 * level;
470
471 newOffers->push_back(new MerchantRecipe(shared_ptr<ItemInstance>(new ItemInstance(Item::book)), shared_ptr<ItemInstance>(new ItemInstance(Item::emerald, cost)), book));
472 }
473 break;
474 case PROFESSION_PRIEST:
475 addItemForPurchase(newOffers, Item::eyeOfEnder_Id, random, getRecipeChance(.3f));
476 addItemForPurchase(newOffers, Item::expBottle_Id, random, getRecipeChance(.2f));
477 addItemForPurchase(newOffers, Item::redStone_Id, random, getRecipeChance(.4f));
478 addItemForPurchase(newOffers, Tile::glowstone_Id, random, getRecipeChance(.3f));
479 {
480 int enchantItems[] = {
481 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,
482 Item::pickAxe_diamond_Id
483 };
484 for (unsigned int i = 0; i < 8; ++i)
485 {
486 int id = enchantItems[i];
487 if (random->nextFloat() < getRecipeChance(.05f))
488 {
489 newOffers->push_back(new MerchantRecipe(shared_ptr<ItemInstance>(new ItemInstance(id, 1, 0)),
490 shared_ptr<ItemInstance>(new ItemInstance(Item::emerald, 2 + random->nextInt(3), 0)),
491 EnchantmentHelper::enchantItem(random, shared_ptr<ItemInstance>(new ItemInstance(id, 1, 0)), 5 + random->nextInt(15))));
492 }
493 }
494 }
495 break;
496 }
497
498 if (newOffers->empty())
499 {
500 addItemForTradeIn(newOffers, Item::goldIngot_Id, random, 1.0f);
501 }
502
503 // shuffle the list to make it more interesting
504 std::random_shuffle(newOffers->begin(), newOffers->end());
505
506 if (offers == NULL)
507 {
508 offers = new MerchantRecipeList();
509 }
510 for (int i = 0; i < addCount && i < newOffers->size(); i++)
511 {
512 if( offers->addIfNewOrBetter(newOffers->at(i)))
513 {
514 // 4J Added so we can delete newOffers
515 newOffers->erase(newOffers->begin() + i);
516 }
517 }
518 delete newOffers;
519}
520
521void Villager::overrideOffers(MerchantRecipeList *recipeList)
522{
523}
524
525
526void Villager::staticCtor()
527{
528 MIN_MAX_VALUES[Item::coal_Id] = pair<int,int>(16, 24);
529 MIN_MAX_VALUES[Item::ironIngot_Id] = pair<int,int>(8, 10);
530 MIN_MAX_VALUES[Item::goldIngot_Id] = pair<int,int>(8, 10);
531 MIN_MAX_VALUES[Item::diamond_Id] = pair<int,int>(4, 6);
532 MIN_MAX_VALUES[Item::paper_Id] = pair<int,int>(24, 36);
533 MIN_MAX_VALUES[Item::book_Id] = pair<int,int>(11, 13);
534 //MIN_MAX_VALUES.insert(Item::writtenBook_Id, pair<int,int>(1, 1));
535 MIN_MAX_VALUES[Item::enderPearl_Id] = pair<int,int>(3, 4);
536 MIN_MAX_VALUES[Item::eyeOfEnder_Id] = pair<int,int>(2, 3);
537 MIN_MAX_VALUES[Item::porkChop_raw_Id] = pair<int,int>(14, 18);
538 MIN_MAX_VALUES[Item::beef_raw_Id] = pair<int,int>(14, 18);
539 MIN_MAX_VALUES[Item::chicken_raw_Id] = pair<int,int>(14, 18);
540 MIN_MAX_VALUES[Item::fish_cooked_Id] = pair<int,int>(9, 13);
541 MIN_MAX_VALUES[Item::seeds_wheat_Id] = pair<int,int>(34, 48);
542 MIN_MAX_VALUES[Item::seeds_melon_Id] = pair<int,int>(30, 38);
543 MIN_MAX_VALUES[Item::seeds_pumpkin_Id] = pair<int,int>(30, 38);
544 MIN_MAX_VALUES[Item::wheat_Id] = pair<int,int>(18, 22);
545 MIN_MAX_VALUES[Tile::wool_Id] = pair<int,int>(14, 22);
546 MIN_MAX_VALUES[Item::rotten_flesh_Id] = pair<int,int>(36, 64);
547
548 MIN_MAX_PRICES[Item::flintAndSteel_Id] = pair<int,int>(3, 4);
549 MIN_MAX_PRICES[Item::shears_Id] = pair<int,int>(3, 4);
550 MIN_MAX_PRICES[Item::sword_iron_Id] = pair<int,int>(7, 11);
551 MIN_MAX_PRICES[Item::sword_diamond_Id] = pair<int,int>(12, 14);
552 MIN_MAX_PRICES[Item::hatchet_iron_Id] = pair<int,int>(6, 8);
553 MIN_MAX_PRICES[Item::hatchet_diamond_Id] = pair<int,int>(9, 12);
554 MIN_MAX_PRICES[Item::pickAxe_iron_Id] = pair<int,int>(7, 9);
555 MIN_MAX_PRICES[Item::pickAxe_diamond_Id] = pair<int,int>(10, 12);
556 MIN_MAX_PRICES[Item::shovel_iron_Id] = pair<int,int>(4, 6);
557 MIN_MAX_PRICES[Item::shovel_diamond_Id] = pair<int,int>(7, 8);
558 MIN_MAX_PRICES[Item::hoe_iron_Id] = pair<int,int>(4, 6);
559 MIN_MAX_PRICES[Item::hoe_diamond_Id] = pair<int,int>(7, 8);
560 MIN_MAX_PRICES[Item::boots_iron_Id] = pair<int,int>(4, 6);
561 MIN_MAX_PRICES[Item::boots_diamond_Id] = pair<int,int>(7, 8);
562 MIN_MAX_PRICES[Item::helmet_iron_Id] = pair<int,int>(4, 6);
563 MIN_MAX_PRICES[Item::helmet_diamond_Id] = pair<int,int>(7, 8);
564 MIN_MAX_PRICES[Item::chestplate_iron_Id] = pair<int,int>(10, 14);
565 MIN_MAX_PRICES[Item::chestplate_diamond_Id] = pair<int,int>(16, 19);
566 MIN_MAX_PRICES[Item::leggings_iron_Id] = pair<int,int>(8, 10);
567 MIN_MAX_PRICES[Item::leggings_diamond_Id] = pair<int,int>(11, 14);
568 MIN_MAX_PRICES[Item::boots_chain_Id] = pair<int,int>(5, 7);
569 MIN_MAX_PRICES[Item::helmet_chain_Id] = pair<int,int>(5, 7);
570 MIN_MAX_PRICES[Item::chestplate_chain_Id] = pair<int,int>(11, 15);
571 MIN_MAX_PRICES[Item::leggings_chain_Id] = pair<int,int>(9, 11);
572 MIN_MAX_PRICES[Item::bread_Id] = pair<int,int>(-4, -2);
573 MIN_MAX_PRICES[Item::melon_Id] = pair<int,int>(-8, -4);
574 MIN_MAX_PRICES[Item::apple_Id] = pair<int,int>(-8, -4);
575 MIN_MAX_PRICES[Item::cookie_Id] = pair<int,int>(-10, -7);
576 MIN_MAX_PRICES[Tile::glass_Id] = pair<int,int>(-5, -3);
577 MIN_MAX_PRICES[Tile::bookshelf_Id] = pair<int,int>(3, 4);
578 MIN_MAX_PRICES[Item::chestplate_leather_Id] = pair<int,int>(4, 5);
579 MIN_MAX_PRICES[Item::boots_leather_Id] = pair<int,int>(2, 4);
580 MIN_MAX_PRICES[Item::helmet_leather_Id] = pair<int,int>(2, 4);
581 MIN_MAX_PRICES[Item::leggings_leather_Id] = pair<int,int>(2, 4);
582 MIN_MAX_PRICES[Item::saddle_Id] = pair<int,int>(6, 8);
583 MIN_MAX_PRICES[Item::expBottle_Id] = pair<int,int>(-4, -1);
584 MIN_MAX_PRICES[Item::redStone_Id] = pair<int,int>(-4, -1);
585 MIN_MAX_PRICES[Item::compass_Id] = pair<int,int>(10, 12);
586 MIN_MAX_PRICES[Item::clock_Id] = pair<int,int>(10, 12);
587 MIN_MAX_PRICES[Tile::glowstone_Id] = pair<int,int>(-3, -1);
588 MIN_MAX_PRICES[Item::porkChop_cooked_Id] = pair<int,int>(-7, -5);
589 MIN_MAX_PRICES[Item::beef_cooked_Id] = pair<int,int>(-7, -5);
590 MIN_MAX_PRICES[Item::chicken_cooked_Id] = pair<int,int>(-8, -6);
591 MIN_MAX_PRICES[Item::eyeOfEnder_Id] = pair<int,int>(7, 11);
592 MIN_MAX_PRICES[Item::arrow_Id] = pair<int,int>(-12, -8);
593}
594
595/**
596* Adds a merchant recipe that trades items for a single ruby.
597*
598* @param list
599* @param itemId
600* @param random
601* @param likelyHood
602*/
603void Villager::addItemForTradeIn(MerchantRecipeList *list, int itemId, Random *random, float likelyHood)
604{
605 if (random->nextFloat() < likelyHood)
606 {
607 list->push_back(new MerchantRecipe(getItemTradeInValue(itemId, random), Item::emerald));
608 }
609}
610
611shared_ptr<ItemInstance> Villager::getItemTradeInValue(int itemId, Random *random)
612{
613 return shared_ptr<ItemInstance>(new ItemInstance(itemId, getTradeInValue(itemId, random), 0));
614}
615
616int Villager::getTradeInValue(int itemId, Random *random)
617{
618 AUTO_VAR(it,MIN_MAX_VALUES.find(itemId));
619 if (it == MIN_MAX_VALUES.end())
620 {
621 return 1;
622 }
623 pair<int, int> minMax = it->second;
624 if (minMax.first >= minMax.second)
625 {
626 return minMax.first;
627 }
628 return minMax.first + random->nextInt(minMax.second - minMax.first);
629}
630
631/**
632* Adds a merchant recipe that trades rubies for an item. If the cost is
633* negative, one ruby will give several of that item.
634*
635* @param list
636* @param itemId
637* @param random
638* @param likelyHood
639*/
640void Villager::addItemForPurchase(MerchantRecipeList *list, int itemId, Random *random, float likelyHood)
641{
642 if (random->nextFloat() < likelyHood)
643 {
644 int purchaseCost = getPurchaseCost(itemId, random);
645 shared_ptr<ItemInstance> rubyItem;
646 shared_ptr<ItemInstance> resultItem;
647 if (purchaseCost < 0)
648 {
649 rubyItem = shared_ptr<ItemInstance>( new ItemInstance(Item::emerald_Id, 1, 0) );
650 resultItem = shared_ptr<ItemInstance>( new ItemInstance(itemId, -purchaseCost, 0) );
651 }
652 else
653 {
654 rubyItem = shared_ptr<ItemInstance>( new ItemInstance(Item::emerald_Id, purchaseCost, 0) );
655 resultItem = shared_ptr<ItemInstance>( new ItemInstance(itemId, 1, 0) );
656 }
657 list->push_back(new MerchantRecipe(rubyItem, resultItem));
658 }
659}
660
661int Villager::getPurchaseCost(int itemId, Random *random)
662{
663 AUTO_VAR(it,MIN_MAX_PRICES.find(itemId));
664 if (it == MIN_MAX_PRICES.end())
665 {
666 return 1;
667 }
668 pair<int, int> minMax = it->second;
669 if (minMax.first >= minMax.second)
670 {
671 return minMax.first;
672 }
673 return minMax.first + random->nextInt(minMax.second - minMax.first);
674}
675
676void Villager::handleEntityEvent(byte id)
677{
678 if (id == EntityEvent::LOVE_HEARTS)
679 {
680 addParticlesAroundSelf(eParticleType_heart);
681 }
682 else if (id == EntityEvent::VILLAGER_ANGRY)
683 {
684 addParticlesAroundSelf(eParticleType_angryVillager);
685 }
686 else if (id == EntityEvent::VILLAGER_HAPPY)
687 {
688 addParticlesAroundSelf(eParticleType_happyVillager);
689 }
690 else
691 {
692 AgableMob::handleEntityEvent(id);
693 }
694}
695
696void Villager::addParticlesAroundSelf(ePARTICLE_TYPE particle)
697{
698 for (int i = 0; i < 5; i++)
699 {
700 double xa = random->nextGaussian() * 0.02;
701 double ya = random->nextGaussian() * 0.02;
702 double za = random->nextGaussian() * 0.02;
703 level->addParticle(particle, x + random->nextFloat() * bbWidth * 2 - bbWidth, y + 1.0f + random->nextFloat() * bbHeight, z + random->nextFloat() * bbWidth * 2 - bbWidth, xa, ya, za);
704 }
705}
706
707MobGroupData *Villager::finalizeMobSpawn(MobGroupData *groupData, int extraData /*= 0*/) // 4J Added extraData param
708{
709 groupData = AgableMob::finalizeMobSpawn(groupData);
710
711 setProfession(level->random->nextInt(PROFESSION_MAX));
712
713 return groupData;
714}
715
716void Villager::setRewardPlayersInVillage()
717{
718 rewardPlayersOnFirstVillage = true;
719}
720
721shared_ptr<AgableMob> Villager::getBreedOffspring(shared_ptr<AgableMob> target)
722{
723 // 4J - added limit to villagers that can be bred
724 if(level->canCreateMore(GetType(), Level::eSpawnType_Breed) )
725 {
726 shared_ptr<Villager> villager = shared_ptr<Villager>(new Villager(level));
727 villager->finalizeMobSpawn(NULL);
728 return villager;
729 }
730 else
731 {
732 return nullptr;
733 }
734}
735
736bool Villager::canBeLeashed()
737{
738 return false;
739}
740
741wstring Villager::getDisplayName()
742{
743 if (hasCustomName()) return getCustomName();
744
745 int name = IDS_VILLAGER;
746 switch(getProfession())
747 {
748 case PROFESSION_FARMER:
749 name = IDS_VILLAGER_FARMER;
750 break;
751 case PROFESSION_LIBRARIAN:
752 name = IDS_VILLAGER_LIBRARIAN;
753 break;
754 case PROFESSION_PRIEST:
755 name = IDS_VILLAGER_PRIEST;
756 break;
757 case PROFESSION_SMITH:
758 name = IDS_VILLAGER_SMITH;
759 break;
760 case PROFESSION_BUTCHER:
761 name = IDS_VILLAGER_BUTCHER;
762 break;
763 };
764 return app.GetString(name);
765}