diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 63dfabb53..c4f66893d 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -126,6 +126,8 @@ public: // Informs client to explode a firework based on its metadata esFireworkExploding = 17, + // Passive mob is in "love mode" + esMobInLove = 18, } ; static const int FIRE_TICKS_PER_DAMAGE = 10; ///< Ticks to wait between damaging an entity when it stands in fire diff --git a/src/Mobs/Cow.cpp b/src/Mobs/Cow.cpp index a45010201..dec8eface 100644 --- a/src/Mobs/Cow.cpp +++ b/src/Mobs/Cow.cpp @@ -36,7 +36,10 @@ void cCow::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cCow::OnRightClicked(cPlayer & a_Player) { - if ((a_Player.GetEquippedItem().m_ItemType == E_ITEM_BUCKET)) + super::OnRightClicked(a_Player); + + short HeldItem = a_Player.GetEquippedItem().m_ItemType; + if (HeldItem == E_ITEM_BUCKET) { if (!a_Player.IsGameModeCreative()) { @@ -45,4 +48,3 @@ void cCow::OnRightClicked(cPlayer & a_Player) } } } - diff --git a/src/Mobs/Horse.cpp b/src/Mobs/Horse.cpp index 8b76d7c50..a338f12bd 100644 --- a/src/Mobs/Horse.cpp +++ b/src/Mobs/Horse.cpp @@ -93,6 +93,8 @@ void cHorse::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cHorse::OnRightClicked(cPlayer & a_Player) { + super::OnRightClicked(a_Player); + if (!m_bIsSaddled && m_bIsTame) { if (a_Player.GetEquippedItem().m_ItemType == E_ITEM_SADDLE) diff --git a/src/Mobs/Horse.h b/src/Mobs/Horse.h index 27168ebae..987ab71a9 100644 --- a/src/Mobs/Horse.h +++ b/src/Mobs/Horse.h @@ -32,6 +32,12 @@ public: int GetHorseStyle (void) const {return m_Style; } int GetHorseArmour (void) const {return m_Armour;} + virtual void GetBreedingItems(cItems & a_Items) override + { + a_Items.Add(E_ITEM_GOLDEN_CARROT); + a_Items.Add(E_ITEM_GOLDEN_APPLE); + } + private: bool m_bHasChest, m_bIsEating, m_bIsRearing, m_bIsMouthOpen, m_bIsTame, m_bIsSaddled; diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 060b934ec..d1173c41c 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -104,6 +104,7 @@ 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 { if (!a_ConfigName.empty()) { @@ -505,6 +506,16 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } // switch (m_EMState) BroadcastMovementUpdate(); + + if (m_AgingTimer > 0) + { + m_AgingTimer--; + if ((m_AgingTimer <= 0) && IsBaby()) + { + SetAge(1); + m_World->BroadcastEntityMetadata(*this); + } + } } diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 963ac9148..7b6c0c488 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -280,6 +280,7 @@ protected: double m_RelativeWalkSpeed; int m_Age; + int m_AgingTimer; /** Adds a random number of a_Item between a_Min and a_Max to itemdrops a_Drops */ void AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned int a_Max, short a_Item, short a_ItemHealth = 0); diff --git a/src/Mobs/Ocelot.h b/src/Mobs/Ocelot.h index f2727d354..796af2050 100644 --- a/src/Mobs/Ocelot.h +++ b/src/Mobs/Ocelot.h @@ -18,6 +18,11 @@ public: { } + virtual void GetBreedingItems(cItems & a_Items) override + { + a_Items.Add(E_ITEM_RAW_FISH); + } + CLASS_PROTODEF(cOcelot) } ; diff --git a/src/Mobs/PassiveMonster.cpp b/src/Mobs/PassiveMonster.cpp index a3d51da35..e39f6e23d 100644 --- a/src/Mobs/PassiveMonster.cpp +++ b/src/Mobs/PassiveMonster.cpp @@ -4,12 +4,17 @@ #include "PassiveMonster.h" #include "../World.h" #include "../Entities/Player.h" +#include "BoundingBox.h" cPassiveMonster::cPassiveMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height) : - super(a_ConfigName, a_MobType, a_SoundHurt, a_SoundDeath, a_Width, a_Height) + super(a_ConfigName, a_MobType, a_SoundHurt, a_SoundDeath, a_Width, a_Height), + m_LovePartner(nullptr), + m_LoveTimer(0), + m_LoveCooldown(0), + m_MatingTimer(0) { m_EMPersonality = PASSIVE; } @@ -35,6 +40,31 @@ 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 + + // 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::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); @@ -43,21 +73,94 @@ void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { CheckEventLostPlayer(); } - cItems FollowedItems; - GetFollowedItems(FollowedItems); - if (FollowedItems.Size() <= 0) + if ((m_LovePartner != nullptr) && m_LovePartner->IsDestroyed()) { - return; + m_LovePartner = nullptr; } - cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast(m_SightDistance)); - if (a_Closest_Player != nullptr) + if (m_LovePartner != nullptr) { - cItem EquippedItem = a_Closest_Player->GetEquippedItem(); - if (FollowedItems.ContainsType(EquippedItem)) + // if we have a partner, bump into them until baby is made + if (m_MatingTimer > 0) { - Vector3d PlayerPos = a_Closest_Player->GetPosition(); - MoveToPosition(PlayerPos); + Vector3d Pos = m_LovePartner->GetPosition(); + MoveToPosition(Pos); } + else + { + // spawn baby + Vector3f Pos = (GetPosition() + m_LovePartner->GetPosition()) * 0.5; + m_World->SpawnMob(Pos.x, Pos.y, Pos.z, GetMobType(), true); + + cFastRandom Random; + m_World->SpawnExperienceOrb(Pos.x, Pos.y, Pos.z, 1 + Random.NextInt(6)); + + m_LovePartner->ResetLoveMode(); + ResetLoveMode(); + } + } + else + { + cItems FollowedItems; + GetFollowedItems(FollowedItems); + if (FollowedItems.Size() > 0) + { + cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast(m_SightDistance)); + if (a_Closest_Player != nullptr) + { + cItem EquippedItem = a_Closest_Player->GetEquippedItem(); + if (FollowedItems.ContainsType(EquippedItem)) + { + Vector3d PlayerPos = a_Closest_Player->GetPosition(); + MoveToPosition(PlayerPos); + } + } + } + } + + if (m_LoveTimer > 0) + { + if (m_LovePartner == nullptr) + { + class LookForLover : public cEntityCallback + { + public: + cEntity * m_Me; + + LookForLover(cEntity * a_Me) : + m_Me(a_Me) + { + } + + virtual bool Item(cEntity * a_Entity) override + { + // if we're the same species as someone around and they dont have a partner, swipe right + if ((a_Entity->GetEntityType() == m_Me->GetEntityType()) && (a_Entity != m_Me)) + { + cPassiveMonster * Me = static_cast(m_Me); + cPassiveMonster * Partner = static_cast(a_Entity); + if (Partner->IsInLove() && (Partner->GetPartner() == nullptr)) + { + Partner->EngageLoveMode(Me); + Me->EngageLoveMode(Partner); + return true; + } + } + return false; + } + } Callback(this); + + m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), 8, 8), Callback); + } + + m_LoveTimer--; + } + if (m_MatingTimer > 0) + { + m_MatingTimer--; + } + if (m_LoveCooldown > 0) + { + m_LoveCooldown--; } } @@ -65,3 +168,27 @@ void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +void cPassiveMonster::OnRightClicked(cPlayer & a_Player) +{ + super::OnRightClicked(a_Player); + + // if right clicked on the player with breeding items, go into lovemode + if ((m_LoveCooldown == 0) && !IsInLove() && !IsBaby()) + { + short HeldItem = a_Player.GetEquippedItem().m_ItemType; + cItems Items; + GetBreedingItems(Items); + if (Items.ContainsType(HeldItem)) + { + if (!a_Player.IsGameModeCreative()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + } + m_LoveTimer = 20 * 30; // half a minute + m_World->BroadcastEntityStatus(*this, esMobInLove); + } + } +} + + + diff --git a/src/Mobs/PassiveMonster.h b/src/Mobs/PassiveMonster.h index a7e574a1d..ecce4ceb6 100644 --- a/src/Mobs/PassiveMonster.h +++ b/src/Mobs/PassiveMonster.h @@ -16,6 +16,7 @@ public: cPassiveMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height); virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + virtual void OnRightClicked(cPlayer & a_Player) override; /** When hit by someone, run away */ virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override; @@ -23,7 +24,22 @@ public: /** 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); } + + cPassiveMonster * GetPartner(void) const { return m_LovePartner; } + void EngageLoveMode(cPassiveMonster * a_Partner); + void ResetLoveMode(); + + bool IsInLove() const { return (m_LoveTimer > 0); } + bool IsInLoveCooldown() const { return (m_LoveCooldown > 0); } + +protected: + cPassiveMonster * m_LovePartner; + int m_LoveTimer; + int m_LoveCooldown; + int m_MatingTimer; +}; diff --git a/src/Mobs/Pig.cpp b/src/Mobs/Pig.cpp index 21c8e923a..b67b29d87 100644 --- a/src/Mobs/Pig.cpp +++ b/src/Mobs/Pig.cpp @@ -39,6 +39,8 @@ void cPig::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cPig::OnRightClicked(cPlayer & a_Player) { + super::OnRightClicked(a_Player); + if (m_bIsSaddled) { if (m_Attachee != nullptr) diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index 373269509..833746c00 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -3368,7 +3368,7 @@ void cProtocol180::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) a_Pkt.WriteBEUInt8(0x56); // Int at index 22 a_Pkt.WriteBEInt32(Horse.GetHorseArmour()); a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Horse.IsBaby() ? -1 : 0); + a_Pkt.WriteBEInt8(Horse.IsBaby() ? -1 : (Horse.IsInLoveCooldown() ? 1 : 0)); break; } // case mtHorse @@ -3384,15 +3384,31 @@ void cProtocol180::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) { auto & Ocelot = reinterpret_cast(a_Mob); a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Ocelot.IsBaby() ? -1 : 0); + a_Pkt.WriteBEInt8(Ocelot.IsBaby() ? -1 : (Ocelot.IsInLoveCooldown() ? 1 : 0)); break; } // case mtOcelot + case mtCow: + { + auto & Cow = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Cow.IsBaby() ? -1 : (Cow.IsInLoveCooldown() ? 1 : 0)); + break; + } // case mtCow + + case mtChicken: + { + auto & Chicken = reinterpret_cast(a_Mob); + a_Pkt.WriteBEUInt8(0x0c); + a_Pkt.WriteBEInt8(Chicken.IsBaby() ? -1 : (Chicken.IsInLoveCooldown() ? 1 : 0)); + break; + } // case mtChicken + case mtPig: { auto & Pig = reinterpret_cast(a_Mob); a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Pig.IsBaby() ? -1 : 0); + a_Pkt.WriteBEInt8(Pig.IsBaby() ? -1 : (Pig.IsInLoveCooldown() ? 1 : 0)); a_Pkt.WriteBEUInt8(0x10); a_Pkt.WriteBEUInt8(Pig.IsSaddled() ? 1 : 0); break; @@ -3402,7 +3418,7 @@ void cProtocol180::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) { auto & Sheep = reinterpret_cast(a_Mob); a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Sheep.IsBaby() ? -1 : 0); + a_Pkt.WriteBEInt8(Sheep.IsBaby() ? -1 : (Sheep.IsInLoveCooldown() ? 1 : 0)); a_Pkt.WriteBEUInt8(0x10); Byte SheepMetadata = 0; @@ -3421,7 +3437,7 @@ void cProtocol180::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) a_Pkt.WriteBEUInt8(0x12); a_Pkt.WriteBEUInt8(Rabbit.GetRabbitTypeAsNumber()); a_Pkt.WriteBEUInt8(0x0c); - a_Pkt.WriteBEInt8(Rabbit.IsBaby() ? -1 : 0); + a_Pkt.WriteBEInt8(Rabbit.IsBaby() ? -1 : (Rabbit.IsInLoveCooldown() ? 1 : 0)); break; } // case mtRabbit