mirror of
https://github.com/TombEngine/TombEngine.git
synced 2025-04-30 08:47:58 +03:00
530 lines
14 KiB
C++
530 lines
14 KiB
C++
#include "framework.h"
|
|
#include "Objects/Generic/puzzles_keys.h"
|
|
|
|
#include "Game/animation.h"
|
|
#include "Game/camera.h"
|
|
#include "Game/collision/collide_item.h"
|
|
#include "Game/control/control.h"
|
|
#include "Game/control/trigger.h"
|
|
#include "Game/Gui.h"
|
|
#include "Game/Hud/Hud.h"
|
|
#include "Game/items.h"
|
|
#include "Game/Lara/lara.h"
|
|
#include "Game/Lara/lara_helpers.h"
|
|
#include "Game/pickup/pickup.h"
|
|
#include "Game/Setup.h"
|
|
#include "Objects/Generic/Switches/generic_switch.h"
|
|
#include "Specific/Input/Input.h"
|
|
#include "Specific/level.h"
|
|
|
|
using namespace TEN::Entities::Switches;
|
|
using namespace TEN::Gui;
|
|
using namespace TEN::Hud;
|
|
using namespace TEN::Input;
|
|
|
|
short PuzzleItem;
|
|
|
|
enum class PuzzleType
|
|
{
|
|
Normal,
|
|
Specfic,
|
|
Cutscene,
|
|
AnimAfter
|
|
};
|
|
|
|
ObjectCollisionBounds PuzzleBounds =
|
|
{
|
|
GameBoundingBox(
|
|
0, 0,
|
|
-CLICK(1), CLICK(1),
|
|
0, 0),
|
|
std::pair(
|
|
EulerAngles(ANGLE(-10.0f), ANGLE(-30.0f), ANGLE(-10.0f)),
|
|
EulerAngles(ANGLE(10.0f), ANGLE(30.0f), ANGLE(10.0f)))
|
|
};
|
|
|
|
const auto KeyHolePosition = Vector3i(0, 0, 312);
|
|
const ObjectCollisionBounds KeyHoleBounds =
|
|
{
|
|
GameBoundingBox(
|
|
-CLICK(1), CLICK(1),
|
|
0, 0,
|
|
0, 412),
|
|
std::pair(
|
|
EulerAngles(ANGLE(-10.0f), ANGLE(-30.0f), ANGLE(-10.0f)),
|
|
EulerAngles(ANGLE(10.0f), ANGLE(30.0f), ANGLE(10.0f)))
|
|
};
|
|
|
|
// Puzzles
|
|
void InitializePuzzleHole(short itemNumber)
|
|
{
|
|
auto& receptacleItem = g_Level.Items[itemNumber];
|
|
receptacleItem.ItemFlags[5] = (int)ReusableReceptacleState::Empty;
|
|
}
|
|
|
|
void InitializePuzzleDone(short itemNumber)
|
|
{
|
|
auto& receptacleItem = g_Level.Items[itemNumber];
|
|
const auto& anim = GetAnimData(receptacleItem);
|
|
|
|
receptacleItem.Animation.RequiredState = NO_STATE;
|
|
receptacleItem.Animation.FrameNumber = anim.frameBase + anim.frameEnd;
|
|
}
|
|
|
|
void PuzzleHoleCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
|
|
{
|
|
auto& receptacleItem = g_Level.Items[itemNumber];
|
|
auto& player = GetLaraInfo(*laraItem);
|
|
|
|
// Start level with correct object when loading game.
|
|
if (receptacleItem.ItemFlags[5] == (int)ReusableReceptacleState::Done)
|
|
{
|
|
receptacleItem.ObjectNumber += GAME_OBJECT_ID{ ID_PUZZLE_DONE1 - ID_PUZZLE_HOLE1 };
|
|
SetAnimation(receptacleItem, 0);
|
|
receptacleItem.ResetModelToDefault();
|
|
return;
|
|
}
|
|
|
|
auto puzzleType = PuzzleType::Normal;
|
|
|
|
if (receptacleItem.TriggerFlags >= 0)
|
|
{
|
|
if (receptacleItem.TriggerFlags <= 1024)
|
|
{
|
|
if (receptacleItem.TriggerFlags &&
|
|
receptacleItem.TriggerFlags != 999 &&
|
|
receptacleItem.TriggerFlags != 998)
|
|
{
|
|
puzzleType = PuzzleType::AnimAfter;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
puzzleType = PuzzleType::Cutscene;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
puzzleType = PuzzleType::Specfic;
|
|
}
|
|
|
|
if (((IsHeld(In::Action) || g_Gui.GetInventoryItemChosen() != NO_ITEM) &&
|
|
laraItem->Animation.ActiveState == LS_IDLE &&
|
|
laraItem->Animation.AnimNumber == LA_STAND_IDLE &&
|
|
player.Control.HandStatus == HandStatus::Free &&
|
|
!BinocularRange) ||
|
|
(player.Control.IsMoving &&
|
|
player.Context.InteractedItem == itemNumber))
|
|
{
|
|
short prevYOrient = receptacleItem.Pose.Orientation.y;
|
|
|
|
auto bounds = GameBoundingBox(&receptacleItem);
|
|
PuzzleBounds.BoundingBox.X1 = bounds.X1 - BLOCK(0.25f);
|
|
PuzzleBounds.BoundingBox.X2 = bounds.X2 + BLOCK(0.25f);
|
|
PuzzleBounds.BoundingBox.Z1 = bounds.Z1 - BLOCK(0.25f);
|
|
PuzzleBounds.BoundingBox.Z2 = bounds.Z2 + BLOCK(0.25f);
|
|
|
|
if (TestLaraPosition(PuzzleBounds, &receptacleItem, laraItem))
|
|
{
|
|
if (!player.Control.IsMoving)
|
|
{
|
|
if (g_Gui.GetInventoryItemChosen() == NO_ITEM)
|
|
{
|
|
if (g_Gui.IsObjectInInventory(receptacleItem.ObjectNumber - (ID_PUZZLE_HOLE1 - ID_PUZZLE_ITEM1)))
|
|
g_Gui.SetEnterInventory(receptacleItem.ObjectNumber - (ID_PUZZLE_HOLE1 - ID_PUZZLE_ITEM1));
|
|
|
|
receptacleItem.Pose.Orientation.y = prevYOrient;
|
|
return;
|
|
}
|
|
|
|
if (g_Gui.GetInventoryItemChosen() != receptacleItem.ObjectNumber - (ID_PUZZLE_HOLE1 - ID_PUZZLE_ITEM1))
|
|
{
|
|
receptacleItem.Pose.Orientation.y = prevYOrient;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (puzzleType != PuzzleType::Cutscene)
|
|
{
|
|
auto pos = Vector3i(0, 0, bounds.Z1 - 100);
|
|
if (!MoveLaraPosition(pos, &receptacleItem, laraItem))
|
|
{
|
|
player.Context.InteractedItem = itemNumber;
|
|
g_Gui.SetInventoryItemChosen(NO_ITEM);
|
|
receptacleItem.Pose.Orientation.y = prevYOrient;
|
|
return;
|
|
}
|
|
}
|
|
|
|
RemoveObjectFromInventory(GAME_OBJECT_ID(receptacleItem.ObjectNumber - (ID_PUZZLE_HOLE1 - ID_PUZZLE_ITEM1)), 1);
|
|
|
|
if (puzzleType == PuzzleType::Specfic)
|
|
{
|
|
laraItem->Animation.ActiveState = LS_MISC_CONTROL;
|
|
laraItem->Animation.AnimNumber = -receptacleItem.TriggerFlags;
|
|
|
|
if (laraItem->Animation.AnimNumber != LA_TRIDENT_SET)
|
|
PuzzleDone(&receptacleItem, itemNumber);
|
|
}
|
|
else
|
|
{
|
|
laraItem->Animation.AnimNumber = LA_USE_PUZZLE;
|
|
laraItem->Animation.ActiveState = LS_INSERT_PUZZLE;
|
|
receptacleItem.ItemFlags[0] = 1;
|
|
}
|
|
|
|
g_Gui.SetInventoryItemChosen(NO_ITEM);
|
|
ResetPlayerFlex(laraItem);
|
|
laraItem->Animation.FrameNumber = GetAnimData(laraItem).frameBase;
|
|
player.Control.IsMoving = false;
|
|
player.Control.HandStatus = HandStatus::Busy;
|
|
player.Context.InteractedItem = itemNumber;
|
|
receptacleItem.Pose.Orientation.y = prevYOrient;
|
|
receptacleItem.Flags |= TRIGGERED;
|
|
return;
|
|
}
|
|
|
|
if (player.Control.IsMoving)
|
|
{
|
|
if (player.Context.InteractedItem == itemNumber)
|
|
{
|
|
player.Control.IsMoving = false;
|
|
player.Control.HandStatus = HandStatus::Free;
|
|
}
|
|
}
|
|
|
|
receptacleItem.Pose.Orientation.y = prevYOrient;
|
|
}
|
|
else
|
|
{
|
|
if (!player.Control.IsMoving && player.Context.InteractedItem == itemNumber || player.Context.InteractedItem != itemNumber)
|
|
{
|
|
if (player.Context.InteractedItem == itemNumber)
|
|
{
|
|
if (laraItem->Animation.ActiveState != LS_MISC_CONTROL)
|
|
{
|
|
if (puzzleType != PuzzleType::Cutscene)
|
|
ObjectCollision(itemNumber, laraItem, coll);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (laraItem->Animation.ActiveState == LS_MISC_CONTROL)
|
|
return;
|
|
|
|
if (puzzleType != PuzzleType::Cutscene)
|
|
ObjectCollision(itemNumber, laraItem, coll);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PuzzleDoneCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
|
|
{
|
|
if ((g_Level.Items[itemNumber].TriggerFlags - 998) > 1)
|
|
ObjectCollision(itemNumber, laraItem, coll);
|
|
|
|
auto& receptacleItem = g_Level.Items[itemNumber];
|
|
auto& player = GetLaraInfo(*laraItem);
|
|
|
|
// NOTE: Only execute code below if Triggertype is switch trigger.
|
|
short* triggerIndexPtr = GetTriggerIndex(&receptacleItem);
|
|
if (triggerIndexPtr == nullptr)
|
|
return;
|
|
|
|
int triggerType = (*(triggerIndexPtr++) >> 8) & TRIGGER_BITS;
|
|
if (triggerType != TRIGGER_TYPES::SWITCH)
|
|
return;
|
|
|
|
AnimateItem(&receptacleItem);
|
|
|
|
// Start level with correct object when loading game.
|
|
if (receptacleItem.ItemFlags[5] == (int)ReusableReceptacleState::Empty)
|
|
{
|
|
receptacleItem.ObjectNumber = GAME_OBJECT_ID(receptacleItem.ObjectNumber - (ID_PUZZLE_DONE1 - ID_PUZZLE_HOLE1));
|
|
SetAnimation(receptacleItem, 0);
|
|
receptacleItem.ResetModelToDefault();
|
|
return;
|
|
}
|
|
|
|
// Activate triggers when startig level for first time.
|
|
if (receptacleItem.ItemFlags[5] == (int)ReusableReceptacleState::None)
|
|
{
|
|
receptacleItem.ItemFlags[1] = true;
|
|
TestTriggers(receptacleItem.Pose.Position.x, receptacleItem.Pose.Position.y, receptacleItem.Pose.Position.z, receptacleItem.RoomNumber, false, 0);
|
|
receptacleItem.ItemFlags[5] = (int)ReusableReceptacleState::Done;
|
|
}
|
|
|
|
auto puzzleType = PuzzleType::Normal;
|
|
|
|
if ((IsHeld(In::Action) &&
|
|
laraItem->Animation.ActiveState == LS_IDLE &&
|
|
laraItem->Animation.AnimNumber == LA_STAND_IDLE &&
|
|
player.Control.HandStatus == HandStatus::Free &&
|
|
!BinocularRange) ||
|
|
(player.Control.IsMoving &&
|
|
player.Context.InteractedItem == itemNumber))
|
|
{
|
|
short prevYOrient = receptacleItem.Pose.Orientation.y;
|
|
|
|
auto bounds = GameBoundingBox(&receptacleItem);
|
|
PuzzleBounds.BoundingBox.X1 = bounds.X1 - BLOCK(0.25f);
|
|
PuzzleBounds.BoundingBox.X2 = bounds.X2 + BLOCK(0.25f);
|
|
PuzzleBounds.BoundingBox.Z1 = bounds.Z1 - BLOCK(0.25f);
|
|
PuzzleBounds.BoundingBox.Z2 = bounds.Z2 + BLOCK(0.25f);
|
|
|
|
if (TestLaraPosition(PuzzleBounds, &receptacleItem, laraItem))
|
|
{
|
|
auto pos = Vector3i(0, 0, bounds.Z1 - 100);
|
|
if (!MoveLaraPosition(pos, &receptacleItem, laraItem))
|
|
{
|
|
player.Context.InteractedItem = itemNumber;
|
|
g_Gui.SetInventoryItemChosen(NO_ITEM);
|
|
receptacleItem.Pose.Orientation.y = prevYOrient;
|
|
return;
|
|
}
|
|
|
|
laraItem->Animation.AnimNumber = LA_REMOVE_PUZZLE;
|
|
laraItem->Animation.ActiveState = LS_REMOVE_PUZZLE;
|
|
receptacleItem.ItemFlags[0] = 1;
|
|
|
|
ResetPlayerFlex(laraItem);
|
|
laraItem->Animation.FrameNumber = GetAnimData(*laraItem, laraItem->Animation.AnimNumber).frameBase;
|
|
player.Control.IsMoving = false;
|
|
player.Control.HandStatus = HandStatus::Busy;
|
|
player.Context.InteractedItem = itemNumber;
|
|
receptacleItem.Pose.Orientation.y = prevYOrient;
|
|
receptacleItem.Flags |= TRIGGERED;
|
|
return;
|
|
}
|
|
|
|
if (player.Control.IsMoving)
|
|
{
|
|
if (player.Context.InteractedItem == itemNumber)
|
|
{
|
|
player.Control.IsMoving = false;
|
|
player.Control.HandStatus = HandStatus::Free;
|
|
}
|
|
}
|
|
|
|
receptacleItem.Pose.Orientation.y = prevYOrient;
|
|
}
|
|
else
|
|
{
|
|
if ((!player.Control.IsMoving && player.Context.InteractedItem == itemNumber) ||
|
|
player.Context.InteractedItem != itemNumber)
|
|
{
|
|
if (laraItem->Animation.ActiveState == LS_MISC_CONTROL)
|
|
return;
|
|
|
|
if (puzzleType != PuzzleType::Cutscene)
|
|
ObjectCollision(itemNumber, laraItem, coll);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PuzzleDone(ItemInfo* item, short itemNumber)
|
|
{
|
|
short* triggerIndexPtr = GetTriggerIndex(item);
|
|
short triggerType = (triggerIndexPtr != nullptr) ? (*(triggerIndexPtr++) >> 8) & TRIGGER_BITS : TRIGGER_TYPES::TRIGGER;
|
|
|
|
if (triggerType == TRIGGER_TYPES::SWITCH)
|
|
{
|
|
item->ItemFlags[1] = true;
|
|
|
|
item->ObjectNumber += GAME_OBJECT_ID{ ID_PUZZLE_DONE1 - ID_PUZZLE_HOLE1 };
|
|
item->ItemFlags[5] = (int)ReusableReceptacleState::Done;
|
|
SetAnimation(item, 0);
|
|
item->ResetModelToDefault();
|
|
}
|
|
else
|
|
{
|
|
item->ObjectNumber += GAME_OBJECT_ID{ ID_PUZZLE_DONE1 - ID_PUZZLE_HOLE1 };
|
|
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex;
|
|
item->Animation.FrameNumber = GetAnimData(item).frameBase;
|
|
item->Animation.ActiveState = GetAnimData(item).ActiveState;
|
|
item->Animation.TargetState = GetAnimData(item).ActiveState;
|
|
item->Animation.RequiredState = NO_STATE;
|
|
item->ResetModelToDefault();
|
|
|
|
AddActiveItem(itemNumber);
|
|
|
|
item->Flags |= IFLAG_ACTIVATION_MASK;
|
|
item->Status = ITEM_ACTIVE;
|
|
}
|
|
}
|
|
|
|
void PuzzleHole(ItemInfo* item, short itemNumber)
|
|
{
|
|
// Display pickup object. TODO: Get offset.
|
|
auto objectID = GAME_OBJECT_ID(item->ObjectNumber - (ID_PUZZLE_DONE1 - ID_PUZZLE_ITEM1));
|
|
g_Hud.PickupSummary.AddDisplayPickup(objectID, item->Pose.Position.ToVector3());
|
|
|
|
item->ItemFlags[1] = true;
|
|
|
|
item->ObjectNumber = GAME_OBJECT_ID(item->ObjectNumber - (ID_PUZZLE_DONE1 - ID_PUZZLE_HOLE1));
|
|
item->ItemFlags[5] = (int)ReusableReceptacleState::Empty;
|
|
SetAnimation(item, 0);
|
|
item->ResetModelToDefault();
|
|
}
|
|
|
|
void DoPuzzle()
|
|
{
|
|
PuzzleItem = Lara.Context.InteractedItem;
|
|
auto* item = &g_Level.Items[PuzzleItem];
|
|
|
|
int flag = 0;
|
|
|
|
if (item->TriggerFlags >= 0)
|
|
{
|
|
if (item->TriggerFlags <= 1024)
|
|
{
|
|
if (item->TriggerFlags && item->TriggerFlags != 999 && item->TriggerFlags != 998)
|
|
flag = 3;
|
|
}
|
|
else
|
|
{
|
|
flag = 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flag = 1;
|
|
}
|
|
|
|
if (LaraItem->Animation.ActiveState == LS_INSERT_PUZZLE)
|
|
{
|
|
if (item->ItemFlags[0])
|
|
{
|
|
if (flag == 3)
|
|
{
|
|
LaraItem->ItemFlags[0] = item->TriggerFlags;
|
|
}
|
|
else
|
|
{
|
|
LaraItem->ItemFlags[0] = 0;
|
|
PuzzleDone(item, PuzzleItem);
|
|
item->ItemFlags[0] = 0;
|
|
}
|
|
}
|
|
|
|
if (LaraItem->Animation.AnimNumber == LA_TRIDENT_SET)
|
|
PuzzleDone(item, PuzzleItem);
|
|
}
|
|
|
|
if (LaraItem->Animation.ActiveState == LS_REMOVE_PUZZLE)
|
|
{
|
|
if (item->ItemFlags[0])
|
|
{
|
|
if (flag == 3)
|
|
{
|
|
LaraItem->ItemFlags[0] = item->TriggerFlags;
|
|
}
|
|
else
|
|
{
|
|
LaraItem->ItemFlags[0] = 0;
|
|
PuzzleHole(item, PuzzleItem);
|
|
item->ItemFlags[0] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keys
|
|
void KeyHoleCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
|
|
{
|
|
auto* keyHoleItem = &g_Level.Items[itemNumber];
|
|
auto* player = GetLaraInfo(laraItem);
|
|
|
|
short* triggerIndexPtr = GetTriggerIndex(keyHoleItem);
|
|
|
|
if (triggerIndexPtr == nullptr)
|
|
return;
|
|
|
|
short triggerType = (*(triggerIndexPtr++) >> 8) & TRIGGER_BITS;
|
|
|
|
bool isActionReady = (IsHeld(In::Action) || g_Gui.GetInventoryItemChosen() != NO_ITEM);
|
|
|
|
bool isPlayerAvailable = !BinocularRange &&
|
|
laraItem->Animation.ActiveState == LS_IDLE &&
|
|
laraItem->Animation.AnimNumber == LA_STAND_IDLE;
|
|
|
|
bool actionActive = player->Control.IsMoving && player->Context.InteractedItem == itemNumber;
|
|
|
|
if (actionActive || (isActionReady && isPlayerAvailable))
|
|
{
|
|
if (TestLaraPosition(KeyHoleBounds, keyHoleItem, laraItem))
|
|
{
|
|
if (!player->Control.IsMoving)
|
|
{
|
|
if (keyHoleItem->Status != ITEM_NOT_ACTIVE && triggerType != TRIGGER_TYPES::SWITCH)
|
|
return;
|
|
|
|
if (g_Gui.GetInventoryItemChosen() == NO_ITEM)
|
|
{
|
|
if (g_Gui.IsObjectInInventory(keyHoleItem->ObjectNumber - (ID_KEY_HOLE1 - ID_KEY_ITEM1)))
|
|
g_Gui.SetEnterInventory(keyHoleItem->ObjectNumber - (ID_KEY_HOLE1 - ID_KEY_ITEM1));
|
|
|
|
return;
|
|
}
|
|
|
|
if (g_Gui.GetInventoryItemChosen() != keyHoleItem->ObjectNumber - (ID_KEY_HOLE1 - ID_KEY_ITEM1))
|
|
return;
|
|
|
|
player->Context.InteractedItem = itemNumber;
|
|
}
|
|
|
|
if (player->Context.InteractedItem != itemNumber)
|
|
return;
|
|
|
|
if (MoveLaraPosition(KeyHolePosition, keyHoleItem, laraItem))
|
|
{
|
|
if (triggerType != TRIGGER_TYPES::SWITCH)
|
|
{
|
|
RemoveObjectFromInventory(GAME_OBJECT_ID(keyHoleItem->ObjectNumber - (ID_KEY_HOLE1 - ID_KEY_ITEM1)), 1);
|
|
}
|
|
else
|
|
{
|
|
keyHoleItem->ItemFlags[1] = true;
|
|
}
|
|
|
|
if (keyHoleItem->TriggerFlags == 0)
|
|
{
|
|
laraItem->Animation.AnimNumber = LA_USE_KEY;
|
|
}
|
|
else
|
|
{
|
|
laraItem->Animation.AnimNumber = keyHoleItem->TriggerFlags;
|
|
}
|
|
|
|
laraItem->Animation.ActiveState = LS_INSERT_KEY;
|
|
laraItem->Animation.FrameNumber = GetAnimData(laraItem).frameBase;
|
|
player->Control.IsMoving = false;
|
|
ResetPlayerFlex(laraItem);
|
|
player->Control.HandStatus = HandStatus::Busy;
|
|
keyHoleItem->Flags |= TRIGGERED;
|
|
keyHoleItem->Status = ITEM_ACTIVE;
|
|
}
|
|
|
|
g_Gui.SetInventoryItemChosen(NO_ITEM);
|
|
return;
|
|
}
|
|
|
|
if (player->Control.IsMoving && player->Context.InteractedItem == itemNumber)
|
|
{
|
|
player->Control.IsMoving = false;
|
|
player->Control.HandStatus = HandStatus::Free;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (keyHoleItem->ObjectNumber < ID_KEY_HOLE6)
|
|
ObjectCollision(itemNumber, laraItem, coll);
|
|
}
|
|
|
|
return;
|
|
}
|