1
0

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 22:49:25 +02:00 committed by GitHub
parent 6fd35be67a
commit 32ee1708a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 352 additions and 233 deletions

View File

@ -12,6 +12,21 @@ typedef std::vector<int> cSlotNums;
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-variable-declarations"
#endif
/** Constant to calculate ticks from seconds "ticks per second" */
constexpr inline const int TPS = 20;
// This is not added to the lua API because it broke the build
#ifdef __clang__
#pragma clang diagnostic pop
#endif
// tolua_begin // tolua_begin
/** Experience Orb setup */ /** Experience Orb setup */
@ -384,7 +399,6 @@ enum eMessageType
/** Returns a textual representation of the click action. */ /** Returns a textual representation of the click action. */
const char * ClickActionToString(int a_ClickAction); const char * ClickActionToString(int a_ClickAction);

View File

@ -14,6 +14,8 @@
#include "../MonsterConfig.h" #include "../MonsterConfig.h"
#include "../BoundingBox.h" #include "../BoundingBox.h"
#include "Items/ItemSpawnEgg.h"
#include "../Chunk.h" #include "../Chunk.h"
#include "../FastRandom.h" #include "../FastRandom.h"
@ -22,8 +24,6 @@
/** Map for eType <-> string /** Map for eType <-> string
Needs to be alpha-sorted by the strings, because binary search is used in StringToMobType() 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()) 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_BurnsInDaylight(false)
, m_RelativeWalkSpeed(1) , m_RelativeWalkSpeed(1)
, m_Age(1) , m_Age(1)
, m_AgingTimer(20 * 60 * 20) // about 20 minutes , m_AgingTimer(TPS * 60 * 20) // about 20 minutes
, m_WasLastTargetAPlayer(false) , m_WasLastTargetAPlayer(false)
, m_LeashedTo(nullptr) , m_LeashedTo(nullptr)
, m_LeashToPos(nullptr) , m_LeashToPos(nullptr)
, m_IsLeashActionJustDone(false) , m_IsLeashActionJustDone(false)
, m_CanBeLeashed(GetMobFamily() == eFamily::mfPassive) , m_CanBeLeashed(GetMobFamily() == eFamily::mfPassive)
, m_LovePartner(nullptr)
, m_LoveTimer(0)
, m_LoveCooldown(0)
, m_MatingTimer(0)
, m_Target(nullptr) , m_Target(nullptr)
{ {
if (!a_ConfigName.empty()) if (!a_ConfigName.empty())
@ -163,6 +167,10 @@ void cMonster::OnRemoveFromWorld(cWorld & a_World)
void cMonster::Destroyed() void cMonster::Destroyed()
{ {
SetTarget(nullptr); // Tell them we're no longer targeting them. SetTarget(nullptr); // Tell them we're no longer targeting them.
if (m_LovePartner != nullptr)
{
m_LovePartner->ResetLoveMode();
}
Super::Destroyed(); Super::Destroyed();
} }
@ -896,7 +904,7 @@ void cMonster::InStateEscaping(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
void cMonster::ResetAttackCooldown() 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) 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); auto Count = GetRandomProvider().RandInt<unsigned int>(a_Min, a_Max);

View File

@ -2,6 +2,7 @@
#pragma once #pragma once
#include "../Entities/Pawn.h" #include "../Entities/Pawn.h"
#include "../UUID.h"
#include "MonsterTypes.h" #include "MonsterTypes.h"
#include "PathFinder.h" #include "PathFinder.h"
@ -217,6 +218,38 @@ public:
/** Returns if this mob last target was a player to avoid destruction on player quit */ /** Returns if this mob last target was a player to avoid destruction on player quit */
bool WasLastTargetAPlayer() const { return m_WasLastTargetAPlayer; } bool WasLastTargetAPlayer() const { return m_WasLastTargetAPlayer; }
/* the breeding processing */
/** Returns the items that the animal of this class follows when a player holds it in hand. */
virtual void GetFollowedItems(cItems & a_Items) { }
/** Returns the items that make the animal breed - this is usually the same as the ones that make the animal follow, but not necessarily. */
virtual void GetBreedingItems(cItems & a_Items) { GetFollowedItems(a_Items); }
/** Called after the baby is born, allows the baby to inherit the parents' properties (color, etc.) */
virtual void InheritFromParents(cMonster * a_Parent1, cMonster * a_Parent2) { }
/** Returns the partner which the monster is currently mating with. */
cMonster * GetPartner(void) const { return m_LovePartner; }
/** Start the mating process. Causes the monster to keep bumping into the partner until m_MatingTimer reaches zero. */
void EngageLoveMode(cMonster * a_Partner);
/** Finish the mating process. Called after a baby is born. Resets all breeding related timers and sets m_LoveCooldown to 20 minutes. */
void ResetLoveMode();
/** Returns whether the monster has just been fed and is ready to mate. If this is "true" and GetPartner isn't "nullptr", then the monster is mating. */
bool IsInLove() const { return (m_LoveTimer > 0); }
/** Returns whether the monster is tired of breeding and is in the cooldown state. */
bool IsInLoveCooldown() const { return (m_LoveCooldown > 0); }
/** Does the whole love and breeding processing */
void LoveTick(void);
/** Right click call to process feeding */
void RightClickFeed(cPlayer & a_Player);
protected: protected:
/** The pathfinder instance handles pathfinding for this monster. */ /** The pathfinder instance handles pathfinding for this monster. */
@ -330,6 +363,23 @@ protected:
virtual void DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) override; virtual void DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) override;
/* The breeding processing */
/** The monster's breeding partner. */
cMonster * m_LovePartner;
/** Remembers the player is was last fed by for statistics tracking */
cUUID m_Feeder;
/** If above 0, the monster is in love mode, and will breed if a nearby monster is also in love mode. Decrements by 1 per tick till reaching zero. */
int m_LoveTimer;
/** If above 0, the monster is in cooldown mode and will refuse to breed. Decrements by 1 per tick till reaching zero. */
int m_LoveCooldown;
/** The monster is engaged in mating, once this reaches zero, a baby will be born. Decrements by 1 per tick till reaching zero, then a baby is made and ResetLoveMode() is called. */
int m_MatingTimer;
private: private:
/** A pointer to the entity this mobile is aiming to reach. /** A pointer to the entity this mobile is aiming to reach.
The validity of this pointer SHALL be guaranteed by the pointee; The validity of this pointer SHALL be guaranteed by the pointee;

View File

@ -11,11 +11,7 @@
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) : 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), 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; m_EMPersonality = PASSIVE;
} }
@ -41,38 +37,8 @@ bool cPassiveMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
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() void cPassiveMonster::Destroyed()
{ {
if (m_LovePartner != nullptr)
{
m_LovePartner->ResetLoveMode();
}
Super::Destroyed(); Super::Destroyed();
} }
@ -94,120 +60,7 @@ void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
CheckEventLostPlayer(); CheckEventLostPlayer();
} }
// if we have a partner, mate cMonster::LoveTick();
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<cPassiveMonster *>(&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<cPassiveMonster&>(*this);
auto & PotentialPartner = static_cast<cPassiveMonster&>(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--;
}
} }
@ -217,42 +70,7 @@ void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
void cPassiveMonster::OnRightClicked(cPlayer & a_Player) void cPassiveMonster::OnRightClicked(cPlayer & a_Player)
{ {
Super::OnRightClicked(a_Player); Super::OnRightClicked(a_Player);
Super::RightClickFeed(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();
} }

View File

@ -31,47 +31,7 @@ public:
/** When hit by someone, run away */ /** When hit by someone, run away */
virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override; virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override;
/** Returns the items that the animal of this class follows when a player holds it in hand. */
virtual void GetFollowedItems(cItems & a_Items) { }
/** Returns the items that make the animal breed - this is usually the same as the ones that make the animal follow, but not necessarily. */
virtual void GetBreedingItems(cItems & a_Items) { GetFollowedItems(a_Items); }
/** Called after the baby is born, allows the baby to inherit the parents' properties (color, etc.) */
virtual void InheritFromParents(cPassiveMonster * a_Parent1, cPassiveMonster * a_Parent2) { }
/** Returns the partner which the monster is currently mating with. */
cPassiveMonster * GetPartner(void) const { return m_LovePartner; }
/** Start the mating process. Causes the monster to keep bumping into the partner until m_MatingTimer reaches zero. */
void EngageLoveMode(cPassiveMonster * a_Partner);
/** Finish the mating process. Called after a baby is born. Resets all breeding related timers and sets m_LoveCooldown to 20 minutes. */
void ResetLoveMode();
/** Returns whether the monster has just been fed and is ready to mate. If this is "true" and GetPartner isn't "nullptr", then the monster is mating. */
bool IsInLove() const { return (m_LoveTimer > 0); }
/** Returns whether the monster is tired of breeding and is in the cooldown state. */
bool IsInLoveCooldown() const { return (m_LoveCooldown > 0); }
virtual void Destroyed(void) override; virtual void Destroyed(void) override;
protected:
/** The monster's breeding partner. */
cPassiveMonster * m_LovePartner;
/** Remembers the player is was last fed by for statistics tracking */
cUUID m_Feeder;
/** If above 0, the monster is in love mode, and will breed if a nearby monster is also in love mode. Decrements by 1 per tick till reaching zero. */
int m_LoveTimer;
/** If above 0, the monster is in cooldown mode and will refuse to breed. Decrements by 1 per tick till reaching zero. */
int m_LoveCooldown;
/** The monster is engaged in mating, once this reaches zero, a baby will be born. Decrements by 1 per tick till reaching zero, then a baby is made and ResetLoveMode() is called. */
int m_MatingTimer;
}; };

