mirror of
https://github.com/TombEngine/TombEngine.git
synced 2025-05-11 04:56:49 +03:00
Updates to pushables
Introduced PUSHABLE_INFO struct, which is stored in item->data of pushable. Tried to implement as many TRNG pushable OCBs as I could.
This commit is contained in:
parent
1d4e69ba36
commit
cf1db753da
3 changed files with 381 additions and 209 deletions
|
@ -17,6 +17,7 @@
|
||||||
#include "tr5_rats_emitter.h"
|
#include "tr5_rats_emitter.h"
|
||||||
#include "tr5_bats_emitter.h"
|
#include "tr5_bats_emitter.h"
|
||||||
#include "tr5_spider_emitter.h"
|
#include "tr5_spider_emitter.h"
|
||||||
|
#include "tr5_pushableblock.h"
|
||||||
#include "pickup.h"
|
#include "pickup.h"
|
||||||
#include "puzzles_keys.h"
|
#include "puzzles_keys.h"
|
||||||
#include "lara_fire.h"
|
#include "lara_fire.h"
|
||||||
|
@ -48,8 +49,8 @@ function<EffectFunction> effect_routines[59] =
|
||||||
draw_left_pistol,
|
draw_left_pistol,
|
||||||
shoot_right_gun,
|
shoot_right_gun,
|
||||||
shoot_left_gun,
|
shoot_left_gun,
|
||||||
void_effect,
|
pushLoop,
|
||||||
void_effect,
|
pushEnd,
|
||||||
void_effect,
|
void_effect,
|
||||||
invisibility_on,
|
invisibility_on,
|
||||||
invisibility_off,
|
invisibility_off,
|
||||||
|
|
|
@ -61,16 +61,56 @@ void ClearMovableBlockSplitters(int x, int y, int z, short roomNumber)
|
||||||
void InitialisePushableBlock(short itemNum)
|
void InitialisePushableBlock(short itemNum)
|
||||||
{
|
{
|
||||||
ITEM_INFO* item = &g_Level.Items[itemNum];
|
ITEM_INFO* item = &g_Level.Items[itemNum];
|
||||||
|
item->itemFlags[1] = NO_ITEM; // need to use itemFlags[1] to hold linked index for now
|
||||||
|
|
||||||
ClearMovableBlockSplitters(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber);
|
ClearMovableBlockSplitters(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber);
|
||||||
item->itemFlags[1] = NO_ITEM; // used for linking pushables together in stack
|
|
||||||
//if (item->status != ITEM_INVISIBLE && item->triggerFlags >= 64)
|
|
||||||
// AlterFloorHeight(item, -((item->triggerFlags - 64) * 256));
|
PUSHABLE_INFO* pushable = new PUSHABLE_INFO;
|
||||||
|
|
||||||
|
pushable->stackLimit = 3; // LUA
|
||||||
|
pushable->gravity = 9; // LUA
|
||||||
|
pushable->weight = 100; // LUA
|
||||||
|
|
||||||
|
// read flags from OCB
|
||||||
|
int OCB = item->triggerFlags;
|
||||||
|
|
||||||
|
pushable->canFall = OCB & 0x20;
|
||||||
|
pushable->hasFloorCeiling = OCB & 0x40;
|
||||||
|
pushable->disablePull = OCB & 0x80;
|
||||||
|
pushable->disablePush = OCB & 0x100;
|
||||||
|
pushable->disableW = pushable->disableE = OCB & 0x200;
|
||||||
|
pushable->disableN = pushable->disableS = OCB & 0x400;
|
||||||
|
|
||||||
|
pushable->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;
|
||||||
|
*/
|
||||||
|
|
||||||
|
int height;
|
||||||
|
if (pushable->hasFloorCeiling)
|
||||||
|
height = (OCB & 0x1F) * CLICK(1);
|
||||||
|
else
|
||||||
|
height = -GetBoundsAccurate(item)->Y1;
|
||||||
|
|
||||||
|
pushable->height = height;
|
||||||
|
|
||||||
|
pushable->loopSound = SFX_PUSHABLE_SOUND; // LUA
|
||||||
|
pushable->stopSound = SFX_PUSH_BLOCK_END; // LUA
|
||||||
|
pushable->fallSound = SFX_TR4_BOULDER_FALL; // LUA
|
||||||
|
|
||||||
|
item->data = (void*) pushable;
|
||||||
|
|
||||||
|
FindStack(itemNum); // check for stack formation on init (can't use pushable->linkedIndex with current setup)
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushableBlockControl(short itemNumber)
|
void PushableBlockControl(short itemNumber)
|
||||||
{
|
{
|
||||||
ITEM_INFO* item = &g_Level.Items[itemNumber];
|
ITEM_INFO* item = &g_Level.Items[itemNumber];
|
||||||
|
Lara.generalPtr = (void*)itemNumber;
|
||||||
|
|
||||||
PHD_VECTOR pos;
|
PHD_VECTOR pos;
|
||||||
pos.x = 0;
|
pos.x = 0;
|
||||||
|
@ -80,57 +120,85 @@ void PushableBlockControl(short itemNumber)
|
||||||
short quadrant = (unsigned short)(LaraItem->pos.yRot + ANGLE(45)) / ANGLE(90);
|
short quadrant = (unsigned short)(LaraItem->pos.yRot + ANGLE(45)) / ANGLE(90);
|
||||||
|
|
||||||
int x, z;
|
int x, z;
|
||||||
|
short roomNumber;
|
||||||
FLOOR_INFO* floor;
|
FLOOR_INFO* floor;
|
||||||
ROOM_INFO* r;
|
ROOM_INFO* r;
|
||||||
int height;
|
PUSHABLE_INFO* pushable = pushable_info(item);
|
||||||
short roomNumber;
|
int blockHeight = GetStackHeight(item);
|
||||||
|
|
||||||
int stackIndex; // used for stacked pushables
|
// do sound effects, it works for now
|
||||||
int blockHeight;
|
if (DoPushPull > 0)
|
||||||
|
|
||||||
if (item->triggerFlags > 64)
|
|
||||||
blockHeight = CLICK(item->triggerFlags - 64);
|
|
||||||
else
|
|
||||||
blockHeight = -(GetBoundsAccurate(item)->Y1);
|
|
||||||
|
|
||||||
// get total height of stack
|
|
||||||
stackIndex = item->itemFlags[1];
|
|
||||||
while (stackIndex != NO_ITEM)
|
|
||||||
{
|
{
|
||||||
auto stackItem = &g_Level.Items[stackIndex];
|
int blockIndex = (short)Lara.generalPtr;
|
||||||
if (stackItem->triggerFlags > 64)
|
ITEM_INFO* block = &g_Level.Items[blockIndex];
|
||||||
blockHeight += CLICK(stackItem->triggerFlags - 64);
|
SoundEffect(pushable_info(block)->loopSound, &block->pos, 2);
|
||||||
else
|
}
|
||||||
blockHeight += -(GetBoundsAccurate(stackItem)->Y1);
|
else if (DoPushPull < 0)
|
||||||
|
{
|
||||||
stackIndex = stackItem->itemFlags[1];
|
DoPushPull = 0;
|
||||||
|
int blockIndex = (short)Lara.generalPtr;
|
||||||
|
ITEM_INFO* block = &g_Level.Items[blockIndex];
|
||||||
|
SoundEffect(pushable_info(block)->stopSound, &block->pos, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// control block falling
|
||||||
|
if (item->gravityStatus)
|
||||||
|
{
|
||||||
|
roomNumber = item->roomNumber;
|
||||||
|
floor = GetFloor(item->pos.xPos, item->pos.yPos, item->pos.zPos, &roomNumber);
|
||||||
|
int floorHeight = GetFloorHeight(floor, item->pos.xPos, item->pos.yPos + 10, item->pos.zPos);
|
||||||
|
|
||||||
|
if (item->pos.yPos < floorHeight - item->fallspeed)
|
||||||
|
{
|
||||||
|
if (item->fallspeed + pushable->gravity < 128)
|
||||||
|
item->fallspeed += pushable->gravity;
|
||||||
|
else
|
||||||
|
item->fallspeed = 128;
|
||||||
|
item->pos.yPos += item->fallspeed;
|
||||||
|
|
||||||
|
MoveStackY(itemNumber, item->fallspeed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item->gravityStatus = false;
|
||||||
|
int relY = floorHeight - item->pos.yPos;
|
||||||
|
item->pos.yPos = floorHeight;
|
||||||
|
if (item->fallspeed >= 128)
|
||||||
|
floor_shake_effect(item);
|
||||||
|
item->fallspeed = 0;
|
||||||
|
SoundEffect(pushable->fallSound, &item->pos, 2);
|
||||||
|
|
||||||
|
MoveStackY(itemNumber, relY);
|
||||||
|
|
||||||
|
if (FindStack(itemNumber) == NO_ITEM) // if fallen on some existing pushables, don't test triggers
|
||||||
|
{
|
||||||
|
roomNumber = item->roomNumber;
|
||||||
|
TestTriggersAtXYZ(item->pos.xPos, item->pos.yPos, item->pos.zPos, roomNumber, 1, item->flags & 0x3E00);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveActiveItem(itemNumber);
|
||||||
|
item->status = ITEM_NOT_ACTIVE;
|
||||||
|
|
||||||
|
if (pushable->hasFloorCeiling)
|
||||||
|
{
|
||||||
|
//AlterFloorHeight(item, -((item->triggerFlags - 64) * 256));
|
||||||
|
AdjustStopperFlag(item, item->itemFlags[0] + 0x8000, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (LaraItem->animNumber)
|
switch (LaraItem->animNumber)
|
||||||
{
|
{
|
||||||
case LA_PUSHABLE_PUSH:
|
case LA_PUSHABLE_PUSH:
|
||||||
if ((LaraItem->frameNumber < g_Level.Anims[LaraItem->animNumber].frameBase + 30
|
|
||||||
|| LaraItem->frameNumber > g_Level.Anims[LaraItem->animNumber].frameBase + 67)
|
if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameBase)
|
||||||
&& (LaraItem->frameNumber < g_Level.Anims[LaraItem->animNumber].frameBase + 78
|
RemoveFromStack(itemNumber);
|
||||||
|| LaraItem->frameNumber > g_Level.Anims[LaraItem->animNumber].frameBase + 125)
|
|
||||||
&& (LaraItem->frameNumber < g_Level.Anims[LaraItem->animNumber].frameBase + 140
|
|
||||||
|| LaraItem->frameNumber > g_Level.Anims[LaraItem->animNumber].frameBase + 160))
|
|
||||||
{
|
|
||||||
if (DoPushPull)
|
|
||||||
{
|
|
||||||
SoundEffect(SFX_PUSH_BLOCK_END, &item->pos, 2);
|
|
||||||
DoPushPull = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SoundEffect(SFX_PUSHABLE_SOUND, &item->pos, 2);
|
|
||||||
DoPushPull = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetLaraJointPosition(&pos, LM_LHAND);
|
GetLaraJointPosition(&pos, LM_LHAND);
|
||||||
|
|
||||||
|
// TODO: come up with better code that doesn't rely on itemFlags
|
||||||
switch (quadrant)
|
switch (quadrant)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -162,31 +230,24 @@ void PushableBlockControl(short itemNumber)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
stackIndex = item->itemFlags[1];
|
MoveStackXZ(itemNumber);
|
||||||
while (stackIndex != NO_ITEM) // move pushblock stack together with bottom pushblock
|
|
||||||
{
|
|
||||||
auto stackItem = &g_Level.Items[stackIndex];
|
|
||||||
stackItem->pos.xPos = item->pos.xPos;
|
|
||||||
stackItem->pos.zPos = item->pos.zPos;
|
|
||||||
|
|
||||||
stackIndex = stackItem->itemFlags[1];
|
if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameEnd - 16)
|
||||||
}
|
|
||||||
|
|
||||||
if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameBase)
|
|
||||||
{
|
{
|
||||||
// unlink pushable from stack if linked
|
if (pushable->canFall)
|
||||||
for (int i = 0; i < g_Level.NumItems; i++)
|
|
||||||
{
|
{
|
||||||
if (i == itemNumber)
|
roomNumber = item->roomNumber;
|
||||||
continue;
|
floor = GetFloor(item->pos.xPos, item->pos.yPos, item->pos.zPos, &roomNumber);
|
||||||
|
if (GetFloorHeight(floor, item->pos.xPos, item->pos.yPos + 10, item->pos.zPos) > item->pos.yPos)
|
||||||
auto belowItem = &g_Level.Items[i];
|
|
||||||
|
|
||||||
int objectNum = belowItem->objectNumber;
|
|
||||||
if (objectNum >= ID_PUSHABLE_OBJECT1 && objectNum <= ID_PUSHABLE_OBJECT10)
|
|
||||||
{
|
{
|
||||||
if (belowItem->itemFlags[1] == itemNumber)
|
item->pos.xPos = item->pos.xPos & 0xFFFFFE00 | 0x200;
|
||||||
belowItem->itemFlags[1] = NO_ITEM;
|
item->pos.zPos = item->pos.zPos & 0xFFFFFE00 | 0x200;
|
||||||
|
MoveStackXZ(itemNumber);
|
||||||
|
SoundEffect(pushable->stopSound, &item->pos, 2);
|
||||||
|
DoPushPull = 0;
|
||||||
|
LaraItem->goalAnimState = LS_STOP;
|
||||||
|
|
||||||
|
item->gravityStatus = true; // do fall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,13 +257,11 @@ void PushableBlockControl(short itemNumber)
|
||||||
if (TrInput & IN_ACTION)
|
if (TrInput & IN_ACTION)
|
||||||
{
|
{
|
||||||
if (!TestBlockPush(item, blockHeight, quadrant))
|
if (!TestBlockPush(item, blockHeight, quadrant))
|
||||||
|
{
|
||||||
LaraItem->goalAnimState = LS_STOP;
|
LaraItem->goalAnimState = LS_STOP;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int newRoomNumber = T5M::Floordata::GetRoom(item->roomNumber, item->pos.xPos, item->pos.yPos, item->pos.zPos);
|
|
||||||
if (newRoomNumber != item->roomNumber)
|
|
||||||
ItemNewRoom(itemNumber, newRoomNumber);
|
|
||||||
|
|
||||||
TestTriggersAtXYZ(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber, 1, item->flags & 0x3E00);
|
TestTriggersAtXYZ(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber, 1, item->flags & 0x3E00);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,22 +273,9 @@ void PushableBlockControl(short itemNumber)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LA_PUSHABLE_PULL:
|
case LA_PUSHABLE_PULL:
|
||||||
if ((LaraItem->frameNumber < g_Level.Anims[LaraItem->animNumber].frameBase + 40
|
|
||||||
|| LaraItem->frameNumber > g_Level.Anims[LaraItem->animNumber].frameBase + 122)
|
if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameBase)
|
||||||
&& (LaraItem->frameNumber < g_Level.Anims[LaraItem->animNumber].frameBase + 130
|
RemoveFromStack(itemNumber);
|
||||||
|| LaraItem->frameNumber > g_Level.Anims[LaraItem->animNumber].frameBase + 170))
|
|
||||||
{
|
|
||||||
if (DoPushPull)
|
|
||||||
{
|
|
||||||
SoundEffect(SFX_PUSH_BLOCK_END, &item->pos, 2);
|
|
||||||
DoPushPull = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SoundEffect(SFX_PUSHABLE_SOUND, &item->pos, 2);
|
|
||||||
DoPushPull = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetLaraJointPosition(&pos, LM_LHAND);
|
GetLaraJointPosition(&pos, LM_LHAND);
|
||||||
|
|
||||||
|
@ -263,48 +309,18 @@ void PushableBlockControl(short itemNumber)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
stackIndex = item->itemFlags[1];
|
MoveStackXZ(itemNumber);
|
||||||
while (stackIndex != NO_ITEM) // move pushblock stack together with bottom pushblock
|
|
||||||
{
|
|
||||||
auto stackItem = &g_Level.Items[stackIndex];
|
|
||||||
stackItem->pos.xPos = item->pos.xPos;
|
|
||||||
stackItem->pos.zPos = item->pos.zPos;
|
|
||||||
|
|
||||||
stackIndex = stackItem->itemFlags[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameBase)
|
|
||||||
{
|
|
||||||
// unlink pushable from stack if linked
|
|
||||||
for (int i = 0; i < g_Level.NumItems; i++)
|
|
||||||
{
|
|
||||||
if (i == itemNumber)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto belowItem = &g_Level.Items[i];
|
|
||||||
|
|
||||||
int objectNum = belowItem->objectNumber;
|
|
||||||
if (objectNum >= ID_PUSHABLE_OBJECT1 && objectNum <= ID_PUSHABLE_OBJECT10)
|
|
||||||
{
|
|
||||||
if (belowItem->itemFlags[1] == itemNumber)
|
|
||||||
belowItem->itemFlags[1] = NO_ITEM;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameEnd - 1)
|
if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameEnd - 1)
|
||||||
{
|
{
|
||||||
if (TrInput & IN_ACTION)
|
if (TrInput & IN_ACTION)
|
||||||
{
|
{
|
||||||
if (!TestBlockPull(item, blockHeight, quadrant))
|
if (!TestBlockPull(item, blockHeight, quadrant))
|
||||||
|
{
|
||||||
LaraItem->goalAnimState = LS_STOP;
|
LaraItem->goalAnimState = LS_STOP;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int newRoomNumber = T5M::Floordata::GetRoom(item->roomNumber, item->pos.xPos, item->pos.yPos, item->pos.zPos);
|
|
||||||
if (newRoomNumber != item->roomNumber)
|
|
||||||
ItemNewRoom(itemNumber, newRoomNumber);
|
|
||||||
|
|
||||||
TestTriggersAtXYZ(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber, 1, item->flags & 0x3E00);
|
TestTriggersAtXYZ(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber, 1, item->flags & 0x3E00);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,6 +330,7 @@ void PushableBlockControl(short itemNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LA_PUSHABLE_PUSH_TO_STAND:
|
case LA_PUSHABLE_PUSH_TO_STAND:
|
||||||
case LA_PUSHABLE_PULL_TO_STAND:
|
case LA_PUSHABLE_PULL_TO_STAND:
|
||||||
if (LaraItem->frameNumber == g_Level.Anims[LA_PUSHABLE_PUSH_TO_STAND].frameBase
|
if (LaraItem->frameNumber == g_Level.Anims[LA_PUSHABLE_PUSH_TO_STAND].frameBase
|
||||||
|
@ -322,71 +339,23 @@ void PushableBlockControl(short itemNumber)
|
||||||
item->pos.xPos = item->pos.xPos & 0xFFFFFE00 | 0x200;
|
item->pos.xPos = item->pos.xPos & 0xFFFFFE00 | 0x200;
|
||||||
item->pos.zPos = item->pos.zPos & 0xFFFFFE00 | 0x200;
|
item->pos.zPos = item->pos.zPos & 0xFFFFFE00 | 0x200;
|
||||||
|
|
||||||
stackIndex = item->itemFlags[1];
|
MoveStackXZ(itemNumber);
|
||||||
while (stackIndex != NO_ITEM) // move pushblock stack together with bottom pushblock
|
FindStack(itemNumber);
|
||||||
{
|
|
||||||
auto stackItem = &g_Level.Items[stackIndex];
|
|
||||||
stackItem->pos.xPos = item->pos.xPos;
|
|
||||||
stackItem->pos.zPos = item->pos.zPos;
|
|
||||||
|
|
||||||
stackIndex = stackItem->itemFlags[1];
|
roomNumber = item->roomNumber;
|
||||||
}
|
TestTriggersAtXYZ(item->pos.xPos, item->pos.yPos, item->pos.zPos, roomNumber, 1, item->flags & 0x3E00);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameEnd)
|
if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameEnd)
|
||||||
{
|
{
|
||||||
int newRoomNumber = T5M::Floordata::GetRoom(item->roomNumber, item->pos.xPos, item->pos.yPos, item->pos.zPos);
|
|
||||||
if (newRoomNumber != item->roomNumber)
|
|
||||||
ItemNewRoom(itemNumber, newRoomNumber);
|
|
||||||
|
|
||||||
roomNumber = item->roomNumber;
|
|
||||||
floor = GetFloor(item->pos.xPos, item->pos.yPos - 256, item->pos.zPos, &roomNumber);
|
|
||||||
GetFloorHeight(floor, item->pos.xPos, item->pos.yPos - 256, item->pos.zPos);
|
|
||||||
TestTriggers(TriggerIndex, 1, item->flags & 0x3E00);
|
|
||||||
RemoveActiveItem(itemNumber);
|
RemoveActiveItem(itemNumber);
|
||||||
item->status = ITEM_NOT_ACTIVE;
|
item->status = ITEM_NOT_ACTIVE;
|
||||||
|
|
||||||
if (item->triggerFlags >= 64)
|
if (pushable->hasFloorCeiling)
|
||||||
{
|
{
|
||||||
//AlterFloorHeight(item, -((item->triggerFlags - 64) * 256));
|
//AlterFloorHeight(item, -((item->triggerFlags - 64) * 256));
|
||||||
AdjustStopperFlag(item, item->itemFlags[0] + 0x8000, 0);
|
AdjustStopperFlag(item, item->itemFlags[0] + 0x8000, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int stackTop = NO_ITEM; // index of heighest (yPos) 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 belowItem = &g_Level.Items[i];
|
|
||||||
|
|
||||||
int objectNum = belowItem->objectNumber;
|
|
||||||
if (objectNum >= ID_PUSHABLE_OBJECT1 && objectNum <= ID_PUSHABLE_OBJECT10)
|
|
||||||
{
|
|
||||||
int x = item->pos.xPos;
|
|
||||||
int y = item->pos.yPos;
|
|
||||||
int z = item->pos.zPos;
|
|
||||||
|
|
||||||
if (belowItem->pos.xPos == x && belowItem->pos.zPos == z)
|
|
||||||
{
|
|
||||||
int belowY = belowItem->pos.yPos;
|
|
||||||
if (belowY > y && belowY < stackYmin)
|
|
||||||
{
|
|
||||||
// set heighest pushable so far as top of stack
|
|
||||||
stackTop = i;
|
|
||||||
stackYmin = belowItem->pos.yPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if top pushable in stack was located, link index of current pushable to below pushable via itemFlags[1]
|
|
||||||
if (stackTop != NO_ITEM)
|
|
||||||
g_Level.Items[stackTop].itemFlags[1] = itemNumber;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -398,28 +367,9 @@ void PushableBlockCollision(short itemNum, ITEM_INFO* l, COLL_INFO* coll)
|
||||||
|
|
||||||
short roomNumber = item->roomNumber;
|
short roomNumber = item->roomNumber;
|
||||||
FLOOR_INFO* floor = GetFloor(item->pos.xPos, item->pos.yPos - 256, item->pos.zPos, &roomNumber);
|
FLOOR_INFO* floor = GetFloor(item->pos.xPos, item->pos.yPos - 256, item->pos.zPos, &roomNumber);
|
||||||
|
PUSHABLE_INFO* pushable = pushable_info(item);
|
||||||
|
|
||||||
int stackIndex; // used for stacked pushables
|
int blockHeight = GetStackHeight(item);
|
||||||
int blockHeight;
|
|
||||||
|
|
||||||
if (item->triggerFlags > 64)
|
|
||||||
blockHeight = CLICK(item->triggerFlags - 64);
|
|
||||||
else
|
|
||||||
blockHeight = -(GetBoundsAccurate(item)->Y1);
|
|
||||||
|
|
||||||
// get total height of stack
|
|
||||||
stackIndex = item->itemFlags[1];
|
|
||||||
while (stackIndex != NO_ITEM)
|
|
||||||
{
|
|
||||||
auto stackItem = &g_Level.Items[stackIndex];
|
|
||||||
if (stackItem->triggerFlags > 64)
|
|
||||||
blockHeight += CLICK(stackItem->triggerFlags - 64);
|
|
||||||
else
|
|
||||||
blockHeight += -(GetBoundsAccurate(stackItem)->Y1);
|
|
||||||
|
|
||||||
stackIndex = stackItem->itemFlags[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ((!(TrInput & IN_ACTION)
|
if ((!(TrInput & IN_ACTION)
|
||||||
|| l->currentAnimState != LS_STOP
|
|| l->currentAnimState != LS_STOP
|
||||||
|
@ -434,22 +384,45 @@ void PushableBlockCollision(short itemNum, ITEM_INFO* l, COLL_INFO* coll)
|
||||||
|| (l->frameNumber != g_Level.Anims[LA_PUSHABLE_GRAB].frameBase + 19)
|
|| (l->frameNumber != g_Level.Anims[LA_PUSHABLE_GRAB].frameBase + 19)
|
||||||
|| Lara.cornerX != (int)item))
|
|| Lara.cornerX != (int)item))
|
||||||
{
|
{
|
||||||
if (item->triggerFlags < 64)
|
if (!pushable->hasFloorCeiling)
|
||||||
ObjectCollision(itemNum, l, coll);
|
ObjectCollision(itemNum, l, coll);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
short quadrant = (unsigned short)(LaraItem->pos.yRot + ANGLE(45)) / ANGLE(90);
|
short quadrant = (unsigned short)(LaraItem->pos.yRot + ANGLE(45)) / ANGLE(90);
|
||||||
|
|
||||||
|
bool quadrantDisabled = false;
|
||||||
|
switch (quadrant)
|
||||||
|
{
|
||||||
|
case NORTH:
|
||||||
|
quadrantDisabled = pushable->disableN;
|
||||||
|
break;
|
||||||
|
case EAST:
|
||||||
|
quadrantDisabled = pushable->disableE;
|
||||||
|
break;
|
||||||
|
case SOUTH:
|
||||||
|
quadrantDisabled = pushable->disableS;
|
||||||
|
break;
|
||||||
|
case WEST:
|
||||||
|
quadrantDisabled = pushable->disableW;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quadrantDisabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!CheckStackLimit(item))
|
||||||
|
return;
|
||||||
|
|
||||||
if (TrInput & IN_FORWARD)
|
if (TrInput & IN_FORWARD)
|
||||||
{
|
{
|
||||||
if (!TestBlockPush(item, blockHeight, quadrant))
|
if (!TestBlockPush(item, blockHeight, quadrant) || pushable->disablePush)
|
||||||
return;
|
return;
|
||||||
l->goalAnimState = LS_PUSHABLE_PUSH;
|
l->goalAnimState = LS_PUSHABLE_PUSH;
|
||||||
}
|
}
|
||||||
else if (TrInput & IN_BACK)
|
else if (TrInput & IN_BACK)
|
||||||
{
|
{
|
||||||
if (!TestBlockPull(item, blockHeight, quadrant))
|
if (!TestBlockPull(item, blockHeight, quadrant) || pushable->disablePull)
|
||||||
return;
|
return;
|
||||||
l->goalAnimState = LS_PUSHABLE_PULL;
|
l->goalAnimState = LS_PUSHABLE_PULL;
|
||||||
}
|
}
|
||||||
|
@ -472,13 +445,14 @@ void PushableBlockCollision(short itemNum, ITEM_INFO* l, COLL_INFO* coll)
|
||||||
|
|
||||||
GetLaraJointPosition(&pos, LM_LHAND);
|
GetLaraJointPosition(&pos, LM_LHAND);
|
||||||
|
|
||||||
|
// TODO: come up with better code that doesn't rely on itemFlags
|
||||||
l->itemFlags[0] = pos.x;
|
l->itemFlags[0] = pos.x;
|
||||||
l->itemFlags[2] = pos.z;
|
l->itemFlags[2] = pos.z;
|
||||||
|
|
||||||
item->itemFlags[0] = item->pos.xPos;
|
item->itemFlags[0] = item->pos.xPos;
|
||||||
item->itemFlags[2] = item->pos.zPos;
|
item->itemFlags[2] = item->pos.zPos;
|
||||||
|
|
||||||
if (item->triggerFlags >= 64)
|
if (pushable->hasFloorCeiling)
|
||||||
{
|
{
|
||||||
//AlterFloorHeight(item, ((item->triggerFlags - 64) * 256));
|
//AlterFloorHeight(item, ((item->triggerFlags - 64) * 256));
|
||||||
AdjustStopperFlag(item, item->itemFlags[0], 0);
|
AdjustStopperFlag(item, item->itemFlags[0], 0);
|
||||||
|
@ -508,7 +482,7 @@ void PushableBlockCollision(short itemNum, ITEM_INFO* l, COLL_INFO* coll)
|
||||||
else
|
else
|
||||||
PushableBlockPos.z = bounds->Z1 - 35;
|
PushableBlockPos.z = bounds->Z1 - 35;
|
||||||
|
|
||||||
if (item->triggerFlags > 64)
|
if (pushable->hasFloorCeiling)
|
||||||
{
|
{
|
||||||
// For now don't use auto-align function because it can collide with climb up moves of Lara
|
// For now don't use auto-align function because it can collide with climb up moves of Lara
|
||||||
|
|
||||||
|
@ -558,6 +532,19 @@ void PushableBlockCollision(short itemNum, ITEM_INFO* l, COLL_INFO* coll)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pushLoop(ITEM_INFO* item) // Do Flipeffect 18 in anims
|
||||||
|
{
|
||||||
|
DoPushPull = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushEnd(ITEM_INFO* item) // Do Flipeffect 19 in anims
|
||||||
|
{
|
||||||
|
if (DoPushPull == 1)
|
||||||
|
{
|
||||||
|
DoPushPull = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int TestBlockMovable(ITEM_INFO* item, int blokhite)
|
int TestBlockMovable(ITEM_INFO* item, int blokhite)
|
||||||
{
|
{
|
||||||
short roomNumber = item->roomNumber;
|
short roomNumber = item->roomNumber;
|
||||||
|
@ -602,8 +589,17 @@ int TestBlockPush(ITEM_INFO* item, int blockhite, unsigned short quadrant)
|
||||||
if (XZ_GET_SECTOR(r, x - r->x, z - r->z).stopper)
|
if (XZ_GET_SECTOR(r, x - r->x, z - r->z).stopper)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (GetFloorHeight(floor, x, y - 256, z) != y)
|
int floorHeight = GetFloorHeight(floor, x, y - 256, z);
|
||||||
|
if (pushable_info(item)->canFall)
|
||||||
|
{
|
||||||
|
if (floorHeight < y)
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (floorHeight != y)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
GetFloorHeight(floor, x, y, z);
|
GetFloorHeight(floor, x, y, z);
|
||||||
if (HeightType)
|
if (HeightType)
|
||||||
|
@ -727,12 +723,154 @@ int TestBlockPull(ITEM_INFO* item, int blockhite, short quadrant)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MoveStackXZ(int itemNum)
|
||||||
|
{
|
||||||
|
auto item = &g_Level.Items[itemNum];
|
||||||
|
|
||||||
|
int newRoomNumber = T5M::Floordata::GetRoom(item->roomNumber, item->pos.xPos, item->pos.yPos, item->pos.zPos);
|
||||||
|
if (newRoomNumber != item->roomNumber)
|
||||||
|
ItemNewRoom(itemNum, newRoomNumber);
|
||||||
|
|
||||||
|
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->pos.xPos = item->pos.xPos;
|
||||||
|
stackItem->pos.zPos = item->pos.zPos;
|
||||||
|
|
||||||
|
int newRoomNumber = T5M::Floordata::GetRoom(stackItem->roomNumber, stackItem->pos.xPos, stackItem->pos.yPos, stackItem->pos.zPos);
|
||||||
|
if (newRoomNumber != stackItem->roomNumber)
|
||||||
|
ItemNewRoom(stackIndex, newRoomNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MoveStackY(int itemNum, int y)
|
||||||
|
{
|
||||||
|
auto item = &g_Level.Items[itemNum];
|
||||||
|
|
||||||
|
int newRoomNumber = T5M::Floordata::GetRoom(item->roomNumber, item->pos.xPos, item->pos.yPos, item->pos.zPos);
|
||||||
|
if (newRoomNumber != item->roomNumber)
|
||||||
|
ItemNewRoom(itemNum, newRoomNumber);
|
||||||
|
|
||||||
|
while (item->itemFlags[1] != NO_ITEM) // move pushblock stack together with bottom pushblock
|
||||||
|
{
|
||||||
|
int stackIndex = item->itemFlags[1];
|
||||||
|
item = &g_Level.Items[stackIndex];
|
||||||
|
|
||||||
|
item->pos.yPos += y;
|
||||||
|
|
||||||
|
int newRoomNumber = T5M::Floordata::GetRoom(item->roomNumber, item->pos.xPos, item->pos.yPos, item->pos.zPos);
|
||||||
|
if (newRoomNumber != item->roomNumber)
|
||||||
|
ItemNewRoom(stackIndex, newRoomNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveFromStack(int itemNum) // unlink pushable from stack if linked
|
||||||
|
{
|
||||||
|
for (int i = 0; i < g_Level.NumItems; i++)
|
||||||
|
{
|
||||||
|
if (i == itemNum)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto belowItem = &g_Level.Items[i];
|
||||||
|
|
||||||
|
int objectNum = belowItem->objectNumber;
|
||||||
|
if (objectNum >= ID_PUSHABLE_OBJECT1 && objectNum <= ID_PUSHABLE_OBJECT10)
|
||||||
|
{
|
||||||
|
if (belowItem->itemFlags[1] == itemNum)
|
||||||
|
belowItem->itemFlags[1] = NO_ITEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int FindStack(int itemNum)
|
||||||
|
{
|
||||||
|
int stackTop = NO_ITEM; // index of heighest (yPos) 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 == itemNum)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto belowItem = &g_Level.Items[i];
|
||||||
|
|
||||||
|
int objectNum = belowItem->objectNumber;
|
||||||
|
if (objectNum >= ID_PUSHABLE_OBJECT1 && objectNum <= ID_PUSHABLE_OBJECT10)
|
||||||
|
{
|
||||||
|
auto item = &g_Level.Items[itemNum];
|
||||||
|
int x = item->pos.xPos;
|
||||||
|
int y = item->pos.yPos;
|
||||||
|
int z = item->pos.zPos;
|
||||||
|
|
||||||
|
if (belowItem->pos.xPos == x && belowItem->pos.zPos == z)
|
||||||
|
{
|
||||||
|
int belowY = belowItem->pos.yPos;
|
||||||
|
if (belowY > y && belowY < stackYmin)
|
||||||
|
{
|
||||||
|
// set heighest pushable so far as top of stack
|
||||||
|
stackTop = i;
|
||||||
|
stackYmin = belowItem->pos.yPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stackTop != NO_ITEM)
|
||||||
|
g_Level.Items[stackTop].itemFlags[1] = itemNum;
|
||||||
|
|
||||||
|
return stackTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetStackHeight(ITEM_INFO* item)
|
||||||
|
{
|
||||||
|
int height = pushable_info(item)->height;
|
||||||
|
|
||||||
|
auto stackItem = item;
|
||||||
|
while (stackItem->itemFlags[1] != NO_ITEM)
|
||||||
|
{
|
||||||
|
stackItem = &g_Level.Items[stackItem->itemFlags[1]];
|
||||||
|
height += pushable_info(stackItem)->height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CheckStackLimit(ITEM_INFO* item)
|
||||||
|
{
|
||||||
|
int limit = pushable_info(item)->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 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUSHABLE_INFO* pushable_info(ITEM_INFO* item) // retrieve PUSHABLE_INFO* from void* data
|
||||||
|
{
|
||||||
|
return (PUSHABLE_INFO*)item->data;
|
||||||
|
}
|
||||||
|
|
||||||
std::tuple<std::optional<int>, bool> PushableBlockFloor(short itemNumber, int x, int y, int z)
|
std::tuple<std::optional<int>, bool> PushableBlockFloor(short itemNumber, int x, int y, int z)
|
||||||
{
|
{
|
||||||
const auto& item = g_Level.Items[itemNumber];
|
const auto& item = g_Level.Items[itemNumber];
|
||||||
if (item.status != ITEM_INVISIBLE && item.triggerFlags >= 64 && abs(item.pos.xPos - x) <= SECTOR(1) / 2 && abs(item.pos.zPos - z) <= SECTOR(1) / 2)
|
const auto pushable = pushable_info(&g_Level.Items[itemNumber]);
|
||||||
|
|
||||||
|
if (item.status != ITEM_INVISIBLE && pushable->hasFloorCeiling && !item.gravityStatus &&
|
||||||
|
abs(item.pos.xPos - x) <= SECTOR(1) / 2 && abs(item.pos.zPos - z) <= SECTOR(1) / 2)
|
||||||
{
|
{
|
||||||
auto height = item.pos.yPos - (item.triggerFlags - 64) * CLICK(1);
|
auto height = item.pos.yPos - pushable->height;
|
||||||
return std::make_tuple(std::optional{height}, y > height && y <= item.pos.yPos);
|
return std::make_tuple(std::optional{height}, y > height && y <= item.pos.yPos);
|
||||||
}
|
}
|
||||||
return std::make_tuple(std::nullopt, false);
|
return std::make_tuple(std::nullopt, false);
|
||||||
|
@ -741,9 +879,12 @@ std::tuple<std::optional<int>, bool> PushableBlockFloor(short itemNumber, int x,
|
||||||
std::tuple<std::optional<int>, bool> PushableBlockCeiling(short itemNumber, int x, int y, int z)
|
std::tuple<std::optional<int>, bool> PushableBlockCeiling(short itemNumber, int x, int y, int z)
|
||||||
{
|
{
|
||||||
const auto& item = g_Level.Items[itemNumber];
|
const auto& item = g_Level.Items[itemNumber];
|
||||||
if (item.status != ITEM_INVISIBLE && item.triggerFlags >= 64 && abs(item.pos.xPos - x) <= SECTOR(1) / 2 && abs(item.pos.zPos - z) <= SECTOR(1) / 2)
|
const auto pushable = pushable_info(&g_Level.Items[itemNumber]);
|
||||||
|
|
||||||
|
if (item.status != ITEM_INVISIBLE && pushable->hasFloorCeiling && !item.gravityStatus &&
|
||||||
|
abs(item.pos.xPos - x) <= SECTOR(1) / 2 && abs(item.pos.zPos - z) <= SECTOR(1) / 2)
|
||||||
{
|
{
|
||||||
auto height = item.pos.yPos - (item.triggerFlags - 64) * CLICK(1);
|
auto height = item.pos.yPos - pushable->height;
|
||||||
return std::make_tuple(std::optional{item.pos.yPos}, y >= height && y < item.pos.yPos);
|
return std::make_tuple(std::optional{item.pos.yPos}, y >= height && y < item.pos.yPos);
|
||||||
}
|
}
|
||||||
return std::make_tuple(std::nullopt, false);
|
return std::make_tuple(std::nullopt, false);
|
||||||
|
|
|
@ -2,6 +2,27 @@
|
||||||
#include "items.h"
|
#include "items.h"
|
||||||
#include "collide.h"
|
#include "collide.h"
|
||||||
|
|
||||||
|
typedef struct PUSHABLE_INFO // void* data of pushable
|
||||||
|
{
|
||||||
|
int height; // height for collision, also in floor procedure
|
||||||
|
int weight;
|
||||||
|
int stackLimit;
|
||||||
|
short linkedIndex; // using itemFlags[1] for now
|
||||||
|
short gravity; // fall acceleration
|
||||||
|
short loopSound; // looped sound index for movement
|
||||||
|
short stopSound; // ending sound index
|
||||||
|
short fallSound; // sound on hitting floor (if dropped)
|
||||||
|
short climb; // not used for now
|
||||||
|
bool canFall; // OCB 32
|
||||||
|
bool hasFloorCeiling; // has floor and ceiling procedures (OCB 64)
|
||||||
|
bool disablePull; // OCB 128
|
||||||
|
bool disablePush; // OCB 256
|
||||||
|
bool disableW; // OCB 512 (W+E)
|
||||||
|
bool disableE; // OCB 512 (W+E)
|
||||||
|
bool disableN; // OCB 1024 (N+S)
|
||||||
|
bool disableS; // OCB 1024 (N+S)
|
||||||
|
};
|
||||||
|
|
||||||
void ClearMovableBlockSplitters(int x, int y, int z, short roomNumber);
|
void ClearMovableBlockSplitters(int x, int y, int z, short roomNumber);
|
||||||
void InitialisePushableBlock(short itemNum);
|
void InitialisePushableBlock(short itemNum);
|
||||||
void PushableBlockControl(short itemNum);
|
void PushableBlockControl(short itemNum);
|
||||||
|
@ -9,5 +30,14 @@ void PushableBlockCollision(short itemNum, ITEM_INFO* laraitem, COLL_INFO* coll)
|
||||||
int TestBlockMovable(ITEM_INFO* item, int blokhite);
|
int TestBlockMovable(ITEM_INFO* item, int blokhite);
|
||||||
int TestBlockPush(ITEM_INFO* item, int blockhite, unsigned short quadrant);
|
int TestBlockPush(ITEM_INFO* item, int blockhite, unsigned short quadrant);
|
||||||
int TestBlockPull(ITEM_INFO* item, int blockhite, short quadrant);
|
int TestBlockPull(ITEM_INFO* item, int blockhite, short quadrant);
|
||||||
|
void MoveStackXZ(int itemNum);
|
||||||
|
void MoveStackY(int itemNum, int y);
|
||||||
|
void RemoveFromStack(int itemNum);
|
||||||
|
int FindStack(int itemNum);
|
||||||
|
int GetStackHeight(ITEM_INFO* item);
|
||||||
|
int CheckStackLimit(ITEM_INFO* item);
|
||||||
|
void pushLoop(ITEM_INFO* item);
|
||||||
|
void pushEnd(ITEM_INFO* item);
|
||||||
|
PUSHABLE_INFO* pushable_info(ITEM_INFO* item);
|
||||||
std::tuple<std::optional<int>, bool> PushableBlockFloor(short itemNumber, int x, int y, int z);
|
std::tuple<std::optional<int>, bool> PushableBlockFloor(short itemNumber, int x, int y, int z);
|
||||||
std::tuple<std::optional<int>, bool> PushableBlockCeiling(short itemNumber, int x, int y, int z);
|
std::tuple<std::optional<int>, bool> PushableBlockCeiling(short itemNumber, int x, int y, int z);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue