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.cpp438
1 files changed, 251 insertions, 187 deletions
diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp
index 55d83302a..84f58ff85 100644
--- a/src/Mobs/Monster.cpp
+++ b/src/Mobs/Monster.cpp
@@ -13,7 +13,7 @@
#include "../Chunk.h"
#include "../FastRandom.h"
-
+#include "Path.h"
@@ -74,8 +74,12 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A
, m_EMState(IDLE)
, m_EMPersonality(AGGRESSIVE)
, m_Target(nullptr)
- , m_bMovingToDestination(false)
+ , m_Path(nullptr)
+ , m_IsFollowingPath(false)
+ , m_GiveUpCounter(0)
+ , m_TicksSinceLastPathReset(1000)
, m_LastGroundHeight(POSY_TOINT)
+ , m_JumpCoolDown(0)
, m_IdleInterval(0)
, m_DestroyTimer(0)
, m_MobType(a_MobType)
@@ -94,8 +98,9 @@ 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.0)
+ , m_RelativeWalkSpeed(1)
{
if (!a_ConfigName.empty())
{
@@ -116,89 +121,137 @@ void cMonster::SpawnOn(cClientHandle & a_Client)
-void cMonster::TickPathFinding()
+bool cMonster::TickPathFinding(cChunk & a_Chunk)
{
- const int PosX = POSX_TOINT;
- const int PosY = POSY_TOINT;
- const int PosZ = POSZ_TOINT;
-
- std::vector<Vector3d> m_PotentialCoordinates;
- m_TraversedCoordinates.push_back(Vector3i(PosX, PosY, PosZ));
-
- static const struct // Define which directions to try to move to
+ if (!m_IsFollowingPath)
{
- int x, z;
- } gCrossCoords[] =
+ return false;
+ }
+ if (m_TicksSinceLastPathReset < 1000)
{
- { 1, 0},
- {-1, 0},
- { 0, 1},
- { 0, -1},
- } ;
-
- if ((PosY - 1 < 0) || (PosY + 2 >= cChunkDef::Height) /* PosY + 1 will never be true if PosY + 2 is not */)
+ // No need to count beyond 1000. 1000 is arbitary here.
+ ++m_TicksSinceLastPathReset;
+ }
+
+ if (ReachedFinalDestination())
{
- // Too low/high, can't really do anything
- FinishPathFinding();
- return;
+ StopMovingToPosition();
+ return false;
}
- for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
+ if ((m_FinalDestination - m_PathFinderDestination).Length() > 0.25) // if the distance between where we're going and where we should go is too big.
{
- if (IsCoordinateInTraversedList(Vector3i(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ)))
+ /* If we reached the last path waypoint,
+ Or if we haven't re-calculated for too long.
+ Interval is proportional to distance squared, and its minimum is 10.
+ (Recalculate lots when close, calculate rarely when far) */
+ if (
+ ((GetPosition() - m_PathFinderDestination).Length() < 0.25) ||
+ ((m_TicksSinceLastPathReset > 10) && (m_TicksSinceLastPathReset > (0.15 * (m_FinalDestination - GetPosition()).SqrLength())))
+ )
{
- continue;
+ ResetPathFinding();
}
+ }
- BLOCKTYPE BlockAtY = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ);
- BLOCKTYPE BlockAtYP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 1, gCrossCoords[i].z + PosZ);
- BLOCKTYPE BlockAtYPP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 2, gCrossCoords[i].z + PosZ);
- int LowestY = FindFirstNonAirBlockPosition(gCrossCoords[i].x + PosX, gCrossCoords[i].z + PosZ);
- BLOCKTYPE BlockAtLowestY = (LowestY >= cChunkDef::Height) ? E_BLOCK_AIR : m_World->GetBlock(gCrossCoords[i].x + PosX, LowestY, gCrossCoords[i].z + PosZ);
+ if (m_Path == nullptr)
+ {
+ if (!EnsureProperDestination(a_Chunk))
+ {
+ StopMovingToPosition(); // Invalid chunks, probably world is loading or something, cancel movement.
+ return false;
+ }
+ m_PathFinderDestination = m_FinalDestination;
+ m_Path = new cPath(a_Chunk, GetPosition().Floor(), m_PathFinderDestination.Floor(), 20);
+ }
- if (
- (!cBlockInfo::IsSolid(BlockAtY)) &&
- (!cBlockInfo::IsSolid(BlockAtYP)) &&
- (!IsBlockLava(BlockAtLowestY)) &&
- (BlockAtLowestY != E_BLOCK_CACTUS) &&
- (PosY - LowestY < FALL_DAMAGE_HEIGHT)
- )
+ switch (m_Path->Step(a_Chunk))
+ {
+ case ePathFinderStatus::PATH_NOT_FOUND:
{
- m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ));
+ StopMovingToPosition(); // Give up pathfinding to that destination.
+ break;
}
- else if (
- (cBlockInfo::IsSolid(BlockAtY)) &&
- (BlockAtY != E_BLOCK_CACTUS) &&
- (!cBlockInfo::IsSolid(BlockAtYP)) &&
- (!cBlockInfo::IsSolid(BlockAtYPP)) &&
- (BlockAtY != E_BLOCK_FENCE) &&
- (BlockAtY != E_BLOCK_FENCE_GATE)
- )
+ case ePathFinderStatus::CALCULATING:
{
- m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ));
+ // Pathfinder needs more time
+ break;
+ }
+ case ePathFinderStatus::PATH_FOUND:
+ {
+ if (--m_GiveUpCounter == 0)
+ {
+ ResetPathFinding(); // Try to calculate a path again.
+ return false;
+ }
+ else if (!m_Path->IsLastPoint() && (m_Path->IsFirstPoint() || ReachedNextWaypoint())) // Have we arrived at the next cell, as denoted by m_NextWayPointPosition?
+ {
+ m_NextWayPointPosition = Vector3d(0.5, 0, 0.5) + m_Path->GetNextPoint();
+ m_GiveUpCounter = 40; // Give up after 40 ticks (2 seconds) if failed to reach m_NextWayPointPosition.
+ }
+ return true;
}
}
- if (!m_PotentialCoordinates.empty())
+ return false;
+}
+
+
+
+
+
+void cMonster::MoveToWayPoint(cChunk & a_Chunk)
+{
+ if (m_JumpCoolDown == 0)
{
- Vector3f ShortestCoords = m_PotentialCoordinates.front();
- for (std::vector<Vector3d>::const_iterator itr = m_PotentialCoordinates.begin(); itr != m_PotentialCoordinates.end(); ++itr)
+ if (DoesPosYRequireJump(FloorC(m_NextWayPointPosition.y)))
{
- Vector3f Distance = m_FinalDestination - ShortestCoords;
- Vector3f Distance2 = m_FinalDestination - *itr;
- if (Distance.SqrLength() > Distance2.SqrLength())
+ if (
+ (IsOnGround() && (GetSpeedX() == 0) && (GetSpeedY() == 0)) ||
+ (IsSwimming() && (m_GiveUpCounter < 15))
+ )
{
- ShortestCoords = *itr;
+ m_bOnGround = false;
+ m_JumpCoolDown = 20;
+ // TODO: Change to AddSpeedY once collision detection is fixed - currently, mobs will go into blocks attempting to jump without a teleport
+ AddPosY(1.6); // Jump!!
+ SetSpeedX(3.2 * (m_NextWayPointPosition.x - GetPosition().x)); // Move forward in a preset speed.
+ SetSpeedZ(3.2 * (m_NextWayPointPosition.z - GetPosition().z)); // The numbers were picked based on trial and error and 1.6 and 3.2 are perfect.
}
}
-
- m_Destination = ShortestCoords;
- m_Destination.z += 0.5f;
- m_Destination.x += 0.5f;
}
else
{
- FinishPathFinding();
+ --m_JumpCoolDown;
+ }
+
+ Vector3d Distance = m_NextWayPointPosition - GetPosition();
+ if ((Distance.x != 0) || (Distance.z != 0))
+ {
+ Distance.y = 0;
+ Distance.Normalize();
+
+ if (m_bOnGround)
+ {
+ Distance *= 2.5f;
+ }
+ else if (IsSwimming())
+ {
+ Distance *= 1.3f;
+ }
+ else
+ {
+ // Don't let the mob move too much if he's falling.
+ Distance *= 0.25f;
+ }
+ // Apply walk speed:
+ Distance *= m_RelativeWalkSpeed;
+ /* Reduced default speed.
+ Close to Vanilla, easier for mobs to follow m_NextWayPointPositions, hence
+ better pathfinding. */
+ Distance *= 0.5;
+ AddSpeedX(Distance.x);
+ AddSpeedZ(Distance.z);
}
}
@@ -206,47 +259,90 @@ void cMonster::TickPathFinding()
-void cMonster::MoveToPosition(const Vector3d & a_Position)
+bool cMonster::EnsureProperDestination(cChunk & a_Chunk)
{
- FinishPathFinding();
+ cChunk * Chunk = a_Chunk.GetNeighborChunk(m_FinalDestination.x, m_FinalDestination.z);
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+ int RelX = m_FinalDestination.x - Chunk->GetPosX() * cChunkDef::Width;
+ int RelZ = m_FinalDestination.z - Chunk->GetPosZ() * cChunkDef::Width;
+ if ((Chunk == nullptr) || !Chunk->IsValid())
+ {
+ return false;
+ }
+
+ // If destination in the air, go down to the lowest air block.
+ while (m_FinalDestination.y > 0)
+ {
+ Chunk->GetBlockTypeMeta(RelX, m_FinalDestination.y - 1, RelZ, BlockType, BlockMeta);
+ if (cBlockInfo::IsSolid(BlockType))
+ {
+ break;
+ }
+ m_FinalDestination.y -= 1;
+ }
+
+
+ // If destination in water, go up to the highest water block.
+ // If destination in solid, go up to first air block.
+ bool InWater = false;
+ while (m_FinalDestination.y < cChunkDef::Height)
+ {
+ Chunk->GetBlockTypeMeta(RelX, m_FinalDestination.y, RelZ, BlockType, BlockMeta);
+ if (BlockType == E_BLOCK_STATIONARY_WATER)
+ {
+ InWater = true;
+ }
+ else if (cBlockInfo::IsSolid(BlockType))
+ {
+ InWater = false;
+ }
+ else
+ {
+ break;
+ }
+ m_FinalDestination.y += 1;
+ }
+ if (InWater)
+ {
+ m_FinalDestination.y -= 1;
+ }
- m_FinalDestination = a_Position;
- m_bMovingToDestination = true;
- TickPathFinding();
+
+ return true;
}
-bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords)
+
+
+void cMonster::MoveToPosition(const Vector3d & a_Position)
{
- return (std::find(m_TraversedCoordinates.begin(), m_TraversedCoordinates.end(), a_Coords) != m_TraversedCoordinates.end());
+ m_FinalDestination = a_Position;
+ m_IsFollowingPath = true;
}
-bool cMonster::ReachedDestination()
+void cMonster::StopMovingToPosition()
{
- if ((m_Destination - GetPosition()).Length() < 0.5f)
- {
- return true;
- }
-
- return false;
+ m_IsFollowingPath = false;
}
-bool cMonster::ReachedFinalDestination()
+
+void cMonster::ResetPathFinding(void)
{
- if ((GetPosition() - m_FinalDestination).Length() <= m_AttackRange)
+ m_TicksSinceLastPathReset = 0;
+ if (m_Path != nullptr)
{
- return true;
+ delete m_Path;
+ m_Path = nullptr;
}
-
- return false;
}
@@ -256,10 +352,11 @@ bool cMonster::ReachedFinalDestination()
void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ GET_AND_VERIFY_CURRENT_CHUNK(Chunk, POSX_TOINT, POSZ_TOINT);
if (m_Health <= 0)
{
- // The mob is dead, but we're still animating the "puff" they leave when they die
+ // The mob is dead, but we're still animating the "puff" they leave when they die.
m_DestroyTimer += a_Dt;
if (m_DestroyTimer > std::chrono::seconds(1))
{
@@ -268,73 +365,36 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
return;
}
+ if (m_TicksSinceLastDamaged < 100)
+ {
+ ++m_TicksSinceLastDamaged;
+ }
if ((m_Target != nullptr) && m_Target->IsDestroyed())
{
m_Target = nullptr;
}
- // Burning in daylight
- HandleDaylightBurning(a_Chunk);
-
- if (m_bMovingToDestination)
+ // Process the undead burning in daylight.
+ HandleDaylightBurning(*Chunk, WouldBurnAt(GetPosition(), *Chunk));
+ if (TickPathFinding(*Chunk))
{
- if (m_bOnGround)
- {
- if (DoesPosYRequireJump((int)floor(m_Destination.y)))
- {
- m_bOnGround = false;
-
- // TODO: Change to AddSpeedY once collision detection is fixed - currently, mobs will go into blocks attempting to jump without a teleport
- AddPosY(1.2); // Jump!!
- }
- }
-
- Vector3d Distance = m_Destination - GetPosition();
- if (!ReachedDestination() && !ReachedFinalDestination()) // If we haven't reached any sort of destination, move
+ /* 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. */
+ if (
+ m_BurnsInDaylight && ((m_TicksSinceLastDamaged >= 100) || (m_EMState == IDLE)) &&
+ WouldBurnAt(m_NextWayPointPosition, *Chunk) &&
+ !WouldBurnAt(GetPosition(), *Chunk)
+ )
{
- Distance.y = 0;
- Distance.Normalize();
-
- if (m_bOnGround)
- {
- Distance *= 2.5f;
- }
- else if (IsSwimming())
- {
- Distance *= 1.3f;
- }
- else
- {
- // Don't let the mob move too much if he's falling.
- Distance *= 0.25f;
- }
-
- // Apply walk speed:
- Distance *= m_RelativeWalkSpeed;
-
- AddSpeedX(Distance.x);
- AddSpeedZ(Distance.z);
-
- // It's too buggy!
- /*
- if (m_EMState == ESCAPING)
- {
- // Runs Faster when escaping :D otherwise they just walk away
- SetSpeedX (GetSpeedX() * 2.f);
- SetSpeedZ (GetSpeedZ() * 2.f);
- }
- */
+ // 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();
+ m_GiveUpCounter = 40; // This doesn't count as giving up, keep the giveup timer as is.
}
else
{
- if (ReachedFinalDestination()) // If we have reached the ultimate, final destination, stop pathfinding and attack if appropriate
- {
- FinishPathFinding();
- }
- else
- {
- TickPathFinding(); // We have reached the next point in our path, calculate another point
- }
+ MoveToWayPoint(*Chunk);
}
}
@@ -345,13 +405,13 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
case IDLE:
{
- // If enemy passive we ignore checks for player visibility
+ // If enemy passive we ignore checks for player visibility.
InStateIdle(a_Dt);
break;
}
case CHASING:
{
- // If we do not see a player anymore skip chasing action
+ // If we do not see a player anymore skip chasing action.
InStateChasing(a_Dt);
break;
}
@@ -360,7 +420,6 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
InStateEscaping(a_Dt);
break;
}
-
case ATTACKING: break;
} // switch (m_EMState)
@@ -370,6 +429,7 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
+
void cMonster::SetPitchAndYawFromDestination()
{
Vector3d FinalDestination = m_FinalDestination;
@@ -377,38 +437,36 @@ void cMonster::SetPitchAndYawFromDestination()
{
if (m_Target->IsPlayer())
{
- FinalDestination.y = ((cPlayer *)m_Target)->GetStance();
+ FinalDestination.y = static_cast<cPlayer *>(m_Target)->GetStance() - 1;
}
else
{
- FinalDestination.y = GetHeight();
+ FinalDestination.y = m_Target->GetPosY() + GetHeight();
}
}
Vector3d Distance = FinalDestination - GetPosition();
- if (Distance.SqrLength() > 0.1f)
{
- {
- double Rotation, Pitch;
- Distance.Normalize();
- VectorToEuler(Distance.x, Distance.y, Distance.z, Rotation, Pitch);
- SetHeadYaw(Rotation);
- SetPitch(-Pitch);
- }
+ double Rotation, Pitch;
+ Distance.Normalize();
+ VectorToEuler(Distance.x, Distance.y, Distance.z, Rotation, Pitch);
+ SetHeadYaw(Rotation);
+ SetPitch(-Pitch);
+ }
- {
- Vector3d BodyDistance = m_Destination - GetPosition();
- double Rotation, Pitch;
- Distance.Normalize();
- VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, Rotation, Pitch);
- SetYaw(Rotation);
- }
+ {
+ Vector3d BodyDistance = m_NextWayPointPosition - GetPosition();
+ double Rotation, Pitch;
+ BodyDistance.Normalize();
+ VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, Rotation, Pitch);
+ SetYaw(Rotation);
}
}
+
void cMonster::HandleFalling()
{
if (m_bOnGround)
@@ -460,7 +518,6 @@ int cMonster::FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ)
-
bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
{
if (!super::DoTakeDamage(a_TDI))
@@ -476,6 +533,7 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
if (a_TDI.Attacker != nullptr)
{
m_Target = a_TDI.Attacker;
+ m_TicksSinceLastDamaged = 0;
}
return true;
}
@@ -641,7 +699,7 @@ void cMonster::EventLosePlayer(void)
void cMonster::InStateIdle(std::chrono::milliseconds a_Dt)
{
- if (m_bMovingToDestination)
+ if (m_IsFollowingPath)
{
return; // Still getting there
}
@@ -661,14 +719,8 @@ void cMonster::InStateIdle(std::chrono::milliseconds a_Dt)
if ((Dist.SqrLength() > 2) && (rem >= 3))
{
Vector3d Destination(GetPosX() + Dist.x, 0, GetPosZ() + Dist.z);
-
- int NextHeight = FindFirstNonAirBlockPosition(Destination.x, Destination.z);
-
- if (IsNextYPosReachable(NextHeight))
- {
- Destination.y = NextHeight;
- MoveToPosition(Destination);
- }
+ Destination.y = FindFirstNonAirBlockPosition(Destination.x, Destination.z);
+ MoveToPosition(Destination);
}
}
}
@@ -692,7 +744,7 @@ void cMonster::InStateChasing(std::chrono::milliseconds a_Dt)
void cMonster::InStateEscaping(std::chrono::milliseconds a_Dt)
{
UNUSED(a_Dt);
-
+
if (m_Target != nullptr)
{
Vector3d newloc = GetPosition();
@@ -771,7 +823,7 @@ AString cMonster::MobTypeToString(eMonsterType a_MobType)
return g_MobTypeNames[i].m_lcName;
}
}
-
+
// Not found:
return "";
}
@@ -866,7 +918,7 @@ cMonster::eFamily cMonster::FamilyFromType(eMonsterType a_Type)
case mtWolf: return mfHostile;
case mtZombie: return mfHostile;
case mtZombiePigman: return mfHostile;
-
+
case mtInvalidType: break;
}
ASSERT(!"Unhandled mob type");
@@ -1041,7 +1093,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel)
a_Drops.push_back(GetEquippedHelmet());
}
}
-
+
if (r1.randInt() % 200 < ((m_DropChanceChestplate * 200) + (a_LootingLevel * 2)))
{
if (!GetEquippedChestplate().IsEmpty())
@@ -1049,7 +1101,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel)
a_Drops.push_back(GetEquippedChestplate());
}
}
-
+
if (r1.randInt() % 200 < ((m_DropChanceLeggings * 200) + (a_LootingLevel * 2)))
{
if (!GetEquippedLeggings().IsEmpty())
@@ -1057,7 +1109,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel)
a_Drops.push_back(GetEquippedLeggings());
}
}
-
+
if (r1.randInt() % 200 < ((m_DropChanceBoots * 200) + (a_LootingLevel * 2)))
{
if (!GetEquippedBoots().IsEmpty())
@@ -1087,50 +1139,62 @@ void cMonster::AddRandomWeaponDropItem(cItems & a_Drops, short a_LootingLevel)
-void cMonster::HandleDaylightBurning(cChunk & a_Chunk)
+void cMonster::HandleDaylightBurning(cChunk & a_Chunk, bool WouldBurn)
{
if (!m_BurnsInDaylight)
{
return;
}
-
+
int RelY = POSY_TOINT;
if ((RelY < 0) || (RelY >= cChunkDef::Height))
{
// Outside the world
return;
}
-
- int RelX = POSX_TOINT - GetChunkX() * cChunkDef::Width;
- int RelZ = POSZ_TOINT - GetChunkZ() * cChunkDef::Width;
-
if (!a_Chunk.IsLightValid())
{
m_World->QueueLightChunk(GetChunkX(), GetChunkZ());
return;
}
+ if (!IsOnFire() && WouldBurn)
+ {
+ // Burn for 100 ticks, then decide again
+ StartBurning(100);
+ }
+}
+
+
+
+
+bool cMonster::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk)
+{
+ cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(m_NextWayPointPosition.x), FloorC(m_NextWayPointPosition.z));
+ if ((Chunk == nullptr) || (!Chunk->IsValid()))
+ {
+ return false;
+ }
+ int RelX = FloorC(a_Location.x) - a_Chunk.GetPosX() * cChunkDef::Width;
+ int RelY = FloorC(a_Location.y);
+ int RelZ = FloorC(a_Location.z) - a_Chunk.GetPosZ() * cChunkDef::Width;
if (
(a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight
(a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand
(GetWorld()->GetTimeOfDay() < (12000 + 1000)) && // It is nighttime
- !IsOnFire() && // Not already burning
GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining
)
{
- // Burn for 100 ticks, then decide again
- StartBurning(100);
+ return true;
}
+ return false;
}
+
cMonster::eFamily cMonster::GetMobFamily(void) const
{
return FamilyFromType(m_MobType);
}
-
-
-
-