#include "framework.h" #include "quad.h" #include "quad_info.h" #include "lara.h" #include "effects/effects.h" #include "items.h" #include "collide.h" #include "camera.h" #include "effects/tomb4fx.h" #include "lara_flare.h" #include "lara_one_gun.h" #include "misc.h" #include "setup.h" #include "level.h" #include "input.h" #include "animation.h" #include "Sound/sound.h" #include "Specific/prng.h" using std::vector; using namespace TEN::Math::Random; #define MAX_VELOCITY 0xA000 #define MIN_DRIFT_SPEED 0x3000 #define BRAKE 0x0280 #define REVERSE_ACC -0x0300 #define MAX_BACK -0x3000 #define MAX_REVS 0xa000 #define TERMINAL_FALLSPEED 240 #define QUAD_SLIP 100 #define QUAD_SLIP_SIDE 50 #define QUAD_FRONT 550 #define QUAD_BACK -550 #define QUAD_SIDE 260 #define QUAD_RADIUS 500 #define QUAD_HEIGHT 512 #define QUAD_SNOW 500 // Unused. #define QUAD_HIT_LEFT 11 #define QUAD_HIT_RIGHT 12 #define QUAD_HIT_FRONT 13 #define QUAD_HIT_BACK 14 #define SMAN_SHOT_DAMAGE 10 #define SMAN_LARA_DAMAGE 50 #define DAMAGE_START 140 #define DAMAGE_LENGTH 14 #define DISMOUNT_DISTANCE 385 // Root bone offset from final frame of animation. #define QUAD_UNDO_TURN ANGLE(2.0f) #define QUAD_TURN_RATE (ANGLE(0.5f) + QUAD_UNDO_TURN) #define QUAD_TURN_MAX ANGLE(5.0f) #define QUAD_DRIFT_TURN_RATE (ANGLE(0.75f) + QUAD_UNDO_TURN) #define QUAD_DRIFT_TURN_MAX ANGLE(8.0f) #define MIN_MOMENTUM_TURN ANGLE(3.0f) #define MAX_MOMENTUM_TURN ANGLE(1.5f) #define QUAD_MAX_MOM_TURN ANGLE(150.0f) #define QUAD_MAX_HEIGHT STEP_SIZE #define QUAD_MIN_BOUNCE (MAX_VELOCITY / 2) / 256 // TODO: Common controls for all vehicles + unique settings page to set them. @Sezz 2021.11.14 #define QUAD_IN_ACCELERATE IN_ACTION #define QUAD_IN_BRAKE IN_JUMP #define QUAD_IN_DRIFT (IN_DUCK | IN_SPRINT) #define QUAD_IN_DISMOUNT IN_ROLL #define QUAD_IN_LEFT IN_LEFT #define QUAD_IN_RIGHT IN_RIGHT enum QuadState { QUAD_STATE_DRIVE = 1, QUAD_STATE_TURN_LEFT = 2, QUAD_STATE_SLOW = 5, QUAD_STATE_BRAKE = 6, QUAD_STATE_BIKE_DEATH = 7, QUAD_STATE_FALL = 8, QUAD_STATE_MOUNT_RIGHT = 9, QUAD_STATE_DISMOUNT_RIGHT = 10, QUAD_STATE_HIT_BACK = 11, QUAD_STATE_HIT_FRONT = 12, QUAD_STATE_HIT_LEFT = 13, QUAD_STATE_HIT_RIGHT = 14, QUAD_STATE_IDLE = 15, QUAD_STATE_LAND = 17, QUAD_STATE_STOP_SLOWLY = 18, QUAD_STATE_FALL_DEATH = 19, QUAD_STATE_FALL_OFF = 20, QUAD_STATE_WHEELIE = 21, // Unused. QUAD_STATE_TURN_RIGHT = 22, QUAD_STATE_MOUNT_LEFT = 23, QUAD_STATE_DISMOUNT_LEFT = 24, }; enum QuadAnim { QUAD_ANIM_IDLE_DEATH = 0, QUAD_ANIM_UNK_1 = 1, QUAD_ANIM_DRIVE_BACK = 2, QUAD_ANIM_TURN_LEFT_START = 3, QUAD_ANIM_TURN_LEFT_CONTINUE = 4, QUAD_ANIM_TURN_LEFT_END = 5, QUAD_ANIM_LEAP_START = 6, QUAD_ANIM_LEAP_CONTINUE = 7, QUAD_ANIM_LEAP_END = 8, QUAD_ANIM_MOUNT_RIGHT = 9, QUAD_ANIM_DISMOUNT_RIGHT = 10, QUAD_ANIM_HIT_FRONT = 11, QUAD_ANIM_HIT_BACK = 12, QUAD_ANIM_HIT_RIGHT = 13, QUAD_ANIM_HIT_LEFT = 14, QUAD_ANIM_UNK_2 = 15, QUAD_ANIM_UNK_3 = 16, QUAD_ANIM_UNK_4 = 17, QUAD_ANIM_IDLE = 18, QUAD_ANIM_FALL_OFF_DEATH = 19, QUAD_ANIM_TURN_RIGHT_START = 20, QUAD_ANIM_TURN_RIGHT_CONTINUE = 21, QUAD_ANIM_TURN_RIGHT_END = 22, QUAD_ANIM_MOUNT_LEFT = 23, QUAD_ANIM_DISMOUNT_LEFT = 24, QUAD_ANIM_LEAP_START2 = 25, QUAD_ANIM_LEAP_CONTINUE2 = 26, QUAD_ANIM_LEAP_END2 = 27, QUAD_ANIM_LEAP_TO_FREEFALL = 28 }; enum QuadFlags { QUAD_FLAG_DEAD = 0x80, QUAD_FLAG_IS_FALLING = 0x40 }; enum QuadEffectPosition { EXHAUST_LEFT = 0, EXHAUST_RIGHT, FRONT_LEFT_TYRE, FRONT_RIGHT_TYRE, BACK_LEFT_TYRE, BACK_RIGHT_TYRE }; BITE_INFO quadEffectsPositions[6] = { { -56, -32, -380, 0 }, { 56, -32, -380, 0 }, { -8, 180, -48, 3 }, { 8, 180, -48, 4 }, { 90, 180, -32, 6 }, { -90, 180, -32, 7 } }; bool QuadDriftStarting; bool QuadCanDriftStart; int QuadSmokeStart; bool QuadNoGetOff; void InitialiseQuadBike(short itemNumber) { ITEM_INFO* quad = &g_Level.Items[itemNumber]; quad->data = QUAD_INFO(); QUAD_INFO* quadInfo = quad->data; quadInfo->velocity = 0; quadInfo->turnRate = 0; quadInfo->leftFallspeed = 0; quadInfo->rightFallspeed = 0; quadInfo->momentumAngle = quad->pos.yRot; quadInfo->extraRotation = 0; quadInfo->trackMesh = 0; quadInfo->pitch = 0; quadInfo->flags = 0; } static void QuadbikeExplode(ITEM_INFO* lara, ITEM_INFO* quad) { LaraInfo*& laraInfo = LaraItem->data; if (g_Level.Rooms[quad->roomNumber].flags & ENV_FLAG_WATER) TriggerUnderwaterExplosion(quad, 1); else { TriggerExplosionSparks(quad->pos.xPos, quad->pos.yPos, quad->pos.zPos, 3, -2, 0, quad->roomNumber); for (int i = 0; i < 3; i++) TriggerExplosionSparks(quad->pos.xPos, quad->pos.yPos, quad->pos.zPos, 3, -1, 0, quad->roomNumber); } auto pos = PHD_3DPOS(quad->pos.xPos, quad->pos.yPos - (STEP_SIZE / 2), quad->pos.zPos, 0, quad->pos.yRot, 0); TriggerShockwave(&pos, 50, 180, 40, GenerateFloat(160, 200), 60, 60, 64, GenerateFloat(0, 359), 0); SoundEffect(SFX_TR4_EXPLOSION1, NULL, 0); SoundEffect(SFX_TR4_EXPLOSION2, NULL, 0); quad->status = ITEM_DEACTIVATED; laraInfo->Vehicle = NO_ITEM; } static int CanQuadbikeGetOff(int direction) { short angle; auto item = &g_Level.Items[Lara.Vehicle]; if (direction < 0) angle = item->pos.yRot - ANGLE(90.0f); else angle = item->pos.yRot + ANGLE(90.0f); int x = item->pos.xPos + (STEP_SIZE * 2) * phd_sin(angle); int y = item->pos.yPos; int z = item->pos.zPos + (STEP_SIZE * 2) * phd_cos(angle); auto collResult = GetCollisionResult(x, y, z, item->roomNumber); if (collResult.Position.Slope || collResult.Position.Floor == NO_HEIGHT) { return false; } if (abs(collResult.Position.Floor - item->pos.yPos) > (STEP_SIZE * 2)) return false; if ((collResult.Position.Ceiling - item->pos.yPos) > -LARA_HEIGHT || (collResult.Position.Floor - collResult.Position.Ceiling) < LARA_HEIGHT) { return false; } return true; } static bool QuadCheckGetOff(ITEM_INFO* lara, ITEM_INFO* quad) { LaraInfo*& laraInfo = lara->data; auto quadInfo = (QUAD_INFO*)quad->data; if (laraInfo->Vehicle == NO_ITEM) return true; if ((lara->currentAnimState == QUAD_STATE_DISMOUNT_RIGHT || lara->currentAnimState == QUAD_STATE_DISMOUNT_LEFT) && TestLastFrame(lara, lara->animNumber)) { if (lara->currentAnimState == QUAD_STATE_DISMOUNT_LEFT) lara->pos.yRot += ANGLE(90.0f); else lara->pos.yRot -= ANGLE(90.0f); SetAnimation(lara, LA_STAND_IDLE); lara->pos.xPos -= DISMOUNT_DISTANCE * phd_sin(lara->pos.yRot); lara->pos.zPos -= DISMOUNT_DISTANCE * phd_cos(lara->pos.yRot); lara->pos.xRot = 0; lara->pos.zRot = 0; laraInfo->Vehicle = NO_ITEM; laraInfo->gunStatus = LG_NO_ARMS; if (lara->currentAnimState == QUAD_STATE_FALL_OFF) { PHD_VECTOR pos = { 0, 0, 0 }; SetAnimation(lara, LA_FREEFALL); GetJointAbsPosition(lara, &pos, LM_HIPS); lara->pos.xPos = pos.x; lara->pos.yPos = pos.y; lara->pos.zPos = pos.z; lara->fallspeed = quad->fallspeed; lara->gravityStatus = true; lara->pos.xRot = 0; lara->pos.zRot = 0; lara->hitPoints = 0; laraInfo->gunStatus = LG_NO_ARMS; quad->flags |= ONESHOT; return false; } else if (lara->currentAnimState == QUAD_STATE_FALL_DEATH) { quadInfo->flags |= QUAD_FLAG_DEAD; lara->goalAnimState = LS_DEATH; lara->fallspeed = DAMAGE_START + DAMAGE_LENGTH; lara->speed = 0; return false; } return true; } else return true; } static int GetOnQuadBike(ITEM_INFO* lara, ITEM_INFO* quad, COLL_INFO* coll) { LaraInfo*& laraInfo = lara->data; if (!(TrInput & IN_ACTION) || lara->gravityStatus || laraInfo->gunStatus != LG_NO_ARMS || quad->flags & ONESHOT || abs(quad->pos.yPos - lara->pos.yPos) > STEP_SIZE) { return false; } auto dist = pow(lara->pos.xPos - quad->pos.xPos, 2) + pow(lara->pos.zPos - quad->pos.zPos, 2); if (dist > 170000) return false; auto probe = GetCollisionResult(quad); if (probe.Position.Floor < -32000) return false; else { short angle = phd_atan(quad->pos.zPos - lara->pos.zPos, quad->pos.xPos - lara->pos.xPos); angle -= quad->pos.yRot; if ((angle > -ANGLE(45.0f)) && (angle < ANGLE(135.0f))) { short tempAngle = lara->pos.yRot - quad->pos.yRot; if (tempAngle > ANGLE(45.0f) && tempAngle < ANGLE(135.0f)) return true; else return false; } else { short tempAngle = lara->pos.yRot - quad->pos.yRot; if (tempAngle > ANGLE(225.0f) && tempAngle < ANGLE(315.0f)) return true; else return false; } } return true; } static void QuadBaddieCollision(ITEM_INFO* lara, ITEM_INFO* quad) { vector roomsList; roomsList.push_back(quad->roomNumber); ROOM_INFO* room = &g_Level.Rooms[quad->roomNumber]; for (int i = 0; i < room->doors.size(); i++) roomsList.push_back(room->doors[i].room); for (int i = 0; i < roomsList.size(); i++) { auto itemNum = g_Level.Rooms[roomsList[i]].itemNumber; while (itemNum != NO_ITEM) { ITEM_INFO* item = &g_Level.Items[itemNum]; if (item->collidable && item->status != ITEM_INVISIBLE && item != lara && item != quad) { OBJECT_INFO* object = &Objects[item->objectNumber]; if (object->collision && object->intelligent) { auto x = quad->pos.xPos - item->pos.xPos; auto y = quad->pos.yPos - item->pos.yPos; auto z = quad->pos.zPos - item->pos.zPos; if (x > -4096 && x < 4096 && z > -4096 && z < 4096 && y > -4096 && y < 4096) { if (TestBoundsCollide(item, quad, QUAD_RADIUS)) { DoLotsOfBlood(item->pos.xPos, quad->pos.yPos - STEP_SIZE, item->pos.zPos, quad->speed, quad->pos.yRot, item->roomNumber, 3); item->hitPoints = 0; } } } } itemNum = item->nextItem; } } } static int GetQuadCollisionAnim(ITEM_INFO* quad, PHD_VECTOR* p) { p->x = quad->pos.xPos - p->x; p->z = quad->pos.zPos - p->z; if (p->x || p->z) { float c = phd_cos(quad->pos.yRot); float s = phd_sin(quad->pos.yRot); int front = p->z * c + p->x * s; int side = -p->z * s + p->x * c; if (abs(front) > abs(side)) { if (front > 0) return QUAD_HIT_BACK; else return QUAD_HIT_FRONT; } else { if (side > 0) return QUAD_HIT_LEFT; else return QUAD_HIT_RIGHT; } } return 0; } static int TestQuadHeight(ITEM_INFO* quad, int dz, int dx, PHD_VECTOR* pos) { pos->y = quad->pos.yPos - dz * phd_sin(quad->pos.xRot) + dx * phd_sin(quad->pos.zRot); float c = phd_cos(quad->pos.yRot); float s = phd_sin(quad->pos.yRot); pos->z = quad->pos.zPos + dz * c - dx * s; pos->x = quad->pos.xPos + dz * s + dx * c; auto probe = GetCollisionResult(pos->x, pos->y, pos->z, quad->roomNumber); if (probe.Position.Ceiling > pos->y || probe.Position.Ceiling == NO_HEIGHT) { return NO_HEIGHT; } return probe.Position.Floor; } static int DoQuadShift(ITEM_INFO* quad, PHD_VECTOR* pos, PHD_VECTOR* old) { COLL_RESULT probe; auto x = pos->x / SECTOR(1); auto z = pos->z / SECTOR(1); auto oldX = old->x / SECTOR(1); auto oldZ = old->z / SECTOR(1); auto shiftX = pos->x & (WALL_SIZE - 1); auto shiftZ = pos->z & (WALL_SIZE - 1); if (x == oldX) { if (z == oldZ) { quad->pos.zPos += (old->z - pos->z); quad->pos.xPos += (old->x - pos->x); } else if (z > oldZ) { quad->pos.zPos -= shiftZ + 1; return (pos->x - quad->pos.xPos); } else { quad->pos.zPos += WALL_SIZE - shiftZ; return (quad->pos.xPos - pos->x); } } else if (z == oldZ) { if (x > oldX) { quad->pos.xPos -= shiftX + 1; return (quad->pos.zPos - pos->z); } else { quad->pos.xPos += WALL_SIZE - shiftX; return (pos->z - quad->pos.zPos); } } else { x = 0; z = 0; probe = GetCollisionResult(old->x, pos->y, pos->z, quad->roomNumber); if (probe.Position.Floor < (old->y - STEP_SIZE)) { if (pos->z > old->z) z = -shiftZ - 1; else z = WALL_SIZE - shiftZ; } probe = GetCollisionResult(pos->x, pos->y, old->z, quad->roomNumber); if (probe.Position.Floor < (old->y - STEP_SIZE)) { if (pos->x > old->x) x = -shiftX - 1; else x = WALL_SIZE - shiftX; } if (x && z) { quad->pos.zPos += z; quad->pos.xPos += x; } else if (z) { quad->pos.zPos += z; if (z > 0) return (quad->pos.xPos - pos->x); else return (pos->x - quad->pos.xPos); } else if (x) { quad->pos.xPos += x; if (x > 0) return (pos->z - quad->pos.zPos); else return (quad->pos.zPos - pos->z); } else { quad->pos.zPos += (old->z - pos->z); quad->pos.xPos += (old->x - pos->x); } } return 0; } static int DoQuadDynamics(int height, int fallspeed, int* y) { if (height > *y) { *y += fallspeed; if (*y > height - QUAD_MIN_BOUNCE) { *y = height; fallspeed = 0; } else fallspeed += 6; } else { int kick = (height - *y) * 4; if (kick < -80) kick = -80; fallspeed += ((kick - fallspeed) / 8); if (*y > height) *y = height; } return fallspeed; } static int QuadDynamics(ITEM_INFO* lara, ITEM_INFO* quad) { LaraInfo*& laraInfo = lara->data; auto quadInfo = (QUAD_INFO*)quad->data; COLL_RESULT probe; PHD_VECTOR moved, fl, fr, br, bl, mtl, mbl, mtr, mbr, mml, mmr; PHD_VECTOR old, oldFrontLeft, oldFrontRight, oldBottomLeft, oldBottomRight, mtl_old, moldBottomLeft, mtr_old, moldBottomRight, mml_old, mmr_old; int heightFrontLeft, heightFrontRight, heightBackRight, heightBackLeft, hmtl, hmbl, hmtr, hmbr, hmml, hmmr; int holdFrontRight, holdFrontLeft, holdBottomRight, holdBottomLeft, hmtl_old, hmoldBottomLeft, hmtr_old, hmoldBottomRight, hmml_old, hmmr_old; int slip, collide; short rot, rotadd; int newspeed; QuadNoGetOff = false; holdFrontLeft = TestQuadHeight(quad, QUAD_FRONT, -QUAD_SIDE, &oldFrontLeft); holdFrontRight = TestQuadHeight(quad, QUAD_FRONT, QUAD_SIDE, &oldFrontRight); holdBottomLeft = TestQuadHeight(quad, -QUAD_FRONT, -QUAD_SIDE, &oldBottomLeft); holdBottomRight = TestQuadHeight(quad, -QUAD_FRONT, QUAD_SIDE, &oldBottomRight); hmml_old = TestQuadHeight(quad, 0, -QUAD_SIDE, &mml_old); hmmr_old = TestQuadHeight(quad, 0, QUAD_SIDE, &mmr_old); hmtl_old = TestQuadHeight(quad, QUAD_FRONT / 2, -QUAD_SIDE, &mtl_old); hmtr_old = TestQuadHeight(quad, QUAD_FRONT / 2, QUAD_SIDE, &mtr_old); hmoldBottomLeft = TestQuadHeight(quad, -QUAD_FRONT / 2, -QUAD_SIDE, &moldBottomLeft); hmoldBottomRight = TestQuadHeight(quad, -QUAD_FRONT / 2, QUAD_SIDE, &moldBottomRight); old.x = quad->pos.xPos; old.y = quad->pos.yPos; old.z = quad->pos.zPos; if (oldBottomLeft.y > holdBottomLeft) oldBottomLeft.y = holdBottomLeft; if (oldBottomRight.y > holdBottomRight) oldBottomRight.y = holdBottomRight; if (oldFrontLeft.y > holdFrontLeft) oldFrontLeft.y = holdFrontLeft; if (oldFrontRight.y > holdFrontRight) oldFrontRight.y = holdFrontRight; if (moldBottomLeft.y > hmoldBottomLeft) moldBottomLeft.y = hmoldBottomLeft; if (moldBottomRight.y > hmoldBottomRight) moldBottomRight.y = hmoldBottomRight; if (mtl_old.y > hmtl_old) mtl_old.y = hmtl_old; if (mtr_old.y > hmtr_old) mtr_old.y = hmtr_old; if (mml_old.y > hmml_old) mml_old.y = hmml_old; if (mmr_old.y > hmmr_old) mmr_old.y = hmmr_old; if (quad->pos.yPos > (quad->floor - STEP_SIZE)) { short momentum; if (quadInfo->turnRate < -QUAD_UNDO_TURN) quadInfo->turnRate += QUAD_UNDO_TURN; else if (quadInfo->turnRate > QUAD_UNDO_TURN) quadInfo->turnRate -= QUAD_UNDO_TURN; else quadInfo->turnRate = 0; quad->pos.yRot += quadInfo->turnRate + quadInfo->extraRotation; rot = quad->pos.yRot - quadInfo->momentumAngle; momentum = MIN_MOMENTUM_TURN - (((((MIN_MOMENTUM_TURN - MAX_MOMENTUM_TURN) * 256) / MAX_VELOCITY) * quadInfo->velocity) / 256); if (!(TrInput & QUAD_IN_ACCELERATE) && quadInfo->velocity > 0) momentum += (momentum / 4); if (rot < -MAX_MOMENTUM_TURN) { if (rot < -QUAD_MAX_MOM_TURN) { rot = -QUAD_MAX_MOM_TURN; quadInfo->momentumAngle = quad->pos.yRot - rot; } else quadInfo->momentumAngle -= momentum; } else if (rot > MAX_MOMENTUM_TURN) { if (rot > QUAD_MAX_MOM_TURN) { rot = QUAD_MAX_MOM_TURN; quadInfo->momentumAngle = quad->pos.yRot - rot; } else quadInfo->momentumAngle += momentum; } else quadInfo->momentumAngle = quad->pos.yRot; } else quad->pos.yRot += quadInfo->turnRate + quadInfo->extraRotation; probe = GetCollisionResult(quad); int speed = 0; if (quad->pos.yPos >= probe.Position.Floor) speed = quad->speed * phd_cos(quad->pos.xRot); else speed = quad->speed; quad->pos.zPos += speed * phd_cos(quadInfo->momentumAngle); quad->pos.xPos += speed * phd_sin(quadInfo->momentumAngle); slip = QUAD_SLIP * phd_sin(quad->pos.xRot); if (abs(slip) > QUAD_SLIP / 2) { if (slip > 0) slip -= 10; else slip += 10; quad->pos.zPos -= slip * phd_cos(quad->pos.yRot); quad->pos.xPos -= slip * phd_sin(quad->pos.yRot); } slip = QUAD_SLIP_SIDE * phd_sin(quad->pos.zRot); if (abs(slip) > QUAD_SLIP_SIDE / 2) { quad->pos.zPos -= slip * phd_sin(quad->pos.yRot); quad->pos.xPos += slip * phd_cos(quad->pos.yRot); } moved.x = quad->pos.xPos; moved.z = quad->pos.zPos; if (!(quad->flags & ONESHOT)) QuadBaddieCollision(lara, quad); rot = 0; heightFrontLeft = TestQuadHeight(quad, QUAD_FRONT, -QUAD_SIDE, &fl); if (heightFrontLeft < oldFrontLeft.y - STEP_SIZE) rot = DoQuadShift(quad, &fl, &oldFrontLeft); hmtl = TestQuadHeight(quad, QUAD_FRONT / 2, -QUAD_SIDE, &mtl); if (hmtl < mtl_old.y - STEP_SIZE) DoQuadShift(quad, &mtl, &mtl_old); hmml = TestQuadHeight(quad, 0, -QUAD_SIDE, &mml); if (hmml < mml_old.y - STEP_SIZE) DoQuadShift(quad, &mml, &mml_old); hmbl = TestQuadHeight(quad, -QUAD_FRONT / 2, -QUAD_SIDE, &mbl); if (hmbl < moldBottomLeft.y - STEP_SIZE) DoQuadShift(quad, &mbl, &moldBottomLeft); heightBackLeft = TestQuadHeight(quad, -QUAD_FRONT, -QUAD_SIDE, &bl); if (heightBackLeft < oldBottomLeft.y - STEP_SIZE) { rotadd = DoQuadShift(quad, &bl, &oldBottomLeft); if ((rotadd > 0 && rot >= 0) || (rotadd < 0 && rot <= 0)) rot += rotadd; } heightFrontRight = TestQuadHeight(quad, QUAD_FRONT, QUAD_SIDE, &fr); if (heightFrontRight < oldFrontRight.y - STEP_SIZE) { rotadd = DoQuadShift(quad, &fr, &oldFrontRight); if ((rotadd > 0 && rot >= 0) || (rotadd < 0 && rot <= 0)) rot += rotadd; } hmtr = TestQuadHeight(quad, QUAD_FRONT / 2, QUAD_SIDE, &mtr); if (hmtr < mtr_old.y - STEP_SIZE) DoQuadShift(quad, &mtr, &mtr_old); hmmr = TestQuadHeight(quad, 0, QUAD_SIDE, &mmr); if (hmmr < mmr_old.y - STEP_SIZE) DoQuadShift(quad, &mmr, &mmr_old); hmbr = TestQuadHeight(quad, -QUAD_FRONT / 2, QUAD_SIDE, &mbr); if (hmbr < moldBottomRight.y - STEP_SIZE) DoQuadShift(quad, &mbr, &moldBottomRight); heightBackRight = TestQuadHeight(quad, -QUAD_FRONT, QUAD_SIDE, &br); if (heightBackRight < oldBottomRight.y - STEP_SIZE) { rotadd = DoQuadShift(quad, &br, &oldBottomRight); if ((rotadd > 0 && rot >= 0) || (rotadd < 0 && rot <= 0)) rot += rotadd; } probe = GetCollisionResult(quad); if (probe.Position.Floor < quad->pos.yPos - STEP_SIZE) DoQuadShift(quad, (PHD_VECTOR*)&quad->pos, &old); quadInfo->extraRotation = rot; collide = GetQuadCollisionAnim(quad, &moved); if (collide) { newspeed = (quad->pos.zPos - old.z) * phd_cos(quadInfo->momentumAngle) + (quad->pos.xPos - old.x) * phd_sin(quadInfo->momentumAngle); newspeed *= 256; if (&g_Level.Items[laraInfo->Vehicle] == quad && quadInfo->velocity == MAX_VELOCITY && newspeed < (quadInfo->velocity - 10)) { lara->hitPoints -= (quadInfo->velocity - newspeed) / 128; lara->hitStatus = 1; } if (quadInfo->velocity > 0 && newspeed < quadInfo->velocity) quadInfo->velocity = (newspeed < 0) ? 0 : newspeed; else if (quadInfo->velocity < 0 && newspeed > quadInfo->velocity) quadInfo->velocity = (newspeed > 0) ? 0 : newspeed; if (quadInfo->velocity < MAX_BACK) quadInfo->velocity = MAX_BACK; } return collide; } static void AnimateQuadBike(ITEM_INFO* lara, ITEM_INFO* quad, int collide, int dead) { auto quadInfo = (QUAD_INFO*)quad->data; if (quad->pos.yPos != quad->floor && lara->currentAnimState != QUAD_STATE_FALL && lara->currentAnimState != QUAD_STATE_LAND && lara->currentAnimState != QUAD_STATE_FALL_OFF && !dead) { if (quadInfo->velocity < 0) lara->animNumber = Objects[ID_QUAD_LARA_ANIMS].animIndex + QUAD_ANIM_LEAP_START; else lara->animNumber = Objects[ID_QUAD_LARA_ANIMS].animIndex + QUAD_ANIM_LEAP_START2; lara->frameNumber = GetFrameNumber(lara, lara->animNumber); lara->currentAnimState = QUAD_STATE_FALL; lara->goalAnimState = QUAD_STATE_FALL; } else if (collide && lara->currentAnimState != QUAD_STATE_HIT_FRONT && lara->currentAnimState != QUAD_STATE_HIT_BACK && lara->currentAnimState != QUAD_STATE_HIT_LEFT && lara->currentAnimState != QUAD_STATE_HIT_RIGHT && lara->currentAnimState != QUAD_STATE_FALL_OFF && quadInfo->velocity > (MAX_VELOCITY / 3) && !dead) { if (collide == QUAD_HIT_FRONT) { lara->animNumber = Objects[ID_QUAD_LARA_ANIMS].animIndex + QUAD_ANIM_HIT_BACK; lara->currentAnimState = QUAD_STATE_HIT_FRONT; lara->goalAnimState = QUAD_STATE_HIT_FRONT; } else if (collide == QUAD_HIT_BACK) { lara->animNumber = Objects[ID_QUAD_LARA_ANIMS].animIndex + QUAD_ANIM_HIT_FRONT; lara->currentAnimState = QUAD_STATE_HIT_BACK; lara->goalAnimState = QUAD_STATE_HIT_BACK; } else if (collide == QUAD_HIT_LEFT) { lara->animNumber = Objects[ID_QUAD_LARA_ANIMS].animIndex + QUAD_ANIM_HIT_RIGHT; lara->currentAnimState = QUAD_STATE_HIT_LEFT; lara->goalAnimState = QUAD_STATE_HIT_LEFT; } else { lara->animNumber = Objects[ID_QUAD_LARA_ANIMS].animIndex + QUAD_ANIM_HIT_LEFT; lara->currentAnimState = QUAD_STATE_HIT_RIGHT; lara->goalAnimState = QUAD_STATE_HIT_RIGHT; } lara->frameNumber = GetFrameNumber(lara, lara->animNumber); SoundEffect(SFX_TR3_QUAD_FRONT_IMPACT, &quad->pos, 0); } else { switch (lara->currentAnimState) { case QUAD_STATE_IDLE: if (dead) lara->goalAnimState = QUAD_STATE_BIKE_DEATH; else if (TrInput & QUAD_IN_DISMOUNT && quadInfo->velocity == 0 && !QuadNoGetOff) { if (TrInput & QUAD_IN_LEFT && CanQuadbikeGetOff(-1)) lara->goalAnimState = QUAD_STATE_DISMOUNT_LEFT; else if (TrInput & QUAD_IN_RIGHT && CanQuadbikeGetOff(1)) lara->goalAnimState = QUAD_STATE_DISMOUNT_RIGHT; } else if (TrInput & (QUAD_IN_ACCELERATE | QUAD_IN_BRAKE)) lara->goalAnimState = QUAD_STATE_DRIVE; break; case QUAD_STATE_DRIVE: if (dead) { if (quadInfo->velocity > (MAX_VELOCITY / 2)) lara->goalAnimState = QUAD_STATE_FALL_DEATH; else lara->goalAnimState = QUAD_STATE_BIKE_DEATH; } else if (!(TrInput & (QUAD_IN_ACCELERATE | QUAD_IN_BRAKE)) && (quadInfo->velocity / 256) == 0) { lara->goalAnimState = QUAD_STATE_IDLE; } else if (TrInput & QUAD_IN_LEFT && !QuadDriftStarting) { lara->goalAnimState = QUAD_STATE_TURN_LEFT; } else if (TrInput & QUAD_IN_RIGHT && !QuadDriftStarting) { lara->goalAnimState = QUAD_STATE_TURN_RIGHT; } else if (TrInput & QUAD_IN_BRAKE) { if (quadInfo->velocity > (MAX_VELOCITY / 3 * 2)) lara->goalAnimState = QUAD_STATE_BRAKE; else lara->goalAnimState = QUAD_STATE_SLOW; } break; case QUAD_STATE_BRAKE: case QUAD_STATE_SLOW: case QUAD_STATE_STOP_SLOWLY: if ((quadInfo->velocity / 256) == 0) lara->goalAnimState = QUAD_STATE_IDLE; else if (TrInput & QUAD_IN_LEFT) lara->goalAnimState = QUAD_STATE_TURN_LEFT; else if (TrInput & QUAD_IN_RIGHT) lara->goalAnimState = QUAD_STATE_TURN_RIGHT; break; case QUAD_STATE_TURN_LEFT: if ((quadInfo->velocity / 256) == 0) lara->goalAnimState = QUAD_STATE_IDLE; else if (TrInput & QUAD_IN_RIGHT) { lara->animNumber = Objects[ID_QUAD_LARA_ANIMS].animIndex + QUAD_ANIM_TURN_RIGHT_START; lara->frameNumber = GetFrameNumber(lara, lara->animNumber); lara->currentAnimState = QUAD_STATE_TURN_RIGHT; lara->goalAnimState = QUAD_STATE_TURN_RIGHT; } else if (!(TrInput & QUAD_IN_LEFT)) lara->goalAnimState = QUAD_STATE_DRIVE; break; case QUAD_STATE_TURN_RIGHT: if ((quadInfo->velocity / 256) == 0) lara->goalAnimState = QUAD_STATE_IDLE; else if (TrInput & QUAD_IN_LEFT) { lara->animNumber = Objects[ID_QUAD_LARA_ANIMS].animIndex + QUAD_ANIM_TURN_LEFT_START; lara->frameNumber = GetFrameNumber(lara, lara->animNumber); lara->currentAnimState = QUAD_STATE_TURN_LEFT; lara->goalAnimState = QUAD_STATE_TURN_LEFT; } else if (!(TrInput & QUAD_IN_RIGHT)) lara->goalAnimState = QUAD_STATE_DRIVE; break; case QUAD_STATE_FALL: if (quad->pos.yPos == quad->floor) lara->goalAnimState = QUAD_STATE_LAND; else if (quad->fallspeed > TERMINAL_FALLSPEED) quadInfo->flags |= QUAD_FLAG_IS_FALLING; break; case QUAD_STATE_FALL_OFF: break; case QUAD_STATE_HIT_FRONT: case QUAD_STATE_HIT_BACK: case QUAD_STATE_HIT_LEFT: case QUAD_STATE_HIT_RIGHT: if (TrInput & (QUAD_IN_ACCELERATE | QUAD_IN_BRAKE)) lara->goalAnimState = QUAD_STATE_DRIVE; break; } if (g_Level.Rooms[quad->roomNumber].flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP)) { lara->goalAnimState = QUAD_STATE_FALL_OFF; lara->pos.yPos = quad->pos.yPos + 700; lara->roomNumber = quad->roomNumber; lara->hitPoints = 0; QuadbikeExplode(lara, quad); } } } static int QuadUserControl(ITEM_INFO* quad, int height, int* pitch) { auto quadInfo = (QUAD_INFO*)quad->data; bool drive = false; // Never changes? if (!(TrInput & QUAD_IN_DRIFT) && !quadInfo->velocity && !QuadCanDriftStart) { QuadCanDriftStart = true; } else if (quadInfo->velocity) QuadCanDriftStart = false; if (!(TrInput & QUAD_IN_DRIFT)) QuadDriftStarting = false; if (!QuadDriftStarting) { if (quadInfo->revs > 0x10) { quadInfo->velocity += (quadInfo->revs / 16); quadInfo->revs -= (quadInfo->revs / 8); } else quadInfo->revs = 0; } if (quad->pos.yPos >= (height - STEP_SIZE)) { if (TrInput & IN_LOOK && !quadInfo->velocity) { LookUpDown(); } // Driving forward. if (quadInfo->velocity > 0) { if (TrInput & QUAD_IN_DRIFT && !QuadDriftStarting && quadInfo->velocity > MIN_DRIFT_SPEED) { if (TrInput & QUAD_IN_LEFT) { quadInfo->turnRate -= QUAD_DRIFT_TURN_RATE; if (quadInfo->turnRate < -QUAD_DRIFT_TURN_MAX) quadInfo->turnRate = -QUAD_DRIFT_TURN_MAX; } else if (TrInput & QUAD_IN_RIGHT) { quadInfo->turnRate += QUAD_DRIFT_TURN_RATE; if (quadInfo->turnRate > QUAD_DRIFT_TURN_MAX) quadInfo->turnRate = QUAD_DRIFT_TURN_MAX; } } else { if (TrInput & QUAD_IN_LEFT) { quadInfo->turnRate -= QUAD_TURN_RATE; if (quadInfo->turnRate < -QUAD_TURN_MAX) quadInfo->turnRate = -QUAD_TURN_MAX; } else if (TrInput & QUAD_IN_RIGHT) { quadInfo->turnRate += QUAD_TURN_RATE; if (quadInfo->turnRate > QUAD_TURN_MAX) quadInfo->turnRate = QUAD_TURN_MAX; } } } // Driving back. else if (quadInfo->velocity < 0) { if (TrInput & QUAD_IN_DRIFT && !QuadDriftStarting && quadInfo->velocity < (-MIN_DRIFT_SPEED + 0x800)) { if (TrInput & QUAD_IN_LEFT) { quadInfo->turnRate -= QUAD_DRIFT_TURN_RATE; if (quadInfo->turnRate < -QUAD_DRIFT_TURN_MAX) quadInfo->turnRate = -QUAD_DRIFT_TURN_MAX; } else if (TrInput & QUAD_IN_RIGHT) { quadInfo->turnRate += QUAD_DRIFT_TURN_RATE; if (quadInfo->turnRate > QUAD_DRIFT_TURN_MAX) quadInfo->turnRate = QUAD_DRIFT_TURN_MAX; } } else { if (TrInput & QUAD_IN_RIGHT) { quadInfo->turnRate -= QUAD_TURN_RATE; if (quadInfo->turnRate < -QUAD_TURN_MAX) quadInfo->turnRate = -QUAD_TURN_MAX; } else if (TrInput & QUAD_IN_LEFT) { quadInfo->turnRate += QUAD_TURN_RATE; if (quadInfo->turnRate > QUAD_TURN_MAX) quadInfo->turnRate = QUAD_TURN_MAX; } } } // Driving back / braking. if (TrInput & QUAD_IN_BRAKE) { if (TrInput & QUAD_IN_DRIFT && (QuadCanDriftStart || QuadDriftStarting)) { QuadDriftStarting = true; quadInfo->revs -= 0x200; if (quadInfo->revs < MAX_BACK) quadInfo->revs = MAX_BACK; } else if (quadInfo->velocity > 0) quadInfo->velocity -= BRAKE; else { if (quadInfo->velocity > MAX_BACK) quadInfo->velocity += REVERSE_ACC; } } else if (TrInput & QUAD_IN_ACCELERATE) { if (TrInput & QUAD_IN_DRIFT && (QuadCanDriftStart || QuadDriftStarting)) { QuadDriftStarting = true; quadInfo->revs += 0x200; if (quadInfo->revs >= MAX_VELOCITY) quadInfo->revs = MAX_VELOCITY; } else if (quadInfo->velocity < MAX_VELOCITY) { if (quadInfo->velocity < 0x4000) quadInfo->velocity += (8 + (0x4000 + 0x800 - quadInfo->velocity) / 8); else if (quadInfo->velocity < 0x7000) quadInfo->velocity += (4 + (0x7000 + 0x800 - quadInfo->velocity) / 16); else if (quadInfo->velocity < MAX_VELOCITY) quadInfo->velocity += (2 + (MAX_VELOCITY - quadInfo->velocity) / 8); } else quadInfo->velocity = MAX_VELOCITY; quadInfo->velocity -= abs(quad->pos.yRot - quadInfo->momentumAngle) / 64; } else if (quadInfo->velocity > 0x0100) quadInfo->velocity -= 0x0100; else if (quadInfo->velocity < -0x0100) quadInfo->velocity += 0x0100; else quadInfo->velocity = 0; if (!(TrInput & (QUAD_IN_ACCELERATE | QUAD_IN_BRAKE)) && QuadDriftStarting && quadInfo->revs) { if (quadInfo->revs > 0x8) quadInfo->revs -= quadInfo->revs / 8; else quadInfo->revs = 0; } quad->speed = quadInfo->velocity / 256; if (quadInfo->engineRevs > 0x7000) quadInfo->engineRevs = -0x2000; int revs = 0; if (quadInfo->velocity < 0) revs = abs(quadInfo->velocity / 2); else if (quadInfo->velocity < 0x7000) revs = -0x2000 + (quadInfo->velocity * (0x6800 - -0x2000)) / 0x7000; else if (quadInfo->velocity <= MAX_VELOCITY) revs = -0x2800 + ((quadInfo->velocity - 0x7000) * (0x7000 - -0x2800)) / (MAX_VELOCITY - 0x7000); revs += abs(quadInfo->revs); quadInfo->engineRevs += (revs - quadInfo->engineRevs) / 8; } else { if (quadInfo->engineRevs < 0xA000) quadInfo->engineRevs += (0xA000 - quadInfo->engineRevs) / 8; } *pitch = quadInfo->engineRevs; return drive; } void QuadBikeCollision(short itemNumber, ITEM_INFO* lara, COLL_INFO* coll) { LaraInfo*& laraInfo = lara->data; ITEM_INFO* quad = &g_Level.Items[itemNumber]; auto quadInfo = (QUAD_INFO*)quad->data; if (lara->hitPoints < 0 || laraInfo->Vehicle != NO_ITEM) return; if (GetOnQuadBike(lara, &g_Level.Items[itemNumber], coll)) { short ang; laraInfo->Vehicle = itemNumber; if (laraInfo->gunType == WEAPON_FLARE) { CreateFlare(lara, ID_FLARE_ITEM, 0); undraw_flare_meshes(lara); laraInfo->flareControlLeft = 0; laraInfo->requestGunType = laraInfo->gunType = WEAPON_NONE; } laraInfo->gunStatus = LG_HANDS_BUSY; ang = phd_atan(quad->pos.zPos - lara->pos.zPos, quad->pos.xPos - lara->pos.xPos); ang -= quad->pos.yRot; if (ang > -ANGLE(45.0f) && ang < ANGLE(135.0f)) { lara->animNumber = Objects[ID_QUAD_LARA_ANIMS].animIndex + QUAD_ANIM_MOUNT_LEFT; lara->currentAnimState = lara->goalAnimState = QUAD_STATE_MOUNT_LEFT; } else { lara->animNumber = Objects[ID_QUAD_LARA_ANIMS].animIndex + QUAD_ANIM_MOUNT_RIGHT; lara->currentAnimState = lara->goalAnimState = QUAD_STATE_MOUNT_RIGHT; } lara->frameNumber = g_Level.Anims[lara->animNumber].frameBase; quad->hitPoints = 1; lara->pos.xPos = quad->pos.xPos; lara->pos.yPos = quad->pos.yPos; lara->pos.zPos = quad->pos.zPos; lara->pos.yRot = quad->pos.yRot; laraInfo->headXrot = laraInfo->headYrot = 0; laraInfo->torsoXrot = laraInfo->torsoYrot = 0; laraInfo->hitDirection = -1; AnimateItem(lara); quadInfo->revs = 0; } else ObjectCollision(itemNumber, lara, coll); } static void TriggerQuadExhaustSmoke(int x, int y, int z, short angle, int speed, int moving) { SPARKS* spark = &Sparks[GetFreeSpark()]; spark->on = true; spark->sR = 0; spark->sG = 0; spark->sB = 0; spark->dR = 96; spark->dG = 96; spark->dB = 128; if (moving) { spark->dR = (spark->dR * speed) / 32; spark->dG = (spark->dG * speed) / 32; spark->dB = (spark->dB * speed) / 32; } spark->sLife = spark->life = (GetRandomControl() & 3) + 20 - (speed / 4096); if (spark->sLife < 9) spark->sLife = spark->life = 9; spark->colFadeSpeed = 4; spark->fadeToBlack = 4; spark->extras = 0; spark->dynamic = -1; spark->x = x + ((GetRandomControl() & 15) - 8); spark->y = y + ((GetRandomControl() & 15) - 8); spark->z = z + ((GetRandomControl() & 15) - 8); int zv = speed * phd_cos(angle) / 4; int xv = speed * phd_sin(angle) / 4; spark->xVel = xv + ((GetRandomControl() & 255) - 128); spark->yVel = -(GetRandomControl() & 7) - 8; spark->zVel = zv + ((GetRandomControl() & 255) - 128); spark->friction = 4; if (GetRandomControl() & 1) { spark->flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; spark->rotAng = GetRandomControl() & 4095; if (GetRandomControl() & 1) spark->rotAdd = -(GetRandomControl() & 7) - 24; else spark->rotAdd = (GetRandomControl() & 7) + 24; } else spark->flags = SP_SCALE | SP_DEF | SP_EXPDEF; spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex; spark->scalar = 2; spark->gravity = -(GetRandomControl() & 3) - 4; spark->maxYvel = -(GetRandomControl() & 7) - 8; int size = (GetRandomControl() & 7) + 64 + (speed / 128); spark->dSize = size; spark->size = spark->sSize = size / 2; } int QuadBikeControl(ITEM_INFO* lara, COLL_INFO* coll) { LaraInfo*& laraInfo = lara->data; ITEM_INFO* quad = &g_Level.Items[laraInfo->Vehicle]; auto quadInfo = (QUAD_INFO*)quad->data; short xRot, zRot, rotadd; int pitch, dead = 0; GAME_VECTOR oldpos; oldpos.x = quad->pos.xPos; oldpos.y = quad->pos.yPos; oldpos.z = quad->pos.zPos; oldpos.roomNumber = quad->roomNumber; bool collide = QuadDynamics(lara, quad); short roomNumber = quad->roomNumber; FLOOR_INFO* floor = GetFloor(quad->pos.xPos, quad->pos.yPos, quad->pos.zPos, &roomNumber); auto height = GetFloorHeight(floor, quad->pos.xPos, quad->pos.yPos, quad->pos.zPos); auto ceiling = GetCeiling(floor, quad->pos.xPos, quad->pos.yPos, quad->pos.zPos); PHD_VECTOR frontLeft; PHD_VECTOR frontRight; auto floorHeightLeft = TestQuadHeight(quad, QUAD_FRONT, -QUAD_SIDE, &frontLeft); auto floorHeightRight = TestQuadHeight(quad, QUAD_FRONT, QUAD_SIDE, &frontRight); TestTriggers(quad, false); if (lara->hitPoints <= 0) { TrInput &= ~(IN_LEFT | IN_RIGHT | IN_BACK | IN_FORWARD); dead = 1; } int drive = -1; if (quadInfo->flags) collide = false; else { switch (lara->currentAnimState) { case QUAD_STATE_MOUNT_LEFT: case QUAD_STATE_MOUNT_RIGHT: case QUAD_STATE_DISMOUNT_LEFT: case QUAD_STATE_DISMOUNT_RIGHT: drive = -1; collide = false; break; default: drive = QuadUserControl(quad, height, &pitch); break; } } if (quadInfo->velocity || quadInfo->revs) { int absvel = abs(quadInfo->velocity) + 1; // unused? quadInfo->pitch = pitch; if (quadInfo->pitch < -0x8000) quadInfo->pitch = -0x8000; else if (quadInfo->pitch > 0xA000) quadInfo->pitch = 0xA000; SoundEffect(SFX_TR3_QUAD_MOVE, &quad->pos, 0, 0.5f + (float)abs(quadInfo->pitch) / (float)MAX_VELOCITY); } else { if (drive != -1) SoundEffect(SFX_TR3_QUAD_IDLE, &quad->pos, 0); quadInfo->pitch = 0; } quad->floor = height; rotadd = quadInfo->velocity / 4; quadInfo->rearRot -= rotadd; quadInfo->rearRot -= (quadInfo->revs / 8); quadInfo->frontRot -= rotadd; quadInfo->leftFallspeed = DoQuadDynamics(floorHeightLeft, quadInfo->leftFallspeed, (int*)&frontLeft.y); quadInfo->rightFallspeed = DoQuadDynamics(floorHeightRight, quadInfo->rightFallspeed, (int*)&frontRight.y); quad->fallspeed = DoQuadDynamics(height, quad->fallspeed, (int*)&quad->pos.yPos); height = (frontLeft.y + frontRight.y) / 2; xRot = phd_atan(QUAD_FRONT, quad->pos.yPos - height); zRot = phd_atan(QUAD_SIDE, height - frontLeft.y); quad->pos.xRot += ((xRot - quad->pos.xRot) / 2); quad->pos.zRot += ((zRot - quad->pos.zRot) / 2); if (!(quadInfo->flags & QUAD_FLAG_DEAD)) { if (roomNumber != quad->roomNumber) { ItemNewRoom(laraInfo->Vehicle, roomNumber); ItemNewRoom(laraInfo->itemNumber, roomNumber); } lara->pos.xPos = quad->pos.xPos; lara->pos.yPos = quad->pos.yPos; lara->pos.zPos = quad->pos.zPos; lara->pos.xRot = quad->pos.xRot; lara->pos.yRot = quad->pos.yRot; lara->pos.zRot = quad->pos.zRot; AnimateQuadBike(lara, quad, collide, dead); AnimateItem(lara); quad->animNumber = Objects[ID_QUAD].animIndex + (lara->animNumber - Objects[ID_QUAD_LARA_ANIMS].animIndex); quad->frameNumber = g_Level.Anims[quad->animNumber].frameBase + (lara->frameNumber - g_Level.Anims[lara->animNumber].frameBase); Camera.targetElevation = -ANGLE(30.0f); if (quadInfo->flags & QUAD_FLAG_IS_FALLING) { if (quad->pos.yPos == quad->floor) { ExplodingDeath(laraInfo->itemNumber, 0xffffffff, 1); lara->hitPoints = 0; lara->flags |= ONESHOT; QuadbikeExplode(lara, quad); return 0; } } } if (lara->currentAnimState != QUAD_STATE_MOUNT_RIGHT && lara->currentAnimState != QUAD_STATE_MOUNT_LEFT && lara->currentAnimState != QUAD_STATE_DISMOUNT_RIGHT && lara->currentAnimState != QUAD_STATE_DISMOUNT_LEFT) { PHD_VECTOR pos; int speed = 0; short angle = 0; for (int i = 0; i < 2; i++) { pos.x = quadEffectsPositions[i].x; pos.y = quadEffectsPositions[i].y; pos.z = quadEffectsPositions[i].z; GetJointAbsPosition(quad, &pos, quadEffectsPositions[i].meshNum); angle = quad->pos.yRot + ((i == 0) ? 0x9000 : 0x7000); if (quad->speed > 32) { if (quad->speed < 64) { speed = 64 - quad->speed; TriggerQuadExhaustSmoke(pos.x, pos.y, pos.z, angle, speed, 1); } } else { if (QuadSmokeStart < 16) { speed = ((QuadSmokeStart * 2) + (GetRandomControl() & 7) + (GetRandomControl() & 16)) * 128; QuadSmokeStart++; } else if (QuadDriftStarting) speed = (abs(quadInfo->revs) * 2) + ((GetRandomControl() & 7) * 128); else if ((GetRandomControl() & 3) == 0) speed = ((GetRandomControl() & 15) + (GetRandomControl() & 16)) * 128; else speed = 0; TriggerQuadExhaustSmoke(pos.x, pos.y, pos.z, angle, speed, 0); } } } else QuadSmokeStart = 0; return QuadCheckGetOff(lara, quad); }