diff options
Diffstat (limited to 'src/Mobs/Monster.cpp')
-rw-r--r-- | src/Mobs/Monster.cpp | 600 |
1 files changed, 290 insertions, 310 deletions
diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 0d433d861..fc1c1678d 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" + @@ -82,8 +85,10 @@ static const struct cMonster::cMonster(const AString & a_ConfigName, 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_BehaviorBreederPointer(nullptr) + , m_BehaviorAttackerPointer(nullptr) , m_EMPersonality(AGGRESSIVE) + , 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,7 +114,11 @@ 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) { if (!a_ConfigName.empty()) { @@ -129,7 +132,7 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A cMonster::~cMonster() { - ASSERT(GetTarget() == nullptr); + } @@ -138,6 +141,9 @@ cMonster::~cMonster() void cMonster::Destroy(bool a_ShouldBroadcast) { + //mobtodo Destroy vs Destroyed + + // mobTodo behavior for leash if (IsLeashed()) { cEntity * LeashedTo = GetLeashedTo(); @@ -159,7 +165,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 +193,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 +293,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 +303,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 +314,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) + { + // A behavior is pinned. We give it control automatically. + ASSERT(m_CurrentTickControllingBehavior == m_PinnedBehavior); + m_CurrentTickControllingBehavior->Tick(a_Dt, a_Chunk); + } + else { - if (!static_cast<cPlayer *>(GetTarget())->CanMobsTarget()) + // 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) { - SetTarget(nullptr); - m_EMState = IDLE; + 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 + { + // The behavior asking for control is not the same as the behavior from last tick. + // Begin the control swapping process. + m_TickControllingBehaviorState = OldControlEnding; } } + } - // Process the undead burning in daylight. - HandleDaylightBurning(*Chunk, WouldBurnAt(GetPosition(), *Chunk)); + // 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; + } + } + + // 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) @@ -337,8 +431,9 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } else { + // mobToDo fix dont care // 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, true)) { case ePathFinderStatus::PATH_FOUND: { @@ -347,9 +442,13 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) 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) + //mobTodo emstate + /* (GetBehaviorDayLightBurner() != nullptr) && (m_TicksSinceLastDamaged >= 100) && + GetBehaviorDayLightBurner()->WouldBurnAt(m_NextWayPointPosition, *Chunk) && + !(GetBehaviorDayLightBurner()->WouldBurnAt(GetPosition(), *Chunk)) */ + 1 == 0 + + // This logic should probably be in chaser ) { // 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: @@ -377,30 +476,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(); } @@ -458,9 +537,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 +552,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 +635,18 @@ 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); } + return true; } @@ -661,6 +737,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 +748,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 +779,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 +994,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 +1020,91 @@ void cMonster::SetTarget (cPawn * a_NewTarget) // Notify the new target that we are now targeting it. m_Target->TargetingMe(this); m_WasLastTargetAPlayer = m_Target->IsPlayer(); - } + }*/ } +bool cMonster::IsPathFinderActivated() const +{ + return m_PathfinderActivated; +} + -void cMonster::UnsafeUnsetTarget() + + + +cBehaviorBreeder * cMonster::GetBehaviorBreeder() { - m_Target = nullptr; + return m_BehaviorBreederPointer; } -cPawn * cMonster::GetTarget() +const cBehaviorBreeder * cMonster::GetBehaviorBreeder() const { - return m_Target; + return static_cast<const cBehaviorBreeder *>(m_BehaviorBreederPointer); +} + + + + + +cBehaviorAttacker * cMonster::GetBehaviorAttacker() +{ + return m_BehaviorAttackerPointer; +} + + + + + +void cMonster::InheritFromParents(cMonster * a_Parent1, cMonster * a_Parent2) +{ + UNUSED(a_Parent1); + UNUSED(a_Parent2); + return; +} + + + + + +void cMonster::GetFollowedItems(cItems & a_Items) +{ + return; +} + + + + + +void cMonster::GetBreedingItems(cItems & a_Items) +{ + return GetFollowedItems(a_Items); +} + + + + + +cPlayer * cMonster::GetNearestPlayer() +{ + if (m_NearestPlayerIsStale) + { + // TODO: Rewrite this to use cWorld's DoWithPlayers() + m_NearestPlayer = GetWorld()->FindClosestPlayer(GetPosition(), static_cast<float>(GetSightDistance())); + m_NearestPlayerIsStale = false; + } + if ((m_NearestPlayer != nullptr) && (!m_NearestPlayer->IsTicking())) + { + m_NearestPlayer = nullptr; + } + return m_NearestPlayer; } @@ -1300,98 +1298,80 @@ 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 < 0) - { - // Never burn under the world - return false; - } - 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; } |