TombEngine/TombEngine/Objects/TR4/Entity/tr4_ahmet.cpp
Stranger1992 e1128c41f8 Revert "Merge branch 'develop' into sezz_x64"
This reverts commit f695769189, reversing
changes made to 54c5e0c70d.
2023-05-19 16:56:45 +01:00

415 lines
12 KiB
C++

#include "framework.h"
#include "Objects/TR4/Entity/tr4_ahmet.h"
#include "Game/collision/sphere.h"
#include "Game/control/box.h"
#include "Game/control/control.h"
#include "Game/control/lot.h"
#include "Game/effects/effects.h"
#include "Game/effects/weather.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/misc.h"
#include "Game/people.h"
#include "Game/Setup.h"
#include "Math/Math.h"
#include "Sound/sound.h"
#include "Specific/level.h"
using namespace TEN::Effects::Environment;
using namespace TEN::Math;
namespace TEN::Entities::TR4
{
constexpr auto AHMET_SWIPE_ATTACK_DAMAGE = 80;
constexpr auto AHMET_BITE_ATTACK_DAMAGE = 120;
constexpr auto AHMET_ATTACK_RANGE = SQUARE(BLOCK(0.67f));
constexpr auto AHMET_AWARE_RANGE = SQUARE(BLOCK(1));
constexpr auto AHMET_IDLE_RANGE = SQUARE(BLOCK(1.25f));
constexpr auto AHMET_RUN_RANGE = SQUARE(BLOCK(2.5f));
constexpr auto AHMET_WALK_FORWARD_TURN_ANGLE = ANGLE(5.0f);
constexpr auto AHMET_RUN_FORWARD_TURN_ANGLE = ANGLE(8.0f);
constexpr auto AHMET_VIEW_ANGLE = ANGLE(45.0f);
constexpr auto AHMET_ENEMY_ANGLE = ANGLE(90.0f);
const auto AhmetBiteLeft = CreatureBiteInfo(Vector3i::Zero, 16);
const auto AhmetBiteRight = CreatureBiteInfo(Vector3i::Zero, 22);
const auto AhmetBiteJaw = CreatureBiteInfo(Vector3i::Zero, 11);
const auto AhmetSwipeAttackLeftJoints = std::vector<unsigned int>{ 14, 15, 16, 17 };
const auto AhmetSwipeAttackRightJoints = std::vector<unsigned int>{ 20, 21, 22, 23 };
enum AhmetState
{
// No state 0.
AHMET_STATE_IDLE = 1,
AHMET_STATE_WALK_FORWARD = 2,
AHMET_STATE_RUN_FORWARD = 3,
AHMET_STATE_SWIPE_ATTACK = 4,
AHMET_STATE_JUMP_BITE_ATTACK = 5,
AHMET_STATE_JUMP_SWIPE_ATTACK = 6,
AHMET_STATE_DEATH = 7
};
enum AhmetAnim
{
AHMET_ANIM_IDLE = 0,
AHMET_ANIM_RUN_FORWARD = 1,
AHMET_ANIM_SWIPE_ATTACK = 2,
AHMET_ANIM_JUMP_BITE_ATTACK_START = 3,
AHMET_ANIM_JUMP_BITE_ATTACK_CONTINUE = 4,
AHMET_ANIM_JUMP_BITE_ATTACK_END = 5,
AHMET_ANIM_WALK_FORWARD = 6,
AHMET_ANIM_JUMP_SWIPE_ATTACK_START = 7,
AHMET_ANIM_JUMP_SWIPE_ATTACK_CONTINUE = 8,
AHMET_ANIM_JUMP_SWIPE_ATTACK_END = 9,
AHMET_ANIM_DEATH = 10,
AHMET_ANIM_IDLE_TO_WALK_FORWARD = 11,
AHMET_ANIM_WALK_FORWARD_TO_IDLE = 12,
AHMET_ANIM_IDLE_TO_RUN_FORWARD = 13,
AHMET_ANIM_RUN_FORWARD_TO_IDLE = 14,
};
// TODO
enum AhmetFlags
{
};
static void TriggerAhmetDeathEffect(ItemInfo* item)
{
// HACK: Using CreatureSpheres here in release mode results in total mess-up
// of LaraSpheres, which in-game appears as a ghostly Lara fire silhouette.
// Later, both CreatureSpheres and LaraSpheres globals should be eradicated.
static SPHERE spheres[MAX_SPHERES] = {};
if (!(Wibble & 7))
{
int meshCount = GetSpheres(item, spheres, SPHERES_SPACE_WORLD, Matrix::Identity);
auto sphere = &spheres[(Wibble / 8) & 1];
for (int i = meshCount; i > 0; i--, sphere += 2)
TriggerFireFlame(sphere->x, sphere->y, sphere->z, FlameType::Medium);
}
TriggerDynamicLight(
item->Pose.Position.x,
item->Pose.Position.y - CLICK(1),
item->Pose.Position.z,
13, (GetRandomControl() & 0x3F) - 64, (GetRandomControl() & 0x1F) + 96, 0
);
SoundEffect(SFX_TR4_LOOP_FOR_SMALL_FIRES, &item->Pose);
}
void InitializeAhmet(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
InitializeCreature(itemNumber);
SetAnimation(item, AHMET_ANIM_IDLE);
item->ItemFlags[0] = item->Pose.Position.x / SECTOR(1);
item->ItemFlags[1] = (item->Pose.Position.y * 4) / SECTOR(1);
item->ItemFlags[2] = item->Pose.Position.z / SECTOR(1);
}
void AhmetControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
if (item->TriggerFlags == 1)
{
item->TriggerFlags = 0;
return;
}
auto* creature = GetCreatureInfo(item);
short angle = 0;
auto extraHeadRot = EulerAngles::Zero;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState == AHMET_STATE_DEATH)
{
// Don't clear.
if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameEnd)
{
item->Animation.FrameNumber = (g_Level.Anims[item->Animation.AnimNumber].frameEnd - 1);
item->Collidable = false;
}
}
else
{
SetAnimation(item, AHMET_ANIM_DEATH);
Lara.Context.InteractedItem = itemNumber; // TODO: Check if it's really required! -- TokyoSU 3/8/2022
}
TriggerAhmetDeathEffect(item);
}
else
{
if (item->AIBits != 0) // Does this entity have AI object? NOTE: Previous one checked "& ALL_AIOBJ" -- TokyoSU 3/8/2022
GetAITarget(creature);
AI_INFO AI, laraAI;
CreatureAIInfo(item, &AI);
if (creature->Enemy->IsLara())
{
laraAI.angle = AI.angle;
laraAI.distance = AI.distance;
}
else
{
int dx = LaraItem->Pose.Position.x - item->Pose.Position.x; // TODO: Make ahmet to not use LaraItem global -- TokyoSU 3/8/2022
int dz = LaraItem->Pose.Position.z - item->Pose.Position.z;
laraAI.angle = short(phd_atan(dx, dz)) - item->Pose.Orientation.y;
laraAI.distance = SQUARE(dx) + SQUARE(dz);
}
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, creature->MaxTurn);
//creature->Enemy = LaraItem; // No need since CreatureAIInfo() set it. -- TokyoSU
if (laraAI.distance < AHMET_AWARE_RANGE ||
item->HitStatus || TargetVisible(item, &laraAI))
{
AlertAllGuards(itemNumber);
}
if (AI.ahead)
extraHeadRot.y = AI.angle;
switch (item->Animation.ActiveState)
{
case AHMET_STATE_IDLE:
creature->MaxTurn = 0;
creature->Flags = 0;
if (item->AIBits & GUARD)
{
item->Animation.TargetState = AHMET_STATE_IDLE;
extraHeadRot.y = AIGuard(creature);
}
else if (item->AIBits & PATROL1)
{
item->Animation.TargetState = AHMET_STATE_WALK_FORWARD;
extraHeadRot.y = 0;
}
else if (creature->Mood == MoodType::Bored || creature->Mood == MoodType::Escape)
{
if (Lara.TargetEntity == item || !AI.ahead) // TODO: Make ahmet not use LaraInfo global. -- TokyoSU 3/8/2022
item->Animation.TargetState = AHMET_STATE_RUN_FORWARD;
else
item->Animation.TargetState = AHMET_STATE_IDLE;
}
else if (AI.bite && AI.distance < AHMET_ATTACK_RANGE)
item->Animation.TargetState = AHMET_STATE_SWIPE_ATTACK;
else if ((AI.angle >= AHMET_VIEW_ANGLE || AI.angle <= -AHMET_VIEW_ANGLE) ||
AI.distance >= AHMET_IDLE_RANGE)
{
if (item->Animation.RequiredState != NO_STATE)
item->Animation.TargetState = item->Animation.RequiredState;
else
{
if (!AI.ahead || AI.distance >= AHMET_RUN_RANGE)
item->Animation.TargetState = AHMET_STATE_RUN_FORWARD;
else
item->Animation.TargetState = AHMET_STATE_WALK_FORWARD;
}
}
else if (Random::TestProbability(1 / 2.0f))
item->Animation.TargetState = AHMET_STATE_JUMP_BITE_ATTACK;
else
item->Animation.TargetState = AHMET_STATE_JUMP_SWIPE_ATTACK;
break;
case AHMET_STATE_WALK_FORWARD:
creature->MaxTurn = AHMET_WALK_FORWARD_TURN_ANGLE;
if (item->AIBits & PATROL1)
{
item->Animation.TargetState = AHMET_STATE_WALK_FORWARD;
extraHeadRot.y = 0;
}
else if (AI.bite && AI.distance < AHMET_IDLE_RANGE)
item->Animation.TargetState = AHMET_STATE_IDLE;
else if (creature->Mood == MoodType::Escape ||
AI.distance > AHMET_RUN_RANGE || !AI.ahead ||
(AI.enemyFacing > -AHMET_ENEMY_ANGLE || AI.enemyFacing < AHMET_ENEMY_ANGLE))
{
item->Animation.TargetState = AHMET_STATE_RUN_FORWARD;
}
break;
case AHMET_STATE_RUN_FORWARD:
creature->MaxTurn = AHMET_RUN_FORWARD_TURN_ANGLE;
creature->Flags = 0;
if (item->AIBits & GUARD ||
((creature->Mood == MoodType::Bored || creature->Mood == MoodType::Escape) &&
(Lara.TargetEntity == item && AI.ahead)) ||
(AI.bite && AI.distance < AHMET_IDLE_RANGE))
{
item->Animation.TargetState = AHMET_STATE_IDLE;
}
else if (AI.ahead && AI.distance < AHMET_RUN_RANGE &&
(AI.enemyFacing < -AHMET_ENEMY_ANGLE || AI.enemyFacing > AHMET_ENEMY_ANGLE))
{
item->Animation.TargetState = AHMET_STATE_WALK_FORWARD;
}
break;
case AHMET_STATE_SWIPE_ATTACK:
creature->MaxTurn = 0;
if (abs(AI.angle) >= ANGLE(5.0f))
{
if (AI.angle >= 0)
item->Pose.Orientation.y += ANGLE(5.0f);
else
item->Pose.Orientation.y -= ANGLE(5.0f);
}
else
item->Pose.Orientation.y += AI.angle;
if (!(creature->Flags & 1) &&
item->Animation.FrameNumber > (g_Level.Anims[item->Animation.AnimNumber].frameBase + 7) &&
item->TouchBits.Test(AhmetSwipeAttackLeftJoints))
{
DoDamage(creature->Enemy, AHMET_SWIPE_ATTACK_DAMAGE);
CreatureEffect2(item, AhmetBiteLeft, 10, -1, DoBloodSplat);
creature->Flags |= 1;
}
else if (!(creature->Flags & 2) &&
item->Animation.FrameNumber > (g_Level.Anims[item->Animation.AnimNumber].frameBase + 32) &&
item->TouchBits.Test(AhmetSwipeAttackRightJoints))
{
DoDamage(creature->Enemy, AHMET_SWIPE_ATTACK_DAMAGE);
CreatureEffect2(item, AhmetBiteRight, 10, -1, DoBloodSplat);
creature->Flags |= 2;
}
break;
case AHMET_STATE_JUMP_BITE_ATTACK:
creature->MaxTurn = 0;
if (item->Animation.AnimNumber == (Objects[item->ObjectNumber].animIndex + AHMET_ANIM_JUMP_SWIPE_ATTACK_START))
{
if (abs(AI.angle) >= ANGLE(5.0f))
{
if (AI.angle >= 0)
item->Pose.Orientation.y += ANGLE(5.0f);
else
item->Pose.Orientation.y -= ANGLE(5.0f);
}
else
item->Pose.Orientation.y += AI.angle;
}
else
{
if (!(creature->Flags & 1) &&
item->Animation.AnimNumber == (Objects[item->ObjectNumber].animIndex + AHMET_ANIM_JUMP_BITE_ATTACK_CONTINUE))
{
if (item->Animation.FrameNumber > (g_Level.Anims[item->Animation.AnimNumber].frameBase + 11) &&
item->TouchBits.Test(AhmetSwipeAttackLeftJoints))
{
DoDamage(creature->Enemy, AHMET_BITE_ATTACK_DAMAGE);
CreatureEffect2(item, AhmetBiteJaw, 10, -1, DoBloodSplat);
creature->Flags |= 1;
}
}
}
break;
case AHMET_STATE_JUMP_SWIPE_ATTACK:
creature->MaxTurn = 0;
if (item->Animation.AnimNumber == (Objects[item->ObjectNumber].animIndex + AHMET_ANIM_JUMP_SWIPE_ATTACK_START))
{
if (abs(AI.angle) >= ANGLE(5.0f))
{
if (AI.angle >= 0)
item->Pose.Orientation.y += ANGLE(5.0f);
else
item->Pose.Orientation.y -= ANGLE(5.0f);
}
else
item->Pose.Orientation.y += AI.angle;
}
else
{
if (!(creature->Flags & 1) &&
item->Animation.FrameNumber > (g_Level.Anims[item->Animation.AnimNumber].frameBase + 14) &&
item->TouchBits.Test(AhmetSwipeAttackLeftJoints))
{
DoDamage(creature->Enemy, AHMET_SWIPE_ATTACK_DAMAGE);
CreatureEffect2(item, AhmetBiteLeft, 10, -1, DoBloodSplat);
creature->Flags |= 1;
}
else if (!(creature->Flags & 2) &&
item->Animation.FrameNumber > (g_Level.Anims[item->Animation.AnimNumber].frameBase + 22) &&
item->TouchBits.Test(AhmetSwipeAttackRightJoints))
{
DoDamage(creature->Enemy, AHMET_SWIPE_ATTACK_DAMAGE);
CreatureEffect2(item, AhmetBiteRight, 10, -1, DoBloodSplat);
creature->Flags |= 2;
}
}
break;
}
}
TestTriggers(item, true);
CreatureTilt(item, 0);
CreatureJoint(item, 0, extraHeadRot.y);
CreatureAnimation(itemNumber, angle, 0);
}
bool RespawnAhmet(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
if (item->Animation.ActiveState != AHMET_STATE_DEATH ||
item->Animation.FrameNumber != g_Level.Anims[item->Animation.AnimNumber].frameEnd)
{
return false;
}
Weather.Flash(255, 64, 0, 0.03f);
item->Pose.Position.x = (item->ItemFlags[0] * SECTOR(1)) + CLICK(2);
item->Pose.Position.y = (item->ItemFlags[1] * CLICK(1));
item->Pose.Position.z = (item->ItemFlags[2] * SECTOR(1)) + CLICK(2);
auto outsideRoom = IsRoomOutside(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z);
if (item->RoomNumber != outsideRoom)
ItemNewRoom(itemNumber, outsideRoom);
SetAnimation(item, AHMET_ANIM_IDLE);
item->HitPoints = Objects[item->ObjectNumber].HitPoints;
AddActiveItem(itemNumber);
item->Status = ITEM_ACTIVE;
item->Collidable = true;
item->AfterDeath = 0;
item->Flags &= ~IFLAG_INVISIBLE;
EnableEntityAI(itemNumber, true);
item->TriggerFlags = 1;
return true;
}
}