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) --- src/Mobs/Monster.cpp | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 1 deletion(-) (limited to 'src/Mobs/Monster.cpp') 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); +} -- cgit v1.2.3