diff options
Diffstat (limited to 'src/Mobs/Behaviors')
27 files changed, 1613 insertions, 0 deletions
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..2e3333e89 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAggressive.cpp @@ -0,0 +1,42 @@ +#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" + +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); + + // 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..840d925d5 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAggressive.h @@ -0,0 +1,31 @@ +#pragma once + + +class cBehaviorAggressive; + +#include "Behavior.h" + +/** The mob is agressive toward specific mobtypes, or toward the player. +This Behavior has a dependency on BehaviorAttacker. */ +class cBehaviorAggressive : public cBehavior +{ + +public: + 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; +}; diff --git a/src/Mobs/Behaviors/BehaviorAttacker.cpp b/src/Mobs/Behaviors/BehaviorAttacker.cpp new file mode 100644 index 000000000..031b5ddf9 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttacker.cpp @@ -0,0 +1,288 @@ + +#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_TicksSinceLastDamaged(100) + , m_IsStriking(false) + , m_Target(nullptr) +{ + +} + + + + + +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); + + if (m_IsStriking) + { + if (StrikeTarget(++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 (TargetOutOfSight()) + { + SetTarget(nullptr); + } + else + { + if (TargetIsInStrikeRadiusAndLineOfSight()) + { + StrikeTargetIfReady(); + } + 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_TicksSinceLastDamaged < 100) + { + ++m_TicksSinceLastDamaged; + } + + if (m_AttackCoolDownTicksLeft > 0) + { + m_AttackCoolDownTicksLeft -= 1; + } +} + + + + + +void cBehaviorAttacker::DoTakeDamage(TakeDamageInfo & a_TDI) +{ + 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)); + } + m_TicksSinceLastDamaged = 0; + } +} + + + + + +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::StrikeTarget() +{ + if (m_IsStriking || (m_Target == nullptr)) + { + return; + } + m_IsStriking = true; + m_StrikeTickCnt = 0; + m_Parent->PinBehavior(this); +} + + + + + +bool cBehaviorAttacker::TargetIsInStrikeRadius(void) +{ + ASSERT(GetTarget() != nullptr); + return ((GetTarget()->GetPosition() - m_Parent->GetPosition()).SqrLength() < (m_AttackRange * m_AttackRange)); +} + + + + + +bool cBehaviorAttacker::TargetIsInStrikeRadiusAndLineOfSight() +{ + ASSERT(m_Target != nullptr); + ASSERT(m_Parent != nullptr); + + + cTracer LineOfSight(m_Parent->GetWorld()); + Vector3d MyHeadPosition = m_Parent->GetPosition() + Vector3d(0, m_Parent->GetHeight(), 0); + Vector3d AttackDirection(GetTarget()->GetPosition() + Vector3d(0, GetTarget()->GetHeight(), 0) - MyHeadPosition); + + if (TargetIsInStrikeRadius()/* && + !LineOfSight.Trace(MyHeadPosition, AttackDirection, static_cast<int>(AttackDirection.Length()))*/ && + (m_Parent->GetHealth() > 0.0) + ) + { + return true; + } + else + { + return false; + } +} + + + + + +bool cBehaviorAttacker::TargetOutOfSight() +{ + 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::StrikeTargetIfReady() +{ + if (m_AttackCoolDownTicksLeft == 0) + { + StrikeTarget(); + } +} diff --git a/src/Mobs/Behaviors/BehaviorAttacker.h b/src/Mobs/Behaviors/BehaviorAttacker.h new file mode 100644 index 000000000..78ed70994 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttacker.h @@ -0,0 +1,81 @@ +#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(); + 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 strike a target the next tick. Ignores the strike cooldown. + * Ignored if already striking or if no target is set. */ + void StrikeTarget(); + + /** 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 StrikeTargetIfReady(); +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. */ + virtual bool StrikeTarget(int a_StrikeTickCnt) = 0; + + // Target related methods + bool TargetIsInStrikeRadius(); + bool TargetIsInStrikeRadiusAndLineOfSight(); + bool TargetOutOfSight(); + void StrikeTargetIfReady(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; + + int m_TicksSinceLastDamaged; // How many ticks ago were we last damaged by a player? + + bool m_IsStriking; + + /** Our parent */ + cMonster * m_Parent; + +private: + + // The mob we want to attack + cPawn * m_Target; + int m_StrikeTickCnt; + +}; diff --git a/src/Mobs/Behaviors/BehaviorAttackerMelee.cpp b/src/Mobs/Behaviors/BehaviorAttackerMelee.cpp new file mode 100644 index 000000000..337d8d216 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttackerMelee.cpp @@ -0,0 +1,13 @@ +#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" + +bool cBehaviorAttackerMelee::StrikeTarget(int a_StrikeTickCnt) +{ + UNUSED(a_StrikeTickCnt); + GetTarget()->TakeDamage(dtMobAttack, m_Parent, m_AttackDamage, 0); + 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..6e990f04c --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorAttackerMelee.h @@ -0,0 +1,11 @@ +#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 cBehaviorAttackerMelee : public cBehaviorAttacker +{ +public: + bool StrikeTarget(int a_StrikeTickCnt) override; +}; 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..7ede92c52 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorBreeder.cpp @@ -0,0 +1,252 @@ +#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); +} 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..e1b0d2a25 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorCoward.cpp @@ -0,0 +1,98 @@ +#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_OldDontCare = m_Parent->GetPathFinder().getDontCare(); + 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(m_OldDontCare); + 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..16d68872c --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorCoward.h @@ -0,0 +1,23 @@ +#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 + bool m_OldDontCare; +}; diff --git a/src/Mobs/Behaviors/BehaviorDayLightBurner.cpp b/src/Mobs/Behaviors/BehaviorDayLightBurner.cpp new file mode 100644 index 000000000..0b0faed08 --- /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); +} + + + + + +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)) + { + // Burn for 100 ticks, then decide again + m_Parent->StartBurning(100); + } +} + + + + +bool cBehaviorDayLightBurner::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk) +{ + 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 + (m_Parent->GetWorld()->GetTimeOfDay() < 12000 + 1000) && // Daytime + m_Parent->GetWorld()->IsWeatherSunnyAt(static_cast<int>(m_Parent->GetPosX()), static_cast<int>(m_Parent->GetPosZ())) // Not raining + ) + { + int MobHeight = CeilC(a_Location.y + m_Parent->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..07812b9db --- /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; + bool WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk); + +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..a37609aa4 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorWanderer.cpp @@ -0,0 +1,103 @@ +#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_OldDontCare = m_Parent->GetPathFinder().getDontCare(); + 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(m_OldDontCare); + 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..219ad32c3 --- /dev/null +++ b/src/Mobs/Behaviors/BehaviorWanderer.h @@ -0,0 +1,25 @@ +#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; + bool m_OldDontCare; +}; diff --git a/src/Mobs/Behaviors/CMakeLists.txt b/src/Mobs/Behaviors/CMakeLists.txt new file mode 100644 index 000000000..e92c850e3 --- /dev/null +++ b/src/Mobs/Behaviors/CMakeLists.txt @@ -0,0 +1,41 @@ + +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 + 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 + BehaviorWanderer.h +) + +if(NOT MSVC) + add_library(Behaviors ${SRCS} ${HDRS}) +endif() |