summaryrefslogtreecommitdiffstats
path: root/src/Mobs/Monster.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Mobs/Monster.cpp')
-rw-r--r--src/Mobs/Monster.cpp627
1 files changed, 311 insertions, 316 deletions
diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp
index 944e8aa94..ae2d601cc 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,113 @@ 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 (m_PinnedBehavior != nullptr)
{
- if (!static_cast<cPlayer *>(GetTarget())->CanMobsTarget())
+ // A behavior is pinned. We give it control automatically.
+ ASSERT(m_CurrentTickControllingBehavior == m_PinnedBehavior);
+ m_CurrentTickControllingBehavior->Tick(a_Dt, a_Chunk);
+ }
+ 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
+ if (m_CurrentTickControllingBehavior == m_NewTickControllingBehavior)
+ {
+ // The Behavior asking for control is the same as the behavior from last tick.
+ // Nothing special, just tick it.
+ // LOGD("mobDebug - Tick");
+ m_CurrentTickControllingBehavior->Tick(a_Dt, a_Chunk);
+ }
+ else if (m_CurrentTickControllingBehavior == nullptr)
+ {
+ // first behavior to ever control
+ m_TickControllingBehaviorState = NewControlStarting;
+ }
+ else
{
- SetTarget(nullptr);
- m_EMState = IDLE;
+ // The behavior asking for control is not the same as the behavior from last tick.
+ // Begin the control swapping process.
+ m_TickControllingBehaviorState = OldControlEnding;
}
}
+
+ }
+
+ // 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 +428,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 +461,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 +470,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 +489,11 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
m_World->BroadcastEntityMetadata(*this);
}
}
+
+ if (m_TicksSinceLastDamaged < 1000)
+ {
+ ++m_TicksSinceLastDamaged;
+ }
}
@@ -458,9 +536,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 +551,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 +634,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 +738,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 +749,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 +780,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 +995,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 +1021,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;
+}
+
+
+
+
+cBehaviorBreeder * cMonster::GetBehaviorBreeder()
+{
+ return m_BehaviorBreederPointer;
+}
+
+
+
+
+
+const cBehaviorBreeder * cMonster::GetBehaviorBreeder() const
+{
+ 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::UnsafeUnsetTarget()
+void cMonster::GetBreedingItems(cItems & a_Items)
{
- m_Target = nullptr;
+ return GetFollowedItems(a_Items);
}
-cPawn * cMonster::GetTarget()
+cPlayer * cMonster::GetNearestPlayer()
{
- return m_Target;
+ 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 +1299,89 @@ 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)
+{
+ ASSERT(m_TickControllingBehaviorState == Normal);
+ m_PinnedBehavior = a_Behavior;
+ ASSERT(m_CurrentTickControllingBehavior == m_PinnedBehavior);
+}
+
+
+
+
+
+void cMonster::UnpinBehavior(cBehavior * a_Behavior)
+{
+ ASSERT(m_TickControllingBehaviorState == Normal);
+ ASSERT(m_PinnedBehavior = a_Behavior);
+ m_PinnedBehavior = nullptr;
+}
+
+
+
+
+
+cPathFinder & cMonster::GetPathFinder()
+{
+ return m_PathFinder;
}