mirror of
https://github.com/TombEngine/TombEngine.git
synced 2025-05-08 03:28:03 +03:00
1003 lines
26 KiB
C++
1003 lines
26 KiB
C++
#include "framework.h"
|
|
#include "Objects/TR5/Object/tr5_pushableblock.h"
|
|
#include "Game/Lara/lara.h"
|
|
#include "Game/Lara/lara_helpers.h"
|
|
#include "Game/animation.h"
|
|
#include "Game/items.h"
|
|
#include "Game/collision/collide_room.h"
|
|
#include "Game/collision/collide_item.h"
|
|
#include "Game/control/flipeffect.h"
|
|
#include "Game/control/box.h"
|
|
#include "Specific/level.h"
|
|
#include "Specific/input.h"
|
|
#include "Sound/sound.h"
|
|
#include "Specific/setup.h"
|
|
#include "Objects/TR5/Object/tr5_pushableblock_info.h"
|
|
|
|
Vector3Int PushableBlockPos = { 0, 0, 0 };
|
|
static OBJECT_COLLISION_BOUNDS PushableBlockBounds =
|
|
{
|
|
0, 0,
|
|
-64, 0,
|
|
0, 0,
|
|
ANGLE(-10.0f), ANGLE(10.0f),
|
|
ANGLE(-30.0f), ANGLE(30.0f),
|
|
ANGLE(-10.0f), ANGLE(10.0f)
|
|
};
|
|
|
|
int DoPushPull = 0;
|
|
|
|
void ClearMovableBlockSplitters(int x, int y, int z, short roomNumber)
|
|
{
|
|
FloorInfo* floor = GetFloor(x, y, z, &roomNumber);
|
|
if (floor->Box == NO_BOX)
|
|
return;
|
|
|
|
g_Level.Boxes[floor->Box].flags &= (~BLOCKED);
|
|
short height = g_Level.Boxes[floor->Box].height;
|
|
short baseRoomNumber = roomNumber;
|
|
|
|
floor = GetFloor(x + 1024, y, z, &roomNumber);
|
|
if (floor->Box != NO_BOX)
|
|
{
|
|
if (g_Level.Boxes[floor->Box].height == height && (g_Level.Boxes[floor->Box].flags & BLOCKABLE) && (g_Level.Boxes[floor->Box].flags & BLOCKED))
|
|
ClearMovableBlockSplitters(x + SECTOR(1), y, z, roomNumber);
|
|
}
|
|
|
|
roomNumber = baseRoomNumber;
|
|
floor = GetFloor(x - 1024, y, z, &roomNumber);
|
|
if (floor->Box != NO_BOX)
|
|
{
|
|
if (g_Level.Boxes[floor->Box].height == height && (g_Level.Boxes[floor->Box].flags & BLOCKABLE) && (g_Level.Boxes[floor->Box].flags & BLOCKED))
|
|
ClearMovableBlockSplitters(x - SECTOR(1), y, z, roomNumber);
|
|
}
|
|
|
|
roomNumber = baseRoomNumber;
|
|
floor = GetFloor(x, y, z + 1024, &roomNumber);
|
|
if (floor->Box != NO_BOX)
|
|
{
|
|
if (g_Level.Boxes[floor->Box].height == height && (g_Level.Boxes[floor->Box].flags & BLOCKABLE) && (g_Level.Boxes[floor->Box].flags & BLOCKED))
|
|
ClearMovableBlockSplitters(x, y, z + SECTOR(1), roomNumber);
|
|
}
|
|
|
|
roomNumber = baseRoomNumber;
|
|
floor = GetFloor(x, y, z - 1024, &roomNumber);
|
|
if (floor->Box != NO_BOX)
|
|
{
|
|
if (g_Level.Boxes[floor->Box].height == height && (g_Level.Boxes[floor->Box].flags & BLOCKABLE) && (g_Level.Boxes[floor->Box].flags & BLOCKED))
|
|
ClearMovableBlockSplitters(x, y, z - SECTOR(1), roomNumber);
|
|
}
|
|
}
|
|
|
|
void InitialisePushableBlock(short itemNumber)
|
|
{
|
|
auto* item = &g_Level.Items[itemNumber];
|
|
item->ItemFlags[1] = NO_ITEM; // need to use itemFlags[1] to hold linked index for now
|
|
|
|
// allocate new pushable info
|
|
item->Data = PushableInfo();
|
|
auto* info = (PushableInfo*)item->Data;
|
|
|
|
info->stackLimit = 3; // LUA
|
|
info->gravity = 8; // LUA
|
|
info->weight = 100; // LUA
|
|
info->moveX = item->Pose.Position.x;
|
|
info->moveZ = item->Pose.Position.z;
|
|
|
|
// read flags from OCB
|
|
int OCB = item->TriggerFlags;
|
|
|
|
info->canFall = OCB & 0x20;
|
|
info->disablePull = OCB & 0x80;
|
|
info->disablePush = OCB & 0x100;
|
|
info->disableW = info->disableE = OCB & 0x200;
|
|
info->disableN = info->disableS = OCB & 0x400;
|
|
|
|
info->climb = 0; // maybe there will be better way to handle this than OCBs?
|
|
/*
|
|
pushable->climb |= (OCB & 0x800) ? CLIMB_WEST : 0;
|
|
pushable->climb |= (OCB & 0x1000) ? CLIMB_NORTH : 0;
|
|
pushable->climb |= (OCB & 0x2000) ? CLIMB_EAST : 0;
|
|
pushable->climb |= (OCB & 0x4000) ? CLIMB_SOUTH : 0;
|
|
*/
|
|
info->hasFloorCeiling = false;
|
|
|
|
int height;
|
|
if (OCB & 0x40 && (OCB & 0x1F) >= 2)
|
|
{
|
|
info->hasFloorCeiling = true;
|
|
TEN::Floordata::AddBridge(itemNumber);
|
|
height = (OCB & 0x1F) * CLICK(1);
|
|
}
|
|
else
|
|
height = -GetBoundsAccurate(item)->Y1;
|
|
|
|
info->height = height;
|
|
|
|
info->loopSound = SFX_TR4_PUSHABLE_SOUND; // LUA
|
|
info->stopSound = SFX_TR4_PUSH_BLOCK_END; // LUA
|
|
info->fallSound = SFX_TR4_BOULDER_FALL; // LUA
|
|
|
|
FindStack(itemNumber); // check for stack formation when pushables are initialised
|
|
}
|
|
|
|
void PushableBlockControl(short itemNumber)
|
|
{
|
|
auto* item = &g_Level.Items[itemNumber];
|
|
auto* info = (PushableInfo*)item->Data;
|
|
|
|
Lara.InteractedItem = itemNumber;
|
|
|
|
Vector3Int pos = { 0, 0, 0 };
|
|
|
|
short quadrant = (unsigned short)(LaraItem->Pose.Orientation.y + ANGLE(45.0f)) / ANGLE(90.0f);
|
|
|
|
int x, z;
|
|
int blockHeight = GetStackHeight(item);
|
|
|
|
// do sound effects, it works for now
|
|
if (DoPushPull > 0)
|
|
SoundEffect(info->loopSound, &item->Pose, SoundEnvironment::Always);
|
|
else if (DoPushPull < 0)
|
|
{
|
|
DoPushPull = 0;
|
|
SoundEffect(info->stopSound, &item->Pose, SoundEnvironment::Always);
|
|
}
|
|
|
|
// control block falling
|
|
if (item->Animation.Airborne)
|
|
{
|
|
int floorHeight = GetCollision(item->Pose.Position.x, item->Pose.Position.y + 10, item->Pose.Position.z, item->RoomNumber).Position.Floor;
|
|
|
|
if (item->Pose.Position.y < (floorHeight - item->Animation.VerticalVelocity))
|
|
{
|
|
if ((item->Animation.VerticalVelocity + info->gravity) < 128)
|
|
item->Animation.VerticalVelocity += info->gravity;
|
|
else
|
|
item->Animation.VerticalVelocity++;
|
|
item->Pose.Position.y += item->Animation.VerticalVelocity;
|
|
|
|
MoveStackY(itemNumber, item->Animation.VerticalVelocity);
|
|
}
|
|
else
|
|
{
|
|
item->Animation.Airborne = false;
|
|
int relY = floorHeight - item->Pose.Position.y;
|
|
item->Pose.Position.y = floorHeight;
|
|
|
|
if (item->Animation.VerticalVelocity >= 96)
|
|
FloorShake(item);
|
|
|
|
item->Animation.VerticalVelocity = 0;
|
|
SoundEffect(info->fallSound, &item->Pose, SoundEnvironment::Always);
|
|
|
|
MoveStackY(itemNumber, relY);
|
|
AddBridgeStack(itemNumber);
|
|
|
|
if (FindStack(itemNumber) == NO_ITEM) // if fallen on some existing pushables, don't test triggers
|
|
TestTriggers(item, true, item->Flags & IFLAG_ACTIVATION_MASK);
|
|
|
|
RemoveActiveItem(itemNumber);
|
|
item->Status = ITEM_NOT_ACTIVE;
|
|
|
|
if (info->hasFloorCeiling)
|
|
{
|
|
//AlterFloorHeight(item, -((item->triggerFlags - 64) * 256));
|
|
AdjustStopperFlag(item, item->ItemFlags[0] + 0x8000, false);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
int displaceBox = GetBoundsAccurate(LaraItem)->Z2 - 80; // move pushable based on bbox->Z2 of Lara
|
|
|
|
switch (LaraItem->Animation.AnimNumber)
|
|
{
|
|
case LA_PUSHABLE_PUSH:
|
|
|
|
if (LaraItem->Animation.FrameNumber == g_Level.Anims[LaraItem->Animation.AnimNumber].frameBase)
|
|
{
|
|
RemoveFromStack(itemNumber);
|
|
RemoveBridgeStack(itemNumber);
|
|
}
|
|
|
|
switch (quadrant)
|
|
{
|
|
case 0:
|
|
z = info->moveZ + displaceBox;
|
|
|
|
if (abs(item->Pose.Position.z - z) < CLICK(2) && item->Pose.Position.z < z)
|
|
item->Pose.Position.z = z;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
x = info->moveX + displaceBox;
|
|
|
|
if (abs(item->Pose.Position.x - x) < CLICK(2) && item->Pose.Position.x < x)
|
|
item->Pose.Position.x = x;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
z = info->moveZ - displaceBox;
|
|
|
|
if (abs(item->Pose.Position.z - z) < CLICK(2) && item->Pose.Position.z > z)
|
|
item->Pose.Position.z = z;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
x = info->moveX - displaceBox;
|
|
|
|
if (abs(item->Pose.Position.x - x) < CLICK(2) && item->Pose.Position.x > x)
|
|
item->Pose.Position.x = x;
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
MoveStackXZ(itemNumber);
|
|
|
|
|
|
if (LaraItem->Animation.FrameNumber == g_Level.Anims[LaraItem->Animation.AnimNumber].frameEnd - 1)
|
|
{
|
|
if (info->canFall) // check if pushable is about to fall
|
|
{
|
|
int floorHeight = GetCollision(item->Pose.Position.x, item->Pose.Position.y + 10, item->Pose.Position.z, item->RoomNumber).Position.Floor;
|
|
if (floorHeight > item->Pose.Position.y)
|
|
{
|
|
item->Pose.Position.x = item->Pose.Position.x & 0xFFFFFE00 | 0x200;
|
|
item->Pose.Position.z = item->Pose.Position.z & 0xFFFFFE00 | 0x200;
|
|
MoveStackXZ(itemNumber);
|
|
//SoundEffect(pushable->stopSound, &item->pos, SoundEnvironment::Always);
|
|
DoPushPull = 0;
|
|
LaraItem->Animation.TargetState = LS_IDLE;
|
|
|
|
item->Animation.Airborne = true; // do fall
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
if (TrInput & IN_ACTION)
|
|
{
|
|
if (!TestBlockPush(item, blockHeight, quadrant))
|
|
LaraItem->Animation.TargetState = LS_IDLE;
|
|
else
|
|
{
|
|
item->Pose.Position.x = info->moveX = item->Pose.Position.x & 0xFFFFFE00 | 0x200;
|
|
item->Pose.Position.z = info->moveZ = item->Pose.Position.z & 0xFFFFFE00 | 0x200;
|
|
TestTriggers(item, true, item->Flags & IFLAG_ACTIVATION_MASK);
|
|
}
|
|
}
|
|
else
|
|
LaraItem->Animation.TargetState = LS_IDLE;
|
|
}
|
|
|
|
break;
|
|
|
|
case LA_PUSHABLE_PULL:
|
|
|
|
if (LaraItem->Animation.FrameNumber == g_Level.Anims[LaraItem->Animation.AnimNumber].frameBase)
|
|
{
|
|
RemoveFromStack(itemNumber);
|
|
RemoveBridgeStack(itemNumber);
|
|
}
|
|
|
|
switch (quadrant)
|
|
{
|
|
case NORTH:
|
|
z = info->moveZ + displaceBox;
|
|
|
|
if (abs(item->Pose.Position.z - z) < CLICK(2) && item->Pose.Position.z > z)
|
|
item->Pose.Position.z = z;
|
|
|
|
break;
|
|
|
|
case EAST:
|
|
x = info->moveX + displaceBox;
|
|
|
|
if (abs(item->Pose.Position.x - x) < CLICK(2) && item->Pose.Position.x > x)
|
|
item->Pose.Position.x = x;
|
|
|
|
break;
|
|
|
|
case SOUTH:
|
|
z = info->moveZ - displaceBox;
|
|
|
|
if (abs(item->Pose.Position.z - z) < CLICK(2) && item->Pose.Position.z < z)
|
|
item->Pose.Position.z = z;
|
|
|
|
break;
|
|
|
|
case WEST:
|
|
x = info->moveX - displaceBox;
|
|
|
|
if (abs(item->Pose.Position.x - x) < CLICK(2) && item->Pose.Position.x < x)
|
|
item->Pose.Position.x = x;
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
MoveStackXZ(itemNumber);
|
|
|
|
if (LaraItem->Animation.FrameNumber == g_Level.Anims[LaraItem->Animation.AnimNumber].frameEnd - 1)
|
|
{
|
|
if (TrInput & IN_ACTION)
|
|
{
|
|
if (!TestBlockPull(item, blockHeight, quadrant))
|
|
LaraItem->Animation.TargetState = LS_IDLE;
|
|
else
|
|
{
|
|
item->Pose.Position.x = info->moveX = item->Pose.Position.x & 0xFFFFFE00 | 0x200;
|
|
item->Pose.Position.z = info->moveZ = item->Pose.Position.z & 0xFFFFFE00 | 0x200;
|
|
TestTriggers(item, true, item->Flags & IFLAG_ACTIVATION_MASK);
|
|
}
|
|
}
|
|
else
|
|
LaraItem->Animation.TargetState = LS_IDLE;
|
|
}
|
|
|
|
break;
|
|
|
|
case LA_PUSHABLE_PUSH_TO_STAND:
|
|
case LA_PUSHABLE_PULL_TO_STAND:
|
|
if (LaraItem->Animation.FrameNumber == g_Level.Anims[LA_PUSHABLE_PUSH_TO_STAND].frameBase ||
|
|
LaraItem->Animation.FrameNumber == g_Level.Anims[LA_PUSHABLE_PULL_TO_STAND].frameBase)
|
|
{
|
|
item->Pose.Position.x = item->Pose.Position.x & 0xFFFFFE00 | 0x200;
|
|
item->Pose.Position.z = item->Pose.Position.z & 0xFFFFFE00 | 0x200;
|
|
|
|
MoveStackXZ(itemNumber);
|
|
FindStack(itemNumber);
|
|
AddBridgeStack(itemNumber);
|
|
|
|
TestTriggers(item, true, item->Flags & IFLAG_ACTIVATION_MASK);
|
|
}
|
|
|
|
if (LaraItem->Animation.FrameNumber == g_Level.Anims[LaraItem->Animation.AnimNumber].frameEnd)
|
|
{
|
|
RemoveActiveItem(itemNumber);
|
|
item->Status = ITEM_NOT_ACTIVE;
|
|
|
|
if (info->hasFloorCeiling)
|
|
{
|
|
//AlterFloorHeight(item, -((item->triggerFlags - 64) * 256));
|
|
AdjustStopperFlag(item, item->ItemFlags[0] + 0x8000, false);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PushableBlockCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
|
|
{
|
|
auto* laraInfo = GetLaraInfo(laraItem);
|
|
auto* pushableItem = &g_Level.Items[itemNumber];
|
|
auto* pushableInfo = (PushableInfo*)pushableItem->Data;
|
|
|
|
int blockHeight = GetStackHeight(pushableItem);
|
|
|
|
if ((!(TrInput & IN_ACTION) ||
|
|
laraItem->Animation.ActiveState != LS_IDLE ||
|
|
laraItem->Animation.AnimNumber != LA_STAND_IDLE ||
|
|
laraItem->Animation.Airborne ||
|
|
laraInfo->Control.HandStatus != HandStatus::Free ||
|
|
pushableItem->Status == ITEM_INVISIBLE ||
|
|
pushableItem->TriggerFlags < 0) &&
|
|
(!laraInfo->Control.IsMoving || laraInfo->InteractedItem != itemNumber))
|
|
{
|
|
if ((laraItem->Animation.ActiveState != LS_PUSHABLE_GRAB ||
|
|
(laraItem->Animation.FrameNumber != g_Level.Anims[LA_PUSHABLE_GRAB].frameBase + 19) ||
|
|
laraInfo->NextCornerPos.Position.x != itemNumber))
|
|
{
|
|
if (!pushableInfo->hasFloorCeiling)
|
|
ObjectCollision(itemNumber, laraItem, coll);
|
|
|
|
return;
|
|
}
|
|
|
|
short quadrant = (unsigned short)(LaraItem->Pose.Orientation.y + ANGLE(45.0f)) / ANGLE(90.0f);
|
|
|
|
bool quadrantDisabled = false;
|
|
switch (quadrant)
|
|
{
|
|
case NORTH:
|
|
quadrantDisabled = pushableInfo->disableN;
|
|
break;
|
|
case EAST:
|
|
quadrantDisabled = pushableInfo->disableE;
|
|
break;
|
|
case SOUTH:
|
|
quadrantDisabled = pushableInfo->disableS;
|
|
break;
|
|
case WEST:
|
|
quadrantDisabled = pushableInfo->disableW;
|
|
break;
|
|
}
|
|
|
|
if (quadrantDisabled)
|
|
return;
|
|
|
|
if (!CheckStackLimit(pushableItem))
|
|
return;
|
|
|
|
if (!TestBlockMovable(pushableItem, blockHeight))
|
|
return;
|
|
|
|
if (TrInput & IN_FORWARD)
|
|
{
|
|
if (!TestBlockPush(pushableItem, blockHeight, quadrant) || pushableInfo->disablePush)
|
|
return;
|
|
|
|
laraItem->Animation.TargetState = LS_PUSHABLE_PUSH;
|
|
}
|
|
else if (TrInput & IN_BACK)
|
|
{
|
|
if (!TestBlockPull(pushableItem, blockHeight, quadrant) || pushableInfo->disablePull)
|
|
return;
|
|
|
|
laraItem->Animation.TargetState = LS_PUSHABLE_PULL;
|
|
}
|
|
else
|
|
return;
|
|
|
|
pushableItem->Status = ITEM_ACTIVE;
|
|
AddActiveItem(itemNumber);
|
|
ResetLaraFlex(laraItem);
|
|
|
|
pushableInfo->moveX = pushableItem->Pose.Position.x;
|
|
pushableInfo->moveZ = pushableItem->Pose.Position.z;
|
|
|
|
if (pushableInfo->hasFloorCeiling)
|
|
{
|
|
//AlterFloorHeight(item, ((item->triggerFlags - 64) * 256));
|
|
AdjustStopperFlag(pushableItem, pushableItem->ItemFlags[0], false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto* bounds = GetBoundsAccurate(pushableItem);
|
|
PushableBlockBounds.boundingBox.X1 = (bounds->X1 / 2) - 100;
|
|
PushableBlockBounds.boundingBox.X2 = (bounds->X2 / 2) + 100;
|
|
PushableBlockBounds.boundingBox.Z1 = bounds->Z1 - 200;
|
|
PushableBlockBounds.boundingBox.Z2 = 0;
|
|
|
|
short rot = pushableItem->Pose.Orientation.y;
|
|
pushableItem->Pose.Orientation.y = (laraItem->Pose.Orientation.y + ANGLE(45.0f)) & 0xC000;
|
|
|
|
if (TestLaraPosition(&PushableBlockBounds, pushableItem, laraItem))
|
|
{
|
|
unsigned short quadrant = (unsigned short)((pushableItem->Pose.Orientation.y / 0x4000) + ((rot + 0x2000) / 0x4000));
|
|
if (quadrant & 1)
|
|
PushableBlockPos.z = bounds->X1 - 35;
|
|
else
|
|
PushableBlockPos.z = bounds->Z1 - 35;
|
|
|
|
if (pushableInfo->hasFloorCeiling)
|
|
{
|
|
// For now don't use auto-align function because it can collide with climb up moves of Lara
|
|
|
|
laraItem->Pose.Orientation.x = pushableItem->Pose.Orientation.x;
|
|
laraItem->Pose.Orientation.y = pushableItem->Pose.Orientation.y;
|
|
laraItem->Pose.Orientation.z = pushableItem->Pose.Orientation.z;
|
|
|
|
laraItem->Animation.AnimNumber = LA_PUSHABLE_GRAB;
|
|
laraItem->Animation.FrameNumber = g_Level.Anims[laraItem->Animation.AnimNumber].frameBase;
|
|
laraItem->Animation.ActiveState = LS_PUSHABLE_GRAB;
|
|
laraItem->Animation.TargetState = LS_PUSHABLE_GRAB;
|
|
laraInfo->Control.IsMoving = false;
|
|
laraInfo->Control.HandStatus = HandStatus::Busy;
|
|
laraInfo->NextCornerPos.Position.x = itemNumber;
|
|
pushableItem->Pose.Orientation.y = rot;
|
|
}
|
|
else
|
|
{
|
|
if (MoveLaraPosition(&PushableBlockPos, pushableItem, laraItem))
|
|
{
|
|
laraItem->Animation.AnimNumber = LA_PUSHABLE_GRAB;
|
|
laraItem->Animation.FrameNumber = g_Level.Anims[laraItem->Animation.AnimNumber].frameBase;
|
|
laraItem->Animation.ActiveState = LS_PUSHABLE_GRAB;
|
|
laraItem->Animation.TargetState = LS_PUSHABLE_GRAB;
|
|
laraInfo->Control.IsMoving = false;
|
|
laraInfo->Control.HandStatus = HandStatus::Busy;
|
|
laraInfo->NextCornerPos.Position.x = itemNumber;
|
|
pushableItem->Pose.Orientation.y = rot;
|
|
}
|
|
else
|
|
{
|
|
laraInfo->InteractedItem = itemNumber;
|
|
pushableItem->Pose.Orientation.y = rot;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (laraInfo->Control.IsMoving && laraInfo->InteractedItem == itemNumber)
|
|
{
|
|
laraInfo->Control.IsMoving = false;
|
|
laraInfo->Control.HandStatus = HandStatus::Free;
|
|
}
|
|
|
|
pushableItem->Pose.Orientation.y = rot;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PushLoop(ItemInfo* item) // Do Flipeffect 18 in anims
|
|
{
|
|
DoPushPull = 1;
|
|
}
|
|
|
|
void PushEnd(ItemInfo* item) // Do Flipeffect 19 in anims
|
|
{
|
|
if (DoPushPull == 1)
|
|
DoPushPull = -1;
|
|
}
|
|
|
|
bool TestBlockMovable(ItemInfo* item, int blokhite)
|
|
{
|
|
short roomNumber = item->RoomNumber;
|
|
FloorInfo* floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber);
|
|
|
|
if (floor->IsWall(floor->SectorPlane(item->Pose.Position.x, item->Pose.Position.z)))
|
|
return false;
|
|
|
|
if (floor->FloorHeight(item->Pose.Position.x, item->Pose.Position.z) != item->Pose.Position.y)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TestBlockPush(ItemInfo* item, int blockHeight, unsigned short quadrant)
|
|
{
|
|
auto* info = (PushableInfo*)item->Data;
|
|
|
|
int x = item->Pose.Position.x;
|
|
int y = item->Pose.Position.y;
|
|
int z = item->Pose.Position.z;
|
|
|
|
switch (quadrant)
|
|
{
|
|
case NORTH:
|
|
z += SECTOR(1);
|
|
break;
|
|
|
|
case EAST:
|
|
x += SECTOR(1);
|
|
break;
|
|
|
|
case SOUTH:
|
|
z -= SECTOR(1);
|
|
break;
|
|
|
|
case WEST:
|
|
x -= SECTOR(1);
|
|
break;
|
|
}
|
|
|
|
auto probe = GetCollision(x, y - blockHeight, z, item->RoomNumber);
|
|
|
|
auto* room = &g_Level.Rooms[probe.RoomNumber];
|
|
if (GetSector(room, x - room->x, z - room->z)->Stopper)
|
|
return false;
|
|
|
|
if (probe.Position.FloorSlope || probe.Position.DiagonalStep ||
|
|
probe.Block->FloorSlope(0) != Vector2::Zero || probe.Block->FloorSlope(1) != Vector2::Zero)
|
|
return false;
|
|
|
|
if (info->canFall)
|
|
{
|
|
if (probe.Position.Floor < y)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (probe.Position.Floor != y)
|
|
return false;
|
|
}
|
|
|
|
int ceiling = y - blockHeight + 100;
|
|
|
|
if (GetCollision(x, ceiling, z, item->RoomNumber).Position.Ceiling > ceiling)
|
|
return false;
|
|
|
|
int oldX = item->Pose.Position.x;
|
|
int oldZ = item->Pose.Position.z;
|
|
item->Pose.Position.x = x;
|
|
item->Pose.Position.z = z;
|
|
GetCollidedObjects(item, 256, true, &CollidedItems[0], nullptr, 1);
|
|
item->Pose.Position.x = oldX;
|
|
item->Pose.Position.z = oldZ;
|
|
|
|
int i = 0;
|
|
while (CollidedItems[i] != NULL)
|
|
{
|
|
if (Objects[CollidedItems[i]->ObjectNumber].floor == NULL)
|
|
return false;
|
|
else
|
|
{
|
|
const auto& object = Objects[CollidedItems[i]->ObjectNumber];
|
|
int collidedIndex = CollidedItems[i] - g_Level.Items.data(); // index of CollidedItems[i]
|
|
|
|
int xCol = CollidedItems[i]->Pose.Position.x;
|
|
int yCol = CollidedItems[i]->Pose.Position.y;
|
|
int zCol = CollidedItems[i]->Pose.Position.z;
|
|
|
|
// check if floor function returns nullopt
|
|
if (object.floor(collidedIndex, xCol, yCol, zCol) == std::nullopt)
|
|
return false;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TestBlockPull(ItemInfo* item, int blockHeight, short quadrant)
|
|
{
|
|
int xadd = 0;
|
|
int zadd = 0;
|
|
|
|
switch (quadrant)
|
|
{
|
|
case NORTH:
|
|
zadd = -SECTOR(1);
|
|
break;
|
|
|
|
case EAST:
|
|
xadd = -SECTOR(1);
|
|
break;
|
|
|
|
case SOUTH:
|
|
zadd = SECTOR(1);
|
|
break;
|
|
|
|
case WEST:
|
|
xadd = SECTOR(1);
|
|
break;
|
|
}
|
|
|
|
int x = item->Pose.Position.x + xadd;
|
|
int y = item->Pose.Position.y;
|
|
int z = item->Pose.Position.z + zadd;
|
|
|
|
short roomNumber = item->RoomNumber;
|
|
auto* room = &g_Level.Rooms[roomNumber];
|
|
if (GetSector(room, x - room->x, z - room->z)->Stopper)
|
|
return false;
|
|
|
|
auto probe = GetCollision(x, y - blockHeight, z, item->RoomNumber);
|
|
|
|
if (probe.Position.Floor != y)
|
|
return false;
|
|
|
|
if (probe.Position.FloorSlope || probe.Position.DiagonalStep ||
|
|
probe.Block->FloorSlope(0) != Vector2::Zero || probe.Block->FloorSlope(1) != Vector2::Zero)
|
|
return false;
|
|
|
|
int ceiling = y - blockHeight + 100;
|
|
|
|
if (GetCollision(x, ceiling, z, item->RoomNumber).Position.Ceiling > ceiling)
|
|
return false;
|
|
|
|
int oldX = item->Pose.Position.x;
|
|
int oldZ = item->Pose.Position.z;
|
|
item->Pose.Position.x = x;
|
|
item->Pose.Position.z = z;
|
|
GetCollidedObjects(item, 256, true, &CollidedItems[0], 0, 1);
|
|
item->Pose.Position.x = oldX;
|
|
item->Pose.Position.z = oldZ;
|
|
|
|
int i = 0;
|
|
while (CollidedItems[i] != NULL)
|
|
{
|
|
if (Objects[CollidedItems[i]->ObjectNumber].floor == NULL)
|
|
return false;
|
|
else
|
|
{
|
|
const auto& object = Objects[CollidedItems[i]->ObjectNumber];
|
|
int collidedIndex = CollidedItems[i] - g_Level.Items.data();
|
|
|
|
int xCol = CollidedItems[i]->Pose.Position.x;
|
|
int yCol = CollidedItems[i]->Pose.Position.y;
|
|
int zCol = CollidedItems[i]->Pose.Position.z;
|
|
|
|
if (object.floor(collidedIndex, xCol, yCol, zCol) == std::nullopt)
|
|
return false;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
int xAddLara = 0, zAddLara = 0;
|
|
switch (quadrant)
|
|
{
|
|
case NORTH:
|
|
zAddLara = GetBestFrame(LaraItem)->offsetZ;
|
|
break;
|
|
case EAST:
|
|
xAddLara = GetBestFrame(LaraItem)->offsetZ;
|
|
break;
|
|
case SOUTH:
|
|
zAddLara = -GetBestFrame(LaraItem)->offsetZ;
|
|
break;
|
|
case WEST:
|
|
xAddLara = -GetBestFrame(LaraItem)->offsetZ;
|
|
break;
|
|
}
|
|
|
|
x = LaraItem->Pose.Position.x + xadd + xAddLara;
|
|
z = LaraItem->Pose.Position.z + zadd + zAddLara;
|
|
|
|
roomNumber = LaraItem->RoomNumber;
|
|
|
|
probe = GetCollision(x, y - LARA_HEIGHT, z, LaraItem->RoomNumber);
|
|
|
|
room = &g_Level.Rooms[roomNumber];
|
|
if (GetSector(room, x - room->x, z - room->z)->Stopper)
|
|
return false;
|
|
|
|
if (probe.Position.Floor != y)
|
|
return false;
|
|
|
|
if (probe.Block->CeilingHeight(x, z) > y - LARA_HEIGHT)
|
|
return false;
|
|
|
|
oldX = LaraItem->Pose.Position.x;
|
|
oldZ = LaraItem->Pose.Position.z;
|
|
LaraItem->Pose.Position.x = x;
|
|
LaraItem->Pose.Position.z = z;
|
|
GetCollidedObjects(LaraItem, 256, true, &CollidedItems[0], 0, 1);
|
|
LaraItem->Pose.Position.x = oldX;
|
|
LaraItem->Pose.Position.z = oldZ;
|
|
|
|
i = 0;
|
|
while (CollidedItems[i] != NULL)
|
|
{
|
|
if (CollidedItems[i] != item) // if collided item is not pushblock in which lara embedded
|
|
{
|
|
if (Objects[CollidedItems[i]->ObjectNumber].floor == NULL)
|
|
return false;
|
|
else
|
|
{
|
|
const auto& object = Objects[CollidedItems[i]->ObjectNumber];
|
|
int collidedIndex = CollidedItems[i] - g_Level.Items.data();
|
|
int xCol = CollidedItems[i]->Pose.Position.x;
|
|
int yCol = CollidedItems[i]->Pose.Position.y;
|
|
int zCol = CollidedItems[i]->Pose.Position.z;
|
|
|
|
if (object.floor(collidedIndex, xCol, yCol, zCol) == std::nullopt)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MoveStackXZ(short itemNumber)
|
|
{
|
|
auto* item = &g_Level.Items[itemNumber];
|
|
|
|
short probedRoomNumber = GetCollision(item).RoomNumber;
|
|
if (probedRoomNumber != item->RoomNumber)
|
|
ItemNewRoom(itemNumber, probedRoomNumber);
|
|
|
|
auto* stackItem = item;
|
|
while (stackItem->ItemFlags[1] != NO_ITEM) // move pushblock stack together with bottom pushblock
|
|
{
|
|
int stackIndex = stackItem->ItemFlags[1];
|
|
stackItem = &g_Level.Items[stackIndex];
|
|
|
|
stackItem->Pose.Position.x = item->Pose.Position.x;
|
|
stackItem->Pose.Position.z = item->Pose.Position.z;
|
|
|
|
probedRoomNumber = GetCollision(item).RoomNumber;
|
|
if (probedRoomNumber != stackItem->RoomNumber)
|
|
ItemNewRoom(stackIndex, probedRoomNumber);
|
|
}
|
|
}
|
|
|
|
void MoveStackY(short itemNumber, int y)
|
|
{
|
|
auto* item = &g_Level.Items[itemNumber];
|
|
|
|
short probedRoomNumber = GetCollision(item).RoomNumber;
|
|
if (probedRoomNumber != item->RoomNumber)
|
|
ItemNewRoom(itemNumber, probedRoomNumber);
|
|
|
|
while (item->ItemFlags[1] != NO_ITEM) // move pushblock stack together with bottom pushblock
|
|
{
|
|
int stackIndex = item->ItemFlags[1];
|
|
item = &g_Level.Items[stackIndex];
|
|
|
|
item->Pose.Position.y += y;
|
|
|
|
probedRoomNumber = GetCollision(item).RoomNumber;
|
|
if (probedRoomNumber != item->RoomNumber)
|
|
ItemNewRoom(stackIndex, probedRoomNumber);
|
|
}
|
|
}
|
|
|
|
void AddBridgeStack(short itemNumber)
|
|
{
|
|
auto* item = &g_Level.Items[itemNumber];
|
|
auto* info = (PushableInfo*)item->Data;
|
|
|
|
if (info->hasFloorCeiling)
|
|
TEN::Floordata::AddBridge(itemNumber);
|
|
|
|
int stackIndex = g_Level.Items[itemNumber].ItemFlags[1];
|
|
while (stackIndex != NO_ITEM)
|
|
{
|
|
auto* stackItem = &g_Level.Items[stackIndex];
|
|
|
|
if (info->hasFloorCeiling)
|
|
TEN::Floordata::AddBridge(stackIndex);
|
|
|
|
stackIndex = g_Level.Items[stackIndex].ItemFlags[1];
|
|
}
|
|
}
|
|
|
|
void RemoveBridgeStack(short itemNumber)
|
|
{
|
|
auto* item = &g_Level.Items[itemNumber];
|
|
auto* info = (PushableInfo*)item->Data;
|
|
|
|
if (info->hasFloorCeiling)
|
|
TEN::Floordata::RemoveBridge(itemNumber);
|
|
|
|
int stackIndex = g_Level.Items[itemNumber].ItemFlags[1];
|
|
while (stackIndex != NO_ITEM)
|
|
{
|
|
auto* stackItem = &g_Level.Items[stackIndex];
|
|
|
|
if (info->hasFloorCeiling)
|
|
TEN::Floordata::RemoveBridge(stackIndex);
|
|
|
|
stackIndex = g_Level.Items[stackIndex].ItemFlags[1];
|
|
}
|
|
}
|
|
|
|
void RemoveFromStack(short itemNumber) // unlink pushable from stack if linked
|
|
{
|
|
for (int i = 0; i < g_Level.NumItems; i++)
|
|
{
|
|
if (i == itemNumber)
|
|
continue;
|
|
|
|
auto* itemBelow = &g_Level.Items[i];
|
|
|
|
int objectNumber = itemBelow->ObjectNumber;
|
|
if (objectNumber >= ID_PUSHABLE_OBJECT1 && objectNumber <= ID_PUSHABLE_OBJECT10)
|
|
{
|
|
if (itemBelow->ItemFlags[1] == itemNumber)
|
|
itemBelow->ItemFlags[1] = NO_ITEM;
|
|
}
|
|
}
|
|
}
|
|
|
|
int FindStack(short itemNumber)
|
|
{
|
|
int stackTop = NO_ITEM; // index of heighest (Position.y) pushable in stack
|
|
int stackYmin = CLICK(256); // set starting height
|
|
|
|
//Check for pushable directly below current one in same sector
|
|
for (int i = 0; i < g_Level.NumItems; i++)
|
|
{
|
|
if (i == itemNumber)
|
|
continue;
|
|
|
|
auto* itemBelow = &g_Level.Items[i];
|
|
|
|
int objectNumber = itemBelow->ObjectNumber;
|
|
if (objectNumber >= ID_PUSHABLE_OBJECT1 && objectNumber <= ID_PUSHABLE_OBJECT10)
|
|
{
|
|
auto* item = &g_Level.Items[itemNumber];
|
|
int x = item->Pose.Position.x;
|
|
int y = item->Pose.Position.y;
|
|
int z = item->Pose.Position.z;
|
|
|
|
if (itemBelow->Pose.Position.x == x && itemBelow->Pose.Position.z == z)
|
|
{
|
|
int belowY = itemBelow->Pose.Position.y;
|
|
if (belowY > y && belowY < stackYmin)
|
|
{
|
|
// set heighest pushable so far as top of stack
|
|
stackTop = i;
|
|
stackYmin = itemBelow->Pose.Position.y;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stackTop != NO_ITEM)
|
|
g_Level.Items[stackTop].ItemFlags[1] = itemNumber;
|
|
|
|
return stackTop;
|
|
}
|
|
|
|
int GetStackHeight(ItemInfo* item)
|
|
{
|
|
auto* info = (PushableInfo*)item->Data;
|
|
|
|
int height = info->height;
|
|
|
|
auto* stackItem = item;
|
|
while (stackItem->ItemFlags[1] != NO_ITEM)
|
|
{
|
|
stackItem = &g_Level.Items[stackItem->ItemFlags[1]];
|
|
height += info->height;
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
bool CheckStackLimit(ItemInfo* item)
|
|
{
|
|
auto* info = (PushableInfo*)item->Data;
|
|
|
|
int limit = info->stackLimit;
|
|
|
|
int count = 1;
|
|
auto* stackItem = item;
|
|
while (stackItem->ItemFlags[1] != NO_ITEM)
|
|
{
|
|
stackItem = &g_Level.Items[stackItem->ItemFlags[1]];
|
|
count++;
|
|
|
|
if (count > limit)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::optional<int> PushableBlockFloor(short itemNumber, int x, int y, int z)
|
|
{
|
|
const auto& item = g_Level.Items[itemNumber];
|
|
const auto& pushable = (PushableInfo&)item.Data;
|
|
|
|
if (item.Status != ITEM_INVISIBLE && pushable.hasFloorCeiling)
|
|
{
|
|
const auto height = item.Pose.Position.y - (item.TriggerFlags & 0x1F) * CLICK(1);
|
|
return std::optional{height};
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<int> PushableBlockCeiling(short itemNumber, int x, int y, int z)
|
|
{
|
|
const auto& item = g_Level.Items[itemNumber];
|
|
const auto& pushable = (PushableInfo&)item.Data;
|
|
|
|
if (item.Status != ITEM_INVISIBLE && pushable.hasFloorCeiling)
|
|
return std::optional{item.Pose.Position.y};
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
int PushableBlockFloorBorder(short itemNumber)
|
|
{
|
|
const auto& item = g_Level.Items[itemNumber];
|
|
const auto height = item.Pose.Position.y - (item.TriggerFlags & 0x1F) * CLICK(1);
|
|
return height;
|
|
}
|
|
|
|
int PushableBlockCeilingBorder(short itemNumber)
|
|
{
|
|
const auto& item = g_Level.Items[itemNumber];
|
|
return item.Pose.Position.y;
|
|
}
|