TombEngine/TombEngine/Objects/TR4/Entity/tr4_harpy.cpp

492 lines
13 KiB
C++

#include "framework.h"
#include "Objects/TR4/Entity/tr4_harpy.h"
#include "Game/animation.h"
#include "Game/collision/collide_room.h"
#include "Game/control/box.h"
#include "Game/control/control.h"
#include "Game/control/lot.h"
#include "Game/effects/effects.h"
#include "Game/effects/spark.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_helpers.h"
#include "Game/misc.h"
#include "Game/people.h"
#include "Game/Setup.h"
#include "Math/Math.h"
#include "Renderer/Renderer11Enums.h"
#include "Specific/level.h"
using namespace TEN::Math;
using namespace TEN::Math::Random;
using namespace TEN::Effects::Spark;
using std::vector;
namespace TEN::Entities::TR4
{
constexpr auto HARPY_STINGER_ATTACK_DAMAGE = 100;
constexpr auto HARPY_SWOOP_ATTACK_DAMAGE = 10;
constexpr auto HARPY_STINGER_POISON_POTENCY = 8;
const auto HarpyBite1 = CreatureBiteInfo(Vector3i::Zero, 4);
const auto HarpyBite2 = CreatureBiteInfo(Vector3i::Zero, 2);
const auto HarpyBite3 = CreatureBiteInfo(Vector3i::Zero, 15);
const auto HarpyAttack1 = CreatureBiteInfo(Vector3i(0, 128, 0), 2);
const auto HarpyAttack2 = CreatureBiteInfo(Vector3i(0, 128, 0), 4);
const auto HarpySwoopAttackJoints = std::vector<unsigned int>{ 2, 4, 15 };
const auto HarpyStingerAttackJoints = std::vector<unsigned int>{ 2, 4 };
enum HarpyState
{
// No state 0.
HARPY_STATE_IDLE = 1,
HARPY_STATE_FLY_FORWARD = 2,
HARPY_STATE_FLY_DOWN = 3,
HARPY_STATE_FLY_FORWARD_DOWN = 4,
HARPY_STATE_SWOOP_ATTACK = 5,
HARPY_STATE_STINGER_ATTACK = 6,
HARPY_STATE_FLY_FORWARD_SPIN = 7,
HARPY_STATE_FLAME_ATTACK = 8,
HARPY_STATE_DEATH_START = 9,
HARPY_STATE_DEATH_FALL = 10,
HARPY_STATE_DEATH_END = 11,
HARPY_STATE_FLY_BACK = 12,
HARPY_STATE_GLIDE = 13
};
enum HarpyAnim
{
HARPY_ANIM_FLY_FORWARD = 0,
HARPY_ANIM_FLAME_ATTACK_START = 1,
HARPY_ANIM_FLAME_ATTACK_CONTINUE = 2,
HARPY_ANIM_FLAME_ATTACK_END = 3,
HARPY_ANIM_IDLE = 4,
HARPY_ANIM_DEATH_START = 5,
HARPY_ANIM_DEATH_FALL = 6,
HARPY_ANIM_DEATH_END = 7,
HARPY_ANIM_STINGER_ATTACK = 8,
HARPY_ANIM_FLY_BACK = 9,
HARPY_ANIM_FLY_FORWARD_DOWN_START = 10,
HARPY_ANIM_FLY_FORWARD_DOWN_CONTINUE = 11,
HARPY_ANIM_FLY_FORWARD_DOWN_END = 12,
HARPY_ANIM_SWOOP_ATTACK = 13,
HARPY_ANIM_FLY_DOWN_START = 14,
HARPY_ANIM_FLY_DOWN_CONTINUE = 15,
HARPY_ANIM_FLY_DOWN_END = 16,
HARPY_ANIM_FLY_FORWARD_SPIN = 17,
HARPY_ANIM_GLIDE = 18
};
void TriggerHarpyMissile(Pose* pose, short roomNumber, short mesh)
{
short fxNumber = CreateNewEffect(roomNumber);
if (fxNumber == -1)
return;
auto* fx = &EffectList[fxNumber];
fx->pos.Position.x = pose->Position.x;
fx->pos.Position.y = pose->Position.y - (GetRandomControl() & 0x3F) - 32;
fx->pos.Position.z = pose->Position.z;
fx->pos.Orientation.x = pose->Orientation.x;
fx->pos.Orientation.y = pose->Orientation.y;
fx->pos.Orientation.z = 0;
fx->roomNumber = roomNumber;
fx->counter = short(2 * GetRandomControl() + 0x8000);
fx->objectNumber = ID_ENERGY_BUBBLES;
fx->speed = (GetRandomControl() & 0x1F) + 96;
fx->flag1 = mesh;
fx->frameNumber = Objects[fx->objectNumber].meshIndex + mesh * 2;
}
void DoHarpyEffects(ItemInfo* item, CreatureInfo* creature, short itemNumber)
{
item->ItemFlags[0]++;
auto rh = GetJointPosition(item, HarpyAttack1);
auto lr = GetJointPosition(item, HarpyAttack2);
int sG = (GetRandomControl() & 0x7F) + 32;
int sR = sG;
int sB = 0;
auto sparkColor = Vector3(sR, sG, sB);
int fG = (GetRandomControl() & 0x7F) + 64;
int fR = fG;
int fB = 0;
auto flameColor = Vector3(fR, fG, fB);
if (item->ItemFlags[0] >= 24 &&
item->ItemFlags[0] <= 47 &&
(GetRandomControl() & 0x1F) < item->ItemFlags[0])
{
for (int i = 0; i < 2; i++)
{
TriggerAttackSpark(lr.ToVector3(), sparkColor);
TriggerAttackSpark(rh.ToVector3(), sparkColor);
}
}
int size = item->ItemFlags[0] * 2;
if (size > 64)
size = 64;
if (size < 80)
{
if ((Wibble & 0xF) == 8)
{
TriggerAttackFlame(lr, flameColor, size);
TriggerAttackFlame(rh, flameColor, size);
}
else if (!(Wibble & 0xF))
{
TriggerAttackFlame(lr, flameColor, size);
TriggerAttackFlame(rh, flameColor, size);
}
}
if (item->ItemFlags[0] >= 61)
{
if (item->ItemFlags[0] <= 65 && GlobalCounter & 1)
{
auto pos3 = GetJointPosition(item, HarpyAttack1.BoneID, Vector3i(HarpyAttack1.Position.x, HarpyAttack1.Position.y * 2, HarpyAttack1.Position.z));
auto orient = Geometry::GetOrientToPoint(lr.ToVector3(), rh.ToVector3());
auto pose = Pose(rh, orient);
TriggerHarpyMissile(&pose, item->RoomNumber, 2);
}
if (item->ItemFlags[0] >= 61 && item->ItemFlags[0] <= 65 && !(GlobalCounter & 1))
{
auto pos3 = GetJointPosition(item, HarpyAttack2.BoneID, Vector3i(HarpyAttack2.Position.x, HarpyAttack2.Position.y * 2, HarpyAttack2.Position.z));
auto orient = Geometry::GetOrientToPoint(lr.ToVector3(), rh.ToVector3());
auto pose = Pose(rh, orient);
TriggerHarpyMissile(&pose, item->RoomNumber, 2);
}
}
}
void InitializeHarpy(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
InitializeCreature(itemNumber);
SetAnimation(item, HARPY_ANIM_IDLE);
}
void HarpyControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short joint0 = 0;
short joint1 = 0;
short joint2 = 0;
if (item->HitPoints <= 0)
{
item->HitPoints = 0;
int state = item->Animation.ActiveState - 9;
if (state)
{
state--;
if (state)
{
if (state == HARPY_STATE_IDLE)
{
item->Pose.Position.y = item->Floor;
item->Pose.Orientation.x = 0;
}
else
{
SetAnimation(item, HARPY_ANIM_DEATH_START);
item->Animation.IsAirborne = true;
item->Animation.Velocity.z = 0;
item->Pose.Orientation.x = 0;
}
CreatureTilt(item, 0);
CreatureJoint(item, 0, joint0);
CreatureJoint(item, 1, joint1);
CreatureJoint(item, 2, joint2);
CreatureAnimation(itemNumber, angle, 0);
return;
}
}
else
item->Animation.TargetState = HARPY_STATE_DEATH_FALL;
if (item->Pose.Position.y >= item->Floor)
{
item->Animation.TargetState = HARPY_STATE_DEATH_END;
item->Animation.IsAirborne = false;
item->Animation.Velocity.y = 0.0f;
item->Pose.Position.y = item->Floor;
AlignEntityToSurface(item, Vector2(Objects[item->ObjectNumber].radius));
}
item->Pose.Orientation.x = 0;
}
else
{
if (item->AIBits)
GetAITarget(creature);
int minDistance = INT_MAX;
creature->Enemy = nullptr;
for (auto& currentCreature : ActiveCreatures)
{
if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber)
continue;
auto* target = &g_Level.Items[currentCreature->ItemNumber];
if (target->ObjectNumber == ID_LARA_DOUBLE)
{
int dx = target->Pose.Position.x - item->Pose.Position.x;
int dz = target->Pose.Position.z - item->Pose.Position.z;
int distance = SQUARE(dx) + SQUARE(dz);
if (distance < minDistance)
{
creature->Enemy = target;
minDistance = distance;
}
}
}
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (!creature->Enemy->IsLara())
phd_atan(LaraItem->Pose.Position.z - item->Pose.Position.z, LaraItem->Pose.Position.x - item->Pose.Position.x);
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, creature->MaxTurn);
if (AI.ahead)
{
joint0 = AI.angle / 2;
joint2 = AI.angle / 2;
joint1 = AI.xAngle;
}
int height = 0;
int dy = 0;
switch (item->Animation.ActiveState)
{
case HARPY_STATE_IDLE:
creature->MaxTurn = ANGLE(7.0f);
creature->Flags = 0;
if (creature->Enemy)
{
height = (item->Pose.Position.y + BLOCK(2));
if (creature->Enemy->Pose.Position.y > height && item->Floor > height)
{
item->Animation.TargetState = HARPY_STATE_FLY_DOWN;
break;
}
}
if (AI.ahead)
{
dy = abs(creature->Enemy->Pose.Position.y - item->Pose.Position.y);
if (dy <= BLOCK(1))
{
if (AI.distance < SQUARE(341))
{
item->Animation.TargetState = HARPY_STATE_STINGER_ATTACK;
break;
}
if (dy <= BLOCK(1) && AI.distance < SQUARE(BLOCK(2)))
{
item->Animation.TargetState = HARPY_STATE_FLY_FORWARD_DOWN;
break;
}
}
}
if (creature->Enemy != LaraItem ||
!Targetable(item, &AI) ||
AI.distance <= SQUARE(BLOCK(3.5f)) ||
Random::TestProbability(1 / 2.0f))
{
item->Animation.TargetState = HARPY_STATE_FLY_FORWARD;
break;
}
item->Animation.TargetState = HARPY_STATE_FLAME_ATTACK;
item->ItemFlags[0] = 0;
break;
case HARPY_STATE_FLY_FORWARD:
creature->MaxTurn = ANGLE(7.0f);
creature->Flags = 0;
if (item->Animation.RequiredState != NO_STATE)
{
item->Animation.TargetState = item->Animation.RequiredState;
if (item->Animation.RequiredState == HARPY_STATE_FLAME_ATTACK)
item->ItemFlags[0] = 0;
break;
}
if (item->HitStatus)
{
item->Animation.TargetState = HARPY_STATE_FLY_FORWARD_SPIN;
break;
}
if (AI.ahead)
{
if (AI.distance >= SQUARE(341))
{
if (AI.ahead && Random::TestProbability(1 / 2.0f) &&
AI.distance >= SQUARE(BLOCK(2)) &&
AI.distance > SQUARE(BLOCK(3.5f)))
{
item->Animation.TargetState = HARPY_STATE_FLAME_ATTACK;
item->ItemFlags[0] = 0;
}
else
item->Animation.TargetState = HARPY_STATE_FLY_FORWARD_DOWN;
}
else
item->Animation.TargetState = HARPY_STATE_STINGER_ATTACK;
break;
}
if (Random::TestProbability(1 / 2.0f))
{
item->Animation.TargetState = HARPY_STATE_FLY_FORWARD_SPIN;
break;
}
if (!AI.ahead)
{
item->Animation.TargetState = HARPY_STATE_FLY_FORWARD_DOWN;
break;
}
if (AI.distance >= SQUARE(341))
{
if (AI.ahead && AI.distance >= SQUARE(BLOCK(2)) &&
AI.distance > SQUARE(BLOCK(3.5f)) &&
Random::TestProbability(1 / 2.0f))
{
item->Animation.TargetState = HARPY_STATE_FLAME_ATTACK;
item->ItemFlags[0] = 0;
}
else
item->Animation.TargetState = HARPY_STATE_FLY_FORWARD_DOWN;
}
else
item->Animation.TargetState = HARPY_STATE_STINGER_ATTACK;
break;
case HARPY_STATE_FLY_DOWN:
if (!creature->Enemy ||
creature->Enemy->Pose.Position.y < (item->Pose.Position.y + BLOCK(2)))
{
item->Animation.TargetState = HARPY_STATE_IDLE;
}
break;
case HARPY_STATE_FLY_FORWARD_DOWN:
creature->MaxTurn = ANGLE(2.0f);
if (AI.ahead && AI.distance < SQUARE(BLOCK(2)))
item->Animation.TargetState = HARPY_STATE_SWOOP_ATTACK;
else
item->Animation.TargetState = HARPY_STATE_GLIDE;
break;
case HARPY_STATE_SWOOP_ATTACK:
item->Animation.TargetState = HARPY_STATE_FLY_FORWARD;
creature->MaxTurn = ANGLE(2.0f);
if (item->TouchBits.Test(HarpySwoopAttackJoints) ||
creature->Enemy != nullptr && !creature->Enemy->IsLara() &&
abs(creature->Enemy->Pose.Position.y - item->Pose.Position.y) <= BLOCK(1) &&
AI.distance < SQUARE(BLOCK(2)))
{
DoDamage(creature->Enemy, HARPY_SWOOP_ATTACK_DAMAGE);
if (item->TouchBits & 0x10)
CreatureEffect2(item, HarpyBite1, 5, -1, DoBloodSplat);
else
CreatureEffect2(item, HarpyBite2, 5, -1, DoBloodSplat);
}
break;
case HARPY_STATE_STINGER_ATTACK:
creature->MaxTurn = ANGLE(2.0f);
if (creature->Flags == 0 &&
(item->TouchBits.Test(HarpyStingerAttackJoints) ||
creature->Enemy != nullptr &&
abs(creature->Enemy->Pose.Position.y - item->Pose.Position.y) <= BLOCK(1) &&
AI.distance < SQUARE(BLOCK(2)) &&
item->Animation.AnimNumber == GetAnimIndex(*item, HARPY_ANIM_STINGER_ATTACK) &&
item->Animation.FrameNumber > GetFrameIndex(item, 17))
)
{
if (creature->Enemy->IsLara())
GetLaraInfo(creature->Enemy)->Status.Poison += HARPY_STINGER_POISON_POTENCY;
DoDamage(creature->Enemy, HARPY_STINGER_ATTACK_DAMAGE);
CreatureEffect2(item, HarpyBite3, 10, -1, DoBloodSplat);
creature->Flags = 1;
}
break;
case HARPY_STATE_FLAME_ATTACK:
DoHarpyEffects(item, creature, itemNumber);
break;
case HARPY_STATE_FLY_BACK:
if (AI.ahead && AI.distance > SQUARE(BLOCK(3.5f)))
{
item->Animation.TargetState = HARPY_STATE_FLY_FORWARD;
item->Animation.RequiredState = HARPY_STATE_FLAME_ATTACK;
}
else if (Random::TestProbability(1 / 2.0f))
item->Animation.TargetState = HARPY_STATE_IDLE;
break;
case HARPY_STATE_GLIDE:
item->Animation.TargetState = HARPY_STATE_FLY_FORWARD;
break;
default:
break;
}
}
CreatureTilt(item, 0);
CreatureJoint(item, 0, joint0);
CreatureJoint(item, 1, joint1);
CreatureJoint(item, 2, joint2);
CreatureAnimation(itemNumber, angle, 0);
}
}