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/Creeper.cpp | 2 + src/Mobs/Horse.cpp | 28 ++++--- src/Mobs/Monster.cpp | 173 +++++++++++++++++++++++++++++++++++++++++++- src/Mobs/Monster.h | 48 ++++++++++++ src/Mobs/PassiveMonster.cpp | 2 +- 5 files changed, 239 insertions(+), 14 deletions(-) (limited to 'src/Mobs') 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--; -- cgit v1.2.3