TombEngine/TR5Main/Game/control/control.cpp

1060 lines
25 KiB
C++
Raw Normal View History

2020-12-21 13:16:29 -03:00
#include "framework.h"
2021-12-24 03:32:19 +03:00
#include "Game/control/control.h"
2021-09-15 17:40:00 +03:00
#include <process.h>
2021-12-22 16:23:57 +03:00
#include "Game/collision/collide_room.h"
#include "Game/pickup/pickup.h"
#include "Game/camera.h"
#include "Game/Lara/lara.h"
#include "Game/items.h"
#include "Game/control/flipeffect.h"
#include "Game/gui.h"
#include "Game/control/lot.h"
#include "Game/health.h"
#include "Game/savegame.h"
#include "Game/room.h"
#include "Game/effects/hair.h"
#include "Game/effects/effects.h"
#include "Game/effects/tomb4fx.h"
#include "Game/effects/debris.h"
#include "Game/effects/footprint.h"
#include "Game/effects/smoke.h"
#include "Game/effects/spark.h"
#include "Game/effects/explosion.h"
#include "Game/effects/drip.h"
#include "Game/effects/weather.h"
#include "Game/effects/lightning.h"
2021-12-24 03:32:19 +03:00
#include "Game/spotcam.h"
#include "Game/control/box.h"
2021-12-22 16:23:57 +03:00
#include "Game/particle/SimpleParticle.h"
2021-12-24 03:32:19 +03:00
#include "Game/collision/sphere.h"
2021-12-22 16:23:57 +03:00
#include "Game/Lara/lara_one_gun.h"
#include "Game/Lara/lara_cheat.h"
#include "Game/Lara/lara_helpers.h"
2021-12-24 03:32:19 +03:00
#include "Objects/Effects/tr4_locusts.h"
#include "Objects/Generic/Object/objects.h"
#include "Objects/Generic/Switches/generic_switch.h"
#include "Objects/TR4/Entity/tr4_littlebeetle.h"
#include "Objects/TR5/Emitter/tr5_rats_emitter.h"
#include "Objects/TR5/Emitter/tr5_bats_emitter.h"
#include "Objects/TR5/Emitter/tr5_spider_emitter.h"
#include "Sound/sound.h"
#include "Specific/clock.h"
#include "Specific/input.h"
#include "Specific/level.h"
#include "Specific/setup.h"
#include "Specific/prng.h"
#include "Specific/winmain.h"
2021-09-16 01:12:19 +03:00
#include "Scripting/GameFlowScript.h"
2021-09-25 11:27:47 +02:00
2020-12-21 13:16:29 -03:00
using std::vector;
using std::unordered_map;
using std::string;
using namespace TEN::Effects::Footprints;
2021-08-30 18:03:21 +03:00
using namespace TEN::Effects::Explosion;
using namespace TEN::Effects::Spark;
using namespace TEN::Effects::Smoke;
using namespace TEN::Effects::Drip;
2021-11-22 19:14:29 +03:00
using namespace TEN::Effects::Lightning;
2021-09-15 11:13:47 +03:00
using namespace TEN::Effects::Environment;
2021-08-30 18:03:21 +03:00
using namespace TEN::Effects;
using namespace TEN::Entities::Switches;
2021-11-22 19:14:29 +03:00
using namespace TEN::Entities::TR4;
using namespace TEN::Renderer;
2021-08-30 18:03:21 +03:00
using namespace TEN::Math::Random;
using namespace TEN::Floordata;
2020-12-21 13:16:29 -03:00
int GameTimer = 0;
2021-10-22 16:33:15 +03:00
int GlobalCounter = 0;
int Wibble = 0;
bool InitialiseGame;
bool DoTheGame;
bool JustLoaded;
bool ThreadEnded;
int RequiredStartPos;
int CurrentLevel;
int LevelComplete;
bool InItemControlLoop;
2020-12-21 13:16:29 -03:00
short ItemNewRoomNo;
short ItemNewRooms[MAX_ROOMS];
2020-12-21 13:16:29 -03:00
short NextItemActive;
short NextItemFree;
short NextFxActive;
short NextFxFree;
2021-09-15 22:43:19 +03:00
int WeaponDelay;
int WeaponEnemyTimer;
int DrawPhase()
{
g_Renderer.Draw();
Camera.numberFrames = g_Renderer.SyncRenderer();
return Camera.numberFrames;
}
2020-12-21 13:16:29 -03:00
GAME_STATUS ControlPhase(int numFrames, int demoMode)
{
short oldLaraFrame;
GameScriptLevel* level = g_GameFlow->GetLevel(CurrentLevel);
RegeneratePickups();
if (numFrames > 10)
numFrames = 10;
if (TrackCameraInit)
UseSpotCam = false;
SetDebounce = true;
2021-09-16 01:12:19 +03:00
g_GameScript->ProcessDisplayStrings(DELTA_TIME);
static int framesCount = 0;
for (framesCount += numFrames; framesCount > 0; framesCount -= 2)
2020-12-21 13:16:29 -03:00
{
GlobalCounter++;
// This might not be the exact amount of time that has passed, but giving it a
// value of 1/30 keeps it in lock-step with the rest of the game logic,
// which assumes 30 iterations per second.
2021-09-16 01:12:19 +03:00
g_GameScript->OnControlPhase(DELTA_TIME);
2020-12-21 13:16:29 -03:00
// Poll the keyboard and update input variables
if (CurrentLevel != 0)
{
if (S_UpdateInput() == -1)
2021-09-25 16:03:28 -05:00
return GAME_STATUS::GAME_STATUS_NONE;
2020-12-21 13:16:29 -03:00
}
// Has Lara control been disabled?
2021-09-16 03:56:04 +03:00
if (Lara.uncontrollable || CurrentLevel == 0)
2020-12-21 13:16:29 -03:00
{
if (CurrentLevel != 0)
DbInput = 0;
TrInput &= IN_LOOK;
2020-12-21 13:16:29 -03:00
}
// Does the player want to enter inventory?
SetDebounce = false;
2021-05-18 19:16:58 -05:00
if (CurrentLevel != 0 && !g_Renderer.isFading())
{
2021-11-16 15:51:50 +03:00
if (TrInput & IN_SAVE && LaraItem->hitPoints > 0 && g_Gui.GetInventoryMode() != InventoryMode::Save)
{
2021-10-29 02:22:26 +03:00
StopAllSounds();
2021-11-16 15:51:50 +03:00
g_Gui.SetInventoryMode(InventoryMode::Save);
2021-10-12 04:15:22 -05:00
2021-11-16 15:51:50 +03:00
if (g_Gui.CallInventory(false))
return GAME_STATUS::GAME_STATUS_LOAD_GAME;
}
2021-11-16 15:51:50 +03:00
else if (TrInput & IN_LOAD && g_Gui.GetInventoryMode() != InventoryMode::Load)
{
2021-10-29 02:22:26 +03:00
StopAllSounds();
2021-11-16 15:51:50 +03:00
g_Gui.SetInventoryMode(InventoryMode::Load);
2021-11-16 15:51:50 +03:00
if (g_Gui.CallInventory(false))
return GAME_STATUS::GAME_STATUS_LOAD_GAME;
}
2021-11-16 15:51:50 +03:00
else if (TrInput & IN_PAUSE && g_Gui.GetInventoryMode() != InventoryMode::Pause && LaraItem->hitPoints > 0)
2021-05-18 19:16:58 -05:00
{
2021-10-29 02:22:26 +03:00
StopAllSounds();
2021-05-18 19:16:58 -05:00
g_Renderer.DumpGameScene();
2021-11-16 15:51:50 +03:00
g_Gui.SetInventoryMode(InventoryMode::Pause);
g_Gui.SetMenuToDisplay(Menu::Pause);
g_Gui.SetSelectedOption(0);
2021-05-18 19:16:58 -05:00
}
2021-11-16 15:51:50 +03:00
else if ((DbInput & IN_DESELECT || g_Gui.GetEnterInventory() != NO_ITEM) && LaraItem->hitPoints > 0)
2021-05-18 19:16:58 -05:00
{
// Stop all sounds
2021-10-29 02:22:26 +03:00
StopAllSounds();
2021-05-18 19:16:58 -05:00
2021-11-16 15:51:50 +03:00
if (g_Gui.CallInventory(true))
2021-09-25 16:03:28 -05:00
return GAME_STATUS::GAME_STATUS_LOAD_GAME;
2021-05-18 19:16:58 -05:00
}
}
2021-11-16 15:51:50 +03:00
while (g_Gui.GetInventoryMode() == InventoryMode::Pause)
2021-05-18 19:16:58 -05:00
{
2021-11-16 15:51:50 +03:00
g_Gui.DrawInventory();
2021-05-18 19:16:58 -05:00
g_Renderer.SyncRenderer();
2021-11-16 15:51:50 +03:00
if (g_Gui.DoPauseMenu() == InventoryResult::ExitToTitle)
2021-09-25 16:03:28 -05:00
return GAME_STATUS::GAME_STATUS_EXIT_TO_TITLE;
2021-05-18 19:16:58 -05:00
}
2020-12-21 13:16:29 -03:00
// Has level been completed?
if (CurrentLevel != 0 && LevelComplete)
2021-09-25 16:03:28 -05:00
return GAME_STATUS::GAME_STATUS_LEVEL_COMPLETED;
2020-12-21 13:16:29 -03:00
int oldInput = TrInput;
// Is Lara dead?
if (CurrentLevel != 0 && (Lara.deathCount > 300 || Lara.deathCount > 60 && TrInput))
{
return GAME_STATUS::GAME_STATUS_EXIT_TO_TITLE; // Maybe do game over menu like some PSX versions have??
2020-12-21 13:16:29 -03:00
}
if (demoMode && TrInput == -1)
{
oldInput = 0;
TrInput = 0;
}
if (CurrentLevel == 0)
TrInput = 0;
// Handle lasersight and binocular
if (CurrentLevel != 0)
{
if (!(TrInput & IN_LOOK) || UseSpotCam || TrackCameraInit ||
((LaraItem->currentAnimState != LS_IDLE || LaraItem->animNumber != LA_STAND_IDLE) && (!Lara.isDucked || TrInput & IN_DUCK || LaraItem->animNumber != LA_CROUCH_IDLE || LaraItem->goalAnimState != LS_CROUCH_IDLE)))
2020-12-21 13:16:29 -03:00
{
if (BinocularRange == 0)
{
if (UseSpotCam || TrackCameraInit)
2020-12-21 13:16:29 -03:00
TrInput &= ~IN_LOOK;
}
else
{
// If any input but optic controls (directions + action), immediately exit binoculars mode.
if (TrInput != IN_NONE && ((TrInput & ~IN_OPTIC_CONTROLS) != IN_NONE))
BinocularRange = 0;
2020-12-21 13:16:29 -03:00
if (LaserSight)
{
BinocularRange = 0;
LaserSight = false;
AlterFOV(ANGLE(80));
LaraItem->meshBits = 0xFFFFFFFF;
Lara.busy = false;
Camera.type = BinocularOldCamera;
ResetLaraFlex(LaraItem);
2020-12-21 13:16:29 -03:00
Camera.bounce = 0;
BinocularOn = -8;
TrInput &= ~IN_LOOK;
}
else
{
TrInput |= IN_LOOK;
DbInput = 0;
2020-12-21 13:16:29 -03:00
}
}
}
else if (BinocularRange == 0)
{
2021-08-11 15:18:22 +03:00
if (Lara.gunStatus == LG_READY && ((Lara.gunType == WEAPON_REVOLVER && Lara.Weapons[WEAPON_REVOLVER].HasLasersight) ||
(Lara.gunType == WEAPON_HK) ||
(Lara.gunType == WEAPON_CROSSBOW && Lara.Weapons[WEAPON_CROSSBOW].HasLasersight)))
2020-12-21 13:16:29 -03:00
{
BinocularRange = 128;
BinocularOldCamera = Camera.oldType;
Lara.busy = true;
LaserSight = true;
}
}
}
// Update all items
InItemControlLoop = true;
short itemNum = NextItemActive;
while (itemNum != NO_ITEM)
{
ITEM_INFO *item = &g_Level.Items[itemNum];
short nextItem = item->nextActive;
if (item->afterDeath <= 128)
{
if (Objects[item->objectNumber].control)
Objects[item->objectNumber].control(itemNum);
2021-11-27 17:50:58 +03:00
if (item->afterDeath > 0 && item->afterDeath < 128 && !(Wibble & 3))
2020-12-21 13:16:29 -03:00
item->afterDeath++;
if (item->afterDeath == 128)
KillItem(itemNum);
}
else
{
KillItem(itemNum);
}
itemNum = nextItem;
}
InItemControlLoop = false;
KillMoveItems();
// Update all effects
InItemControlLoop = true;
short fxNum = NextFxActive;
while (fxNum != NO_ITEM)
{
short nextFx = EffectList[fxNum].nextActive;
FX_INFO *fx = &EffectList[fxNum];
if (Objects[fx->objectNumber].control)
Objects[fx->objectNumber].control(fxNum);
fxNum = nextFx;
}
InItemControlLoop = false;
KillMoveEffects();
// Update some effect timers
if (SmokeCountL)
SmokeCountL--;
if (SmokeCountR)
SmokeCountR--;
if (SplashCount)
SplashCount--;
if (WeaponDelay)
WeaponDelay--;
if (WeaponEnemyTimer)
WeaponEnemyTimer--;
if (CurrentLevel != 0)
{
// Control Lara
InItemControlLoop = true;
2021-11-09 16:03:56 +11:00
LaraControl(LaraItem, &LaraCollision);
2020-12-21 13:16:29 -03:00
InItemControlLoop = false;
KillMoveItems();
g_Renderer.updateLaraAnimations(true);
2021-11-16 15:51:50 +03:00
if (g_Gui.GetInventoryItemChosen() != NO_ITEM)
2021-05-31 11:57:42 -05:00
{
SayNo();
2021-11-16 15:51:50 +03:00
g_Gui.SetInventoryItemChosen(NO_ITEM);
2021-05-31 11:57:42 -05:00
}
2021-10-12 00:26:46 -05:00
LaraCheatyBits();
TriggerLaraDrips(LaraItem);
2020-12-21 13:16:29 -03:00
// Update Lara's ponytails
HairControl(LaraItem, level->LaraType == LaraType::Young);
2020-12-21 13:16:29 -03:00
}
if (UseSpotCam)
{
// Draw flyby cameras
//if (CurrentLevel != 0)
// g_Renderer->EnableCinematicBars(true);
CalculateSpotCameras();
}
else
{
// Do the standard camera
//g_Renderer->EnableCinematicBars(false);
TrackCameraInit = false;
CalculateCamera();
}
2021-09-16 01:12:19 +03:00
// Update oscillator seed
2021-11-22 19:14:29 +03:00
Wibble = (Wibble + WIBBLE_SPEED) & WIBBLE_MAX;
2020-12-21 13:16:29 -03:00
2021-09-16 01:12:19 +03:00
// Smash shatters and clear stopper flags under them
UpdateShatters();
2020-12-21 13:16:29 -03:00
2021-09-15 11:13:47 +03:00
// Update weather
Weather.Update();
// Update special FX
2020-12-21 13:16:29 -03:00
UpdateSparks();
UpdateFireSparks();
UpdateSmoke();
UpdateBlood();
UpdateBubbles();
UpdateDebris();
UpdateGunShells();
UpdateFootprints();
2020-12-21 13:16:29 -03:00
UpdateSplashes();
2021-11-22 19:14:29 +03:00
UpdateLightning();
2020-12-21 13:16:29 -03:00
UpdateDrips();
UpdateRats();
UpdateBats();
UpdateSpiders();
UpdateSparkParticles();
UpdateSmokeParticles();
updateSimpleParticles();
2021-11-22 19:14:29 +03:00
UpdateDripParticles();
2020-12-21 13:16:29 -03:00
UpdateExplosionParticles();
UpdateShockwaves();
2021-11-22 19:14:29 +03:00
UpdateScarabs();
UpdateLocusts();
2020-12-21 13:16:29 -03:00
AnimateWaterfalls();
// Rumble screen (like in submarine level of TRC)
2020-12-21 13:16:29 -03:00
if (level->Rumble)
RumbleScreen();
2021-09-15 17:49:01 +03:00
PlaySoundSources();
DoFlipEffect(FlipEffect);
2020-12-21 13:16:29 -03:00
// Clear savegame loaded flag
JustLoaded = false;
// Update timers
2020-12-21 13:16:29 -03:00
HealthBarTimer--;
GameTimer++;
}
2021-09-25 16:03:28 -05:00
return GAME_STATUS::GAME_STATUS_NONE;
2020-12-21 13:16:29 -03:00
}
unsigned CALLBACK GameMain(void *)
{
2021-09-16 01:12:19 +03:00
try
{
TENLog("Starting GameMain...", LogLevel::Info);
2021-09-16 01:12:19 +03:00
TimeInit();
if (g_GameFlow->IntroImagePath.empty())
{
throw TENScriptException("Intro image path is not set.");
}
2020-12-21 13:16:29 -03:00
// Do a fixed time title image
g_Renderer.renderTitleImage();
2020-12-21 13:16:29 -03:00
// Execute the LUA gameflow and play the game
g_GameFlow->DoGameflow();
2020-12-21 13:16:29 -03:00
DoTheGame = false;
2020-12-21 13:16:29 -03:00
// Finish the thread
PostMessage(WindowsHandle, WM_CLOSE, NULL, NULL);
EndThread();
}
2021-09-16 01:12:19 +03:00
catch (TENScriptException const& e)
{
std::string msg = std::string{ "An unrecoverable error occurred in " } + __func__ + ": " + e.what();
TENLog(msg, LogLevel::Error, LogConfig::All);
throw;
}
2020-12-21 13:16:29 -03:00
return true;
}
GAME_STATUS DoTitle(int index)
{
TENLog("DoTitle", LogLevel::Info);
2020-12-21 13:16:29 -03:00
2021-09-14 16:34:58 +03:00
// Reset all the globals for the game which needs this
CleanUp();
2021-09-14 16:34:58 +03:00
2020-12-21 13:16:29 -03:00
// Load the level
LoadLevelFile(index);
2020-12-21 13:16:29 -03:00
2021-11-16 15:35:41 +03:00
InventoryResult inventoryResult;
2020-12-21 13:16:29 -03:00
2021-08-07 19:25:13 +01:00
if (g_GameFlow->TitleType == TITLE_TYPE::FLYBY)
2020-12-21 13:16:29 -03:00
{
// Initialise items, effects, lots, camera
InitialiseFXArray(true);
InitialisePickupDisplay();
InitialiseCamera();
2021-10-29 02:22:26 +03:00
StopAllSounds();
2020-12-21 13:16:29 -03:00
2021-07-10 17:51:01 +01:00
// Run the level script
GameScriptLevel* level = g_GameFlow->Levels[index];
std::string err;
if (!level->ScriptFileName.empty())
{
g_GameScript->ExecuteScript(level->ScriptFileName);
2021-07-10 17:51:01 +01:00
g_GameScript->InitCallbacks();
g_GameScript->SetCallbackDrawString([](std::string const key, D3DCOLOR col, int x, int y, int flags)
{
g_Renderer.drawString(float(x)/float(g_Configuration.Width) * ASSUMED_WIDTH_FOR_TEXT_DRAWING, float(y)/float(g_Configuration.Height) * ASSUMED_HEIGHT_FOR_TEXT_DRAWING, key.c_str(), col, flags);
});
2021-07-10 17:51:01 +01:00
}
2021-09-16 01:12:19 +03:00
2020-12-21 13:16:29 -03:00
RequiredStartPos = false;
if (InitialiseGame)
{
GameTimer = 0;
RequiredStartPos = false;
InitialiseGame = false;
}
Statistics.Level.Timer = 0;
2020-12-21 13:16:29 -03:00
// Initialise flyby cameras
InitSpotCamSequences();
2021-09-16 01:12:19 +03:00
InitialiseSpotCam(0);
2020-12-21 13:16:29 -03:00
UseSpotCam = true;
// Play background music
2021-10-29 02:22:26 +03:00
PlaySoundTrack(83);
2020-12-21 13:16:29 -03:00
2021-11-16 14:38:04 +03:00
// Initialize menu
2021-11-16 15:51:50 +03:00
g_Gui.SetMenuToDisplay(Menu::Title);
g_Gui.SetSelectedOption(0);
2021-11-16 14:38:04 +03:00
2020-12-21 13:16:29 -03:00
// Initialise ponytails
InitialiseHair();
2021-09-27 07:12:30 +03:00
InitialiseItemBoxData();
g_GameScript->OnStart();
2020-12-21 13:16:29 -03:00
ControlPhase(2, 0);
2021-10-12 00:26:46 -05:00
2021-11-16 15:35:41 +03:00
int frames = 0;
auto status = InventoryResult::None;
while (status == InventoryResult::None)
2021-05-18 19:16:58 -05:00
{
g_Renderer.renderTitle();
SetDebounce = true;
S_UpdateInput();
SetDebounce = false;
2021-11-16 15:51:50 +03:00
status = g_Gui.TitleOptions();
2021-05-18 19:16:58 -05:00
2021-11-16 15:35:41 +03:00
if (status != InventoryResult::None)
2021-05-18 19:16:58 -05:00
break;
Camera.numberFrames = g_Renderer.SyncRenderer();
frames = Camera.numberFrames;
ControlPhase(frames, 0);
}
inventoryResult = status;
2020-12-21 13:16:29 -03:00
}
else
2021-11-16 15:51:50 +03:00
inventoryResult = g_Gui.TitleOptions();
2020-12-21 13:16:29 -03:00
2021-09-16 01:12:19 +03:00
StopSoundTracks();
2020-12-21 13:16:29 -03:00
g_GameScript->OnEnd();
g_GameScript->FreeLevelScripts();
2021-09-16 01:12:19 +03:00
2020-12-21 13:16:29 -03:00
switch (inventoryResult)
{
2021-11-16 15:35:41 +03:00
case InventoryResult::NewGame:
2021-09-25 16:03:28 -05:00
return GAME_STATUS::GAME_STATUS_NEW_GAME;
2021-11-16 15:35:41 +03:00
case InventoryResult::LoadGame:
2021-09-25 16:03:28 -05:00
return GAME_STATUS::GAME_STATUS_LOAD_GAME;
2021-11-16 15:35:41 +03:00
case InventoryResult::ExitGame:
2021-09-25 16:03:28 -05:00
return GAME_STATUS::GAME_STATUS_EXIT_GAME;
2020-12-21 13:16:29 -03:00
}
2021-09-25 16:03:28 -05:00
return GAME_STATUS::GAME_STATUS_NEW_GAME;
2020-12-21 13:16:29 -03:00
}
GAME_STATUS DoLevel(int index, std::string ambient, bool loadFromSavegame)
2020-12-21 13:16:29 -03:00
{
// If not loading a savegame, then clear all the infos
if (!loadFromSavegame)
{
Statistics.Level.Timer = 0;
Statistics.Level.Distance = 0;
Statistics.Level.AmmoUsed = 0;
Statistics.Level.AmmoHits = 0;
Statistics.Level.Kills = 0;
2020-12-21 13:16:29 -03:00
}
// Reset all the globals for the game which needs this
CleanUp();
2021-09-14 16:34:58 +03:00
// Load the level
LoadLevelFile(index);
2021-09-14 16:34:58 +03:00
2020-12-21 13:16:29 -03:00
// Initialise items, effects, lots, camera
InitialiseFXArray(true);
InitialisePickupDisplay();
InitialiseCamera();
2021-10-29 02:22:26 +03:00
StopAllSounds();
2020-12-21 13:16:29 -03:00
// Run the level script
GameScriptLevel* level = g_GameFlow->Levels[index];
if (!level->ScriptFileName.empty())
{
g_GameScript->ExecuteScript(level->ScriptFileName);
g_GameScript->InitCallbacks();
g_GameScript->SetCallbackDrawString([](std::string const key, D3DCOLOR col, int x, int y, int flags)
{
g_Renderer.drawString(float(x)/float(g_Configuration.Width) * ASSUMED_WIDTH_FOR_TEXT_DRAWING, float(y)/float(g_Configuration.Height) * ASSUMED_HEIGHT_FOR_TEXT_DRAWING, key.c_str(), col, flags);
});
}
// Play default background music
PlaySoundTrack(ambient, SOUNDTRACK_PLAYTYPE::BGM);
2020-12-21 13:16:29 -03:00
// Restore the game?
if (loadFromSavegame)
{
SaveGame::Load(g_GameFlow->SelectedSaveGame);
2020-12-21 13:16:29 -03:00
Camera.pos.x = LaraItem->pos.xPos + 256;
Camera.pos.y = LaraItem->pos.yPos + 256;
Camera.pos.z = LaraItem->pos.zPos + 256;
Camera.target.x = LaraItem->pos.xPos;
Camera.target.y = LaraItem->pos.yPos;
Camera.target.z = LaraItem->pos.zPos;
int x = Lara.weaponItem;
RequiredStartPos = false;
InitialiseGame = false;
g_GameFlow->SelectedSaveGame = 0;
}
else
{
RequiredStartPos = false;
if (InitialiseGame)
{
GameTimer = 0;
RequiredStartPos = false;
InitialiseGame = false;
}
Statistics.Level.Timer = 0;
2020-12-21 13:16:29 -03:00
}
2021-11-16 15:51:50 +03:00
g_Gui.SetInventoryItemChosen(NO_ITEM);
g_Gui.SetEnterInventory(NO_ITEM);
2020-12-21 13:16:29 -03:00
// Initialise flyby cameras
InitSpotCamSequences();
// Initialise ponytails
InitialiseHair();
2021-09-27 07:12:30 +03:00
InitialiseItemBoxData();
g_GameScript->OnStart();
if (loadFromSavegame)
{
g_GameScript->OnLoad();
}
2020-12-21 13:16:29 -03:00
int nframes = 2;
// First control phase
g_Renderer.resetAnimations();
GAME_STATUS result = ControlPhase(nframes, 0);
// Fade in screen
g_Renderer.fadeIn();
// The game loop, finally!
while (true)
{
result = ControlPhase(nframes, 0);
nframes = DrawPhase();
Sound_UpdateScene();
2020-12-21 13:16:29 -03:00
2021-09-25 16:03:28 -05:00
if (result == GAME_STATUS::GAME_STATUS_EXIT_TO_TITLE ||
result == GAME_STATUS::GAME_STATUS_LOAD_GAME ||
result == GAME_STATUS::GAME_STATUS_LEVEL_COMPLETED)
2020-12-21 13:16:29 -03:00
{
g_GameScript->OnEnd();
g_GameScript->FreeLevelScripts();
2020-12-21 13:16:29 -03:00
// Here is the only way for exiting from the loop
2021-10-29 02:22:26 +03:00
StopAllSounds();
2021-09-16 01:12:19 +03:00
StopSoundTracks();
2020-12-21 13:16:29 -03:00
return result;
}
}
}
int GetWaterSurface(int x, int y, int z, short roomNumber)
{
ROOM_INFO *room = &g_Level.Rooms[roomNumber];
2021-09-17 16:07:53 +03:00
FLOOR_INFO *floor = GetSector(room, x - room->x, z - room->z);
2020-12-21 13:16:29 -03:00
if (room->flags & ENV_FLAG_WATER)
{
2021-11-08 17:48:57 +03:00
while (floor->RoomAbove(x, y, z).value_or(NO_ROOM) != NO_ROOM)
2020-12-21 13:16:29 -03:00
{
2021-11-08 17:48:57 +03:00
room = &g_Level.Rooms[floor->RoomAbove(x, y, z).value_or(floor->Room)];
2020-12-21 13:16:29 -03:00
if (!(room->flags & ENV_FLAG_WATER))
return (floor->CeilingHeight(x, z));
2021-09-17 16:07:53 +03:00
floor = GetSector(room, x - room->x, z - room->z);
2020-12-21 13:16:29 -03:00
}
2020-12-21 13:16:29 -03:00
return NO_HEIGHT;
}
else
{
2021-11-08 17:48:57 +03:00
while (floor->RoomBelow(x, y, z).value_or(NO_ROOM) != NO_ROOM)
2020-12-21 13:16:29 -03:00
{
2021-11-08 17:48:57 +03:00
room = &g_Level.Rooms[floor->RoomBelow(x, y, z).value_or(floor->Room)];
2020-12-21 13:16:29 -03:00
if (room->flags & ENV_FLAG_WATER)
return (floor->FloorHeight(x, z));
2021-09-17 16:07:53 +03:00
floor = GetSector(room, x - room->x, z - room->z);
2020-12-21 13:16:29 -03:00
}
}
return NO_HEIGHT;
}
2021-09-16 01:12:19 +03:00
void UpdateShatters()
{
if (!SmashedMeshCount)
return;
do
{
SmashedMeshCount--;
FLOOR_INFO* floor = GetFloor(
SmashedMesh[SmashedMeshCount]->pos.xPos,
SmashedMesh[SmashedMeshCount]->pos.yPos,
SmashedMesh[SmashedMeshCount]->pos.zPos,
&SmashedMeshRoom[SmashedMeshCount]);
TestTriggers(SmashedMesh[SmashedMeshCount]->pos.xPos,
SmashedMesh[SmashedMeshCount]->pos.yPos,
SmashedMesh[SmashedMeshCount]->pos.zPos,
SmashedMeshRoom[SmashedMeshCount], true);
floor->Stopper = false;
SmashedMesh[SmashedMeshCount] = 0;
} while (SmashedMeshCount != 0);
}
2020-12-21 13:16:29 -03:00
void KillMoveItems()
{
if (ItemNewRoomNo > 0)
{
for (int i = 0; i < ItemNewRoomNo; i++)
{
short itemNumber = ItemNewRooms[2 * i];
if (itemNumber >= 0)
ItemNewRoom(itemNumber, ItemNewRooms[2 * i + 1]);
else
KillItem(itemNumber & 0x7FFF);
}
}
ItemNewRoomNo = 0;
}
void KillMoveEffects()
{
if (ItemNewRoomNo > 0)
{
for (int i = 0; i < ItemNewRoomNo; i++)
{
short itemNumber = ItemNewRooms[2 * i];
if (itemNumber >= 0)
EffectNewRoom(itemNumber, ItemNewRooms[2 * i + 1]);
else
KillEffect(itemNumber & 0x7FFF);
}
}
ItemNewRoomNo = 0;
}
void AlterFloorHeight(ITEM_INFO *item, int height)
{
FLOOR_INFO *floor;
FLOOR_INFO *ceiling;
BOX_INFO *box;
short roomNumber;
int flag = 0;
if (abs(height))
{
flag = 1;
if (height >= 0)
height++;
else
height--;
}
roomNumber = item->roomNumber;
floor = GetFloor(item->pos.xPos, item->pos.yPos, item->pos.zPos, &roomNumber);
ceiling = GetFloor(item->pos.xPos, height + item->pos.yPos - WALL_SIZE, item->pos.zPos, &roomNumber);
floor->FloorCollision.Planes[0].z += height;
floor->FloorCollision.Planes[1].z += height;
2021-09-13 02:46:48 +03:00
box = &g_Level.Boxes[floor->Box];
2020-12-21 13:16:29 -03:00
if (box->flags & BLOCKABLE)
{
if (height >= 0)
box->flags &= ~BLOCKED;
else
box->flags |= BLOCKED;
}
}
FLOOR_INFO *GetFloor(int x, int y, int z, short *roomNumber)
{
2021-01-06 17:53:13 -03:00
const auto location = GetRoom(ROOM_VECTOR{*roomNumber, y}, x, y, z);
*roomNumber = location.roomNumber;
2020-12-21 13:16:29 -03:00
return &GetFloor(*roomNumber, x, z);
}
2021-08-20 15:26:12 +03:00
int GetFloorHeight(FLOOR_INFO *floor, int x, int y, int z)
{
2021-01-06 17:53:13 -03:00
return GetFloorHeight(ROOM_VECTOR{floor->Room, y}, x, z).value_or(NO_HEIGHT);
2020-12-21 13:16:29 -03:00
}
int GetRandomControl()
{
2021-09-17 16:07:53 +03:00
return GenerateInt();
2020-12-21 13:16:29 -03:00
}
int GetRandomDraw()
{
2021-09-17 16:07:53 +03:00
return GenerateInt();
2020-12-21 13:16:29 -03:00
}
2021-02-03 01:50:59 -03:00
int GetCeiling(FLOOR_INFO *floor, int x, int y, int z)
2020-12-21 13:16:29 -03:00
{
2021-01-06 17:53:13 -03:00
return GetCeilingHeight(ROOM_VECTOR{floor->Room, y}, x, z).value_or(NO_HEIGHT);
2020-12-21 13:16:29 -03:00
}
int ExplodeItemNode(ITEM_INFO *item, int Node, int NoXZVel, int bits)
{
if (1 << Node & item->meshBits)
{
auto num = bits;
2020-12-21 13:16:29 -03:00
if (item->objectNumber == ID_SHOOT_SWITCH1 && (CurrentLevel == 4 || CurrentLevel == 7)) // TODO: remove hardcoded think !
{
SoundEffect(SFX_TR5_SMASH_METAL, &item->pos, 0);
2020-12-21 13:16:29 -03:00
}
else if (num == 256)
2020-12-21 13:16:29 -03:00
{
num = -64;
2020-12-21 13:16:29 -03:00
}
GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD | SPHERES_SPACE_BONE_ORIGIN, Matrix::Identity);
ShatterItem.yRot = item->pos.yRot;
ShatterItem.bit = 1 << Node;
ShatterItem.meshp = &g_Level.Meshes[Objects[item->objectNumber].meshIndex + Node];
ShatterItem.sphere.x = CreatureSpheres[Node].x;
ShatterItem.sphere.y = CreatureSpheres[Node].y;
ShatterItem.sphere.z = CreatureSpheres[Node].z;
ShatterItem.flags = item->objectNumber == ID_CROSSBOW_BOLT ? 0x400 : 0;
ShatterImpactData.impactDirection = Vector3(0, -1, 0);
ShatterImpactData.impactLocation = {(float)ShatterItem.sphere.x, (float)ShatterItem.sphere.y, (float)ShatterItem.sphere.z};
ShatterObject(&ShatterItem, NULL, num, item->roomNumber, NoXZVel);
2020-12-21 13:16:29 -03:00
item->meshBits &= ~ShatterItem.bit;
return 1;
}
return 0;
}
2021-11-10 02:01:43 +11:00
int GetWaterDepth(int x, int y, int z, short roomNumber)
{
FLOOR_INFO* floor;
ROOM_INFO* r = &g_Level.Rooms[roomNumber];
short roomIndex = NO_ROOM;
do
{
int zFloor = (z - r->z) / SECTOR(1);
int xFloor = (x - r->x) / SECTOR(1);
if (zFloor <= 0)
{
zFloor = 0;
if (xFloor < 1)
xFloor = 1;
else if (xFloor > r->xSize - 2)
xFloor = r->xSize - 2;
}
else if (zFloor >= r->zSize - 1)
{
zFloor = r->zSize - 1;
if (xFloor < 1)
xFloor = 1;
else if (xFloor > r->xSize - 2)
xFloor = r->xSize - 2;
}
else if (xFloor < 0)
xFloor = 0;
else if (xFloor >= r->xSize)
xFloor = r->xSize - 1;
floor = &r->floor[zFloor + xFloor * r->zSize];
roomIndex = floor->WallPortal;
if (roomIndex != NO_ROOM)
{
roomNumber = roomIndex;
r = &g_Level.Rooms[roomIndex];
}
} while (roomIndex != NO_ROOM);
if (r->flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP))
{
while (floor->RoomAbove(x, y, z).value_or(NO_ROOM) != NO_ROOM)
{
r = &g_Level.Rooms[floor->RoomAbove(x, y, z).value_or(floor->Room)];
if (!(r->flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP)))
{
int wh = floor->CeilingHeight(x, z);
floor = GetFloor(x, y, z, &roomNumber);
return (GetFloorHeight(floor, x, y, z) - wh);
}
floor = GetSector(r, x - r->x, z - r->z);
}
return DEEP_WATER;
}
else
{
while (floor->RoomBelow(x, y, z).value_or(NO_ROOM) != NO_ROOM)
{
r = &g_Level.Rooms[floor->RoomBelow(x, y, z).value_or(floor->Room)];
if (r->flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP))
{
int wh = floor->FloorHeight(x, z);
floor = GetFloor(x, y, z, &roomNumber);
return (GetFloorHeight(floor, x, y, z) - wh);
}
floor = GetSector(r, x - r->x, z - r->z);
}
return NO_HEIGHT;
}
}
2020-12-21 13:16:29 -03:00
int GetWaterHeight(int x, int y, int z, short roomNumber)
{
ROOM_INFO *r = &g_Level.Rooms[roomNumber];
FLOOR_INFO *floor;
short adjoiningRoom = NO_ROOM;
do
{
2021-09-08 03:01:32 -05:00
int xBlock = (x - r->x) / SECTOR(1);
int zBlock = (z - r->z) / SECTOR(1);
2020-12-21 13:16:29 -03:00
if (zBlock <= 0)
{
zBlock = 0;
if (xBlock < 1)
xBlock = 1;
2021-11-08 17:28:04 +03:00
else if (xBlock > r->xSize - 2)
xBlock = r->xSize - 2;
2020-12-21 13:16:29 -03:00
}
2021-11-08 17:28:04 +03:00
else if (zBlock >= r->zSize - 1)
2020-12-21 13:16:29 -03:00
{
2021-11-08 17:28:04 +03:00
zBlock = r->zSize - 1;
2020-12-21 13:16:29 -03:00
if (xBlock < 1)
xBlock = 1;
2021-11-08 17:28:04 +03:00
else if (xBlock > r->xSize - 2)
xBlock = r->xSize - 2;
2020-12-21 13:16:29 -03:00
}
else if (xBlock < 0)
xBlock = 0;
2021-11-08 17:28:04 +03:00
else if (xBlock >= r->xSize)
xBlock = r->xSize - 1;
2020-12-21 13:16:29 -03:00
2021-11-08 17:28:04 +03:00
floor = &r->floor[zBlock + xBlock * r->zSize];
2021-09-11 22:41:25 +03:00
adjoiningRoom = floor->WallPortal;
2020-12-21 13:16:29 -03:00
if (adjoiningRoom != NO_ROOM)
{
roomNumber = adjoiningRoom;
r = &g_Level.Rooms[adjoiningRoom];
}
} while (adjoiningRoom != NO_ROOM);
if (floor->IsWall(x, z))
return NO_HEIGHT;
2020-12-21 13:16:29 -03:00
if (r->flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP))
{
2021-11-08 17:48:57 +03:00
while (floor->RoomAbove(x, y, z).value_or(NO_ROOM) != NO_ROOM)
2020-12-21 13:16:29 -03:00
{
2021-11-08 17:48:57 +03:00
auto r = &g_Level.Rooms[floor->RoomAbove(x, y, z).value_or(floor->Room)];
2020-12-21 13:16:29 -03:00
if (!(r->flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP)))
return GetCollisionResult(x, r->maxceiling, z, floor->RoomAbove(x, r->maxceiling, z).value_or(NO_ROOM)).Block->FloorHeight(x, r->maxceiling, z);
//return r->minfloor; // TODO: check if individual block floor height checks provoke any game-breaking bugs!
2021-09-17 16:07:53 +03:00
floor = GetSector(r, x - r->x, z - r->z);
2021-11-08 17:48:57 +03:00
if (floor->RoomAbove(x, y, z).value_or(NO_ROOM) == NO_ROOM)
2020-12-21 13:16:29 -03:00
break;
}
return r->maxceiling;
}
else
{
2021-11-08 17:48:57 +03:00
while (floor->RoomBelow(x, y, z).value_or(NO_ROOM) != NO_ROOM)
2020-12-21 13:16:29 -03:00
{
2021-11-08 17:48:57 +03:00
auto r = &g_Level.Rooms[floor->RoomBelow(x, y, z).value_or(floor->Room)];
2020-12-21 13:16:29 -03:00
if (r->flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP))
return GetCollisionResult(x, r->minfloor, z, floor->RoomBelow(x, r->minfloor, z).value_or(NO_ROOM)).Block->CeilingHeight(x, r->minfloor, z);
//return r->maxceiling; // TODO: check if individual block ceiling height checks provoke any game-breaking bugs!
2021-09-17 16:07:53 +03:00
floor = GetSector(r, x - r->x, z - r->z);
2021-11-08 17:48:57 +03:00
if (floor->RoomBelow(x, y, z).value_or(NO_ROOM) == NO_ROOM)
2020-12-21 13:16:29 -03:00
break;
}
}
return NO_HEIGHT;
}
2021-09-24 13:50:31 +03:00
int GetDistanceToFloor(int itemNumber, bool precise)
{
auto item = &g_Level.Items[itemNumber];
auto result = GetCollisionResult(item);
// HACK: Remove item from bridge objects temporarily.
result.Block->RemoveItem(itemNumber);
auto height = GetFloorHeight(result.Block, item->pos.xPos, item->pos.yPos, item->pos.zPos);
result.Block->AddItem(itemNumber);
auto bounds = GetBoundsAccurate(item);
int minHeight = precise ? bounds->Y2 : 0;
2021-09-24 13:50:31 +03:00
return minHeight + item->pos.yPos - height;
}
void CleanUp()
{
// Reset oscillator seed
Wibble = 0;
2021-09-14 15:54:49 +03:00
// Needs to be cleared or otherwise controls will lockup if user will exit to title
// while playing flyby with locked controls
2021-09-16 03:56:04 +03:00
Lara.uncontrollable = false;
2021-09-14 15:54:49 +03:00
2021-09-15 11:18:11 +03:00
// Weather.Clear resets lightning and wind parameters so user won't see prev weather in new level
2021-09-15 11:13:47 +03:00
Weather.Clear();
2021-09-14 16:00:06 +03:00
2021-09-14 15:54:49 +03:00
// Needs to be cleared because otherwise a list of active creatures from previous level
// will spill into new level
ActiveCreatures.clear();
2021-09-14 16:34:58 +03:00
// Clear spotcam array
ClearSpotCamSequences();
// Clear all kinds of particles
DisableSmokeParticles();
DisableDripParticles();
DisableBubbles();
DisableDebris();
2021-10-29 02:22:26 +03:00
// Clear soundtrack masks
ClearSoundTrackMasks();
2021-09-15 17:20:42 +03:00
}