#include "framework.h" #include "boat.h" #include "lara.h" #include "items.h" #include "collide.h" #include "sphere.h" #include "camera.h" #include "setup.h" #include "level.h" #include "input.h" #include "animation.h" #include "Sound/sound.h" #include "effects/effects.h" #include "particle/SimpleParticle.h" #include "boat_info.h" enum SpeedBoatState { SBOAT_STATE_MOUNT, SBOAT_STATE_IDLE, SBOAT_STATE_MOVING, SBOAT_STATE_DISMOUNT_RIGHT, SBOAT_STATE_DISMOUNT_LEFT, SBOAT_STATE_HIT, SBOAT_STATE_FALL, SBOAT_STATE_TURN_RIGHT, SBOAT_STATE_DEATH, SBOAT_STATE_TURN_LEFT }; enum SpeedBoatAnim { SBOAT_ANIM_MOUNT_LEFT = 0, SBOAT_ANIM_IDLE = 1, // ? SBOAT_ANIM_FORWARD = 2, // ? SBOAT_ANIM_DISMOUNT_LEFT = 5, SBOAT_ANIM_MOUNT_JUMP = 6, SBOAT_ANIM_DISMOUNT_RIGHT = 7, SBOAT_ANIM_MOUNT_RIGHT = 8, SBOAT_ANIM_HIT_LEFT = 11, SBOAT_ANIM_HIT_RIGHT = 12, SBOAT_ANIM_HIT_FRONT = 13, SBOAT_ANIM_HIT_BACK = 14, SBOAT_ANIM_LEAP_START = 15, SBOAT_ANIM_LEAP = 16, SBOAT_ANIM_LEAP_END = 17, SBOAT_ANIM_DEATH = 18 }; #define BOAT_UNDO_TURN (ONE_DEGREE / 4) #define BOAT_TURN (ONE_DEGREE / 8) #define BOAT_MAX_TURN ANGLE(4.0f) #define BOAT_MAX_SPEED 110 #define BOAT_SLOW_SPEED (BOAT_MAX_SPEED / 3) #define BOAT_FAST_SPEED (BOAT_MAX_SPEED + 75) #define BOAT_MIN_SPEED 20 #define BOAT_ACCELERATION 5 #define BOAT_BRAKE 5 #define BOAT_SLOWDOWN 1 #define BOAT_REVERSE -2 // -5 #define BOAT_MAX_BACK -20 #define BOAT_MAX_KICK -80 #define BOAT_SLIP 10 #define BOAT_SIDE_SLIP 30 #define BOAT_FRONT 750 #define BOAT_SIDE 300 #define BOAT_RADIUS 500 #define BOAT_SNOW 500 #define BOAT_MAX_HEIGHT CLICK(1) #define DISMOUNT_DIST WALL_SIZE #define BOAT_WAKE 700 #define BOAT_SOUND_CEILING (WALL_SIZE * 5) #define BOAT_TIP (BOAT_FRONT + 250) #define SBOAT_IN_ACCELERATE IN_FORWARD #define SBOAT_IN_REVERSE IN_BACK #define SBOAT_IN_LEFT (IN_LEFT | IN_LSTEP) #define SBOAT_IN_RIGHT (IN_RIGHT | IN_RSTEP) #define SBOAT_IN_SPEED (IN_ACTION | IN_SPRINT) #define SBOAT_IN_SLOW IN_WALK #define SBOAT_IN_DISMOUNT (IN_JUMP | IN_ROLL) void InitialiseSpeedBoat(short itemNum) { ITEM_INFO* sBoatItem = &g_Level.Items[itemNum]; sBoatItem->data = BOAT_INFO(); BOAT_INFO* sBoatInfo = sBoatItem->data; sBoatInfo->boatTurn = 0; sBoatInfo->leftFallspeed = 0; sBoatInfo->rightFallspeed = 0; sBoatInfo->tiltAngle = 0; sBoatInfo->extraRotation = 0; sBoatInfo->water = 0; sBoatInfo->pitch = 0; } void DoBoatWakeEffect(ITEM_INFO* sBoatItem) { SetupRipple(sBoatItem->pos.xPos, sBoatItem->pos.yPos, sBoatItem->pos.zPos, 512, RIPPLE_FLAG_RAND_POS, Objects[1368].meshIndex, TO_RAD(sBoatItem->pos.yRot)); TEN::Effects::TriggerSpeedboatFoam(sBoatItem); // OLD WAKE EFFECT /*int c = phd_cos(boat->pos.yRot); int s = phd_sin(boat->pos.yRot); int c = phd_cos(boat->pos.yRot); for (int i = 0; i < 3; i++) { int h = BOAT_WAKE; int w = (1 - i) * BOAT_SIDE; int x = boat->pos.xPos + (-(c * w) - (h * s) >> W2V_SHIFT); int y = boat->pos.yPos; int z = boat->pos.zPos + ((s * w) - (h * c) >> W2V_SHIFT); SPARKS* spark = &Sparks[GetFreeSpark()]; spark->on = 1; spark->sR = 64; spark->sG = 64; spark->sB = 64; spark->dR = 64; spark->dG = 64; spark->dB = 64; spark->colFadeSpeed = 1; spark->transType = TransTypeEnum::COLADD; spark->life = spark->sLife = (GetRandomControl() & 3) + 6; spark->fadeToBlack = spark->life - 4; spark->x = (BOAT_SIDE * phd_sin(boat->pos.yRot) >> W2V_SHIFT) + (GetRandomControl() & 128) + x - 8; spark->y = (GetRandomControl() & 0xF) + y - 8; spark->z = (BOAT_SIDE * phd_cos(boat->pos.yRot) >> W2V_SHIFT) + (GetRandomControl() & 128) + z - 8; spark->xVel = 0; spark->zVel = 0; spark->friction = 0; spark->flags = 538; spark->yVel = (GetRandomControl() & 0x7F) - 256; spark->rotAng = GetRandomControl() & 0xFFF; spark->scalar = 3; spark->maxYvel = 0; spark->rotAdd = (GetRandomControl() & 0x1F) - 16; spark->gravity = -spark->yVel >> 2; spark->sSize = spark->size = ((GetRandomControl() & 3) + 16) * 16; spark->dSize = 2 * spark->size; spark = &Sparks[GetFreeSpark()]; spark->on = 1; spark->sR = 64; spark->sG = 64; spark->sB = 64; spark->dR = 64; spark->dG = 64; spark->dB = 64; spark->colFadeSpeed = 1; spark->transType = TransTypeEnum::COLADD; spark->life = spark->sLife = (GetRandomControl() & 3) + 6; spark->fadeToBlack = spark->life - 4; spark->x = (BOAT_SIDE * phd_sin(boat->pos.yRot) >> W2V_SHIFT) + (GetRandomControl() & 128) + x - 8; spark->y = (GetRandomControl() & 0xF) + y - 8; spark->z = (BOAT_SIDE * phd_cos(boat->pos.yRot) >> W2V_SHIFT) + (GetRandomControl() & 128) + z - 8; spark->xVel = 0; spark->zVel = 0; spark->friction = 0; spark->flags = 538; spark->yVel = (GetRandomControl() & 0x7F) - 256; spark->rotAng = GetRandomControl() & 0xFFF; spark->scalar = 3; spark->maxYvel = 0; spark->rotAdd = (GetRandomControl() & 0x1F) - 16; spark->gravity = -spark->yVel >> 2; spark->sSize = spark->size = ((GetRandomControl() & 3) + 16) * 4; spark->dSize = 2 * spark->size; spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + 17; }*/ } BoatMountType GetSpeedBoatMountType(ITEM_INFO* laraItem, ITEM_INFO* sBoatItem, COLL_INFO* coll) { LaraInfo*& laraInfo = laraItem->data; BoatMountType mountType = BoatMountType::None; if (laraInfo->gunStatus != LG_HANDS_FREE) return mountType; if (!TestBoundsCollide(sBoatItem, laraItem, coll->Setup.Radius)) return mountType; if (!TestCollision(sBoatItem, laraItem)) return mountType; int dist = (laraItem->pos.zPos - sBoatItem->pos.zPos) * phd_cos(-sBoatItem->pos.yRot) - (laraItem->pos.xPos - sBoatItem->pos.xPos) * phd_sin(-sBoatItem->pos.yRot); if (dist > 200) return mountType; short rot = sBoatItem->pos.yRot - laraItem->pos.yRot; if (laraInfo->waterStatus == LW_SURFACE || laraInfo->waterStatus == LW_WADE) { if (!(TrInput & IN_ACTION) || laraItem->gravityStatus || sBoatItem->speed) return mountType; if (rot > ANGLE(45.0f) && rot < ANGLE(135.0f)) mountType = BoatMountType::WaterRight; else if (rot > -ANGLE(135.0f) && rot < -ANGLE(45.0f)) mountType = BoatMountType::WaterLeft; } else if (laraInfo->waterStatus == LW_ABOVE_WATER) { if (laraItem->fallspeed > 0) { if (rot > -ANGLE(135.0f) && rot < ANGLE(135.0f) && laraItem->pos.yPos > sBoatItem->pos.yPos) { mountType = BoatMountType::Jump; } } else if (laraItem->fallspeed == 0) { if (rot > -ANGLE(135.0f) && rot < ANGLE(135.0f)) { if (laraItem->pos.xPos == sBoatItem->pos.xPos && laraItem->pos.yPos == sBoatItem->pos.yPos && laraItem->pos.zPos == sBoatItem->pos.zPos) { mountType = BoatMountType::StartPosition; } else mountType = BoatMountType::Jump; } } } return mountType; } bool TestSpeedBoatDismount(ITEM_INFO* sBoatItem, int direction) { short angle; if (direction < 0) angle = sBoatItem->pos.yRot - ANGLE(90.0f); else angle = sBoatItem->pos.yRot + ANGLE(90.0f); int x = sBoatItem->pos.xPos + DISMOUNT_DIST * phd_sin(angle); int y = sBoatItem->pos.yPos; int z = sBoatItem->pos.zPos + DISMOUNT_DIST * phd_cos(angle); auto probe = GetCollisionResult(x, y, z, sBoatItem->roomNumber); if ((probe.Position.Floor - sBoatItem->pos.yPos) < -CLICK(2)) return false; if (probe.Position.Slope || probe.Position.Floor == NO_HEIGHT) { return false; } if ((probe.Position.Floor - probe.Position.Ceiling) < LARA_HEIGHT || (probe.Position.Ceiling - sBoatItem->pos.yPos) > -LARA_HEIGHT) { return false; } return true; } void DoSpeedBoatDismount(ITEM_INFO* laraItem, ITEM_INFO* sBoatItem) { LaraInfo*& laraInfo = laraItem->data; if ((laraItem->currentAnimState == SBOAT_STATE_DISMOUNT_LEFT || laraItem->currentAnimState == SBOAT_STATE_DISMOUNT_RIGHT) && TestLastFrame(laraItem, laraItem->animNumber)) { if (laraItem->currentAnimState == SBOAT_STATE_DISMOUNT_LEFT) laraItem->pos.yRot -= ANGLE(90.0f); else if(laraItem->currentAnimState == SBOAT_STATE_DISMOUNT_RIGHT) laraItem->pos.yRot += ANGLE(90.0f); SetAnimation(laraItem, LA_JUMP_FORWARD); laraItem->gravityStatus = true; laraItem->fallspeed = -50; laraItem->speed = 40; laraItem->pos.xRot = 0; laraItem->pos.zRot = 0; laraInfo->Vehicle = NO_ITEM; int x = laraItem->pos.xPos + 360 * phd_sin(laraItem->pos.yRot); int y = laraItem->pos.yPos - 90; int z = laraItem->pos.zPos + 360 * phd_cos(laraItem->pos.yRot); auto probe = GetCollisionResult(x, y, z, laraItem->roomNumber); if (probe.Position.Floor >= y - STEP_SIZE) { laraItem->pos.xPos = x; laraItem->pos.zPos = z; if (probe.RoomNumber != laraItem->roomNumber) ItemNewRoom(laraInfo->itemNumber, probe.RoomNumber); } laraItem->pos.yPos = y; sBoatItem->animNumber = Objects[ID_SPEEDBOAT].animIndex; sBoatItem->frameNumber = g_Level.Anims[sBoatItem->animNumber].frameBase; } } int SpeedBoatTestWaterHeight(ITEM_INFO* sBoatItem, int zOff, int xOff, PHD_VECTOR* pos) { float s = phd_sin(sBoatItem->pos.yRot); float c = phd_cos(sBoatItem->pos.yRot); pos->x = sBoatItem->pos.xPos + zOff * s + xOff * c; pos->y = sBoatItem->pos.yPos - zOff * phd_sin(sBoatItem->pos.xRot) + xOff * phd_sin(sBoatItem->pos.zRot); pos->z = sBoatItem->pos.zPos + zOff * c - xOff * s; auto probe = GetCollisionResult(pos->x, pos->y, pos->z, sBoatItem->roomNumber); auto height = GetWaterHeight(pos->x, pos->y, pos->z, probe.RoomNumber); if (height == NO_HEIGHT) { height = probe.Position.Floor; if (height == NO_HEIGHT) return height; } return height - 5; } void SpeedBoatDoBoatShift(ITEM_INFO* sBoatItem, int itemNum) { ITEM_INFO* item; int x, z, distance, radius; auto item_number = g_Level.Rooms[sBoatItem->roomNumber].itemNumber; while (item_number != NO_ITEM) { item = &g_Level.Items[item_number]; if (item->objectNumber == ID_SPEEDBOAT && item_number != itemNum && Lara.Vehicle != item_number) { x = item->pos.xPos - sBoatItem->pos.xPos; z = item->pos.zPos - sBoatItem->pos.zPos; distance = SQUARE(x) + SQUARE(z); radius = SQUARE(BOAT_RADIUS * 2); if (distance < radius) { sBoatItem->pos.xPos = item->pos.xPos - x * radius / distance; sBoatItem->pos.zPos = item->pos.zPos - z * radius / distance; } return; } // TODO: mine and gondola item_number = item->nextItem; } } short SpeedBoatDoShift(ITEM_INFO* skidoo, PHD_VECTOR* pos, PHD_VECTOR* old) { int x = pos->x / SECTOR(1); int z = pos->z / SECTOR(1); int xOld = old->x / SECTOR(1); int zOld = old->z / SECTOR(1); int shiftX = pos->x & (WALL_SIZE - 1); int shiftZ = pos->z & (WALL_SIZE - 1); if (x == xOld) { if (z == zOld) { skidoo->pos.zPos += (old->z - pos->z); skidoo->pos.xPos += (old->x - pos->x); } else if (z > zOld) { skidoo->pos.zPos -= shiftZ + 1; return (pos->x - skidoo->pos.xPos); } else { skidoo->pos.zPos += WALL_SIZE - shiftZ; return (skidoo->pos.xPos - pos->x); } } else if (z == zOld) { if (x > xOld) { skidoo->pos.xPos -= shiftX + 1; return (skidoo->pos.zPos - pos->z); } else { skidoo->pos.xPos += WALL_SIZE - shiftX; return (pos->z - skidoo->pos.zPos); } } else { x = 0; z = 0; short roomNumber = skidoo->roomNumber; FLOOR_INFO* floor = GetFloor(old->x, pos->y, pos->z, &roomNumber); int height = GetFloorHeight(floor, old->x, pos->y, pos->z); if (height < old->y - STEP_SIZE) { if (pos->z > old->z) z = -shiftZ - 1; else z = WALL_SIZE - shiftZ; } roomNumber = skidoo->roomNumber; floor = GetFloor(pos->x, pos->y, old->z, &roomNumber); height = GetFloorHeight(floor, pos->x, pos->y, old->z); if (height < old->y - STEP_SIZE) { if (pos->x > old->x) x = -shiftX - 1; else x = WALL_SIZE - shiftX; } if (x && z) { skidoo->pos.zPos += z; skidoo->pos.xPos += x; } else if (z) { skidoo->pos.zPos += z; if (z > 0) return (skidoo->pos.xPos - pos->x); else return (pos->x - skidoo->pos.xPos); } else if (x) { skidoo->pos.xPos += x; if (x > 0) return (pos->z - skidoo->pos.zPos); else return (skidoo->pos.zPos - pos->z); } else { skidoo->pos.zPos += (old->z - pos->z); skidoo->pos.xPos += (old->x - pos->x); } } return 0; } int GetSpeedBoatHitAnim(ITEM_INFO* sBoatItem, PHD_VECTOR* moved) { moved->x = sBoatItem->pos.xPos - moved->x; moved->z = sBoatItem->pos.zPos - moved->z; if (moved->x || moved->z) { float s = phd_sin(sBoatItem->pos.yRot); float c = phd_cos(sBoatItem->pos.yRot); int front = moved->z * c + moved->x * s; int side = -moved->z * s + moved->x * c; if (abs(front) > abs(side)) { if (front > 0) return SBOAT_ANIM_HIT_BACK; else return SBOAT_ANIM_HIT_FRONT; } else { if (side > 0) return SBOAT_ANIM_HIT_LEFT; else return SBOAT_ANIM_HIT_RIGHT; } } return 0; } int SpeedBoatDoBoatDynamics(int height, int fallspeed, int* y) { if (height > * y) { *y += fallspeed; if (*y > height) { *y = height; fallspeed = 0; } else fallspeed += GRAVITY; } else { fallspeed += ((height - *y - fallspeed) / 8); if (fallspeed < BOAT_MAX_BACK) fallspeed = BOAT_MAX_BACK; if (*y > height) *y = height; } return fallspeed; } int SpeedBoatDynamics(ITEM_INFO* laraItem, short itemNum) { LaraInfo*& laraInfo = laraItem->data; auto sBoatItem = &g_Level.Items[itemNum]; BOAT_INFO* sBoatInfo = sBoatItem->data; sBoatItem->pos.zRot -= sBoatInfo->tiltAngle; PHD_VECTOR old, fl_old, fr_old, bl_old, br_old, f_old; auto hfl_old = SpeedBoatTestWaterHeight(sBoatItem, BOAT_FRONT, -BOAT_SIDE, &fl_old); auto hfr_old = SpeedBoatTestWaterHeight(sBoatItem, BOAT_FRONT, BOAT_SIDE, &fr_old); auto hbl_old = SpeedBoatTestWaterHeight(sBoatItem, -BOAT_FRONT, -BOAT_SIDE, &bl_old); auto hbr_old = SpeedBoatTestWaterHeight(sBoatItem, -BOAT_FRONT, BOAT_SIDE, &br_old); auto hf_old = SpeedBoatTestWaterHeight(sBoatItem, BOAT_TIP, 0, &f_old); old.x = sBoatItem->pos.xPos; old.y = sBoatItem->pos.yPos; old.z = sBoatItem->pos.zPos; if (bl_old.y > hbl_old) bl_old.y = hbl_old; if (br_old.y > hbr_old) br_old.y = hbr_old; if (fl_old.y > hfl_old) fl_old.y = hfl_old; if (fr_old.y > hfr_old) fr_old.y = hfr_old; if (f_old.y > hf_old) f_old.y = hf_old; sBoatItem->pos.yRot += sBoatInfo->boatTurn + sBoatInfo->extraRotation; sBoatInfo->tiltAngle = sBoatInfo->boatTurn * 6; sBoatItem->pos.xPos += sBoatItem->speed * phd_sin(sBoatItem->pos.yRot); sBoatItem->pos.zPos += sBoatItem->speed * phd_cos(sBoatItem->pos.yRot); int slip = BOAT_SIDE_SLIP * phd_sin(sBoatItem->pos.zRot); if (!slip && sBoatItem->pos.zRot) slip = (sBoatItem->pos.zRot > 0) ? 1 : -1; sBoatItem->pos.xPos += slip * phd_sin(sBoatItem->pos.yRot); sBoatItem->pos.zPos -= slip * phd_cos(sBoatItem->pos.yRot); slip = BOAT_SLIP * phd_sin(sBoatItem->pos.xRot); if (!slip && sBoatItem->pos.xRot) slip = (sBoatItem->pos.xRot > 0) ? 1 : -1; sBoatItem->pos.xPos -= slip * phd_sin(sBoatItem->pos.yRot); sBoatItem->pos.zPos -= slip * phd_cos(sBoatItem->pos.yRot); auto moved = PHD_VECTOR(sBoatItem->pos.xPos, 0, sBoatItem->pos.zPos); SpeedBoatDoBoatShift(sBoatItem, itemNum); PHD_VECTOR fl, fr, br, bl, f; short rot = 0; auto heightBackLeft = SpeedBoatTestWaterHeight(sBoatItem, -BOAT_FRONT, -BOAT_SIDE, &bl); if (heightBackLeft < bl_old.y - CLICK(0.5f)) rot = SpeedBoatDoShift(sBoatItem, &bl, &bl_old); auto heightBackRight = SpeedBoatTestWaterHeight(sBoatItem, -BOAT_FRONT, BOAT_SIDE, &br); if (heightBackRight < br_old.y - CLICK(0.5f)) rot += SpeedBoatDoShift(sBoatItem, &br, &br_old); auto heightFrontLeft = SpeedBoatTestWaterHeight(sBoatItem, BOAT_FRONT, -BOAT_SIDE, &fl); if (heightFrontLeft < fl_old.y - CLICK(0.5f)) rot += SpeedBoatDoShift(sBoatItem, &fl, &fl_old); auto heightFrontRight = SpeedBoatTestWaterHeight(sBoatItem, BOAT_FRONT, BOAT_SIDE, &fr); if (heightFrontRight < fr_old.y - CLICK(0.5f)) rot += SpeedBoatDoShift(sBoatItem, &fr, &fr_old); int heightFront = 0; if (!slip) { heightFront = SpeedBoatTestWaterHeight(sBoatItem, BOAT_TIP, 0, &f); if (heightFront < f_old.y - CLICK(0.5f)) SpeedBoatDoShift(sBoatItem, &f, &f_old); } auto probe = GetCollisionResult(sBoatItem); auto height = GetWaterHeight(sBoatItem->pos.xPos, sBoatItem->pos.yPos - 5, sBoatItem->pos.zPos, probe.RoomNumber); if (height == NO_HEIGHT) height = GetFloorHeight(probe.Block, sBoatItem->pos.xPos, sBoatItem->pos.yPos - 5, sBoatItem->pos.zPos); if (height < sBoatItem->pos.yPos - CLICK(0.5f)) SpeedBoatDoShift(sBoatItem, (PHD_VECTOR*)&sBoatItem->pos, &old); sBoatInfo->extraRotation = rot; auto collide = GetSpeedBoatHitAnim(sBoatItem, &moved); int newspeed = 0; if (slip || collide) { newspeed = (sBoatItem->pos.zPos - old.z) * phd_cos(sBoatItem->pos.yRot) + (sBoatItem->pos.xPos - old.x) * phd_sin(sBoatItem->pos.yRot); if (laraInfo->Vehicle == itemNum && sBoatItem->speed > BOAT_MAX_SPEED + BOAT_ACCELERATION && newspeed < sBoatItem->speed - 10) { laraItem->hitPoints -= sBoatItem->speed; laraItem->hitStatus = 1; SoundEffect(SFX_TR4_LARA_INJURY, &laraItem->pos, 0); newspeed /= 2; sBoatItem->speed /= 2; } if (slip) { if (sBoatItem->speed <= BOAT_MAX_SPEED + 10) sBoatItem->speed = newspeed; } else { if (sBoatItem->speed > 0 && newspeed < sBoatItem->speed) sBoatItem->speed = newspeed; else if (sBoatItem->speed < 0 && newspeed > sBoatItem->speed) sBoatItem->speed = newspeed; } if (sBoatItem->speed < BOAT_MAX_BACK) sBoatItem->speed = BOAT_MAX_BACK; } return collide; } bool SpeedBoatUserControl(ITEM_INFO* sBoatItem) { BOAT_INFO* sBoatInfo = (BOAT_INFO*)sBoatItem->data; bool noTurn = true; int maxSpeed; if (sBoatItem->pos.yPos >= sBoatInfo->water - STEP_SIZE / 2 && sBoatInfo->water != NO_HEIGHT) { if (!(TrInput & SBOAT_IN_DISMOUNT) && !(TrInput & IN_LOOK) || sBoatItem->speed) { if (TrInput & SBOAT_IN_LEFT && !(TrInput & SBOAT_IN_REVERSE) || TrInput & SBOAT_IN_RIGHT && TrInput & SBOAT_IN_REVERSE) { if (sBoatInfo->boatTurn > 0) sBoatInfo->boatTurn -= BOAT_UNDO_TURN; else { sBoatInfo->boatTurn -= BOAT_TURN; if (sBoatInfo->boatTurn < -BOAT_MAX_TURN) sBoatInfo->boatTurn = -BOAT_MAX_TURN; } noTurn = false; } else if (TrInput & SBOAT_IN_RIGHT && !(TrInput & SBOAT_IN_REVERSE) || TrInput & SBOAT_IN_LEFT && TrInput & SBOAT_IN_REVERSE) { if (sBoatInfo->boatTurn < 0) sBoatInfo->boatTurn += BOAT_UNDO_TURN; else { sBoatInfo->boatTurn += BOAT_TURN; if (sBoatInfo->boatTurn > BOAT_MAX_TURN) sBoatInfo->boatTurn = BOAT_MAX_TURN; } noTurn = false; } if (TrInput & SBOAT_IN_REVERSE) { if (sBoatItem->speed > 0) sBoatItem->speed -= BOAT_BRAKE; else if (sBoatItem->speed > BOAT_MAX_BACK) sBoatItem->speed += BOAT_REVERSE; } else if (TrInput & SBOAT_IN_ACCELERATE) { if (TrInput & SBOAT_IN_SPEED) maxSpeed = BOAT_FAST_SPEED; else maxSpeed = (TrInput & SBOAT_IN_SLOW) ? BOAT_SLOW_SPEED : BOAT_MAX_SPEED; if (sBoatItem->speed < maxSpeed) sBoatItem->speed += BOAT_ACCELERATION / 2 + BOAT_ACCELERATION * sBoatItem->speed / (2 * maxSpeed); else if (sBoatItem->speed > maxSpeed + BOAT_SLOWDOWN) sBoatItem->speed -= BOAT_SLOWDOWN; } else if (TrInput & (SBOAT_IN_LEFT | SBOAT_IN_RIGHT) && sBoatItem->speed >= 0 && sBoatItem->speed < BOAT_MIN_SPEED) { if (!(TrInput & SBOAT_IN_DISMOUNT) && sBoatItem->speed == 0) sBoatItem->speed = BOAT_MIN_SPEED; } else if (sBoatItem->speed > BOAT_SLOWDOWN) sBoatItem->speed -= BOAT_SLOWDOWN; else sBoatItem->speed = 0; } else { if (TrInput & (SBOAT_IN_LEFT | SBOAT_IN_RIGHT) && sBoatItem->speed >= 0 && sBoatItem->speed < BOAT_MIN_SPEED) { if (sBoatItem->speed == 0 && !(TrInput & SBOAT_IN_DISMOUNT)) sBoatItem->speed = BOAT_MIN_SPEED; } else if (sBoatItem->speed > BOAT_SLOWDOWN) sBoatItem->speed -= BOAT_SLOWDOWN; else sBoatItem->speed = 0; if (TrInput & IN_LOOK && sBoatItem->speed == 0) LookUpDown(); } } return noTurn; } void SpeedBoatAnimation(ITEM_INFO* laraItem, ITEM_INFO* sBoatItem, int collide) { BOAT_INFO* sBoatInfo = sBoatItem->data; if (laraItem->hitPoints <= 0) { if (laraItem->currentAnimState != SBOAT_STATE_DEATH) { laraItem->animNumber = Objects[ID_SPEEDBOAT_LARA_ANIMS].animIndex + SBOAT_ANIM_DEATH; laraItem->frameNumber = g_Level.Anims[laraItem->animNumber].frameBase; laraItem->currentAnimState = laraItem->goalAnimState = SBOAT_STATE_DEATH; } } else if (sBoatItem->pos.yPos < sBoatInfo->water - CLICK(0.5f) && sBoatItem->fallspeed > 0) { if (laraItem->currentAnimState != SBOAT_STATE_FALL) { laraItem->animNumber = Objects[ID_SPEEDBOAT_LARA_ANIMS].animIndex + SBOAT_ANIM_LEAP_START; laraItem->frameNumber = g_Level.Anims[laraItem->animNumber].frameBase; laraItem->currentAnimState = laraItem->goalAnimState = SBOAT_STATE_FALL; } } else if (collide) { if (laraItem->currentAnimState != SBOAT_STATE_HIT) { laraItem->animNumber = (short)(Objects[ID_SPEEDBOAT_LARA_ANIMS].animIndex + collide); laraItem->frameNumber = g_Level.Anims[laraItem->animNumber].frameBase; laraItem->currentAnimState = laraItem->goalAnimState = SBOAT_STATE_HIT; } } else { switch (laraItem->currentAnimState) { case SBOAT_STATE_IDLE: if (TrInput & SBOAT_IN_DISMOUNT) { if (sBoatItem->speed == 0) { if (TrInput & SBOAT_IN_RIGHT && TestSpeedBoatDismount(sBoatItem, sBoatItem->pos.yRot + ANGLE(90.0f))) laraItem->goalAnimState = SBOAT_STATE_DISMOUNT_RIGHT; else if (TrInput & SBOAT_IN_LEFT && TestSpeedBoatDismount(sBoatItem, sBoatItem->pos.yRot - ANGLE(90.0f))) laraItem->goalAnimState = SBOAT_STATE_DISMOUNT_LEFT; } } if (sBoatItem->speed > 0) laraItem->goalAnimState = SBOAT_STATE_MOVING; break; case SBOAT_STATE_MOVING: if (TrInput & SBOAT_IN_DISMOUNT) { if (TrInput & SBOAT_IN_RIGHT) laraItem->goalAnimState = SBOAT_STATE_DISMOUNT_RIGHT; else if (TrInput & SBOAT_IN_RIGHT) laraItem->goalAnimState = SBOAT_STATE_DISMOUNT_LEFT; } else if (sBoatItem->speed <= 0) laraItem->goalAnimState = SBOAT_STATE_IDLE; break; case SBOAT_STATE_FALL: laraItem->goalAnimState = SBOAT_STATE_MOVING; break; //case BOAT_TURNR: if (sBoatItem->speed <= 0) laraItem->goalAnimState = SBOAT_STATE_IDLE; else if (!(TrInput & SBOAT_IN_RIGHT)) laraItem->goalAnimState = SBOAT_STATE_MOVING; break; case SBOAT_STATE_TURN_LEFT: if (sBoatItem->speed <= 0) laraItem->goalAnimState = SBOAT_STATE_IDLE; else if (!(TrInput & SBOAT_IN_LEFT)) laraItem->goalAnimState = SBOAT_STATE_MOVING; break; } } } void SpeedBoatSplash(ITEM_INFO* item, long fallspeed, long water) { //OLD SPLASH /* splash_setup.x = item->pos.x_pos; splash_setup.y = water; splash_setup.z = item->pos.z_pos; splash_setup.InnerXZoff = 16 << 2; splash_setup.InnerXZsize = 12 << 2; splash_setup.InnerYsize = -96 << 2; splash_setup.InnerXZvel = 0xa0; splash_setup.InnerYvel = -fallspeed << 7; splash_setup.InnerGravity = 0x80; splash_setup.InnerFriction = 7; splash_setup.MiddleXZoff = 24 << 2; splash_setup.MiddleXZsize = 24 << 2; splash_setup.MiddleYsize = -64 << 2; splash_setup.MiddleXZvel = 0xe0; splash_setup.MiddleYvel = -fallspeed << 6; splash_setup.MiddleGravity = 0x48; splash_setup.MiddleFriction = 8; splash_setup.OuterXZoff = 32 << 2; splash_setup.OuterXZsize = 32 << 2; splash_setup.OuterXZvel = 0x110; splash_setup.OuterFriction = 9; SetupSplash(&splash_setup); SplashCount = 16; */ } void SpeedBoatCollision(short itemNum, ITEM_INFO* laraItem, COLL_INFO* coll) { LaraInfo*& laraInfo = laraItem->data; if (laraItem->hitPoints < 0 || laraInfo->Vehicle != NO_ITEM) return; auto sBoatItem = &g_Level.Items[itemNum]; switch (GetSpeedBoatMountType(laraItem, sBoatItem, coll)) { case BoatMountType::None: coll->Setup.EnableObjectPush = true; ObjectCollision(itemNum, laraItem, coll); return; case BoatMountType::WaterLeft: laraItem->animNumber = Objects[ID_SPEEDBOAT_LARA_ANIMS].animIndex + SBOAT_ANIM_MOUNT_LEFT; break; case BoatMountType::WaterRight: laraItem->animNumber = Objects[ID_SPEEDBOAT_LARA_ANIMS].animIndex + SBOAT_ANIM_MOUNT_RIGHT; break; case BoatMountType::Jump: laraItem->animNumber = Objects[ID_SPEEDBOAT_LARA_ANIMS].animIndex + SBOAT_ANIM_MOUNT_JUMP; break; case BoatMountType::StartPosition: laraItem->animNumber = Objects[ID_SPEEDBOAT_LARA_ANIMS].animIndex + SBOAT_ANIM_IDLE; break; } laraInfo->waterStatus = LW_ABOVE_WATER; laraItem->pos.xPos = sBoatItem->pos.xPos; laraItem->pos.yPos = sBoatItem->pos.yPos - 5; laraItem->pos.zPos = sBoatItem->pos.zPos; laraItem->pos.yRot = sBoatItem->pos.yRot; laraItem->pos.xRot = 0; laraItem->pos.zRot = 0; laraItem->gravityStatus = false; laraItem->speed = 0; laraItem->fallspeed = 0; laraItem->frameNumber = g_Level.Anims[laraItem->animNumber].frameBase; laraItem->currentAnimState = SBOAT_STATE_MOUNT; laraItem->goalAnimState = SBOAT_STATE_MOUNT; if (laraItem->roomNumber != sBoatItem->roomNumber) ItemNewRoom(laraInfo->itemNumber, sBoatItem->roomNumber); AnimateItem(laraItem); if (g_Level.Items[itemNum].status != ITEM_ACTIVE) { AddActiveItem(itemNum); g_Level.Items[itemNum].status = ITEM_ACTIVE; } laraInfo->Vehicle = itemNum; } void SpeedBoatControl(short itemNum) { auto* laraItem = LaraItem; LaraInfo*& laraInfo = laraItem->data; auto sBoatItem = &g_Level.Items[itemNum]; BOAT_INFO* sBoatInfo = sBoatItem->data; PHD_VECTOR frontLeft, frontRight; bool noTurn = true; bool drive = false; int collide = SpeedBoatDynamics(laraItem, itemNum); int heightFrontLeft = SpeedBoatTestWaterHeight(sBoatItem, BOAT_FRONT, -BOAT_SIDE, &frontLeft); int heightFrontRight = SpeedBoatTestWaterHeight(sBoatItem, BOAT_FRONT, BOAT_SIDE, &frontRight); auto roomNum = sBoatItem->roomNumber; auto floor = GetFloor(sBoatItem->pos.xPos, sBoatItem->pos.yPos, sBoatItem->pos.zPos, &roomNum); auto height = GetFloorHeight(floor, sBoatItem->pos.xPos, sBoatItem->pos.yPos, sBoatItem->pos.zPos); auto ceiling = GetCeiling(floor, sBoatItem->pos.xPos, sBoatItem->pos.yPos, sBoatItem->pos.zPos); if (laraInfo->Vehicle == itemNum) { TestTriggers(sBoatItem, true); TestTriggers(sBoatItem, false); } auto water = GetWaterHeight(sBoatItem->pos.xPos, sBoatItem->pos.yPos, sBoatItem->pos.zPos, roomNum); sBoatInfo->water = water; if (laraInfo->Vehicle == itemNum && laraItem->hitPoints > 0) { switch (laraItem->currentAnimState) { case SBOAT_STATE_MOUNT: case SBOAT_STATE_DISMOUNT_RIGHT: case SBOAT_STATE_DISMOUNT_LEFT: break; default: drive = true; noTurn = SpeedBoatUserControl(sBoatItem); break; } } else { if (sBoatItem->speed > BOAT_SLOWDOWN) sBoatItem->speed -= BOAT_SLOWDOWN; else sBoatItem->speed = 0; } if (noTurn) { if (sBoatInfo->boatTurn < -BOAT_UNDO_TURN) sBoatInfo->boatTurn += BOAT_UNDO_TURN; else if (sBoatInfo->boatTurn > BOAT_UNDO_TURN) sBoatInfo->boatTurn -= BOAT_UNDO_TURN; else sBoatInfo->boatTurn = 0; } sBoatItem->floor = height - 5; if (sBoatInfo->water == NO_HEIGHT) sBoatInfo->water = height; else sBoatInfo->water -= 5; sBoatInfo->leftFallspeed = SpeedBoatDoBoatDynamics(heightFrontLeft, sBoatInfo->leftFallspeed, (int*)&frontLeft.y); sBoatInfo->rightFallspeed = SpeedBoatDoBoatDynamics(heightFrontRight, sBoatInfo->rightFallspeed, (int*)&frontRight.y); auto ofs = sBoatItem->fallspeed; sBoatItem->fallspeed = SpeedBoatDoBoatDynamics(sBoatInfo->water, sBoatItem->fallspeed, (int*)&sBoatItem->pos.yPos); if (ofs - sBoatItem->fallspeed > 32 && sBoatItem->fallspeed == 0 && water != NO_HEIGHT) SpeedBoatSplash(sBoatItem, ofs - sBoatItem->fallspeed, water); height = (frontLeft.y + frontRight.y); if (height < 0) height = -(abs(height) / 2); else height /= 2; short xRot = phd_atan(BOAT_FRONT, sBoatItem->pos.yPos - height); short zRot = phd_atan(BOAT_SIDE, height - frontLeft.y); sBoatItem->pos.xRot += ((xRot - sBoatItem->pos.xRot) / 2); sBoatItem->pos.zRot += ((zRot - sBoatItem->pos.zRot) / 2); if (!xRot && abs(sBoatItem->pos.xRot) < 4) sBoatItem->pos.xRot = 0; if (!zRot && abs(sBoatItem->pos.zRot) < 4) sBoatItem->pos.zRot = 0; if (laraInfo->Vehicle == itemNum) { SpeedBoatAnimation(laraItem, sBoatItem, collide); if (roomNum != sBoatItem->roomNumber) { ItemNewRoom(laraInfo->Vehicle, roomNum); ItemNewRoom(laraInfo->itemNumber, roomNum); } sBoatItem->pos.zRot += sBoatInfo->tiltAngle; laraItem->pos.xPos = sBoatItem->pos.xPos; laraItem->pos.yPos = sBoatItem->pos.yPos; laraItem->pos.zPos = sBoatItem->pos.zPos; laraItem->pos.xRot = sBoatItem->pos.xRot; laraItem->pos.yRot = sBoatItem->pos.yRot; laraItem->pos.zRot = sBoatItem->pos.zRot; AnimateItem(laraItem); if (laraItem->hitPoints > 0) { sBoatItem->animNumber = Objects[ID_SPEEDBOAT].animIndex + (laraItem->animNumber - Objects[ID_SPEEDBOAT_LARA_ANIMS].animIndex); sBoatItem->frameNumber = g_Level.Anims[sBoatItem->animNumber].frameBase + (laraItem->frameNumber - g_Level.Anims[laraItem->animNumber].frameBase); } Camera.targetElevation = -ANGLE(20.0f); Camera.targetDistance = WALL_SIZE * 2; } else { if (roomNum != sBoatItem->roomNumber) ItemNewRoom(itemNum, roomNum); sBoatItem->pos.zRot += sBoatInfo->tiltAngle; } auto pitch = sBoatItem->speed; sBoatInfo->pitch += ((pitch - sBoatInfo->pitch) / 4); int fx = (sBoatItem->speed > 8) ? SFX_TR2_BOAT_MOVING : (drive ? SFX_TR2_BOAT_IDLE : SFX_TR2_BOAT_ACCELERATE); SoundEffect(fx, &sBoatItem->pos, 0, sBoatInfo->pitch / (float)BOAT_MAX_SPEED); if (sBoatItem->speed && water - 5 == sBoatItem->pos.yPos) DoBoatWakeEffect(sBoatItem); if (laraInfo->Vehicle != itemNum) return; DoSpeedBoatDismount(laraItem, sBoatItem); }