View File

@ -140,7 +140,7 @@ void cSheep::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
void cSheep::InheritFromParents(cPassiveMonster * a_Parent1, cPassiveMonster * a_Parent2) void cSheep::InheritFromParents(cMonster * a_Parent1, cMonster * a_Parent2)
{ {
static const struct static const struct
{ {

View File

@ -25,7 +25,7 @@ public:
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override;
virtual void OnRightClicked(cPlayer & a_Player) override; virtual void OnRightClicked(cPlayer & a_Player) override;
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
virtual void InheritFromParents(cPassiveMonster * a_Parent1, cPassiveMonster * a_Parent2) override; virtual void InheritFromParents(cMonster * a_Parent1, cMonster * a_Parent2) override;
virtual void GetFollowedItems(cItems & a_Items) override virtual void GetFollowedItems(cItems & a_Items) override
{ {

View File

@ -5,6 +5,7 @@
#include "../World.h" #include "../World.h"
#include "../Entities/Player.h" #include "../Entities/Player.h"
#include "../Items/ItemHandler.h" #include "../Items/ItemHandler.h"
#include "../Items/ItemSpawnEgg.h"
@ -16,7 +17,6 @@ cWolf::cWolf(void) :
m_IsTame(false), m_IsTame(false),
m_IsBegging(false), m_IsBegging(false),
m_IsAngry(false), m_IsAngry(false),
m_OwnerName(""),
m_CollarColor(E_META_DYE_ORANGE), m_CollarColor(E_META_DYE_ORANGE),
m_NotificationCooldown(0) m_NotificationCooldown(0)
{ {
@ -198,6 +198,10 @@ void cWolf::OnRightClicked(cPlayer & a_Player)
} }
else if (IsTame()) else if (IsTame())
{ {
if (a_Player.GetUUID() == m_OwnerUUID)
{
cMonster::RightClickFeed(a_Player);
}
// Feed the wolf, restoring its health, or dye its collar: // Feed the wolf, restoring its health, or dye its collar:
switch (EquippedItemType) switch (EquippedItemType)
{ {
@ -208,6 +212,10 @@ void cWolf::OnRightClicked(cPlayer & a_Player)
case E_ITEM_RAW_CHICKEN: case E_ITEM_RAW_CHICKEN:
case E_ITEM_COOKED_CHICKEN: case E_ITEM_COOKED_CHICKEN:
case E_ITEM_ROTTEN_FLESH: case E_ITEM_ROTTEN_FLESH:
case E_ITEM_RAW_MUTTON:
case E_ITEM_RAW_RABBIT:
case E_ITEM_COOKED_RABBIT:
case E_ITEM_COOKED_MUTTON:
{ {
if (m_Health < m_MaxHealth) if (m_Health < m_MaxHealth)
{ {
@ -217,6 +225,13 @@ void cWolf::OnRightClicked(cPlayer & a_Player)
a_Player.GetInventory().RemoveOneEquippedItem(); a_Player.GetInventory().RemoveOneEquippedItem();
} }
} }
else if (a_Player.GetUUID() == m_OwnerUUID) // Is the player the owner of the dog?
{
if (IsBaby())
{
m_AgingTimer = FloorC(m_AgingTimer * 0.9);
}
}
break; break;
} }
case E_ITEM_DYE: case E_ITEM_DYE:
@ -231,6 +246,11 @@ void cWolf::OnRightClicked(cPlayer & a_Player)
} }
break; break;
} }
// Multiplication is handled in cMonster. Just prevents from sitting down.
case E_ITEM_SPAWN_EGG:
{
break;
}
default: default:
{ {
if (a_Player.GetUUID() == m_OwnerUUID) // Is the player the owner of the dog? if (a_Player.GetUUID() == m_OwnerUUID) // Is the player the owner of the dog?
@ -241,6 +261,21 @@ void cWolf::OnRightClicked(cPlayer & a_Player)
} }
} }
if ((EquippedItemType == E_ITEM_SPAWN_EGG) && (!IsTame()))
{
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();
}
}
}
m_World->BroadcastEntityMetadata(*this); m_World->BroadcastEntityMetadata(*this);
} }
@ -337,6 +372,8 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
StopMovingToPosition(); StopMovingToPosition();
} }
cMonster::LoveTick();
} }
@ -400,3 +437,29 @@ void cWolf::InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
} }
void cWolf::InheritFromParents(cMonster * a_Parent1, cMonster * a_Parent2)
{
const auto Parent1 = static_cast<cWolf *>(a_Parent1);
const auto Parent2 = static_cast<cWolf *>(a_Parent2);
if (Parent1->GetOwnerUUID() == Parent2->GetOwnerUUID())
{
SetOwner(Parent1->GetOwnerName(), Parent2->GetOwnerUUID());
}
else
{
auto Parent1Age = Parent1->GetAge();
auto Parent2Age = Parent2->GetAge();
if (Parent1Age > Parent2Age)
{
SetOwner(Parent2->GetOwnerName(), Parent2->GetOwnerUUID());
}
else
{
SetOwner(Parent1->GetOwnerName(), Parent1->GetOwnerUUID());
}
}
}

View File

@ -59,6 +59,23 @@ public:
virtual void InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; virtual void InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
virtual void InheritFromParents(cMonster * a_Parent1, cMonster * a_Parent2) override;
virtual void GetBreedingItems(cItems & a_Items) override
{
a_Items.Add(E_ITEM_RAW_BEEF);
a_Items.Add(E_ITEM_STEAK);
a_Items.Add(E_ITEM_RAW_PORKCHOP);
a_Items.Add(E_ITEM_COOKED_PORKCHOP);
a_Items.Add(E_ITEM_RAW_CHICKEN);
a_Items.Add(E_ITEM_COOKED_CHICKEN);
a_Items.Add(E_ITEM_RAW_MUTTON);
a_Items.Add(E_ITEM_COOKED_MUTTON);
a_Items.Add(E_ITEM_RAW_RABBIT);
a_Items.Add(E_ITEM_COOKED_RABBIT);
a_Items.Add(E_ITEM_ROTTEN_FLESH);
}
protected: protected:
bool m_IsSitting; bool m_IsSitting;