summaryrefslogtreecommitdiffstats
path: root/src/Mobs/Behaviors
diff options
context:
space:
mode:
Diffstat (limited to 'src/Mobs/Behaviors')
-rw-r--r--src/Mobs/Behaviors/Behavior.cpp102
-rw-r--r--src/Mobs/Behaviors/Behavior.h30
-rw-r--r--src/Mobs/Behaviors/BehaviorAggressive.cpp42
-rw-r--r--src/Mobs/Behaviors/BehaviorAggressive.h31
-rw-r--r--src/Mobs/Behaviors/BehaviorAttacker.cpp288
-rw-r--r--src/Mobs/Behaviors/BehaviorAttacker.h81
-rw-r--r--src/Mobs/Behaviors/BehaviorAttackerMelee.cpp13
-rw-r--r--src/Mobs/Behaviors/BehaviorAttackerMelee.h11
-rw-r--r--src/Mobs/Behaviors/BehaviorBrave.cpp27
-rw-r--r--src/Mobs/Behaviors/BehaviorBrave.h15
-rw-r--r--src/Mobs/Behaviors/BehaviorBreeder.cpp252
-rw-r--r--src/Mobs/Behaviors/BehaviorBreeder.h52
-rw-r--r--src/Mobs/Behaviors/BehaviorCoward.cpp98
-rw-r--r--src/Mobs/Behaviors/BehaviorCoward.h23
-rw-r--r--src/Mobs/Behaviors/BehaviorDayLightBurner.cpp103
-rw-r--r--src/Mobs/Behaviors/BehaviorDayLightBurner.h17
-rw-r--r--src/Mobs/Behaviors/BehaviorDoNothing.cpp24
-rw-r--r--src/Mobs/Behaviors/BehaviorDoNothing.h19
-rw-r--r--src/Mobs/Behaviors/BehaviorItemDropper.cpp50
-rw-r--r--src/Mobs/Behaviors/BehaviorItemDropper.h17
-rw-r--r--src/Mobs/Behaviors/BehaviorItemFollower.cpp64
-rw-r--r--src/Mobs/Behaviors/BehaviorItemFollower.h19
-rw-r--r--src/Mobs/Behaviors/BehaviorItemReplacer.cpp40
-rw-r--r--src/Mobs/Behaviors/BehaviorItemReplacer.h26
-rw-r--r--src/Mobs/Behaviors/BehaviorWanderer.cpp103
-rw-r--r--src/Mobs/Behaviors/BehaviorWanderer.h25
-rw-r--r--src/Mobs/Behaviors/CMakeLists.txt41
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()