summaryrefslogtreecommitdiffstats
path: root/src/Mobs/Monster.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Mobs/Monster.cpp205
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);