diff options
Diffstat (limited to 'src/Mobs')
105 files changed, 3163 insertions, 1553 deletions
diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp deleted file mode 100644 index fec14e6e9..000000000 --- a/src/Mobs/AggressiveMonster.cpp +++ /dev/null @@ -1,109 +0,0 @@ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "AggressiveMonster.h" - -#include "../World.h" -#include "../Entities/Player.h" -#include "../LineBlockTracer.h" - - - - - -cAggressiveMonster::cAggressiveMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height) : - super(a_ConfigName, a_MobType, a_SoundHurt, a_SoundDeath, a_Width, a_Height) -{ - m_EMPersonality = AGGRESSIVE; -} - - - - - -// What to do if in Chasing State -void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - super::InStateChasing(a_Dt, a_Chunk); - - if (GetTarget() != nullptr) - { - MoveToPosition(GetTarget()->GetPosition()); - } -} - - - - - - -void cAggressiveMonster::EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk) -{ - if (!a_Player->CanMobsTarget()) - { - return; - } - - super::EventSeePlayer(a_Player, a_Chunk); - m_EMState = CHASING; -} - - - - - -void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - super::Tick(a_Dt, a_Chunk); - if (!IsTicking()) - { - // The base class tick destroyed us - return; - } - - if (m_EMState == CHASING) - { - CheckEventLostPlayer(); - } - else - { - CheckEventSeePlayer(a_Chunk); - } - - auto target = GetTarget(); - if (target == nullptr) - { - return; - } - - // TODO: Currently all mobs see through lava, but only Nether-native mobs should be able to. - Vector3d MyHeadPosition = GetPosition() + Vector3d(0, GetHeight(), 0); - Vector3d TargetPosition = target->GetPosition() + Vector3d(0, target->GetHeight(), 0); - if ( - TargetIsInRange() && - cLineBlockTracer::LineOfSightTrace(*GetWorld(), MyHeadPosition, TargetPosition, cLineBlockTracer::losAirWaterLava) && - (GetHealth() > 0.0) - ) - { - // Attack if reached destination, target isn't null, and have a clear line of sight to target (so won't attack through walls) - Attack(a_Dt); - } -} - - - - - -bool cAggressiveMonster::Attack(std::chrono::milliseconds a_Dt) -{ - if ((GetTarget() == nullptr) || (m_AttackCoolDownTicksLeft != 0)) - { - return false; - } - - // Setting this higher gives us more wiggle room for attackrate - ResetAttackCooldown(); - GetTarget()->TakeDamage(dtMobAttack, this, m_AttackDamage, 0); - - return true; -} diff --git a/src/Mobs/AggressiveMonster.h b/src/Mobs/AggressiveMonster.h deleted file mode 100644 index 9ab8df06f..000000000 --- a/src/Mobs/AggressiveMonster.h +++ /dev/null @@ -1,33 +0,0 @@ - -#pragma once - -#include "Monster.h" - - - - - -class cAggressiveMonster : - public cMonster -{ - typedef cMonster super; - -public: - - cAggressiveMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height); - - virtual void Tick (std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - virtual void InStateChasing(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - - - virtual void EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk) override; - - /** Try to perform attack - returns true if attack was deemed successful (hit player, fired projectile, creeper exploded, etc.) even if it didn't actually do damage - return false if e.g. the mob is still in cooldown from a previous attack */ - virtual bool Attack(std::chrono::milliseconds a_Dt); -} ; - - - - diff --git a/src/Mobs/Bat.cpp b/src/Mobs/Bat.cpp index e419ceb2d..317dc50dc 100644 --- a/src/Mobs/Bat.cpp +++ b/src/Mobs/Bat.cpp @@ -4,12 +4,14 @@ #include "Bat.h" #include "../Chunk.h" - cBat::cBat(void) : - super("Bat", mtBat, "entity.bat.hurt", "entity.bat.death", 0.5, 0.9) + super(mtBat, "entity.bat.hurt", "entity.bat.death", 0.5, 0.9) { SetGravity(-2.0f); SetAirDrag(0.05f); + m_EMPersonality = PASSIVE; + m_BehaviorWanderer.AttachToMonster(*this); + GetMonsterConfig("Bat"); } diff --git a/src/Mobs/Bat.h b/src/Mobs/Bat.h index 2da2dc3f2..cdda2e1c7 100644 --- a/src/Mobs/Bat.h +++ b/src/Mobs/Bat.h @@ -1,16 +1,16 @@ #pragma once -#include "PassiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorWanderer.h" class cBat : - public cPassiveMonster + public cMonster { - typedef cPassiveMonster super; + typedef cMonster super; public: cBat(void); @@ -18,6 +18,9 @@ public: CLASS_PROTODEF(cBat) bool IsHanging(void) const {return false; } + +private: + cBehaviorWanderer m_BehaviorWanderer; } ; diff --git a/src/Mobs/Behaviors/Behavior.cpp b/src/Mobs/Behaviors/Behavior.cpp new file mode 100644 index 000000000..86922427f --- /dev/null +++ b/src/Mobs/Behaviors/Behavior.cpp @@ -0,0 +1,102 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "Behavior.h" +#include "../Monster.h" + + + +bool cBehavior::IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + LOGD("ERROR: Probably forgot to implement cBehavior::IsControlDesired but implement cBehavior::Tick"); + return false; +} + + + + + +bool cBehavior::ControlStarting(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + return true; +} + + + + + +bool cBehavior::ControlEnding(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + return true; +} + + + + + +void cBehavior::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + LOGD("ERROR: Called a TICK on a behavior that doesn't have one."); + ASSERT(1 == 0); +} + + + + + +void cBehavior::PostTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + LOGD("ERROR: Called a PostTick on a behavior that doesn't have one."); + ASSERT(1 == 0); +} + + + + + +void cBehavior::PreTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + LOGD("ERROR: Called a PreTick on a behavior that doesn't have one."); + ASSERT(1 == 0); +} + + + + + +void cBehavior::OnRightClicked(cPlayer & a_Player) +{ + LOGD("ERROR: Called onRightClicked on a behavior that doesn't have one."); + ASSERT(1 == 0); +} + + + + + +void cBehavior::Destroyed() +{ + LOGD("ERROR: Called Destroyed on a behavior that doesn't have one."); + ASSERT(1 == 0); +} + + + + +void cBehavior::DoTakeDamage(TakeDamageInfo & a_TDI) +{ + UNUSED(a_TDI); + LOGD("ERROR: Called DoTakeDamage on a behavior that doesn't have one."); + ASSERT(1 == 0); +} diff --git a/src/Mobs/Behaviors/Behavior.h b/src/Mobs/Behaviors/Behavior.h new file mode 100644 index 000000000..27833cc9b --- /dev/null +++ b/src/Mobs/Behaviors/Behavior.h @@ -0,0 +1,30 @@ +#pragma once + +struct TakeDamageInfo; +class cChunk; +class cPlayer; +class cMonster; +class cPawn; +class cWorld; +class cItems; +class cEntity; +struct TakeDamageInfo; +#include <chrono> + +class cBehavior +{ +public: + // Tick-related + virtual bool IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + virtual bool ControlStarting(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + virtual bool ControlEnding(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + virtual void PostTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + virtual void PreTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + + // Other + virtual void OnRightClicked(cPlayer & a_Player); + virtual void Destroyed(); + virtual void DoTakeDamage(TakeDamageInfo & a_TDI); + virtual ~cBehavior() {} +}; diff --git a/src/Mobs/Behaviors/BehaviorAggressive.cpp b/src/Mobs/Behaviors/BehaviorAggressive.cpp new file mode 100644 index 000000000..8cbacf5fa --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAggressive.cpp @@ -0,0 +1,66 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorAggressive.h" +#include "BehaviorAttacker.h" +#include "../Monster.h" +#include "../../Chunk.h" +#include "../../Entities/Player.h" + + + +cBehaviorAggressive::cBehaviorAggressive(ShouldBeAggressiveFunction a_ShouldBeAggressiveFunction) + : m_ShouldBeAggressiveFunction(a_ShouldBeAggressiveFunction) + , m_ShouldBeAgressive(true) + , m_AgressionCheckCountdown(1) +{ + +} + +void cBehaviorAggressive::AttachToMonster(cMonster & a_Parent) +{ + m_Parent = &a_Parent; + m_Parent->AttachPreTickBehavior(this); +} + + +void cBehaviorAggressive::PreTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + + if (m_ShouldBeAggressiveFunction != nullptr) + { + if (--m_AgressionCheckCountdown == 0) + { + m_AgressionCheckCountdown = 40; + m_ShouldBeAgressive = m_ShouldBeAggressiveFunction(*this, *m_Parent, a_Chunk); + } + } + + if (!m_ShouldBeAgressive) + { + return; + } + + // Target something new if we have no target + cBehaviorAttacker * BehaviorAttacker = m_Parent->GetBehaviorAttacker(); + if ((BehaviorAttacker != nullptr) && (BehaviorAttacker->GetTarget() == nullptr)) + { + // mobTodo enhance this + BehaviorAttacker->SetTarget(FindNewTarget()); + } +} + + + + + +cPawn * cBehaviorAggressive::FindNewTarget() +{ + cPlayer * Closest = m_Parent->GetNearestPlayer(); + if ((Closest != nullptr) && (!Closest->CanMobsTarget())) + { + return nullptr; + } + return Closest; // May be null +} diff --git a/src/Mobs/Behaviors/BehaviorAggressive.h b/src/Mobs/Behaviors/BehaviorAggressive.h new file mode 100644 index 000000000..923d646e0 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAggressive.h @@ -0,0 +1,40 @@ +#pragma once + + +class cBehaviorAggressive; + +#include "Behavior.h" +#include <functional> + +/** The mob is agressive toward specific mobtypes, or toward the player. +This Behavior has a dependency on BehaviorAttacker. */ + +typedef std::function<bool(cBehaviorAggressive & a_Behavior, cMonster & a_Monster, cChunk & a_Chunk)> ShouldBeAggressiveFunction; + +class cBehaviorAggressive : public cBehavior +{ + +public: + cBehaviorAggressive(ShouldBeAggressiveFunction a_ShouldBeAggressiveFunction = nullptr); + void AttachToMonster(cMonster & a_Parent); + + // cBehaviorAggressive(cMonster * a_Parent, bool a_HatesPlayer); + // TODO agression toward specific players, and specific mobtypes, etc + // Agression under specific conditions (nighttime, etc) + + // Functions our host Monster should invoke: + void PreTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + +private: + cPawn * FindNewTarget(); + + // Our parent + cMonster * m_Parent; + + // The mob we want to attack + cPawn * m_Target; + + ShouldBeAggressiveFunction m_ShouldBeAggressiveFunction; + bool m_ShouldBeAgressive; + int m_AgressionCheckCountdown; +}; diff --git a/src/Mobs/Behaviors/BehaviorAttacker.cpp b/src/Mobs/Behaviors/BehaviorAttacker.cpp new file mode 100644 index 000000000..dd3b48de2 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttacker.cpp @@ -0,0 +1,301 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorAttacker.h" +#include "../Monster.h" +#include "../../Entities/Pawn.h" +#include "../../Entities/Player.h" +#include "../../Tracer.h" + + +cBehaviorAttacker::cBehaviorAttacker() : + m_AttackRate(3) + , m_AttackDamage(1) + , m_AttackRange(1) + , m_AttackCoolDownTicksLeft(0) + , m_IsStriking(false) + , m_Target(nullptr) + , m_ShouldRetaliate(true) +{ + +} + + + + + +void cBehaviorAttacker::AttachToMonster(cMonster & a_Parent) +{ + m_Parent = &a_Parent; + m_Parent->AttachTickBehavior(this); + m_Parent->AttachDestroyBehavior(this); + m_Parent->AttachPostTickBehavior(this); + m_Parent->AttachDoTakeDamageBehavior(this); + m_Parent->m_BehaviorAttackerPointer = this; +} + + + + + +bool cBehaviorAttacker::IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + // If we have a target, we have something to do! Return true and control the mob Ticks. + // Otherwise return false. + return (m_IsStriking || (GetTarget() != nullptr)); +} + + + + + +void cBehaviorAttacker::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + + ASSERT(m_Parent->GetHealth() > 0.0); + + if (m_IsStriking) + { + if (DoStrike(++m_StrikeTickCnt)) + { + m_Parent->UnpinBehavior(this); + m_IsStriking = false; + ResetStrikeCooldown(); + } + #ifdef _DEBUG + if (m_StrikeTickCnt > 100) + { + LOGD("Sanity check failed. An attack took more than 5 seconds. Hmm"); + ASSERT(1 == 0); + } + #endif + return; + } + + if ((GetTarget() != nullptr)) + { + ASSERT(GetTarget()->IsTicking()); + + if (GetTarget()->IsPlayer()) + { + if (!static_cast<cPlayer *>(GetTarget())->CanMobsTarget()) + { + SetTarget(nullptr); + } + } + } + + + ASSERT((GetTarget() == nullptr) || (GetTarget()->IsPawn() && (GetTarget()->GetWorld() == m_Parent->GetWorld()))); + if (GetTarget() != nullptr) + { + m_Parent->SetLookingAt(m_Target); + if (TargetTooFar()) + { + SetTarget(nullptr); + } + else + { + if (TargetIsInStrikeRadius() && TargetIsInLineOfSight()) + { + StrikeIfReady(); + } + else + { + m_Parent->MoveToPosition(m_Target->GetPosition()); + // todo BehaviorApproacher for creeper sneaking, etc + } + } + } +} + + + + + +void cBehaviorAttacker::PostTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + if (m_AttackCoolDownTicksLeft > 0) + { + m_AttackCoolDownTicksLeft -= 1; + } +} + + + + + +void cBehaviorAttacker::DoTakeDamage(TakeDamageInfo & a_TDI) +{ + if (!m_ShouldRetaliate) + { + return; + } + + if ((a_TDI.Attacker != nullptr) && a_TDI.Attacker->IsPawn()) + { + if ( + (!a_TDI.Attacker->IsPlayer()) || + (static_cast<cPlayer *>(a_TDI.Attacker)->CanMobsTarget()) + ) + { + SetTarget(static_cast<cPawn*>(a_TDI.Attacker)); + } + } +} + + + + + +void cBehaviorAttacker::Destroyed() +{ + SetTarget(nullptr); +} + + + + + +void cBehaviorAttacker::SetAttackRate(float a_AttackRate) +{ + m_AttackRate = a_AttackRate; +} + + + + + +void cBehaviorAttacker::SetAttackRange(int a_AttackRange) +{ + m_AttackRange = a_AttackRange; +} + + + + + +void cBehaviorAttacker::SetAttackDamage(int a_AttackDamage) +{ + m_AttackDamage = a_AttackDamage; +} + + + + +cPawn * cBehaviorAttacker::GetTarget() +{ + return m_Target; +} + + + + + +void cBehaviorAttacker::SetTarget(cPawn * a_Target) +{ + m_Target = a_Target; +} + + + + + +void cBehaviorAttacker::Strike() +{ + if (m_IsStriking) + { + return; + } + m_IsStriking = true; + m_StrikeTickCnt = 0; + m_Parent->PinBehavior(this); + m_Parent->StopMovingToPosition(); +} + + + + + +bool cBehaviorAttacker::TargetIsInStrikeRadius(void) +{ + ASSERT(GetTarget() != nullptr); + ASSERT(m_Parent != nullptr); + return ((GetTarget()->GetPosition() - m_Parent->GetPosition()).SqrLength() < (m_AttackRange * m_AttackRange)); +} + + + + + +bool cBehaviorAttacker::TargetIsInLineOfSight() +{ + ASSERT(GetTarget() != nullptr); + ASSERT(m_Parent != nullptr); + + + // mobtodo make this smarter + + cTracer LineOfSight(m_Parent->GetWorld()); + Vector3d MyHeadPosition = m_Parent->GetHeadPosition(); + Vector3d TargetHeadPosition = GetTarget()->GetHeadPosition(); + Vector3d AttackDirection1(TargetHeadPosition - MyHeadPosition); + + if (LineOfSight.Trace(MyHeadPosition, AttackDirection1, + static_cast<int>(AttackDirection1.Length())) + ) + { + return false; + } + + Vector3d MyFeetPosition = m_Parent->GetPosition() + Vector3d(0, 0.5, 0); + Vector3d TargetFeetPosition = GetTarget()->GetPosition() + Vector3d(0, 0.5, 0); + Vector3d AttackDirection2(TargetFeetPosition - MyFeetPosition); + if (LineOfSight.Trace(MyFeetPosition, AttackDirection2, + static_cast<int>(AttackDirection2.Length())) + ) + { + return false; + } + + return true; +} + + + + + +bool cBehaviorAttacker::TargetTooFar() +{ + ASSERT(m_Target != nullptr); + if ((GetTarget()->GetPosition() - m_Parent->GetPosition()).Length() > m_Parent->GetSightDistance()) + { + return true; + } + return false; +} + + + + + +void cBehaviorAttacker::ResetStrikeCooldown() +{ + m_AttackCoolDownTicksLeft = static_cast<int>(3 * 20 * m_AttackRate); // A second has 20 ticks, an attack rate of 1 means 1 hit every 3 seconds +} + + + + + +void cBehaviorAttacker::StrikeIfReady() +{ + if (m_AttackCoolDownTicksLeft == 0) + { + Strike(); + } +} diff --git a/src/Mobs/Behaviors/BehaviorAttacker.h b/src/Mobs/Behaviors/BehaviorAttacker.h new file mode 100644 index 000000000..428e7a340 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttacker.h @@ -0,0 +1,83 @@ +#pragma once + +class cBehaviorAttacker; + +#include "Behavior.h" + + +/** Grants attack capability to the mob. Note that this is not the same as agression! +The mob may possess this trait and not attack anyone or only attack when provoked. +Unlike most traits, this one has several forms, and therefore it is an abstract type +You should use one of its derived classes, and you cannot use it directly. */ +class cBehaviorAttacker : public cBehavior +{ + +public: + cBehaviorAttacker(); + virtual void AttachToMonster(cMonster & a_Parent); + + + // Our host monster will call these once it loads its config file + void SetAttackRate(float a_AttackRate); + void SetAttackRange(int a_AttackRange); + void SetAttackDamage(int a_AttackDamage); + + // Behavior functions + bool IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + void Destroyed() override; + void PostTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + void DoTakeDamage(TakeDamageInfo & a_TDI) override; + + /** Returns the target pointer, or a nullptr if we're not targeting anyone. */ + cPawn * GetTarget(); + + /** Sets a new target. Forgets the older target if present. Set this to nullptr to unset target. */ + void SetTarget(cPawn * a_Target); + + /** Makes the mob perform a strike the next tick. Ignores the strike cooldown. + * Ignored if already striking. Some attack behaviors do not require a target + to be set (e.g. creeper explosion). The behaviors that require a target + ignore this call if the target is null. */ + void Strike(); + + /** Makes the mob strike a target the next tick only if the strike cooldown permits it. + * Ignored if already striking or if no target is set. */ + void StrikeIfReady(); +protected: + + /** Called when the actual attack should be made. Will be called again and again every tick until + it returns false. a_StrikeTickCnt tracks how many times it was called. It is 1 the first call. + It increments by 1 each call. This mechanism allows multi-tick attacks, like blazes shooting multiple + fireballs, but most attacks are single tick and return true the first call. Target is not guaranteed to be valid + during these calls. The behavior is pinned until true is returned, meaning no other behaviors can tick. */ + virtual bool DoStrike(int a_StrikeTickCnt) = 0; + + // Target related methods + bool TargetIsInStrikeRadius(); + bool TargetIsInLineOfSight(); + bool TargetTooFar(); + void StrikeIfReady(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + + // Cooldown stuff + void ResetStrikeCooldown(); + + // Our attacking parameters (Set by the setter methods, loaded from a config file in cMonster) + float m_AttackRate; + int m_AttackDamage; + int m_AttackRange; + int m_AttackCoolDownTicksLeft; + + bool m_IsStriking; + + /** Our parent */ + cMonster * m_Parent; + +private: + + // The mob we want to attack + cPawn * m_Target; + int m_StrikeTickCnt; + bool m_ShouldRetaliate; // Should we attack back whoever attacks us? + +}; diff --git a/src/Mobs/Behaviors/BehaviorAttackerMelee.cpp b/src/Mobs/Behaviors/BehaviorAttackerMelee.cpp new file mode 100644 index 000000000..3d25f34b4 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttackerMelee.cpp @@ -0,0 +1,27 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorAttackerMelee.h" +#include "../Monster.h" +#include "../../Entities/Pawn.h" +#include "../../BlockID.h" + +cBehaviorAttackerMelee::cBehaviorAttackerMelee(PostAttackFunction a_PostAttackFunction) + : m_PostAttackFunction(a_PostAttackFunction) +{ + +} + + + + + +bool cBehaviorAttackerMelee::DoStrike(int a_StrikeTickCnt) +{ + UNUSED(a_StrikeTickCnt); + GetTarget()->TakeDamage(dtMobAttack, m_Parent, m_AttackDamage, 0); + if (m_PostAttackFunction != nullptr) + { + m_PostAttackFunction(*this, *m_Parent, *GetTarget()); + } + return true; // Finish the strike. It only takes 1 tick. +} diff --git a/src/Mobs/Behaviors/BehaviorAttackerMelee.h b/src/Mobs/Behaviors/BehaviorAttackerMelee.h new file mode 100644 index 000000000..69d626fbc --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttackerMelee.h @@ -0,0 +1,17 @@ +#pragma once + +#include "BehaviorAttacker.h" +#include <functional> +class cBehaviorAttackerMelee; + +/** Grants the mob that ability to approach a target and then melee attack it. +Use BehaviorAttackerMelee::SetTarget to attack. */ +typedef std::function<void(cBehaviorAttackerMelee & a_Behavior, cMonster & a_Attacker, cPawn & a_Attacked)> PostAttackFunction; +class cBehaviorAttackerMelee : public cBehaviorAttacker +{ +public: + cBehaviorAttackerMelee(PostAttackFunction a_PostAttackFunction = nullptr); + bool DoStrike(int a_StrikeTickCnt) override; +private: + PostAttackFunction m_PostAttackFunction; +}; diff --git a/src/Mobs/Behaviors/BehaviorAttackerRanged.cpp b/src/Mobs/Behaviors/BehaviorAttackerRanged.cpp new file mode 100644 index 000000000..888dc3497 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttackerRanged.cpp @@ -0,0 +1,43 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorAttackerRanged.h" +#include "../Monster.h" +#include "../../Entities/Pawn.h" +#include "../../BlockID.h" +#include "../../Entities/ArrowEntity.h" + +cBehaviorAttackerRanged::cBehaviorAttackerRanged( + RangedShootingFunction a_RangedShootingFunction, int a_ProjectileAmount, + int a_ShootingIntervals) : + m_ShootingFunction(a_RangedShootingFunction), + m_ProjectileAmount(a_ProjectileAmount), + m_ShootingIntervals(a_ShootingIntervals) +{ + +} + + +bool cBehaviorAttackerRanged::DoStrike(int a_StrikeTickCnt) +{ + UNUSED(a_StrikeTickCnt); + + // stop shooting if target is lost + if ((GetTarget() == nullptr)) + { + return true; + } + + // Stop shooting if we've shot m_ProjectileAmount times. + if (a_StrikeTickCnt - 1 == m_ShootingIntervals * m_ProjectileAmount) + { + return true; + } + + // shoot once every m_ShootingIntervals. + // Starting immediately at first call to DoStrike + if ((a_StrikeTickCnt - 1) % m_ShootingIntervals == 0) + { + m_ShootingFunction(*this, *m_Parent, *GetTarget()); + } + return false; +} diff --git a/src/Mobs/Behaviors/BehaviorAttackerRanged.h b/src/Mobs/Behaviors/BehaviorAttackerRanged.h new file mode 100644 index 000000000..1d9e4fcd9 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttackerRanged.h @@ -0,0 +1,23 @@ +#pragma once + +#include "BehaviorAttacker.h" +#include <functional> +class cBehaviorAttackerRanged; + +/** Grants the mob that ability to approach a target and then melee attack it. +Use BehaviorAttackerMelee::SetTarget to attack. */ +typedef std::function<void(cBehaviorAttackerRanged & a_Behavior, + cMonster & a_Attacker, cPawn & a_Attacked)> RangedShootingFunction; + +class cBehaviorAttackerRanged : public cBehaviorAttacker +{ +public: + cBehaviorAttackerRanged(RangedShootingFunction a_RangedShootingFUnction, + int a_ProjectileAmount = 1, int a_ShootingIntervals = 1); + bool DoStrike(int a_StrikeTickCnt) override; + +private: + RangedShootingFunction m_ShootingFunction; + int m_ProjectileAmount; + int m_ShootingIntervals; +}; diff --git a/src/Mobs/Behaviors/BehaviorAttackerSuicideBomber.cpp b/src/Mobs/Behaviors/BehaviorAttackerSuicideBomber.cpp new file mode 100644 index 000000000..843cc58ef --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttackerSuicideBomber.cpp @@ -0,0 +1,125 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorAttackerSuicideBomber.h" +#include "../Monster.h" +#include "../../Entities/Pawn.h" +#include "../../Entities/Player.h" +#include "../../BlockID.h" + + + + +cBehaviorAttackerSuicideBomber::cBehaviorAttackerSuicideBomber() : + m_bIsBlowing(false), + m_bIsCharged(false), + m_BurnedWithFlintAndSteel(false) +{ + +} + + + + + +void cBehaviorAttackerSuicideBomber::AttachToMonster(cMonster & a_Parent) +{ + cBehaviorAttacker::AttachToMonster(a_Parent); + m_Parent->AttachRightClickBehavior(this); +} + + + + + +bool cBehaviorAttackerSuicideBomber::DoStrike(int a_StrikeTickCnt) +{ + UNUSED(a_StrikeTickCnt); + + // phase 1: start blowing up + if (a_StrikeTickCnt == 1) + { + ASSERT(!m_bIsBlowing); + + m_Parent->GetWorld()->BroadcastSoundEffect("entity.creeper.primed", m_Parent->GetPosX(), m_Parent->GetPosY(), m_Parent->GetPosZ(), 1.f, (0.75f + (static_cast<float>((m_Parent->GetUniqueID() * 23) % 32)) / 64)); + m_bIsBlowing = true; + m_Parent->GetWorld()->BroadcastEntityMetadata(*m_Parent); + + return false; + } + + ASSERT(m_bIsBlowing); + if (((GetTarget() == nullptr) || (!TargetIsInStrikeRadius())) && (!m_BurnedWithFlintAndSteel)) + { + m_bIsBlowing = false; + m_Parent->GetWorld()->BroadcastEntityMetadata(*m_Parent); + return true; + } + + if (a_StrikeTickCnt == 30) + { + ASSERT(m_Parent->GetHealth() > 0.0); + m_Parent->GetWorld()->DoExplosionAt((m_bIsCharged ? 5 : 3), m_Parent->GetPosX(), m_Parent->GetPosY(), m_Parent->GetPosZ(), false, esMonster, this); + m_Parent->Destroy(); // Just in case we aren't killed by the explosion + return true; + } + + return false; +} + + + + + +void cBehaviorAttackerSuicideBomber::OnRightClicked(cPlayer & a_Player) +{ + if ((a_Player.GetEquippedItem().m_ItemType == E_ITEM_FLINT_AND_STEEL)) + { + if (!a_Player.IsGameModeCreative()) + { + a_Player.UseEquippedItem(); + } + if (!m_BurnedWithFlintAndSteel) + { + m_BurnedWithFlintAndSteel = true; + Strike(); + } + } +} + + +bool cBehaviorAttackerSuicideBomber::IsBlowing(void) const +{ + return m_bIsBlowing; +} + + + + + +bool cBehaviorAttackerSuicideBomber::IsCharged(void) const +{ + return m_bIsCharged; +} + + + + + +bool cBehaviorAttackerSuicideBomber::IsBurnedWithFlintAndSteel(void) const +{ + return m_BurnedWithFlintAndSteel; +} + + + + + +void cBehaviorAttackerSuicideBomber::DoTakeDamage(TakeDamageInfo & a_TDI) +{ + if (a_TDI.DamageType == dtLightning) + { + m_bIsCharged = true; + m_Parent->GetWorld()->BroadcastEntityMetadata(*m_Parent); + } + cBehaviorAttacker::DoTakeDamage(a_TDI); +} diff --git a/src/Mobs/Behaviors/BehaviorAttackerSuicideBomber.h b/src/Mobs/Behaviors/BehaviorAttackerSuicideBomber.h new file mode 100644 index 000000000..b9cb155e8 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttackerSuicideBomber.h @@ -0,0 +1,24 @@ +#pragma once + +#include "BehaviorAttacker.h" + +/** Grants the mob that ability to approach a target and then melee attack it. +Use BehaviorAttackerMelee::SetTarget to attack. */ +class cBehaviorAttackerSuicideBomber : public cBehaviorAttacker +{ +public: + cBehaviorAttackerSuicideBomber(); + void AttachToMonster(cMonster & a_Parent) override; + // cBehaviorAttacker also implements those and we need to call super on them + void DoTakeDamage(TakeDamageInfo & a_TDI) override; + + bool DoStrike(int a_StrikeTickCnt) override; + void OnRightClicked(cPlayer & a_Player) override; + + bool IsBlowing(void) const; + bool IsCharged(void) const; + bool IsBurnedWithFlintAndSteel(void) const; + +private: + bool m_bIsBlowing, m_bIsCharged, m_BurnedWithFlintAndSteel; +}; diff --git a/src/Mobs/Behaviors/BehaviorBrave.cpp b/src/Mobs/Behaviors/BehaviorBrave.cpp new file mode 100644 index 000000000..6aba310a1 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorBrave.cpp @@ -0,0 +1,27 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorBrave.h" +#include "BehaviorAttacker.h" +#include "../Monster.h" +#include "../../Entities/Entity.h" + +void cBehaviorBrave::AttachToMonster(cMonster & a_Parent) +{ + m_Parent = &a_Parent; + m_Parent->AttachDoTakeDamageBehavior(this); +} + + + + +void cBehaviorBrave::DoTakeDamage(TakeDamageInfo & a_TDI) +{ + cBehaviorAttacker * AttackBehavior = m_Parent->GetBehaviorAttacker(); + if ((AttackBehavior != nullptr) && (a_TDI.Attacker != m_Parent) && + (a_TDI.Attacker != nullptr) && (a_TDI.Attacker->IsPawn()) + ) + { + AttackBehavior->SetTarget(static_cast<cPawn*>(a_TDI.Attacker)); + } +} + diff --git a/src/Mobs/Behaviors/BehaviorBrave.h b/src/Mobs/Behaviors/BehaviorBrave.h new file mode 100644 index 000000000..0a59f90b8 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorBrave.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Behavior.h" + +/** Makes the mob fight back any other mob that damages it. Mob should have BehaviorAttacker to work. +This behavior does not make sense in combination with BehaviorCoward. */ +class cBehaviorBrave : cBehavior +{ +public: + void AttachToMonster(cMonster & a_Parent); + void DoTakeDamage(TakeDamageInfo & a_TDI) override; + +private: + cMonster * m_Parent; // Our Parent +}; diff --git a/src/Mobs/Behaviors/BehaviorBreeder.cpp b/src/Mobs/Behaviors/BehaviorBreeder.cpp new file mode 100644 index 000000000..cec7e6295 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorBreeder.cpp @@ -0,0 +1,365 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorBreeder.h" +#include "../../World.h" +#include "../Monster.h" +#include "../../Entities/Player.h" +#include "../../Item.h" +#include "../../BoundingBox.h" + +cBehaviorBreeder::cBehaviorBreeder() : + m_LovePartner(nullptr), + m_LoveTimer(0), + m_LoveCooldown(0), + m_MatingTimer(0) +{ +} + + + + + +void cBehaviorBreeder::AttachToMonster(cMonster & a_Parent) +{ + m_Parent = &a_Parent; + m_Parent->AttachTickBehavior(this); + m_Parent->AttachPostTickBehavior(this); + m_Parent->AttachRightClickBehavior(this); + m_Parent->AttachDestroyBehavior(this); + m_Parent->m_BehaviorBreederPointer = this; +} + + + + + +void cBehaviorBreeder::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + LOGD("mobDebug - Behavior Breeder: Tick"); + UNUSED(a_Dt); + UNUSED(a_Chunk); + cWorld * World = m_Parent->GetWorld(); + // if we have a partner, mate + if (m_LovePartner != nullptr) + { + if (m_MatingTimer > 0) + { + // If we should still mate, keep bumping into them until baby is made + Vector3d Pos = m_LovePartner->GetPosition(); + m_Parent->MoveToPosition(Pos); + } + else + { + // Mating finished. Spawn baby + Vector3f Pos = (m_Parent->GetPosition() + m_LovePartner->GetPosition()) * 0.5; + UInt32 BabyID = World->SpawnMob(Pos.x, Pos.y, Pos.z, m_Parent->GetMobType(), true); + + class cBabyInheritCallback : + public cEntityCallback + { + public: + cMonster * Baby; + cBabyInheritCallback() : Baby(nullptr) { } + virtual bool Item(cEntity * a_Entity) override + { + Baby = static_cast<cMonster *>(a_Entity); + return true; + } + } Callback; + + m_Parent->GetWorld()->DoWithEntityByID(BabyID, Callback); + if (Callback.Baby != nullptr) + { + Callback.Baby->InheritFromParents(m_Parent, m_LovePartner); + } + + cFastRandom Random; + World->SpawnExperienceOrb(Pos.x, Pos.y, Pos.z, 1 + (Random.RandInt() % 6)); + + m_LovePartner->GetBehaviorBreeder()->ResetLoveMode(); + ResetLoveMode(); + } + } +} + + + + + +void cBehaviorBreeder::PostTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + if (m_MatingTimer > 0) + { + m_MatingTimer--; + } + if (m_LoveCooldown > 0) + { + m_LoveCooldown--; + } + if (m_LoveTimer > 0) + { + m_LoveTimer--; + } +} + + + + + +bool cBehaviorBreeder::IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + cWorld * World = m_Parent->GetWorld(); + + // if we have a love partner, we should control the mob + if (m_LovePartner != nullptr) + { + return true; + } + + // If we are in love mode and we have no partner, try to find one + if (m_LoveTimer > 0) + { + class LookForLover : public cEntityCallback + { + public: + cMonster * m_Me; + LookForLover(cMonster * a_Me) : + m_Me(a_Me) + { + } + + virtual bool Item(cEntity * a_Entity) override + { + // If the entity is not a monster, don't breed with it + // Also, do not self-breed + if ((a_Entity->GetEntityType() != cEntity::eEntityType::etMonster) || (a_Entity == m_Me)) + { + return false; + } + + auto PotentialPartner = static_cast<cMonster*>(a_Entity); + + // If the potential partner is not of the same species, don't breed with it + if (PotentialPartner->GetMobType() != m_Me->GetMobType()) + { + return false; + } + + auto PartnerBreedingBehavior = PotentialPartner->GetBehaviorBreeder(); + auto MyBreedingBehavior = m_Me->GetBehaviorBreeder(); + + // If the potential partner is not in love + // Or they already have a mate, do not breed with them + + if ((!PartnerBreedingBehavior->IsInLove()) || (PartnerBreedingBehavior->GetPartner() != nullptr)) + { + return false; + } + + // All conditions met, let's breed! + PartnerBreedingBehavior->EngageLoveMode(m_Me); + MyBreedingBehavior->EngageLoveMode(PotentialPartner); + return true; + } + } Callback(m_Parent); + + World->ForEachEntityInBox(cBoundingBox(m_Parent->GetPosition(), 8, 8), Callback); + if (m_LovePartner != nullptr) + { + return true; // We found love and took control of the monster, prevent other Behaviors from doing so + } + } + + return false; +} + +void cBehaviorBreeder::Destroyed() +{ + LOGD("mobDebug - Behavior Breeder: Destroyed"); + if (m_LovePartner != nullptr) + { + m_LovePartner->GetBehaviorBreeder()->ResetLoveMode(); + } +} + + + + + +void cBehaviorBreeder::OnRightClicked(cPlayer & a_Player) +{ + // If a player holding breeding items right-clicked me, go into love mode + if ((m_LoveCooldown == 0) && !IsInLove() && !m_Parent->IsBaby()) + { + short HeldItem = a_Player.GetEquippedItem().m_ItemType; + cItems BreedingItems; + m_Parent->GetFollowedItems(BreedingItems); + if (BreedingItems.ContainsType(HeldItem)) + { + if (!a_Player.IsGameModeCreative()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + } + m_LoveTimer = 20 * 30; // half a minute + m_Parent->GetWorld()->BroadcastEntityStatus(*m_Parent, cEntity::eEntityStatus::esMobInLove); + } + } +} + + + +void cBehaviorBreeder::EngageLoveMode(cMonster * a_Partner) +{ + m_LovePartner = a_Partner; + m_MatingTimer = 50; // about 3 seconds of mating +} + + + + + +void cBehaviorBreeder::ResetLoveMode() +{ + m_LovePartner = nullptr; + m_LoveTimer = 0; + m_MatingTimer = 0; + m_LoveCooldown = 20 * 60 * 5; // 5 minutes + + // when an animal is in love mode, the client only stops sending the hearts if we let them know it's in cooldown, which is done with the "age" metadata + m_Parent->GetWorld()->BroadcastEntityMetadata(*m_Parent); +} + + + + + +bool cBehaviorBreeder::IsInLove() const +{ + return m_LoveTimer > 0; +} + + + + + +bool cBehaviorBreeder::IsInLoveCooldown() const +{ + return (m_LoveCooldown > 0); +} + + + + +/*** CODE TO BE USED AFTER the lambda merge + * bool cBehaviorBreeder::IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + cWorld * World = m_Parent->GetWorld(); + + // if we have a love partner, we should control the mob + if (m_LovePartner != nullptr) + { + return true; + } + + // If we are in love mode and we have no partner, try to find one + if (m_LoveTimer > 0) + { + m_Parent->GetWorld()->ForEachEntityInBox(cBoundingBox(m_Parent->GetPosition(), 8, 8), [=](cEntity & a_Entity) + { + // If the entity is not a monster, don't breed with it + // Also, do not self-breed + if ((a_Entity.GetEntityType() != cEntity::eEntityType::etMonster) || (&a_Entity == m_Parent)) + { + return false; + } + + auto PotentialPartner = static_cast<cMonster*>(&a_Entity); + + // If the potential partner is not of the same species, don't breed with it + if (PotentialPartner->GetMobType() != m_Parent->GetMobType()) + { + return false; + } + + auto PartnerBreedingBehavior = PotentialPartner->GetBehaviorBreeder(); + auto MyBreedingBehavior = m_Parent->GetBehaviorBreeder(); + + // If the potential partner is not in love + // Or they already have a mate, do not breed with them + + if ((!PartnerBreedingBehavior->IsInLove()) || (PartnerBreedingBehavior->GetPartner() != nullptr)) + { + return false; + } + + // All conditions met, let's breed! + PartnerBreedingBehavior->EngageLoveMode(m_Parent); + MyBreedingBehavior->EngageLoveMode(PotentialPartner); + return true; + }); + + + if (m_LovePartner != nullptr) + { + return true; // We found love and took control of the monster, prevent other Behaviors from doing so + } + } + + return false; +} **/ + + + + + + +/* + * CODE to be used AFTER the lambda merge + * void cBehaviorBreeder::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + LOGD("mobDebug - Behavior Breeder: Tick"); + UNUSED(a_Dt); + UNUSED(a_Chunk); + cWorld * World = m_Parent->GetWorld(); + // if we have a partner, mate + if (m_LovePartner != nullptr) + { + if (m_MatingTimer > 0) + { + // If we should still mate, keep bumping into them until baby is made + Vector3d Pos = m_LovePartner->GetPosition(); + m_Parent->MoveToPosition(Pos); + } + else + { + // Mating finished. Spawn baby + Vector3f Pos = (m_Parent->GetPosition() + m_LovePartner->GetPosition()) * 0.5; + UInt32 BabyID = World->SpawnMob(Pos.x, Pos.y, Pos.z, m_Parent->GetMobType(), true); + + cMonster * Baby = nullptr; + + m_Parent->GetWorld()->DoWithEntityByID(BabyID, [&](cEntity & a_Entity) + { + Baby = static_cast<cMonster *>(&a_Entity); + return true; + }); + + if (Baby != nullptr) + { + Baby->InheritFromParents(m_Parent, m_LovePartner); + } + + cFastRandom Random; + World->SpawnExperienceOrb(Pos.x, Pos.y, Pos.z, 1 + (Random.RandInt() % 6)); + + m_LovePartner->GetBehaviorBreeder()->ResetLoveMode(); + ResetLoveMode(); + } + } +}*/ diff --git a/src/Mobs/Behaviors/BehaviorBreeder.h b/src/Mobs/Behaviors/BehaviorBreeder.h new file mode 100644 index 000000000..a3d0b51af --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorBreeder.h @@ -0,0 +1,52 @@ +#pragma once + +class cBehaviorBreeder; + +#include "Behavior.h" + +/** Grants breeding capabilities to the mob. */ +class cBehaviorBreeder : public cBehavior +{ + +public: + cBehaviorBreeder(); + void AttachToMonster(cMonster & a_Parent); + + // Functions our host Monster should invoke: + bool IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + void PostTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + void OnRightClicked(cPlayer & a_Player) override; + void Destroyed() override; + + /** Returns the partner which the monster is currently mating with. */ + cMonster * GetPartner(void) const { return m_LovePartner; } + + /** Start the mating process. Causes the monster to keep bumping into the partner until m_MatingTimer reaches zero. */ + void EngageLoveMode(cMonster * a_Partner); + + /** Finish the mating process. Called after a baby is born. Resets all breeding related timers and sets m_LoveCooldown to 20 minutes. */ + void ResetLoveMode(); + + /** Returns whether the monster has just been fed and is ready to mate. If this is "true" and GetPartner isn't "nullptr", then the monster is mating. */ + bool IsInLove() const; + + /** Returns whether the monster is tired of breeding and is in the cooldown state. */ + bool IsInLoveCooldown() const; + +private: + /** Our parent */ + cMonster * m_Parent; + + /** The monster's breeding partner. */ + cMonster * m_LovePartner; + + /** If above 0, the monster is in love mode, and will breed if a nearby monster is also in love mode. Decrements by 1 per tick till reaching zero. */ + int m_LoveTimer; + + /** If above 0, the monster is in cooldown mode and will refuse to breed. Decrements by 1 per tick till reaching zero. */ + int m_LoveCooldown; + + /** The monster is engaged in mating, once this reaches zero, a baby will be born. Decrements by 1 per tick till reaching zero, then a baby is made and ResetLoveMode() is called. */ + int m_MatingTimer; +}; diff --git a/src/Mobs/Behaviors/BehaviorCoward.cpp b/src/Mobs/Behaviors/BehaviorCoward.cpp new file mode 100644 index 000000000..fc13cf5b9 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorCoward.cpp @@ -0,0 +1,97 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorCoward.h" +#include "../Monster.h" +#include "../../World.h" +#include "../../Entities/Player.h" +#include "../../Entities/Entity.h" + +cBehaviorCoward::cBehaviorCoward() : + m_Attacker(nullptr) +{ +} + + + + +void cBehaviorCoward::AttachToMonster(cMonster & a_Parent) +{ + m_Parent = &a_Parent; + m_Parent->AttachTickBehavior(this); + m_Parent->AttachDoTakeDamageBehavior(this); +} + + + + + +bool cBehaviorCoward::IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + return (m_Attacker != nullptr); //mobTodo probably not so safe pointer (and cChaser m_Target too) +} + + + + + +bool cBehaviorCoward::ControlStarting(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + m_Parent->GetPathFinder().SetDontCare(true); // We don't care we're we are going when + // wandering. If a path is not found, the pathfinder just modifies our destination. + m_Parent->SetRelativeWalkSpeed(m_Parent->GetRelativeWalkSpeed() * 3); + return true; +} + + +bool cBehaviorCoward::ControlEnding(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + m_Parent->SetRelativeWalkSpeed(m_Parent->GetRelativeWalkSpeed() / 3); + m_Parent->GetPathFinder().SetDontCare(false); + return true; +} + + + + + +void cBehaviorCoward::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + if (m_Attacker == nullptr) + { + return; + } + + // TODO NOT SAFE + if (m_Attacker->IsDestroyed() || (m_Attacker->GetPosition() - m_Parent->GetPosition()).Length() > m_Parent->GetSightDistance()) + { + // We lost the attacker + m_Attacker = nullptr; + return; + } + + Vector3d newloc = m_Parent->GetPosition(); + newloc.x = (m_Attacker->GetPosition().x < newloc.x)? (newloc.x + m_Parent->GetSightDistance()): (newloc.x - m_Parent->GetSightDistance()); + newloc.z = (m_Attacker->GetPosition().z < newloc.z)? (newloc.z + m_Parent->GetSightDistance()): (newloc.z - m_Parent->GetSightDistance()); + m_Parent->MoveToPosition(newloc); +} + + + + + +void cBehaviorCoward::DoTakeDamage(TakeDamageInfo & a_TDI) +{ + if ((a_TDI.Attacker != m_Parent) && (a_TDI.Attacker != nullptr)) + { + m_Attacker = a_TDI.Attacker; + } +} + diff --git a/src/Mobs/Behaviors/BehaviorCoward.h b/src/Mobs/Behaviors/BehaviorCoward.h new file mode 100644 index 000000000..3232f807b --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorCoward.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Behavior.h" + +/** Makes the mob run away from any other mob that damages it. */ +class cBehaviorCoward : cBehavior +{ +public: + cBehaviorCoward(); + void AttachToMonster(cMonster & a_Parent); + + bool IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + bool ControlStarting(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + bool ControlEnding(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + void DoTakeDamage(TakeDamageInfo & a_TDI) override; + + +private: + cMonster * m_Parent; // Our Parent + cEntity * m_Attacker; // The entity we're running away from +}; diff --git a/src/Mobs/Behaviors/BehaviorDayLightBurner.cpp b/src/Mobs/Behaviors/BehaviorDayLightBurner.cpp new file mode 100644 index 000000000..1271574fe --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorDayLightBurner.cpp @@ -0,0 +1,103 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorDayLightBurner.h" +#include "../Monster.h" +#include "../../Entities/Player.h" +#include "../../Entities/Entity.h" +#include "../../Chunk.h" + + + + + +void cBehaviorDayLightBurner::AttachToMonster(cMonster & a_Parent) +{ + m_Parent = &a_Parent; + m_Parent->AttachPostTickBehavior(this); + m_Parent->GetPathFinder().SetAvoidSunlight(true); +} + + + + + +void cBehaviorDayLightBurner::PostTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + int RelY = static_cast<int>(m_Parent->GetPosY()); + if ((RelY < 0) || (RelY >= cChunkDef::Height)) + { + // Outside the world + return; + } + if (!a_Chunk.IsLightValid()) + { + m_Parent->GetWorld()->QueueLightChunk(m_Parent->GetChunkX(), m_Parent->GetChunkZ()); + return; + } + + if (!m_Parent->IsOnFire() && WouldBurnAt(m_Parent->GetPosition(), a_Chunk, *m_Parent)) + { + // Burn for 100 ticks, then decide again + m_Parent->StartBurning(100); + } +} + + + + +bool cBehaviorDayLightBurner::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk, cMonster & a_Monster) +{ + int RelY = FloorC(a_Location.y); + if (RelY <= 0) + { + // The mob is about to die, no point in burning + return false; + } + if (RelY >= cChunkDef::Height) + { + // Always burn above the world + return true; + } + + PREPARE_REL_AND_CHUNK(a_Location, a_Chunk); + if (!RelSuccess) + { + return false; + } + + if ( + (Chunk->GetBlock(Rel.x, Rel.y, Rel.z) != E_BLOCK_SOULSAND) && // Not on soulsand + (a_Monster.GetWorld()->GetTimeOfDay() < 12000 + 1000) && // Daytime + a_Monster.GetWorld()->IsWeatherSunnyAt(static_cast<int>(a_Monster.GetPosX()), static_cast<int>(a_Monster.GetPosZ())) // Not raining + ) + { + int MobHeight = CeilC(a_Location.y + a_Monster.GetHeight()) - 1; // The height of the mob head + if (MobHeight >= cChunkDef::Height) + { + return true; + } + // Start with the highest block and scan down to just abovethe mob's head. + // If a non transparent is found, return false (do not burn). Otherwise return true. + // Note that this loop is not a performance concern as transparent blocks are rare and the loop almost always bailes out + // instantly.(An exception is e.g. standing under a long column of glass). + int CurrentBlock = Chunk->GetHeight(Rel.x, Rel.z); + while (CurrentBlock > MobHeight) + { + BLOCKTYPE Block = Chunk->GetBlock(Rel.x, CurrentBlock, Rel.z); + if ( + // Do not burn if a block above us meets one of the following conditions: + (!cBlockInfo::IsTransparent(Block)) || + (Block == E_BLOCK_LEAVES) || + (Block == E_BLOCK_NEW_LEAVES) || + (IsBlockWater(Block)) + ) + { + return false; + } + --CurrentBlock; + } + return true; + + } + return false; +} diff --git a/src/Mobs/Behaviors/BehaviorDayLightBurner.h b/src/Mobs/Behaviors/BehaviorDayLightBurner.h new file mode 100644 index 000000000..920f9d5c7 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorDayLightBurner.h @@ -0,0 +1,17 @@ +#pragma once + +// mobTodo I just need vector3d +#include "Behavior.h" +#include "../../World.h" + +class cBehaviorDayLightBurner : cBehavior +{ +public: + void AttachToMonster(cMonster & a_Parent); + void PostTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + static bool WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk, cMonster & a_Monster); + +private: + cMonster * m_Parent; // Our Parent + cEntity * m_Attacker; // The entity we're running away from +}; diff --git a/src/Mobs/Behaviors/BehaviorDoNothing.cpp b/src/Mobs/Behaviors/BehaviorDoNothing.cpp new file mode 100644 index 000000000..aeb4b815e --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorDoNothing.cpp @@ -0,0 +1,24 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorDoNothing.h" +#include "../Monster.h" + +void cBehaviorDoNothing::AttachToMonster(cMonster & a_Parent) +{ + m_Parent = &a_Parent; + m_Parent->AttachTickBehavior(this); +} + +bool cBehaviorDoNothing::IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + return true; +} + +void cBehaviorDoNothing::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + return; +} diff --git a/src/Mobs/Behaviors/BehaviorDoNothing.h b/src/Mobs/Behaviors/BehaviorDoNothing.h new file mode 100644 index 000000000..52c0c7c08 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorDoNothing.h @@ -0,0 +1,19 @@ +#pragma once + +// Always takes control of the tick and does nothing. Used for unimplemented mobs like squids. + +class cBehaviorDoNothing; + +#include "Behavior.h" + +class cBehaviorDoNothing : public cBehavior +{ +public: + void AttachToMonster(cMonster & a_Parent); + bool IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + +private: + /** Our parent */ + cMonster * m_Parent; +}; diff --git a/src/Mobs/Behaviors/BehaviorItemDropper.cpp b/src/Mobs/Behaviors/BehaviorItemDropper.cpp new file mode 100644 index 000000000..d39993012 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorItemDropper.cpp @@ -0,0 +1,50 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorItemDropper.h" +#include "../Monster.h" +#include "../../World.h" + + +void cBehaviorItemDropper::AttachToMonster(cMonster & a_Parent) +{ + m_Parent = &a_Parent; + m_EggDropTimer = 0; + m_Parent->AttachPostTickBehavior(this); +} + + + + + +void cBehaviorItemDropper::PostTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + if (!m_Parent->IsTicking()) + { + // The base class tick destroyed us + return; + } + + if (m_Parent->IsBaby()) + { + return; // Babies don't lay eggs + } + + if ((m_EggDropTimer == 6000) && GetRandomProvider().RandBool()) + { + cItems Drops; + m_EggDropTimer = 0; + Drops.push_back(cItem(E_ITEM_EGG, 1)); + m_Parent->GetWorld()->SpawnItemPickups(Drops, m_Parent->GetPosX(), m_Parent->GetPosY(), m_Parent->GetPosZ(), 10); + } + else if (m_EggDropTimer == 12000) + { + cItems Drops; + m_EggDropTimer = 0; + Drops.push_back(cItem(E_ITEM_EGG, 1)); + m_Parent->GetWorld()->SpawnItemPickups(Drops, m_Parent->GetPosX(), m_Parent->GetPosY(), m_Parent->GetPosZ(), 10); + } + else + { + m_EggDropTimer++; + } +} diff --git a/src/Mobs/Behaviors/BehaviorItemDropper.h b/src/Mobs/Behaviors/BehaviorItemDropper.h new file mode 100644 index 000000000..2b9623ecf --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorItemDropper.h @@ -0,0 +1,17 @@ +#pragma once + +// mobTodo a more generic, not chickenspecific dropper + +#include "Behavior.h" + +/** Makes the mob periodically lay eggs. */ +class cBehaviorItemDropper : cBehavior +{ +public: + void AttachToMonster(cMonster & a_Parent); + void PostTick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + +private: + cMonster * m_Parent; // Our Parent + int m_EggDropTimer; +}; diff --git a/src/Mobs/Behaviors/BehaviorItemFollower.cpp b/src/Mobs/Behaviors/BehaviorItemFollower.cpp new file mode 100644 index 000000000..6978b2765 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorItemFollower.cpp @@ -0,0 +1,64 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorItemFollower.h" +#include "../Monster.h" +#include "../../World.h" +#include "../../Entities/Player.h" + + +void cBehaviorItemFollower::AttachToMonster(cMonster & a_Parent) +{ + LOGD("mobDebug - Behavior ItemFollower: Attach"); + m_Parent = &a_Parent; + m_Parent->AttachTickBehavior(this); +} + + + + + +bool cBehaviorItemFollower::IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + cItems FollowedItems; + m_Parent->GetFollowedItems(FollowedItems); + if (FollowedItems.Size() > 0) + { + cPlayer * a_Closest_Player = m_Parent->GetNearestPlayer(); + if (a_Closest_Player != nullptr) + { + cItem EquippedItem = a_Closest_Player->GetEquippedItem(); + if (FollowedItems.ContainsType(EquippedItem)) + { + return true; // We take control of the monster. Now it'll call our tick + } + } + } + return false; +} + + + + + +void cBehaviorItemFollower::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + cItems FollowedItems; + m_Parent->GetFollowedItems(FollowedItems); + if (FollowedItems.Size() > 0) + { + cPlayer * a_Closest_Player = m_Parent->GetNearestPlayer(); + if (a_Closest_Player != nullptr) + { + cItem EquippedItem = a_Closest_Player->GetEquippedItem(); + if (FollowedItems.ContainsType(EquippedItem)) + { + Vector3d PlayerPos = a_Closest_Player->GetPosition(); + m_Parent->MoveToPosition(PlayerPos); + } + } + } +} diff --git a/src/Mobs/Behaviors/BehaviorItemFollower.h b/src/Mobs/Behaviors/BehaviorItemFollower.h new file mode 100644 index 000000000..8c79ec763 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorItemFollower.h @@ -0,0 +1,19 @@ +#pragma once + + +class cBehaviorItemFollower; + +#include "Behavior.h" +/** Makes the mob follow specific items when held by the player. +Currently relies on cMonster::GetFollowedItems for the item list. */ +class cBehaviorItemFollower : public cBehavior +{ +public: + void AttachToMonster(cMonster & a_Parent); + bool IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + +private: + /** Our parent */ + cMonster * m_Parent; +}; diff --git a/src/Mobs/Behaviors/BehaviorItemReplacer.cpp b/src/Mobs/Behaviors/BehaviorItemReplacer.cpp new file mode 100644 index 000000000..b53f08b33 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorItemReplacer.cpp @@ -0,0 +1,40 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BehaviorItemReplacer.h" +#include "../Monster.h" +#include "../Entities/Player.h" + + +cBehaviorItemReplacer::cBehaviorItemReplacer(short a_OriginalItem, short a_NewItem) : + m_OriginalItem(a_OriginalItem) , + m_NewItem(a_NewItem) +{ + +} + + + + + +void cBehaviorItemReplacer::AttachToMonster(cMonster & a_Parent) +{ + m_Parent = &a_Parent; + m_Parent->AttachRightClickBehavior(this); +} + + + + + +void cBehaviorItemReplacer::OnRightClicked(cPlayer & a_Player) +{ + short HeldItem = a_Player.GetEquippedItem().m_ItemType; + if (HeldItem == m_OriginalItem) + { + if (!a_Player.IsGameModeCreative()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + a_Player.GetInventory().AddItem(m_NewItem); + } + } +} diff --git a/src/Mobs/Behaviors/BehaviorItemReplacer.h b/src/Mobs/Behaviors/BehaviorItemReplacer.h new file mode 100644 index 000000000..e35729f0e --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorItemReplacer.h @@ -0,0 +1,26 @@ +#pragma once + + +class cBehaviorItemReplacer; + +#include "Behavior.h" + +/** When right clicked while holding a_OriginalItem, a mob having this behavior replaces the original item +with a_NewItem. This is used for milking cows. +*/ +class cBehaviorItemReplacer : public cBehavior +{ + +public: + cBehaviorItemReplacer(short a_OriginalItem, short a_NewItem); + void AttachToMonster(cMonster & a_Parent); + void OnRightClicked(cPlayer & a_Player) override; +private: + // Our parent + cMonster * m_Parent; + short m_OriginalItem; // Replace this item with NewItem + short m_NewItem; +}; + + + diff --git a/src/Mobs/Behaviors/BehaviorWanderer.cpp b/src/Mobs/Behaviors/BehaviorWanderer.cpp new file mode 100644 index 000000000..56316bd9f --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorWanderer.cpp @@ -0,0 +1,102 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules +#include "BehaviorWanderer.h" +#include "../Monster.h" +#include "../../Chunk.h" +#include "../../World.h" + +cBehaviorWanderer::cBehaviorWanderer() : m_IdleInterval(0) +{ + +} + + + + + +void cBehaviorWanderer::AttachToMonster(cMonster & a_Parent) +{ + LOGD("mobDebug - Behavior Wanderer: Attach"); + m_Parent = &a_Parent; + m_Parent->AttachTickBehavior(this); +} + + + + + +bool cBehaviorWanderer::IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + // Wandering behavior always happily accepts control. + // It should therefore be the last one attached to a monster. + UNUSED(a_Dt); + UNUSED(a_Chunk); + return true; +} + + + +bool cBehaviorWanderer::ControlStarting(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + m_Parent->GetPathFinder().SetDontCare(true); // We don't care we're we are going when + // wandering. If a path is not found, the pathfinder just modifies our destination. + return true; +} + + +bool cBehaviorWanderer::ControlEnding(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + UNUSED(a_Dt); + UNUSED(a_Chunk); + m_Parent->GetPathFinder().SetDontCare(false); + return true; +} + + +void cBehaviorWanderer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +{ + if (m_Parent->IsPathFinderActivated()) + { + return; // Still getting there + } + + m_IdleInterval += a_Dt; + + if (m_IdleInterval > std::chrono::seconds(1)) + { + // At this interval the results are predictable + int rem = m_Parent->GetWorld()->GetTickRandomNumber(6) + 1; + m_IdleInterval -= std::chrono::seconds(1); // So nothing gets dropped when the server hangs for a few seconds + + Vector3d Dist; + Dist.x = static_cast<double>(m_Parent->GetWorld()->GetTickRandomNumber(10)) - 5.0; + Dist.z = static_cast<double>(m_Parent->GetWorld()->GetTickRandomNumber(10)) - 5.0; + + if ((Dist.SqrLength() > 2) && (rem >= 3)) + { + + Vector3d Destination(m_Parent->GetPosX() + Dist.x, m_Parent->GetPosition().y, m_Parent->GetPosZ() + Dist.z); + + cChunk * Chunk = a_Chunk.GetNeighborChunk(static_cast<int>(Destination.x), static_cast<int>(Destination.z)); + if ((Chunk == nullptr) || !Chunk->IsValid()) + { + return; + } + + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + int RelX = static_cast<int>(Destination.x) - Chunk->GetPosX() * cChunkDef::Width; + int RelZ = static_cast<int>(Destination.z) - Chunk->GetPosZ() * cChunkDef::Width; + int YBelowUs = static_cast<int>(Destination.y) - 1; + if (YBelowUs >= 0) + { + Chunk->GetBlockTypeMeta(RelX, YBelowUs, RelZ, BlockType, BlockMeta); + if (BlockType != E_BLOCK_STATIONARY_WATER) // Idle mobs shouldn't enter water on purpose + { + m_Parent->MoveToPosition(Destination); + } + } + } + } +} diff --git a/src/Mobs/Behaviors/BehaviorWanderer.h b/src/Mobs/Behaviors/BehaviorWanderer.h new file mode 100644 index 000000000..24c8885bf --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorWanderer.h @@ -0,0 +1,24 @@ +#pragma once + +// The mob will wander around +#include <chrono> +#include "Behavior.h" + +class cBehaviorWanderer : cBehavior +{ + +public: + cBehaviorWanderer(); + void AttachToMonster(cMonster & a_Parent); + + // Functions our host Monster should invoke: + bool IsControlDesired(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + bool ControlStarting(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + bool ControlEnding(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + + +private: + cMonster * m_Parent; // Our Parent + std::chrono::milliseconds m_IdleInterval; +}; diff --git a/src/Mobs/Behaviors/CMakeLists.txt b/src/Mobs/Behaviors/CMakeLists.txt new file mode 100644 index 000000000..f205e9cb4 --- /dev/null +++ b/src/Mobs/Behaviors/CMakeLists.txt @@ -0,0 +1,45 @@ + +cmake_minimum_required (VERSION 2.6) +project (Cuberite) + +include_directories ("${PROJECT_SOURCE_DIR}/../") + +SET (SRCS + Behavior.cpp + BehaviorAggressive.cpp + BehaviorAttacker.cpp + BehaviorBrave.cpp + BehaviorBreeder.cpp + BehaviorDoNothing.cpp + BehaviorDayLightBurner.cpp + BehaviorCoward.cpp + BehaviorItemDropper.cpp + BehaviorItemFollower.cpp + BehaviorItemReplacer.cpp + BehaviorAttackerMelee.cpp + BehaviorAttackerRanged.cpp + BehaviorAttackerSuicideBomber.cpp + BehaviorWanderer.cpp +) + +SET (HDRS + Behavior.h + BehaviorAggressive.h + BehaviorAttacker.h + BehaviorBrave.h + BehaviorBreeder.h + BehaviorDoNothing.h + BehaviorDayLightBurner.h + BehaviorCoward.h + BehaviorItemDropper.h + BehaviorItemFollower.h + BehaviorItemReplacer.h + BehaviorAttackerMelee.h + BehaviorAttackerRanged.h + BehaviorAttackerSuicideBomber.h + BehaviorWanderer.h +) + +if(NOT MSVC) + add_library(Behaviors ${SRCS} ${HDRS}) +endif() diff --git a/src/Mobs/Blaze.cpp b/src/Mobs/Blaze.cpp index a48bfa886..444c8ef39 100644 --- a/src/Mobs/Blaze.cpp +++ b/src/Mobs/Blaze.cpp @@ -6,13 +6,33 @@ #include "../Entities/FireChargeEntity.h" +void FireballShootingFunction(cBehaviorAttackerRanged & a_Behavior, + cMonster & a_Attacker, cPawn & a_Attacked) +{ + UNUSED(a_Behavior); + + // Setting this higher gives us more wiggle room for attackrate + Vector3d Speed = a_Attacker.GetLookVector() * 20; + Speed.y = Speed.y + 3; + + auto FireCharge = cpp14::make_unique<cFireChargeEntity>(&a_Attacker, + a_Attacker.GetPosX(), a_Attacker.GetPosY() + 1, a_Attacker.GetPosZ(), Speed); + auto FireChargePtr = FireCharge.get(); + FireChargePtr->Initialize(std::move(FireCharge), *(a_Attacker.GetWorld())); +} cBlaze::cBlaze(void) : - super("Blaze", mtBlaze, "entity.blaze.hurt", "entity.blaze.death", 0.6, 1.8) + super(mtBlaze, "entity.blaze.hurt", "entity.blaze.death", 0.6, 1.8), + m_BehaviorAttackerRanged(FireballShootingFunction, 3, 15) { + m_EMPersonality = AGGRESSIVE; + m_BehaviorAttackerRanged.AttachToMonster(*this); + m_BehaviorDoNothing.AttachToMonster(*this); + m_BehaviorAggressive.AttachToMonster(*this); SetGravity(-8.0f); SetAirDrag(0.05f); + GetMonsterConfig("Blaze"); } @@ -27,30 +47,3 @@ void cBlaze::GetDrops(cItems & a_Drops, cEntity * a_Killer) AddRandomDropItem(a_Drops, 0, 1 + LootingLevel, E_ITEM_BLAZE_ROD); } } - - - - - -bool cBlaze::Attack(std::chrono::milliseconds a_Dt) -{ - if ((GetTarget() != nullptr) && (m_AttackCoolDownTicksLeft == 0)) - { - // Setting this higher gives us more wiggle room for attackrate - Vector3d Speed = GetLookVector() * 20; - Speed.y = Speed.y + 1; - - auto FireCharge = cpp14::make_unique<cFireChargeEntity>(this, GetPosX(), GetPosY() + 1, GetPosZ(), Speed); - auto FireChargePtr = FireCharge.get(); - if (!FireChargePtr->Initialize(std::move(FireCharge), *m_World)) - { - return false; - } - - ResetAttackCooldown(); - // ToDo: Shoot 3 fireballs instead of 1. - - return true; - } - return false; -} diff --git a/src/Mobs/Blaze.h b/src/Mobs/Blaze.h index ca755b626..aad6b4af6 100644 --- a/src/Mobs/Blaze.h +++ b/src/Mobs/Blaze.h @@ -1,16 +1,14 @@ - #pragma once -#include "AggressiveMonster.h" - - - - +#include "Monster.h" +#include "Behaviors/BehaviorAttackerRanged.h" +#include "Behaviors/BehaviorDoNothing.h" +#include "Behaviors/BehaviorAggressive.h" class cBlaze : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cBlaze(void); @@ -18,5 +16,12 @@ public: CLASS_PROTODEF(cBlaze) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual bool Attack(std::chrono::milliseconds a_Dt) override; + +private: + // tick behaviors + cBehaviorAttackerRanged m_BehaviorAttackerRanged; + cBehaviorDoNothing m_BehaviorDoNothing; + + // other behaviors + cBehaviorAggressive m_BehaviorAggressive; } ; diff --git a/src/Mobs/CMakeLists.txt b/src/Mobs/CMakeLists.txt index 55ae36e1e..51d9cd630 100644 --- a/src/Mobs/CMakeLists.txt +++ b/src/Mobs/CMakeLists.txt @@ -3,7 +3,6 @@ project (Cuberite) include_directories ("${PROJECT_SOURCE_DIR}/../") SET (SRCS - AggressiveMonster.cpp Bat.cpp Blaze.cpp CaveSpider.cpp @@ -21,12 +20,11 @@ SET (SRCS Monster.cpp Mooshroom.cpp Ocelot.cpp - PassiveAggressiveMonster.cpp - PassiveMonster.cpp Path.cpp PathFinder.cpp Pig.cpp Rabbit.cpp + MobPointer.cpp Sheep.cpp Skeleton.cpp Slime.cpp @@ -41,7 +39,6 @@ SET (SRCS ZombiePigman.cpp) SET (HDRS - AggressiveMonster.h Bat.h Blaze.h CaveSpider.h @@ -61,12 +58,11 @@ SET (HDRS MonsterTypes.h Mooshroom.h Ocelot.h - PassiveAggressiveMonster.h - PassiveMonster.h Path.h PathFinder.h Pig.h Rabbit.h + MobPointer.h Sheep.h Silverfish.h Skeleton.h diff --git a/src/Mobs/CaveSpider.cpp b/src/Mobs/CaveSpider.cpp index c6aa8af61..505e6a9df 100644 --- a/src/Mobs/CaveSpider.cpp +++ b/src/Mobs/CaveSpider.cpp @@ -2,14 +2,30 @@ #include "CaveSpider.h" #include "../World.h" +#include "Mobs/Behaviors/BehaviorAttackerMelee.h" + + +void CaveSpiderPostAttack(cBehaviorAttackerMelee & a_Behavior, cMonster & a_Attacker, cPawn & a_Attacked) +{ + UNUSED(a_Behavior); + UNUSED(a_Attacker); + // TODO: Easy = no poison, Medium = 7 seconds, Hard = 15 seconds + a_Attacked.AddEntityEffect(cEntityEffect::effPoison, 7 * 20, 0); +} -cCaveSpider::cCaveSpider(void) : - super("CaveSpider", mtCaveSpider, "entity.spider.hurt", "entity.spider.death", 0.7, 0.5) +cCaveSpider::cCaveSpider(void) + : super(mtCaveSpider, "entity.spider.hurt", "entity.spider.death", 0.7, 0.5) + , m_BehaviorAttackerMelee(CaveSpiderPostAttack) { + m_EMPersonality = AGGRESSIVE; + m_BehaviorAttackerMelee.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + m_BehaviorAggressive.AttachToMonster(*this); + GetMonsterConfig("CaveSpider"); } @@ -32,25 +48,6 @@ void cCaveSpider::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -bool cCaveSpider::Attack(std::chrono::milliseconds a_Dt) -{ - if (!super::Attack(a_Dt)) - { - return false; - } - - if (GetTarget()->IsPawn()) - { - // TODO: Easy = no poison, Medium = 7 seconds, Hard = 15 seconds - static_cast<cPawn *>(GetTarget())->AddEntityEffect(cEntityEffect::effPoison, 7 * 20, 0); - } - return true; -} - - - - - void cCaveSpider::GetDrops(cItems & a_Drops, cEntity * a_Killer) { unsigned int LootingLevel = 0; diff --git a/src/Mobs/CaveSpider.h b/src/Mobs/CaveSpider.h index cf4b8e17c..c7a660c75 100644 --- a/src/Mobs/CaveSpider.h +++ b/src/Mobs/CaveSpider.h @@ -1,15 +1,17 @@ #pragma once -#include "AggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorAttackerMelee.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Behaviors/BehaviorAggressive.h" class cCaveSpider : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cCaveSpider(void); @@ -17,8 +19,14 @@ public: CLASS_PROTODEF(cCaveSpider) virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - virtual bool Attack(std::chrono::milliseconds a_Dt) override; virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; + + // tick behaviors + cBehaviorAttackerMelee m_BehaviorAttackerMelee; + cBehaviorWanderer m_BehaviorWanderer; + + // other behaviors + cBehaviorAggressive m_BehaviorAggressive; } ; diff --git a/src/Mobs/Chicken.cpp b/src/Mobs/Chicken.cpp index 1068295e6..556f02319 100644 --- a/src/Mobs/Chicken.cpp +++ b/src/Mobs/Chicken.cpp @@ -5,53 +5,18 @@ - - - - cChicken::cChicken(void) : - super("Chicken", mtChicken, "entity.chicken.hurt", "entity.chicken.death", 0.3, 0.4), - m_EggDropTimer(0) + super(mtChicken, "entity.chicken.hurt", "entity.chicken.death", 0.3, 0.4) { SetGravity(-2.0f); SetAirDrag(0.0f); -} - - - - -void cChicken::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - super::Tick(a_Dt, a_Chunk); - if (!IsTicking()) - { - // The base class tick destroyed us - return; - } - - if (IsBaby()) - { - return; // Babies don't lay eggs - } - - if ((m_EggDropTimer == 6000) && GetRandomProvider().RandBool()) - { - cItems Drops; - m_EggDropTimer = 0; - Drops.push_back(cItem(E_ITEM_EGG, 1)); - m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ(), 10); - } - else if (m_EggDropTimer == 12000) - { - cItems Drops; - m_EggDropTimer = 0; - Drops.push_back(cItem(E_ITEM_EGG, 1)); - m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ(), 10); - } - else - { - m_EggDropTimer++; - } + m_EMPersonality = PASSIVE; + m_BehaviorBreeder.AttachToMonster(*this); + m_BehaviorCoward.AttachToMonster(*this); + m_BehaviorItemFollower.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + m_BehaviorItemDropper.AttachToMonster(*this); + GetMonsterConfig("Chicken"); } diff --git a/src/Mobs/Chicken.h b/src/Mobs/Chicken.h index 3be338b15..f60f3672f 100644 --- a/src/Mobs/Chicken.h +++ b/src/Mobs/Chicken.h @@ -1,15 +1,18 @@ #pragma once -#include "PassiveMonster.h" - - +#include "Behaviors/BehaviorBreeder.h" +#include "Behaviors/BehaviorItemFollower.h" +#include "Behaviors/BehaviorCoward.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Behaviors/BehaviorItemDropper.h" +#include "Monster.h" class cChicken : - public cPassiveMonster + public cMonster { - typedef cPassiveMonster super; + typedef cMonster super; public: cChicken(void); @@ -17,7 +20,6 @@ public: CLASS_PROTODEF(cChicken) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; virtual void GetFollowedItems(cItems & a_Items) override { @@ -27,8 +29,13 @@ public: virtual void HandleFalling(void) override; private: + // Tick controlling behaviors + cBehaviorBreeder m_BehaviorBreeder; + cBehaviorItemFollower m_BehaviorItemFollower; + cBehaviorCoward m_BehaviorCoward; + cBehaviorWanderer m_BehaviorWanderer; - int m_EggDropTimer; + cBehaviorItemDropper m_BehaviorItemDropper; } ; diff --git a/src/Mobs/Cow.cpp b/src/Mobs/Cow.cpp index 9736fe440..6c142b04b 100644 --- a/src/Mobs/Cow.cpp +++ b/src/Mobs/Cow.cpp @@ -6,13 +6,17 @@ - - - - cCow::cCow(void) : - super("Cow", mtCow, "entity.cow.hurt", "entity.cow.death", 0.9, 1.3) + super(mtCow, "entity.cow.hurt", "entity.cow.death", 0.9, 1.3), + m_BehaviorItemReplacer(E_ITEM_BUCKET, E_ITEM_MILK) { + m_EMPersonality = PASSIVE; + m_BehaviorBreeder.AttachToMonster(*this); + m_BehaviorCoward.AttachToMonster(*this); + m_BehaviorItemFollower.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + m_BehaviorItemReplacer.AttachToMonster(*this); + GetMonsterConfig("Cow"); } @@ -29,22 +33,3 @@ void cCow::GetDrops(cItems & a_Drops, cEntity * a_Killer) AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_LEATHER); AddRandomDropItem(a_Drops, 1, 3 + LootingLevel, IsOnFire() ? E_ITEM_STEAK : E_ITEM_RAW_BEEF); } - - - - - -void cCow::OnRightClicked(cPlayer & a_Player) -{ - super::OnRightClicked(a_Player); - - short HeldItem = a_Player.GetEquippedItem().m_ItemType; - if (HeldItem == E_ITEM_BUCKET) - { - if (!a_Player.IsGameModeCreative()) - { - a_Player.GetInventory().RemoveOneEquippedItem(); - a_Player.GetInventory().AddItem(E_ITEM_MILK); - } - } -} diff --git a/src/Mobs/Cow.h b/src/Mobs/Cow.h index 569c6e619..58380065a 100644 --- a/src/Mobs/Cow.h +++ b/src/Mobs/Cow.h @@ -1,30 +1,36 @@ - #pragma once -#include "PassiveMonster.h" - - - +#include "Behaviors/BehaviorBreeder.h" +#include "Behaviors/BehaviorItemFollower.h" +#include "Behaviors/BehaviorCoward.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Behaviors/BehaviorItemReplacer.h" +#include "Monster.h" - -class cCow : - public cPassiveMonster +class cCow : public cMonster { - typedef cPassiveMonster super; - public: cCow(); + ~cCow() {} + typedef cMonster super; CLASS_PROTODEF(cCow) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual void OnRightClicked(cPlayer & a_Player) override; virtual void GetFollowedItems(cItems & a_Items) override { a_Items.Add(E_ITEM_WHEAT); } - +private: + // Tick controlling behaviors + cBehaviorBreeder m_BehaviorBreeder; + cBehaviorItemFollower m_BehaviorItemFollower; + cBehaviorCoward m_BehaviorCoward; + cBehaviorWanderer m_BehaviorWanderer; + + // Non tick controlling behaviors + cBehaviorItemReplacer m_BehaviorItemReplacer; } ; diff --git a/src/Mobs/Creeper.cpp b/src/Mobs/Creeper.cpp index 84b68cf7c..8fb46a3c3 100644 --- a/src/Mobs/Creeper.cpp +++ b/src/Mobs/Creeper.cpp @@ -11,49 +11,13 @@ cCreeper::cCreeper(void) : - super("Creeper", mtCreeper, "entity.creeper.hurt", "entity.creeper.death", 0.6, 1.8), - m_bIsBlowing(false), - m_bIsCharged(false), - m_BurnedWithFlintAndSteel(false), - m_ExplodingTimer(0) + super(mtCreeper, "entity.creeper.hurt", "entity.creeper.death", 0.6, 1.8) { -} - - - - - -void cCreeper::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - super::Tick(a_Dt, a_Chunk); - if (!IsTicking()) - { - // The base class tick destroyed us - return; - } - - if (((GetTarget() == nullptr) || !TargetIsInRange()) && !m_BurnedWithFlintAndSteel) - { - if (m_bIsBlowing) - { - m_ExplodingTimer = 0; - m_bIsBlowing = false; - m_World->BroadcastEntityMetadata(*this); - } - } - else - { - if (m_bIsBlowing) - { - m_ExplodingTimer += 1; - } - - if ((m_ExplodingTimer == 30) && (GetHealth() > 0.0)) // only explode when not already dead - { - m_World->DoExplosionAt((m_bIsCharged ? 5 : 3), GetPosX(), GetPosY(), GetPosZ(), false, esMonster, this); - Destroy(); // Just in case we aren't killed by the explosion - } - } + m_EMPersonality = AGGRESSIVE; + m_BehaviorAttackerSuicideBomber.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + m_BehaviorAggressive.AttachToMonster(*this); + GetMonsterConfig("Creeper"); } @@ -62,7 +26,7 @@ void cCreeper::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cCreeper::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - if (m_ExplodingTimer == 30) + if (IsBlowing()) { // Exploded creepers drop naught but charred flesh, which Minecraft doesn't have return; @@ -106,59 +70,25 @@ void cCreeper::GetDrops(cItems & a_Drops, cEntity * a_Killer) -bool cCreeper::DoTakeDamage(TakeDamageInfo & a_TDI) +bool cCreeper::IsBlowing(void) const { - if (!super::DoTakeDamage(a_TDI)) - { - return false; - } - - if (a_TDI.DamageType == dtLightning) - { - m_bIsCharged = true; - } - - m_World->BroadcastEntityMetadata(*this); - return true; + return m_BehaviorAttackerSuicideBomber.IsBlowing(); } -bool cCreeper::Attack(std::chrono::milliseconds a_Dt) +bool cCreeper::IsCharged(void) const { - UNUSED(a_Dt); - - if (!m_bIsBlowing) - { - m_World->BroadcastSoundEffect("entity.creeper.primed", GetPosX(), GetPosY(), GetPosZ(), 1.f, (0.75f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)); - m_bIsBlowing = true; - m_World->BroadcastEntityMetadata(*this); - - return true; - } - return false; + return m_BehaviorAttackerSuicideBomber.IsCharged(); } -void cCreeper::OnRightClicked(cPlayer & a_Player) +bool cCreeper::IsBurnedWithFlintAndSteel(void) const { - super::OnRightClicked(a_Player); - - if ((a_Player.GetEquippedItem().m_ItemType == E_ITEM_FLINT_AND_STEEL)) - { - if (!a_Player.IsGameModeCreative()) - { - a_Player.UseEquippedItem(); - } - m_World->BroadcastSoundEffect("entity.creeper.primed", GetPosX(), GetPosY(), GetPosZ(), 1.f, (0.75f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)); - m_bIsBlowing = true; - m_World->BroadcastEntityMetadata(*this); - m_BurnedWithFlintAndSteel = true; - } + return m_BehaviorAttackerSuicideBomber.IsBurnedWithFlintAndSteel(); } - diff --git a/src/Mobs/Creeper.h b/src/Mobs/Creeper.h index aea36def3..85b94c397 100644 --- a/src/Mobs/Creeper.h +++ b/src/Mobs/Creeper.h @@ -1,16 +1,17 @@ #pragma once -#include "AggressiveMonster.h" - - +#include "Monster.h" +#include "Behaviors/BehaviorAttackerSuicideBomber.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Behaviors/BehaviorAggressive.h" class cCreeper : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cCreeper(void); @@ -18,20 +19,18 @@ public: CLASS_PROTODEF(cCreeper) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override; - virtual bool Attack(std::chrono::milliseconds a_Dt) override; - virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - virtual void OnRightClicked(cPlayer & a_Player) override; - bool IsBlowing(void) const {return m_bIsBlowing; } - bool IsCharged(void) const {return m_bIsCharged; } - bool IsBurnedWithFlintAndSteel(void) const {return m_BurnedWithFlintAndSteel; } + bool IsBlowing(void) const; + bool IsCharged(void) const; + bool IsBurnedWithFlintAndSteel(void) const; private: + // tick behaviors + cBehaviorAttackerSuicideBomber m_BehaviorAttackerSuicideBomber; + cBehaviorWanderer m_BehaviorWanderer; - bool m_bIsBlowing, m_bIsCharged, m_BurnedWithFlintAndSteel; - int m_ExplodingTimer; - + // other behaviors + cBehaviorAggressive m_BehaviorAggressive; } ; diff --git a/src/Mobs/EnderDragon.cpp b/src/Mobs/EnderDragon.cpp index 360fe581b..f33a9d888 100644 --- a/src/Mobs/EnderDragon.cpp +++ b/src/Mobs/EnderDragon.cpp @@ -9,8 +9,11 @@ cEnderDragon::cEnderDragon(void) : // TODO: Vanilla source says this, but is it right? Dragons fly, they don't stand - super("EnderDragon", mtEnderDragon, "entity.enderdragon.hurt", "entity.enderdragon.death", 16.0, 8.0) + super(mtEnderDragon, "entity.enderdragon.hurt", "entity.enderdragon.death", 16.0, 8.0) { + m_EMPersonality = AGGRESSIVE; + m_BehaviorDoNothing.AttachToMonster(*this); + GetMonsterConfig("EnderDragon"); } diff --git a/src/Mobs/EnderDragon.h b/src/Mobs/EnderDragon.h index 43acdcd54..b756aab17 100644 --- a/src/Mobs/EnderDragon.h +++ b/src/Mobs/EnderDragon.h @@ -1,16 +1,13 @@ #pragma once -#include "AggressiveMonster.h" - - - - +#include "Monster.h" +#include "Behaviors/BehaviorDoNothing.h" class cEnderDragon : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cEnderDragon(void); @@ -18,6 +15,9 @@ public: CLASS_PROTODEF(cEnderDragon) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; + +private: + cBehaviorDoNothing m_BehaviorDoNothing; } ; diff --git a/src/Mobs/Enderman.cpp b/src/Mobs/Enderman.cpp index 5cfe0d4cd..3d874e4d6 100644 --- a/src/Mobs/Enderman.cpp +++ b/src/Mobs/Enderman.cpp @@ -74,11 +74,13 @@ protected: cEnderman::cEnderman(void) : - super("Enderman", mtEnderman, "entity.endermen.hurt", "entity.endermen.death", 0.5, 2.9), + super(mtEnderman, "entity.endermen.hurt", "entity.endermen.death", 0.5, 2.9), m_bIsScreaming(false), CarriedBlock(E_BLOCK_AIR), CarriedMeta(0) { + m_EMPersonality = PASSIVE; + GetMonsterConfig("Enderman"); } @@ -98,67 +100,6 @@ void cEnderman::GetDrops(cItems & a_Drops, cEntity * a_Killer) -void cEnderman::CheckEventSeePlayer(cChunk & a_Chunk) -{ - if (GetTarget() != nullptr) - { - return; - } - - cPlayerLookCheck Callback(GetPosition(), m_SightDistance); - if (m_World->ForEachPlayer(Callback)) - { - return; - } - - ASSERT(Callback.GetPlayer() != nullptr); - - if (!CheckLight()) - { - // Insufficient light for enderman to become aggravated - // TODO: Teleport to a suitable location - return; - } - - if (!Callback.GetPlayer()->CanMobsTarget()) - { - return; - } - - // Target the player - cMonster::EventSeePlayer(Callback.GetPlayer(), a_Chunk); - m_EMState = CHASING; - m_bIsScreaming = true; - GetWorld()->BroadcastEntityMetadata(*this); -} - - - - - -void cEnderman::CheckEventLostPlayer(void) -{ - super::CheckEventLostPlayer(); - if (!CheckLight()) - { - EventLosePlayer(); - } -} - - - - - -void cEnderman::EventLosePlayer() -{ - super::EventLosePlayer(); - m_bIsScreaming = false; - GetWorld()->BroadcastEntityMetadata(*this); -} - - - - bool cEnderman::CheckLight() { @@ -197,7 +138,7 @@ void cEnderman::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) // Take damage when touching water, drowning damage seems to be most appropriate if (CheckRain() || IsSwimming()) { - EventLosePlayer(); + // EventLosePlayer(); //mobTodo TakeDamage(dtDrowning, nullptr, 1, 0); // TODO teleport to a safe location } diff --git a/src/Mobs/Enderman.h b/src/Mobs/Enderman.h index c9ffbeaba..c3568e1db 100644 --- a/src/Mobs/Enderman.h +++ b/src/Mobs/Enderman.h @@ -1,16 +1,14 @@ - #pragma once - -#include "PassiveAggressiveMonster.h" +#include "Monster.h" class cEnderman : - public cPassiveAggressiveMonster + public cMonster { - typedef cPassiveAggressiveMonster super; + typedef cMonster super; public: cEnderman(void); @@ -18,9 +16,6 @@ public: CLASS_PROTODEF(cEnderman) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual void CheckEventSeePlayer(cChunk & a_Chunk) override; - virtual void CheckEventLostPlayer(void) override; - virtual void EventLosePlayer(void) override; virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; bool IsScreaming(void) const {return m_bIsScreaming; } diff --git a/src/Mobs/Ghast.cpp b/src/Mobs/Ghast.cpp index 2488e63b1..913d88c7f 100644 --- a/src/Mobs/Ghast.cpp +++ b/src/Mobs/Ghast.cpp @@ -9,8 +9,11 @@ cGhast::cGhast(void) : - super("Ghast", mtGhast, "entity.ghast.hurt", "entity.ghast.death", 4, 4) + super(mtGhast, "entity.ghast.hurt", "entity.ghast.death", 4, 4) { + m_EMPersonality = AGGRESSIVE; + m_BehaviorDoNothing.AttachToMonster(*this); + GetMonsterConfig("Ghast"); } @@ -31,8 +34,8 @@ void cGhast::GetDrops(cItems & a_Drops, cEntity * a_Killer) - -bool cGhast::Attack(std::chrono::milliseconds a_Dt) +// mobTODO +/*bool cGhast::Attack(std::chrono::milliseconds a_Dt) { if ((GetTarget() != nullptr) && (m_AttackCoolDownTicksLeft == 0)) { @@ -51,7 +54,7 @@ bool cGhast::Attack(std::chrono::milliseconds a_Dt) return true; } return false; -} +}*/ diff --git a/src/Mobs/Ghast.h b/src/Mobs/Ghast.h index a41a72ddc..014e9c0dc 100644 --- a/src/Mobs/Ghast.h +++ b/src/Mobs/Ghast.h @@ -1,16 +1,14 @@ - #pragma once - -#include "AggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorDoNothing.h" class cGhast : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cGhast(void); @@ -18,9 +16,11 @@ public: CLASS_PROTODEF(cGhast) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual bool Attack(std::chrono::milliseconds a_Dt) override; bool IsCharging(void) const {return false; } + +private: + cBehaviorDoNothing m_BehaviorDoNothing; } ; diff --git a/src/Mobs/Giant.cpp b/src/Mobs/Giant.cpp index 0f235e10f..3dc621865 100644 --- a/src/Mobs/Giant.cpp +++ b/src/Mobs/Giant.cpp @@ -8,9 +8,11 @@ cGiant::cGiant(void) : - super("Giant", mtGiant, "entity.zombie.hurt", "entity.zombie.death", 3.6, 10.8) + super(mtGiant, "entity.zombie.hurt", "entity.zombie.death", 3.6, 10.8) { - + m_EMPersonality = AGGRESSIVE; + m_BehaviorDoNothing.AttachToMonster(*this); + GetMonsterConfig("Giant"); } diff --git a/src/Mobs/Giant.h b/src/Mobs/Giant.h index 70e93894c..9cd7c2865 100644 --- a/src/Mobs/Giant.h +++ b/src/Mobs/Giant.h @@ -1,16 +1,16 @@ #pragma once -#include "AggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorDoNothing.h" class cGiant : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cGiant(void); @@ -18,6 +18,9 @@ public: CLASS_PROTODEF(cGiant) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; + +private: + cBehaviorDoNothing m_BehaviorDoNothing; } ; diff --git a/src/Mobs/Guardian.cpp b/src/Mobs/Guardian.cpp index f36f98ea8..c5fc0405d 100644 --- a/src/Mobs/Guardian.cpp +++ b/src/Mobs/Guardian.cpp @@ -9,8 +9,10 @@ cGuardian::cGuardian(void) : - super("Guardian", mtGuardian, "entity.guardian.hurt", "entity.guardian.death", 0.875, 0.8) + super(mtGuardian, "entity.guardian.hurt", "entity.guardian.death", 0.875, 0.8) { + m_EMPersonality = AGGRESSIVE; + GetMonsterConfig("Guardian"); } diff --git a/src/Mobs/Guardian.h b/src/Mobs/Guardian.h index 289654f57..988f36832 100644 --- a/src/Mobs/Guardian.h +++ b/src/Mobs/Guardian.h @@ -1,16 +1,16 @@ #pragma once -#include "AggressiveMonster.h" +#include "Monster.h" class cGuardian : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cGuardian(); diff --git a/src/Mobs/Horse.cpp b/src/Mobs/Horse.cpp index 13630b0e3..c3d805b69 100644 --- a/src/Mobs/Horse.cpp +++ b/src/Mobs/Horse.cpp @@ -11,7 +11,7 @@ cHorse::cHorse(int Type, int Color, int Style, int TameTimes) : - super("Horse", mtHorse, "entity.horse.hurt", "entity.horse.death", 1.4, 1.6), + super(mtHorse, "entity.horse.hurt", "entity.horse.death", 1.4, 1.6), m_bHasChest(false), m_bIsEating(false), m_bIsRearing(false), @@ -27,6 +27,12 @@ cHorse::cHorse(int Type, int Color, int Style, int TameTimes) : m_RearTickCount(0), m_MaxSpeed(14.0) { + m_EMPersonality = PASSIVE; + m_BehaviorBreeder.AttachToMonster(*this); + m_BehaviorCoward.AttachToMonster(*this); + m_BehaviorItemFollower.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + GetMonsterConfig("Horse"); } @@ -185,19 +191,6 @@ void cHorse::GetDrops(cItems & a_Drops, cEntity * a_Killer) -void cHorse::InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - // If horse is tame and someone is sitting on it, don't walk around - if ((!m_bIsTame) || (m_Attachee == nullptr)) - { - super::InStateIdle(a_Dt, a_Chunk); - } -} - - - - - void cHorse::HandleSpeedFromAttachee(float a_Forward, float a_Sideways) { if ((m_bIsTame) && (m_bIsSaddled)) diff --git a/src/Mobs/Horse.h b/src/Mobs/Horse.h index 82026a0ee..e96a1f8a9 100644 --- a/src/Mobs/Horse.h +++ b/src/Mobs/Horse.h @@ -1,16 +1,20 @@ #pragma once -#include "PassiveMonster.h" +#include "Behaviors/BehaviorBreeder.h" +#include "Behaviors/BehaviorItemFollower.h" +#include "Behaviors/BehaviorCoward.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Monster.h" class cHorse : - public cPassiveMonster + public cMonster { - typedef cPassiveMonster super; + typedef cMonster super; public: cHorse(int Type, int Color, int Style, int TameTimes); @@ -18,7 +22,6 @@ public: CLASS_PROTODEF(cHorse) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual void InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; virtual void HandleSpeedFromAttachee(float a_Forward, float a_Sideways) override; virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; virtual void OnRightClicked(cPlayer & a_Player) override; @@ -39,8 +42,12 @@ public: a_Items.Add(E_ITEM_GOLDEN_CARROT); a_Items.Add(E_ITEM_GOLDEN_APPLE); } - private: + // Tick controlling behaviors + cBehaviorBreeder m_BehaviorBreeder; + cBehaviorItemFollower m_BehaviorItemFollower; + cBehaviorCoward m_BehaviorCoward; + cBehaviorWanderer m_BehaviorWanderer; bool m_bHasChest, m_bIsEating, m_bIsRearing, m_bIsMouthOpen, m_bIsTame, m_bIsSaddled; int m_Type, m_Color, m_Style, m_Armour, m_TimesToTame, m_TameAttemptTimes, m_RearTickCount; diff --git a/src/Mobs/IronGolem.cpp b/src/Mobs/IronGolem.cpp index 448ed8dc0..a6fd5e8c4 100644 --- a/src/Mobs/IronGolem.cpp +++ b/src/Mobs/IronGolem.cpp @@ -8,8 +8,11 @@ cIronGolem::cIronGolem(void) : - super("IronGolem", mtIronGolem, "entity.irongolem.hurt", "entity.irongolem.death", 1.4, 2.9) + super(mtIronGolem, "entity.irongolem.hurt", "entity.irongolem.death", 1.4, 2.9) { + m_EMPersonality = PASSIVE; + m_BehaviorWanderer.AttachToMonster(*this); + GetMonsterConfig("IronGolem"); } diff --git a/src/Mobs/IronGolem.h b/src/Mobs/IronGolem.h index 7d35686e7..ea1cc759d 100644 --- a/src/Mobs/IronGolem.h +++ b/src/Mobs/IronGolem.h @@ -1,16 +1,16 @@ #pragma once -#include "PassiveAggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorWanderer.h" class cIronGolem : - public cPassiveAggressiveMonster + public cMonster { - typedef cPassiveAggressiveMonster super; + typedef cMonster super; public: cIronGolem(void); @@ -22,6 +22,9 @@ public: // Iron golems do not drown nor float virtual void HandleAir(void) override {} virtual void SetSwimState(cChunk & a_Chunk) override {} + +private: + cBehaviorWanderer m_BehaviorWanderer; } ; diff --git a/src/Mobs/MagmaCube.cpp b/src/Mobs/MagmaCube.cpp index 4d70a0291..2891605c8 100644 --- a/src/Mobs/MagmaCube.cpp +++ b/src/Mobs/MagmaCube.cpp @@ -7,9 +7,12 @@ cMagmaCube::cMagmaCube(int a_Size) : - super("MagmaCube", mtMagmaCube, Printf("entity.%smagmacube.hurt", GetSizeName(a_Size).c_str()), Printf("entity.%smagmacube.death", GetSizeName(a_Size).c_str()), 0.6 * a_Size, 0.6 * a_Size), + super(mtMagmaCube, Printf("entity.%smagmacube.hurt", GetSizeName(a_Size).c_str()), Printf("entity.%smagmacube.death", GetSizeName(a_Size).c_str()), 0.6 * a_Size, 0.6 * a_Size), m_Size(a_Size) { + m_EMPersonality = AGGRESSIVE; + m_BehaviorDoNothing.AttachToMonster(*this); + GetMonsterConfig("MagmaCube"); } diff --git a/src/Mobs/MagmaCube.h b/src/Mobs/MagmaCube.h index 5fc8105ba..4cfc1c27d 100644 --- a/src/Mobs/MagmaCube.h +++ b/src/Mobs/MagmaCube.h @@ -1,15 +1,15 @@ #pragma once -#include "AggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorDoNothing.h" class cMagmaCube : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: /** Creates a MagmaCube of the specified size; with 1 being the smallest */ @@ -28,6 +28,9 @@ protected: /** Size of the MagmaCube, with 1 being the smallest */ int m_Size; + +private: + cBehaviorDoNothing m_BehaviorDoNothing; } ; diff --git a/src/Mobs/MobPointer.cpp b/src/Mobs/MobPointer.cpp new file mode 100644 index 000000000..8fb64867d --- /dev/null +++ b/src/Mobs/MobPointer.cpp @@ -0,0 +1,78 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "../Entities/Pawn.h" +#include "MobPointer.h" +#include "../World.h" + +cMobPointer::cMobPointer(cPawn * a_Pointer) : m_Pointer(a_Pointer) +{ + // Constructor +} + + + + + +cMobPointer::cMobPointer(const cMobPointer & a_MobPointer) : m_Pointer(a_MobPointer.m_Pointer) +{ + // Copy constructor +} + + + + + +cMobPointer::cMobPointer(cMobPointer && a_MobPointer) +{ + // move Constructor + m_Pointer = a_MobPointer.m_Pointer; + a_MobPointer.m_Pointer = nullptr; +} + + + + + +cMobPointer& cMobPointer::operator=(const cMobPointer& a_MobPointer) +{ + // Copy assignment operator + m_Pointer = a_MobPointer.m_Pointer; + return *this; +} + + + + + +cMobPointer& cMobPointer::operator=(cMobPointer&& a_MobPointer) +{ + // Move assignment operator + m_Pointer = a_MobPointer.m_Pointer; + a_MobPointer.m_Pointer = nullptr; + return *this; +} + + + + + +void cMobPointer::SetPointer(cPawn * a_Pointer) +{ + m_Pointer = a_Pointer; +} + + + + + +cPawn * cMobPointer::GetPointer(cWorld * a_CurrentWorld) +{ + if (m_Pointer != nullptr) + { + if (!m_Pointer->IsTicking() || (m_Pointer->GetWorld() != a_CurrentWorld)) + { + m_Pointer = nullptr; + } + } + return m_Pointer; +} diff --git a/src/Mobs/MobPointer.h b/src/Mobs/MobPointer.h new file mode 100644 index 000000000..3534d760d --- /dev/null +++ b/src/Mobs/MobPointer.h @@ -0,0 +1,35 @@ +#pragma once + +/** This class allows mobs to hold pointers to other mobs/players in a safe manner. +When calling GetPointer(), it first checks if the Monster/Player being pointed to is still valid. +If not, it returns a nullptr. The returned raw pointer must be used locally and then discarded. +it MUST NOT be preserved across ticks. +*/ + +class cPawn; +class cWorld; +class cMobPointer +{ +public: + cMobPointer(cPawn * a_Pointer); // Constructor + cMobPointer(const cMobPointer & a_MobPointer); // Copy constructor + cMobPointer(cMobPointer && a_MobPointer); // Move constructor + cMobPointer& operator=(const cMobPointer& a_MobPointer); // Copy assignment operator + cMobPointer& operator=(cMobPointer&& a_MobPointer); // Move assignment operator + + void SetPointer(cPawn * a_Pointer); // set Pointer + + /** Returns the raw pointer. The returned raw pointer must + be used locally and then discarded. it MUST NOT be preserved across ticks. + Returns null if raw pointer is null. Returns null if mob is destroyed or moved worlds. + Must be called at least once per tick, even if the raw pointer is not going to be used that tick. */ + cPawn * GetPointer(cWorld * a_CurrentWorld); + + +private: + cPawn * m_Pointer; +} ; + + + + diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 944e8aa94..bf6eda50c 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -19,6 +19,9 @@ #include "PathFinder.h" #include "../Entities/LeashKnot.h" +// Temporary pathfinder hack +#include "Behaviors/BehaviorDayLightBurner.h" + @@ -80,10 +83,12 @@ static const struct //////////////////////////////////////////////////////////////////////////////// // cMonster: -cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height) +cMonster::cMonster(eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height) : super(etMonster, a_Width, a_Height) - , m_EMState(IDLE) , m_EMPersonality(AGGRESSIVE) + , m_BehaviorBreederPointer(nullptr) + , m_BehaviorAttackerPointer(nullptr) + , m_NearestPlayerIsStale(true) , m_PathFinder(a_Width, a_Height) , m_PathfinderActivated(false) , m_JumpCoolDown(0) @@ -94,10 +99,6 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_CustomNameAlwaysVisible(false) , m_SoundHurt(a_SoundHurt) , m_SoundDeath(a_SoundDeath) - , m_AttackRate(3) - , m_AttackDamage(1) - , m_AttackRange(1) - , m_AttackCoolDownTicksLeft(0) , m_SightDistance(25) , m_DropChanceWeapon(0.085f) , m_DropChanceHelmet(0.085f) @@ -105,8 +106,6 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_DropChanceLeggings(0.085f) , m_DropChanceBoots(0.085f) , m_CanPickUpLoot(true) - , m_TicksSinceLastDamaged(100) - , m_BurnsInDaylight(false) , m_RelativeWalkSpeed(1) , m_Age(1) , m_AgingTimer(20 * 60 * 20) // about 20 minutes @@ -115,12 +114,14 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_LeashToPos(nullptr) , m_IsLeashActionJustDone(false) , m_CanBeLeashed(GetMobFamily() == eFamily::mfPassive) - , m_Target(nullptr) + , m_LookingAt(nullptr) + , m_CurrentTickControllingBehavior(nullptr) + , m_NewTickControllingBehavior(nullptr) + , m_PinnedBehavior(nullptr) + , m_TickControllingBehaviorState(Normal) + , m_TicksSinceLastDamaged(1000) { - if (!a_ConfigName.empty()) - { - GetMonsterConfig(a_ConfigName); - } + } @@ -129,7 +130,7 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A cMonster::~cMonster() { - ASSERT(GetTarget() == nullptr); + } @@ -138,6 +139,7 @@ cMonster::~cMonster() void cMonster::Destroy(bool a_ShouldBroadcast) { + // mobTodo behavior for leash if (IsLeashed()) { cEntity * LeashedTo = GetLeashedTo(); @@ -159,7 +161,11 @@ void cMonster::Destroy(bool a_ShouldBroadcast) void cMonster::Destroyed() { - SetTarget(nullptr); // Tell them we're no longer targeting them. + for (cBehavior * Behavior : m_AttachedDestroyBehaviors) + { + Behavior->Destroyed(); + } + super::Destroyed(); } @@ -183,6 +189,7 @@ void cMonster::SpawnOn(cClientHandle & a_Client) void cMonster::MoveToWayPoint(cChunk & a_Chunk) { + UNUSED(a_Chunk); if ((m_NextWayPointPosition - GetPosition()).SqrLength() < WAYPOINT_RADIUS * WAYPOINT_RADIUS) { return; @@ -282,6 +289,8 @@ void cMonster::StopMovingToPosition() void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { + // LOGD("mobDebug - Monster tick begins"); + m_NearestPlayerIsStale = true; super::Tick(a_Dt, a_Chunk); if (!IsTicking()) { @@ -290,12 +299,6 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } GET_AND_VERIFY_CURRENT_CHUNK(Chunk, POSX_TOINT, POSZ_TOINT); - ASSERT((GetTarget() == nullptr) || (GetTarget()->IsPawn() && (GetTarget()->GetWorld() == GetWorld()))); - if (m_AttackCoolDownTicksLeft > 0) - { - m_AttackCoolDownTicksLeft -= 1; - } - if (m_Health <= 0) { // The mob is dead, but we're still animating the "puff" they leave when they die @@ -307,26 +310,121 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) return; } - if (m_TicksSinceLastDamaged < 100) + // All behaviors can execute PostTick and PreTick. + // These are for bookkeeping or passive actions like laying eggs. + // They MUST NOT control mob movement or interefere with the main Tick. + for (cBehavior * Behavior : m_AttachedPreTickBehaviors) { - ++m_TicksSinceLastDamaged; + // LOGD("mobDebug - preTick"); + ASSERT(Behavior != nullptr); + Behavior->PreTick(a_Dt, a_Chunk); } - if ((GetTarget() != nullptr)) - { - ASSERT(GetTarget()->IsTicking()); - if (GetTarget()->IsPlayer()) + // Note 1: Each monster tick, at most one Behavior executes its Tick method. + // Note 2: Each monster tick, exactly one of these is executed: + // ControlStarting, Tick, ControlEnding + + // If we're in a regular tick cycle + if (m_TickControllingBehaviorState == Normal) + { + if (IsLeashed()) + { + // do not tick behaviors + // mobTodo temporary leash special case. Needs a behavior eventually. + } + else { - if (!static_cast<cPlayer *>(GetTarget())->CanMobsTarget()) + // STEP 1: Decide who'll control us this tick + + if (m_PinnedBehavior != nullptr) + { + // A behavior is pinned. We give it control automatically. + m_NewTickControllingBehavior = m_PinnedBehavior; + } + else + { + // ask the behaviors sequentially if they are interested in controlling this mob + // Stop at the first one that says yes. + m_NewTickControllingBehavior = nullptr; + for (cBehavior * Behavior : m_AttachedTickBehaviors) + { + if (Behavior->IsControlDesired(a_Dt, a_Chunk)) + { + m_NewTickControllingBehavior = Behavior; + break; + } + } + } + ASSERT(m_NewTickControllingBehavior != nullptr); // it's not OK if no one asks for control + + // STEP 2: decide whether to tick or do behavior swapping + + if (m_CurrentTickControllingBehavior == m_NewTickControllingBehavior) { - SetTarget(nullptr); - m_EMState = IDLE; + // The Behavior asking for control is the same as the behavior from last tick. + // Nothing special, just tick it. + m_CurrentTickControllingBehavior->Tick(a_Dt, a_Chunk); } + else if (m_CurrentTickControllingBehavior == nullptr) + { + // first behavior to ever control + m_TickControllingBehaviorState = NewControlStarting; + } + else + { + // The behavior asking for control is not the same as the behavior from last tick. + // Begin the control swapping process. + m_TickControllingBehaviorState = OldControlEnding; + } + } + + } + + // STEP 3: Behavior swapping if needed + + // Make the current controlling behavior clean up + if (m_TickControllingBehaviorState == OldControlEnding) + { + ASSERT(m_CurrentTickControllingBehavior != nullptr); + if (m_CurrentTickControllingBehavior->ControlEnding(a_Dt, a_Chunk)) + { + // The current behavior told us it is ready for letting go of control + m_TickControllingBehaviorState = NewControlStarting; + } + else + { + // The current behavior is not ready for releasing control. We'll execute ControlEnding + // next tick too. + m_TickControllingBehaviorState = OldControlEnding; + } + } + // Make the new controlling behavior set up + else if (m_TickControllingBehaviorState == NewControlStarting) + { + ASSERT(m_NewTickControllingBehavior != nullptr); + if (m_NewTickControllingBehavior->ControlStarting(a_Dt, a_Chunk)) + { + // The new behavior told us it is ready for taking control + // The new behavior is now the current behavior. Next tick it will execute its Tick. + m_TickControllingBehaviorState = Normal; + m_CurrentTickControllingBehavior = m_NewTickControllingBehavior; + } + else + { + // The new behavior is not ready for taking control. + // We'll execute ControlStarting next tick too. + m_TickControllingBehaviorState = NewControlStarting; } } - // Process the undead burning in daylight. - HandleDaylightBurning(*Chunk, WouldBurnAt(GetPosition(), *Chunk)); + // All behaviors can execute PostTick and PreTick. + // These are for bookkeeping or passive actions like laying eggs. + // They MUST NOT control mob movement or interefere with the main Tick. + for (cBehavior * Behavior : m_AttachedPostTickBehaviors) + { + // LOGD("mobDebug - PostTick"); + Behavior->PostTick(a_Dt, a_Chunk); + } bool a_IsFollowingPath = false; if (m_PathfinderActivated) @@ -338,21 +436,23 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) else { // Note that m_NextWayPointPosition is actually returned by GetNextWayPoint) - switch (m_PathFinder.GetNextWayPoint(*Chunk, GetPosition(), &m_FinalDestination, &m_NextWayPointPosition, m_EMState == IDLE ? true : false)) + switch (m_PathFinder.GetNextWayPoint(*Chunk, GetPosition(), &m_FinalDestination, &m_NextWayPointPosition)) { case ePathFinderStatus::PATH_FOUND: { - /* If I burn in daylight, and I won't burn where I'm standing, and I'll burn in my next position, and at least one of those is true: - 1. I am idle - 2. I was not hurt by a player recently. - Then STOP. */ + // mobTodo move this logic to cPathfinder or to something + // more generic in cPath. if ( - m_BurnsInDaylight && ((m_TicksSinceLastDamaged >= 100) || (m_EMState == IDLE)) && - WouldBurnAt(m_NextWayPointPosition, *Chunk) && - !WouldBurnAt(GetPosition(), *Chunk) + // I am supposed to avoid daylight + (m_PathFinder.GetAvoidSunlight()) && + // I was not hurt recently + (m_TicksSinceLastDamaged >= 100) && + // I won't burn where I stand now + cBehaviorDayLightBurner::WouldBurnAt(m_NextWayPointPosition, *Chunk, *this) && + // I will burn where I'm going to + !(cBehaviorDayLightBurner::WouldBurnAt(GetPosition(), *Chunk, *this)) ) { - // If we burn in daylight, and we would burn at the next step, and we won't burn where we are right now, and we weren't provoked recently: StopMovingToPosition(); } else @@ -369,7 +469,8 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } default: { - + // NEARBY_FOUND is handled internally by cPathFinder. + // Do nothing if CALCULATING. } } } @@ -377,30 +478,10 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) SetPitchAndYawFromDestination(a_IsFollowingPath); - switch (m_EMState) - { - case IDLE: - { - // If enemy passive we ignore checks for player visibility. - InStateIdle(a_Dt, a_Chunk); - break; - } - case CHASING: - { - // If we do not see a player anymore skip chasing action. - InStateChasing(a_Dt, a_Chunk); - break; - } - case ESCAPING: - { - InStateEscaping(a_Dt, a_Chunk); - break; - } - case ATTACKING: break; - } // switch (m_EMState) - // Leash calculations - if ((m_TicksAlive % LEASH_ACTIONS_TICK_STEP) == 0) + if ((m_TickControllingBehaviorState == Normal) && + ((m_TicksAlive % LEASH_ACTIONS_TICK_STEP) == 0) + ) { CalcLeashActions(); } @@ -416,6 +497,11 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_World->BroadcastEntityMetadata(*this); } } + + if (m_TicksSinceLastDamaged < 1000) + { + ++m_TicksSinceLastDamaged; + } } @@ -458,9 +544,10 @@ void cMonster::CalcLeashActions() void cMonster::SetPitchAndYawFromDestination(bool a_IsFollowingPath) { Vector3d BodyDistance; - if (!a_IsFollowingPath && (GetTarget() != nullptr)) + cPawn * LookingAt = m_LookingAt.GetPointer(GetWorld()); + if (!a_IsFollowingPath && (LookingAt != nullptr)) { - BodyDistance = GetTarget()->GetPosition() - GetPosition(); + BodyDistance = LookingAt->GetPosition() - GetPosition(); } else { @@ -472,15 +559,15 @@ void cMonster::SetPitchAndYawFromDestination(bool a_IsFollowingPath) SetYaw(BodyRotation); Vector3d HeadDistance; - if (GetTarget() != nullptr) + if (LookingAt != nullptr) { - if (GetTarget()->IsPlayer()) // Look at a player + if (LookingAt->IsPlayer()) // Look at a player { - HeadDistance = GetTarget()->GetPosition() - GetPosition(); + HeadDistance = LookingAt->GetPosition() - GetPosition(); } else // Look at some other entity { - HeadDistance = GetTarget()->GetPosition() - GetPosition(); + HeadDistance = LookingAt->GetPosition() - GetPosition(); // HeadDistance.y = GetTarget()->GetPosY() + GetHeight(); } } @@ -555,22 +642,20 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) return false; } + if (!m_SoundHurt.empty() && (m_Health > 0)) { m_World->BroadcastSoundEffect(m_SoundHurt, GetPosX(), GetPosY(), GetPosZ(), 1.0f, 0.8f); } - if ((a_TDI.Attacker != nullptr) && a_TDI.Attacker->IsPawn()) + for (cBehavior * Behavior : m_AttachedDoTakeDamageBehaviors) { - if ( - (!a_TDI.Attacker->IsPlayer()) || - (static_cast<cPlayer *>(a_TDI.Attacker)->CanMobsTarget()) - ) - { - SetTarget(static_cast<cPawn*>(a_TDI.Attacker)); - } - m_TicksSinceLastDamaged = 0; + ASSERT(Behavior != nullptr); + Behavior->DoTakeDamage(a_TDI); } + + m_TicksSinceLastDamaged = 0; + return true; } @@ -661,6 +746,7 @@ void cMonster::OnRightClicked(cPlayer & a_Player) { super::OnRightClicked(a_Player); + // mobTodo put this in a behavior? const cItem & EquippedItem = a_Player.GetEquippedItem(); if ((EquippedItem.m_ItemType == E_ITEM_NAME_TAG) && !EquippedItem.m_CustomName.empty()) { @@ -671,6 +757,12 @@ void cMonster::OnRightClicked(cPlayer & a_Player) } } + for (cBehavior * Behavior : m_AttachedOnRightClickBehaviors) + { + Behavior->OnRightClicked(a_Player); + } + + // mobTodo put this in a behavior? // Using leashes m_IsLeashActionJustDone = false; if (IsLeashed() && (GetLeashedTo() == &a_Player)) // a player can only unleash a mob leashed to him @@ -696,159 +788,6 @@ void cMonster::OnRightClicked(cPlayer & a_Player) -// Checks to see if EventSeePlayer should be fired -// monster sez: Do I see the player -void cMonster::CheckEventSeePlayer(cChunk & a_Chunk) -{ - // TODO: Rewrite this to use cWorld's DoWithPlayers() - cPlayer * Closest = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance), false); - - if (Closest != nullptr) - { - EventSeePlayer(Closest, a_Chunk); - } -} - - - - - -void cMonster::CheckEventLostPlayer(void) -{ - if (GetTarget() != nullptr) - { - if ((GetTarget()->GetPosition() - GetPosition()).Length() > m_SightDistance) - { - EventLosePlayer(); - } - } - else - { - EventLosePlayer(); - } -} - - - - - -// What to do if player is seen -// default to change state to chasing -void cMonster::EventSeePlayer(cPlayer * a_SeenPlayer, cChunk & a_Chunk) -{ - UNUSED(a_Chunk); - SetTarget(a_SeenPlayer); -} - - - - - -void cMonster::EventLosePlayer(void) -{ - SetTarget(nullptr); - m_EMState = IDLE; -} - - - - - -void cMonster::InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - if (m_PathfinderActivated) - { - return; // Still getting there - } - - m_IdleInterval += a_Dt; - - if (m_IdleInterval > std::chrono::seconds(1)) - { - auto & Random = GetRandomProvider(); - - // At this interval the results are predictable - int rem = Random.RandInt(1, 7); - m_IdleInterval -= std::chrono::seconds(1); // So nothing gets dropped when the server hangs for a few seconds - - Vector3d Dist; - Dist.x = static_cast<double>(Random.RandInt(-5, 5)); - Dist.z = static_cast<double>(Random.RandInt(-5, 5)); - - if ((Dist.SqrLength() > 2) && (rem >= 3)) - { - - Vector3d Destination(GetPosX() + Dist.x, GetPosition().y, GetPosZ() + Dist.z); - - cChunk * Chunk = a_Chunk.GetNeighborChunk(static_cast<int>(Destination.x), static_cast<int>(Destination.z)); - if ((Chunk == nullptr) || !Chunk->IsValid()) - { - return; - } - - BLOCKTYPE BlockType; - NIBBLETYPE BlockMeta; - int RelX = static_cast<int>(Destination.x) - Chunk->GetPosX() * cChunkDef::Width; - int RelZ = static_cast<int>(Destination.z) - Chunk->GetPosZ() * cChunkDef::Width; - int YBelowUs = static_cast<int>(Destination.y) - 1; - if (YBelowUs >= 0) - { - Chunk->GetBlockTypeMeta(RelX, YBelowUs, RelZ, BlockType, BlockMeta); - if (BlockType != E_BLOCK_STATIONARY_WATER) // Idle mobs shouldn't enter water on purpose - { - MoveToPosition(Destination); - } - } - } - } -} - - - - - -// What to do if in Chasing State -// This state should always be defined in each child class -void cMonster::InStateChasing(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - UNUSED(a_Dt); -} - - - - - -// What to do if in Escaping State -void cMonster::InStateEscaping(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - UNUSED(a_Dt); - - if (GetTarget() != nullptr) - { - Vector3d newloc = GetPosition(); - newloc.x = (GetTarget()->GetPosition().x < newloc.x)? (newloc.x + m_SightDistance): (newloc.x - m_SightDistance); - newloc.z = (GetTarget()->GetPosition().z < newloc.z)? (newloc.z + m_SightDistance): (newloc.z - m_SightDistance); - MoveToPosition(newloc); - } - else - { - m_EMState = IDLE; // This shouldnt be required but just to be safe - } -} - - - - - -void cMonster::ResetAttackCooldown() -{ - m_AttackCoolDownTicksLeft = static_cast<int>(3 * 20 * m_AttackRate); // A second has 20 ticks, an attack rate of 1 means 1 hit every 3 seconds -} - - - - - void cMonster::SetCustomName(const AString & a_CustomName) { m_CustomName = a_CustomName; @@ -1064,16 +1003,19 @@ int cMonster::GetSpawnDelay(cMonster::eFamily a_MobFamily) -/** Sets the target. */ -void cMonster::SetTarget (cPawn * a_NewTarget) + +void cMonster::SetLookingAt(cPawn * a_NewTarget) { + m_LookingAt.SetPointer(a_NewTarget); + + /* ASSERT((a_NewTarget == nullptr) || (IsTicking())); - if (m_Target == a_NewTarget) + if (m_LookingAt == a_NewTarget) { return; } - cPawn * OldTarget = m_Target; - m_Target = a_NewTarget; + cPawn * OldTarget = m_LookingAt; + m_LookingAt = a_NewTarget; if (OldTarget != nullptr) { @@ -1087,26 +1029,91 @@ void cMonster::SetTarget (cPawn * a_NewTarget) // Notify the new target that we are now targeting it. m_Target->TargetingMe(this); m_WasLastTargetAPlayer = m_Target->IsPlayer(); - } + }*/ } +bool cMonster::IsPathFinderActivated() const +{ + return m_PathfinderActivated; +} + + + -void cMonster::UnsafeUnsetTarget() + +cBehaviorBreeder * cMonster::GetBehaviorBreeder() { - m_Target = nullptr; + return m_BehaviorBreederPointer; } -cPawn * cMonster::GetTarget() +const cBehaviorBreeder * cMonster::GetBehaviorBreeder() const { - return m_Target; + return static_cast<const cBehaviorBreeder *>(m_BehaviorBreederPointer); +} + + + + + +cBehaviorAttacker * cMonster::GetBehaviorAttacker() +{ + return m_BehaviorAttackerPointer; +} + + + + + +void cMonster::InheritFromParents(cMonster * a_Parent1, cMonster * a_Parent2) +{ + UNUSED(a_Parent1); + UNUSED(a_Parent2); + return; +} + + + + + +void cMonster::GetFollowedItems(cItems & a_Items) +{ + return; +} + + + + + +void cMonster::GetBreedingItems(cItems & a_Items) +{ + return GetFollowedItems(a_Items); +} + + + + + +cPlayer * cMonster::GetNearestPlayer() +{ + if (m_NearestPlayerIsStale) + { + // TODO: Rewrite this to use cWorld's DoWithPlayers() + m_NearestPlayer = GetWorld()->FindClosestPlayer(GetPosition(), static_cast<float>(GetSightDistance())); + m_NearestPlayerIsStale = false; + } + if ((m_NearestPlayer != nullptr) && (!m_NearestPlayer->IsTicking())) + { + m_NearestPlayer = nullptr; + } + return m_NearestPlayer; } @@ -1300,93 +1307,86 @@ void cMonster::AddRandomWeaponDropItem(cItems & a_Drops, unsigned int a_LootingL +void cMonster::AttachPreTickBehavior(cBehavior * a_Behavior) +{ + ASSERT(a_Behavior != nullptr); + m_AttachedPreTickBehaviors.push_back(a_Behavior); +} + + + -void cMonster::HandleDaylightBurning(cChunk & a_Chunk, bool WouldBurn) +void cMonster::AttachPostTickBehavior(cBehavior * a_Behavior) { - if (!m_BurnsInDaylight) - { - return; - } + ASSERT(a_Behavior != nullptr); + m_AttachedPostTickBehaviors.push_back(a_Behavior); +} - int RelY = POSY_TOINT; - if ((RelY < 0) || (RelY >= cChunkDef::Height)) - { - // Outside the world - return; - } - if (!a_Chunk.IsLightValid()) - { - m_World->QueueLightChunk(GetChunkX(), GetChunkZ()); - return; - } - if (!IsOnFire() && WouldBurn) - { - // Burn for 100 ticks, then decide again - StartBurning(100); - } + + + +void cMonster::AttachTickBehavior(cBehavior * a_Behavior) +{ + ASSERT(a_Behavior != nullptr); + m_AttachedTickBehaviors.push_back(a_Behavior); } -bool cMonster::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk) + +void cMonster::AttachDestroyBehavior(cBehavior * a_Behavior) { - // If the Y coord is out of range, return the most logical result without considering anything else: - int RelY = FloorC(a_Location.y); - if (RelY >= cChunkDef::Height) - { - // Always burn above the world - return true; - } - if (RelY <= 0) - { - // The mob is about to die, no point in burning - return false; - } + ASSERT(a_Behavior != nullptr); + m_AttachedDestroyBehaviors.push_back(a_Behavior); +} - PREPARE_REL_AND_CHUNK(a_Location, a_Chunk); - if (!RelSuccess) - { - return false; - } - if ( - (Chunk->GetBlock(Rel.x, Rel.y, Rel.z) != E_BLOCK_SOULSAND) && // Not on soulsand - (GetWorld()->GetTimeOfDay() < 12000 + 1000) && // Daytime - GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining - ) - { - int MobHeight = CeilC(a_Location.y + GetHeight()) - 1; // The block Y coord of the mob's head - if (MobHeight >= cChunkDef::Height) - { - return true; - } - // Start with the highest block and scan down to just above the mob's head. - // If a non transparent is found, return false (do not burn). Otherwise return true. - // Note that this loop is not a performance concern as transparent blocks are rare and the loop almost always bailes out - // instantly.(An exception is e.g. standing under a long column of glass). - int CurrentBlock = Chunk->GetHeight(Rel.x, Rel.z); - while (CurrentBlock > MobHeight) - { - BLOCKTYPE Block = Chunk->GetBlock(Rel.x, CurrentBlock, Rel.z); - if ( - // Do not burn if a block above us meets one of the following conditions: - (!cBlockInfo::IsTransparent(Block)) || - (Block == E_BLOCK_LEAVES) || - (Block == E_BLOCK_NEW_LEAVES) || - (IsBlockWater(Block)) - ) - { - return false; - } - --CurrentBlock; - } - return true; - } - return false; + + +void cMonster::AttachRightClickBehavior(cBehavior * a_Behavior) +{ + ASSERT(a_Behavior != nullptr); + m_AttachedOnRightClickBehaviors.push_back(a_Behavior); +} + + + + + +void cMonster::AttachDoTakeDamageBehavior(cBehavior * a_Behavior) +{ + m_AttachedDoTakeDamageBehaviors.push_back(a_Behavior); +} + + + + +void cMonster::PinBehavior(cBehavior * a_Behavior) +{ + m_PinnedBehavior = a_Behavior; +} + + + + + +void cMonster::UnpinBehavior(cBehavior * a_Behavior) +{ + ASSERT(m_PinnedBehavior == a_Behavior); + m_PinnedBehavior = nullptr; +} + + + + + +cPathFinder & cMonster::GetPathFinder() +{ + return m_PathFinder; } diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 1f6bd6011..f8cac4e7a 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -4,12 +4,18 @@ #include "../Entities/Pawn.h" #include "MonsterTypes.h" #include "PathFinder.h" - +#include "Behaviors/BehaviorWanderer.h" +#include "MobPointer.h" +#include <vector> class cItem; class cClientHandle; +//Behavior fwds +class cBehaviorBreeder; +class cBehaviorAttacker; +class cBehavior; // tolua_begin class cMonster : @@ -31,15 +37,13 @@ public: // tolua_end - enum MState{ATTACKING, IDLE, CHASING, ESCAPING} m_EMState; enum MPersonality{PASSIVE, AGGRESSIVE, COWARDLY} m_EMPersonality; /** Creates the mob object. - If a_ConfigName is not empty, the configuration is loaded using GetMonsterConfig() a_MobType is the type of the mob (also used in the protocol ( http://wiki.vg/Entities#Mobs 2012_12_22)) a_SoundHurt and a_SoundDeath are assigned into m_SoundHurt and m_SoundDeath, respectively */ - cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height); + cMonster(eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height); virtual ~cMonster() override; @@ -64,14 +68,17 @@ public: /** Engage pathfinder and tell it to calculate a path to a given position, and move the mob accordingly. */ virtual void MoveToPosition(const Vector3d & a_Position); // tolua_export + // mobTodo - MoveToPosition export is probably broken. The AI will keep overriding the + // destination. + + /** Stops pathfinding. Calls ResetPathFinding and sets m_IsFollowingPath to false */ + void StopMovingToPosition(); + // tolua_begin eMonsterType GetMobType(void) const { return m_MobType; } eFamily GetMobFamily(void) const; // tolua_end - virtual void CheckEventSeePlayer(cChunk & a_Chunk); - virtual void EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk); - // tolua_begin /** Returns whether the mob can be leashed. */ @@ -109,18 +116,8 @@ public: /** Returns whether this mob is undead (skeleton, zombie, etc.) */ virtual bool IsUndead(void); - virtual void EventLosePlayer(void); - virtual void CheckEventLostPlayer(void); - - virtual void InStateIdle (std::chrono::milliseconds a_Dt, cChunk & a_Chunk); - virtual void InStateChasing (std::chrono::milliseconds a_Dt, cChunk & a_Chunk); - virtual void InStateEscaping(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); - - int GetAttackRate() { return static_cast<int>(m_AttackRate); } - void SetAttackRate(float a_AttackRate) { m_AttackRate = a_AttackRate; } - void SetAttackRange(int a_AttackRange) { m_AttackRange = a_AttackRange; } - void SetAttackDamage(int a_AttackDamage) { m_AttackDamage = a_AttackDamage; } void SetSightDistance(int a_SightDistance) { m_SightDistance = a_SightDistance; } + int GetSightDistance() { return m_SightDistance; } float GetDropChanceWeapon() { return m_DropChanceWeapon; } float GetDropChanceHelmet() { return m_DropChanceHelmet; } @@ -134,10 +131,6 @@ public: void SetDropChanceLeggings(float a_DropChanceLeggings) { m_DropChanceLeggings = a_DropChanceLeggings; } void SetDropChanceBoots(float a_DropChanceBoots) { m_DropChanceBoots = a_DropChanceBoots; } void SetCanPickUpLoot(bool a_CanPickUpLoot) { m_CanPickUpLoot = a_CanPickUpLoot; } - void ResetAttackCooldown(); - - /** Sets whether the mob burns in daylight. Only evaluated at next burn-decision tick */ - void SetBurnsInDaylight(bool a_BurnsInDaylight) { m_BurnsInDaylight = a_BurnsInDaylight; } double GetRelativeWalkSpeed(void) const { return m_RelativeWalkSpeed; } // tolua_export void SetRelativeWalkSpeed(double a_WalkSpeed) { m_RelativeWalkSpeed = a_WalkSpeed; } // tolua_export @@ -193,14 +186,7 @@ public: static AString MobTypeToVanillaNBT(eMonsterType a_MobType); /** Sets the target that this mob will chase. Pass a nullptr to unset. */ - void SetTarget (cPawn * a_NewTarget); - - /** Unset the target without notifying the target entity. Do not use this, use SetTarget(nullptr) instead. - This is only used by cPawn internally. */ - void UnsafeUnsetTarget(); - - /** Returns the current target. */ - cPawn * GetTarget(); + void SetLookingAt(cPawn * a_NewTarget); /** Creates a new object of the specified mob. a_MobType is the type of the mob to be created @@ -211,8 +197,52 @@ public: /** Returns if this mob last target was a player to avoid destruction on player quit */ bool WasLastTargetAPlayer() const { return m_WasLastTargetAPlayer; } + bool IsPathFinderActivated() const; + + // Behavior getters for the rare occasion where we need "polymorphism" + // Currently only for attacking and breeding. + cBehaviorBreeder * GetBehaviorBreeder(); + const cBehaviorBreeder * GetBehaviorBreeder() const; + cBehaviorAttacker * GetBehaviorAttacker();\ + cBehaviorBreeder * m_BehaviorBreederPointer; + cBehaviorAttacker * m_BehaviorAttackerPointer; + + // Polymorphic monster-specific functions that behaviors may use + virtual void InheritFromParents(cMonster * a_Parent1, cMonster * a_Parent2); + virtual void GetFollowedItems(cItems & a_Items); + virtual void GetBreedingItems(cItems & a_Items); + + cPlayer * GetNearestPlayer(); + + // These should only be called from cBehavior::attachToMonster + void AttachPreTickBehavior(cBehavior * a_Behavior); + void AttachPostTickBehavior(cBehavior * a_Behavior); + void AttachTickBehavior(cBehavior * a_Behavior); + void AttachDestroyBehavior(cBehavior * a_Behavior); + void AttachRightClickBehavior(cBehavior * a_Behavior); + void AttachDoTakeDamageBehavior(cBehavior * a_Behavior); + + /** If a behavior calls this within its tick, it will be the only behavior + that gets to tick until UnpinBehavior is called. ControlDesired will no + longer be called for any tick until unpinning is done. MUST be called + from the tick function of a behavior. */ + void PinBehavior(cBehavior * a_Behavior); + + /** Unpins the behavior and ticking goes back to normal. MUST be called + on the same behavior pointer that called PinBehavior. */ + void UnpinBehavior(cBehavior * a_Behavior); + + /** Returns a reference to this mob's pathfinder. Typically used by behaviors + to tweak pathfinding behavior as they gain or release control of the mob. */ + cPathFinder & GetPathFinder(); protected: + /** Whether or not m_NearestPlayer is stale. Always true at the beginning of a tick. + When true, GetNearestPlayer() actually searches for a player, updates m_NearestPlayer, and sets it to false. + otherwise it returns m_NearestPlayer. This means we only perform 1 search per tick. */ + bool m_NearestPlayerIsStale; + cPlayer * m_NearestPlayer; + /** The pathfinder instance handles pathfinding for this monster. */ cPathFinder m_PathFinder; @@ -234,13 +264,6 @@ protected: /** Returns if the ultimate, final destination has been reached. */ bool ReachedFinalDestination(void) { return ((m_FinalDestination - GetPosition()).SqrLength() < WAYPOINT_RADIUS * WAYPOINT_RADIUS); } - /** Returns whether or not the target is close enough for attack. */ - bool TargetIsInRange(void) - { - ASSERT(GetTarget() != nullptr); - return ((GetTarget()->GetPosition() - GetPosition()).SqrLength() < (m_AttackRange * m_AttackRange)); - } - /** Returns whether the monster needs to jump to reach a given height. */ inline bool DoesPosYRequireJump(double a_PosY) { @@ -250,9 +273,6 @@ protected: /** Move in a straight line to the next waypoint in the path, will jump if needed. */ void MoveToWayPoint(cChunk & a_Chunk); - /** Stops pathfinding. Calls ResetPathFinding and sets m_IsFollowingPath to false */ - void StopMovingToPosition(); - /** Sets the body yaw and head yaw */ void SetPitchAndYawFromDestination(bool a_IsFollowingPath); @@ -268,11 +288,7 @@ protected: AString m_SoundHurt; AString m_SoundDeath; - float m_AttackRate; - int m_AttackDamage; - int m_AttackRange; - int m_AttackCoolDownTicksLeft; - int m_SightDistance; + int m_SightDistance; // mobTodo what to do with this? float m_DropChanceWeapon; float m_DropChanceHelmet; @@ -280,11 +296,7 @@ protected: float m_DropChanceLeggings; float m_DropChanceBoots; bool m_CanPickUpLoot; - int m_TicksSinceLastDamaged; // How many ticks ago we were last damaged by a player? - void HandleDaylightBurning(cChunk & a_Chunk, bool WouldBurn); - bool WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk); - bool m_BurnsInDaylight; double m_RelativeWalkSpeed; int m_Age; @@ -320,12 +332,23 @@ protected: void AddRandomWeaponDropItem(cItems & a_Drops, unsigned int a_LootingLevel); private: - /** A pointer to the entity this mobile is aiming to reach. - The validity of this pointer SHALL be guaranteed by the pointee; - it MUST be reset when the pointee changes worlds or is destroyed. */ - cPawn * m_Target; + /** A pointer to the entity this mobile is lookingAt */ + cMobPointer m_LookingAt; /** Leash calculations inside Tick function */ void CalcLeashActions(); + std::vector<cBehavior*> m_AttachedPreTickBehaviors; + std::vector<cBehavior*> m_AttachedTickBehaviors; + std::vector<cBehavior*> m_AttachedPostTickBehaviors; + std::vector<cBehavior*> m_AttachedDestroyBehaviors; + std::vector<cBehavior*> m_AttachedOnRightClickBehaviors; + std::vector<cBehavior*> m_AttachedDoTakeDamageBehaviors; + + cBehavior * m_CurrentTickControllingBehavior; + cBehavior * m_NewTickControllingBehavior; + cBehavior * m_PinnedBehavior; + enum TickState{NewControlStarting, OldControlEnding, Normal} m_TickControllingBehaviorState; + + int m_TicksSinceLastDamaged; // How many ticks ago were we last damaged by a player? } ; // tolua_export diff --git a/src/Mobs/Mooshroom.cpp b/src/Mobs/Mooshroom.cpp index b6feca76e..aa5209ecf 100644 --- a/src/Mobs/Mooshroom.cpp +++ b/src/Mobs/Mooshroom.cpp @@ -14,8 +14,14 @@ cMooshroom::cMooshroom(void) : - super("Mooshroom", mtMooshroom, "entity.cow.hurt", "entity.cow.death", 0.9, 1.3) + super(mtMooshroom, "entity.cow.hurt", "entity.cow.death", 0.9, 1.3) { + m_EMPersonality = PASSIVE; + m_BehaviorBreeder.AttachToMonster(*this); + m_BehaviorCoward.AttachToMonster(*this); + m_BehaviorItemFollower.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + GetMonsterConfig("Mooshroom"); } @@ -39,6 +45,7 @@ void cMooshroom::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cMooshroom::OnRightClicked(cPlayer & a_Player) { + // mobTodo Behaviors switch (a_Player.GetEquippedItem().m_ItemType) { case E_ITEM_BUCKET: @@ -72,4 +79,3 @@ void cMooshroom::OnRightClicked(cPlayer & a_Player) } break; } } - diff --git a/src/Mobs/Mooshroom.h b/src/Mobs/Mooshroom.h index 625963190..06e5cf8ce 100644 --- a/src/Mobs/Mooshroom.h +++ b/src/Mobs/Mooshroom.h @@ -1,16 +1,20 @@ #pragma once -#include "PassiveMonster.h" +#include "Behaviors/BehaviorBreeder.h" +#include "Behaviors/BehaviorItemFollower.h" +#include "Behaviors/BehaviorCoward.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Monster.h" class cMooshroom : - public cPassiveMonster + public cMonster { - typedef cPassiveMonster super; + typedef cMonster super; public: cMooshroom(void); @@ -24,6 +28,12 @@ public: { a_Items.Add(E_ITEM_WHEAT); } +private: + // Tick controlling behaviors + cBehaviorBreeder m_BehaviorBreeder; + cBehaviorItemFollower m_BehaviorItemFollower; + cBehaviorCoward m_BehaviorCoward; + cBehaviorWanderer m_BehaviorWanderer; } ; diff --git a/src/Mobs/Ocelot.cpp b/src/Mobs/Ocelot.cpp index e5004a1d1..cf4356f4f 100644 --- a/src/Mobs/Ocelot.cpp +++ b/src/Mobs/Ocelot.cpp @@ -13,13 +13,16 @@ cOcelot::cOcelot(void) : - super("Ocelot", mtOcelot, "entity.cat.hurt", "entity.cat.death", 0.6, 0.8), + super(mtOcelot, "entity.cat.hurt", "entity.cat.death", 0.6, 0.8), m_IsSitting(false), m_IsTame(false), m_IsBegging(false), m_CatType(ctWildOcelot), m_OwnerName("") { + m_EMPersonality = PASSIVE; + m_BehaviorDoNothing.AttachToMonster(*this); + GetMonsterConfig("Ocelot"); } @@ -29,6 +32,7 @@ cOcelot::cOcelot(void) : void cOcelot::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); + if (!IsTicking()) { // The base class tick destroyed us diff --git a/src/Mobs/Ocelot.h b/src/Mobs/Ocelot.h index 75758a973..1c1b383cb 100644 --- a/src/Mobs/Ocelot.h +++ b/src/Mobs/Ocelot.h @@ -1,17 +1,17 @@ #pragma once -#include "PassiveMonster.h" +#include "Monster.h" #include "../UUID.h" - +#include "Behaviors/BehaviorDoNothing.h" class cOcelot : - public cPassiveMonster + public cMonster { - typedef cPassiveMonster super; + typedef cMonster super; public: @@ -68,6 +68,9 @@ protected: int m_CheckPlayerTickCount; AString m_OwnerName; cUUID m_OwnerUUID; + +private: + cBehaviorDoNothing m_BehaviorDoNothing; } ; diff --git a/src/Mobs/PassiveAggressiveMonster.cpp b/src/Mobs/PassiveAggressiveMonster.cpp deleted file mode 100644 index 8715ba9c2..000000000 --- a/src/Mobs/PassiveAggressiveMonster.cpp +++ /dev/null @@ -1,48 +0,0 @@ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "PassiveAggressiveMonster.h" - -#include "../Entities/Player.h" - - - - - -cPassiveAggressiveMonster::cPassiveAggressiveMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height) : - super(a_ConfigName, a_MobType, a_SoundHurt, a_SoundDeath, a_Width, a_Height) -{ - m_EMPersonality = PASSIVE; -} - - - - - -bool cPassiveAggressiveMonster::DoTakeDamage(TakeDamageInfo & a_TDI) -{ - if (!super::DoTakeDamage(a_TDI)) - { - return false; - } - - if ((GetTarget() != nullptr) && (GetTarget()->IsPlayer())) - { - if (static_cast<cPlayer *>(GetTarget())->CanMobsTarget()) - { - m_EMState = CHASING; - } - } - return true; -} - - - - - -void cPassiveAggressiveMonster::EventSeePlayer(cPlayer *, cChunk & a_Chunk) -{ - // don't do anything, neutral mobs don't react to just seeing the player -} - - diff --git a/src/Mobs/PassiveAggressiveMonster.h b/src/Mobs/PassiveAggressiveMonster.h deleted file mode 100644 index 764e27779..000000000 --- a/src/Mobs/PassiveAggressiveMonster.h +++ /dev/null @@ -1,24 +0,0 @@ - -#pragma once - -#include "AggressiveMonster.h" - - - - - -class cPassiveAggressiveMonster : - public cAggressiveMonster -{ - typedef cAggressiveMonster super; - -public: - cPassiveAggressiveMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height); - - virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override; - virtual void EventSeePlayer(cPlayer *, cChunk & a_Chunk) override; -} ; - - - - diff --git a/src/Mobs/PassiveMonster.h b/src/Mobs/PassiveMonster.h deleted file mode 100644 index 9a2627417..000000000 --- a/src/Mobs/PassiveMonster.h +++ /dev/null @@ -1,66 +0,0 @@ - -#pragma once - -#include "Monster.h" - - - - - -class cPassiveMonster : - public cMonster -{ - typedef cMonster super; - -public: - cPassiveMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height); - - virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - virtual void OnRightClicked(cPlayer & a_Player) override; - - /** When hit by someone, run away */ - virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override; - - /** Returns the items that the animal of this class follows when a player holds it in hand. */ - virtual void GetFollowedItems(cItems & a_Items) { } - - /** Returns the items that make the animal breed - this is usually the same as the ones that make the animal follow, but not necessarily. */ - virtual void GetBreedingItems(cItems & a_Items) { GetFollowedItems(a_Items); } - - /** Called after the baby is born, allows the baby to inherit the parents' properties (color, etc.) */ - virtual void InheritFromParents(cPassiveMonster * a_Parent1, cPassiveMonster * a_Parent2) { } - - /** Returns the partner which the monster is currently mating with. */ - cPassiveMonster * GetPartner(void) const { return m_LovePartner; } - - /** Start the mating process. Causes the monster to keep bumping into the partner until m_MatingTimer reaches zero. */ - void EngageLoveMode(cPassiveMonster * a_Partner); - - /** Finish the mating process. Called after a baby is born. Resets all breeding related timers and sets m_LoveCooldown to 20 minutes. */ - void ResetLoveMode(); - - /** Returns whether the monster has just been fed and is ready to mate. If this is "true" and GetPartner isn't "nullptr", then the monster is mating. */ - bool IsInLove() const { return (m_LoveTimer > 0); } - - /** Returns whether the monster is tired of breeding and is in the cooldown state. */ - bool IsInLoveCooldown() const { return (m_LoveCooldown > 0); } - - virtual void Destroyed(void) override; - -protected: - /** The monster's breeding partner. */ - cPassiveMonster * m_LovePartner; - - /** If above 0, the monster is in love mode, and will breed if a nearby monster is also in love mode. Decrements by 1 per tick till reaching zero. */ - int m_LoveTimer; - - /** If above 0, the monster is in cooldown mode and will refuse to breed. Decrements by 1 per tick till reaching zero. */ - int m_LoveCooldown; - - /** The monster is engaged in mating, once this reaches zero, a baby will be born. Decrements by 1 per tick till reaching zero, then a baby is made and ResetLoveMode() is called. */ - int m_MatingTimer; -}; - - - - diff --git a/src/Mobs/Path.h b/src/Mobs/Path.h index 977c5be1c..40e1de6f0 100644 --- a/src/Mobs/Path.h +++ b/src/Mobs/Path.h @@ -10,7 +10,7 @@ class cPath; #include "../FastRandom.h" #ifdef COMPILING_PATHFIND_DEBUGGER - /* Note: the COMPILING_PATHFIND_DEBUGGER flag is used by Native / WiseOldMan95 to debug + /* Note: the COMPILING_PATHFIND_DEBUGGER flag is used by LogicParrot to debug this class outside of Cuberite. This preprocessor flag is never set when compiling Cuberite. */ #include "PathFinderIrrlicht_Head.h" #endif diff --git a/src/Mobs/PathFinder.cpp b/src/Mobs/PathFinder.cpp index 93664b596..8fdcb1ae6 100644 --- a/src/Mobs/PathFinder.cpp +++ b/src/Mobs/PathFinder.cpp @@ -9,7 +9,9 @@ cPathFinder::cPathFinder(double a_MobWidth, double a_MobHeight) : m_Path(), m_GiveUpCounter(0), - m_NotFoundCooldown(0) + m_NotFoundCooldown(0), + m_DontCare(false), + m_AvoidSunlight(false) { m_Width = a_MobWidth; m_Height = a_MobHeight; @@ -19,7 +21,7 @@ cPathFinder::cPathFinder(double a_MobWidth, double a_MobHeight) : -ePathFinderStatus cPathFinder::GetNextWayPoint(cChunk & a_Chunk, const Vector3d & a_Source, Vector3d * a_Destination, Vector3d * a_OutputWaypoint, bool a_DontCare) +ePathFinderStatus cPathFinder::GetNextWayPoint(cChunk & a_Chunk, const Vector3d & a_Source, Vector3d * a_Destination, Vector3d * a_OutputWaypoint) { m_FinalDestination = *a_Destination; m_Source = a_Source; @@ -65,7 +67,7 @@ ePathFinderStatus cPathFinder::GetNextWayPoint(cChunk & a_Chunk, const Vector3d { m_NoPathToTarget = true; m_PathDestination = m_Path->AcceptNearbyPath(); - if (a_DontCare) + if (m_DontCare) { m_FinalDestination = m_PathDestination; *a_Destination = m_FinalDestination; // Modify the mob's final destination because it doesn't care about reaching an exact spot @@ -93,7 +95,7 @@ ePathFinderStatus cPathFinder::GetNextWayPoint(cChunk & a_Chunk, const Vector3d if (m_GiveUpCounter == 0) { - if (a_DontCare) + if (m_DontCare) { // We're having trouble reaching the next waypoint but the mob // Doesn't care where to go, just tell him we got there ;) @@ -166,6 +168,42 @@ ePathFinderStatus cPathFinder::GetNextWayPoint(cChunk & a_Chunk, const Vector3d +void cPathFinder::SetDontCare(bool a_DontCare) +{ + m_DontCare = a_DontCare; +} + + + + + +bool cPathFinder::GetDontCare() +{ + return m_DontCare; +} + + + + + +void cPathFinder::SetAvoidSunlight(bool a_AvoidSunlight) +{ + m_AvoidSunlight = a_AvoidSunlight; +} + + + + + +bool cPathFinder::GetAvoidSunlight() +{ + return m_AvoidSunlight; +} + + + + + void cPathFinder::ResetPathFinding(cChunk &a_Chunk) { m_GiveUpCounter = 40; diff --git a/src/Mobs/PathFinder.h b/src/Mobs/PathFinder.h index 213530b11..252ac29ff 100644 --- a/src/Mobs/PathFinder.h +++ b/src/Mobs/PathFinder.h @@ -25,8 +25,9 @@ public: @param a_Source The mob's position. a_Source's coordinates are expected to be within the chunk given in a_Chunk. @param a_Destination The position the mob would like to reach. If a_ExactPath is true, the PathFinder may modify this. @param a_OutputWaypoint An output parameter: The next waypoint to go to. - @param a_DontCare If true, the mob doesn't care where to go, and the Pathfinder may modify a_Destination. - This should usually be false. An exception is a wandering idle mob which doesn't care about its final destination. + + If m_DontCare is true, the mob doesn't care where to go, and the Pathfinder may modify a_Destination. + This should usually be false. One exception is a wandering idle mob which doesn't care about its final destination. In the future, idle mobs shouldn't use A* at all. Returns an ePathFinderStatus. @@ -36,8 +37,19 @@ public: ePathFinderStatus:PATH_NOT_FOUND - The PathFinder did not find a destination to the target. Nothing was written to a_OutputWaypoint. The mob should probably not move. Note: Once NEARBY_FOUND is returned once, subsequent calls return PATH_FOUND. */ - ePathFinderStatus GetNextWayPoint(cChunk & a_Chunk, const Vector3d & a_Source, Vector3d * a_Destination, Vector3d * a_OutputWaypoint, bool a_DontCare = false); + ePathFinderStatus GetNextWayPoint(cChunk & a_Chunk, const Vector3d & a_Source, Vector3d * a_Destination, Vector3d * a_OutputWaypoint); + + /** Sets the dontCare value. See the GetNextWayPoint documentation for details. */ + void SetDontCare(bool a_DontCare); + + /** Returns the current dontCare value. */ + bool GetDontCare(); + /** If true, the mob will try avoiding sunlight. */ + void SetAvoidSunlight(bool a_AvoidSunlight); + + /** Returns the current AvoidSunlight value. */ + bool GetAvoidSunlight(); private: /** The width of the Mob which owns this PathFinder. */ @@ -55,7 +67,7 @@ private: /** Coordinates of the next position that should be reached. */ Vector3d m_WayPoint; - /** Coordinates for where we should go. This is out ultimate, final destination. */ + /** Coordinates for where we should go. This is our ultimate, final destination. */ Vector3d m_FinalDestination; /** Coordinates for where we are practically going. */ @@ -75,6 +87,13 @@ private: /** When a path is not found, this cooldown prevents any recalculations for several ticks. */ int m_NotFoundCooldown; + /** If true, the mob doesn't care where to go, and the Pathfinder may modify a_Destination + in an GetNextWayPoint call. */ + bool m_DontCare; + + /** If true, */ + bool m_AvoidSunlight; + /** Ensures the location is not in the air or under water. May change the Y coordinate of the given vector. 1. If a_Vector is the position of water, a_Vector's Y will be modified to point to the first air block above it. diff --git a/src/Mobs/Pig.cpp b/src/Mobs/Pig.cpp index 82901b061..e6cc197bb 100644 --- a/src/Mobs/Pig.cpp +++ b/src/Mobs/Pig.cpp @@ -10,9 +10,15 @@ cPig::cPig(void) : - super("Pig", mtPig, "entity.pig.hurt", "entity.pig.death", 0.9, 0.9), + super(mtPig, "entity.pig.hurt", "entity.pig.death", 0.9, 0.9), m_bIsSaddled(false) { + m_EMPersonality = PASSIVE; + m_BehaviorBreeder.AttachToMonster(*this); + m_BehaviorCoward.AttachToMonster(*this); + m_BehaviorItemFollower.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + GetMonsterConfig("Pig"); } @@ -41,6 +47,9 @@ void cPig::OnRightClicked(cPlayer & a_Player) { super::OnRightClicked(a_Player); + // Behavior note: saddling is pig-specific. It is not transferrable to other mobs. + // Therefore saddling is not a standalone behavior and is hardcoded into the pig. + if (m_bIsSaddled) { if (m_Attachee != nullptr) @@ -91,6 +100,9 @@ void cPig::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) return; } + // Behavior note: saddling is pig-specific. It is not transferrable to other mobs. + // Therefore saddling is not a standalone behavior and is hardcoded into the pig. + // If the attachee player is holding a carrot-on-stick, let them drive this pig: if (m_bIsSaddled && (m_Attachee != nullptr)) { @@ -120,7 +132,3 @@ bool cPig::DoTakeDamage(TakeDamageInfo & a_TDI) } return true; } - - - - diff --git a/src/Mobs/Pig.h b/src/Mobs/Pig.h index ed0685e5f..9a138f83e 100644 --- a/src/Mobs/Pig.h +++ b/src/Mobs/Pig.h @@ -1,16 +1,16 @@ - #pragma once -#include "PassiveMonster.h" - - - +#include "Behaviors/BehaviorBreeder.h" +#include "Behaviors/BehaviorItemFollower.h" +#include "Behaviors/BehaviorCoward.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Monster.h" class cPig : - public cPassiveMonster + public cMonster { - typedef cPassiveMonster super; + typedef cMonster super; public: cPig(void); @@ -30,8 +30,12 @@ public: } bool IsSaddled(void) const { return m_bIsSaddled; } - private: + // Tick controlling behaviors + cBehaviorBreeder m_BehaviorBreeder; + cBehaviorItemFollower m_BehaviorItemFollower; + cBehaviorCoward m_BehaviorCoward; + cBehaviorWanderer m_BehaviorWanderer; bool m_bIsSaddled; diff --git a/src/Mobs/Rabbit.cpp b/src/Mobs/Rabbit.cpp index f4de0ba0c..a4a71a44d 100644 --- a/src/Mobs/Rabbit.cpp +++ b/src/Mobs/Rabbit.cpp @@ -14,6 +14,12 @@ cRabbit::cRabbit(void) : static_cast<UInt8>(eRabbitType::SaltAndPepper) // Max possible Rabbit-Type )), 0) { + m_EMPersonality = PASSIVE; + m_BehaviorBreeder.AttachToMonster(*this); + m_BehaviorCoward.AttachToMonster(*this); + m_BehaviorItemFollower.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + GetMonsterConfig("Rabbit"); } @@ -21,10 +27,16 @@ cRabbit::cRabbit(void) : cRabbit::cRabbit(eRabbitType Type, int MoreCarrotTicks) : - super("Rabbit", mtRabbit, "entity.rabbit.hurt", "entity.rabbit.death", 0.82, 0.68), + super(mtRabbit, "entity.rabbit.hurt", "entity.rabbit.death", 0.82, 0.68), m_Type(Type), m_MoreCarrotTicks(MoreCarrotTicks) { + m_EMPersonality = PASSIVE; + m_BehaviorBreeder.AttachToMonster(*this); + m_BehaviorCoward.AttachToMonster(*this); + m_BehaviorItemFollower.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + GetMonsterConfig("Rabbit"); } @@ -44,4 +56,3 @@ void cRabbit::GetDrops(cItems & a_Drops, cEntity * a_Killer) RareDrops.Add(cItem(E_ITEM_RABBITS_FOOT)); AddRandomRareDropItem(a_Drops, RareDrops, LootingLevel); } - diff --git a/src/Mobs/Rabbit.h b/src/Mobs/Rabbit.h index 289ff0282..119ba280b 100644 --- a/src/Mobs/Rabbit.h +++ b/src/Mobs/Rabbit.h @@ -1,8 +1,11 @@ #pragma once -#include "PassiveMonster.h" - +#include "Behaviors/BehaviorBreeder.h" +#include "Behaviors/BehaviorItemFollower.h" +#include "Behaviors/BehaviorCoward.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Monster.h" @@ -23,9 +26,9 @@ enum class eRabbitType : UInt8 class cRabbit : - public cPassiveMonster + public cMonster { - typedef cPassiveMonster super; + typedef cMonster super; public: cRabbit(); @@ -43,8 +46,12 @@ public: eRabbitType GetRabbitType() const { return m_Type; } int GetMoreCarrotTicks() const { return m_MoreCarrotTicks; } - private: + // Tick controlling behaviors + cBehaviorBreeder m_BehaviorBreeder; + cBehaviorItemFollower m_BehaviorItemFollower; + cBehaviorCoward m_BehaviorCoward; + cBehaviorWanderer m_BehaviorWanderer; eRabbitType m_Type; int m_MoreCarrotTicks; // Ticks until the Rabbit eat planted Carrots diff --git a/src/Mobs/Sheep.cpp b/src/Mobs/Sheep.cpp index fef1adac6..001b80294 100644 --- a/src/Mobs/Sheep.cpp +++ b/src/Mobs/Sheep.cpp @@ -12,11 +12,17 @@ cSheep::cSheep(int a_Color) : - super("Sheep", mtSheep, "entity.sheep.hurt", "entity.sheep.death", 0.6, 1.3), + super(mtSheep, "entity.sheep.hurt", "entity.sheep.death", 0.6, 1.3), + m_TimeToStopEating(-1), m_IsSheared(false), - m_WoolColor(a_Color), - m_TimeToStopEating(-1) + m_WoolColor(a_Color) { + m_EMPersonality = PASSIVE; + m_BehaviorBreeder.AttachToMonster(*this); + m_BehaviorCoward.AttachToMonster(*this); + m_BehaviorItemFollower.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + // Generate random wool color. if (m_WoolColor == -1) { @@ -27,6 +33,8 @@ cSheep::cSheep(int a_Color) : { m_WoolColor = 0; } + + GetMonsterConfig("Sheep"); } @@ -135,7 +143,7 @@ void cSheep::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -void cSheep::InheritFromParents(cPassiveMonster * a_Parent1, cPassiveMonster * a_Parent2) +void cSheep::InheritFromParents(cMonster * a_Parent1, cMonster * a_Parent2) { static const struct { @@ -203,4 +211,3 @@ NIBBLETYPE cSheep::GenerateNaturalRandomColor(void) return E_META_WOOL_PINK; } } - diff --git a/src/Mobs/Sheep.h b/src/Mobs/Sheep.h index c8af067f3..89fa41edf 100644 --- a/src/Mobs/Sheep.h +++ b/src/Mobs/Sheep.h @@ -1,16 +1,18 @@ #pragma once -#include "PassiveMonster.h" - - +#include "Behaviors/BehaviorBreeder.h" +#include "Behaviors/BehaviorItemFollower.h" +#include "Behaviors/BehaviorCoward.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Monster.h" class cSheep : - public cPassiveMonster + public cMonster { - typedef cPassiveMonster super; + typedef cMonster super; public: @@ -25,7 +27,7 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; virtual void OnRightClicked(cPlayer & a_Player) override; virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - virtual void InheritFromParents(cPassiveMonster * a_Parent1, cPassiveMonster * a_Parent2) override; + virtual void InheritFromParents(cMonster * a_Parent1, cMonster * a_Parent2) override; virtual void GetFollowedItems(cItems & a_Items) override { @@ -41,12 +43,21 @@ public: int GetFurColor(void) const { return m_WoolColor; } void SetFurColor(int a_WoolColor) { m_WoolColor = a_WoolColor; } - private: - bool m_IsSheared; - int m_WoolColor; + + // Tick controlling behaviors + cBehaviorBreeder m_BehaviorBreeder; + cBehaviorItemFollower m_BehaviorItemFollower; + cBehaviorCoward m_BehaviorCoward; + cBehaviorWanderer m_BehaviorWanderer; + + // mobTodo transfer this to a behavior int m_TimeToStopEating; + // Behavior note: These are ship-specific things not transferrable to other mobs. + // Therefore they do not need a Behavior and can stay hardcoded in the sheep. + bool m_IsSheared; + int m_WoolColor; } ; diff --git a/src/Mobs/Silverfish.h b/src/Mobs/Silverfish.h index 90ef5ea5d..72b772a2c 100644 --- a/src/Mobs/Silverfish.h +++ b/src/Mobs/Silverfish.h @@ -1,24 +1,29 @@ #pragma once -#include "AggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorDoNothing.h" class cSilverfish : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cSilverfish(void) : - super("Silverfish", mtSilverfish, "entity.silverfish.hurt", "entity.silverfish.death", 0.3, 0.7) + super(mtSilverfish, "entity.silverfish.hurt", "entity.silverfish.death", 0.3, 0.7) { + m_BehaviorDoNothing.AttachToMonster(*this); + GetMonsterConfig("Silverfish"); } CLASS_PROTODEF(cSilverfish) + +private: + cBehaviorDoNothing m_BehaviorDoNothing; } ; diff --git a/src/Mobs/Skeleton.cpp b/src/Mobs/Skeleton.cpp index e48991a06..3e74ebeeb 100644 --- a/src/Mobs/Skeleton.cpp +++ b/src/Mobs/Skeleton.cpp @@ -6,14 +6,33 @@ #include "../Entities/ArrowEntity.h" #include "ClientHandle.h" - +void ArrowShootingFunction(cBehaviorAttackerRanged & a_Behavior, + cMonster & a_Attacker, cPawn & a_Attacked) +{ + UNUSED(a_Behavior); + auto & Random = GetRandomProvider(); + Vector3d Inaccuracy = Vector3d(Random.RandReal<double>(-0.25, 0.25), Random.RandReal<double>(-0.25, 0.25), Random.RandReal<double>(-0.25, 0.25)); + Vector3d Speed = (a_Attacked.GetPosition() + Inaccuracy - a_Attacker.GetPosition()) * 5; + Speed.y += Random.RandInt(-1, 1); + + auto Arrow = cpp14::make_unique<cArrowEntity>(&a_Attacker, + a_Attacker.GetPosX(), a_Attacker.GetPosY() + 1, a_Attacker.GetPosZ(), Speed); + auto ArrowPtr = Arrow.get(); + ArrowPtr->Initialize(std::move(Arrow), *(a_Attacker.GetWorld())); +} cSkeleton::cSkeleton(bool IsWither) : - super("Skeleton", mtSkeleton, "entity.skeleton.hurt", "entity.skeleton.death", 0.6, 1.8), - m_bIsWither(IsWither) + super(mtSkeleton, "entity.skeleton.hurt", "entity.skeleton.death", 0.6, 1.8), + m_bIsWither(IsWither), + m_BehaviorAttackerRanged(ArrowShootingFunction) { - SetBurnsInDaylight(true); + m_EMPersonality = AGGRESSIVE; + m_BehaviorAttackerRanged.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + m_BehaviorAggressive.AttachToMonster(*this); + m_BehaviourDayLightBurner.AttachToMonster(*this); + GetMonsterConfig("Skeleton"); } @@ -48,33 +67,6 @@ void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer) -bool cSkeleton::Attack(std::chrono::milliseconds a_Dt) -{ - StopMovingToPosition(); // Todo handle this in a better way, the skeleton does some uneeded recalcs due to inStateChasing - auto & Random = GetRandomProvider(); - if ((GetTarget() != nullptr) && (m_AttackCoolDownTicksLeft == 0)) - { - Vector3d Inaccuracy = Vector3d(Random.RandReal<double>(-0.25, 0.25), Random.RandReal<double>(-0.25, 0.25), Random.RandReal<double>(-0.25, 0.25)); - Vector3d Speed = (GetTarget()->GetPosition() + Inaccuracy - GetPosition()) * 5; - Speed.y += Random.RandInt(-1, 1); - - auto Arrow = cpp14::make_unique<cArrowEntity>(this, GetPosX(), GetPosY() + 1, GetPosZ(), Speed); - auto ArrowPtr = Arrow.get(); - if (!ArrowPtr->Initialize(std::move(Arrow), *m_World)) - { - return false; - } - - ResetAttackCooldown(); - return true; - } - return false; -} - - - - - void cSkeleton::SpawnOn(cClientHandle & a_ClientHandle) { super::SpawnOn(a_ClientHandle); diff --git a/src/Mobs/Skeleton.h b/src/Mobs/Skeleton.h index 0316fb9b5..59277729b 100644 --- a/src/Mobs/Skeleton.h +++ b/src/Mobs/Skeleton.h @@ -1,16 +1,17 @@ #pragma once -#include "AggressiveMonster.h" - - - +#include "Monster.h" +#include "Behaviors/BehaviorAttackerRanged.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Behaviors/BehaviorAggressive.h" +#include "Behaviors/BehaviorDayLightBurner.h" class cSkeleton : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cSkeleton(bool IsWither); @@ -18,7 +19,7 @@ public: CLASS_PROTODEF(cSkeleton) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual bool Attack(std::chrono::milliseconds a_Dt) override; + /*virtual bool Attack(std::chrono::milliseconds a_Dt) override;*/ virtual void SpawnOn(cClientHandle & a_ClientHandle) override; virtual bool IsUndead(void) override { return true; } @@ -29,6 +30,13 @@ private: bool m_bIsWither; + // tick behaviors + cBehaviorAttackerRanged m_BehaviorAttackerRanged; + cBehaviorWanderer m_BehaviorWanderer; + + // other behaviors + cBehaviorAggressive m_BehaviorAggressive; + cBehaviorDayLightBurner m_BehaviourDayLightBurner; } ; diff --git a/src/Mobs/Slime.cpp b/src/Mobs/Slime.cpp index 291a3a57f..10898fa4c 100644 --- a/src/Mobs/Slime.cpp +++ b/src/Mobs/Slime.cpp @@ -10,8 +10,7 @@ cSlime::cSlime(int a_Size) : - super("Slime", - mtSlime, + super(mtSlime, Printf("entity.%sslime.hurt", GetSizeName(a_Size).c_str()), Printf("entity.%sslime.death", GetSizeName(a_Size).c_str()), 0.6 * a_Size, @@ -19,8 +18,10 @@ cSlime::cSlime(int a_Size) : ), m_Size(a_Size) { + m_EMPersonality = AGGRESSIVE; SetMaxHealth(a_Size * a_Size); - SetAttackDamage(a_Size); + // SetAttackDamage(a_Size); //mobTodo myBehavior.setaTTACKDamage + GetMonsterConfig("Slime"); } @@ -45,8 +46,8 @@ void cSlime::GetDrops(cItems & a_Drops, cEntity * a_Killer) - -bool cSlime::Attack(std::chrono::milliseconds a_Dt) +//mobTodo +/*bool cSlime::Attack(std::chrono::milliseconds a_Dt) { if (m_Size > 1) { @@ -55,7 +56,7 @@ bool cSlime::Attack(std::chrono::milliseconds a_Dt) } return false; -} +}*/ diff --git a/src/Mobs/Slime.h b/src/Mobs/Slime.h index c78461a02..f4f1aabd4 100644 --- a/src/Mobs/Slime.h +++ b/src/Mobs/Slime.h @@ -1,16 +1,15 @@ - #pragma once -#include "AggressiveMonster.h" +#include "Monster.h" class cSlime : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: /** Creates a slime of the specified size; size can be 1, 2 or 4, with 1 is the smallest and 4 is the tallest. */ @@ -20,7 +19,7 @@ public: // cAggressiveMonster overrides: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual bool Attack(std::chrono::milliseconds a_Dt) override; + // virtual bool Attack(std::chrono::milliseconds a_Dt) override; virtual void KilledBy(TakeDamageInfo & a_TDI) override; int GetSize(void) const { return m_Size; } diff --git a/src/Mobs/SnowGolem.cpp b/src/Mobs/SnowGolem.cpp index c86577a1e..9e0295785 100644 --- a/src/Mobs/SnowGolem.cpp +++ b/src/Mobs/SnowGolem.cpp @@ -9,8 +9,11 @@ cSnowGolem::cSnowGolem(void) : - super("SnowGolem", mtSnowGolem, "entity.snowman.hurt", "entity.snowman.death", 0.4, 1.8) + super(mtSnowGolem, "entity.snowman.hurt", "entity.snowman.death", 0.4, 1.8) { + m_EMPersonality = PASSIVE; + m_BehaviorWanderer.AttachToMonster(*this); + GetMonsterConfig("SnowGolem"); } diff --git a/src/Mobs/SnowGolem.h b/src/Mobs/SnowGolem.h index 708ddbef9..0170a57a9 100644 --- a/src/Mobs/SnowGolem.h +++ b/src/Mobs/SnowGolem.h @@ -1,16 +1,16 @@ #pragma once -#include "PassiveAggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorWanderer.h" class cSnowGolem : - public cPassiveAggressiveMonster + public cMonster { - typedef cPassiveAggressiveMonster super; + typedef cMonster super; public: cSnowGolem(void); @@ -19,6 +19,9 @@ public: virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; + +private: + cBehaviorWanderer m_BehaviorWanderer; } ; diff --git a/src/Mobs/Spider.cpp b/src/Mobs/Spider.cpp index 971ff22f6..6db7905ec 100644 --- a/src/Mobs/Spider.cpp +++ b/src/Mobs/Spider.cpp @@ -7,10 +7,39 @@ #include "../Entities/Player.h" #include "../Chunk.h" +bool AggressiveAtNightFunction(cBehaviorAggressive & a_Behavior, cMonster & a_Monster, cChunk & a_Chunk) +{ + UNUSED(a_Behavior); + if (!a_Monster.GetWorld()->IsChunkLighted(a_Monster.GetChunkX(), a_Monster.GetChunkZ())) + { + return false; + } + + PREPARE_REL_AND_CHUNK(a_Monster.GetPosition(), a_Chunk); + if (!RelSuccess) + { + return false; + } + + if ( + !((Chunk->GetSkyLightAltered(Rel.x, Rel.y, Rel.z) > 11) || (Chunk->GetBlockLight(Rel.x, Rel.y, Rel.z) > 11)) + ) + { + return true; + } + + return false; +} cSpider::cSpider(void) : - super("Spider", mtSpider, "entity.spider.hurt", "entity.spider.death", 1.4, 0.9) + super(mtSpider, "entity.spider.hurt", "entity.spider.death", 1.4, 0.9) , + m_BehaviorAggressive(AggressiveAtNightFunction) { + m_EMPersonality = AGGRESSIVE; + m_BehaviorAttackerMelee.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + m_BehaviorAggressive.AttachToMonster(*this); + GetMonsterConfig("Spider"); } @@ -35,32 +64,6 @@ void cSpider::GetDrops(cItems & a_Drops, cEntity * a_Killer) -void cSpider::EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk) -{ - if (!GetWorld()->IsChunkLighted(GetChunkX(), GetChunkZ())) - { - return; - } - - PREPARE_REL_AND_CHUNK(GetPosition(), a_Chunk); - if (!RelSuccess) - { - return; - } - - if ( - a_Player->CanMobsTarget() && - !((Chunk->GetSkyLightAltered(Rel.x, Rel.y, Rel.z) > 11) || (Chunk->GetBlockLight(Rel.x, Rel.y, Rel.z) > 11)) - ) - { - super::EventSeePlayer(a_Player, a_Chunk); - } -} - - - - - bool cSpider::DoTakeDamage(TakeDamageInfo & a_TDI) { if (!super::DoTakeDamage(a_TDI)) @@ -68,6 +71,7 @@ bool cSpider::DoTakeDamage(TakeDamageInfo & a_TDI) return false; } + /* mobTodo // If the source of the damage is not from an pawn entity, switch to idle if ((a_TDI.Attacker == nullptr) || !a_TDI.Attacker->IsPawn()) { @@ -77,7 +81,7 @@ bool cSpider::DoTakeDamage(TakeDamageInfo & a_TDI) { // If the source of the damage is from a pawn entity, chase that entity m_EMState = CHASING; - } + }*/ return true; } diff --git a/src/Mobs/Spider.h b/src/Mobs/Spider.h index af2753012..7113b9a3e 100644 --- a/src/Mobs/Spider.h +++ b/src/Mobs/Spider.h @@ -1,16 +1,20 @@ #pragma once -#include "AggressiveMonster.h" +#include "Monster.h" +#include "Behaviors/BehaviorAttackerMelee.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Behaviors/BehaviorAggressive.h" +#include "Behaviors/BehaviorDayLightBurner.h" class cSpider : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cSpider(void); @@ -18,8 +22,14 @@ public: CLASS_PROTODEF(cSpider) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual void EventSeePlayer(cPlayer *, cChunk & a_Chunk) override; virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override; +private: + // tick behaviors + cBehaviorAttackerMelee m_BehaviorAttackerMelee; + cBehaviorWanderer m_BehaviorWanderer; + + // other behaviors + cBehaviorAggressive m_BehaviorAggressive; } ; diff --git a/src/Mobs/Squid.cpp b/src/Mobs/Squid.cpp index 8ae688a1b..0f35fe634 100644 --- a/src/Mobs/Squid.cpp +++ b/src/Mobs/Squid.cpp @@ -9,8 +9,11 @@ cSquid::cSquid(void) : - super("Squid", mtSquid, "entity.squid.hurt", "entity.squid.death", 0.95, 0.95) + super(mtSquid, "entity.squid.hurt", "entity.squid.death", 0.95, 0.95) { + m_EMPersonality = PASSIVE; + m_BehaviorDoNothing.AttachToMonster(*this); + GetMonsterConfig("Squid"); } diff --git a/src/Mobs/Squid.h b/src/Mobs/Squid.h index aeeec77df..8039089e3 100644 --- a/src/Mobs/Squid.h +++ b/src/Mobs/Squid.h @@ -1,16 +1,17 @@ #pragma once -#include "PassiveMonster.h" +#include "Monster.h" +#include "Behaviors/BehaviorDoNothing.h" class cSquid : - public cPassiveMonster + public cMonster { - typedef cPassiveMonster super; + typedef cMonster super; public: cSquid(); @@ -23,6 +24,9 @@ public: // Squids do not drown (or float) virtual void HandleAir(void) override {} + +private: + cBehaviorDoNothing m_BehaviorDoNothing; } ; diff --git a/src/Mobs/Villager.cpp b/src/Mobs/Villager.cpp index 26462ba31..ed3b3305f 100644 --- a/src/Mobs/Villager.cpp +++ b/src/Mobs/Villager.cpp @@ -12,11 +12,14 @@ cVillager::cVillager(eVillagerType VillagerType) : - super("Villager", mtVillager, "entity.villager.hurt", "entity.villager.death", 0.6, 1.8), + super(mtVillager, "entity.villager.hurt", "entity.villager.death", 0.6, 1.8), m_ActionCountDown(-1), m_Type(VillagerType), m_VillagerAction(false) { + m_EMPersonality = PASSIVE; + m_BehaviorDoNothing.AttachToMonster(*this); + GetMonsterConfig("Villager"); } diff --git a/src/Mobs/Villager.h b/src/Mobs/Villager.h index 6f3e7b4e8..72c7c7301 100644 --- a/src/Mobs/Villager.h +++ b/src/Mobs/Villager.h @@ -1,16 +1,21 @@ #pragma once -#include "PassiveMonster.h" +#include "Monster.h" #include "Blocks/ChunkInterface.h" +#include "Behaviors/BehaviorBreeder.h" +#include "Behaviors/BehaviorItemFollower.h" +#include "Behaviors/BehaviorCoward.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Monster.h" - +#include "Behaviors/BehaviorDoNothing.h" class cVillager : - public cPassiveMonster + public cMonster { - typedef cPassiveMonster super; + typedef cMonster super; public: @@ -53,12 +58,13 @@ public: bool DoesHaveActionActivated(void) const { return m_VillagerAction; } private: + // Tick controlling behaviors + cBehaviorDoNothing m_BehaviorDoNothing; int m_ActionCountDown; int m_Type; bool m_VillagerAction; Vector3i m_CropsPos; - } ; diff --git a/src/Mobs/Witch.cpp b/src/Mobs/Witch.cpp index 3f56108ae..43edf4b43 100644 --- a/src/Mobs/Witch.cpp +++ b/src/Mobs/Witch.cpp @@ -9,8 +9,11 @@ cWitch::cWitch(void) : - super("Witch", mtWitch, "entity.witch.hurt", "entity.witch.death", 0.6, 1.8) + super(mtWitch, "entity.witch.hurt", "entity.witch.death", 0.6, 1.8) { + m_EMPersonality = AGGRESSIVE; + m_BehaviorDoNothing.AttachToMonster(*this); + GetMonsterConfig("Witch"); } diff --git a/src/Mobs/Witch.h b/src/Mobs/Witch.h index 706fcd9b3..a57016a2a 100644 --- a/src/Mobs/Witch.h +++ b/src/Mobs/Witch.h @@ -1,16 +1,16 @@ #pragma once -#include "AggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorDoNothing.h" class cWitch : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cWitch(); @@ -18,8 +18,10 @@ public: CLASS_PROTODEF(cWitch) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; + bool IsAngry() const { return false; } - bool IsAngry(void) const {return ((m_EMState == ATTACKING) || (m_EMState == CHASING)); } +private: + cBehaviorDoNothing m_BehaviorDoNothing; } ; diff --git a/src/Mobs/Wither.cpp b/src/Mobs/Wither.cpp index dd85d7d2b..a76ac80a1 100644 --- a/src/Mobs/Wither.cpp +++ b/src/Mobs/Wither.cpp @@ -11,11 +11,14 @@ cWither::cWither(void) : - super("Wither", mtWither, "entity.wither.hurt", "entity.wither.death", 0.9, 4.0), + super(mtWither, "entity.wither.hurt", "entity.wither.death", 0.9, 4.0), m_WitherInvulnerableTicks(220) { SetMaxHealth(300); SetHealth(GetMaxHealth() / 3); + m_EMPersonality = AGGRESSIVE; + m_BehaviorDoNothing.AttachToMonster(*this); + GetMonsterConfig("Wither"); } diff --git a/src/Mobs/Wither.h b/src/Mobs/Wither.h index 5f6ec607c..39a2d5020 100644 --- a/src/Mobs/Wither.h +++ b/src/Mobs/Wither.h @@ -1,16 +1,16 @@ #pragma once -#include "AggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorDoNothing.h" class cWither : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cWither(void); @@ -33,6 +33,7 @@ public: virtual bool IsUndead(void) override { return true; } private: + cBehaviorDoNothing m_BehaviorDoNothing; /** The number of ticks of invulnerability left after being initially created. Zero once invulnerability has expired. */ unsigned int m_WitherInvulnerableTicks; diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp index f3b859c76..bfc7b10e8 100644 --- a/src/Mobs/Wolf.cpp +++ b/src/Mobs/Wolf.cpp @@ -12,7 +12,7 @@ cWolf::cWolf(void) : - super("Wolf", mtWolf, "entity.wolf.hurt", "entity.wolf.death", 0.6, 0.8), + super(mtWolf, "entity.wolf.hurt", "entity.wolf.death", 0.6, 0.8), m_IsSitting(false), m_IsTame(false), m_IsBegging(false), @@ -22,51 +22,16 @@ cWolf::cWolf(void) : m_NotificationCooldown(0) { m_RelativeWalkSpeed = 2; + m_EMPersonality = PASSIVE; + GetMonsterConfig("Wolf"); } - bool cWolf::DoTakeDamage(TakeDamageInfo & a_TDI) { - cPawn * PreviousTarget = GetTarget(); - if (!super::DoTakeDamage(a_TDI)) - { - return false; - } - - if ((a_TDI.Attacker != nullptr) && a_TDI.Attacker->IsPawn()) - { - auto currTarget = GetTarget(); - if ((currTarget != nullptr) && currTarget->IsPlayer()) - { - if (m_IsTame) - { - if ((static_cast<cPlayer*>(currTarget)->GetUUID() == m_OwnerUUID)) - { - SetTarget(PreviousTarget); // Do not attack owner - } - else - { - SetIsSitting(false); - NotifyAlliesOfFight(static_cast<cPawn*>(a_TDI.Attacker)); - } - } - else - { - m_IsAngry = true; - } - } - else if (m_IsTame) - { - SetIsSitting(false); - NotifyAlliesOfFight(static_cast<cPawn*>(a_TDI.Attacker)); - } - } - - m_World->BroadcastEntityMetadata(*this); // Broadcast health and possibly angry face - return true; + /*TODO bring from master and adapt*/ } @@ -75,42 +40,7 @@ bool cWolf::DoTakeDamage(TakeDamageInfo & a_TDI) void cWolf::NotifyAlliesOfFight(cPawn * a_Opponent) { - if (GetOwnerName() == "") - { - return; - } - m_NotificationCooldown = 15; - class cCallback : public cPlayerListCallback - { - virtual bool Item(cPlayer * a_Player) override - { - a_Player->NotifyNearbyWolves(m_Opponent, false); - return false; - } - public: - cPawn * m_Opponent; - } Callback; - - Callback.m_Opponent = a_Opponent; - m_World->DoWithPlayerByUUID(m_OwnerUUID, Callback); -} - -bool cWolf::Attack(std::chrono::milliseconds a_Dt) -{ - UNUSED(a_Dt); - - if ((GetTarget() != nullptr) && (GetTarget()->IsPlayer())) - { - if (static_cast<cPlayer *>(GetTarget())->GetUUID() == m_OwnerUUID) - { - SetTarget(nullptr); - return false; - } - } - - NotifyAlliesOfFight(static_cast<cPawn*>(GetTarget())); - return super::Attack(a_Dt); - + /*TODO bring from master and adapt*/ } @@ -119,47 +49,8 @@ bool cWolf::Attack(std::chrono::milliseconds a_Dt) void cWolf::ReceiveNearbyFightInfo(const cUUID & a_PlayerID, cPawn * a_Opponent, bool a_IsPlayerInvolved) { - if ( - (a_Opponent == nullptr) || IsSitting() || (!IsTame()) || - (!a_Opponent->IsPawn()) || (a_PlayerID != m_OwnerUUID) - ) - { - return; - } - - // If we already have a target - if (GetTarget() != nullptr) - { - // If a wolf is asking for help and we already have a target, do nothing - if (!a_IsPlayerInvolved) - { - return; - } - // If a player is asking for help and we already have a target, - // there's a 50% chance of helping and a 50% chance of doing nothing - // This helps spread a wolf pack's targets over several mobs - else if (GetRandomProvider().RandBool()) - { - return; - } - } - - if (a_Opponent->IsPlayer() && static_cast<cPlayer *>(a_Opponent)->GetUUID() == m_OwnerUUID) - { - return; // Our owner has hurt himself, avoid attacking them. - } - - if (a_Opponent->IsMob() && static_cast<cMonster *>(a_Opponent)->GetMobType() == mtWolf) - { - cWolf * Wolf = static_cast<cWolf *>(a_Opponent); - if (Wolf->GetOwnerUUID() == GetOwnerUUID()) - { - return; // Our owner attacked one of their wolves. Abort attacking wolf. - } - } - - SetTarget(a_Opponent); - + /*TODO bring from master and adapt + */ } @@ -169,82 +60,8 @@ void cWolf::ReceiveNearbyFightInfo(const cUUID & a_PlayerID, cPawn * a_Opponent, void cWolf::OnRightClicked(cPlayer & a_Player) { - const cItem & EquippedItem = a_Player.GetEquippedItem(); - const int EquippedItemType = EquippedItem.m_ItemType; - - if (!IsTame() && !IsAngry()) - { - // If the player is holding a bone, try to tame the wolf: - if (EquippedItemType == E_ITEM_BONE) - { - if (!a_Player.IsGameModeCreative()) - { - a_Player.GetInventory().RemoveOneEquippedItem(); - } - - if (GetRandomProvider().RandBool(0.125)) - { - // Taming succeeded - SetMaxHealth(20); - SetIsTame(true); - SetOwner(a_Player.GetName(), a_Player.GetUUID()); - m_World->BroadcastEntityStatus(*this, esWolfTamed); - m_World->GetBroadcaster().BroadcastParticleEffect("heart", static_cast<Vector3f>(GetPosition()), Vector3f{}, 0, 5); - } - else - { - // Taming failed - m_World->BroadcastEntityStatus(*this, esWolfTaming); - m_World->GetBroadcaster().BroadcastParticleEffect("smoke", static_cast<Vector3f>(GetPosition()), Vector3f{}, 0, 5); - } - } - } - else if (IsTame()) - { - // Feed the wolf, restoring its health, or dye its collar: - switch (EquippedItemType) - { - case E_ITEM_RAW_BEEF: - case E_ITEM_STEAK: - case E_ITEM_RAW_PORKCHOP: - case E_ITEM_COOKED_PORKCHOP: - case E_ITEM_RAW_CHICKEN: - case E_ITEM_COOKED_CHICKEN: - case E_ITEM_ROTTEN_FLESH: - { - if (m_Health < m_MaxHealth) - { - Heal(ItemHandler(EquippedItemType)->GetFoodInfo(&EquippedItem).FoodLevel); - if (!a_Player.IsGameModeCreative()) - { - a_Player.GetInventory().RemoveOneEquippedItem(); - } - } - break; - } - case E_ITEM_DYE: - { - if (a_Player.GetUUID() == m_OwnerUUID) // Is the player the owner of the dog? - { - SetCollarColor(EquippedItem.m_ItemDamage); - if (!a_Player.IsGameModeCreative()) - { - a_Player.GetInventory().RemoveOneEquippedItem(); - } - } - break; - } - default: - { - if (a_Player.GetUUID() == m_OwnerUUID) // Is the player the owner of the dog? - { - SetIsSitting(!IsSitting()); - } - } - } - } - - m_World->BroadcastEntityMetadata(*this); + /*TODO bring from master and adapt + */ } @@ -253,92 +70,11 @@ void cWolf::OnRightClicked(cPlayer & a_Player) void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { - if (!IsAngry()) - { - cMonster::Tick(a_Dt, a_Chunk); - if (m_NotificationCooldown > 0) - { - m_NotificationCooldown -= 1; - } - } - else - { - super::Tick(a_Dt, a_Chunk); - } - - if (!IsTicking()) - { - // The base class tick destroyed us - return; - } - - if (GetTarget() == nullptr) - { - cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance)); - if (a_Closest_Player != nullptr) - { - switch (a_Closest_Player->GetEquippedItem().m_ItemType) - { - case E_ITEM_BONE: - case E_ITEM_RAW_BEEF: - case E_ITEM_STEAK: - case E_ITEM_RAW_CHICKEN: - case E_ITEM_COOKED_CHICKEN: - case E_ITEM_ROTTEN_FLESH: - case E_ITEM_RAW_PORKCHOP: - case E_ITEM_COOKED_PORKCHOP: - { - if (!IsBegging()) - { - SetIsBegging(true); - m_World->BroadcastEntityMetadata(*this); - } - - m_FinalDestination = a_Closest_Player->GetPosition(); // So that we will look at a player holding food - - // Don't move to the player if the wolf is sitting. - if (!IsSitting()) - { - MoveToPosition(a_Closest_Player->GetPosition()); - } - - break; - } - default: - { - if (IsBegging()) - { - SetIsBegging(false); - m_World->BroadcastEntityMetadata(*this); - } - } - } - } - } - else - { - if (IsSitting()) - { - SetTarget(nullptr); - } - else - { - MoveToPosition(GetTarget()->GetPosition()); - if (TargetIsInRange()) - { - Attack(a_Dt); - } - } - } - - if (IsTame() && !IsSitting()) - { - TickFollowPlayer(); - } - else if (IsSitting()) - { - StopMovingToPosition(); - } + //mobTodo behaviors! + + /* + TODO bring from master and adapt + */ } @@ -347,61 +83,9 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cWolf::TickFollowPlayer() { - class cCallback : - public cPlayerListCallback - { - virtual bool Item(cPlayer * a_Player) override - { - OwnerPos = a_Player->GetPosition(); - OwnerFlying = a_Player->IsFlying(); - return true; - } - public: - Vector3d OwnerPos; - bool OwnerFlying; - } Callback; - - if (m_World->DoWithPlayerByUUID(m_OwnerUUID, Callback)) - { - // The player is present in the world, follow him: - double Distance = (Callback.OwnerPos - GetPosition()).Length(); - if (Distance > 20) - { - if (!Callback.OwnerFlying) - { - Callback.OwnerPos.y = FindFirstNonAirBlockPosition(Callback.OwnerPos.x, Callback.OwnerPos.z); - TeleportToCoords(Callback.OwnerPos.x, Callback.OwnerPos.y, Callback.OwnerPos.z); - SetTarget(nullptr); - } - } - if (Distance < 2) - { - if (GetTarget() == nullptr) - { - StopMovingToPosition(); - } - } - else - { - if (GetTarget() == nullptr) - { - if (!Callback.OwnerFlying) - { - MoveToPosition(Callback.OwnerPos); - } - } - } - } -} - - - -void cWolf::InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -{ - if (!IsTame()) - { - cMonster::InStateIdle(a_Dt, a_Chunk); - } + /* + TODO bring from master and adapt + */ } diff --git a/src/Mobs/Wolf.h b/src/Mobs/Wolf.h index 861419ba8..515cd33e8 100644 --- a/src/Mobs/Wolf.h +++ b/src/Mobs/Wolf.h @@ -1,7 +1,7 @@ #pragma once -#include "PassiveAggressiveMonster.h" +#include "Monster.h" #include "../UUID.h" @@ -10,9 +10,9 @@ class cEntity; class cWolf : - public cPassiveAggressiveMonster + public cMonster { - typedef cPassiveAggressiveMonster super; + typedef cMonster super; public: cWolf(void); @@ -24,7 +24,6 @@ public: virtual void OnRightClicked(cPlayer & a_Player) override; virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; virtual void TickFollowPlayer(); - virtual bool Attack(std::chrono::milliseconds a_Dt) override; // Get functions bool IsSitting (void) const override { return m_IsSitting; } @@ -56,8 +55,6 @@ public: @param a_IsPlayerInvolved Whether the fighter a player or a wolf. */ void ReceiveNearbyFightInfo(const cUUID & a_PlayerUUID, cPawn * a_Opponent, bool a_IsPlayerInvolved); - virtual void InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - protected: bool m_IsSitting; diff --git a/src/Mobs/Zombie.cpp b/src/Mobs/Zombie.cpp index 882e98bf1..085d90b7f 100644 --- a/src/Mobs/Zombie.cpp +++ b/src/Mobs/Zombie.cpp @@ -10,11 +10,17 @@ cZombie::cZombie(bool a_IsVillagerZombie) : - super("Zombie", mtZombie, "entity.zombie.hurt", "entity.zombie.death", 0.6, 1.8), + super(mtZombie, "entity.zombie.hurt", "entity.zombie.death", 0.6, 1.8), m_IsVillagerZombie(a_IsVillagerZombie), m_IsConverting(false) { - SetBurnsInDaylight(true); + m_EMPersonality = AGGRESSIVE; + m_BehaviorAttackerMelee.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + m_BehaviorAggressive.AttachToMonster(*this); + m_BehaviourDayLightBurner.AttachToMonster(*this); + GetMonsterConfig("Zombie"); + // mobTodo I need the config to load after attaching the Behaviors but this is not clean. } diff --git a/src/Mobs/Zombie.h b/src/Mobs/Zombie.h index 47a9f1904..5c2c225e4 100644 --- a/src/Mobs/Zombie.h +++ b/src/Mobs/Zombie.h @@ -1,15 +1,18 @@ #pragma once -#include "AggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorAttackerMelee.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Behaviors/BehaviorAggressive.h" +#include "Behaviors/BehaviorDayLightBurner.h" class cZombie : - public cAggressiveMonster + public cMonster { - typedef cAggressiveMonster super; + typedef cMonster super; public: cZombie(bool a_IsVillagerZombie); @@ -21,12 +24,18 @@ public: bool IsVillagerZombie(void) const { return m_IsVillagerZombie; } bool IsConverting (void) const { return m_IsConverting; } - private: bool m_IsVillagerZombie; bool m_IsConverting; + // tick behaviors + cBehaviorAttackerMelee m_BehaviorAttackerMelee; + cBehaviorWanderer m_BehaviorWanderer; + + // other behaviors + cBehaviorAggressive m_BehaviorAggressive; + cBehaviorDayLightBurner m_BehaviourDayLightBurner; } ; diff --git a/src/Mobs/ZombiePigman.cpp b/src/Mobs/ZombiePigman.cpp index 2581d3751..d817902f8 100644 --- a/src/Mobs/ZombiePigman.cpp +++ b/src/Mobs/ZombiePigman.cpp @@ -9,8 +9,13 @@ cZombiePigman::cZombiePigman(void) : - super("ZombiePigman", mtZombiePigman, "entity.zombie_pig.hurt", "entity.zombie_pig.death", 0.6, 1.8) + super(mtZombiePigman, "entity.zombie_pig.hurt", "entity.zombie_pig.death", 0.6, 1.8) { + m_EMPersonality = PASSIVE; + m_BehaviorAttackerMelee.AttachToMonster(*this); + m_BehaviorWanderer.AttachToMonster(*this); + m_BehaviorAggressive.AttachToMonster(*this); + GetMonsterConfig("ZombiePigman"); } diff --git a/src/Mobs/ZombiePigman.h b/src/Mobs/ZombiePigman.h index 23a7be8da..e3bab0539 100644 --- a/src/Mobs/ZombiePigman.h +++ b/src/Mobs/ZombiePigman.h @@ -1,15 +1,17 @@ #pragma once -#include "PassiveAggressiveMonster.h" - +#include "Monster.h" +#include "Behaviors/BehaviorAttackerMelee.h" +#include "Behaviors/BehaviorWanderer.h" +#include "Behaviors/BehaviorAggressive.h" class cZombiePigman : - public cPassiveAggressiveMonster + public cMonster { - typedef cPassiveAggressiveMonster super; + typedef cMonster super; public: cZombiePigman(void); @@ -21,6 +23,13 @@ public: virtual void SpawnOn(cClientHandle & a_ClientHandle) override; virtual bool IsUndead(void) override { return true; } + + // tick behaviors + cBehaviorAttackerMelee m_BehaviorAttackerMelee; + cBehaviorWanderer m_BehaviorWanderer; + + // other behaviors + cBehaviorAggressive m_BehaviorAggressive; } ; |