#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "PassiveMonster.h" #include "../World.h" #include "../Entities/Player.h" #include "../BoundingBox.h" #include "../Items/ItemSpawnEgg.h" cPassiveMonster::cPassiveMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, const AString & a_SoundAmbient, double a_Width, double a_Height) : Super(a_ConfigName, a_MobType, a_SoundHurt, a_SoundDeath, a_SoundAmbient, a_Width, a_Height), m_LovePartner(nullptr), m_LoveTimer(0), m_LoveCooldown(0), m_MatingTimer(0) { m_EMPersonality = PASSIVE; } bool cPassiveMonster::DoTakeDamage(TakeDamageInfo & a_TDI) { if (!Super::DoTakeDamage(a_TDI)) { return false; } if ((a_TDI.Attacker != this) && (a_TDI.Attacker != nullptr)) { m_EMState = ESCAPING; } return true; } void cPassiveMonster::EngageLoveMode(cPassiveMonster * a_Partner) { m_LovePartner = a_Partner; m_MatingTimer = 50; // about 3 seconds of mating } void cPassiveMonster::ResetLoveMode() { m_LovePartner = nullptr; m_LoveTimer = 0; m_MatingTimer = 0; m_LoveCooldown = 20 * 60 * 5; // 5 minutes m_Feeder = cUUID(); // when an animal is in love mode, the client only stops sending the hearts if we let them know it's in cooldown, which is done with the "age" metadata m_World->BroadcastEntityMetadata(*this); } void cPassiveMonster::Destroyed() { if (m_LovePartner != nullptr) { m_LovePartner->ResetLoveMode(); } Super::Destroyed(); } void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { Super::Tick(a_Dt, a_Chunk); if (!IsTicking()) { // The base class tick destroyed us return; } if (m_EMState == ESCAPING) { CheckEventLostPlayer(); } // if we have a partner, mate if (m_LovePartner != nullptr) { if (m_MatingTimer > 0) { // If we should still mate, keep bumping into them until baby is made Vector3d Pos = m_LovePartner->GetPosition(); MoveToPosition(Pos); } else { // Mating finished. Spawn baby Vector3f Pos = (GetPosition() + m_LovePartner->GetPosition()) * 0.5; UInt32 BabyID = m_World->SpawnMob(Pos.x, Pos.y, Pos.z, GetMobType(), true); cPassiveMonster * Baby = nullptr; m_World->DoWithEntityByID(BabyID, [&](cEntity & a_Entity) { Baby = static_cast(&a_Entity); return true; } ); if (Baby != nullptr) { Baby->InheritFromParents(this, m_LovePartner); } m_World->SpawnExperienceOrb(Pos.x, Pos.y, Pos.z, GetRandomProvider().RandInt(1, 6)); m_World->DoWithPlayerByUUID(m_Feeder, [&] (cPlayer & a_Player) { a_Player.GetStatManager().AddValue(Statistic::AnimalsBred); if (GetMobType() == eMonsterType::mtCow) { a_Player.AwardAchievement(Statistic::AchBreedCow); } return true; }); m_LovePartner->ResetLoveMode(); ResetLoveMode(); } } else { // We have no partner, so we just chase the player if they have our breeding item cItems FollowedItems; GetFollowedItems(FollowedItems); if (FollowedItems.Size() > 0) { m_World->DoWithNearestPlayer(GetPosition(), static_cast(m_SightDistance), [&](cPlayer & a_Player) -> bool { const cItem & EquippedItem = a_Player.GetEquippedItem(); if (FollowedItems.ContainsType(EquippedItem)) { Vector3d PlayerPos = a_Player.GetPosition(); MoveToPosition(PlayerPos); } return true; }); } } // If we are in love mode but we have no partner, search for a partner neabry if (m_LoveTimer > 0) { if (m_LovePartner == nullptr) { m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), 8, 8), [=](cEntity & a_Entity) { // If the entity is not a monster, don't breed with it // Also, do not self-breed if ((a_Entity.GetEntityType() != etMonster) || (&a_Entity == this)) { return false; } auto & Me = static_cast(*this); auto & PotentialPartner = static_cast(a_Entity); // If the potential partner is not of the same species, don't breed with it if (PotentialPartner.GetMobType() != Me.GetMobType()) { return false; } // If the potential partner is not in love // Or they already have a mate, do not breed with them if ((!PotentialPartner.IsInLove()) || (PotentialPartner.GetPartner() != nullptr)) { return false; } // All conditions met, let's breed! PotentialPartner.EngageLoveMode(&Me); Me.EngageLoveMode(&PotentialPartner); return true; } ); } m_LoveTimer--; } if (m_MatingTimer > 0) { m_MatingTimer--; } if (m_LoveCooldown > 0) { m_LoveCooldown--; } } void cPassiveMonster::OnRightClicked(cPlayer & a_Player) { Super::OnRightClicked(a_Player); const cItem & EquippedItem = a_Player.GetEquippedItem(); // If a player holding breeding items right-clicked me, go into love mode if ((m_LoveCooldown == 0) && !IsInLove() && !IsBaby()) { cItems Items; GetBreedingItems(Items); if (Items.ContainsType(EquippedItem.m_ItemType)) { if (!a_Player.IsGameModeCreative()) { a_Player.GetInventory().RemoveOneEquippedItem(); } m_LoveTimer = 20 * 30; // half a minute m_World->BroadcastEntityStatus(*this, esMobInLove); } } // If a player holding my spawn egg right-clicked me, spawn a new baby if (EquippedItem.m_ItemType == E_ITEM_SPAWN_EGG) { eMonsterType MonsterType = cItemSpawnEggHandler::ItemDamageToMonsterType(EquippedItem.m_ItemDamage); if ( (MonsterType == m_MobType) && (m_World->SpawnMob(GetPosX(), GetPosY(), GetPosZ(), m_MobType, true) != cEntity::INVALID_ID) // Spawning succeeded ) { if (!a_Player.IsGameModeCreative()) { // The mob was spawned, "use" the item: a_Player.GetInventory().RemoveOneEquippedItem(); } } } // Stores feeder UUID for statistic tracking m_Feeder = a_Player.GetUUID(); }