diff options
Diffstat (limited to 'src/Mobs/Monster.cpp')
-rw-r--r-- | src/Mobs/Monster.cpp | 205 |
1 files changed, 201 insertions, 4 deletions
diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index ec240b61c..740223bfa 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -14,6 +14,8 @@ #include "../MonsterConfig.h" #include "../BoundingBox.h" +#include "Items/ItemSpawnEgg.h" + #include "../Chunk.h" #include "../FastRandom.h" @@ -22,8 +24,6 @@ - - /** Map for eType <-> string Needs to be alpha-sorted by the strings, because binary search is used in StringToMobType() The strings need to be lowercase (for more efficient comparisons in StringToMobType()) @@ -109,12 +109,16 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_BurnsInDaylight(false) , m_RelativeWalkSpeed(1) , m_Age(1) - , m_AgingTimer(20 * 60 * 20) // about 20 minutes + , m_AgingTimer(TPS * 60 * 20) // about 20 minutes , m_WasLastTargetAPlayer(false) , m_LeashedTo(nullptr) , m_LeashToPos(nullptr) , m_IsLeashActionJustDone(false) , m_CanBeLeashed(GetMobFamily() == eFamily::mfPassive) + , m_LovePartner(nullptr) + , m_LoveTimer(0) + , m_LoveCooldown(0) + , m_MatingTimer(0) , m_Target(nullptr) { if (!a_ConfigName.empty()) @@ -163,6 +167,10 @@ void cMonster::OnRemoveFromWorld(cWorld & a_World) void cMonster::Destroyed() { SetTarget(nullptr); // Tell them we're no longer targeting them. + if (m_LovePartner != nullptr) + { + m_LovePartner->ResetLoveMode(); + } Super::Destroyed(); } @@ -896,7 +904,7 @@ void cMonster::InStateEscaping(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cMonster::ResetAttackCooldown() { - m_AttackCoolDownTicksLeft = static_cast<int>(20 * m_AttackRate); // A second has 20 ticks, an attack rate of 1 means 1 hit every second + m_AttackCoolDownTicksLeft = static_cast<int>(TPS * m_AttackRate); // A second has 20 ticks, an attack rate of 1 means 1 hit every second } @@ -1248,6 +1256,195 @@ std::unique_ptr<cMonster> cMonster::NewMonsterFromType(eMonsterType a_MobType) +void cMonster::EngageLoveMode(cMonster *a_Partner) +{ + m_LovePartner = a_Partner; + m_MatingTimer = 50; // about 3 seconds of mating +} + + + + + +void cMonster::ResetLoveMode() +{ + m_LovePartner = nullptr; + m_LoveTimer = 0; + m_MatingTimer = 0; + m_LoveCooldown = TPS * 60 * 5; // 5 minutes + + // 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 cMonster::LoveTick(void) +{ + // 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); + + cMonster * Baby = nullptr; + + m_World->DoWithEntityByID(BabyID, [&](cEntity & a_Entity) + { + Baby = static_cast<cMonster *>(&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<float>(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<cMonster &>(*this); + auto & PotentialPartner = static_cast<cMonster &>(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 cMonster::RightClickFeed(cPlayer & 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 = TPS * 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(); +} + + + + + void cMonster::AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned int a_Max, short a_Item, short a_ItemHealth) { auto Count = GetRandomProvider().RandInt<unsigned int>(a_Min, a_Max); |