#include "framework.h" #include "tr4_mutant.h" #include "tr4_locusts.h" #include "effect.h" #include "effect2.h" #include "misc.h" #include "lara.h" #include "setup.h" #include "sphere.h" #include "objectslist.h" #include "trmath.h" enum MUTANT_STATE { MUTANT_EMPTY, MUTANT_APPEAR, MUTANT_IDLE, MUTANT_SHOOT, MUTANT_LOCUST1, MUTANT_LOCUST2, }; enum class MissileRotationType { M_FRONT, M_LEFT, M_RIGHT }; constexpr auto MUTANT_ANIM_APPEAR = 0; constexpr auto MUTANT_SHOOT_RANGE = SQUARE(SECTOR(10)); constexpr auto MUTANT_LOCUST1_RANGE = SQUARE(SECTOR(15)); constexpr auto MUTANT_LOCUST2_RANGE = SQUARE(SECTOR(30)); static void TriggerMutantRocket(PHD_3DPOS* src, short roomNumber, short counter) { FX_INFO* fx; short fxNumber = NO_ITEM; fxNumber = CreateNewEffect(roomNumber); if (fxNumber != NO_ITEM) { fx = &EffectList[fxNumber]; fx->pos.xPos = src->xPos; fx->pos.yPos = src->yPos - (GetRandomControl() & 0x3F) - 32; fx->pos.zPos = src->zPos; fx->pos.xRot = src->xRot; fx->pos.yRot = src->yRot; fx->pos.zRot = 0; fx->roomNumber = roomNumber; fx->counter = 16 * counter + 15; fx->objectNumber = ID_ENERGY_BUBBLES; fx->frameNumber = Objects[fx->objectNumber].meshIndex + 5 * 2; fx->speed = (GetRandomControl() & 0x1F) + 96; fx->flag1 = 6; } } void TriggerMutantRocketEffects(short fxNumber, short xVel, short yVel, short zVel) { FX_INFO* fx; SPARKS* sptr; BYTE color, life, size; //x = LaraItem->pos.xPos - Effects[m_fxNumber].pos.xPos; //z = LaraItem->pos.zPos - Effects[m_fxNumber].pos.zPos; //if (x >= -0x4000u && x <= 0x4000 && z >= -0x4000u && z <= 0x4000) fx = &EffectList[fxNumber]; sptr = &Sparks[GetFreeSpark()]; sptr->on = true; color = (GetRandomControl() & 0x3F) - 128; sptr->sB = 0; sptr->sR = color; sptr->sG = color / 2; color = (GetRandomControl() & 0x3F) - 128; sptr->dB = 0; sptr->dR = color; sptr->dG = color / 2; sptr->fadeToBlack = 8; sptr->colFadeSpeed = (GetRandomControl() & 3) + 8; sptr->transType = COLADD; sptr->dynamic = -1; life = (GetRandomControl() & 7) + 32; sptr->life = life; sptr->sLife = life; sptr->x = (GetRandomControl() & 0xF) - 8; sptr->y = 0; sptr->z = (GetRandomControl() & 0xF) - 8; sptr->x += fx->pos.xPos; sptr->y += fx->pos.yPos; sptr->z += fx->pos.zPos; sptr->xVel = xVel; sptr->yVel = yVel; sptr->zVel = zVel; sptr->friction = 34; sptr->flags = SP_EXPDEF | SP_ROTATE | SP_DEF | SP_SCALE; sptr->rotAng = GetRandomControl() & 0xFFF; if (GetRandomControl() & 1) sptr->rotAdd = (GetRandomControl() & 0x1F) - 32; else sptr->rotAdd = (GetRandomControl() & 0x1F) + 32; sptr->gravity = 0; sptr->maxYvel = 0; sptr->fxObj = byte(fxNumber); sptr->scalar = 2; size = (GetRandomControl() & 0xF) + 128; sptr->size = size; sptr->sSize = size; sptr->dSize = size / 4; } static void ShootFireball(PHD_3DPOS* src, MissileRotationType rotation, short roomNumber, int timer) { switch (rotation) { case MissileRotationType::M_LEFT: src->yRot -= GetRandomControl() % 0x2000; break; case MissileRotationType::M_RIGHT: src->yRot += GetRandomControl() % 0x2000; break; } TriggerMutantRocket(src, roomNumber, timer); } static bool ShootFrame(ITEM_INFO* item) { short frameNumber = (item->frameNumber - g_Level.Anims[item->objectNumber].frameBase); if (frameNumber == 45 //|| frameNumber == 50 //|| frameNumber == 55 || frameNumber == 60 //|| frameNumber == 65 //|| frameNumber == 70 || frameNumber == 75) return true; else return false; } static void RotateHeadToTarget(ITEM_INFO* item, CREATURE_INFO* creature, int joint, short& headAngle) { if (creature->enemy == nullptr) { headAngle = item->pos.yRot; return; } ITEM_INFO* enemy = creature->enemy; PHD_VECTOR pos; int x, z; pos.x = 0; pos.y = 0; pos.z = 0; GetJointAbsPosition(item, &pos, joint); x = enemy->pos.xPos - pos.x; z = enemy->pos.zPos - pos.z; headAngle = (short)(phd_atan(z, x) - item->pos.yRot) / 2; } static void GetTargetPosition(ITEM_INFO* item, PHD_3DPOS* target) { PHD_VECTOR start, end; short angles[2]; start.x = 0; start.y = -96; start.z = 144; GetJointAbsPosition(item, &start, 9); end.x = 0; end.y = -128; end.z = 288; GetJointAbsPosition(item, &end, 9); phd_GetVectorAngles(end.x - start.x, end.y - start.y, end.z - start.z, angles); target->xPos = end.x; target->yPos = end.y; target->zPos = end.z; target->yRot = angles[0]; target->xRot = angles[1]; target->zRot = 0; } enum CARDINAL_POINT { C_NORTH = 0, C_NORTH_EAST = 45, C_EAST = 90, C_EAST_SOUTH = 135, C_SOUTH = 180, C_SOUTH_WEST = 225, C_WEST = 270, C_WEST_NORTH = 315 }; static void MoveItemFront(ITEM_INFO* item, int distance) { short degree = short(TO_DEGREES(item->pos.yRot)); switch (degree) { case C_NORTH: item->pos.zPos += distance; break; case C_EAST: item->pos.xPos += distance; break; case C_SOUTH: item->pos.zPos -= distance; break; case C_WEST: item->pos.xPos -= distance; break; } } static void MoveItemBack(ITEM_INFO* item, int distance) { short degree = short(TO_DEGREES(item->pos.yRot)); switch (degree) { case C_NORTH: item->pos.zPos -= distance; break; case C_EAST: item->pos.xPos -= distance; break; case C_SOUTH: item->pos.zPos += distance; break; case C_WEST: item->pos.xPos += distance; break; } } static void MutantAIFix(ITEM_INFO* item, AI_INFO* info) { MoveItemFront(item, SECTOR(2)); item->pos.yPos -= CLICK(3); CreatureAIInfo(item, info); item->pos.yPos += CLICK(3); MoveItemBack(item, SECTOR(2)); } void InitialiseMutant(short itemNumber) { ITEM_INFO* item; InitialiseCreature(itemNumber); item = &g_Level.Items[itemNumber]; item->animNumber = Objects[item->objectNumber].animIndex + MUTANT_ANIM_APPEAR; item->frameNumber = g_Level.Anims[item->animNumber].frameBase; item->currentAnimState = MUTANT_APPEAR; item->goalAnimState = MUTANT_APPEAR; } void MutantControl(short itemNumber) { if (!CreatureActive(itemNumber)) return; ITEM_INFO* item; CREATURE_INFO* mutant; AI_INFO info; OBJECT_Bones mutant_joint; short frameNumber; short headY; short angle; item = &g_Level.Items[itemNumber]; mutant = GetCreatureInfo(item); angle = 0; headY = 0; if (item->aiBits & ALL_AIOBJ) GetAITarget(mutant); else if (mutant->hurtByLara) mutant->enemy = LaraItem; else TargetNearestEntity(item, mutant); MutantAIFix(item, &info); RotateHeadToTarget(item, mutant, 9, headY); GetCreatureMood(item, &info, VIOLENT); CreatureMood(item, &info, VIOLENT); mutant->maximumTurn = 0; angle = CreatureTurn(item, 0); switch (item->currentAnimState) { case MUTANT_IDLE: if (info.ahead) { int random = GetRandomControl() & 31; if ((random > 0 && random < 10) && info.distance <= MUTANT_SHOOT_RANGE) item->goalAnimState = MUTANT_SHOOT; else if ((random > 10 && random < 20) && info.distance <= MUTANT_LOCUST1_RANGE) item->goalAnimState = MUTANT_LOCUST1; else if ((random > 20 && random < 30) && info.distance <= MUTANT_LOCUST2_RANGE) item->goalAnimState = MUTANT_LOCUST2; } break; case MUTANT_SHOOT: frameNumber = (item->frameNumber - g_Level.Anims[item->objectNumber].frameBase); if (frameNumber >= 94 && frameNumber <= 96) { PHD_3DPOS src; GetTargetPosition(item, &src); if (frameNumber == 94) { ShootFireball(&src, MissileRotationType::M_FRONT, item->roomNumber, 0); } else if (frameNumber == 95) { ShootFireball(&src, MissileRotationType::M_LEFT, item->roomNumber, 1); //ShootFireball(&src, MissileRotationType::M_LEFT, item->roomNumber, 1); } else if (frameNumber == 96) { ShootFireball(&src, MissileRotationType::M_RIGHT, item->roomNumber, 1); //ShootFireball(&src, MissileRotationType::M_RIGHT, item->roomNumber, 1); } } break; case MUTANT_LOCUST1: frameNumber = (item->frameNumber - g_Level.Anims[item->objectNumber].frameBase); if (frameNumber >= 60 && frameNumber <= 120) SpawnLocust(item); break; case MUTANT_LOCUST2: if (ShootFrame(item)) { PHD_3DPOS src; GetTargetPosition(item, &src); ShootFireball(&src, MissileRotationType::M_FRONT, item->roomNumber, 1); } break; } if (item->currentAnimState != MUTANT_LOCUST1) mutant_joint = OBJECT_Bones(headY, info.xAngle, true); else mutant_joint = OBJECT_Bones(0); CreatureJoint(item, 0, mutant_joint.bone0); CreatureJoint(item, 1, mutant_joint.bone1); CreatureJoint(item, 2, mutant_joint.bone2); CreatureJoint(item, 3, mutant_joint.bone3); CreatureAnimation(itemNumber, angle, 0); }