mirror of
https://github.com/TombEngine/TombEngine.git
synced 2025-05-11 13:06:49 +03:00

This is not ACTUALLY the amount of time that has passed, since things in the gameplay loop appear to assume they are being called thirty times per second, even if more or less time has passed. Thus to keep the scripts in sync with the the rest of the engine, we force a 1/30 second delta time.
3634 lines
83 KiB
C++
3634 lines
83 KiB
C++
#include "framework.h"
|
|
#include "collide.h"
|
|
#include "control.h"
|
|
#include "pickup.h"
|
|
#include "puzzles_keys.h"
|
|
#include "camera.h"
|
|
#include "Lara.h"
|
|
#include "hair.h"
|
|
#include "items.h"
|
|
#include "effect2.h"
|
|
#include "draw.h"
|
|
#ifdef NEW_INV
|
|
#include "newinv2.h"
|
|
#else
|
|
#include "inventory.h"
|
|
#endif
|
|
#include "gameflow.h"
|
|
#include "lot.h"
|
|
#include "pickup.h"
|
|
#include "draw.h"
|
|
#include "health.h"
|
|
#include "savegame.h"
|
|
#include "sound.h"
|
|
#include "spotcam.h"
|
|
#include "box.h"
|
|
#include "objects.h"
|
|
#include "switch.h"
|
|
#include "rope.h"
|
|
#include "tomb4fx.h"
|
|
#include "traps.h"
|
|
#include "effect.h"
|
|
#include "sphere.h"
|
|
#include "debris.h"
|
|
#include "lara_fire.h"
|
|
#include "footprint.h"
|
|
#include "level.h"
|
|
#include "input.h"
|
|
#include "winmain.h"
|
|
#include "Renderer11.h"
|
|
#include "setup.h"
|
|
#include "tr5_rats_emitter.h"
|
|
#include "tr5_bats_emitter.h"
|
|
#include "tr5_spider_emitter.h"
|
|
#include "tr4_locusts.h"
|
|
#include "smoke.h"
|
|
#include "spark.h"
|
|
#include <tr4_littlebeetle.h>
|
|
#include "explosion.h"
|
|
#include "drip.h"
|
|
#include "particle/SimpleParticle.h"
|
|
#include <process.h>
|
|
#include "prng.h"
|
|
#include <Game/Lara/lara_one_gun.h>
|
|
|
|
using std::vector;
|
|
using std::unordered_map;
|
|
using std::string;
|
|
using namespace T5M::Effects::Explosion;
|
|
using namespace T5M::Effects::Spark;
|
|
using namespace T5M::Effects::Smoke;
|
|
using namespace T5M::Effects;
|
|
using T5M::Renderer::g_Renderer;
|
|
using namespace T5M::Math::Random;
|
|
using namespace T5M::Floordata;
|
|
|
|
short ShatterSounds[18][10] =
|
|
{
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_WOOD, SFX_TR5_SMASH_WOOD, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_METAL, SFX_TR5_SMASH_METAL, SFX_TR5_SMASH_METAL, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_METAL, SFX_TR5_SMASH_METAL, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_METAL, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_METAL, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS},
|
|
{SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS, SFX_TR5_SMASH_GLASS}};
|
|
|
|
int KeyTriggerActive;
|
|
int number_los_rooms;
|
|
short los_rooms[20];
|
|
int ClosestItem;
|
|
int ClosestDist;
|
|
PHD_VECTOR ClosestCoord;
|
|
int RumbleTimer = 0;
|
|
int InGameCnt = 0;
|
|
byte IsAtmospherePlaying = 0;
|
|
byte FlipStatus = 0;
|
|
int FlipStats[MAX_FLIPMAP];
|
|
int FlipMap[MAX_FLIPMAP];
|
|
bool InItemControlLoop;
|
|
short ItemNewRoomNo;
|
|
short ItemNewRooms[512];
|
|
short NextFxActive;
|
|
short NextFxFree;
|
|
short NextItemActive;
|
|
short NextItemFree;
|
|
short *TriggerIndex;
|
|
|
|
int DisableLaraControl = 0;
|
|
int WeatherType;
|
|
int LaraDrawType;
|
|
int NumAnimatedTextures;
|
|
short *AnimTextureRanges;
|
|
int nAnimUVRanges;
|
|
int Wibble = 0;
|
|
int SetDebounce = 0;
|
|
|
|
std::string CurrentAtmosphere;
|
|
short CurrentRoom;
|
|
int GameTimer;
|
|
short GlobalCounter;
|
|
byte LevelComplete;
|
|
short DelCutSeqPlayer;
|
|
#ifndef NEW_INV
|
|
int LastInventoryItem;
|
|
#endif
|
|
int TrackCameraInit;
|
|
short TorchRoom;
|
|
int InitialiseGame;
|
|
int RequiredStartPos;
|
|
int WeaponDelay;
|
|
int WeaponEnemyTimer;
|
|
HEIGHT_TYPES HeightType;
|
|
int HeavyTriggered;
|
|
short SkyPos1;
|
|
short SkyPos2;
|
|
signed char SkyVelocity1;
|
|
signed char SkyVelocity2;
|
|
CVECTOR SkyColor1;
|
|
CVECTOR SkyColor2;
|
|
int CutSeqNum;
|
|
int CutSeqTriggered;
|
|
int GlobalPlayingCutscene;
|
|
int CurrentLevel;
|
|
bool SoundActive;
|
|
bool DoTheGame;
|
|
bool ThreadEnded;
|
|
int OnFloor;
|
|
int SmokeWindX;
|
|
int SmokeWindZ;
|
|
int OnObject;
|
|
int KillEverythingFlag;
|
|
int FlipTimer;
|
|
int FlipEffect;
|
|
int TriggerTimer;
|
|
int JustLoaded;
|
|
int PoisonFlags;
|
|
int OldLaraBusy;
|
|
int Infrared;
|
|
short FlashFadeR;
|
|
short FlashFadeG;
|
|
short FlashFadeB;
|
|
short FlashFader;
|
|
|
|
int SplitFloor;
|
|
int SplitCeiling;
|
|
int TiltXOffset;
|
|
int TiltYOffset;
|
|
int FramesCount;
|
|
|
|
std::vector<short> OutsideRoomTable[OUTSIDE_SIZE][OUTSIDE_SIZE];
|
|
short IsRoomOutsideNo;
|
|
|
|
bool g_CollidedVolume = false;
|
|
|
|
extern GameFlow *g_GameFlow;
|
|
extern GameScript *g_GameScript;
|
|
#ifndef NEW_INV
|
|
extern Inventory g_Inventory;
|
|
#endif
|
|
extern int SplashCount;
|
|
extern short FXType;
|
|
extern unordered_map<string, AudioTrack> g_AudioTracks;
|
|
using namespace T5M::Effects::Footprints;
|
|
extern std::deque<FOOTPRINT_STRUCT> footprints;
|
|
extern bool BlockAllInput;
|
|
extern int skipLoop;
|
|
extern int skipFrames;
|
|
extern int lockInput;
|
|
|
|
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;
|
|
|
|
if (skipLoop != -1)
|
|
{
|
|
if (skipLoop == 0)
|
|
return GAME_STATUS_NONE;
|
|
else
|
|
oldLaraFrame = LaraItem->frameNumber;
|
|
}
|
|
|
|
for (FramesCount += numFrames; FramesCount > 0; FramesCount -= skipFrames)
|
|
{
|
|
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.
|
|
g_GameScript->OnControlPhase(1.0f/30.0f);
|
|
UpdateSky();
|
|
|
|
// Poll the keyboard and update input variables
|
|
if (CurrentLevel != 0)
|
|
{
|
|
if (S_UpdateInput() == -1)
|
|
return GAME_STATUS_NONE;
|
|
}
|
|
|
|
if (BlockAllInput)
|
|
{
|
|
DbInput = 0;
|
|
TrInput = 0;
|
|
}
|
|
|
|
if (lockInput)
|
|
TrInput = lockInput;
|
|
|
|
// Has Lara control been disabled?
|
|
if (DisableLaraControl || CurrentLevel == 0)
|
|
{
|
|
if (CurrentLevel != 0)
|
|
DbInput = 0;
|
|
TrInput &= IN_LOOK;
|
|
}
|
|
|
|
// If cutscene has been triggered then clear input
|
|
if (CutSeqTriggered)
|
|
TrInput = IN_NONE;
|
|
|
|
// Does the player want to enter inventory?
|
|
SetDebounce = false;
|
|
|
|
#ifdef NEW_INV
|
|
if (CurrentLevel != 0 && !g_Renderer.isFading())
|
|
{
|
|
if (TrInput & IN_PAUSE && GLOBAL_invMode != IM_PAUSE && LaraItem->hitPoints > 0 && !CutSeqTriggered)
|
|
{
|
|
SOUND_Stop();
|
|
g_Renderer.DumpGameScene();
|
|
GLOBAL_invMode = IM_PAUSE;
|
|
pause_menu_to_display = pause_main_menu;
|
|
pause_selected_option = 1;
|
|
}
|
|
else if ((DbInput & IN_DESELECT || GLOBAL_enterinventory != NO_ITEM) && !CutSeqTriggered && LaraItem->hitPoints > 0)
|
|
{
|
|
// Stop all sounds
|
|
SOUND_Stop();
|
|
|
|
if (S_CallInventory2())
|
|
return GAME_STATUS_LOAD_GAME;
|
|
}
|
|
}
|
|
|
|
while (GLOBAL_invMode == IM_PAUSE)
|
|
{
|
|
DrawInv();
|
|
g_Renderer.SyncRenderer();
|
|
|
|
int z = DoPauseMenu();
|
|
|
|
if (z == INV_RESULT_EXIT_TO_TILE)
|
|
return GAME_STATUS_EXIT_TO_TITLE;
|
|
}
|
|
#else
|
|
if (CurrentLevel != 0 && !g_Renderer.isFading())
|
|
{
|
|
if ((DbInput & IN_DESELECT || g_Inventory.GetEnterObject() != NO_ITEM) && !CutSeqTriggered && LaraItem->hitPoints > 0)
|
|
{
|
|
// Stop all sounds
|
|
SOUND_Stop();
|
|
int inventoryResult = g_Inventory.DoInventory();
|
|
switch (inventoryResult)
|
|
{
|
|
case INV_RESULT_LOAD_GAME:
|
|
return GAME_STATUS_LOAD_GAME;
|
|
case INV_RESULT_EXIT_TO_TILE:
|
|
return GAME_STATUS_EXIT_TO_TITLE;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Has level been completed?
|
|
if (CurrentLevel != 0 && LevelComplete)
|
|
return GAME_STATUS_LEVEL_COMPLETED;
|
|
|
|
int oldInput = TrInput;
|
|
|
|
// Is Lara dead?
|
|
if (CurrentLevel != 0 && (Lara.deathCount > 300 || Lara.deathCount > 60 && TrInput))
|
|
{
|
|
#ifdef NEW_INV
|
|
return GAME_STATUS_EXIT_TO_TITLE;//maybe do game over menu like some PSX versions have??
|
|
#else
|
|
int inventoryResult = g_Inventory.DoInventory();
|
|
switch (inventoryResult)
|
|
{
|
|
case INV_RESULT_NEW_GAME:
|
|
return GAME_STATUS_NEW_GAME;
|
|
case INV_RESULT_LOAD_GAME:
|
|
return GAME_STATUS_LOAD_GAME;
|
|
case INV_RESULT_EXIT_TO_TILE:
|
|
return GAME_STATUS_EXIT_TO_TITLE;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (demoMode && TrInput == -1)
|
|
{
|
|
oldInput = 0;
|
|
TrInput = 0;
|
|
}
|
|
|
|
if (CurrentLevel == 0)
|
|
TrInput = 0;
|
|
|
|
// Handle lasersight and binocular
|
|
if (CurrentLevel != 0)
|
|
{
|
|
if (!(TrInput & IN_LOOK) || SniperCameraActive || UseSpotCam || TrackCameraInit ||
|
|
((LaraItem->currentAnimState != LS_STOP || LaraItem->animNumber != LA_STAND_IDLE) && (!Lara.isDucked || TrInput & IN_DUCK || LaraItem->animNumber != LA_CROUCH_IDLE || LaraItem->goalAnimState != LS_CROUCH_IDLE)))
|
|
{
|
|
if (BinocularRange == 0)
|
|
{
|
|
if (SniperCameraActive || UseSpotCam || TrackCameraInit)
|
|
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;
|
|
|
|
if (LaserSight)
|
|
{
|
|
BinocularRange = 0;
|
|
LaserSight = false;
|
|
AlterFOV(ANGLE(80));
|
|
LaraItem->meshBits = 0xFFFFFFFF;
|
|
Lara.busy = false;
|
|
Camera.type = BinocularOldCamera;
|
|
|
|
Lara.headYrot = 0;
|
|
Lara.headXrot = 0;
|
|
|
|
Lara.torsoYrot = 0;
|
|
Lara.torsoXrot = 0;
|
|
|
|
Camera.bounce = 0;
|
|
BinocularOn = -8;
|
|
|
|
TrInput &= ~IN_LOOK;
|
|
}
|
|
else
|
|
{
|
|
TrInput |= IN_LOOK;
|
|
DbInput = 0;
|
|
}
|
|
}
|
|
|
|
Infrared = false;
|
|
}
|
|
else if (BinocularRange == 0)
|
|
{
|
|
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)))
|
|
{
|
|
BinocularRange = 128;
|
|
BinocularOldCamera = Camera.oldType;
|
|
|
|
Lara.busy = true;
|
|
LaserSight = true;
|
|
Infrared = true;
|
|
}
|
|
else
|
|
Infrared = false;
|
|
}
|
|
else
|
|
{
|
|
if (LaserSight)
|
|
{
|
|
Infrared = true;
|
|
}
|
|
else
|
|
{
|
|
Infrared = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
GotLaraSpheres = false;
|
|
|
|
// 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);
|
|
|
|
if (item->afterDeath < 128 && item->afterDeath > 0 && !(Wibble & 3))
|
|
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)
|
|
{
|
|
if (Lara.hasFired)
|
|
{
|
|
AlertNearbyGuards(LaraItem);
|
|
Lara.hasFired = false;
|
|
}
|
|
|
|
// Is Lara poisoned?
|
|
if (Lara.poisoned)
|
|
{
|
|
if (Lara.poisoned > 4096)
|
|
Lara.poisoned = 4096;
|
|
else if (Lara.dpoisoned)
|
|
Lara.dpoisoned++;
|
|
|
|
|
|
if (!Lara.gassed)
|
|
{
|
|
if (Lara.dpoisoned)
|
|
{
|
|
Lara.dpoisoned -= 8;
|
|
if (Lara.dpoisoned < 0)
|
|
Lara.dpoisoned = 0;
|
|
}
|
|
}
|
|
if (Lara.poisoned >= 256 && !(Wibble & 0xFF))
|
|
{
|
|
LaraItem->hitPoints -= Lara.poisoned >> (8 - Lara.gassed);
|
|
PoisonFlags = 16;
|
|
}
|
|
}
|
|
|
|
// Control Lara
|
|
InItemControlLoop = true;
|
|
// Lara.skelebob = NULL;
|
|
LaraControl(Lara.itemNumber);
|
|
InItemControlLoop = false;
|
|
KillMoveItems();
|
|
|
|
g_Renderer.updateLaraAnimations(true);
|
|
|
|
#ifdef NEW_INV
|
|
if (GLOBAL_inventoryitemchosen != -1)
|
|
{
|
|
SayNo();
|
|
GLOBAL_inventoryitemchosen = -1;
|
|
}
|
|
#endif
|
|
// Update Lara's ponytails
|
|
HairControl(0, 0, 0);
|
|
if (level->LaraType == LARA_TYPE::YOUNG)
|
|
HairControl(0, 1, 0);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
//WTF: what is this? It's used everywhere so it has to stay
|
|
Wibble = (Wibble + 4) & 0xFC;
|
|
|
|
// Update special effects
|
|
TriggerLaraDrips();
|
|
|
|
if (SmashedMeshCount)
|
|
{
|
|
do
|
|
{
|
|
SmashedMeshCount--;
|
|
|
|
FLOOR_INFO *floor = GetFloor(
|
|
SmashedMesh[SmashedMeshCount]->x,
|
|
SmashedMesh[SmashedMeshCount]->y,
|
|
SmashedMesh[SmashedMeshCount]->z,
|
|
&SmashedMeshRoom[SmashedMeshCount]);
|
|
|
|
int height = GetFloorHeight(
|
|
floor,
|
|
SmashedMesh[SmashedMeshCount]->x,
|
|
SmashedMesh[SmashedMeshCount]->y,
|
|
SmashedMesh[SmashedMeshCount]->z);
|
|
|
|
TestTriggers(TriggerIndex, 1, 0);
|
|
|
|
floor->stopper = false;
|
|
SmashedMesh[SmashedMeshCount] = 0;
|
|
} while (SmashedMeshCount != 0);
|
|
}
|
|
|
|
// Update special FX
|
|
UpdateSparks();
|
|
UpdateFireSparks();
|
|
UpdateSmoke();
|
|
UpdateBlood();
|
|
UpdateBubbles();
|
|
UpdateDebris();
|
|
UpdateGunShells();
|
|
updateFootprints();
|
|
UpdateSplashes();
|
|
UpdateEnergyArcs();
|
|
UpdateLightning();
|
|
UpdateDrips();
|
|
UpdateRats();
|
|
UpdateBats();
|
|
UpdateSpiders();
|
|
UpdateLittleBeetles();
|
|
UpdateSparkParticles();
|
|
UpdateSmokeParticles();
|
|
updateSimpleParticles();
|
|
T5M::Effects::Drip::UpdateDrips();
|
|
UpdateExplosionParticles();
|
|
UpdateShockwaves();
|
|
UpdateLocusts();
|
|
UpdateLittleBeetles();
|
|
//Legacy_UpdateLightning();
|
|
AnimateWaterfalls();
|
|
|
|
// Rumble screen (like in submarine level of TRC)
|
|
if (level->Rumble)
|
|
RumbleScreen();
|
|
|
|
// Play sound sources
|
|
for (int i = 0; i < g_Level.SoundSources.size(); i++)
|
|
{
|
|
SOUND_SOURCE_INFO* sound = &g_Level.SoundSources[i];
|
|
|
|
short t = sound->flags & 31;
|
|
short group = t & 1;
|
|
group += t & 2;
|
|
group += ((t >> 2) & 1) * 3;
|
|
group += ((t >> 3) & 1) * 4;
|
|
group += ((t >> 4) & 1) * 5;
|
|
|
|
if (!FlipStats[group] && (sound->flags & 128) == 0)
|
|
continue;
|
|
else if (FlipStats[group] && (sound->flags & 128) == 0)
|
|
continue;
|
|
|
|
SoundEffect(sound->soundId, (PHD_3DPOS*)&sound->x, 0);
|
|
}
|
|
|
|
// Do flipeffects
|
|
if (FlipEffect != -1)
|
|
effect_routines[FlipEffect](NULL);
|
|
|
|
// Update timers
|
|
HealthBarTimer--;
|
|
GameTimer++;
|
|
}
|
|
|
|
if (skipLoop != -1)
|
|
{
|
|
if (oldLaraFrame != LaraItem->frameNumber)
|
|
--skipLoop;
|
|
}
|
|
|
|
return GAME_STATUS_NONE;
|
|
}
|
|
|
|
unsigned CALLBACK GameMain(void *)
|
|
{
|
|
try {
|
|
printf("GameMain\n");
|
|
|
|
// Initialise legacy memory buffer and game timer
|
|
init_game_malloc();
|
|
TIME_Init();
|
|
if (g_GameFlow->IntroImagePath.empty())
|
|
{
|
|
throw TENScriptException("Intro image path is not set.");
|
|
}
|
|
|
|
// Do a fixed time title image
|
|
g_Renderer.renderTitleImage();
|
|
|
|
// Execute the LUA gameflow and play the game
|
|
g_GameFlow->DoGameflow();
|
|
|
|
DoTheGame = false;
|
|
|
|
// Finish the thread
|
|
PostMessage(WindowsHandle, WM_CLOSE, NULL, NULL);
|
|
EndThread();
|
|
}
|
|
catch (TENScriptException const& e) {
|
|
std::string msg = std::string{ "An unrecoverable error occurred in " } + __func__ + ": " + e.what();
|
|
TENLog(msg, LogLevel::Error, LogConfig::All);
|
|
throw;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
GAME_STATUS DoTitle(int index)
|
|
{
|
|
//DB_Log(2, "DoTitle - DLL");
|
|
printf("DoTitle\n");
|
|
|
|
// Load the level
|
|
S_LoadLevelFile(index);
|
|
|
|
int inventoryResult;
|
|
|
|
if (g_GameFlow->TitleType == TITLE_TYPE::FLYBY)
|
|
{
|
|
// Initialise items, effects, lots, camera
|
|
InitialiseFXArray(true);
|
|
InitialisePickupDisplay();
|
|
InitialiseCamera();
|
|
SOUND_Stop();
|
|
|
|
// Run the level script
|
|
GameScriptLevel* level = g_GameFlow->Levels[index];
|
|
std::string err;
|
|
if (!level->ScriptFileName.empty())
|
|
{
|
|
g_GameScript->ExecuteScript(level->ScriptFileName);
|
|
g_GameScript->InitCallbacks();
|
|
}
|
|
RequiredStartPos = false;
|
|
if (InitialiseGame)
|
|
{
|
|
GameTimer = 0;
|
|
RequiredStartPos = false;
|
|
InitialiseGame = false;
|
|
}
|
|
|
|
Savegame.Level.Timer = 0;
|
|
if (CurrentLevel == 1)
|
|
Savegame.TLCount = 0;
|
|
#ifndef NEW_INV
|
|
LastInventoryItem = -1;
|
|
#endif
|
|
DelCutSeqPlayer = 0;
|
|
|
|
// Initialise flyby cameras
|
|
InitSpotCamSequences();
|
|
|
|
InitialiseSpotCam(2);
|
|
CurrentAtmosphere = "083_horus";
|
|
UseSpotCam = true;
|
|
|
|
// Play background music
|
|
//CurrentAtmosphere = ambient;
|
|
S_CDPlay(CurrentAtmosphere, 1);
|
|
IsAtmospherePlaying = true;
|
|
|
|
// Initialise ponytails
|
|
InitialiseHair();
|
|
|
|
ControlPhase(2, 0);
|
|
#ifdef NEW_INV
|
|
int status = 0, frames;
|
|
while (!status)
|
|
{
|
|
g_Renderer.renderTitle();
|
|
|
|
SetDebounce = true;
|
|
S_UpdateInput();
|
|
SetDebounce = false;
|
|
|
|
status = TitleOptions();
|
|
|
|
if (status)
|
|
break;
|
|
|
|
Camera.numberFrames = g_Renderer.SyncRenderer();
|
|
frames = Camera.numberFrames;
|
|
ControlPhase(frames, 0);
|
|
}
|
|
|
|
inventoryResult = status;
|
|
#else
|
|
inventoryResult = g_Inventory.DoTitleInventory();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef NEW_INV
|
|
inventoryResult = TitleOptions();
|
|
#else
|
|
inventoryResult = g_Inventory.DoTitleInventory();
|
|
#endif
|
|
}
|
|
|
|
UseSpotCam = false;
|
|
S_CDStop();
|
|
|
|
switch (inventoryResult)
|
|
{
|
|
case INV_RESULT_NEW_GAME:
|
|
return GAME_STATUS_NEW_GAME;
|
|
case INV_RESULT_LOAD_GAME:
|
|
return GAME_STATUS_LOAD_GAME;
|
|
case INV_RESULT_EXIT_GAME:
|
|
return GAME_STATUS_EXIT_GAME;
|
|
}
|
|
|
|
return GAME_STATUS_NEW_GAME;
|
|
}
|
|
|
|
GAME_STATUS DoLevel(int index, std::string ambient, bool loadFromSavegame)
|
|
{
|
|
// If not loading a savegame, then clear all the infos
|
|
if (!loadFromSavegame)
|
|
{
|
|
Savegame.Level.Timer = 0;
|
|
Savegame.Level.Distance = 0;
|
|
Savegame.Level.AmmoUsed = 0;
|
|
Savegame.Level.AmmoHits = 0;
|
|
Savegame.Level.Kills = 0;
|
|
}
|
|
|
|
// Load the level
|
|
S_LoadLevelFile(index);
|
|
|
|
// Reset all the globals for the game which needs this
|
|
ResetGlobals();
|
|
|
|
// Initialise items, effects, lots, camera
|
|
InitialiseFXArray(true);
|
|
InitialisePickupDisplay();
|
|
InitialiseCamera();
|
|
SOUND_Stop();
|
|
|
|
// Run the level script
|
|
GameScriptLevel* level = g_GameFlow->Levels[index];
|
|
|
|
if (!level->ScriptFileName.empty())
|
|
{
|
|
g_GameScript->ExecuteScript(level->ScriptFileName);
|
|
g_GameScript->InitCallbacks();
|
|
}
|
|
|
|
// Restore the game?
|
|
if (loadFromSavegame)
|
|
{
|
|
char fileName[255];
|
|
ZeroMemory(fileName, 255);
|
|
sprintf(fileName, "savegame.%d", g_GameFlow->SelectedSaveGame);
|
|
SaveGame::Load(fileName);
|
|
|
|
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;
|
|
}
|
|
|
|
Savegame.Level.Timer = 0;
|
|
if (CurrentLevel == 1)
|
|
Savegame.TLCount = 0;
|
|
}
|
|
#ifndef NEW_INV
|
|
LastInventoryItem = -1;
|
|
#endif
|
|
DelCutSeqPlayer = 0;
|
|
|
|
#ifdef NEW_INV
|
|
GLOBAL_inventoryitemchosen = NO_ITEM;
|
|
GLOBAL_enterinventory = NO_ITEM;
|
|
#else
|
|
g_Inventory.SetEnterObject(NO_ITEM);
|
|
g_Inventory.SetSelectedObject(NO_ITEM);
|
|
#endif
|
|
|
|
// Initialise flyby cameras
|
|
InitSpotCamSequences();
|
|
|
|
// Play background music
|
|
CurrentAtmosphere = ambient;
|
|
S_CDPlay(CurrentAtmosphere, 1);
|
|
IsAtmospherePlaying = true;
|
|
|
|
// Initialise ponytails
|
|
InitialiseHair();
|
|
|
|
g_GameScript->OnStart();
|
|
if (loadFromSavegame)
|
|
{
|
|
g_GameScript->OnLoad();
|
|
}
|
|
|
|
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 = DrawPhaseGame();
|
|
Sound_UpdateScene();
|
|
|
|
if (result == GAME_STATUS_EXIT_TO_TITLE ||
|
|
result == GAME_STATUS_LOAD_GAME ||
|
|
result == GAME_STATUS_LEVEL_COMPLETED)
|
|
{
|
|
g_GameScript->OnEnd();
|
|
// Here is the only way for exiting from the loop
|
|
SOUND_Stop();
|
|
S_CDStop();
|
|
DisableBubbles();
|
|
DisableDebris();
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestTriggers(short *data, int heavy, int HeavyFlags)
|
|
{
|
|
int flip = -1;
|
|
int flipAvailable = 0;
|
|
int newEffect = -1;
|
|
int switchOff = 0;
|
|
int switchFlag = 0;
|
|
short objectNumber = 0;
|
|
int keyResult = 0;
|
|
short cameraFlags = 0;
|
|
short cameraTimer = 0;
|
|
int spotCamIndex = 0;
|
|
|
|
// Test trigger volumes
|
|
g_CollidedVolume = false;
|
|
|
|
ROOM_INFO* room = &g_Level.Rooms[LaraItem->roomNumber];
|
|
for (int i = 0; i < room->triggerVolumes.size(); i++)
|
|
{
|
|
|
|
TRIGGER_VOLUME* volume = &room->triggerVolumes[i];
|
|
|
|
bool contains = false;
|
|
switch (volume->type)
|
|
{
|
|
case VOLUME_BOX:
|
|
g_Renderer.addDebugBox(volume->box, Vector4(1.0f, 0.0f, 1.0f, 1.0f), RENDERER_DEBUG_PAGE::LOGIC_STATS);
|
|
contains = (volume->box.Contains(Vector3(LaraItem->pos.xPos, LaraItem->pos.yPos, LaraItem->pos.zPos)) == ContainmentType::CONTAINS);
|
|
break;
|
|
|
|
case VOLUME_SPHERE:
|
|
g_Renderer.addDebugSphere(volume->sphere.Center, volume->sphere.Radius, Vector4(1.0f, 0.0f, 1.0f, 1.0f), RENDERER_DEBUG_PAGE::LOGIC_STATS);
|
|
contains = (volume->sphere.Contains(Vector3(LaraItem->pos.xPos, LaraItem->pos.yPos, LaraItem->pos.zPos)) == ContainmentType::CONTAINS);
|
|
break;
|
|
}
|
|
|
|
if (contains)
|
|
{
|
|
g_CollidedVolume = true;
|
|
|
|
if (volume->status == TriggerStatus::TS_OUTSIDE)
|
|
{
|
|
volume->status = TriggerStatus::TS_ENTERING;
|
|
if (!volume->onEnter.empty())
|
|
g_GameScript->ExecuteFunction(volume->onEnter);
|
|
}
|
|
else
|
|
{
|
|
volume->status = TriggerStatus::TS_INSIDE;
|
|
if (!volume->onInside.empty())
|
|
g_GameScript->ExecuteFunction(volume->onInside);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (volume->status == TriggerStatus::TS_INSIDE)
|
|
{
|
|
volume->status = TriggerStatus::TS_LEAVING;
|
|
if (!volume->onLeave.empty())
|
|
g_GameScript->ExecuteFunction(volume->onLeave);
|
|
}
|
|
else
|
|
{
|
|
volume->status = TriggerStatus::TS_OUTSIDE;
|
|
}
|
|
}
|
|
}
|
|
|
|
HeavyTriggered = false;
|
|
|
|
if (!heavy)
|
|
{
|
|
Lara.canMonkeySwing = false;
|
|
Lara.climbStatus = false;
|
|
Lara.ClockworkBeetleFlag = false;
|
|
}
|
|
|
|
if (!data)
|
|
return;
|
|
|
|
// Burn Lara
|
|
if ((*data & 0x1F) == LAVA_TYPE)
|
|
{
|
|
if (!heavy && (LaraItem->pos.yPos == LaraItem->floor || Lara.waterStatus))
|
|
LavaBurn(LaraItem);
|
|
|
|
if (*data & 0x8000)
|
|
return;
|
|
|
|
data++;
|
|
}
|
|
|
|
// Lara can climb
|
|
if ((*data & 0x1F) == CLIMB_TYPE)
|
|
{
|
|
if (!heavy)
|
|
{
|
|
short quad = GetQuadrant(LaraItem->pos.yRot);
|
|
if ((1 << (quad + 8)) & *data)
|
|
Lara.climbStatus = true;
|
|
}
|
|
|
|
if (*data & 0x8000)
|
|
return;
|
|
|
|
data++;
|
|
}
|
|
|
|
// Lara can monkey
|
|
if ((*data & 0x1F) == MONKEY_TYPE)
|
|
{
|
|
if (!heavy)
|
|
Lara.canMonkeySwing = true;
|
|
|
|
if (*data & 0x8000)
|
|
return;
|
|
|
|
data++;
|
|
}
|
|
|
|
//for the stupid beetle
|
|
if ((*data & 0x1F) == MINER_TYPE)
|
|
{
|
|
if (!heavy)
|
|
Lara.ClockworkBeetleFlag = 1;
|
|
|
|
if (*data & 0x8000)
|
|
return;
|
|
|
|
data++;
|
|
}
|
|
|
|
// Trigger triggerer
|
|
if ((*data & 0x1F) == TRIGTRIGGER_TYPE)
|
|
{
|
|
if (!(*data & 0x20) || *data & 0x8000)
|
|
return;
|
|
|
|
data++;
|
|
}
|
|
|
|
short triggerType = (*(data++) >> 8) & 0x3F;
|
|
short flags = *(data++);
|
|
short timer = flags & 0xFF;
|
|
|
|
if (Camera.type != HEAVY_CAMERA)
|
|
RefreshCamera(triggerType, data);
|
|
|
|
short value = 0;
|
|
|
|
if (heavy)
|
|
{
|
|
switch (triggerType)
|
|
{
|
|
case HEAVY:
|
|
case HEAVYANTITRIGGER:
|
|
break;
|
|
|
|
case HEAVYSWITCH:
|
|
if (!HeavyFlags)
|
|
return;
|
|
|
|
if (HeavyFlags >= 0)
|
|
{
|
|
flags &= CODE_BITS;
|
|
if (flags != HeavyFlags)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
flags |= CODE_BITS;
|
|
flags += HeavyFlags;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Enemies can only activate heavy triggers
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (triggerType)
|
|
{
|
|
case TRIGGER_TYPES::SWITCH:
|
|
value = *(data++) & 0x3FF;
|
|
|
|
if (flags & ONESHOT)
|
|
g_Level.Items[value].itemFlags[0] = 1;
|
|
|
|
if (!SwitchTrigger(value, timer))
|
|
return;
|
|
|
|
objectNumber = g_Level.Items[value].objectNumber;
|
|
if (objectNumber >= ID_SWITCH_TYPE1 && objectNumber <= ID_SWITCH_TYPE6 && g_Level.Items[value].triggerFlags == 5)
|
|
switchFlag = 1;
|
|
|
|
switchOff = (g_Level.Items[value].currentAnimState == 1);
|
|
|
|
break;
|
|
|
|
case TRIGGER_TYPES::MONKEY:
|
|
if (LaraItem->currentAnimState >= LS_MONKEYSWING_IDLE &&
|
|
(LaraItem->currentAnimState <= LS_MONKEYSWING_TURN_180 ||
|
|
LaraItem->currentAnimState == LS_MONKEYSWING_TURN_LEFT ||
|
|
LaraItem->currentAnimState == LS_MONKEYSWING_TURN_RIGHT))
|
|
break;
|
|
return;
|
|
|
|
case TRIGGER_TYPES::TIGHTROPE_T:
|
|
if (LaraItem->currentAnimState >= LS_TIGHTROPE_IDLE &&
|
|
LaraItem->currentAnimState <= LS_TIGHTROPE_RECOVER_BALANCE &&
|
|
LaraItem->currentAnimState != LS_DOVESWITCH)
|
|
break;
|
|
return;
|
|
|
|
case TRIGGER_TYPES::CRAWLDUCK_T:
|
|
if (LaraItem->currentAnimState == LS_DOVESWITCH ||
|
|
LaraItem->currentAnimState == LS_CRAWL_IDLE ||
|
|
LaraItem->currentAnimState == LS_CRAWL_TURN_LEFT ||
|
|
LaraItem->currentAnimState == LS_CRAWL_TURN_RIGHT ||
|
|
LaraItem->currentAnimState == LS_CRAWL_BACK ||
|
|
LaraItem->currentAnimState == LS_CROUCH_IDLE ||
|
|
LaraItem->currentAnimState == LS_CROUCH_ROLL ||
|
|
LaraItem->currentAnimState == LS_CROUCH_TURN_LEFT ||
|
|
LaraItem->currentAnimState == LS_CROUCH_TURN_RIGHT)
|
|
break;
|
|
return;
|
|
|
|
case TRIGGER_TYPES::CLIMB_T:
|
|
if (LaraItem->currentAnimState == LS_HANG ||
|
|
LaraItem->currentAnimState == LS_LADDER_IDLE ||
|
|
LaraItem->currentAnimState == LS_LADDER_UP ||
|
|
LaraItem->currentAnimState == LS_LADDER_LEFT ||
|
|
LaraItem->currentAnimState == LS_LADDER_STOP ||
|
|
LaraItem->currentAnimState == LS_LADDER_RIGHT ||
|
|
LaraItem->currentAnimState == LS_LADDER_DOWN ||
|
|
LaraItem->currentAnimState == LS_MONKEYSWING_IDLE)
|
|
break;
|
|
return;
|
|
|
|
case TRIGGER_TYPES::PAD:
|
|
case TRIGGER_TYPES::ANTIPAD:
|
|
if (LaraItem->pos.yPos == LaraItem->floor)
|
|
break;
|
|
return;
|
|
|
|
case TRIGGER_TYPES::KEY:
|
|
value = *(data++) & 0x3FF;
|
|
keyResult = KeyTrigger(value);
|
|
if (keyResult != -1)
|
|
break;
|
|
return;
|
|
|
|
case TRIGGER_TYPES::PICKUP:
|
|
value = *(data++) & 0x3FF;
|
|
if (!PickupTrigger(value))
|
|
return;
|
|
break;
|
|
|
|
case TRIGGER_TYPES::COMBAT:
|
|
if (Lara.gunStatus == LG_READY)
|
|
break;
|
|
return;
|
|
|
|
// case TRIGGER_TYPES::SKELETON_T: //NO.
|
|
// Lara.skelebob = 2;
|
|
// break;
|
|
|
|
case TRIGGER_TYPES::HEAVY:
|
|
case TRIGGER_TYPES::DUMMY:
|
|
case TRIGGER_TYPES::HEAVYSWITCH:
|
|
case TRIGGER_TYPES::HEAVYANTITRIGGER:
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
short targetType = 0;
|
|
short trigger = 0;
|
|
|
|
ITEM_INFO *item = NULL;
|
|
ITEM_INFO *cameraItem = NULL;
|
|
|
|
do
|
|
{
|
|
trigger = *(data++);
|
|
value = trigger & VALUE_BITS;
|
|
targetType = (trigger >> 10) & 0xF;
|
|
|
|
switch (targetType)
|
|
{
|
|
case TO_OBJECT:
|
|
item = &g_Level.Items[value];
|
|
|
|
if (keyResult >= 2 ||
|
|
(triggerType == TRIGGER_TYPES::ANTIPAD ||
|
|
triggerType == TRIGGER_TYPES::ANTITRIGGER ||
|
|
triggerType == TRIGGER_TYPES::HEAVYANTITRIGGER) &&
|
|
item->flags & ATONESHOT)
|
|
break;
|
|
|
|
if (triggerType == TRIGGER_TYPES::SWITCH)
|
|
{
|
|
if (item->flags & SWONESHOT)
|
|
break;
|
|
if (item->objectNumber == ID_DART_EMITTER && item->active)
|
|
break;
|
|
}
|
|
|
|
item->timer = timer;
|
|
if (timer != 1)
|
|
item->timer = 30 * timer;
|
|
|
|
if (triggerType == TRIGGER_TYPES::SWITCH ||
|
|
triggerType == TRIGGER_TYPES::HEAVYSWITCH)
|
|
{
|
|
if (HeavyFlags >= 0)
|
|
{
|
|
if (switchFlag)
|
|
item->flags |= (flags & CODE_BITS);
|
|
else
|
|
item->flags ^= (flags & CODE_BITS);
|
|
|
|
if (flags & ONESHOT)
|
|
item->flags |= SWONESHOT;
|
|
}
|
|
else
|
|
{
|
|
if (((flags ^ item->flags) & CODE_BITS) == CODE_BITS)
|
|
{
|
|
item->flags ^= (flags & CODE_BITS);
|
|
if (flags & ONESHOT)
|
|
item->flags |= SWONESHOT;
|
|
}
|
|
}
|
|
}
|
|
else if (triggerType == TRIGGER_TYPES::ANTIPAD ||
|
|
triggerType == TRIGGER_TYPES::ANTITRIGGER ||
|
|
triggerType == TRIGGER_TYPES::HEAVYANTITRIGGER)
|
|
{
|
|
if (item->objectNumber == ID_EARTHQUAKE)
|
|
{
|
|
item->itemFlags[0] = 0;
|
|
item->itemFlags[1] = 100;
|
|
}
|
|
|
|
item->flags &= ~(CODE_BITS | REVERSE);
|
|
|
|
if (flags & ONESHOT)
|
|
item->flags |= ATONESHOT;
|
|
|
|
if (item->active && Objects[item->objectNumber].intelligent)
|
|
{
|
|
item->hitPoints = NOT_TARGETABLE;
|
|
DisableBaddieAI(value);
|
|
KillItem(value);
|
|
}
|
|
}
|
|
else if (flags & CODE_BITS)
|
|
{
|
|
item->flags |= flags & CODE_BITS;
|
|
}
|
|
|
|
if ((item->flags & CODE_BITS) == CODE_BITS)
|
|
{
|
|
item->flags |= 0x20;
|
|
|
|
if (flags & ONESHOT)
|
|
item->flags |= ONESHOT;
|
|
|
|
if (!(item->active) && !(item->flags & IFLAG_KILLED))
|
|
{
|
|
if (Objects[item->objectNumber].intelligent)
|
|
{
|
|
if (item->status != ITEM_NOT_ACTIVE)
|
|
{
|
|
if (item->status == ITEM_INVISIBLE)
|
|
{
|
|
item->touchBits = 0;
|
|
if (EnableBaddieAI(value, 0))
|
|
{
|
|
item->status = ITEM_ACTIVE;
|
|
AddActiveItem(value);
|
|
}
|
|
else
|
|
{
|
|
item->status == ITEM_INVISIBLE;
|
|
AddActiveItem(value);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
item->touchBits = 0;
|
|
item->status = ITEM_ACTIVE;
|
|
AddActiveItem(value);
|
|
EnableBaddieAI(value, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
item->touchBits = 0;
|
|
AddActiveItem(value);
|
|
item->status = ITEM_ACTIVE;
|
|
HeavyTriggered = heavy;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TO_CAMERA:
|
|
trigger = *(data++);
|
|
|
|
if (keyResult == 1)
|
|
break;
|
|
|
|
if (g_Level.Cameras[value].flags & 0x100)
|
|
break;
|
|
|
|
Camera.number = value;
|
|
|
|
if (Camera.type == LOOK_CAMERA || Camera.type == COMBAT_CAMERA && !(g_Level.Cameras[value].flags & 3))
|
|
break;
|
|
|
|
if (triggerType == TRIGGER_TYPES::COMBAT)
|
|
break;
|
|
|
|
if (triggerType == TRIGGER_TYPES::SWITCH && timer && switchOff)
|
|
break;
|
|
|
|
if (Camera.number != Camera.last || triggerType == TRIGGER_TYPES::SWITCH)
|
|
{
|
|
Camera.timer = (trigger & 0xFF) * 30;
|
|
|
|
if (trigger & 0x100)
|
|
g_Level.Cameras[Camera.number].flags |= ONESHOT;
|
|
|
|
Camera.speed = ((trigger & CODE_BITS) >> 6) + 1;
|
|
Camera.type = heavy ? HEAVY_CAMERA : FIXED_CAMERA;
|
|
}
|
|
break;
|
|
|
|
case TO_FLYBY:
|
|
trigger = *(data++);
|
|
|
|
if (keyResult == 1)
|
|
break;
|
|
|
|
if (triggerType == TRIGGER_TYPES::ANTIPAD ||
|
|
triggerType == TRIGGER_TYPES::ANTITRIGGER ||
|
|
triggerType == TRIGGER_TYPES::HEAVYANTITRIGGER)
|
|
UseSpotCam = false;
|
|
else
|
|
{
|
|
spotCamIndex = 0;
|
|
if (SpotCamRemap[value] != 0)
|
|
{
|
|
for (int i = 0; i < SpotCamRemap[value]; i++)
|
|
{
|
|
spotCamIndex += CameraCnt[i];
|
|
}
|
|
}
|
|
|
|
if (!(SpotCam[spotCamIndex].flags & SCF_CAMERA_ONE_SHOT))
|
|
{
|
|
if (trigger & 0x100)
|
|
SpotCam[spotCamIndex].flags |= SCF_CAMERA_ONE_SHOT;
|
|
|
|
if (!UseSpotCam || CurrentLevel == 0)
|
|
{
|
|
UseSpotCam = true;
|
|
if (LastSpotCam != value)
|
|
TrackCameraInit = false;
|
|
InitialiseSpotCam(value);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TO_TARGET:
|
|
cameraItem = &g_Level.Items[value];
|
|
break;
|
|
|
|
case TO_SINK:
|
|
Lara.currentActive = value + 1;
|
|
break;
|
|
|
|
case TO_FLIPMAP:
|
|
flipAvailable = true;
|
|
|
|
if (FlipMap[value] & 0x100)
|
|
break;
|
|
|
|
if (triggerType == TRIGGER_TYPES::SWITCH)
|
|
FlipMap[value] ^= (flags & CODE_BITS);
|
|
else if (flags & CODE_BITS)
|
|
FlipMap[value] |= (flags & CODE_BITS);
|
|
|
|
if ((FlipMap[value] & CODE_BITS) == CODE_BITS)
|
|
{
|
|
|
|
if (flags & 0x100)
|
|
FlipMap[value] |= 0x100;
|
|
if (!FlipStats[value])
|
|
flip = value;
|
|
}
|
|
else if (FlipStats[value])
|
|
flip = value;
|
|
break;
|
|
|
|
case TO_FLIPON:
|
|
flipAvailable = true;
|
|
FlipMap[value] |= CODE_BITS;
|
|
if (!FlipStats[value])
|
|
flip = value;
|
|
break;
|
|
|
|
case TO_FLIPOFF:
|
|
flipAvailable = true;
|
|
FlipMap[value] &= ~CODE_BITS;
|
|
if (FlipStats[value])
|
|
flip = value;
|
|
break;
|
|
|
|
case TO_FLIPEFFECT:
|
|
TriggerTimer = timer;
|
|
newEffect = value;
|
|
break;
|
|
|
|
case TO_FINISH:
|
|
RequiredStartPos = false;
|
|
LevelComplete = CurrentLevel + 1;
|
|
break;
|
|
|
|
case TO_CD:
|
|
PlaySoundTrack(value, flags);
|
|
break;
|
|
|
|
case TO_CUTSCENE:
|
|
// TODO: not used for now
|
|
break;
|
|
|
|
case TO_LUA_SCRIPT:
|
|
trigger = *(data++);
|
|
g_GameScript->ExecuteTrigger(trigger & 0x7FFF);
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
} while (!(trigger & 0x8000));
|
|
|
|
if (cameraItem && (Camera.type == FIXED_CAMERA || Camera.type == HEAVY_CAMERA))
|
|
Camera.item = cameraItem;
|
|
|
|
if (flip != -1)
|
|
DoFlipMap(flip);
|
|
|
|
if (newEffect != -1 && (flip || !flipAvailable))
|
|
{
|
|
FlipEffect = newEffect;
|
|
FlipTimer = 0;
|
|
}
|
|
}
|
|
|
|
void UpdateSky()
|
|
{
|
|
GameScriptLevel *level = g_GameFlow->GetLevel(CurrentLevel);
|
|
|
|
if (level->Layer1.Enabled)
|
|
{
|
|
SkyPos1 += level->Layer1.CloudSpeed;
|
|
if (SkyPos1 <= 9728)
|
|
{
|
|
if (SkyPos1 < 0)
|
|
SkyPos1 += 9728;
|
|
}
|
|
else
|
|
{
|
|
SkyPos1 -= 9728;
|
|
}
|
|
}
|
|
|
|
if (level->Layer2.Enabled)
|
|
{
|
|
SkyPos2 += level->Layer2.CloudSpeed;
|
|
if (SkyPos2 <= 9728)
|
|
{
|
|
if (SkyPos2 < 0)
|
|
SkyPos2 += 9728;
|
|
}
|
|
else
|
|
{
|
|
SkyPos2 -= 9728;
|
|
}
|
|
}
|
|
}
|
|
|
|
short GetDoor(FLOOR_INFO *floor)
|
|
{
|
|
if (!floor->index)
|
|
return NO_ROOM;
|
|
|
|
short *data = &g_Level.FloorData[floor->index];
|
|
short type = *(data++);
|
|
|
|
if (((type & DATA_TYPE) == TILT_TYPE) || ((type & DATA_TYPE) == SPLIT1) || ((type & DATA_TYPE) == SPLIT2) || ((type & DATA_TYPE) == NOCOLF1B) || ((type & DATA_TYPE) == NOCOLF1T) || ((type & DATA_TYPE) == NOCOLF2B) || ((type & DATA_TYPE) == NOCOLF2T))
|
|
{
|
|
if (type & END_BIT)
|
|
return NO_ROOM;
|
|
|
|
data++;
|
|
type = *(data++);
|
|
}
|
|
|
|
if (((type & DATA_TYPE) == ROOF_TYPE) || ((type & DATA_TYPE) == SPLIT3) || ((type & DATA_TYPE) == SPLIT4) || ((type & DATA_TYPE) == NOCOLC1B) || ((type & DATA_TYPE) == NOCOLC1T) || ((type & DATA_TYPE) == NOCOLC2B) || ((type & DATA_TYPE) == NOCOLC2T))
|
|
{
|
|
if (type & END_BIT)
|
|
return NO_ROOM;
|
|
|
|
data++;
|
|
type = *(data++);
|
|
}
|
|
|
|
if ((type & DATA_TYPE) == DOOR_TYPE)
|
|
return (*data);
|
|
|
|
return NO_ROOM;
|
|
}
|
|
|
|
void TranslateItem(ITEM_INFO *item, int x, int y, int z)
|
|
{
|
|
float c = phd_cos(item->pos.yRot);
|
|
float s = phd_sin(item->pos.yRot);
|
|
|
|
item->pos.xPos += c * x + s * z;
|
|
item->pos.yPos += y;
|
|
item->pos.zPos += -s * x + c * z;
|
|
}
|
|
|
|
int GetWaterSurface(int x, int y, int z, short roomNumber)
|
|
{
|
|
ROOM_INFO *room = &g_Level.Rooms[roomNumber];
|
|
FLOOR_INFO *floor = &XZ_GET_SECTOR(room, x - room->x, z - room->z);
|
|
|
|
if (room->flags & ENV_FLAG_WATER)
|
|
{
|
|
while (floor->skyRoom != NO_ROOM)
|
|
{
|
|
room = &g_Level.Rooms[floor->skyRoom];
|
|
if (!(room->flags & ENV_FLAG_WATER))
|
|
return (floor->ceiling * 256);
|
|
floor = &XZ_GET_SECTOR(room, x - room->x, z - room->z);
|
|
}
|
|
return NO_HEIGHT;
|
|
}
|
|
else
|
|
{
|
|
while (floor->pitRoom != NO_ROOM)
|
|
{
|
|
room = &g_Level.Rooms[floor->pitRoom];
|
|
if (room->flags & ENV_FLAG_WATER)
|
|
return (floor->floor * 256);
|
|
floor = &XZ_GET_SECTOR(room, x - room->x, z - room->z);
|
|
}
|
|
}
|
|
|
|
return NO_HEIGHT;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int GetChange(ITEM_INFO *item, ANIM_STRUCT *anim)
|
|
{
|
|
if (item->currentAnimState == item->goalAnimState)
|
|
return 0;
|
|
|
|
if (anim->numberChanges <= 0)
|
|
return 0;
|
|
|
|
for (int i = 0; i < anim->numberChanges; i++)
|
|
{
|
|
CHANGE_STRUCT *change = &g_Level.Changes[anim->changeIndex + i];
|
|
if (change->goalAnimState == item->goalAnimState)
|
|
{
|
|
for (int j = 0; j < change->numberRanges; j++)
|
|
{
|
|
RANGE_STRUCT *range = &g_Level.Ranges[change->rangeIndex + j];
|
|
if (item->frameNumber >= range->startFrame && item->frameNumber <= range->endFrame)
|
|
{
|
|
item->animNumber = range->linkAnimNum;
|
|
item->frameNumber = range->linkFrameNum;
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 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);
|
|
|
|
/*if (floor->floor == NO_HEIGHT / STEP_SIZE)
|
|
{
|
|
floor->floor = ceiling->ceiling + height / STEP_SIZE;
|
|
}
|
|
else
|
|
{
|
|
floor->floor += height / STEP_SIZE;
|
|
if (floor->floor == ceiling->ceiling && !flag)
|
|
floor->floor = NO_HEIGHT / STEP_SIZE;
|
|
}*/
|
|
|
|
floor->FloorCollision.Planes[0].z += height;
|
|
floor->FloorCollision.Planes[1].z += height;
|
|
|
|
box = &g_Level.Boxes[floor->box];
|
|
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)
|
|
{
|
|
#if 0
|
|
ROOM_INFO *r;
|
|
FLOOR_INFO *floor;
|
|
short data;
|
|
int xFloor = 0;
|
|
int yFloor = 0;
|
|
short roomDoor = 0;
|
|
int retval;
|
|
|
|
r = &g_Level.Rooms[*roomNumber];
|
|
do
|
|
{
|
|
xFloor = (z - r->z) / SECTOR(1);
|
|
yFloor = (x - r->x) / SECTOR(1);
|
|
|
|
if (xFloor <= 0)
|
|
{
|
|
xFloor = 0;
|
|
if (yFloor < 1)
|
|
yFloor = 1;
|
|
else if (yFloor > r->ySize - 2)
|
|
yFloor = r->ySize - 2;
|
|
}
|
|
else if (xFloor >= r->xSize - 1)
|
|
{
|
|
xFloor = r->xSize - 1;
|
|
if (yFloor < 1)
|
|
yFloor = 1;
|
|
else if (yFloor > r->ySize - 2)
|
|
yFloor = r->ySize - 2;
|
|
}
|
|
else if (yFloor < 0)
|
|
{
|
|
yFloor = 0;
|
|
}
|
|
else if (yFloor >= r->ySize)
|
|
{
|
|
yFloor = r->ySize - 1;
|
|
}
|
|
|
|
floor = &r->floor[xFloor + (yFloor * r->xSize)];
|
|
data = GetDoor(floor);
|
|
if (data != NO_ROOM)
|
|
{
|
|
*roomNumber = data;
|
|
r = &g_Level.Rooms[data];
|
|
}
|
|
} while (data != NO_ROOM);
|
|
|
|
if (y < floor->floor * 256)
|
|
{
|
|
if (y < floor->ceiling * 256 && floor->skyRoom != NO_ROOM)
|
|
{
|
|
do
|
|
{
|
|
int noCollision = CheckNoColCeilingTriangle(floor, x, z);
|
|
if (noCollision == 1 || noCollision == -1 && y >= r->maxceiling)
|
|
break;
|
|
|
|
*roomNumber = floor->skyRoom;
|
|
r = &g_Level.Rooms[floor->skyRoom];
|
|
floor = &XZ_GET_SECTOR(r, x - r->x, z - r->z);
|
|
if (y >= floor->ceiling * 256)
|
|
break;
|
|
} while (floor->skyRoom != NO_ROOM);
|
|
}
|
|
}
|
|
else if (floor->pitRoom != NO_ROOM)
|
|
{
|
|
do
|
|
{
|
|
int noCollision = CheckNoColFloorTriangle(floor, x, z);
|
|
if (noCollision == 1 || noCollision == -1 && y < r->minfloor)
|
|
break;
|
|
|
|
*roomNumber = floor->pitRoom;
|
|
r = &g_Level.Rooms[floor->pitRoom];
|
|
floor = &XZ_GET_SECTOR(r, x - r->x, z - r->z);
|
|
if (y < floor->floor * 256)
|
|
break;
|
|
} while (floor->pitRoom != NO_ROOM);
|
|
}
|
|
|
|
return floor;
|
|
#endif
|
|
|
|
const auto location = GetRoom(ROOM_VECTOR{*roomNumber, y}, x, y, z);
|
|
*roomNumber = location.roomNumber;
|
|
return &GetFloor(*roomNumber, x, z);
|
|
}
|
|
|
|
int CheckNoColFloorTriangle(FLOOR_INFO *floor, int x, int z)
|
|
{
|
|
if (!floor->index)
|
|
return 0;
|
|
|
|
short *data = &g_Level.FloorData[floor->index];
|
|
short type = *(data)&DATA_TYPE;
|
|
|
|
if (type == NOCOLF1T || type == NOCOLF1B || type == NOCOLF2T || type == NOCOLF2B)
|
|
{
|
|
int dx = x & 1023;
|
|
int dz = z & 1023;
|
|
|
|
if (type == NOCOLF1T && dx <= (SECTOR(1) - dz))
|
|
return -1;
|
|
else if (type == NOCOLF1B && dx > (SECTOR(1) - dz))
|
|
return -1;
|
|
else if (type == NOCOLF2T && dx <= dz)
|
|
return -1;
|
|
else if (type == NOCOLF2B && dx > dz)
|
|
return -1;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int CheckNoColCeilingTriangle(FLOOR_INFO *floor, int x, int z)
|
|
{
|
|
if (!floor->index)
|
|
return 0;
|
|
|
|
short *data = &g_Level.FloorData[floor->index];
|
|
short type = *(data)&DATA_TYPE;
|
|
|
|
if (type == TILT_TYPE || type == SPLIT1 || type == SPLIT2 || type == NOCOLF1T || type == NOCOLF1B || type == NOCOLF2T || type == NOCOLF2B) // gibby
|
|
{
|
|
if (*(data)&END_BIT)
|
|
return 0;
|
|
type = *(data + 2) & DATA_TYPE;
|
|
}
|
|
|
|
if (type == NOCOLC1T || type == NOCOLC1B || type == NOCOLC2T || type == NOCOLC2B)
|
|
{
|
|
int dx = x & 1023;
|
|
int dz = z & 1023;
|
|
|
|
if (type == NOCOLC1T && dx <= (SECTOR(1) - dz))
|
|
return -1;
|
|
else if (type == NOCOLC1B && dx > (SECTOR(1) - dz))
|
|
return -1;
|
|
else if (type == NOCOLC2T && dx <= dz)
|
|
return -1;
|
|
else if (type == NOCOLC2B && dx > dz)
|
|
return -1;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int GetFloorHeight(FLOOR_INFO *floor, int x, int y, int z)
|
|
{
|
|
|
|
TiltYOffset = 0;
|
|
TiltXOffset = 0;
|
|
OnObject = 0;
|
|
HeightType = WALL;
|
|
SplitFloor = 0;
|
|
|
|
ROOM_INFO *r;
|
|
while (floor->pitRoom != NO_ROOM)
|
|
{
|
|
if (CheckNoColFloorTriangle(floor, x, z) == 1)
|
|
break;
|
|
r = &g_Level.Rooms[floor->pitRoom];
|
|
floor = &XZ_GET_SECTOR(r, x - r->x, z - r->z);
|
|
}
|
|
|
|
int height = floor->floor * 256;
|
|
if (height != NO_HEIGHT)
|
|
{
|
|
// return height;
|
|
|
|
|
|
TriggerIndex = NULL;
|
|
|
|
if (floor->index != 0)
|
|
{
|
|
// return height;
|
|
|
|
short* data = &g_Level.FloorData[floor->index];
|
|
short type, hadj;
|
|
|
|
int xOff, yOff, trigger;
|
|
ITEM_INFO* item;
|
|
OBJECT_INFO* obj;
|
|
int tilts, t0, t1, t2, t3, t4, dx, dz, h1, h2;
|
|
do
|
|
{
|
|
type = *(data++);
|
|
|
|
switch (type & DATA_TYPE)
|
|
{
|
|
case DOOR_TYPE:
|
|
case ROOF_TYPE:
|
|
case SPLIT3:
|
|
case SPLIT4:
|
|
case NOCOLC1T:
|
|
case NOCOLC1B:
|
|
case NOCOLC2T:
|
|
case NOCOLC2B:
|
|
data++;
|
|
break;
|
|
|
|
case TILT_TYPE:
|
|
TiltXOffset = xOff = (*data >> 8);
|
|
TiltYOffset = yOff = *(char*)data;
|
|
|
|
if ((abs(xOff)) > 2 || (abs(yOff)) > 2)
|
|
HeightType = BIG_SLOPE;
|
|
else
|
|
HeightType = SMALL_SLOPE;
|
|
|
|
if (xOff >= 0)
|
|
height += (xOff * ((-1 - z) & 1023) >> 2);
|
|
else
|
|
height -= (xOff * (z & 1023) >> 2);
|
|
|
|
if (yOff >= 0)
|
|
height += yOff * ((-1 - x) & 1023) >> 2;
|
|
else
|
|
height -= yOff * (x & 1023) >> 2;
|
|
|
|
data++;
|
|
break;
|
|
|
|
case TRIGGER_TYPE:
|
|
if (!TriggerIndex)
|
|
TriggerIndex = data - 1;
|
|
|
|
data++;
|
|
|
|
do
|
|
{
|
|
trigger = *(data++);
|
|
|
|
if (TRIG_BITS(trigger) != TO_OBJECT)
|
|
{
|
|
if (TRIG_BITS(trigger) == TO_CAMERA ||
|
|
TRIG_BITS(trigger) == TO_FLYBY)
|
|
{
|
|
trigger = *(data++);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*item = &g_Level.Items[trigger & VALUE_BITS];
|
|
obj = &Objects[item->objectNumber];
|
|
|
|
if (obj->floor && !(item->flags & 0x8000))
|
|
{
|
|
(obj->floor)(item, x, y, z, &height);
|
|
}*/
|
|
}
|
|
|
|
} while (!(trigger & END_BIT));
|
|
break;
|
|
|
|
case LAVA_TYPE:
|
|
TriggerIndex = data - 1;
|
|
break;
|
|
|
|
case CLIMB_TYPE:
|
|
case MONKEY_TYPE:
|
|
case TRIGTRIGGER_TYPE:
|
|
case MINER_TYPE:
|
|
if (!TriggerIndex)
|
|
TriggerIndex = data - 1;
|
|
break;
|
|
|
|
case SPLIT1:
|
|
case SPLIT2:
|
|
case NOCOLF1T:
|
|
case NOCOLF1B:
|
|
case NOCOLF2T:
|
|
case NOCOLF2B:
|
|
tilts = *data;
|
|
t0 = tilts & 15;
|
|
t1 = (tilts >> 4) & 15;
|
|
t2 = (tilts >> 8) & 15;
|
|
t3 = (tilts >> 12) & 15;
|
|
|
|
dx = x & 1023;
|
|
dz = z & 1023;
|
|
|
|
xOff = yOff = 0;
|
|
|
|
HeightType = SPLIT_TRI;
|
|
SplitFloor = (type & DATA_TYPE);
|
|
|
|
if ((type & DATA_TYPE) == SPLIT1 ||
|
|
(type & DATA_TYPE) == NOCOLF1T ||
|
|
(type & DATA_TYPE) == NOCOLF1B)
|
|
{
|
|
if (dx <= (1024 - dz))
|
|
{
|
|
hadj = (type >> 10) & 0x1F;
|
|
if (hadj & 0x10)
|
|
hadj |= 0xfff0;
|
|
height += 256 * hadj;
|
|
xOff = t2 - t1;
|
|
yOff = t0 - t1;
|
|
}
|
|
else
|
|
{
|
|
hadj = (type >> 5) & 0x1F;
|
|
if (hadj & 0x10)
|
|
hadj |= 0xFFF0;
|
|
height += 256 * hadj;
|
|
xOff = t3 - t0;
|
|
yOff = t3 - t2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dx <= dz)
|
|
{
|
|
hadj = (type >> 10) & 0x1f;
|
|
if (hadj & 0x10)
|
|
hadj |= 0xfff0;
|
|
height += 256 * hadj;
|
|
xOff = t2 - t1;
|
|
yOff = t3 - t2;
|
|
}
|
|
else
|
|
{
|
|
hadj = (type >> 5) & 0x1f;
|
|
if (hadj & 0x10)
|
|
hadj |= 0xfff0;
|
|
height += 256 * hadj;
|
|
xOff = t3 - t0;
|
|
yOff = t0 - t1;
|
|
}
|
|
}
|
|
|
|
TiltXOffset = xOff;
|
|
TiltYOffset = yOff;
|
|
|
|
if ((abs(xOff)) > 2 || (abs(yOff)) > 2)
|
|
HeightType = DIAGONAL;
|
|
else if (HeightType != SPLIT_TRI)
|
|
HeightType = SMALL_SLOPE;
|
|
|
|
if (xOff >= 0)
|
|
height += xOff * ((-1 - z) & 1023) >> 2;
|
|
else
|
|
height -= xOff * (z & 1023) >> 2;
|
|
|
|
if (yOff >= 0)
|
|
height += yOff * ((-1 - x) & 1023) >> 2;
|
|
else
|
|
height -= yOff * (x & 1023) >> 2;
|
|
|
|
data++;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} while (!(type & END_BIT));
|
|
}
|
|
}
|
|
/*return height;*/
|
|
|
|
return GetFloorHeight(ROOM_VECTOR{floor->Room, y}, x, z).value_or(NO_HEIGHT);
|
|
}
|
|
|
|
int LOS(GAME_VECTOR *start, GAME_VECTOR *end)
|
|
{
|
|
int result1, result2;
|
|
|
|
end->roomNumber = start->roomNumber;
|
|
if (abs(end->z - start->z) > abs(end->x - start->x))
|
|
{
|
|
result1 = xLOS(start, end);
|
|
result2 = zLOS(start, end);
|
|
}
|
|
else
|
|
{
|
|
result1 = zLOS(start, end);
|
|
result2 = xLOS(start, end);
|
|
}
|
|
if (result2)
|
|
{
|
|
GetFloor(end->x, end->y, end->z, &end->roomNumber);
|
|
if (ClipTarget(start, end) && result1 == 1 && result2 == 1)
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int xLOS(GAME_VECTOR *start, GAME_VECTOR *end)
|
|
{
|
|
int dx, dy, dz, x, y, z, flag;
|
|
short room, room2;
|
|
FLOOR_INFO *floor;
|
|
|
|
dx = end->x - start->x;
|
|
if (!dx)
|
|
return 1;
|
|
dy = ((end->y - start->y) * 1024) / dx;
|
|
dz = ((end->z - start->z) * 1024) / dx;
|
|
number_los_rooms = 1;
|
|
los_rooms[0] = start->roomNumber;
|
|
room = start->roomNumber;
|
|
room2 = start->roomNumber;
|
|
flag = 1;
|
|
if (dx < 0)
|
|
{
|
|
x = start->x & 0xFFFFFC00;
|
|
y = (((x - start->x) * dy) / 1024) + start->y;
|
|
z = (((x - start->x) * dz) / 1024) + start->z;
|
|
while (x > end->x)
|
|
{
|
|
floor = GetFloor(x, y, z, &room);
|
|
if (room != room2)
|
|
{
|
|
room2 = room;
|
|
los_rooms[number_los_rooms] = room;
|
|
++number_los_rooms;
|
|
}
|
|
if (y > GetFloorHeight(floor, x, y, z) || y < GetCeiling(floor, x, y, z))
|
|
{
|
|
flag = -1;
|
|
break;
|
|
}
|
|
floor = GetFloor(x - 1, y, z, &room);
|
|
if (room != room2)
|
|
{
|
|
room2 = room;
|
|
los_rooms[number_los_rooms] = room;
|
|
++number_los_rooms;
|
|
}
|
|
if (y > GetFloorHeight(floor, x - 1, y, z) || y < GetCeiling(floor, x - 1, y, z))
|
|
{
|
|
flag = 0;
|
|
break;
|
|
}
|
|
x -= 1024;
|
|
y -= dy;
|
|
z -= dz;
|
|
}
|
|
if (flag != 1)
|
|
{
|
|
end->x = x;
|
|
end->y = y;
|
|
end->z = z;
|
|
}
|
|
end->roomNumber = flag ? room : room2;
|
|
}
|
|
else
|
|
{
|
|
x = start->x | 0x3FF;
|
|
y = (((x - start->x) * dy) / 1024) + start->y;
|
|
z = (((x - start->x) * dz) / 1024) + start->z;
|
|
while (x < end->x)
|
|
{
|
|
floor = GetFloor(x, y, z, &room);
|
|
if (room != room2)
|
|
{
|
|
room2 = room;
|
|
los_rooms[number_los_rooms] = room;
|
|
++number_los_rooms;
|
|
}
|
|
if (y > GetFloorHeight(floor, x, y, z) || y < GetCeiling(floor, x, y, z))
|
|
{
|
|
flag = -1;
|
|
break;
|
|
}
|
|
floor = GetFloor(x + 1, y, z, &room);
|
|
if (room != room2)
|
|
{
|
|
room2 = room;
|
|
los_rooms[number_los_rooms] = room;
|
|
++number_los_rooms;
|
|
}
|
|
if (y > GetFloorHeight(floor, x + 1, y, z) || y < GetCeiling(floor, x + 1, y, z))
|
|
{
|
|
flag = 0;
|
|
break;
|
|
}
|
|
x += 1024;
|
|
y += dy;
|
|
z += dz;
|
|
}
|
|
if (flag != 1)
|
|
{
|
|
end->x = x;
|
|
end->y = y;
|
|
end->z = z;
|
|
}
|
|
end->roomNumber = flag ? room : room2;
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
int zLOS(GAME_VECTOR *start, GAME_VECTOR *end)
|
|
{
|
|
int dx, dy, dz, x, y, z, flag;
|
|
short room, room2;
|
|
FLOOR_INFO *floor;
|
|
|
|
dz = end->z - start->z;
|
|
if (!dz)
|
|
return 1;
|
|
dx = ((end->x - start->x) * 1024) / dz;
|
|
dy = ((end->y - start->y) * 1024) / dz;
|
|
number_los_rooms = 1;
|
|
los_rooms[0] = start->roomNumber;
|
|
room = start->roomNumber;
|
|
room2 = start->roomNumber;
|
|
flag = 1;
|
|
if (dz < 0)
|
|
{
|
|
z = start->z & 0xFFFFFC00;
|
|
x = (((z - start->z) * dx) / 1024) + start->x;
|
|
y = (((z - start->z) * dy) / 1024) + start->y;
|
|
while (z > end->z)
|
|
{
|
|
floor = GetFloor(x, y, z, &room);
|
|
if (room != room2)
|
|
{
|
|
room2 = room;
|
|
los_rooms[number_los_rooms] = room;
|
|
++number_los_rooms;
|
|
}
|
|
if (y > GetFloorHeight(floor, x, y, z) || y < GetCeiling(floor, x, y, z))
|
|
{
|
|
flag = -1;
|
|
break;
|
|
}
|
|
floor = GetFloor(x, y, z - 1, &room);
|
|
if (room != room2)
|
|
{
|
|
room2 = room;
|
|
los_rooms[number_los_rooms] = room;
|
|
++number_los_rooms;
|
|
}
|
|
if (y > GetFloorHeight(floor, x, y, z - 1) || y < GetCeiling(floor, x, y, z - 1))
|
|
{
|
|
flag = 0;
|
|
break;
|
|
}
|
|
z -= 1024;
|
|
x -= dx;
|
|
y -= dy;
|
|
}
|
|
if (flag != 1)
|
|
{
|
|
end->x = x;
|
|
end->y = y;
|
|
end->z = z;
|
|
}
|
|
end->roomNumber = flag ? room : room2;
|
|
}
|
|
else
|
|
{
|
|
z = start->z | 0x3FF;
|
|
x = (((z - start->z) * dx) / 1024) + start->x;
|
|
y = (((z - start->z) * dy) / 1024) + start->y;
|
|
while (z < end->z)
|
|
{
|
|
floor = GetFloor(x, y, z, &room);
|
|
if (room != room2)
|
|
{
|
|
room2 = room;
|
|
los_rooms[number_los_rooms] = room;
|
|
++number_los_rooms;
|
|
}
|
|
if (y > GetFloorHeight(floor, x, y, z) || y < GetCeiling(floor, x, y, z))
|
|
{
|
|
flag = -1;
|
|
break;
|
|
}
|
|
floor = GetFloor(x, y, z + 1, &room);
|
|
if (room != room2)
|
|
{
|
|
room2 = room;
|
|
los_rooms[number_los_rooms] = room;
|
|
++number_los_rooms;
|
|
}
|
|
if (y > GetFloorHeight(floor, x, y, z + 1) || y < GetCeiling(floor, x, y, z + 1))
|
|
{
|
|
flag = 0;
|
|
break;
|
|
}
|
|
z += 1024;
|
|
x += dx;
|
|
y += dy;
|
|
}
|
|
if (flag != 1)
|
|
{
|
|
end->x = x;
|
|
end->y = y;
|
|
end->z = z;
|
|
}
|
|
end->roomNumber = flag ? room : room2;
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
int ClipTarget(GAME_VECTOR *start, GAME_VECTOR *target)
|
|
{
|
|
short room;
|
|
int x, y, z, wx, wy, wz;
|
|
|
|
room = target->roomNumber;
|
|
if (target->y > GetFloorHeight(GetFloor(target->x, target->y, target->z, &room), target->x, target->y, target->z))
|
|
{
|
|
x = ((7 * (target->x - start->x)) / 8) + start->x;
|
|
y = ((7 * (target->y - start->y)) / 8) + start->y;
|
|
z = ((7 * (target->z - start->z)) / 8) + start->z;
|
|
for (int i = 3; i > 0; --i)
|
|
{
|
|
wx = (((target->x - x) * i) / 4) + x;
|
|
wy = (((target->y - y) * i) / 4) + y;
|
|
wz = (((target->z - z) * i) / 4) + z;
|
|
if (wy < GetFloorHeight(GetFloor(wx, wy, wz, &room), wx, wy, wz))
|
|
break;
|
|
}
|
|
target->x = wx;
|
|
target->y = wy;
|
|
target->z = wz;
|
|
target->roomNumber = room;
|
|
return 0;
|
|
}
|
|
room = target->roomNumber;
|
|
if (target->y < GetCeiling(GetFloor(target->x, target->y, target->z, &room), target->x, target->y, target->z))
|
|
{
|
|
x = ((7 * (target->x - start->x)) / 8) + start->x;
|
|
y = ((7 * (target->y - start->y)) / 8) + start->y;
|
|
z = ((7 * (target->z - start->z)) / 8) + start->z;
|
|
for (int i = 3; i > 0; --i)
|
|
{
|
|
wx = (((target->x - x) * i) / 4) + x;
|
|
wy = (((target->y - y) * i) / 4) + y;
|
|
wz = (((target->z - z) * i) / 4) + z;
|
|
if (wy > GetCeiling(GetFloor(wx, wy, wz, &room), wx, wy, wz))
|
|
break;
|
|
}
|
|
target->x = wx;
|
|
target->y = wy;
|
|
target->z = wz;
|
|
target->roomNumber = room;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void FireCrossBowFromLaserSight(GAME_VECTOR* src, GAME_VECTOR* target)
|
|
{
|
|
short angles[2];
|
|
PHD_3DPOS pos;
|
|
|
|
/* this part makes arrows fire at bad angles
|
|
target->x &= ~1023;
|
|
target->z &= ~1023;
|
|
target->x |= 512;
|
|
target->z |= 512;*/
|
|
|
|
phd_GetVectorAngles(target->x - src->x, target->y - src->y, target->z - src->z, &angles[0]);
|
|
|
|
pos.xPos = src->x;
|
|
pos.yPos = src->y;
|
|
pos.zPos = src->z;
|
|
pos.xRot = angles[1];
|
|
pos.yRot = angles[0];
|
|
pos.zRot = 0;
|
|
|
|
FireCrossbow(&pos);
|
|
}
|
|
|
|
int GetTargetOnLOS(GAME_VECTOR *src, GAME_VECTOR *dest, int DrawTarget, int firing)
|
|
{
|
|
GAME_VECTOR target;
|
|
int result, hit, itemNumber, count;
|
|
MESH_INFO *mesh;
|
|
PHD_VECTOR vector;
|
|
ITEM_INFO *item;
|
|
short angle, room, triggerItems[8];
|
|
VECTOR dir;
|
|
Vector3 direction = Vector3(dest->x, dest->y, dest->z) - Vector3(src->x, src->y, src->z);
|
|
direction.Normalize();
|
|
target.x = dest->x;
|
|
target.y = dest->y;
|
|
target.z = dest->z;
|
|
|
|
result = LOS(src, &target);
|
|
|
|
GetFloor(target.x, target.y, target.z, &target.roomNumber);
|
|
|
|
if (firing && LaserSight)
|
|
{
|
|
Lara.hasFired = true;
|
|
Lara.fired = true;
|
|
|
|
if (Lara.gunType == WEAPON_REVOLVER)
|
|
{
|
|
SoundEffect(SFX_TR4_DESSERT_EAGLE_FIRE, NULL, 0);
|
|
}
|
|
}
|
|
|
|
hit = 0;
|
|
itemNumber = ObjectOnLOS2(src, dest, &vector, &mesh);
|
|
if (itemNumber != 999)
|
|
{
|
|
target.x = vector.x - ((vector.x - src->x) / 32);
|
|
target.y = vector.y - ((vector.y - src->y) / 32);
|
|
target.z = vector.z - ((vector.z - src->z) / 32);
|
|
|
|
GetFloor(target.x, target.y, target.z, &target.roomNumber);
|
|
|
|
// TODO: for covering scientist
|
|
// if ((itemNumber >= 0) && (BaddieSlots[itemNumber].itemNum != NO_ITEM)) // BUGFIX: ensure target has AI. No more pistol desync and camera wobble when shooting non-AI movable objects.
|
|
// Lara.target = &g_Level.Items[itemNumber];
|
|
// this is crashing and it's not really doing anything..
|
|
|
|
if (firing)
|
|
{
|
|
if (Lara.gunType != WEAPON_CROSSBOW)
|
|
{
|
|
if (itemNumber < 0)
|
|
{
|
|
if (mesh->staticNumber >= 50 && mesh->staticNumber < 58)
|
|
{
|
|
ShatterImpactData.impactDirection = direction;
|
|
ShatterImpactData.impactLocation = Vector3(mesh->x, mesh->y, mesh->z);
|
|
ShatterObject(NULL, mesh, 128, target.roomNumber, 0);
|
|
SmashedMeshRoom[SmashedMeshCount] = target.roomNumber;
|
|
SmashedMesh[SmashedMeshCount] = mesh;
|
|
++SmashedMeshCount;
|
|
mesh->flags &= ~0x1;
|
|
SoundEffect(ShatterSounds[CurrentLevel - 5][mesh->staticNumber], (PHD_3DPOS *)mesh, 0);
|
|
}
|
|
TriggerRicochetSpark(&target, LaraItem->pos.yRot, 3, 0);
|
|
TriggerRicochetSpark(&target, LaraItem->pos.yRot, 3, 0);
|
|
}
|
|
else
|
|
{
|
|
item = &g_Level.Items[itemNumber];
|
|
if (item->objectNumber < ID_SHOOT_SWITCH1 || item->objectNumber > ID_SHOOT_SWITCH4)
|
|
{
|
|
if ((Objects[item->objectNumber].explodableMeshbits & ShatterItem.bit)
|
|
&& LaserSight)
|
|
{
|
|
//if (!Objects[item->objectNumber].intelligent)
|
|
//{
|
|
item->meshBits &= ~ShatterItem.bit;
|
|
ShatterImpactData.impactDirection = direction;
|
|
ShatterImpactData.impactLocation = Vector3(ShatterItem.sphere.x, ShatterItem.sphere.y, ShatterItem.sphere.z);
|
|
ShatterObject(&ShatterItem, 0, 128, target.roomNumber, 0);
|
|
TriggerRicochetSpark(&target, LaraItem->pos.yRot, 3, 0);
|
|
/*}
|
|
else
|
|
{
|
|
if (item->objectNumber != ID_GUARD_LASER)
|
|
{
|
|
item->hitPoints -= 30;
|
|
if (item->hitPoints < 0)
|
|
item->hitPoints = 0;
|
|
HitTarget(item, &target, Weapons[Lara.gunType].damage, 0);
|
|
}
|
|
else
|
|
{
|
|
angle = phd_atan(LaraItem->pos.zPos - item->pos.zPos, LaraItem->pos.xPos - item->pos.xPos) - item->pos.yRot;
|
|
if (angle > -ANGLE(90) && angle < ANGLE(90))
|
|
{
|
|
item->hitPoints = 0;
|
|
HitTarget(item, &target, Weapons[Lara.gunType].damage, 0);
|
|
}
|
|
}
|
|
}*/
|
|
}
|
|
else
|
|
{
|
|
if (DrawTarget && (Lara.gunType == WEAPON_REVOLVER || Lara.gunType == WEAPON_HK))
|
|
{
|
|
if (Objects[item->objectNumber].intelligent)
|
|
{
|
|
HitTarget(item, &target, Weapons[Lara.gunType].damage, 0);
|
|
}
|
|
else
|
|
{
|
|
// TR5
|
|
if (Objects[item->objectNumber].hitEffect == HIT_RICOCHET)
|
|
TriggerRicochetSpark(&target, LaraItem->pos.yRot, 3, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (item->objectNumber >= ID_SMASH_OBJECT1 && item->objectNumber <= ID_SMASH_OBJECT8)
|
|
{
|
|
SmashObject(itemNumber);
|
|
}
|
|
else
|
|
{
|
|
if (Objects[item->objectNumber].hitEffect == HIT_BLOOD)
|
|
{
|
|
DoBloodSplat(target.x, target.y, target.z, (GetRandomControl() & 3) + 3, item->pos.yRot, item->roomNumber);
|
|
}
|
|
else if (Objects[item->objectNumber].hitEffect == HIT_SMOKE)
|
|
{
|
|
TriggerRicochetSpark(&target, LaraItem->pos.yRot, 3, -5);
|
|
}
|
|
else if (Objects[item->objectNumber].hitEffect == HIT_RICOCHET)
|
|
{
|
|
TriggerRicochetSpark(&target, LaraItem->pos.yRot, 3, 0);
|
|
}
|
|
item->hitStatus = true;
|
|
if (!Objects[item->objectNumber].undead)
|
|
{
|
|
item->hitPoints -= Weapons[Lara.gunType].damage;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ShatterItem.bit == 1 << Objects[item->objectNumber].nmeshes - 1)
|
|
{
|
|
if (!(item->flags & 0x40))
|
|
{
|
|
if (item->objectNumber == ID_SHOOT_SWITCH1)
|
|
ExplodeItemNode(item, Objects[item->objectNumber].nmeshes - 1, 0, 64);
|
|
if (item->triggerFlags == 444 && item->objectNumber == ID_SHOOT_SWITCH2)
|
|
{
|
|
// TR5 ID_SWITCH_TYPE_8/ID_SHOOT_SWITCH2
|
|
ProcessExplodingSwitchType8(item);
|
|
}
|
|
else
|
|
{
|
|
/*if (item->objectNumber == ID_SHOOT_SWITCH3)
|
|
{
|
|
// TR4 ID_SWITCH_TYPE7
|
|
ExplodeItemNode(item, Objects[item->objectNumber].nmeshes - 1, 0, 64);
|
|
}*/
|
|
|
|
if (item->flags & IFLAG_ACTIVATION_MASK && (item->flags & IFLAG_ACTIVATION_MASK) != IFLAG_ACTIVATION_MASK)
|
|
{
|
|
room = item->roomNumber;
|
|
GetFloorHeight(GetFloor(item->pos.xPos, item->pos.yPos - 256, item->pos.zPos, &room), item->pos.xPos, item->pos.yPos - 256, item->pos.zPos);
|
|
TestTriggers(TriggerIndex, 1, item->flags & IFLAG_ACTIVATION_MASK);
|
|
}
|
|
else
|
|
{
|
|
for (count = GetSwitchTrigger(item, triggerItems, 1); count > 0; --count)
|
|
{
|
|
AddActiveItem(triggerItems[count - 1]);
|
|
g_Level.Items[triggerItems[count - 1]].status = ITEM_ACTIVE;
|
|
g_Level.Items[triggerItems[count - 1]].flags |= IFLAG_ACTIVATION_MASK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (item->status != ITEM_DEACTIVATED)
|
|
{
|
|
AddActiveItem(itemNumber);
|
|
item->status = ITEM_ACTIVE;
|
|
item->flags |= IFLAG_ACTIVATION_MASK | 0x40;
|
|
}
|
|
}
|
|
TriggerRicochetSpark(&target, LaraItem->pos.yRot, 3, 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (LaserSight && firing)
|
|
{
|
|
FireCrossBowFromLaserSight(src, &target);
|
|
}
|
|
}
|
|
}
|
|
|
|
hit = 1;
|
|
}
|
|
else
|
|
{
|
|
if (Lara.gunType == WEAPON_CROSSBOW)
|
|
{
|
|
if (firing && LaserSight)
|
|
FireCrossBowFromLaserSight(src, &target);
|
|
}
|
|
else
|
|
{
|
|
target.x -= (target.x - src->x) / 32;
|
|
target.y -= (target.y - src->y) / 32;
|
|
target.z -= (target.z - src->z) / 32;
|
|
if (firing && !result)
|
|
TriggerRicochetSpark(&target, LaraItem->pos.yRot, 8, 0);
|
|
}
|
|
}
|
|
|
|
if (DrawTarget && (hit || !result))
|
|
{
|
|
TriggerDynamicLight(target.x, target.y, target.z, 64, 255, 0, 0);
|
|
LaserSightActive = 1;
|
|
LaserSightX = target.x;
|
|
LaserSightY = target.y;
|
|
LaserSightZ = target.z;
|
|
}
|
|
|
|
return hit;
|
|
}
|
|
|
|
int ObjectOnLOS2(GAME_VECTOR *start, GAME_VECTOR *end, PHD_VECTOR *vec, MESH_INFO **mesh)
|
|
{
|
|
int r, m;
|
|
ROOM_INFO *room;
|
|
short linknum;
|
|
ITEM_INFO *item;
|
|
PHD_3DPOS pos;
|
|
MESH_INFO *meshp;
|
|
BOUNDING_BOX* box;
|
|
|
|
ClosestItem = 999;
|
|
ClosestDist = SQUARE(end->x - start->x) + SQUARE(end->y - start->y) + SQUARE(end->z - start->z);
|
|
|
|
for (r = 0; r < number_los_rooms; ++r)
|
|
{
|
|
room = &g_Level.Rooms[los_rooms[r]];
|
|
|
|
for (m = 0; m < room->mesh.size(); m++)
|
|
{
|
|
meshp = &room->mesh[m];
|
|
|
|
if (meshp->flags & 1)
|
|
{
|
|
pos.xPos = meshp->x;
|
|
pos.yPos = meshp->y;
|
|
pos.zPos = meshp->z;
|
|
pos.yRot = meshp->yRot;
|
|
|
|
if (DoRayBox(start, end, &StaticObjects[meshp->staticNumber].collisionBox, &pos, vec, -1 - meshp->staticNumber))
|
|
{
|
|
*mesh = meshp;
|
|
end->roomNumber = los_rooms[r];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (linknum = room->itemNumber; linknum != NO_ITEM; linknum = g_Level.Items[linknum].nextItem)
|
|
{
|
|
item = &g_Level.Items[linknum];
|
|
|
|
if (item->status != ITEM_DEACTIVATED && item->status != ITEM_INVISIBLE
|
|
&& (item->objectNumber != ID_LARA
|
|
&& Objects[item->objectNumber].collision != NULL
|
|
|| item->objectNumber == ID_LARA
|
|
&& GetLaraOnLOS))
|
|
{
|
|
box = GetBoundsAccurate(item);
|
|
|
|
pos.xPos = item->pos.xPos;
|
|
pos.yPos = item->pos.yPos;
|
|
pos.zPos = item->pos.zPos;
|
|
pos.yRot = item->pos.yRot;
|
|
|
|
if (DoRayBox(start, end, box, &pos, vec, linknum))
|
|
{
|
|
end->roomNumber = los_rooms[r];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
vec->x = ClosestCoord.x;
|
|
vec->y = ClosestCoord.y;
|
|
vec->z = ClosestCoord.z;
|
|
|
|
return ClosestItem;
|
|
}
|
|
|
|
int GetRandomControl()
|
|
{
|
|
return generateInt();
|
|
}
|
|
|
|
int GetRandomDraw()
|
|
{
|
|
return generateInt();
|
|
}
|
|
|
|
int GetCeiling(FLOOR_INFO *floor, int x, int y, int z)
|
|
{
|
|
#if 0
|
|
ROOM_INFO *room;
|
|
FLOOR_INFO *floor2;
|
|
int ceiling, t0, t1, t2, t3, dx, dz, xOff, yOff;
|
|
short type, type2, function, cadj, trigger, *data;
|
|
bool end;
|
|
ITEM_INFO *item;
|
|
|
|
SplitCeiling = 0;
|
|
floor2 = floor;
|
|
while (floor2->skyRoom != NO_ROOM)
|
|
{
|
|
if (CheckNoColCeilingTriangle(floor, x, z) == 1)
|
|
break;
|
|
room = &g_Level.Rooms[floor2->skyRoom];
|
|
floor2 = &XZ_GET_SECTOR(room, x - room->x, z - room->z);
|
|
}
|
|
ceiling = 256 * floor2->ceiling;
|
|
if (ceiling != NO_HEIGHT)
|
|
{
|
|
if (floor2->index)
|
|
{
|
|
data = &g_Level.FloorData[floor2->index];
|
|
type = *data;
|
|
function = type & DATA_TYPE;
|
|
++data;
|
|
end = false;
|
|
if (function == TILT_TYPE || function == SPLIT1 || function == SPLIT2 || function == NOCOLF1T || function == NOCOLF1B || function == NOCOLF2T || function == NOCOLF2B)
|
|
{
|
|
++data;
|
|
if (type & END_BIT)
|
|
end = true;
|
|
type = *data;
|
|
function = type & DATA_TYPE;
|
|
++data;
|
|
}
|
|
if (!end)
|
|
{
|
|
xOff = 0;
|
|
yOff = 0;
|
|
if (function != ROOF_TYPE)
|
|
{
|
|
if (function == SPLIT3 || function == SPLIT4 || function == NOCOLC1T || function == NOCOLC1B || function == NOCOLC2T || function == NOCOLC2B)
|
|
{
|
|
SplitCeiling = function;
|
|
dx = x & 0x3FF;
|
|
dz = z & 0x3FF;
|
|
t0 = -(*data & DATA_TILT);
|
|
t1 = -(*data >> 4 & DATA_TILT);
|
|
t2 = -(*data >> 8 & DATA_TILT);
|
|
t3 = -(*data >> 12 & DATA_TILT);
|
|
if (function == SPLIT3 || function == NOCOLC1T || function == NOCOLC1B)
|
|
{
|
|
if (dx <= 1024 - dz)
|
|
{
|
|
cadj = type >> 10 & DATA_TYPE;
|
|
if (cadj & 0x10)
|
|
cadj |= 0xFFF0;
|
|
ceiling += 256 * cadj;
|
|
xOff = t2 - t1;
|
|
yOff = t3 - t2;
|
|
}
|
|
else
|
|
{
|
|
cadj = type >> 5 & DATA_TYPE;
|
|
if (cadj & 0x10)
|
|
cadj |= 0xFFF0;
|
|
ceiling += 256 * cadj;
|
|
xOff = t3 - t0;
|
|
yOff = t0 - t1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dx <= dz)
|
|
{
|
|
cadj = type >> 10 & DATA_TYPE;
|
|
if (cadj & 0x10)
|
|
cadj |= 0xFFF0;
|
|
ceiling += 256 * cadj;
|
|
xOff = t2 - t1;
|
|
yOff = t0 - t1;
|
|
}
|
|
else
|
|
{
|
|
cadj = type >> 5 & DATA_TYPE;
|
|
if (cadj & 0x10)
|
|
cadj |= 0xFFF0;
|
|
ceiling += 256 * cadj;
|
|
xOff = t3 - t0;
|
|
yOff = t3 - t2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
xOff = *data >> 8;
|
|
yOff = *(char *)data;
|
|
}
|
|
if (xOff < 0)
|
|
{
|
|
ceiling += (z & 0x3FF) * xOff >> 2;
|
|
}
|
|
else
|
|
{
|
|
ceiling -= (-1 - z & 0x3FF) * xOff >> 2;
|
|
}
|
|
if (yOff < 0)
|
|
{
|
|
ceiling += (-1 - x & 0x3FF) * yOff >> 2;
|
|
}
|
|
else
|
|
{
|
|
ceiling -= (x & 0x3FF) * yOff >> 2;
|
|
}
|
|
}
|
|
}
|
|
while (floor->pitRoom != NO_ROOM)
|
|
{
|
|
if (CheckNoColFloorTriangle(floor, x, z) == 1)
|
|
break;
|
|
room = &g_Level.Rooms[floor->pitRoom];
|
|
floor = &XZ_GET_SECTOR(room, x - room->x, z - room->z);
|
|
}
|
|
if (floor->index)
|
|
{
|
|
data = &g_Level.FloorData[floor->index];
|
|
do
|
|
{
|
|
type = *data;
|
|
function = type & DATA_TYPE;
|
|
++data;
|
|
switch (function)
|
|
{
|
|
case DOOR_TYPE:
|
|
case TILT_TYPE:
|
|
case ROOF_TYPE:
|
|
case SPLIT1:
|
|
case SPLIT2:
|
|
case SPLIT3:
|
|
case SPLIT4:
|
|
case NOCOLF1T:
|
|
case NOCOLF1B:
|
|
case NOCOLF2T:
|
|
case NOCOLF2B:
|
|
case NOCOLC1T:
|
|
case NOCOLC1B:
|
|
case NOCOLC2T:
|
|
case NOCOLC2B:
|
|
++data;
|
|
break;
|
|
case TRIGGER_TYPE:
|
|
++data;
|
|
do
|
|
{
|
|
type2 = *data;
|
|
trigger = TRIG_BITS(type2);
|
|
++data;
|
|
if (trigger != TO_OBJECT)
|
|
{
|
|
if (trigger == TO_CAMERA || trigger == TO_FLYBY)
|
|
{
|
|
type2 = *data;
|
|
++data;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
item = &g_Level.Items[type2 & VALUE_BITS];
|
|
if (Objects[item->objectNumber].ceiling && !(item->flags & 0x8000))
|
|
{
|
|
Objects[item->objectNumber].ceiling(item, x, y, z, &ceiling);
|
|
}
|
|
}
|
|
} while (!(type2 & END_BIT));
|
|
}
|
|
} while (!(type & END_BIT));
|
|
}
|
|
}
|
|
return ceiling;
|
|
#endif
|
|
|
|
return GetCeilingHeight(ROOM_VECTOR{floor->Room, y}, x, z).value_or(NO_HEIGHT);
|
|
}
|
|
|
|
int DoRayBox(GAME_VECTOR *start, GAME_VECTOR *end, BOUNDING_BOX *box, PHD_3DPOS *itemOrStaticPos, PHD_VECTOR *hitPos, short closesItemNumber)
|
|
{
|
|
// Ray
|
|
FXMVECTOR rayStart = {start->x, start->y, start->z};
|
|
FXMVECTOR rayEnd = {end->x, end->y, end->z};
|
|
FXMVECTOR rayDir = {end->x - start->x, end->y - start->y, end->z - start->z};
|
|
XMVECTOR rayDirNormalized = XMVector3Normalize(rayDir);
|
|
|
|
// Create the bounding box for raw collision detection
|
|
Vector3 boxCentre = Vector3(itemOrStaticPos->xPos + (box->X2 + box->X1) / 2.0f, itemOrStaticPos->yPos + (box->Y2 + box->Y1) / 2.0f, itemOrStaticPos->zPos + (box->Z2 + box->Z1) / 2.0f);
|
|
Vector3 boxExtent = Vector3((box->X2 - box->X1) / 2.0f, (box->Y2 - box->Y1) / 2.0f, (box->Z2 - box->Z1) / 2.0f);
|
|
Quaternion rotation = Quaternion::CreateFromAxisAngle(Vector3::UnitY, TO_RAD(itemOrStaticPos->yRot));
|
|
BoundingOrientedBox obox = BoundingOrientedBox(boxCentre, boxExtent, rotation);
|
|
|
|
// Get the collision with the bounding box
|
|
float distance;
|
|
bool collided = obox.Intersects(rayStart, rayDirNormalized, distance);
|
|
|
|
// If no collision happened, then don't test spheres
|
|
if (!collided)
|
|
return 0;
|
|
|
|
// Get the raw collision point
|
|
Vector3 collidedPoint = rayStart + distance * rayDirNormalized;
|
|
hitPos->x = collidedPoint.x - itemOrStaticPos->xPos;
|
|
hitPos->y = collidedPoint.y - itemOrStaticPos->yPos;
|
|
hitPos->z = collidedPoint.z - itemOrStaticPos->zPos;
|
|
|
|
// Now in the case of items we need to test single spheres
|
|
MESH* meshPtr = NULL;
|
|
int bit = 0;
|
|
int sp = -2;
|
|
float minDistance = std::numeric_limits<float>::max();
|
|
|
|
int action = TrInput & IN_ACTION;
|
|
|
|
if (closesItemNumber < 0)
|
|
{
|
|
// Static meshes don't require further tests
|
|
sp = -1;
|
|
minDistance = distance;
|
|
}
|
|
else
|
|
{
|
|
// For items instead we need to test spheres
|
|
ITEM_INFO *item = &g_Level.Items[closesItemNumber];
|
|
OBJECT_INFO *obj = &Objects[item->objectNumber];
|
|
|
|
// Get the ransformed sphere of meshes
|
|
GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD, Matrix::Identity);
|
|
SPHERE spheres[34];
|
|
memcpy(spheres, CreatureSpheres, sizeof(SPHERE) * 34);
|
|
if (obj->nmeshes <= 0)
|
|
return 0;
|
|
|
|
meshPtr = &g_Level.Meshes[obj->meshIndex];
|
|
|
|
for (int i = 0; i < obj->nmeshes; i++)
|
|
{
|
|
// If mesh is visibile...
|
|
if (item->meshBits & (1 << i))
|
|
{
|
|
SPHERE *sphere = &CreatureSpheres[i];
|
|
|
|
// TODO: this approach is the correct one but, again, Core's math is a mistery and this test was meant
|
|
// to fail delberately in some way. I've so added again Core's legacy test for allowing the current game logic
|
|
// but after more testing we should trash it in the future and restore the new way.
|
|
|
|
#if 0
|
|
// Create the bounding sphere and test it against the ray
|
|
BoundingSphere sph = BoundingSphere(Vector3(sphere->x, sphere->y, sphere->z), sphere->r);
|
|
float newDist;
|
|
if (sph.Intersects(rayStart, rayDirNormalized, newDist))
|
|
{
|
|
// HACK: Core seems to take in account for distance not the real hit point but the centre of the sphere.
|
|
// This can work well for example for GUARDIAN because the head sphere is so big that would always be hit
|
|
// and eyes would not be destroyed.
|
|
newDist = sqrt(SQUARE(sphere->x - start->x) + SQUARE(sphere->y - start->y) + SQUARE(sphere->z - start->z));
|
|
|
|
// Test for min distance
|
|
if (newDist < minDistance)
|
|
{
|
|
minDistance = newDist;
|
|
meshPtr = &g_Level.Meshes[obj->meshIndex + i];
|
|
bit = 1 << i;
|
|
sp = i;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
PHD_VECTOR p[4];
|
|
|
|
p[1].x = start->x;
|
|
p[1].y = start->y;
|
|
p[1].z = start->z;
|
|
p[2].x = end->x;
|
|
p[2].y = end->y;
|
|
p[2].z = end->z;
|
|
p[3].x = sphere->x;
|
|
p[3].y = sphere->y;
|
|
p[3].z = sphere->z;
|
|
|
|
int r0 = (p[3].x - p[1].x) * (p[2].x - p[1].x) +
|
|
(p[3].y - p[1].y) * (p[2].y - p[1].y) +
|
|
(p[3].z - p[1].z) * (p[2].z - p[1].z);
|
|
|
|
int r1 = SQUARE(p[2].x - p[1].x) +
|
|
SQUARE(p[2].y - p[1].y) +
|
|
SQUARE(p[2].z - p[1].z);
|
|
|
|
if (((r0 < 0 && r1 < 0)
|
|
|| (r1 > 0 && r0 > 0))
|
|
&& (abs(r0) <= abs(r1)))
|
|
{
|
|
r1 >>= 16;
|
|
if (r1)
|
|
r0 /= r1;
|
|
else
|
|
r0 = 0;
|
|
|
|
p[0].x = p[1].x + ((r0 * (p[2].x - p[1].x)) >> 16);
|
|
p[0].y = p[1].y + ((r0 * (p[2].y - p[1].y)) >> 16);
|
|
p[0].z = p[1].z + ((r0 * (p[2].z - p[1].z)) >> 16);
|
|
|
|
int dx = p[0].x - p[3].x;
|
|
int dy = p[0].y - p[3].y;
|
|
int dz = p[0].z - p[3].z;
|
|
|
|
int distance = dx + dy + dz;
|
|
|
|
if (distance < SQUARE(sphere->r))
|
|
{
|
|
dx = SQUARE(sphere->x - start->x);
|
|
dy = SQUARE(sphere->y - start->y);
|
|
dz = SQUARE(sphere->z - start->z);
|
|
|
|
distance = dx + dy + dz;
|
|
|
|
if (distance < minDistance)
|
|
{
|
|
minDistance = distance;
|
|
meshPtr = &g_Level.Meshes[obj->meshIndex + i];
|
|
bit = 1 << i;
|
|
sp = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sp < -1)
|
|
return 0;
|
|
}
|
|
|
|
if (distance >= ClosestDist)
|
|
return 0;
|
|
|
|
// Setup test result
|
|
ClosestCoord.x = hitPos->x + itemOrStaticPos->xPos;
|
|
ClosestCoord.y = hitPos->y + itemOrStaticPos->yPos;
|
|
ClosestCoord.z = hitPos->z + itemOrStaticPos->zPos;
|
|
ClosestDist = distance;
|
|
ClosestItem = closesItemNumber;
|
|
|
|
// If collided object is an item, then setup the shatter item data struct
|
|
if (sp >= 0)
|
|
{
|
|
ITEM_INFO *item = &g_Level.Items[closesItemNumber];
|
|
|
|
GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD | SPHERES_SPACE_BONE_ORIGIN, Matrix::Identity);
|
|
|
|
ShatterItem.yRot = item->pos.yRot;
|
|
ShatterItem.meshp = meshPtr;
|
|
ShatterItem.sphere.x = CreatureSpheres[sp].x;
|
|
ShatterItem.sphere.y = CreatureSpheres[sp].y;
|
|
ShatterItem.sphere.z = CreatureSpheres[sp].z;
|
|
ShatterItem.bit = bit;
|
|
ShatterItem.flags = 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void AnimateItem(ITEM_INFO *item)
|
|
{
|
|
item->touchBits = 0;
|
|
item->hitStatus = false;
|
|
|
|
item->frameNumber++;
|
|
|
|
ANIM_STRUCT *anim = &g_Level.Anims[item->animNumber];
|
|
if (anim->numberChanges > 0 && GetChange(item, anim))
|
|
{
|
|
anim = &g_Level.Anims[item->animNumber];
|
|
|
|
item->currentAnimState = anim->currentAnimState;
|
|
|
|
if (item->requiredAnimState == item->currentAnimState)
|
|
item->requiredAnimState = 0;
|
|
}
|
|
|
|
if (item->frameNumber > anim->frameEnd)
|
|
{
|
|
if (anim->numberCommands > 0)
|
|
{
|
|
short *cmd = &g_Level.Commands[anim->commandIndex];
|
|
for (int i = anim->numberCommands; i > 0; i--)
|
|
{
|
|
switch (*(cmd++))
|
|
{
|
|
case COMMAND_MOVE_ORIGIN:
|
|
TranslateItem(item, cmd[0], cmd[1], cmd[2]);
|
|
cmd += 3;
|
|
break;
|
|
|
|
case COMMAND_JUMP_VELOCITY:
|
|
item->fallspeed = *(cmd++);
|
|
item->speed = *(cmd++);
|
|
item->gravityStatus = true;
|
|
break;
|
|
|
|
case COMMAND_DEACTIVATE:
|
|
if (Objects[item->objectNumber].intelligent && !item->afterDeath)
|
|
item->afterDeath = 1;
|
|
item->status = ITEM_DEACTIVATED;
|
|
break;
|
|
case COMMAND_SOUND_FX:
|
|
case COMMAND_EFFECT:
|
|
cmd += 2;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
item->animNumber = anim->jumpAnimNum;
|
|
item->frameNumber = anim->jumpFrameNum;
|
|
|
|
anim = &g_Level.Anims[item->animNumber];
|
|
|
|
if (item->currentAnimState != anim->currentAnimState)
|
|
{
|
|
item->currentAnimState = anim->currentAnimState;
|
|
item->goalAnimState = anim->currentAnimState;
|
|
}
|
|
|
|
if (item->requiredAnimState == item->currentAnimState)
|
|
item->requiredAnimState = 0;
|
|
}
|
|
|
|
if (anim->numberCommands > 0)
|
|
{
|
|
short *cmd = &g_Level.Commands[anim->commandIndex];
|
|
int flags;
|
|
int effectID = 0;
|
|
|
|
for (int i = anim->numberCommands; i > 0; i--)
|
|
{
|
|
switch (*(cmd++))
|
|
{
|
|
case COMMAND_MOVE_ORIGIN:
|
|
cmd += 3;
|
|
break;
|
|
|
|
case COMMAND_JUMP_VELOCITY:
|
|
cmd += 2;
|
|
break;
|
|
|
|
case COMMAND_SOUND_FX:
|
|
if (item->frameNumber != *cmd)
|
|
{
|
|
cmd += 2;
|
|
break;
|
|
}
|
|
|
|
flags = cmd[1] & 0xC000;
|
|
|
|
if (!Objects[item->objectNumber].waterCreature)
|
|
{
|
|
if (item->roomNumber == NO_ROOM)
|
|
{
|
|
item->pos.xPos = LaraItem->pos.xPos;
|
|
item->pos.yPos = LaraItem->pos.yPos - 762;
|
|
item->pos.zPos = LaraItem->pos.zPos;
|
|
|
|
SoundEffect(cmd[1] & 0x3FFF, &item->pos, 2);
|
|
}
|
|
else if (g_Level.Rooms[item->roomNumber].flags & ENV_FLAG_WATER)
|
|
{
|
|
if (!flags || flags == SFX_WATERONLY && (g_Level.Rooms[Camera.pos.roomNumber].flags & ENV_FLAG_WATER || Objects[item->objectNumber].intelligent))
|
|
{
|
|
SoundEffect(cmd[1] & 0x3FFF, &item->pos, 2);
|
|
}
|
|
}
|
|
else if (!flags || flags == SFX_LANDONLY && !(g_Level.Rooms[Camera.pos.roomNumber].flags & ENV_FLAG_WATER))
|
|
{
|
|
SoundEffect(cmd[1] & 0x3FFF, &item->pos, 2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (g_Level.Rooms[Camera.pos.roomNumber].flags & ENV_FLAG_WATER)
|
|
SoundEffect(cmd[1] & 0x3FFF, &item->pos, 1);
|
|
else
|
|
SoundEffect(cmd[1] & 0x3FFF, &item->pos, 0);
|
|
}
|
|
break;
|
|
|
|
case COMMAND_EFFECT:
|
|
if (item->frameNumber != *cmd)
|
|
{
|
|
cmd += 2;
|
|
break;
|
|
}
|
|
|
|
FXType = cmd[1] & 0xC000;
|
|
effectID = cmd[1] & 0x3FFF;
|
|
effect_routines[effectID](item);
|
|
|
|
cmd += 2;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int lateral = 0;
|
|
|
|
if (item->gravityStatus)
|
|
{
|
|
item->fallspeed += (item->fallspeed >= 128 ? 1 : 6);
|
|
item->pos.yPos += item->fallspeed;
|
|
}
|
|
else
|
|
{
|
|
int velocity = anim->velocity;
|
|
if (anim->acceleration)
|
|
velocity += anim->acceleration * (item->frameNumber - anim->frameBase);
|
|
item->speed = velocity / 65536;
|
|
|
|
lateral = anim->Xvelocity;
|
|
if (anim->Xacceleration)
|
|
lateral += anim->Xacceleration * (item->frameNumber - anim->frameBase);
|
|
|
|
lateral /= 65536;
|
|
}
|
|
|
|
item->pos.xPos += item->speed * phd_sin(item->pos.yRot);
|
|
item->pos.zPos += item->speed * phd_cos(item->pos.yRot);
|
|
|
|
item->pos.xPos += lateral * phd_sin(item->pos.yRot + ANGLE(90));
|
|
item->pos.zPos += lateral * phd_cos(item->pos.yRot + ANGLE(90));
|
|
|
|
// Update matrices
|
|
short itemNumber = item - g_Level.Items.data();
|
|
g_Renderer.updateItemAnimations(itemNumber, true);
|
|
}
|
|
|
|
void DoFlipMap(short group)
|
|
{
|
|
ROOM_INFO temp;
|
|
|
|
for (int i = 0; i < g_Level.Rooms.size(); i++)
|
|
{
|
|
ROOM_INFO *r = &g_Level.Rooms[i];
|
|
|
|
if (r->flippedRoom >= 0 && r->flipNumber == group)
|
|
{
|
|
RemoveRoomFlipItems(r);
|
|
|
|
ROOM_INFO *flipped = &g_Level.Rooms[r->flippedRoom];
|
|
|
|
temp = *r;
|
|
*r = *flipped;
|
|
*flipped = temp;
|
|
|
|
r->flippedRoom = flipped->flippedRoom;
|
|
flipped->flippedRoom = -1;
|
|
|
|
r->itemNumber = flipped->itemNumber;
|
|
r->fxNumber = flipped->fxNumber;
|
|
|
|
AddRoomFlipItems(r);
|
|
|
|
g_Renderer.flipRooms(i, r->flippedRoom);
|
|
|
|
for (auto& fd : r->floor)
|
|
fd.Room = i;
|
|
for (auto& fd : flipped->floor)
|
|
fd.Room = r->flippedRoom;
|
|
}
|
|
}
|
|
|
|
int status = FlipStats[group] == 0;
|
|
FlipStats[group] = status;
|
|
FlipStatus = status;
|
|
|
|
for (int i = 0; i < NUM_SLOTS; i++)
|
|
{
|
|
BaddieSlots[i].LOT.targetBox = NO_BOX;
|
|
}
|
|
}
|
|
|
|
void AddRoomFlipItems(ROOM_INFO *r)
|
|
{
|
|
for (short linkNum = r->itemNumber; linkNum != NO_ITEM; linkNum = g_Level.Items[linkNum].nextItem)
|
|
{
|
|
ITEM_INFO *item = &g_Level.Items[linkNum];
|
|
|
|
//if (item->objectNumber == ID_RAISING_BLOCK1 && item->itemFlags[1])
|
|
// AlterFloorHeight(item, -1024);
|
|
|
|
if (item->objectNumber == ID_RAISING_BLOCK2)
|
|
{
|
|
//if (item->itemFlags[1])
|
|
// AlterFloorHeight(item, -2048);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoveRoomFlipItems(ROOM_INFO *r)
|
|
{
|
|
for (short linkNum = r->itemNumber; linkNum != NO_ITEM; linkNum = g_Level.Items[linkNum].nextItem)
|
|
{
|
|
ITEM_INFO *item = &g_Level.Items[linkNum];
|
|
|
|
if (item->flags & 0x100 && Objects[item->objectNumber].intelligent && item->hitPoints <= 0 && item->hitPoints != NOT_TARGETABLE)
|
|
{
|
|
KillItem(linkNum);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlaySoundTrack(short track, short flags)
|
|
{
|
|
S_CDPlayEx(track, flags, 0);
|
|
}
|
|
|
|
void RumbleScreen()
|
|
{
|
|
if (!(GlobalCounter & 0x1FF))
|
|
SoundEffect(SFX_TR5_KLAXON, 0, 4104);
|
|
|
|
if (RumbleTimer >= 0)
|
|
RumbleTimer++;
|
|
|
|
if (RumbleTimer > 450)
|
|
{
|
|
if (!(GetRandomControl() & 0x1FF))
|
|
{
|
|
InGameCnt = 0;
|
|
RumbleTimer = -32 - (GetRandomControl() & 0x1F);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (RumbleTimer < 0)
|
|
{
|
|
if (InGameCnt >= abs(RumbleTimer))
|
|
{
|
|
Camera.bounce = -(GetRandomControl() % abs(RumbleTimer));
|
|
RumbleTimer++;
|
|
}
|
|
else
|
|
{
|
|
InGameCnt++;
|
|
Camera.bounce = -(GetRandomControl() % InGameCnt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RefreshCamera(short type, short *data)
|
|
{
|
|
short trigger, value, targetOk;
|
|
|
|
targetOk = 2;
|
|
|
|
do
|
|
{
|
|
trigger = *(data++);
|
|
value = trigger & VALUE_BITS;
|
|
|
|
switch (TRIG_BITS(trigger))
|
|
{
|
|
case TO_CAMERA:
|
|
data++;
|
|
|
|
if (value == Camera.last)
|
|
{
|
|
Camera.number = value;
|
|
|
|
if ((Camera.timer < 0) || (Camera.type == LOOK_CAMERA) || (Camera.type == COMBAT_CAMERA))
|
|
{
|
|
Camera.timer = -1;
|
|
targetOk = 0;
|
|
break;
|
|
}
|
|
Camera.type = FIXED_CAMERA;
|
|
targetOk = 1;
|
|
}
|
|
else
|
|
targetOk = 0;
|
|
break;
|
|
|
|
case TO_TARGET:
|
|
if (Camera.type == LOOK_CAMERA || Camera.type == COMBAT_CAMERA)
|
|
break;
|
|
|
|
Camera.item = &g_Level.Items[value];
|
|
break;
|
|
}
|
|
} while (!(trigger & END_BIT));
|
|
|
|
if (Camera.item)
|
|
if (!targetOk || (targetOk == 2 && Camera.item->lookedAt && Camera.item != Camera.lastItem))
|
|
Camera.item = NULL;
|
|
|
|
if (Camera.number == -1 && Camera.timer > 0)
|
|
Camera.timer = -1;
|
|
}
|
|
|
|
int ExplodeItemNode(ITEM_INFO *item, int Node, int NoXZVel, int bits)
|
|
{
|
|
short Num;
|
|
|
|
if (1 << Node & item->meshBits)
|
|
{
|
|
Num = bits;
|
|
if (item->objectNumber == ID_SHOOT_SWITCH1 && (CurrentLevel == 4 || CurrentLevel == 7)) // TODO: remove hardcoded think !
|
|
{
|
|
SoundEffect(SFX_TR5_SMASH_METAL, &item->pos, 0);
|
|
}
|
|
else if (Num == 256)
|
|
{
|
|
Num = -64;
|
|
}
|
|
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);
|
|
item->meshBits &= ~ShatterItem.bit;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int TriggerActive(ITEM_INFO *item)
|
|
{
|
|
int flag;
|
|
|
|
flag = (~item->flags & IFLAG_REVERSE) / 16384;
|
|
if ((item->flags & IFLAG_ACTIVATION_MASK) != IFLAG_ACTIVATION_MASK)
|
|
{
|
|
flag = !flag;
|
|
}
|
|
else
|
|
{
|
|
if (item->timer)
|
|
{
|
|
if (item->timer > 0)
|
|
{
|
|
--item->timer;
|
|
if (!item->timer)
|
|
item->timer = -1;
|
|
}
|
|
else if (item->timer < -1)
|
|
{
|
|
++item->timer;
|
|
if (item->timer == -1)
|
|
item->timer = 0;
|
|
}
|
|
if (item->timer <= -1)
|
|
flag = !flag;
|
|
}
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
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
|
|
{
|
|
int xBlock = (x - r->x) / SECTOR(1);
|
|
int zBlock = (z - r->z) / SECTOR(1);
|
|
|
|
if (zBlock <= 0)
|
|
{
|
|
zBlock = 0;
|
|
if (xBlock < 1)
|
|
xBlock = 1;
|
|
else if (xBlock > r->ySize - 2)
|
|
xBlock = r->ySize - 2;
|
|
}
|
|
else if (zBlock >= r->xSize - 1)
|
|
{
|
|
zBlock = r->xSize - 1;
|
|
if (xBlock < 1)
|
|
xBlock = 1;
|
|
else if (xBlock > r->ySize - 2)
|
|
xBlock = r->ySize - 2;
|
|
}
|
|
else if (xBlock < 0)
|
|
xBlock = 0;
|
|
else if (xBlock >= r->ySize)
|
|
xBlock = r->ySize - 1;
|
|
|
|
floor = &r->floor[zBlock + xBlock * r->xSize];
|
|
adjoiningRoom = GetDoor(floor);
|
|
|
|
if (adjoiningRoom != NO_ROOM)
|
|
{
|
|
roomNumber = adjoiningRoom;
|
|
r = &g_Level.Rooms[adjoiningRoom];
|
|
}
|
|
} while (adjoiningRoom != NO_ROOM);
|
|
|
|
if (r->flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP))
|
|
{
|
|
while (floor->skyRoom != NO_ROOM)
|
|
{
|
|
if (CheckNoColCeilingTriangle(floor, x, z) == 1)
|
|
break;
|
|
r = &g_Level.Rooms[floor->skyRoom];
|
|
if (!(r->flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP)))
|
|
return r->minfloor;
|
|
floor = &XZ_GET_SECTOR(r, x - r->x, z - r->z);
|
|
if (floor->skyRoom == NO_ROOM)
|
|
break;
|
|
}
|
|
|
|
return r->maxceiling;
|
|
}
|
|
else
|
|
{
|
|
while (floor->pitRoom != NO_ROOM)
|
|
{
|
|
if (CheckNoColFloorTriangle(floor, x, z) == 1)
|
|
break;
|
|
r = &g_Level.Rooms[floor->pitRoom];
|
|
if (r->flags & (ENV_FLAG_WATER | ENV_FLAG_SWAMP))
|
|
return r->maxceiling;
|
|
floor = &XZ_GET_SECTOR(r, x - r->x, z - r->z);
|
|
if (floor->pitRoom == NO_ROOM)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NO_HEIGHT;
|
|
}
|
|
|
|
int is_object_in_room(short roomNumber, short objectNumber)
|
|
{
|
|
short itemNumber = g_Level.Rooms[roomNumber].itemNumber;
|
|
|
|
if (itemNumber == NO_ITEM)
|
|
return 0;
|
|
|
|
while (true)
|
|
{
|
|
ITEM_INFO *item = &g_Level.Items[itemNumber];
|
|
|
|
if (item->objectNumber == objectNumber)
|
|
break;
|
|
|
|
itemNumber = item->nextItem;
|
|
|
|
if (itemNumber == NO_ITEM)
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void InterpolateAngle(short angle, short *rotation, short *outAngle, int shift)
|
|
{
|
|
short deltaAngle = angle - *rotation;
|
|
|
|
if (deltaAngle < -32768)
|
|
deltaAngle += 65536;
|
|
else if (deltaAngle > 32768)
|
|
deltaAngle -= 65536;
|
|
|
|
if (outAngle)
|
|
*outAngle = deltaAngle;
|
|
|
|
*rotation += deltaAngle >> shift;
|
|
}
|
|
|
|
int IsRoomOutside(int x, int y, int z)
|
|
{
|
|
if (x < 0 || z < 0)
|
|
return -2;
|
|
|
|
int xTable = x / 1024;
|
|
int zTable = z / 1024;
|
|
|
|
if (OutsideRoomTable[xTable][zTable].size() == 0)
|
|
return -2;
|
|
|
|
for (int i = 0; i < OutsideRoomTable[xTable][zTable].size(); i++)
|
|
{
|
|
short roomNumber = OutsideRoomTable[xTable][zTable][i];
|
|
ROOM_INFO* r = &g_Level.Rooms[roomNumber];
|
|
|
|
if ((y > r->maxceiling) && (y < r->minfloor)
|
|
&& ((z > (r->z + 1024)) && (z < (r->z + ((r->xSize - 1) * 1024))))
|
|
&& ((x > (r->x + 1024)) && (x < (r->x + ((r->ySize - 1) * 1024)))))
|
|
{
|
|
IsRoomOutsideNo = roomNumber;
|
|
|
|
FLOOR_INFO* floor = GetFloor(x, y, z, &roomNumber);
|
|
int height = GetFloorHeight(floor, x, y, z);
|
|
if (height == NO_HEIGHT || y > height)
|
|
return -2;
|
|
height = GetCeiling(floor, x, y, z);
|
|
if (y < height)
|
|
return -2;
|
|
|
|
return ((r->flags & (ENV_FLAG_WIND | ENV_FLAG_WATER)) != 0 ? 1 : -3);
|
|
}
|
|
}
|
|
|
|
return -2;
|
|
}
|
|
|
|
void TestTriggersAtXYZ(int x, int y, int z, short roomNumber, int heavy, int flags)
|
|
{
|
|
GetFloorHeight(GetFloor(x, y, z, &roomNumber), x, y, z);
|
|
TestTriggers(TriggerIndex, heavy, flags);
|
|
}
|
|
|
|
void ResetGlobals()
|
|
{
|
|
// It's kinda lonely here for now, but I recommend gradually putting here all the globals which need reset
|
|
// on level change, unless we refactor all the code better way -- Lwmte
|
|
|
|
DisableLaraControl = false;
|
|
}
|
|
|