From 6a1984112146a14c96f43e9cdf758f5104d6d2a3 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 12 Jul 2017 12:42:02 +0200 Subject: Added basic ocelot behavior (#3829) --- src/BlockEntities/ChestEntity.cpp | 31 ++++- src/Mobs/CMakeLists.txt | 1 + src/Mobs/Ocelot.cpp | 205 ++++++++++++++++++++++++++++++++ src/Mobs/Ocelot.h | 49 +++++++- src/Mobs/PassiveMonster.cpp | 2 +- src/Protocol/Protocol_1_11.cpp | 18 +++ src/Protocol/Protocol_1_12.cpp | 18 +++ src/WorldStorage/NBTChunkSerializer.cpp | 13 +- src/WorldStorage/WSSAnvil.cpp | 45 +++++-- src/WorldStorage/WSSAnvil.h | 6 +- 10 files changed, 368 insertions(+), 20 deletions(-) create mode 100644 src/Mobs/Ocelot.cpp diff --git a/src/BlockEntities/ChestEntity.cpp b/src/BlockEntities/ChestEntity.cpp index a4576b66d..54f6e0dfa 100644 --- a/src/BlockEntities/ChestEntity.cpp +++ b/src/BlockEntities/ChestEntity.cpp @@ -6,6 +6,8 @@ #include "../Entities/Player.h" #include "../UI/ChestWindow.h" #include "../ClientHandle.h" +#include "../Mobs/Ocelot.h" +#include "../BoundingBox.h" @@ -217,11 +219,36 @@ void cChestEntity::DestroyWindow() +class cFindSittingCat : + public cEntityCallback +{ + virtual bool Item(cEntity * a_Entity) override + { + return ( + (a_Entity->GetEntityType() == cEntity::etMonster) && + (static_cast(a_Entity)->GetMobType() == eMonsterType::mtOcelot) && + (static_cast(a_Entity)->IsSitting()) + ); + } +}; + + + + + bool cChestEntity::IsBlocked() { - // TODO: cats are an obstruction + cFindSittingCat FindSittingCat; return ( (GetPosY() >= cChunkDef::Height - 1) || - !cBlockInfo::IsTransparent(GetWorld()->GetBlock(GetPosX(), GetPosY() + 1, GetPosZ())) + !cBlockInfo::IsTransparent(GetWorld()->GetBlock(GetPosX(), GetPosY() + 1, GetPosZ())) || + ( + (GetWorld()->GetBlock(GetPosX(), GetPosY() + 1, GetPosZ()) == E_BLOCK_AIR) && + !GetWorld()->ForEachEntityInBox(cBoundingBox(Vector3d(GetPosX(), GetPosY() + 1, GetPosZ()), 1, 1), FindSittingCat) + ) ); } + + + + diff --git a/src/Mobs/CMakeLists.txt b/src/Mobs/CMakeLists.txt index 44c664b6e..55ae36e1e 100644 --- a/src/Mobs/CMakeLists.txt +++ b/src/Mobs/CMakeLists.txt @@ -20,6 +20,7 @@ SET (SRCS MagmaCube.cpp Monster.cpp Mooshroom.cpp + Ocelot.cpp PassiveAggressiveMonster.cpp PassiveMonster.cpp Path.cpp diff --git a/src/Mobs/Ocelot.cpp b/src/Mobs/Ocelot.cpp new file mode 100644 index 000000000..47776670c --- /dev/null +++ b/src/Mobs/Ocelot.cpp @@ -0,0 +1,205 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "Ocelot.h" +#include "../World.h" +#include "../Entities/Player.h" +#include "../Items/ItemHandler.h" +#include "Broadcaster.h" + + + + + +cOcelot::cOcelot(void) : + super("Ocelot", mtOcelot, "entity.cat.hurt", "entity.cat.death", 0.6, 0.8), + m_IsSitting(false), + m_IsTame(false), + m_IsBegging(false), + m_CatType(ctWildOcelot), + m_OwnerName("") +{ +} + + + + + +void cOcelot::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + if (!IsTicking()) + { + // The base class tick destroyed us + return; + } + + if (!IsTame() && !IsBaby()) + { + if (m_CheckPlayerTickCount == 23) + { + cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), 10, true); + if (a_Closest_Player != nullptr) + { + cItems Items; + GetBreedingItems(Items); + if (Items.ContainsType(a_Closest_Player->GetEquippedItem().m_ItemType)) + { + if (!IsBegging()) + { + SetIsBegging(true); + m_World->BroadcastEntityMetadata(*this); + } + + MoveToPosition(a_Closest_Player->GetPosition()); + } + else + { + if (IsBegging()) + { + SetIsBegging(false); + m_World->BroadcastEntityMetadata(*this); + } + } + } + + m_CheckPlayerTickCount = 0; + } + else + { + m_CheckPlayerTickCount++; + } + } + + if (IsTame() && !IsSitting()) + { + TickFollowPlayer(); + } + else if (IsSitting()) + { + StopMovingToPosition(); + } + + m_World->BroadcastEntityMetadata(*this); +} + + + + + +void cOcelot::TickFollowPlayer() +{ + class cCallback : + public cPlayerListCallback + { + virtual bool Item(cPlayer * a_Player) override + { + OwnerPos = a_Player->GetPosition(); + OwnerFlying = a_Player->IsFlying(); + return true; + } + public: + Vector3d OwnerPos; + bool OwnerFlying; + } Callback; + + if (m_World->DoWithPlayerByUUID(m_OwnerUUID, Callback)) + { + // The player is present in the world, follow him: + double Distance = (Callback.OwnerPos - GetPosition()).Length(); + if (Distance > 12) + { + if (!Callback.OwnerFlying) + { + Callback.OwnerPos.y = FindFirstNonAirBlockPosition(Callback.OwnerPos.x, Callback.OwnerPos.z); + TeleportToCoords(Callback.OwnerPos.x, Callback.OwnerPos.y, Callback.OwnerPos.z); + } + } + if (Distance < 2) + { + StopMovingToPosition(); + } + else + { + if (!Callback.OwnerFlying) + { + MoveToPosition(Callback.OwnerPos); + } + } + } +} + + + + + +void cOcelot::OnRightClicked(cPlayer & a_Player) +{ + if (!IsTame()) + { + if ( + IsBegging() && + ((a_Player.GetPosition() - GetPosition()).Length() <= 3) + ) + { + cItems Items; + GetBreedingItems(Items); + if (Items.ContainsType(a_Player.GetEquippedItem().m_ItemType)) + { + if (!a_Player.IsGameModeCreative()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + } + + auto & Random = GetRandomProvider(); + + if (Random.RandBool(1.0 / 3.0)) + { + // Taming succeeded + SetIsBegging(false); + + SetMaxHealth(20); + SetIsTame(true); + SetOwner(a_Player.GetName(), a_Player.GetUUID()); + SetCatType(static_cast(Random.RandInt(1, 3))); + m_World->BroadcastEntityStatus(*this, esWolfTamed); + m_World->GetBroadcaster().BroadcastParticleEffect("heart", static_cast(GetPosition()), Vector3f{}, 0, 5); + } + else + { + // Taming failed + m_World->BroadcastEntityStatus(*this, esWolfTaming); + m_World->GetBroadcaster().BroadcastParticleEffect("smoke", static_cast(GetPosition()), Vector3f{}, 0, 5); + } + } + } + else + { + super::OnRightClicked(a_Player); + } + } + else if (a_Player.GetUUID() == m_OwnerUUID) + { + super::OnRightClicked(a_Player); + SetIsSitting(!IsSitting()); + } + m_World->BroadcastEntityMetadata(*this); +} + + + + + +void cOcelot::SpawnOn(cClientHandle & a_ClientHandle) +{ + super::SpawnOn(a_ClientHandle); + if (!IsBaby() && GetRandomProvider().RandBool(1.0 / 7.0)) + { + m_World->SpawnMob(GetPosX(), GetPosY(), GetPosZ(), m_MobType, true); + m_World->SpawnMob(GetPosX(), GetPosY(), GetPosZ(), m_MobType, true); + } +} + + + + diff --git a/src/Mobs/Ocelot.h b/src/Mobs/Ocelot.h index a352e5854..fbff991c7 100644 --- a/src/Mobs/Ocelot.h +++ b/src/Mobs/Ocelot.h @@ -2,6 +2,7 @@ #pragma once #include "PassiveMonster.h" +#include "../Entities/Entity.h" @@ -13,17 +14,57 @@ class cOcelot : typedef cPassiveMonster super; public: - cOcelot(void) : - super("Ocelot", mtOcelot, "entity.cat.hurt", "entity.cat.death", 0.6, 0.8) + + enum eCatType { - } + ctWildOcelot, + ctTuxedo, + ctTabby, + ctSiamese, + } ; + + cOcelot(void); + CLASS_PROTODEF(cOcelot) + + virtual void OnRightClicked(cPlayer & a_Player) override; + virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + virtual void TickFollowPlayer(); + virtual void SpawnOn(cClientHandle & a_ClientHandle) override; virtual void GetBreedingItems(cItems & a_Items) override { a_Items.Add(E_ITEM_RAW_FISH); } - CLASS_PROTODEF(cOcelot) + // Get functions + bool IsSitting (void) const override { return m_IsSitting; } + bool IsTame (void) const override { return m_IsTame; } + bool IsBegging (void) const { return m_IsBegging; } + AString GetOwnerName (void) const { return m_OwnerName; } + AString GetOwnerUUID (void) const { return m_OwnerUUID; } + eCatType GetOcelotType (void) const { return m_CatType; } + + // Set functions + void SetIsSitting (bool a_IsSitting) { m_IsSitting = a_IsSitting; } + void SetIsTame (bool a_IsTame) { m_IsTame = a_IsTame; } + void SetIsBegging (bool a_IsBegging) { m_IsBegging = a_IsBegging; } + void SetOwner (const AString & a_NewOwnerName, const AString & a_NewOwnerUUID) + { + m_OwnerName = a_NewOwnerName; + m_OwnerUUID = a_NewOwnerUUID; + } + void SetCatType (eCatType a_CatType) { m_CatType = a_CatType; } + +protected: + + bool m_IsSitting; + bool m_IsTame; + bool m_IsBegging; + eCatType m_CatType; + /** Only check for a nearby player holding the breeding items every 23 ticks. */ + int m_CheckPlayerTickCount; + AString m_OwnerName; + AString m_OwnerUUID; } ; diff --git a/src/Mobs/PassiveMonster.cpp b/src/Mobs/PassiveMonster.cpp index 02e16d22f..73defaf7f 100644 --- a/src/Mobs/PassiveMonster.cpp +++ b/src/Mobs/PassiveMonster.cpp @@ -247,7 +247,7 @@ void cPassiveMonster::OnRightClicked(cPlayer & a_Player) eMonsterType MonsterType = cItemSpawnEggHandler::ItemDamageToMonsterType(EquippedItem.m_ItemDamage); if ( (MonsterType == m_MobType) && - (GetWorld()->SpawnMob(GetPosX(), GetPosY(), GetPosZ(), m_MobType, true) != cEntity::INVALID_ID) // Spawning succeeded + (m_World->SpawnMob(GetPosX(), GetPosY(), GetPosZ(), m_MobType, true) != cEntity::INVALID_ID) // Spawning succeeded ) { if (!a_Player.IsGameModeCreative()) diff --git a/src/Protocol/Protocol_1_11.cpp b/src/Protocol/Protocol_1_11.cpp index e4aaa8f6f..3376e8f88 100644 --- a/src/Protocol/Protocol_1_11.cpp +++ b/src/Protocol/Protocol_1_11.cpp @@ -956,6 +956,24 @@ void cProtocol_1_11_0::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_ a_Pkt.WriteBEUInt8(AGEABLE_BABY); a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); a_Pkt.WriteBool(Ocelot.IsBaby()); + + Int8 OcelotStatus = 0; + if (Ocelot.IsSitting()) + { + OcelotStatus |= 0x1; + } + if (Ocelot.IsTame()) + { + OcelotStatus |= 0x4; + } + a_Pkt.WriteBEUInt8(TAMEABLE_ANIMAL_STATUS); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(OcelotStatus); + + a_Pkt.WriteBEUInt8(OCELOT_TYPE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(Ocelot.GetOcelotType())); + break; } // case mtOcelot diff --git a/src/Protocol/Protocol_1_12.cpp b/src/Protocol/Protocol_1_12.cpp index a702361b2..a25ac1c0d 100644 --- a/src/Protocol/Protocol_1_12.cpp +++ b/src/Protocol/Protocol_1_12.cpp @@ -769,6 +769,24 @@ void cProtocol_1_12::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mo a_Pkt.WriteBEUInt8(AGEABLE_BABY); a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); a_Pkt.WriteBool(Ocelot.IsBaby()); + + Int8 OcelotStatus = 0; + if (Ocelot.IsSitting()) + { + OcelotStatus |= 0x1; + } + if (Ocelot.IsTame()) + { + OcelotStatus |= 0x4; + } + a_Pkt.WriteBEUInt8(TAMEABLE_ANIMAL_STATUS); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(OcelotStatus); + + a_Pkt.WriteBEUInt8(OCELOT_TYPE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(Ocelot.GetOcelotType())); + break; } // case mtOcelot diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index d61e61879..ecc7a550a 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -670,7 +670,18 @@ void cNBTChunkSerializer::AddMonsterEntity(cMonster * a_Monster) } case mtOcelot: { - m_Writer.AddInt("Age", reinterpret_cast(a_Monster)->GetAge()); + const auto *Ocelot = reinterpret_cast(a_Monster); + if (!Ocelot->GetOwnerName().empty()) + { + m_Writer.AddString("Owner", Ocelot->GetOwnerName()); + } + if (!Ocelot->GetOwnerUUID().empty()) + { + m_Writer.AddString("OwnerUUID", Ocelot->GetOwnerUUID()); + } + m_Writer.AddByte("Sitting", Ocelot->IsSitting() ? 1 : 0); + m_Writer.AddInt ("CatType", Ocelot->GetOcelotType()); + m_Writer.AddInt ("Age", Ocelot->GetAge()); break; } case mtPig: diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index 8b8a0482e..3715548e7 100755 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -2496,6 +2496,27 @@ void cWSSAnvil::LoadOcelotFromNBT(cEntityList & a_Entities, const cParsedNBT & a return; } + auto OwnerInfo = LoadEntityOwner(a_NBT, a_TagIdx); + if (!OwnerInfo.first.empty() && !OwnerInfo.second.empty()) + { + Monster->SetOwner(OwnerInfo.first, OwnerInfo.second); + Monster->SetIsTame(true); + } + + int TypeIdx = a_NBT.FindChildByName(a_TagIdx, "CatType"); + if (TypeIdx > 0) + { + int Type = a_NBT.GetInt(TypeIdx); + Monster->SetCatType(static_cast(Type)); + } + + int SittingIdx = a_NBT.FindChildByName(a_TagIdx, "Sitting"); + if ((SittingIdx > 0) && (a_NBT.GetType(SittingIdx) == TAG_Byte)) + { + bool Sitting = ((a_NBT.GetByte(SittingIdx) == 1) ? true : false); + Monster->SetIsSitting(Sitting); + } + int AgeableIdx = a_NBT.FindChildByName(a_TagIdx, "Age"); if (AgeableIdx > 0) { @@ -2876,7 +2897,12 @@ void cWSSAnvil::LoadWolfFromNBT(cEntityList & a_Entities, const cParsedNBT & a_N return; } - LoadWolfOwner(*Monster.get(), a_NBT, a_TagIdx); + auto OwnerInfo = LoadEntityOwner(a_NBT, a_TagIdx); + if (!OwnerInfo.first.empty() && !OwnerInfo.second.empty()) + { + Monster->SetOwner(OwnerInfo.first, OwnerInfo.second); + Monster->SetIsTame(true); + } int SittingIdx = a_NBT.FindChildByName(a_TagIdx, "Sitting"); if ((SittingIdx > 0) && (a_NBT.GetType(SittingIdx) == TAG_Byte)) @@ -3009,7 +3035,7 @@ void cWSSAnvil::LoadPigZombieFromNBT(cEntityList & a_Entities, const cParsedNBT -void cWSSAnvil::LoadWolfOwner(cWolf & a_Wolf, const cParsedNBT & a_NBT, int a_TagIdx) +std::pair cWSSAnvil::LoadEntityOwner(const cParsedNBT & a_NBT, int a_TagIdx) { // Load the owner information. OwnerUUID or Owner may be specified, possibly both: AString OwnerUUID, OwnerName; @@ -3026,19 +3052,19 @@ void cWSSAnvil::LoadWolfOwner(cWolf & a_Wolf, const cParsedNBT & a_NBT, int a_Ta if (OwnerName.empty() && OwnerUUID.empty()) { // There is no owner, bail out: - return; + return std::pair(); } // Convert name to UUID, if needed: if (OwnerUUID.empty()) { - // This wolf has only playername stored (pre-1.7.6), look up the UUID + // This entity has only playername stored (pre-1.7.6), look up the UUID // The lookup is blocking, but we're running in a separate thread, so it's ok OwnerUUID = cRoot::Get()->GetMojangAPI().GetUUIDFromPlayerName(OwnerName); if (OwnerUUID.empty()) { - // Not a known player, un-tame the wolf by bailing out - return; + // Not a known player, un-tame the entity by bailing out + return std::pair(); } } else @@ -3054,13 +3080,12 @@ void cWSSAnvil::LoadWolfOwner(cWolf & a_Wolf, const cParsedNBT & a_NBT, int a_Ta OwnerName = cRoot::Get()->GetMojangAPI().GetPlayerNameFromUUID(OwnerUUID); if (OwnerName.empty()) { - // Not a known player, un-tame the wolf by bailing out - return; + // Not a known player, un-tame the entity by bailing out + return std::pair(); } } - a_Wolf.SetOwner(OwnerName, OwnerUUID); - a_Wolf.SetIsTame(true); + return std::make_pair(OwnerName, OwnerUUID); } diff --git a/src/WorldStorage/WSSAnvil.h b/src/WorldStorage/WSSAnvil.h index 12acbbcff..4d37aa244 100755 --- a/src/WorldStorage/WSSAnvil.h +++ b/src/WorldStorage/WSSAnvil.h @@ -22,6 +22,7 @@ class cItemGrid; class cProjectileEntity; class cHangingEntity; class cWolf; +class cOcelot; @@ -230,8 +231,9 @@ protected: void LoadZombieFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx); void LoadPigZombieFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx); - /** Loads the wolf's owner information from the NBT into the specified wolf entity. */ - void LoadWolfOwner(cWolf & a_Wolf, const cParsedNBT & a_NBT, int a_TagIdx); + /** Loads the owner name and UUID from the entity at the specified NBT tag. + Returns a pair of {name, uuid}. If the entity is not owned, both are empty strings. */ + std::pair LoadEntityOwner(const cParsedNBT & a_NBT, int a_TagIdx); /** Loads entity common data from the NBT compound; returns true if successful */ bool LoadEntityBaseFromNBT(cEntity & a_Entity, const cParsedNBT & a_NBT, int a_TagIdx); -- cgit v1.2.3