From b18f6637b6c58db20353cd3e77584b646ab36b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Beltr=C3=A1n?= Date: Mon, 21 Aug 2017 10:46:41 +0200 Subject: Fully implemented leashes (#3798) --- CONTRIBUTORS | 1 + Server/Plugins/APIDump/APIDesc.lua | 84 ++++++++++++++- src/Bindings/AllToLua.pkg | 1 + src/Bindings/CMakeLists.txt | 1 + src/Bindings/LuaState.cpp | 1 + src/BlockID.h | 1 + src/Blocks/BlockFence.h | 62 ++++++++++- src/Blocks/WorldInterface.h | 5 + src/BoundingBox.cpp | 10 ++ src/BoundingBox.h | 4 + src/Chunk.cpp | 22 ++++ src/Chunk.h | 2 + src/ChunkMap.cpp | 29 +++++ src/ChunkMap.h | 2 + src/ClientHandle.cpp | 18 ++++ src/ClientHandle.h | 2 + src/Entities/CMakeLists.txt | 2 + src/Entities/Entity.cpp | 36 +++++++ src/Entities/Entity.h | 20 +++- src/Entities/LeashKnot.cpp | 185 ++++++++++++++++++++++++++++++++ src/Entities/LeashKnot.h | 50 +++++++++ src/Items/ItemHandler.cpp | 1 + src/Mobs/Creeper.cpp | 2 + src/Mobs/Horse.cpp | 28 ++--- src/Mobs/Monster.cpp | 173 ++++++++++++++++++++++++++++- src/Mobs/Monster.h | 48 +++++++++ src/Mobs/PassiveMonster.cpp | 2 +- src/Protocol/Protocol.h | 2 + src/Protocol/ProtocolRecognizer.cpp | 20 ++++ src/Protocol/ProtocolRecognizer.h | 2 + src/Protocol/Protocol_1_12.cpp | 24 +++++ src/Protocol/Protocol_1_12.h | 2 + src/Protocol/Protocol_1_8.cpp | 28 +++++ src/Protocol/Protocol_1_8.h | 2 + src/Protocol/Protocol_1_9.cpp | 24 +++++ src/Protocol/Protocol_1_9.h | 2 + src/World.cpp | 18 ++++ src/World.h | 4 +- src/WorldStorage/NBTChunkSerializer.cpp | 38 ++++++- src/WorldStorage/NBTChunkSerializer.h | 2 + src/WorldStorage/WSSAnvil.cpp | 77 +++++++++++++ src/WorldStorage/WSSAnvil.h | 4 + 42 files changed, 1021 insertions(+), 20 deletions(-) create mode 100644 src/Entities/LeashKnot.cpp create mode 100644 src/Entities/LeashKnot.h diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 2b3c877d9..5ed1d02b2 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -45,6 +45,7 @@ SamJBarney Schwertspize Seadragon91 (Lukas Pioch) Sofapriester +Spekdrum (Pablo Beltran) SphinxC0re structinf (xdot) sweetgiorni diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index ce6277533..b35735736 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -3655,6 +3655,16 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") }, Notes = "Returns true if the entity is an item frame.", }, + IsLeashKnot = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if the entity is a leash knot.", + }, IsMinecart = { Returns = @@ -4327,7 +4337,11 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") }, etItemFrame = { - Notes = "", + Notes = "The entity is an item frame", + }, + etLeashKnot = + { + Notes = "The entity is a leash knot", }, etMinecart = { @@ -8620,6 +8634,16 @@ a_Player:OpenWindow(Window); ]], Functions = { + CanBeLeashed = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns whether the mob can be leashed.", + }, FamilyFromType = { IsStatic = true, @@ -8659,6 +8683,17 @@ a_Player:OpenWindow(Window); }, Notes = "Gets the custom name of the monster. If no custom name is set, the function returns an empty string.", }, + GetLeashedTo = + { + Returns = + { + { + Name = "LeashedTo", + Type = "cEntity", + }, + }, + Notes = "Returns the entity to where this mob is leashed, returns nil if it's not leashed", + }, GetMobFamily = { Returns = @@ -8739,6 +8774,27 @@ a_Player:OpenWindow(Window); }, Notes = "Is the custom name of this monster always visible? If not, you only see the name when you sight the mob.", }, + IsLeashed = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns whether the monster is leashed to an entity.", + }, + LeashTo = + { + Params = + { + { + Name = "Entity", + Type = "cEntity", + } + }, + Notes = "Leash the monster to an entity.", + }, MobTypeToString = { IsStatic = true, @@ -8797,6 +8853,17 @@ a_Player:OpenWindow(Window); }, Notes = "Sets the age of the monster", }, + SetCanBeLeashed = + { + Params = + { + { + Name = "CanBeLeashed", + Type = "boolean", + } + }, + Notes = "Sets whether the mob can be leashed, for extensibility in plugins" + }, SetCustomName = { Params = @@ -8849,6 +8916,17 @@ a_Player:OpenWindow(Window); }, Notes = "Returns the mob type ({{Globals#eMonsterType|mtXXX}} constant) parsed from the string type (\"creeper\"), or mtInvalidType if unrecognized.", }, + Unleash = + { + Params = + { + { + Name = "ShouldDropLeashPickup", + Type = "boolean", + }, + }, + Notes = "Unleash the monster.", + }, }, Constants = { @@ -15534,6 +15612,10 @@ end { Notes = "The itemtype for lead" }, + E_ITEM_LEASH = + { + Notes = "The itemtype for lead (E_ITEM_LEAD synonym)" + }, E_ITEM_LEATHER = { Notes = "The itemtype for leather" diff --git a/src/Bindings/AllToLua.pkg b/src/Bindings/AllToLua.pkg index a109913e2..b99ac2f88 100644 --- a/src/Bindings/AllToLua.pkg +++ b/src/Bindings/AllToLua.pkg @@ -86,6 +86,7 @@ $cfile "../Entities/Floater.h" $cfile "../Entities/GhastFireballEntity.h" $cfile "../Entities/HangingEntity.h" $cfile "../Entities/ItemFrame.h" +$cfile "../Entities/LeashKnot.h" $cfile "../Entities/Player.h" $cfile "../Entities/Painting.h" $cfile "../Entities/Pickup.h" diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt index da7c8bbe8..6b2ad7ab0 100644 --- a/src/Bindings/CMakeLists.txt +++ b/src/Bindings/CMakeLists.txt @@ -109,6 +109,7 @@ set(BINDING_DEPENDENCIES ../Entities/GhastFireballEntity.h ../Entities/HangingEntity.h ../Entities/ItemFrame.h + ../Entities/LeashKnot.h ../Entities/Pawn.h ../Entities/Player.h ../Entities/Painting.h diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index 185759acc..b443325ec 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -993,6 +993,7 @@ void cLuaState::Push(cEntity * a_Entity) case cEntity::etExpOrb: case cEntity::etItemFrame: case cEntity::etPainting: + case cEntity::etLeashKnot: { // Push the generic entity class type: tolua_pushusertype(m_LuaState, a_Entity, "cEntity"); diff --git a/src/BlockID.h b/src/BlockID.h index 96540bd6f..10a9b6672 100644 --- a/src/BlockID.h +++ b/src/BlockID.h @@ -459,6 +459,7 @@ enum ENUM_ITEM_ID : short E_ITEM_GOLD_HORSE_ARMOR = 418, E_ITEM_DIAMOND_HORSE_ARMOR = 419, E_ITEM_LEAD = 420, + E_ITEM_LEASH = E_ITEM_LEAD, E_ITEM_NAME_TAG = 421, E_ITEM_MINECART_WITH_COMMAND_BLOCK = 422, E_ITEM_RAW_MUTTON = 423, diff --git a/src/Blocks/BlockFence.h b/src/Blocks/BlockFence.h index 736466e99..2f2d4e46d 100644 --- a/src/Blocks/BlockFence.h +++ b/src/Blocks/BlockFence.h @@ -3,7 +3,10 @@ #include "BlockHandler.h" #include "../BoundingBox.h" - +#include "../EffectID.h" +#include "Entities/LeashKnot.h" +#include "BoundingBox.h" +#include "../Mobs/PassiveMonster.h" @@ -72,6 +75,63 @@ public: return PlacementBox; } + + virtual bool OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override + { + auto LeashKnot = cLeashKnot::FindKnotAtPos(*a_Player.GetWorld(), { a_BlockX, a_BlockY, a_BlockZ }); + auto KnotAlreadyExists = (LeashKnot != nullptr); + + if (KnotAlreadyExists) + { + // Check leashed nearby mobs to leash them to the knot + LeashKnot->TiePlayersLeashedMobs(a_Player, KnotAlreadyExists); + } + // New knot? needs to init and produce sound effect + else + { + auto NewLeashKnot = cpp14::make_unique(a_BlockFace, a_BlockX, a_BlockY, a_BlockZ); + auto NewLeashKnotPtr = NewLeashKnot.get(); + + NewLeashKnotPtr->TiePlayersLeashedMobs(a_Player, KnotAlreadyExists); + + // Only put the knot in the world if any mob has been leashed to + if (NewLeashKnotPtr->HasAnyMobLeashed()) + { + if (!NewLeashKnotPtr->Initialize(std::move(NewLeashKnot), *a_Player.GetWorld())) + { + return false; + } + a_Player.GetWorld()->BroadcastSoundEffect("entity.leashknot.place", a_Player.GetPosX(), a_Player.GetPosY(), a_Player.GetPosZ(), 1, 1); + } + else + { + return false; + } + } + + return true; + } + + virtual void OnCancelRightClick(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) override + { + a_WorldInterface.SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, a_Player); + } + + virtual bool IsUseable(void) override + { + return true; + } + + virtual void OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ) + { + auto LeashKnot = cLeashKnot::FindKnotAtPos(a_WorldInterface, { a_BlockX, a_BlockY, a_BlockZ }); + + if (LeashKnot != nullptr) + { + LeashKnot->SetShouldSelfDestroy(); + } + } + }; diff --git a/src/Blocks/WorldInterface.h b/src/Blocks/WorldInterface.h index 1300ffe8c..0c92a64e5 100644 --- a/src/Blocks/WorldInterface.h +++ b/src/Blocks/WorldInterface.h @@ -53,6 +53,11 @@ public: /** Calls the callback for each player in the list; returns true if all players processed, false if the callback aborted by returning true */ virtual bool ForEachPlayer(cItemCallback & a_Callback) = 0; + /** Calls the callback for each entity that has a nonempty intersection with the specified boundingbox. + Returns true if all entities processed, false if the callback aborted by returning true. + If any chunk in the box is missing, ignores the entities in that chunk silently. */ + virtual bool ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_Callback) = 0; + virtual void SetTimeOfDay(int a_TimeOfDay) = 0; /** Returns true if it is raining, stormy or snowing at the specified location. This takes into account biomes. */ diff --git a/src/BoundingBox.cpp b/src/BoundingBox.cpp index afb7e025b..bc4926011 100644 --- a/src/BoundingBox.cpp +++ b/src/BoundingBox.cpp @@ -39,6 +39,16 @@ cBoundingBox::cBoundingBox(Vector3d a_Pos, double a_Radius, double a_Height) : +cBoundingBox::cBoundingBox(Vector3d a_Pos, double a_Radius, double a_Height, double a_VerticalOffset) : + m_Min(a_Pos.x - a_Radius, a_Pos.y + a_VerticalOffset, a_Pos.z - a_Radius), + m_Max(a_Pos.x + a_Radius, a_Pos.y + a_VerticalOffset + a_Height, a_Pos.z + a_Radius) +{ +} + + + + + cBoundingBox::cBoundingBox(Vector3d a_Pos, double a_CubeLength) : m_Min(a_Pos.x - a_CubeLength / 2, a_Pos.y - a_CubeLength / 2, a_Pos.z - a_CubeLength / 2), m_Max(a_Pos.x + a_CubeLength / 2, a_Pos.y + a_CubeLength / 2, a_Pos.z + a_CubeLength / 2) diff --git a/src/BoundingBox.h b/src/BoundingBox.h index 809b7fe7c..c71c42d1a 100644 --- a/src/BoundingBox.h +++ b/src/BoundingBox.h @@ -26,6 +26,10 @@ public: cBoundingBox(double a_MinX, double a_MaxX, double a_MinY, double a_MaxY, double a_MinZ, double a_MaxZ); cBoundingBox(Vector3d a_Min, Vector3d a_Max); cBoundingBox(Vector3d a_Pos, double a_Radius, double a_Height); + /** Constructor that allows to define a bounding box given a center point (a_Pos), a horizontal radius (a_Radius), + a height starting from given center point (a_Height) and a vertical offset (a_VerticalOffset) to adjust the vertical starting point. + For example: cBoundingBox([0, 0, 0], 6, 6, -3) would create a bounding cube from (-3, -3, -3) to (3, 3, 3). */ + cBoundingBox(Vector3d a_Pos, double a_Radius, double a_Height, double a_VerticalOffset); cBoundingBox(Vector3d a_Pos, double a_CubeLength); cBoundingBox(const cBoundingBox & a_Orig); diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 4c85c9c80..95fb55ecb 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -2683,6 +2683,28 @@ void cChunk::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & a_V +void cChunk::BroadcastLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) +{ + for (auto ClientHandle : m_LoadedByClient) + { + ClientHandle->SendLeashEntity(a_Entity, a_EntityLeashedTo); + } +} + + + + +void cChunk::BroadcastUnleashEntity(const cEntity & a_Entity) +{ + for (auto ClientHandle : m_LoadedByClient) + { + ClientHandle->SendUnleashEntity(a_Entity); + } +} + + + + void cChunk::BroadcastBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle * a_Exclude) { diff --git a/src/Chunk.h b/src/Chunk.h index 7e399052d..c5e252114 100644 --- a/src/Chunk.h +++ b/src/Chunk.h @@ -368,12 +368,14 @@ public: void BroadcastEntityStatus (const cEntity & a_Entity, char a_Status, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityVelocity (const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityAnimation (const cEntity & a_Entity, char a_Animation, const cClientHandle * a_Exclude = nullptr); + void BroadcastLeashEntity (const cEntity & a_Entity, const cEntity & a_EntityLeashedTo); void BroadcastParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount, cClientHandle * a_Exclude = nullptr); void BroadcastRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID, const cClientHandle * a_Exclude = nullptr); void BroadcastSoundEffect (const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch, const cClientHandle * a_Exclude = nullptr); void BroadcastSoundParticleEffect(const EffectID a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data, const cClientHandle * a_Exclude = nullptr); void BroadcastSpawnEntity (cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); void BroadcastThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude = nullptr); + void BroadcastUnleashEntity (const cEntity & a_Entity); void BroadcastUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ); void SendBlockEntity (int a_BlockX, int a_BlockY, int a_BlockZ, cClientHandle & a_Client); diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index fcd990ad6..6afce338d 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -275,6 +275,35 @@ void cChunkMap::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & +void cChunkMap::BroadcastLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) +{ + cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); + if (Chunk == nullptr) + { + return; + } + Chunk->BroadcastLeashEntity(a_Entity, a_EntityLeashedTo); +} + + + + + +void cChunkMap::BroadcastUnleashEntity(const cEntity & a_Entity) +{ + cCSLock Lock(m_CSChunks); + cChunkPtr Chunk = GetChunkNoGen(a_Entity.GetChunkX(), a_Entity.GetChunkZ()); + if (Chunk == nullptr) + { + return; + } + Chunk->BroadcastUnleashEntity(a_Entity); +} + + + + void cChunkMap::BroadcastBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle * a_Exclude) { diff --git a/src/ChunkMap.h b/src/ChunkMap.h index e902be60c..66d1d4840 100644 --- a/src/ChunkMap.h +++ b/src/ChunkMap.h @@ -88,12 +88,14 @@ public: void BroadcastEntityStatus(const cEntity & a_Entity, char a_Status, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityVelocity(const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityAnimation(const cEntity & a_Entity, char a_Animation, const cClientHandle * a_Exclude = nullptr); + void BroadcastLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo); void BroadcastParticleEffect(const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount, cClientHandle * a_Exclude = nullptr); void BroadcastRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID, const cClientHandle * a_Exclude = nullptr); void BroadcastSoundEffect(const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch, const cClientHandle * a_Exclude = nullptr); void BroadcastSoundParticleEffect(const EffectID a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data, const cClientHandle * a_Exclude = nullptr); void BroadcastSpawnEntity(cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); void BroadcastThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude = nullptr); + void BroadcastUnleashEntity(const cEntity & a_Entity); void BroadcastUseBed(const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ); /** Sends the block entity, if it is at the coords specified, to a_Client */ diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index a8279a738..5fc659fe1 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -2249,6 +2249,24 @@ void cClientHandle::SendAttachEntity(const cEntity & a_Entity, const cEntity & a +void cClientHandle::SendLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) +{ + m_Protocol->SendLeashEntity(a_Entity, a_EntityLeashedTo); +} + + + + + +void cClientHandle::SendUnleashEntity(const cEntity & a_Entity) +{ + m_Protocol->SendUnleashEntity(a_Entity); +} + + + + + void cClientHandle::SendBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType) { m_Protocol->SendBlockAction(a_BlockX, a_BlockY, a_BlockZ, a_Byte1, a_Byte2, a_BlockType); diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 9e1d1a9b5..2a661e283 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -179,6 +179,7 @@ public: // tolua_export void SendHealth (void); void SendHideTitle (void); // tolua_export void SendInventorySlot (char a_WindowID, short a_SlotNum, const cItem & a_Item); + void SendLeashEntity (const cEntity & a_Entity, const cEntity & a_EntityLeashedTo); void SendMapData (const cMap & a_Map, int a_DataStartX, int a_DataStartY); void SendPaintingSpawn (const cPainting & a_Painting); void SendParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount); @@ -216,6 +217,7 @@ public: // tolua_export void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ); void SendTitleTimes (int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks); // tolua_export void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle); // tolua_export + void SendUnleashEntity (const cEntity & a_Entity); void SendUnloadChunk (int a_ChunkX, int a_ChunkZ); void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity); void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4); diff --git a/src/Entities/CMakeLists.txt b/src/Entities/CMakeLists.txt index 488c8da59..aaab6ebe4 100644 --- a/src/Entities/CMakeLists.txt +++ b/src/Entities/CMakeLists.txt @@ -17,6 +17,7 @@ SET (SRCS GhastFireballEntity.cpp HangingEntity.cpp ItemFrame.cpp + LeashKnot.cpp Minecart.cpp Painting.cpp Pawn.cpp @@ -45,6 +46,7 @@ SET (HDRS GhastFireballEntity.h HangingEntity.h ItemFrame.h + LeashKnot.h Minecart.h Painting.h Pawn.h diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index a38a6552d..56f7b33a3 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -159,6 +159,15 @@ bool cEntity::Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld) // Spawn the entity on the clients: a_EntityWorld.BroadcastSpawnEntity(*this); + // If has any mob leashed broadcast every leashed entity to this + if (HasAnyMobLeashed()) + { + for (auto LeashedMob : m_LeashedMobs) + { + m_World->BroadcastLeashEntity(*LeashedMob, *this); + } + } + return true; } @@ -218,6 +227,12 @@ void cEntity::Destroy(bool a_ShouldBroadcast) ASSERT(GetParentChunk() != nullptr); SetIsTicking(false); + // Unleash leashed mobs + while (!m_LeashedMobs.empty()) + { + m_LeashedMobs.front()->Unleash(true, true); + } + if (a_ShouldBroadcast) { m_World->BroadcastDestroyEntity(*this); @@ -2195,3 +2210,24 @@ void cEntity::SetPosition(const Vector3d & a_Position) + +void cEntity::AddLeashedMob(cMonster * a_Monster) +{ + // Not there already + ASSERT(std::find(m_LeashedMobs.begin(), m_LeashedMobs.end(), a_Monster) == m_LeashedMobs.end()); + + m_LeashedMobs.push_back(a_Monster); +} + + + + +void cEntity::RemoveLeashedMob(cMonster * a_Monster) +{ + ASSERT(a_Monster->GetLeashedTo() == this); + + // Must exists + ASSERT(std::find(m_LeashedMobs.begin(), m_LeashedMobs.end(), a_Monster) != m_LeashedMobs.end()); + + m_LeashedMobs.remove(a_Monster); +} diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 8f433b816..db147f3fc 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -45,6 +45,7 @@ class cWorld; class cClientHandle; class cPlayer; class cChunk; +class cMonster; @@ -87,6 +88,7 @@ public: etFloater, etItemFrame, etPainting, + etLeashKnot, // Common variations etMob = etMonster, // DEPRECATED, use etMonster instead! @@ -176,6 +178,7 @@ public: bool IsExpOrb (void) const { return (m_EntityType == etExpOrb); } bool IsFloater (void) const { return (m_EntityType == etFloater); } bool IsItemFrame (void) const { return (m_EntityType == etItemFrame); } + bool IsLeashKnot (void) const { return (m_EntityType == etLeashKnot); } bool IsPainting (void) const { return (m_EntityType == etPainting); } /** Returns true if the entity is of the specified class or a subclass (cPawn's IsA("cEntity") returns true) */ @@ -267,7 +270,7 @@ public: bool IsTicking(void) const; /** Destroys the entity and schedules it for memory freeing; if a_ShouldBroadcast is set to true, broadcasts the DestroyEntity packet */ - void Destroy(bool a_ShouldBroadcast = true); + virtual void Destroy(bool a_ShouldBroadcast = true); /** Makes this pawn take damage from an attack by a_Attacker. Damage values are calculated automatically and DoTakeDamage() called */ void TakeDamage(cEntity & a_Attacker); @@ -519,6 +522,15 @@ public: /** Set the entity's status to either ticking or not ticking. */ void SetIsTicking(bool a_IsTicking); + /** Adds a mob to the leashed list of mobs */ + void AddLeashedMob(cMonster * a_Monster); + + /** Removes a mob from the leashed list of mobs */ + void RemoveLeashedMob(cMonster * a_Monster); + + /** Returs whether the entity has any mob leashed to */ + bool HasAnyMobLeashed() const { return m_LeashedMobs.size() > 0; } + protected: /** Structure storing the portal delay timer and cooldown boolean */ struct sPortalCooldownData @@ -668,6 +680,12 @@ private: /** If a player hit a entity, the entity receive a invulnerable of 10 ticks. While this ticks, a player can't hit this entity. */ int m_InvulnerableTicks; + + typedef std::list cMonsterList; + + /** List of leashed mobs to this entity */ + cMonsterList m_LeashedMobs; + } ; // tolua_export diff --git a/src/Entities/LeashKnot.cpp b/src/Entities/LeashKnot.cpp new file mode 100644 index 000000000..52bb1b4b3 --- /dev/null +++ b/src/Entities/LeashKnot.cpp @@ -0,0 +1,185 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "LeashKnot.h" +#include "ClientHandle.h" +#include "Player.h" +#include "Mobs/Monster.h" +#include "BoundingBox.h" + +// Ticks to wait in Tick function to optimize calculations +#define TICK_STEP 10 + + + + + +cLeashKnot::cLeashKnot(eBlockFace a_BlockFace, double a_X, double a_Y, double a_Z) : + cHangingEntity(etLeashKnot, a_BlockFace, a_X, a_Y, a_Z), + m_ShouldSelfDestroy(false), + m_TicksToSelfDestroy(20 * 1) +{ +} + + + + + +void cLeashKnot::OnRightClicked(cPlayer & a_Player) +{ + super::OnRightClicked(a_Player); + + TiePlayersLeashedMobs(a_Player, true); + + GetWorld()->BroadcastEntityMetadata(*this); // Update clients +} + + + + + +void cLeashKnot::TiePlayersLeashedMobs(cPlayer & a_Player, bool a_ShouldBroadCast) +{ + // Check leashed nearby mobs to tie them to this knot + class LookForLeasheds : public cEntityCallback + { + public: + cLeashKnot * m_Knot; + cPlayer * m_Player; + bool m_ShouldBroadcast; + + LookForLeasheds(cLeashKnot * a_Knot, cPlayer * a_PlayerLeashedTo, bool a_ShouldBroadcast) : + m_Knot(a_Knot), + m_Player(a_PlayerLeashedTo), + m_ShouldBroadcast(a_ShouldBroadcast) + { + } + + virtual bool Item(cEntity * a_Entity) override + { + // If the entity is not a monster skip it + if (a_Entity->GetEntityType() != cEntity::eEntityType::etMonster) + { + return false; + } + + cMonster * PotentialLeashed = static_cast(a_Entity); + + // If can't be leashed skip it + if (!PotentialLeashed->CanBeLeashed()) + { + return false; + } + + // If it's not leashed to the player skip it + if ( + !PotentialLeashed->IsLeashed() || + !PotentialLeashed->GetLeashedTo()->IsPlayer() || + (PotentialLeashed->GetLeashedTo()->GetUniqueID() != m_Player->GetUniqueID()) + ) + { + return false; + } + + // All conditions met, unleash from player and leash to fence + PotentialLeashed->Unleash(false, false); + PotentialLeashed->LeashTo(m_Knot, m_ShouldBroadcast); + return false; + } + } LookForLeashedsCallback(this, &a_Player, a_ShouldBroadCast); + + // taking world from player (instead from this) because this can be called before entity was initialized + a_Player.GetWorld()->ForEachEntityInBox(cBoundingBox(GetPosition(), 8, 8, -4), LookForLeashedsCallback); +} + + + + + + +void cLeashKnot::KilledBy(TakeDamageInfo & a_TDI) +{ + super::KilledBy(a_TDI); + m_World->BroadcastSoundEffect("entity.leashknot.break", GetPosX(), GetPosY(), GetPosZ(), 1, 1); + Destroy(); + return; +} + + + + + +void cLeashKnot::GetDrops(cItems & a_Items, cEntity * a_Killer) +{ + if ((a_Killer != nullptr) && a_Killer->IsPlayer() && !static_cast(a_Killer)->IsGameModeCreative()) + { + a_Items.push_back(cItem(E_ITEM_LEASH)); + } +} + + + + + +void cLeashKnot::SpawnOn(cClientHandle & a_ClientHandle) +{ + super::SpawnOn(a_ClientHandle); + a_ClientHandle.SendSpawnObject(*this, 77, GetProtocolFacing(), static_cast(GetYaw()), static_cast(GetPitch())); + a_ClientHandle.SendEntityMetadata(*this); +} + + + + +void cLeashKnot::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + m_TicksAlive++; + + if ((m_TicksAlive % TICK_STEP) != 0) + { + return; + } + + if (m_ShouldSelfDestroy) + { + m_TicksToSelfDestroy -= TICK_STEP; + + if (m_TicksToSelfDestroy <= 0) + { + Destroy(); + m_World->BroadcastSoundEffect("entity.leashknot.break", GetPosX(), GetPosY(), GetPosZ(), 1, 1); + } + } +} + + + + + +cLeashKnot * cLeashKnot::FindKnotAtPos(cWorldInterface & a_WorldInterface, Vector3i a_BlockPos) +{ + class LookForKnot : public cEntityCallback + { + public: + cLeashKnot * m_LeashKnot = nullptr; + + virtual bool Item(cEntity * a_Entity) override + { + if (a_Entity->IsLeashKnot()) + { + m_LeashKnot = reinterpret_cast(a_Entity); + return true; + } + return false; + } + + } CallbackFindKnot; + + a_WorldInterface.ForEachEntityInBox(cBoundingBox(a_BlockPos, 0.5, 1), CallbackFindKnot); + + return CallbackFindKnot.m_LeashKnot; +} + + + + diff --git a/src/Entities/LeashKnot.h b/src/Entities/LeashKnot.h new file mode 100644 index 000000000..1a854ef34 --- /dev/null +++ b/src/Entities/LeashKnot.h @@ -0,0 +1,50 @@ + +#pragma once + +#include "HangingEntity.h" + + +class cWorldInterface; + + + + +// tolua_begin +class cLeashKnot : + public cHangingEntity +{ + typedef cHangingEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cLeashKnot) + + cLeashKnot(eBlockFace a_BlockFace, double a_X, double a_Y, double a_Z); + + /** Looks for mobs leashed to a player and ties them to this knot */ + void TiePlayersLeashedMobs(cPlayer & a_Player, bool a_ShouldBroadCast); + + void SetShouldSelfDestroy() { m_ShouldSelfDestroy = true; } + + /** Returns the leash knot entity representing the knot at the specified position. Returns nullptr if there's no knot. */ + static cLeashKnot * FindKnotAtPos(cWorldInterface & a_WorldInterface, Vector3i a_BlockPos); + +private: + + /** When a fence is destroyed, the knot on it gets destroyed after a while. This flag turns on the countdown to self destroy. */ + bool m_ShouldSelfDestroy; + int m_TicksToSelfDestroy; + + virtual void OnRightClicked(cPlayer & a_Player) override; + virtual void KilledBy(TakeDamageInfo & a_TDI) override; + virtual void GetDrops(cItems & a_Items, cEntity * a_Killer) override; + virtual void SpawnOn(cClientHandle & a_ClientHandle) override; + virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + +}; // tolua_export + + + + diff --git a/src/Items/ItemHandler.cpp b/src/Items/ItemHandler.cpp index 6f64fb25b..e0c5bb56c 100644 --- a/src/Items/ItemHandler.cpp +++ b/src/Items/ItemHandler.cpp @@ -614,6 +614,7 @@ char cItemHandler::GetMaxStackSize(void) case E_ITEM_IRON: return 64; case E_ITEM_IRON_NUGGET: return 64; case E_ITEM_ITEM_FRAME: return 64; + case E_ITEM_LEAD: return 64; case E_ITEM_LEATHER: return 64; case E_ITEM_MAGMA_CREAM: return 64; case E_ITEM_MAP: return 64; diff --git a/src/Mobs/Creeper.cpp b/src/Mobs/Creeper.cpp index da4270af9..84b68cf7c 100644 --- a/src/Mobs/Creeper.cpp +++ b/src/Mobs/Creeper.cpp @@ -147,6 +147,8 @@ bool cCreeper::Attack(std::chrono::milliseconds a_Dt) void cCreeper::OnRightClicked(cPlayer & a_Player) { + super::OnRightClicked(a_Player); + if ((a_Player.GetEquippedItem().m_ItemType == E_ITEM_FLINT_AND_STEEL)) { if (!a_Player.IsGameModeCreative()) diff --git a/src/Mobs/Horse.cpp b/src/Mobs/Horse.cpp index 24287ecc8..13630b0e3 100644 --- a/src/Mobs/Horse.cpp +++ b/src/Mobs/Horse.cpp @@ -132,24 +132,28 @@ void cHorse::OnRightClicked(cPlayer & a_Player) } else if (a_Player.GetEquippedItem().IsEmpty()) { - if (m_Attachee != nullptr) + // Check if leashed / unleashed to player before try to ride + if (!m_IsLeashActionJustDone) { - if (m_Attachee->GetUniqueID() == a_Player.GetUniqueID()) + if (m_Attachee != nullptr) { - a_Player.Detach(); - return; - } + if (m_Attachee->GetUniqueID() == a_Player.GetUniqueID()) + { + a_Player.Detach(); + return; + } - if (m_Attachee->IsPlayer()) - { - return; + if (m_Attachee->IsPlayer()) + { + return; + } + + m_Attachee->Detach(); } - m_Attachee->Detach(); + m_TameAttemptTimes++; + a_Player.AttachTo(this); } - - m_TameAttemptTimes++; - a_Player.AttachTo(this); } else { diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 8077e41d6..d1c2413c3 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -11,11 +11,19 @@ #include "../Entities/Player.h" #include "../Entities/ExpOrb.h" #include "../MonsterConfig.h" +#include "BoundingBox.h" #include "../Chunk.h" #include "../FastRandom.h" #include "PathFinder.h" +#include "../Entities/LeashKnot.h" + + + + +// Ticks to wait to do leash calculations +#define LEASH_ACTIONS_TICK_STEP 10 @@ -103,6 +111,10 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_Age(1) , m_AgingTimer(20 * 60 * 20) // about 20 minutes , m_WasLastTargetAPlayer(false) + , m_LeashedTo(nullptr) + , m_LeashToPos(nullptr) + , m_IsLeashActionJustDone(false) + , m_CanBeLeashed(GetMobFamily() == eFamily::mfPassive) , m_Target(nullptr) { if (!a_ConfigName.empty()) @@ -124,6 +136,27 @@ cMonster::~cMonster() +void cMonster::Destroy(bool a_ShouldBroadcast) +{ + if (IsLeashed()) + { + cEntity * LeashedTo = GetLeashedTo(); + Unleash(false, a_ShouldBroadcast); + + // Remove leash knot if there are no more mobs leashed to + if (!LeashedTo->HasAnyMobLeashed() && LeashedTo->IsLeashKnot()) + { + LeashedTo->Destroy(); + } + } + + super::Destroy(a_ShouldBroadcast); +} + + + + + void cMonster::Destroyed() { SetTarget(nullptr); // Tell them we're no longer targeting them. @@ -137,6 +170,11 @@ void cMonster::Destroyed() void cMonster::SpawnOn(cClientHandle & a_Client) { a_Client.SendSpawnMob(*this); + + if (IsLeashed()) + { + a_Client.SendLeashEntity(*this, *this->GetLeashedTo()); + } } @@ -201,6 +239,16 @@ void cMonster::MoveToWayPoint(cChunk & a_Chunk) AddSpeedX(Distance.x); AddSpeedZ(Distance.z); } + + // Speed up leashed mobs getting far from player + if (IsLeashed() && GetLeashedTo()->IsPlayer()) + { + Distance = GetLeashedTo()->GetPosition() - GetPosition(); + Distance.Normalize(); + AddSpeedX(Distance.x); + AddSpeedZ(Distance.z); + } + } @@ -283,7 +331,7 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) bool a_IsFollowingPath = false; if (m_PathfinderActivated) { - if (ReachedFinalDestination()) + if (ReachedFinalDestination() || (m_LeashToPos != nullptr)) { StopMovingToPosition(); // Simply sets m_PathfinderActivated to false. } @@ -351,6 +399,12 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) case ATTACKING: break; } // switch (m_EMState) + // Leash calculations + if ((m_TicksAlive % LEASH_ACTIONS_TICK_STEP) == 0) + { + CalcLeashActions(); + } + BroadcastMovementUpdate(); if (m_AgingTimer > 0) @@ -368,6 +422,39 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +void cMonster::CalcLeashActions() +{ + // This mob just spotted in the world and [m_LeashToPos not null] shows that should be leashed to a leash knot at m_LeashToPos. + // This keeps trying until knot is found. Leash knot may be in a different chunk that needn't or can't be loaded yet. + if (!IsLeashed() && (m_LeashToPos != nullptr)) + { + auto LeashKnot = cLeashKnot::FindKnotAtPos(*m_World, { FloorC(m_LeashToPos->x), FloorC(m_LeashToPos->y), FloorC(m_LeashToPos->z) }); + if (LeashKnot != nullptr) + { + LeashTo(LeashKnot); + SetLeashToPos(nullptr); + } + } + else if (IsLeashed()) // Mob is already leashed to an entity: follow it. + { + // TODO: leashed mobs in vanilla can move around up to 5 blocks distance from leash origin + MoveToPosition(m_LeashedTo->GetPosition()); + + // If distance to target > 10 break leash + Vector3f a_Distance(m_LeashedTo->GetPosition() - GetPosition()); + double Distance(a_Distance.Length()); + if (Distance > 10.0) + { + LOGD("Leash broken (distance)"); + Unleash(false); + } + } +} + + + + + void cMonster::SetPitchAndYawFromDestination(bool a_IsFollowingPath) { Vector3d BodyDistance; @@ -583,6 +670,26 @@ void cMonster::OnRightClicked(cPlayer & a_Player) a_Player.GetInventory().RemoveOneEquippedItem(); } } + + // Using leashes + m_IsLeashActionJustDone = false; + if (IsLeashed() && (GetLeashedTo() == &a_Player)) // a player can only unleash a mob leashed to him + { + Unleash(!a_Player.IsGameModeCreative()); + } + else if (IsLeashed()) + { + // Mob is already leashed but client anticipates the server action and draws a leash link, so we need to send current leash to cancel it + m_World->BroadcastLeashEntity(*this, *this->GetLeashedTo()); + } + else if (CanBeLeashed() && (EquippedItem.m_ItemType == E_ITEM_LEASH)) + { + if (!a_Player.IsGameModeCreative()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + } + LeashTo(&a_Player); + } } @@ -1295,3 +1402,67 @@ cMonster::eFamily cMonster::GetMobFamily(void) const { return FamilyFromType(m_MobType); } + + + + + +void cMonster::LeashTo(cEntity * a_Entity, bool a_ShouldBroadcast) +{ + // Do nothing if already leashed + if (m_LeashedTo != nullptr) + { + return; + } + + m_LeashedTo = a_Entity; + + a_Entity->AddLeashedMob(this); + + if (a_ShouldBroadcast) + { + m_World->BroadcastLeashEntity(*this, *a_Entity); + } + + m_IsLeashActionJustDone = true; +} + + + + + +void cMonster::Unleash(bool a_ShouldDropLeashPickup, bool a_ShouldBroadcast) +{ + // Do nothing if not leashed + if (m_LeashedTo == nullptr) + { + return; + } + + m_LeashedTo->RemoveLeashedMob(this); + + m_LeashedTo = nullptr; + + if (a_ShouldDropLeashPickup) + { + cItems Pickups; + Pickups.Add(cItem(E_ITEM_LEASH, 1, 0)); + GetWorld()->SpawnItemPickups(Pickups, GetPosX() + 0.5, GetPosY() + 0.5, GetPosZ() + 0.5); + } + + if (a_ShouldBroadcast) + { + m_World->BroadcastUnleashEntity(*this); + } + + m_IsLeashActionJustDone = true; +} + + + + + +void cMonster::Unleash(bool a_ShouldDropLeashPickup) +{ + Unleash(a_ShouldDropLeashPickup, true); +} diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 268db6168..ab5b2cf2f 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -43,6 +43,8 @@ public: virtual ~cMonster() override; + virtual void Destroy(bool a_ShouldBroadcast = true) override; + virtual void Destroyed() override; CLASS_PROTODEF(cMonster) @@ -71,6 +73,37 @@ public: virtual void CheckEventSeePlayer(cChunk & a_Chunk); virtual void EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk); + // tolua_begin + + /** Returns whether the mob can be leashed. */ + bool CanBeLeashed() const { return m_CanBeLeashed; } + + /** Sets whether the mob can be leashed, for extensibility in plugins */ + void SetCanBeLeashed(bool a_CanBeLeashed) { m_CanBeLeashed = a_CanBeLeashed; } + + /** Returns whether the monster is leashed to an entity. */ + bool IsLeashed() const { return (m_LeashedTo != nullptr); } + + /** Leash the monster to an entity. */ + void LeashTo(cEntity * a_Entity, bool a_ShouldBroadcast = true); + + /** Unleash the monster. Overload for the Unleash(bool, bool) function for plugins */ + void Unleash(bool a_ShouldDropLeashPickup); + + /** Returns the entity to where this mob is leashed, returns nullptr if it's not leashed */ + cEntity * GetLeashedTo() const { return m_LeashedTo; } + + // tolua_end + + /** Unleash the monster. */ + void Unleash(bool a_ShouldDropLeashPickup, bool a_ShouldBroadcast); + + /** Sets entity position to where is leashed this mob */ + void SetLeashToPos(Vector3d * pos) { m_LeashToPos = std::unique_ptr(pos); } + + /** Gets entity position to where mob should be leashed */ + Vector3d * GetLeashToPos() const { return m_LeashToPos.get(); } + /** Reads the monster configuration for the specified monster name and assigns it to this object. */ void GetMonsterConfig(const AString & a_Name); @@ -260,6 +293,18 @@ protected: bool m_WasLastTargetAPlayer; + /** Entity leashed to */ + cEntity * m_LeashedTo; + + /** Entity pos where this mob was leashed to. Used when deserializing the chunk in order to make the mob find the leash knot. */ + std::unique_ptr m_LeashToPos; + + /** Mob has ben leashed or unleashed in current player action. Avoids double actions on horses. */ + bool m_IsLeashActionJustDone; + + /** Determines whether a monster can be leashed */ + bool m_CanBeLeashed; + /** 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); @@ -281,4 +326,7 @@ private: it MUST be reset when the pointee changes worlds or is destroyed. */ cPawn * m_Target; + /** Leash calculations inside Tick function */ + void CalcLeashActions(); + } ; // tolua_export diff --git a/src/Mobs/PassiveMonster.cpp b/src/Mobs/PassiveMonster.cpp index 73defaf7f..a2089e13f 100644 --- a/src/Mobs/PassiveMonster.cpp +++ b/src/Mobs/PassiveMonster.cpp @@ -201,7 +201,7 @@ void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } } Callback(this); - m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), 8, 8), Callback); + m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), 8, 8, -4), Callback); } m_LoveTimer--; diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 18ede0640..54c5b7223 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -91,6 +91,7 @@ public: virtual void SendHideTitle (void) = 0; virtual void SendInventorySlot (char a_WindowID, short a_SlotNum, const cItem & a_Item) = 0; virtual void SendKeepAlive (UInt32 a_PingID) = 0; + virtual void SendLeashEntity (const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) = 0; virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) = 0; virtual void SendLoginSuccess (void) = 0; virtual void SendMapData (const cMap & a_Map, int a_DataStartX, int a_DataStartY) = 0; @@ -134,6 +135,7 @@ public: virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) = 0; virtual void SendTitleTimes (int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) = 0; virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle) = 0; + virtual void SendUnleashEntity (const cEntity & a_Entity) = 0; virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) = 0; virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) = 0; virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) = 0; diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index a65fb931e..cb2a4fc9d 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -468,6 +468,26 @@ void cProtocolRecognizer::SendKeepAlive(UInt32 a_PingID) +void cProtocolRecognizer::SendLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) +{ + ASSERT(m_Protocol != nullptr); + m_Protocol->SendLeashEntity(a_Entity, a_EntityLeashedTo); +} + + + + + +void cProtocolRecognizer::SendUnleashEntity(const cEntity & a_Entity) +{ + ASSERT(m_Protocol != nullptr); + m_Protocol->SendUnleashEntity(a_Entity); +} + + + + + void cProtocolRecognizer::SendLogin(const cPlayer & a_Player, const cWorld & a_World) { ASSERT(m_Protocol != nullptr); diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index 4c338a5b8..295c6db16 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -87,6 +87,7 @@ public: virtual void SendHideTitle (void) override; virtual void SendInventorySlot (char a_WindowID, short a_SlotNum, const cItem & a_Item) override; virtual void SendKeepAlive (UInt32 a_PingID) override; + virtual void SendLeashEntity (const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) override; virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) override; virtual void SendLoginSuccess (void) override; virtual void SendMapData (const cMap & a_Map, int a_DataStartX, int a_DataStartY) override; @@ -130,6 +131,7 @@ public: virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override; virtual void SendTitleTimes (int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) override; virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle) override; + virtual void SendUnleashEntity (const cEntity & a_Entity) override; virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) override; virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override; virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override; diff --git a/src/Protocol/Protocol_1_12.cpp b/src/Protocol/Protocol_1_12.cpp index b9eb1801d..0dba45e15 100644 --- a/src/Protocol/Protocol_1_12.cpp +++ b/src/Protocol/Protocol_1_12.cpp @@ -1252,6 +1252,30 @@ void cProtocol_1_12::SendScoreUpdate(const AString & a_Objective, const AString +void cProtocol_1_12::SendLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) +{ + ASSERT(m_State == 3); // In game mode? + cPacketizer Pkt(*this, 0x3c); // Set Attach Entity packet + Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt32(a_EntityLeashedTo.GetUniqueID()); +} + + + + + +void cProtocol_1_12::SendUnleashEntity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + cPacketizer Pkt(*this, 0x3c); // Set Attach Entity packet + Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEInt32(-1); // Unleash a_Entity +} + + + + + void cProtocol_1_12::SendLogin(const cPlayer & a_Player, const cWorld & a_World) { // Send the Join Game packet: diff --git a/src/Protocol/Protocol_1_12.h b/src/Protocol/Protocol_1_12.h index 53ff05aa6..e1fc12149 100644 --- a/src/Protocol/Protocol_1_12.h +++ b/src/Protocol/Protocol_1_12.h @@ -48,6 +48,7 @@ public: virtual void SendExperience(void) override; virtual void SendHealth(void) override; virtual void SendHideTitle(void) override; + virtual void SendLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) override; virtual void SendLogin(const cPlayer & a_Player, const cWorld & a_World) override; virtual void SendPlayerMaxSpeed(void) override; virtual void SendRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID) override; @@ -62,6 +63,7 @@ public: virtual void SendTimeUpdate(Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle) override; virtual void SendTitleTimes(int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) override; virtual void SendUpdateBlockEntity(cBlockEntity & a_BlockEntity) override; + virtual void SendUnleashEntity(const cEntity & a_Entity) override; protected: virtual bool HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType) override; virtual void HandlePacketAdvancementTab(cByteBuffer & a_ByteBuffer); diff --git a/src/Protocol/Protocol_1_8.cpp b/src/Protocol/Protocol_1_8.cpp index 873ed2902..f278437ff 100644 --- a/src/Protocol/Protocol_1_8.cpp +++ b/src/Protocol/Protocol_1_8.cpp @@ -629,6 +629,34 @@ void cProtocol_1_8_0::SendKeepAlive(UInt32 a_PingID) +void cProtocol_1_8_0::SendLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1b); // Attach Entity packet + Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt32(a_EntityLeashedTo.GetUniqueID()); + Pkt.WriteBool(true); +} + + + + + +void cProtocol_1_8_0::SendUnleashEntity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x1b); // Attach Entity packet + Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEInt32(-1); + Pkt.WriteBool(true); +} + + + + + void cProtocol_1_8_0::SendLogin(const cPlayer & a_Player, const cWorld & a_World) { // Send the Join Game packet: diff --git a/src/Protocol/Protocol_1_8.h b/src/Protocol/Protocol_1_8.h index 0adf7a93c..b04e5c5f0 100644 --- a/src/Protocol/Protocol_1_8.h +++ b/src/Protocol/Protocol_1_8.h @@ -80,6 +80,7 @@ public: virtual void SendHideTitle (void) override; virtual void SendInventorySlot (char a_WindowID, short a_SlotNum, const cItem & a_Item) override; virtual void SendKeepAlive (UInt32 a_PingID) override; + virtual void SendLeashEntity (const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) override; virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) override; virtual void SendLoginSuccess (void) override; virtual void SendMapData (const cMap & a_Map, int a_DataStartX, int a_DataStartY) override; @@ -123,6 +124,7 @@ public: virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override; virtual void SendTitleTimes (int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) override; virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle) override; + virtual void SendUnleashEntity (const cEntity & a_Entity) override; virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) override; virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override; virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override; diff --git a/src/Protocol/Protocol_1_9.cpp b/src/Protocol/Protocol_1_9.cpp index 56f41ec51..167bc4ddf 100644 --- a/src/Protocol/Protocol_1_9.cpp +++ b/src/Protocol/Protocol_1_9.cpp @@ -648,6 +648,30 @@ void cProtocol_1_9_0::SendKeepAlive(UInt32 a_PingID) +void cProtocol_1_9_0::SendLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) +{ + ASSERT(m_State == 3); // In game mode? + cPacketizer Pkt(*this, 0x3a); // Set Attach Entity packet + Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt32(a_EntityLeashedTo.GetUniqueID()); +} + + + + + +void cProtocol_1_9_0::SendUnleashEntity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + cPacketizer Pkt(*this, 0x3a); // Set Attach Entity packet + Pkt.WriteBEUInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEInt32(-1); // Unleash a_Entity +} + + + + + void cProtocol_1_9_0::SendLogin(const cPlayer & a_Player, const cWorld & a_World) { // Send the Join Game packet: diff --git a/src/Protocol/Protocol_1_9.h b/src/Protocol/Protocol_1_9.h index fcb261575..b4fdc7f67 100644 --- a/src/Protocol/Protocol_1_9.h +++ b/src/Protocol/Protocol_1_9.h @@ -86,6 +86,7 @@ public: virtual void SendHideTitle (void) override; virtual void SendInventorySlot (char a_WindowID, short a_SlotNum, const cItem & a_Item) override; virtual void SendKeepAlive (UInt32 a_PingID) override; + virtual void SendLeashEntity (const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) override; virtual void SendLogin (const cPlayer & a_Player, const cWorld & a_World) override; virtual void SendLoginSuccess (void) override; virtual void SendMapData (const cMap & a_Map, int a_DataStartX, int a_DataStartY) override; @@ -129,6 +130,7 @@ public: virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override; virtual void SendTitleTimes (int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) override; virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle) override; + virtual void SendUnleashEntity (const cEntity & a_Entity) override; virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) override; virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override; virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override; diff --git a/src/World.cpp b/src/World.cpp index 738761aa6..8d8ba90a5 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -2503,6 +2503,24 @@ void cWorld::BroadcastDetachEntity(const cEntity & a_Entity, const cEntity & a_P +void cWorld::BroadcastLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo) +{ + m_ChunkMap->BroadcastLeashEntity(a_Entity, a_EntityLeashedTo); +} + + + + + +void cWorld::BroadcastUnleashEntity(const cEntity & a_Entity) +{ + m_ChunkMap->BroadcastUnleashEntity(a_Entity); +} + + + + + void cWorld::BroadcastEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude) { m_ChunkMap->BroadcastEntityEffect(a_Entity, a_EffectID, a_Amplifier, a_Duration, a_Exclude); diff --git a/src/World.h b/src/World.h index 8ff64046d..ed3bf9919 100644 --- a/src/World.h +++ b/src/World.h @@ -196,6 +196,7 @@ public: void BroadcastEntityStatus (const cEntity & a_Entity, char a_Status, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityVelocity (const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); virtual void BroadcastEntityAnimation (const cEntity & a_Entity, char a_Animation, const cClientHandle * a_Exclude = nullptr) override; // tolua_export + void BroadcastLeashEntity (const cEntity & a_Entity, const cEntity & a_EntityLeashedTo); void BroadcastPlayerListAddPlayer (const cPlayer & a_Player, const cClientHandle * a_Exclude = nullptr); void BroadcastPlayerListRemovePlayer (const cPlayer & a_Player, const cClientHandle * a_Exclude = nullptr); void BroadcastPlayerListUpdateGameMode (const cPlayer & a_Player, const cClientHandle * a_Exclude = nullptr); @@ -211,6 +212,7 @@ public: void BroadcastTeleportEntity (const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); void BroadcastThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude = nullptr); void BroadcastTimeUpdate (const cClientHandle * a_Exclude = nullptr); + void BroadcastUnleashEntity (const cEntity & a_Entity); virtual void BroadcastUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) override; void BroadcastWeather (eWeather a_Weather, const cClientHandle * a_Exclude = nullptr); @@ -307,7 +309,7 @@ public: /** Calls the callback for each entity that has a nonempty intersection with the specified boundingbox. Returns true if all entities processed, false if the callback aborted by returning true. If any chunk in the box is missing, ignores the entities in that chunk silently. */ - bool ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_Callback); // Exported in ManualBindings.cpp + virtual bool ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_Callback) override; // Exported in ManualBindings.cpp /** Calls the callback if the entity with the specified ID is found, with the entity object as the callback param. Returns true if entity found and callback returned false. */ diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index 480558fa3..62f981eb4 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -37,6 +37,7 @@ #include "../Entities/ExpOrb.h" #include "../Entities/HangingEntity.h" #include "../Entities/ItemFrame.h" +#include "../Entities/LeashKnot.h" #include "../Entities/Painting.h" #include "../Mobs/IncludeAllMonsters.h" @@ -574,6 +575,33 @@ void cNBTChunkSerializer::AddMonsterEntity(cMonster * a_Monster) m_Writer.AddByte("CanPickUpLoot", (a_Monster->CanPickUpLoot())? 1 : 0); m_Writer.AddString("CustomName", a_Monster->GetCustomName()); m_Writer.AddByte("CustomNameVisible", static_cast(a_Monster->IsCustomNameAlwaysVisible())); + + // Mob was leashed + if (a_Monster->IsLeashed() || (a_Monster->GetLeashToPos() != nullptr)) + { + m_Writer.AddByte("Leashed", 1); + + const Vector3d * LeashedToPos = nullptr; + + if (a_Monster->GetLeashToPos() != nullptr) + { + LeashedToPos = a_Monster->GetLeashToPos(); + } + else if (a_Monster->GetLeashedTo()->IsLeashKnot()) + { + LeashedToPos = & a_Monster->GetLeashedTo()->GetPosition(); + } + + if (LeashedToPos != nullptr) + { + m_Writer.BeginCompound("Leash"); + m_Writer.AddDouble("X", LeashedToPos->x); + m_Writer.AddDouble("Y", LeashedToPos->y); + m_Writer.AddDouble("Z", LeashedToPos->z); + m_Writer.EndCompound(); + } + } + switch (a_Monster->GetMobType()) { case mtBat: @@ -850,8 +878,13 @@ void cNBTChunkSerializer::AddItemFrameEntity(cItemFrame * a_ItemFrame) m_Writer.EndCompound(); } - - +void cNBTChunkSerializer::AddLeashKnotEntity(cLeashKnot * a_LeashKnot) +{ + m_Writer.BeginCompound(""); + AddBasicEntity(a_LeashKnot, "LeashKnot"); + AddHangingEntity(a_LeashKnot); + m_Writer.EndCompound(); +} void cNBTChunkSerializer::AddPaintingEntity(cPainting * a_Painting) @@ -965,6 +998,7 @@ void cNBTChunkSerializer::Entity(cEntity * a_Entity) case cEntity::etTNT: AddTNTEntity (reinterpret_cast (a_Entity)); break; case cEntity::etExpOrb: AddExpOrbEntity (reinterpret_cast (a_Entity)); break; case cEntity::etItemFrame: AddItemFrameEntity (reinterpret_cast (a_Entity)); break; + case cEntity::etLeashKnot: AddLeashKnotEntity (reinterpret_cast (a_Entity)); break; case cEntity::etPainting: AddPaintingEntity (reinterpret_cast (a_Entity)); break; case cEntity::etPlayer: return; // Players aren't saved into the world case cEntity::etFloater: return; // Floaters aren't saved either diff --git a/src/WorldStorage/NBTChunkSerializer.h b/src/WorldStorage/NBTChunkSerializer.h index a55e519a7..3637ea655 100644 --- a/src/WorldStorage/NBTChunkSerializer.h +++ b/src/WorldStorage/NBTChunkSerializer.h @@ -47,6 +47,7 @@ class cTNTEntity; class cExpOrb; class cHangingEntity; class cItemFrame; +class cLeashKnot; class cPainting; @@ -123,6 +124,7 @@ protected: void AddTNTEntity (cTNTEntity * a_TNT); void AddExpOrbEntity (cExpOrb * a_ExpOrb); void AddItemFrameEntity (cItemFrame * a_ItemFrame); + void AddLeashKnotEntity (cLeashKnot * a_LeashKnot); void AddPaintingEntity (cPainting * a_Painting); void AddMinecartChestContents(cMinecartWithChest * a_Minecart); diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index 77d1e46b8..7aa3eb0cd 100755 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -50,10 +50,12 @@ #include "../Entities/ExpOrb.h" #include "../Entities/HangingEntity.h" #include "../Entities/ItemFrame.h" +#include "../Entities/LeashKnot.h" #include "../Entities/Painting.h" #include "../Protocol/MojangAPI.h" #include "Server.h" +#include "BoundingBox.h" @@ -1540,6 +1542,8 @@ void cWSSAnvil::LoadEntityFromNBT(cEntityList & a_Entities, const cParsedNBT & a { "minecraft:xp_orb", &cWSSAnvil::LoadExpOrbFromNBT }, { "ItemFrame", &cWSSAnvil::LoadItemFrameFromNBT }, { "minecraft:item_frame", &cWSSAnvil::LoadItemFrameFromNBT }, + { "LeashKnot", &cWSSAnvil::LoadLeashKnotFromNBT }, + { "minecraft:leash_knot", &cWSSAnvil::LoadLeashKnotFromNBT }, { "Arrow", &cWSSAnvil::LoadArrowFromNBT }, { "minecraft:arrow", &cWSSAnvil::LoadArrowFromNBT }, { "SplashPotion", &cWSSAnvil::LoadSplashPotionFromNBT }, @@ -1958,6 +1962,24 @@ void cWSSAnvil::LoadItemFrameFromNBT(cEntityList & a_Entities, const cParsedNBT +void cWSSAnvil::LoadLeashKnotFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx) +{ + auto LeashKnot = cpp14::make_unique(BLOCK_FACE_NONE, 0.0, 0.0, 0.0); + + if (!LoadEntityBaseFromNBT(*LeashKnot.get(), a_NBT, a_TagIdx)) + { + return; + } + + LoadHangingFromNBT(*LeashKnot.get(), a_NBT, a_TagIdx); + + a_Entities.emplace_back(std::move(LeashKnot)); +} + + + + + void cWSSAnvil::LoadPaintingFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx) { // Load painting name: @@ -3182,6 +3204,13 @@ bool cWSSAnvil::LoadMonsterBaseFromNBT(cMonster & a_Monster, const cParsedNBT & a_Monster.SetCustomNameAlwaysVisible(CustomNameVisible); } + // Leashed to a knot + int LeashedIdx = a_NBT.FindChildByName(a_TagIdx, "Leashed"); + if ((LeashedIdx >= 0) && a_NBT.GetByte(LeashedIdx)) + { + LoadLeashToPosition(a_Monster, a_NBT, a_TagIdx); + } + return true; } @@ -3189,6 +3218,54 @@ bool cWSSAnvil::LoadMonsterBaseFromNBT(cMonster & a_Monster, const cParsedNBT & +void cWSSAnvil::LoadLeashToPosition(cMonster & a_Monster, const cParsedNBT & a_NBT, int a_TagIdx) +{ + int LeashIdx = a_NBT.FindChildByName(a_TagIdx, "Leash"); + if (LeashIdx < 0) + { + return; + } + + double PosX = 0.0, PosY = 0.0, PosZ = 0.0; + bool KnotPosPresent = true; + int LeashDataLine = a_NBT.FindChildByName(LeashIdx, "X"); + if (LeashDataLine >= 0) + { + PosX = a_NBT.GetDouble(LeashDataLine); + } + else + { + KnotPosPresent = false; + } + LeashDataLine = a_NBT.FindChildByName(LeashIdx, "Y"); + if (LeashDataLine >= 0) + { + PosY = a_NBT.GetDouble(LeashDataLine); + } + else + { + KnotPosPresent = false; + } + LeashDataLine = a_NBT.FindChildByName(LeashIdx, "Z"); + if (LeashDataLine >= 0) + { + PosZ = a_NBT.GetDouble(LeashDataLine); + } + else + { + KnotPosPresent = false; + } + if (KnotPosPresent) + { + // Set leash pos for the mob + a_Monster.SetLeashToPos(new Vector3d(PosX, PosY, PosZ)); + } +} + + + + + bool cWSSAnvil::LoadProjectileBaseFromNBT(cProjectileEntity & a_Entity, const cParsedNBT & a_NBT, int a_TagIdx) { if (!LoadEntityBaseFromNBT(a_Entity, a_NBT, a_TagIdx)) diff --git a/src/WorldStorage/WSSAnvil.h b/src/WorldStorage/WSSAnvil.h index 454e6f73d..a53d8d8c4 100755 --- a/src/WorldStorage/WSSAnvil.h +++ b/src/WorldStorage/WSSAnvil.h @@ -179,6 +179,7 @@ protected: void LoadExpOrbFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx); void LoadHangingFromNBT (cHangingEntity & a_Hanging, const cParsedNBT & a_NBT, int a_TagIdx); void LoadItemFrameFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx); + void LoadLeashKnotFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx); void LoadPaintingFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx); void LoadOldMinecartFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx); @@ -238,6 +239,9 @@ protected: /** Loads monster common data from the NBT compound; returns true if successful */ bool LoadMonsterBaseFromNBT(cMonster & a_Monster, const cParsedNBT & a_NBT, int a_TagIdx); + /** Loads the position to where is leashed the monster */ + void LoadLeashToPosition(cMonster & a_Monster, const cParsedNBT & a_NBT, int a_TagIdx); + /** Loads projectile common data from the NBT compound; returns true if successful */ bool LoadProjectileBaseFromNBT(cProjectileEntity & a_Entity, const cParsedNBT & a_NBT, int a_TagIx); -- cgit v1.2.3