Adding wolf breading and moving breeding functionality to cMonster (#4951)

* added wolf breading

* mpoved breeding to monster

* checkstyle

* fixed my IDE "helping"

* removed magic number
and fixed faster aging

* added flooring to age manipulation

* fixed copiler error

* fixed typo

* moved tps to Defines.h

* removed the TPS constant from the lua API exposure

* added inline constexpr
added explanation

* fixed broken build

* "fixed" build

Co-authored-by: 12xx12 <12xx12100@gmail.com>
This commit is contained in:
12xx12
2020-10-09 20:49:25 +00:00
committed by GitHub
co-authored by 12xx12
parent 6fd35be67a
commit 32ee1708a2
9 changed files with 352 additions and 233 deletions
+201 -4
View File
@@ -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);