TombEngine/TR5Main/Objects/TR2/tr2_dragon.cpp

599 lines
16 KiB
C++
Raw Normal View History

#include "../newobjects.h"
#include "../../Game/lara.h"
#include "../../Game/effect2.h"
#include "../../Game/items.h"
#include "../../Game/collide.h"
#include "../../Game/Box.h"
#include "../../Game/lot.h"
2019-12-08 07:38:22 +01:00
#include "../../Game/sphere.h"
extern LaraExtraInfo g_LaraExtra;
// TODO: test the dragon and bartoli to check if it work !
// TODO: upgrade the collision of dragon to pick the dagger correctly.
// TODO: finish the dragon, like light when spawning, dragon breath, dragon bone slot, etc...
#define DRAGON_SWIPE_DAMAGE 250
#define DRAGON_TOUCH_DAMAGE 10
#define DRAGON_DIE_ANIM 21
#define DRAGON_DEAD_ANIM 22
#define DRAGON_LIVE_TIME (30*11)
#define DRAGON_ALMOST_LIVE 100
#define DRAGON_WALK_TURN ANGLE(2)
#define DRAGON_NEED_TURN ANGLE(1)
#define DRAGON_TURN_TURN ANGLE(1)
#define DRAGON_CLOSE_RANGE SQUARE(WALL_SIZE*3)
#define DRAGON_STOP_RANGE SQUARE(WALL_SIZE*6)
#define DRAGON_FLAME_SPEED 200
#define DRAGON_TOUCH_R 0x0fe
#define DRAGON_TOUCH_L 0x7f000000
#define BOOM_TIME (130)
#define BOOM_TIME_MIDDLE (140)
#define BOOM_TIME_END (150)
#define BARTOLI_RANGE (WALL_SIZE*9)
#define DRAGON_CLOSE 900
#define DRAGON_FAR 2300
#define DRAGON_MID ((DRAGON_CLOSE+DRAGON_FAR)/2)
#define DRAGON_LCOL -512
#define DRAGON_RCOL +512
BITE_INFO dragonMouthBite = { 35, 171, 1168, 12 };
enum DRAGON_STATE {
DRAGON_EMPTY,
DRAGON_WALK,
DRAGON_LEFT,
DRAGON_RIGHT,
DRAGON_AIM1,
DRAGON_FIRE1,
DRAGON_STOP,
DRAGON_TURNLEFT,
DRAGON_TURNRIGHT,
DRAGON_SWIPELEFT,
DRAGON_SWIPERIGHT,
DRAGON_DEATH
};
void createBartoliLight(short ItemIndex, int type)
{
ITEM_INFO* item;
item = &Items[ItemIndex];
if (type == 0)
TriggerDynamicLight(item->pos.xPos, item->pos.yPos - STEP_SIZE, item->pos.zPos, (GetRandomControl() & 150) + 25, (GetRandomControl() & 30) + 200, (GetRandomControl() & 25) + 200, (GetRandomControl() & 20) + 200);
else if (type == 1)
TriggerDynamicLight(item->pos.xPos, item->pos.yPos - STEP_SIZE, item->pos.zPos, (GetRandomControl() & 75) + 25, (GetRandomControl() & 30) + 200, (GetRandomControl() & 25) + 100, (GetRandomControl() & 20) + 50);
else if (type == 2)
TriggerDynamicLight(item->pos.xPos, item->pos.yPos - STEP_SIZE, item->pos.zPos, (GetRandomControl() & 20) + 25, (GetRandomControl() & 30) + 200, (GetRandomControl() & 25) + 50, (GetRandomControl() & 20) + 0);
}
short dragonFire(int x, int y, int z, short speed, short yrot, short room_number)
{
short fx_number = NO_ITEM;
/*
FX_INFO* fx;
fx_number = CreateNewEffect(room_number);
if (fx_number != NO_ITEM)
{
fx = &Effects[fx_number];
fx->pos.xPos = x;
fx->pos.yPos = y;
fx->pos.zPos = z;
fx->roomNumber = room_number;
fx->pos.xRot = fx->pos.zRot = 0;
fx->pos.yRot = yrot;
fx->speed = 200;
fx->objectNumber = ID_DRAGON_FIRE;
fx->shade = 14 * 256;
fx->counter = 0x4000;
fx->flag1 = 0;
ShootAtLara(fx);
}
*/
return fx_number;
}
void createExplosion(ITEM_INFO* item)
{
ITEM_INFO* itemExplo;
short ExplIndex;
ExplIndex = CreateItem();
if (ExplIndex != NO_ITEM)
{
itemExplo = &Items[ExplIndex];
if (item->timer == BOOM_TIME)
itemExplo->objectNumber = ID_SPHERE_OF_DOOM;
else if (item->timer == BOOM_TIME + 10)
itemExplo->objectNumber = ID_SPHERE_OF_DOOM2;
else if (item->timer == BOOM_TIME + 20)
itemExplo->objectNumber = ID_SPHERE_OF_DOOM3;
itemExplo->pos.xPos = item->pos.xPos;
itemExplo->pos.yPos = item->pos.yPos + STEP_SIZE;
itemExplo->pos.zPos = item->pos.zPos;
itemExplo->roomNumber = item->roomNumber;
itemExplo->pos.yRot = 0;
itemExplo->pos.xRot = 0;
itemExplo->pos.zRot = 0;
itemExplo->speed = 0;
itemExplo->fallspeed = 0;
InitialiseItem(ExplIndex);
AddActiveItem(ExplIndex);
itemExplo->status = ITEM_ACTIVE;
}
}
void createDragonBone(short front_number)
{
/* Create the bones of the dragon */
short bone_back, bone_front;
ITEM_INFO* back_dragon, *front_dragon, *item;
bone_front = CreateItem();
bone_back = CreateItem();
if (bone_back != NO_ITEM && bone_front != NO_ITEM)
{
item = &Items[front_number];
back_dragon = &Items[bone_back];
back_dragon->objectNumber = ID_DRAGON_BONE_BACK;
back_dragon->pos.xPos = item->pos.xPos;
back_dragon->pos.yPos = item->pos.yPos;
back_dragon->pos.zPos = item->pos.zPos;
back_dragon->pos.xRot = back_dragon->pos.zRot = 0;
back_dragon->pos.yRot = item->pos.yRot;
back_dragon->roomNumber = item->roomNumber;
InitialiseItem(bone_back);
front_dragon = &Items[bone_front];
front_dragon->objectNumber = ID_DRAGON_BONE_FRONT;
front_dragon->pos.xPos = item->pos.xPos;
front_dragon->pos.yPos = item->pos.yPos;
front_dragon->pos.zPos = item->pos.zPos;
front_dragon->pos.xRot = front_dragon->pos.zRot = 0;
front_dragon->pos.yRot = item->pos.yRot;
front_dragon->roomNumber = item->roomNumber;
InitialiseItem(bone_front);
front_dragon->meshBits = 0xFF3FFFFF;
}
}
void DragonCollision(short itemNum, ITEM_INFO* laraitem, COLL_INFO* coll)
{
ITEM_INFO* item;
int c, s, rx, rz, shift, side_shift, angle;
int anim, frame;
/* If Lara has collided with correct bit of dragon, then start him up */
item = &Items[itemNum];
if (!TestBoundsCollide(item, laraitem, coll->radius))
return;
if (!TestCollision(item, laraitem))
return;
/* Check for possible Lara extraction of knife */
if (item->currentAnimState == DRAGON_DEATH)
{
rx = laraitem->pos.xPos - item->pos.xPos;
rz = laraitem->pos.zPos - item->pos.zPos;
c = COS(item->pos.yRot);
s = SIN(item->pos.yRot);
/* See if Lara within leg space of dragon; if so use different collision to sphere stuff */
side_shift = (rx * s + rz * c) >> W2V_SHIFT;
if (side_shift > DRAGON_LCOL&& side_shift < DRAGON_RCOL)
{
shift = (rx * c - rz * s) >> W2V_SHIFT;
if (shift <= DRAGON_CLOSE && shift >= DRAGON_FAR)
return;
angle = laraitem->pos.yRot - item->pos.yRot;
/* Check if in a position to pull dagger from dragon */
anim = item->animNumber - Objects[ID_DRAGON_BACK].animIndex;
frame = item->frameNumber - Anims[item->animNumber].frameBase;
if ((anim == DRAGON_DEAD_ANIM || (anim == DRAGON_DEAD_ANIM + 1 && frame <= DRAGON_ALMOST_LIVE)) &&
(TrInput & IN_ACTION) && item->objectNumber == ID_DRAGON_BACK && !laraitem->gravityStatus &&
shift <= DRAGON_MID && shift > DRAGON_CLOSE - 350 && side_shift > -350 && side_shift < 350 &&
angle > 0x4000 - ANGLE(30) && angle < 0x4000 + ANGLE(30))
{
/* Jump to animation state in LARA_EXTRA */
laraitem->animNumber = Objects[ID_LARA_EXTRA_ANIMS].animIndex;
laraitem->frameNumber = Anims[laraitem->animNumber].frameBase;
laraitem->currentAnimState = 0;
laraitem->goalAnimState = 7;
/* Move to position */
laraitem->pos.xPos = item->pos.xPos;
laraitem->pos.yPos = item->pos.yPos;
laraitem->pos.zPos = item->pos.zPos;
laraitem->pos.yRot = item->pos.yRot;
laraitem->pos.xRot = item->pos.xRot;
laraitem->pos.zRot = item->pos.zRot;
laraitem->fallspeed = 0;
laraitem->gravityStatus = false;
laraitem->speed = 0;
if (item->roomNumber != laraitem->roomNumber)
ItemNewRoom(Lara.itemNumber, item->roomNumber);
/* Jump to that animation */
AnimateItem(LaraItem);
/* Flag that Lara is running animations from another project */
g_LaraExtra.ExtraAnim = 1;
Lara.gunStatus = LG_HANDS_BUSY;
Lara.hitDirection = -1;
2019-12-15 16:19:01 +01:00
LARA_MESHES(ID_LARA_EXTRA_ANIMS, LM_RHAND);
/* Do cinematic camera */
Camera.type = CINEMATIC_CAMERA;
//CineFrame = 0;
//memcpy(&CinematicPos, &laraitem->pos, sizeof(PHD_3DPOS));
/* Flag dragon is dead forever */
((CREATURE_INFO*)Items[(short)item->data].data)->flags = -1;
return;
}
/* Push away from body part (but not like ItemPushLara does it) */
if (shift < DRAGON_MID)
shift = DRAGON_CLOSE - shift;
else
shift = DRAGON_FAR - shift;
laraitem->pos.xPos += shift * c >> W2V_SHIFT;
laraitem->pos.zPos -= shift * s >> W2V_SHIFT;
return;
}
}
ItemPushLara(item, laraitem, coll, 1, 0);
}
void DragonControl(short backNum)
{
ITEM_INFO* item, *back;
CREATURE_INFO* dragon;
AI_INFO info;
bool ahead;
short head, angle;
short itemNum;
back = &Items[backNum];
if (back->data != NULL && back->objectNumber == ID_DRAGON_FRONT) // check if data is not null and back is front
return;
itemNum = (short)back->data;
if (!CreatureActive(itemNum))
return;
item = &Items[itemNum];
dragon = (CREATURE_INFO*)item->data;
head = angle = 0;
if (item->hitPoints <= 0)
{
if (item->currentAnimState != DRAGON_DEATH)
{
item->animNumber = Objects[ID_DRAGON_FRONT].animIndex + 21;
item->frameNumber = Anims[item->animNumber].frameBase;
item->currentAnimState = item->goalAnimState = DRAGON_DEATH;
dragon->flags = 0;
}
else if (dragon->flags >= 0)
{
createBartoliLight(itemNum, 1);
dragon->flags++;
if (dragon->flags == DRAGON_LIVE_TIME)
item->goalAnimState = DRAGON_STOP;
if (dragon->flags == DRAGON_LIVE_TIME + DRAGON_ALMOST_LIVE) // too late !
item->hitPoints = Objects[ID_DRAGON_FRONT].hitPoints >> 1; // half
}
else
{
// dragon has truly popped his clogs
if (dragon->flags > -20)
createBartoliLight(itemNum, 2);
if (dragon->flags == -100)
{
createDragonBone(itemNum);
}
else if (dragon->flags == -200)
{
// all over
DisableBaddieAI(itemNum);
KillItem(backNum);
back->status = ITEM_DEACTIVATED;
KillItem(itemNum);
item->status = ITEM_DEACTIVATED;
return;
}
else if (dragon->flags < -100)
{
item->pos.yPos += 10;
back->pos.yPos += 10;
}
dragon->flags--;
return;
}
}
else
{
CreatureAIInfo(item, &info);
GetCreatureMood(item, &info, VIOLENT);
CreatureMood(item, &info, VIOLENT);
angle = CreatureTurn(item, DRAGON_WALK_TURN);
ahead = (info.ahead && info.distance > DRAGON_CLOSE_RANGE && info.distance < DRAGON_STOP_RANGE);
/* Hurts just to touch dragon */
if (item->touchBits)
{
LaraItem->hitStatus = true;
LaraItem->hitPoints -= DRAGON_TOUCH_DAMAGE;
}
switch (item->currentAnimState)
{
case DRAGON_STOP:
item->pos.yRot -= angle;
if (!ahead)
{
if (info.distance > DRAGON_STOP_RANGE || !info.ahead)
item->goalAnimState = DRAGON_WALK;
else if (info.ahead && info.distance < DRAGON_CLOSE_RANGE && !dragon->flags)
{
dragon->flags = 1; // if not reset by swipe, then didn't hit Lara, so move next time
if (info.angle < 0)
item->goalAnimState = DRAGON_SWIPELEFT;
else
item->goalAnimState = DRAGON_SWIPERIGHT;
}
else if (info.angle < 0)
item->goalAnimState = DRAGON_TURNLEFT;
else
item->goalAnimState = DRAGON_TURNRIGHT;
}
else
item->goalAnimState = DRAGON_AIM1;
break;
case DRAGON_SWIPELEFT:
if (item->touchBits & DRAGON_TOUCH_L)
{
LaraItem->hitStatus = true;
LaraItem->hitPoints -= DRAGON_SWIPE_DAMAGE;
dragon->flags = 0;
}
break;
case DRAGON_SWIPERIGHT:
if (item->touchBits & DRAGON_TOUCH_R)
{
LaraItem->hitStatus = true;
LaraItem->hitPoints -= DRAGON_SWIPE_DAMAGE;
dragon->flags = 0;
}
break;
case DRAGON_WALK:
dragon->flags = 0;
if (ahead)
item->goalAnimState = DRAGON_STOP;
else if (angle < -DRAGON_NEED_TURN)
{
if (info.distance < DRAGON_STOP_RANGE && info.ahead)
item->goalAnimState = DRAGON_STOP;
else
item->goalAnimState = DRAGON_LEFT;
}
else if (angle > DRAGON_NEED_TURN)
{
if (info.distance < DRAGON_STOP_RANGE && info.ahead)
item->goalAnimState = DRAGON_STOP;
else
item->goalAnimState = DRAGON_RIGHT;
}
break;
case DRAGON_LEFT:
if (angle > -DRAGON_NEED_TURN || ahead)
item->goalAnimState = DRAGON_WALK;
break;
case DRAGON_RIGHT:
if (angle < DRAGON_NEED_TURN || ahead)
item->goalAnimState = DRAGON_WALK;
break;
case DRAGON_TURNLEFT:
dragon->flags = 0;
item->pos.yRot += -(ANGLE(1) - angle);
break;
case DRAGON_TURNRIGHT:
dragon->flags = 0;
item->pos.yRot += (ANGLE(1) - angle);
break;
case DRAGON_AIM1:
item->pos.yRot -= angle;
if (info.ahead)
head = -info.angle;
if (ahead)
{
dragon->flags = 30;
item->goalAnimState = DRAGON_FIRE1;
}
else
{
dragon->flags = 0;
item->goalAnimState = DRAGON_AIM1;
}
break;
case DRAGON_FIRE1:
item->pos.yRot -= angle;
if (info.ahead)
head = -info.angle;
SoundEffect(305, &item->pos, 0);
if (dragon->flags)
{
if (info.ahead)
CreatureEffect(item, &dragonMouthBite, dragonFire);
dragon->flags--;
}
else
item->goalAnimState = DRAGON_STOP;
break;
}
}
CreatureJoint(item, 0, head);
CreatureAnimation(itemNum, angle, 0);
/* Set back end to same frame */
back->currentAnimState = item->currentAnimState;
back->animNumber = Objects[ID_DRAGON_BACK].animIndex + (item->animNumber - Objects[ID_DRAGON_FRONT].animIndex);
back->frameNumber = Anims[back->animNumber].frameBase + (item->frameNumber - Anims[item->animNumber].frameBase);
back->pos.xPos = item->pos.xPos;
back->pos.yPos = item->pos.yPos;
back->pos.zPos = item->pos.zPos;
back->pos.xRot = item->pos.xRot;
back->pos.yRot = item->pos.yRot;
back->pos.zRot = item->pos.zRot;
if (back->roomNumber != item->roomNumber)
ItemNewRoom(backNum, item->roomNumber);
}
void InitialiseBartoli(short itemNum)
{
ITEM_INFO* item, *back, *front;
short back_item, front_item;
item = &Items[itemNum];
item->pos.xPos -= STEP_SIZE * 2;
item->pos.zPos -= STEP_SIZE * 2;
/* Create dragon (but keep INVISIBLE for time being) */
back_item = CreateItem();
front_item = CreateItem();
if (back_item != NO_ITEM && front_item != NO_ITEM)
{
back = &Items[back_item];
back->objectNumber = ID_DRAGON_BACK;
back->pos.xPos = item->pos.xPos;
back->pos.yPos = item->pos.yPos;
back->pos.zPos = item->pos.zPos;
back->pos.yRot = item->pos.yRot;
back->roomNumber = item->roomNumber;
back->status = ITEM_INVISIBLE;
back->shade = -1;
InitialiseItem(back_item);
back->meshBits = 0x1FFFFF; // turn off spine
item->data = (void*)back_item; // Bartoli points at back of dragon
front = &Items[front_item];
front->objectNumber = ID_DRAGON_FRONT;
front->pos.xPos = item->pos.xPos;
front->pos.yPos = item->pos.yPos;
front->pos.zPos = item->pos.zPos;
front->pos.yRot = item->pos.yRot;
front->roomNumber = item->roomNumber;
front->status = ITEM_INVISIBLE;
front->shade = -1;
InitialiseItem(front_item);
back->data = (void*)front_item; // back of dragon points at front
LevelItems += 2;
}
}
void BartoliControl(short itemNum)
{
ITEM_INFO* item, *back, *front;
short front_item, back_item;
item = &Items[itemNum];
if (item->timer)
{
item->timer++;
if (!(item->timer & 7))
Camera.bounce = item->timer;
createBartoliLight(itemNum, 1);
AnimateItem(item);
if (item->timer == BOOM_TIME || item->timer == BOOM_TIME + 10 || item->timer == BOOM_TIME + 20)
{
front_item = CreateItem();
if (front_item != NO_ITEM)
{
front = &Items[front_item];
if (item->timer == BOOM_TIME)
front->objectNumber = ID_SPHERE_OF_DOOM;
else if (item->timer == BOOM_TIME + 10)
front->objectNumber = ID_SPHERE_OF_DOOM2;
else
front->objectNumber = ID_SPHERE_OF_DOOM3;
front->pos.xPos = item->pos.xPos;
front->pos.yPos = item->pos.yPos + STEP_SIZE;
front->pos.zPos = item->pos.zPos;
front->roomNumber = item->roomNumber;
front->shade = -1;
InitialiseItem(front_item);
AddActiveItem(front_item);
front->status = ITEM_ACTIVE;
}
}
else if (item->timer >= 30 * 5)
{
/* Convert Bartoli into a dragon */
back_item = (short)item->data;
back = &Items[back_item];
front_item = (short)back->data;
front = &Items[front_item];
front->touchBits = back->touchBits = 0;
EnableBaddieAI(front_item, 1);
AddActiveItem(front_item); // needed so dragon targeted, but no actual control routine
AddActiveItem(back_item);
back->status = ITEM_ACTIVE;
KillItem(itemNum);
}
}
else if (abs(LaraItem->pos.xPos - item->pos.xPos) < BARTOLI_RANGE && abs(LaraItem->pos.zPos - item->pos.zPos) < BARTOLI_RANGE)
{
item->timer = 1;
}
}