Fully implemented leashes (#3798)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user