TombEngine/TR5Main/Objects/TR5/Entity/tr5_laser_head.cpp
2020-10-17 23:36:06 -05:00

682 lines
No EOL
18 KiB
C++

#include "framework.h"
#include "tr5_laser_head.h"
#include "sphere.h"
#include "items.h"
#include "tomb4fx.h"
#include "effect2.h"
#include "box.h"
#include "people.h"
#include "debris.h"
#include "draw.h"
#include "control.h"
#include "effect.h"
#include "switch.h"
#include "traps.h"
#include "setup.h"
#include "level.h"
#include "lara.h"
#include "sound.h"
struct LASER_HEAD_INFO
{
short baseItem;
short tentacles[8];
short puzzleItem;
};
struct LASER_HEAD_STRUCT
{
PHD_VECTOR target;
ENERGY_ARC* fireArcs[2];
ENERGY_ARC* chargeArcs[8];
bool LOS;
byte byte1;
byte byte2;
short xRot;
short yRot;
};
LASER_HEAD_STRUCT LaserHeadData;
PHD_VECTOR LaserHeadBasePosition = { 0, -640, 0 };
PHD_VECTOR GuardianChargePositions[8];
int GuardianMeshes[5] = { 1,0,0,0,2 };
static void TriggerLaserHeadSparks(PHD_VECTOR* pos, int count, byte r, byte g, byte b, int unk)
{
if (count > 0)
{
for (int i = 0; i < count; i++)
{
SPARKS* spark = &Sparks[GetFreeSpark()];
spark->on = 1;
spark->sR = r;
spark->sG = g;
spark->sB = b;
spark->dB = 0;
spark->dG = 0;
spark->dR = 0;
spark->colFadeSpeed = 9 << unk;
spark->fadeToBlack = 0;
spark->life = 9 << unk;
spark->sLife = 9 << unk;
spark->transType = COLADD;
spark->x = pos->x;
spark->y = pos->y;
spark->z = pos->z;
spark->gravity = (GetRandomControl() / 128) & 0x1F;
spark->yVel = ((GetRandomControl() & 0xFFF) - 2048) << unk;
spark->xVel = ((GetRandomControl() & 0xFFF) - 2048) << unk;
spark->zVel = ((GetRandomControl() & 0xFFF) - 2048) << unk;
spark->flags = SP_NONE;
spark->maxYvel = 0;
spark->friction = 34 << unk;
}
}
}
static void LaserHeadCharge(ITEM_INFO* item)
{
byte size = item->itemFlags[3];
byte g = ((GetRandomControl() & 0x1F) + 128);
byte b = ((GetRandomControl() & 0x1F) + 64);
if (item->itemFlags[3] <= 32)
{
g = (item->itemFlags[3] * g) / 32;
b = (item->itemFlags[3] * b) / 32;
}
else
{
size = 32;
}
LASER_HEAD_INFO* creature = (LASER_HEAD_INFO*)item->data;
PHD_VECTOR src, dest;
dest.x = LaserHeadBasePosition.x;
dest.y = LaserHeadBasePosition.y;
dest.z = LaserHeadBasePosition.z;
GetJointAbsPosition(&g_Level.Items[creature->baseItem], &dest, 0);
for (int i = 0; i < 4; i++)
{
ENERGY_ARC* arc = LaserHeadData.chargeArcs[i];
if (item->itemFlags[3] & 0x0F && arc != NULL)
{
arc->r = 0;
arc->g = g;
arc->b = b;
//arc->segmentSize = 50;
}
else
{
src.x = GuardianChargePositions[i].x;
src.y = GuardianChargePositions[i].y;
src.z = GuardianChargePositions[i].z;
GetJointAbsPosition(&g_Level.Items[creature->baseItem], &src, 0);
//LaserHeadData.chargeArcs[i] = TriggerEnergyArc(&src, &dest, 255, 255, 255, 256, 90, 64, ENERGY_ARC_STRAIGHT_LINE); // (GetRandomControl() & 7) + 8, v4 | ((v1 | 0x240000) << 8), 13, 48, 3);
}
}
if (GlobalCounter & 1)
{
for (int i = 0; i < 5; i += 4)
{
if (2 * GuardianMeshes[i] & item->meshBits)
{
src.x = 0;
src.y = 0;
src.z = 0;
GetJointAbsPosition(item, &src, GuardianMeshes[i]);
TriggerLightningGlow(src.x, src.y, src.z, size + (GetRandomControl() & 3), 0, g, b);
TriggerLaserHeadSparks(&src, 3, 0, g, b, 0);
}
}
TriggerLightningGlow(dest.x, dest.y, dest.z, (GetRandomControl() & 3) + size + 8, 0, g, b);
TriggerDynamicLight(dest.x, dest.y, dest.z, (GetRandomControl() & 3) + 16, 0, g, b);
}
if (!(GlobalCounter & 3))
{
TriggerEnergyArc(&dest, (PHD_VECTOR*)&item->pos, 0, g, b, 256, 3, 64, ENERGY_ARC_NO_RANDOMIZE, ENERGY_ARC_STRAIGHT_LINE);
//TriggerEnergyArc(&dest, &item->pos, (GetRandomControl() & 7) + 8, v4 | ((v1 | 0x180000) << 8), 13, 64, 3);
}
TriggerLaserHeadSparks(&dest, 3, 0, g, b, 1);
}
void InitialiseLaserHead(short itemNumber)
{
ITEM_INFO* item = &g_Level.Items[itemNumber];
item->data = game_malloc<LASER_HEAD_INFO>();
LASER_HEAD_INFO* info = (LASER_HEAD_INFO*)item->data;
for (int i = 0; i < g_Level.NumItems; i++)
{
if (g_Level.Items[i].objectNumber == ID_LASERHEAD_BASE)
{
info->baseItem = i;
break;
}
}
short rotation = 0;
int j = 0;
for (int i = 0; i < g_Level.NumItems; i++)
{
if (g_Level.Items[i].objectNumber == ID_LASERHEAD_TENTACLE && j < 8)
{
info->tentacles[j] = i;
rotation += ANGLE(45);
j++;
}
}
for (int i = 0; i < g_Level.NumItems; i++)
{
if (g_Level.Items[i].objectNumber == ID_PUZZLE_ITEM4)
{
info->puzzleItem = i;
break;
}
}
int y = item->pos.yPos - 640;
item->pos.yPos = y;
item->itemFlags[1] = y - 640;
item->currentAnimState = 0;
item->itemFlags[3] = 90;
ZeroMemory(&LaserHeadData, sizeof(LASER_HEAD_STRUCT));
}
void LaserHeadControl(short itemNumber)
{
ITEM_INFO* item = &g_Level.Items[itemNumber];
LASER_HEAD_INFO* creature = (LASER_HEAD_INFO*)item->data;
GAME_VECTOR src, dest;
// NOTICE: itemFlags[0] seems to be a state machine, if it's equal to 3 then death animations is triggered
// Other values still unknown
if (item->itemFlags[0])
{
// Maybe number of eye hits?
if (item->itemFlags[0] > 2)
{
if (!(GlobalCounter & 7))
{
if (item->currentAnimState < 8)
{
short tentacleNumber = creature->tentacles[item->currentAnimState];
g_Level.Items[tentacleNumber].goalAnimState = 2;
item->currentAnimState++;
}
}
// Destroy tentacle items
if (item->currentAnimState > 0)
{
for (int i = 0; i < 8; i++)
{
ITEM_INFO* tentacleItem = &g_Level.Items[creature->tentacles[i]];
if (tentacleItem->animNumber == Objects[tentacleItem->objectNumber].animIndex + 1
&& tentacleItem->frameNumber == g_Level.Anims[tentacleItem->animNumber].frameEnd
&& tentacleItem->meshBits & 1)
{
SoundEffect(SFX_SMASH_ROCK, &item->pos, 0);
ExplodeItemNode(tentacleItem, 0, 0, 128);
KillItem(creature->tentacles[i]);
}
}
}
item->pos.yPos = item->itemFlags[1] - (192 - item->speed) * phd_sin(item->itemFlags[2]);
item->itemFlags[2] += ONE_DEGREE * item->speed;
if (!(GlobalCounter & 7))
{
item->itemFlags[3] = item->pos.yRot + (GetRandomControl() & 0x3FFF) - 4096;
item->triggerFlags = (GetRandomControl() & 0x1000) - 2048;
}
InterpolateAngle(item->itemFlags[3], &item->pos.yRot, 0, 2);
InterpolateAngle(item->triggerFlags, &item->pos.xRot, 0, 2);
// Final death
item->speed++;
if (item->speed > 136)
{
ExplodeItemNode(&g_Level.Items[creature->baseItem], 0, 0, 128);
KillItem(creature->baseItem);
ExplodeItemNode(item, 0, 0, 128);
TriggerExplosionSparks(item->pos.xPos, item->pos.yPos - 256, item->pos.zPos, 3, -2, 2, item->roomNumber);
TriggerExplosionSparks(item->pos.xPos, item->pos.yPos, item->pos.zPos, 2, 0, 2, item->roomNumber);
TriggerShockwave(&item->pos, 32, 160, 64, 64, 128, 0, 36, 0, 0);
TriggerShockwave(&item->pos, 32, 160, 64, 64, 128, 0, 36, 0x3000, 0);
TriggerShockwave(&item->pos, 32, 160, 64, 64, 128, 0, 36, 0x6000, 0);
g_Level.Items[creature->puzzleItem].pos.yPos = item->pos.yPos;
TestTriggersAtXYZ(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber, 1, 0);
SoundEffect(SFX_GOD_HEAD_BLAST, &item->pos, 0x800004);
SoundEffect(SFX_EXPLOSION2, &item->pos, 20971524);
SoundEffect(SFX_EXPLOSION1, &item->pos, 0);
SoundEffect(SFX_EXPLOSION1, &item->pos, 4194308);
KillItem(itemNumber);
}
}
else
{
item->triggerFlags++;
item->pos.yPos = item->itemFlags[1] - 128 * phd_sin(item->itemFlags[2]);
item->itemFlags[2] += ANGLE(3);
// Get guardian head's position
src.x = 0;
src.y = 168;
src.z = 248;
src.roomNumber = item->roomNumber;
GetJointAbsPosition(item, (PHD_VECTOR*)&src, 0);
if (item->itemFlags[0] == 1)
{
// Get Lara's left hand position
// TODO: check if left hand or head
dest.x = 0;
dest.y = 0;
dest.z = 0;
GetJointAbsPosition(LaraItem, (PHD_VECTOR*)&dest, LM_LHAND);
// Calculate distance between guardian and Lara
int distance = sqrt(SQUARE(src.x - dest.x) + SQUARE(src.y - dest.y) + SQUARE(src.z - dest.z));
// Check if there's a valid LOS between guardian and Lara
// and if distance is less than 8 sectors and if Lara is alive and not burning
if (LOS(&src, &dest)
&& distance <= 8192
&& LaraItem->hitPoints > 0
&& !Lara.burn
&& (LaserHeadData.target.x || LaserHeadData.target.y || LaserHeadData.target.z))
{
// Lock target for attacking
dest.x = 0;
dest.y = 0;
dest.z = 0;
GetJointAbsPosition(LaraItem, (PHD_VECTOR*)&dest, LM_HIPS);
LaserHeadData.target.x = dest.x;
LaserHeadData.target.y = dest.y;
LaserHeadData.target.z = dest.z;
LaserHeadData.byte1 = 3;
LaserHeadData.byte2 = 1;
}
else
{
// Randomly turn head try to finding Lara
bool condition = !(GetRandomControl() & 0x7F) && item->triggerFlags > 150;
item->itemFlags[3]--;
if (item->itemFlags[3] <= 0 || condition)
{
short xRot = (GetRandomControl() / 4) - 4096;
short yRot;
if (condition)
yRot = item->pos.yRot + (GetRandomControl() & 0x3FFF) + ANGLE(135);
else
yRot = 2 * GetRandomControl();
int v = ((GetRandomControl() & 0x1FFF) + 8192);
int c = v * phd_cos(-xRot);
dest.x = src.x + c * phd_sin(yRot);
dest.y = src.y + v * phd_sin(-xRot);
dest.z = src.z + c * phd_cos(yRot);
if (condition)
{
LaserHeadData.byte1 = 2;
item->triggerFlags = 0;
}
else
{
LaserHeadData.byte1 = (GetRandomControl() & 2) + 3;
}
item->itemFlags[3] = LaserHeadData.byte1 * ((GetRandomControl() & 3) + 8);
LaserHeadData.target.x = dest.x;
LaserHeadData.target.y = dest.y;
LaserHeadData.target.z = dest.z;
}
else
{
dest.x = LaserHeadData.target.x;
dest.y = LaserHeadData.target.y;
dest.z = LaserHeadData.target.z;
}
LaserHeadData.byte2 = 0;
}
}
else
{
LaserHeadData.byte1 = 3;
if (JustLoaded)
{
int c = 8192 * phd_cos(item->pos.xRot + 3328);
dest.x = LaserHeadData.target.x = src.x + c * phd_sin(item->pos.yRot);
dest.y = LaserHeadData.target.y = src.y + 8192 * phd_sin(3328 - item->pos.xRot);
dest.z = LaserHeadData.target.z = src.z + c * phd_cos(item->pos.yRot);
}
else
{
dest.x = LaserHeadData.target.x;
dest.y = LaserHeadData.target.y;
dest.z = LaserHeadData.target.z;
}
}
short angles[2];
short outAngle;
phd_GetVectorAngles(LaserHeadData.target.x - src.x, LaserHeadData.target.y - src.y, LaserHeadData.target.z - src.z, angles);
InterpolateAngle(angles[0], &item->pos.yRot, &LaserHeadData.yRot, LaserHeadData.byte1);
InterpolateAngle(angles[1] + 3328, &item->pos.xRot, &LaserHeadData.xRot, LaserHeadData.byte1);
if (item->itemFlags[0] == 1)
{
if (LaserHeadData.byte2)
{
if (!(GetRandomControl() & 0x1F)
&& abs(LaserHeadData.xRot) < 1024
&& abs(LaserHeadData.yRot) < 1024
&& !LaraItem->fallspeed
|| !(GetRandomControl() & 0x1FF))
{
item->itemFlags[0]++;
item->itemFlags[3] = 0;
}
}
else if (!(GetRandomControl() & 0x3F) && item->triggerFlags > 300)
{
item->itemFlags[0]++;
item->triggerFlags = 0;
item->itemFlags[3] = 0;
}
}
else
{
bool condition = false;
if (item->itemFlags[3] <= 90)
{
SoundEffect(SFX_GOD_HEAD_CHARGE, &item->pos, 0);
LaserHeadCharge(item);
item->itemFlags[3]++;
condition = item->itemFlags[3] >= 90;
}
if (item->itemFlags[3] > 90 || condition)
{
byte r, g, b;
g = (GetRandomControl() & 0x1F) + 128;
b = (GetRandomControl() & 0x1F) + 64;
ENERGY_ARC* arc = LaserHeadData.fireArcs[0];
if (!LaserHeadData.fireArcs[0])
arc = LaserHeadData.fireArcs[1];
if (item->itemFlags[3] > 90
&& arc
&& !arc->life
|| LaraItem->hitPoints <= 0
|| Lara.burn)
{
if (arc)
{
LaserHeadData.fireArcs[0] = NULL;
LaserHeadData.fireArcs[1] = NULL;
}
item->itemFlags[0] = 1;
item->triggerFlags = 0;
}
else
{
if (item->itemFlags[3] > 90
&& arc
&& arc->life < 16)
{
g = b = (arc->life * g) / 16;
}
for (int i = 0, j = 0; i < 5; i += 4, j++)
{
// If eye was not destroyed then fire from it
if ((1 << GuardianMeshes[i]) & item->meshBits)
{
src.x = 0;
src.y = 0;
src.z = 0;
GetJointAbsPosition(item, (PHD_VECTOR*)& src, GuardianMeshes[i]);
int c = 8192 * phd_cos(angles[1]);
dest.x = src.x + c * phd_sin(item->pos.yRot);
dest.y = src.y + 8192 * phd_sin(-angles[1]);
dest.z = src.z + c * phd_cos(item->pos.yRot);
if (item->itemFlags[3] != 90
&& LaserHeadData.fireArcs[j] != NULL)
{
// Eye is aready firing
SoundEffect(SFX_GOD_HEAD_LASER_LOOPS, &item->pos, 0);
LaserHeadData.fireArcs[j]->pos1.x = src.x;
LaserHeadData.fireArcs[j]->pos1.y = src.y;
LaserHeadData.fireArcs[j]->pos1.z = src.z;
}
else
{
// Start firing from eye
src.roomNumber = item->roomNumber;
LaserHeadData.LOS = LOS(&src, &dest);
LaserHeadData.fireArcs[j] = TriggerEnergyArc((PHD_VECTOR*)& src, (PHD_VECTOR*)& dest, 128, g, b, 32, 64, 64, ENERGY_ARC_NO_RANDOMIZE, ENERGY_ARC_STRAIGHT_LINE); // (GetRandomControl() & 7) + 4, b | ((&unk_640000 | g) << 8), 12, 64, 5);
StopSoundEffect(SFX_GOD_HEAD_CHARGE);
SoundEffect(SFX_GOD_HEAD_BLAST, &item->pos, 0);
}
ENERGY_ARC* currentArc = LaserHeadData.fireArcs[j];
if (GlobalCounter & 1)
{
TriggerLaserHeadSparks((PHD_VECTOR*)& src, 3, 0, g, b, 0);
TriggerLightningGlow(src.x, src.y, src.z, (GetRandomControl() & 3) + 32, 0, g, b);
TriggerDynamicLight(src.x, src.y, src.z, (GetRandomControl() & 3) + 16, 0, g, b);
if (!LaserHeadData.LOS)
{
TriggerLightningGlow(currentArc->pos4.x, currentArc->pos4.y, currentArc->pos4.z, (GetRandomControl() & 3) + 16, 0, g, b);
TriggerDynamicLight(currentArc->pos4.x, currentArc->pos4.y, currentArc->pos4.z, (GetRandomControl() & 3) + 6, 0, g, b);
TriggerLaserHeadSparks((PHD_VECTOR*)& currentArc->pos4, 3, 0, g, b, 0);
}
}
// Check if Lara was hit by energy arcs
if (!Lara.burn)
{
int someIndex = 0;
BOUNDING_BOX* bounds = GetBoundsAccurate(LaraItem);
BOUNDING_BOX tbounds;
phd_RotBoundingBoxNoPersp(&LaraItem->pos, bounds, &tbounds);
int x1 = LaraItem->pos.xPos + tbounds.X1;
int x2 = LaraItem->pos.xPos + tbounds.X2;
int y1 = LaraItem->pos.yPos + tbounds.Y1;
int y2 = LaraItem->pos.yPos + tbounds.Y1;
int z1 = LaraItem->pos.zPos + tbounds.Z1;
int z2 = LaraItem->pos.zPos + tbounds.Z2;
int xc = LaraItem->pos.xPos + ((bounds->X1 + bounds->X2) / 2);
int yc = LaraItem->pos.yPos + ((bounds->Y1 + bounds->Y2) / 2);
int zc = LaraItem->pos.zPos + ((bounds->Z1 + bounds->Z2) / 2);
int distance = sqrt(SQUARE(xc - src.x) + SQUARE(yc - src.y) + SQUARE(zc - src.z));
if (distance < 8192)
{
int dl = distance + 512;
if (dl < 8192)
{
dest.x = src.x + dl * (dest.x - src.x) / 8192;
dest.y = src.y + dl * (dest.y - src.y) / 8192;
dest.z = src.z + dl * (dest.z - src.z) / 8192;
}
int dx = (dest.x - src.x) / 32;
int dy = (dest.y - src.y) / 32;
int dz = (dest.z - src.z) / 32;
int adx = currentArc->pos4.x - src.z;
int ady = currentArc->pos4.y - src.y;
int adz = currentArc->pos4.z - src.z;
int x = src.x;
int y = src.y;
int z = src.z;
for (int j = 0; j < 32; j++)
{
if (someIndex)
{
someIndex--;
if (!someIndex)
break;
}
if (abs(adx) < 280 && abs(ady) < 280 && abs(adz) < 280)
someIndex = 2;
if (x > x1 && x < x2 && y > y1 && y < y2 && z > z1 && z < z2)
{
LaraBurn();
Lara.burnCount = 48;
Lara.burnBlue = 2;
LaraItem->hitPoints = 0;
break;
}
x += dx;
y += dy;
z += dz;
adx -= dx;
ady -= dy;
adz -= dz;
}
}
}
}
else if (item->itemFlags[3] > 90)
{
if (LaserHeadData.fireArcs[j])
{
LaserHeadData.fireArcs[j]->life = 0;
LaserHeadData.fireArcs[j] = NULL;
}
}
}
}
}
}
}
}
else
{
if (item->itemFlags[2] >= 8)
{
if (item->pos.yPos <= item->itemFlags[1])
{
src.x = 0;
src.y = 168;
src.z = 248;
src.roomNumber = item->roomNumber;
GetJointAbsPosition(item, (PHD_VECTOR*)& src, 0);
dest.x = 0;
dest.y = 0;
dest.z = 0;
GetJointAbsPosition(LaraItem, (PHD_VECTOR*)& dest, LM_LHAND);
if (LOS(&src, &src))
{
item->itemFlags[0]++;
item->itemFlags[1] = item->pos.yPos;
item->itemFlags[2] = 2640;
}
}
else
{
item->fallspeed += 3;
if (item->fallspeed > 32)
item->fallspeed = 32;
item->pos.yPos -= item->fallspeed;
}
}
else if (!(GlobalCounter & 7))
{
short tentacleItemNumber = creature->tentacles[item->itemFlags[2]];
ITEM_INFO* tentacleItem = &g_Level.Items[tentacleItemNumber];
AddActiveItem(tentacleItemNumber);
tentacleItem->status = ITEM_ACTIVE;
tentacleItem->flags |= 0x3E00;
item->itemFlags[2]++;
}
}
if (item->itemFlags[0] < 3)
{
int i = 0;
/*for (i = 0; i < 8; i++)
{
short tentacleItemNumber = creature->tentacles[item->itemFlags[2]];
ITEM_INFO* tentacleItem = &g_Level.Items[tentacleItemNumber];
if (tentacleItem->animNumber == Objects[tentacleItem->objectNumber].animIndex
&& tentacleItem->frameNumber != g_Level.Anims[tentacleItem->animNumber].frameEnd)
{
break;
}
}*/
// If all tentacles animations are done and both eyes are destroyed it's time to die
if (/*i == 8*/ !(item->meshBits & 6))
{
if (LaserHeadData.fireArcs[0])
LaserHeadData.fireArcs[0]->life = 2;
if (LaserHeadData.fireArcs[1])
LaserHeadData.fireArcs[1]->life = 2;
LaserHeadData.fireArcs[0] = NULL;
LaserHeadData.fireArcs[1] = NULL;
item->itemFlags[0] = 3;
item->itemFlags[3] = item->pos.yRot + (GetRandomControl() & 0x1000) - 2048;
item->speed = 3;
item->triggerFlags = item->pos.xRot + (GetRandomControl() & 0x1000) - 2048;
}
}
}