1
0
Fork 0

Fully implemented leashes (#3798)

This commit is contained in:
Pablo Beltrán 2017-08-21 10:46:41 +02:00 committed by Mattes D
parent f81e6f6b6d
commit b18f6637b6
42 changed files with 1022 additions and 21 deletions

View File

@ -45,6 +45,7 @@ SamJBarney
Schwertspize
Seadragon91 (Lukas Pioch)
Sofapriester
Spekdrum (Pablo Beltran)
SphinxC0re
structinf (xdot)
sweetgiorni

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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");

View File

@ -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,

View File

@ -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<cLeashKnot>(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();
}
}
};

View File

@ -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<cPlayer> & 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. */

View File

@ -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)

View File

@ -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);

View File

@ -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)
{

View File

@ -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);

View File

@ -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)
{

View File

@ -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 */

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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);
}

View File

@ -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<cMonster *> cMonsterList;
/** List of leashed mobs to this entity */
cMonsterList m_LeashedMobs;
} ; // tolua_export

185
src/Entities/LeashKnot.cpp Normal file
View File

@ -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<cMonster*>(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<cPlayer *>(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<Byte>(GetYaw()), static_cast<Byte>(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<cLeashKnot *>(a_Entity);
return true;
}
return false;
}
} CallbackFindKnot;
a_WorldInterface.ForEachEntityInBox(cBoundingBox(a_BlockPos, 0.5, 1), CallbackFindKnot);
return CallbackFindKnot.m_LeashKnot;
}

50
src/Entities/LeashKnot.h Normal file
View File

@ -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

View File

@ -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;

View File

@ -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())

View File

@ -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;
}
m_Attachee->Detach();
}
if (m_Attachee->IsPlayer())
{
return;
}
m_Attachee->Detach();
m_TameAttemptTimes++;
a_Player.AttachTo(this);
}
m_TameAttemptTimes++;
a_Player.AttachTo(this);
}
else
{

View File

@ -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);
}

View File

@ -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<Vector3d>(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<Vector3d> 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

View File

@ -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--;

View File

@ -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;

View File

@ -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);

View File

@ -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,