#include "framework.h" #include "tr5_pushableblock.h" #include "lara.h" #include "draw.h" #include "items.h" #include "collide.h" #include "effect.h" #include "box.h" #include "level.h" #include "input.h" #include "sound.h" #define GET_PUSHABLEINFO(item) ((PUSHABLE_INFO*) item->data) static OBJECT_COLLISION_BOUNDS PushableBlockBounds = { 0x0000, 0x0000, 0xFFC0, 0x0000, 0x0000, 0x0000, 0xF8E4, 0x071C, 0xEAAC, 0x1554, 0xF8E4, 0x071C }; PHD_VECTOR PushableBlockPos = { 0, 0, 0 }; int DoPushPull = 0; void ClearMovableBlockSplitters(int x, int y, int z, short roomNumber) { FLOOR_INFO* floor = GetFloor(x, y, z, &roomNumber); 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 + 1024, 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 - 1024, 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 + 1024, 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 - 1024, roomNumber); } } void InitialisePushableBlock(short 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); // allocate new pushable info PUSHABLE_INFO* pushable = new PUSHABLE_INFO; pushable->stackLimit = 3; // LUA pushable->gravity = 8; // LUA pushable->weight = 100; // LUA pushable->moveX = item->pos.xPos; pushable->moveZ = item->pos.zPos; // read flags from OCB int OCB = item->triggerFlags; pushable->canFall = OCB & 0x20; 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; */ pushable->hasFloorCeiling = false; int height; if (OCB & 0x40 && (OCB & 0x1F) >= 2) { pushable->hasFloorCeiling = true; T5M::Floordata::AddBridge(itemNum); height = (OCB & 0x1F) * CLICK(1); } else height = -GetBoundsAccurate(item)->Y1; pushable->height = height; pushable->loopSound = SFX_TR4_PUSHABLE_SOUND; // LUA pushable->stopSound = SFX_TR4_PUSH_BLOCK_END; // LUA pushable->fallSound = SFX_TR4_BOULDER_FALL; // LUA item->data = (void*) pushable; FindStack(itemNum); // check for stack formation when pushables are initialised } void PushableBlockControl(short itemNumber) { ITEM_INFO* item = &g_Level.Items[itemNumber]; Lara.generalPtr = (void*)itemNumber; PHD_VECTOR pos; pos.x = 0; pos.y = 0; pos.z = 0; short quadrant = (unsigned short)(LaraItem->pos.yRot + ANGLE(45)) / ANGLE(90); int x, z; short roomNumber; FLOOR_INFO* floor; ROOM_INFO* r; PUSHABLE_INFO* pushable = GET_PUSHABLEINFO(item); int blockHeight = GetStackHeight(item); // do sound effects, it works for now if (DoPushPull > 0) { SoundEffect(pushable->loopSound, &item->pos, 2); } else if (DoPushPull < 0) { DoPushPull = 0; SoundEffect(pushable->stopSound, &item->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++; 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 >= 96) floor_shake_effect(item); item->fallspeed = 0; SoundEffect(pushable->fallSound, &item->pos, 2); MoveStackY(itemNumber, relY); AddBridgeStack(itemNumber); 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; } int displaceBox = GetBoundsAccurate(LaraItem)->Z2 - 80; // move pushable based on bbox->Z2 of Lara switch (LaraItem->animNumber) { case LA_PUSHABLE_PUSH: if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameBase) { RemoveFromStack(itemNumber); RemoveBridgeStack(itemNumber); } switch (quadrant) { case 0: z = pushable->moveZ + displaceBox; if (abs(item->pos.zPos - z) < 512 && item->pos.zPos < z) item->pos.zPos = z; break; case 1: x = pushable->moveX + displaceBox; if (abs(item->pos.xPos - x) < 512 && item->pos.xPos < x) item->pos.xPos = x; break; case 2: z = pushable->moveZ - displaceBox; if (abs(item->pos.zPos - z) < 512 && item->pos.zPos > z) item->pos.zPos = z; break; case 3: x = pushable->moveX - displaceBox; if (abs(item->pos.xPos - x) < 512 && item->pos.xPos > x) item->pos.xPos = x; break; default: break; } MoveStackXZ(itemNumber); if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameEnd - 1) { if (pushable->canFall) // check if pushable is about to fall { roomNumber = item->roomNumber; 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) { item->pos.xPos = item->pos.xPos & 0xFFFFFE00 | 0x200; 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 return; } } if (TrInput & IN_ACTION) { if (!TestBlockPush(item, blockHeight, quadrant)) { LaraItem->goalAnimState = LS_STOP; } else { item->pos.xPos = pushable->moveX = item->pos.xPos & 0xFFFFFE00 | 0x200; item->pos.zPos = pushable->moveZ = item->pos.zPos & 0xFFFFFE00 | 0x200; TestTriggersAtXYZ(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber, 1, item->flags & 0x3E00); } } else { LaraItem->goalAnimState = LS_STOP; } } break; case LA_PUSHABLE_PULL: if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameBase) { RemoveFromStack(itemNumber); RemoveBridgeStack(itemNumber); } switch (quadrant) { case NORTH: z = pushable->moveZ + displaceBox; if (abs(item->pos.zPos - z) < 512 && item->pos.zPos > z) item->pos.zPos = z; break; case EAST: x = pushable->moveX + displaceBox; if (abs(item->pos.xPos - x) < 512 && item->pos.xPos > x) item->pos.xPos = x; break; case SOUTH: z = pushable->moveZ - displaceBox; if (abs(item->pos.zPos - z) < 512 && item->pos.zPos < z) item->pos.zPos = z; break; case WEST: x = pushable->moveX - displaceBox; if (abs(item->pos.xPos - x) < 512 && item->pos.xPos < x) item->pos.xPos = x; break; default: break; } MoveStackXZ(itemNumber); if (LaraItem->frameNumber == g_Level.Anims[LaraItem->animNumber].frameEnd - 1) { if (TrInput & IN_ACTION) { if (!TestBlockPull(item, blockHeight, quadrant)) { LaraItem->goalAnimState = LS_STOP; } else { item->pos.xPos = pushable->moveX = item->pos.xPos & 0xFFFFFE00 | 0x200; item->pos.zPos = pushable->moveZ = item->pos.zPos & 0xFFFFFE00 | 0x200; TestTriggersAtXYZ(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber, 1, item->flags & 0x3E00); } } else { LaraItem->goalAnimState = LS_STOP; } } break; case LA_PUSHABLE_PUSH_TO_STAND: case LA_PUSHABLE_PULL_TO_STAND: if (LaraItem->frameNumber == g_Level.Anims[LA_PUSHABLE_PUSH_TO_STAND].frameBase || LaraItem->frameNumber == g_Level.Anims[LA_PUSHABLE_PULL_TO_STAND].frameBase) { item->pos.xPos = item->pos.xPos & 0xFFFFFE00 | 0x200; item->pos.zPos = item->pos.zPos & 0xFFFFFE00 | 0x200; MoveStackXZ(itemNumber); FindStack(itemNumber); AddBridgeStack(itemNumber); 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) { RemoveActiveItem(itemNumber); item->status = ITEM_NOT_ACTIVE; if (pushable->hasFloorCeiling) { //AlterFloorHeight(item, -((item->triggerFlags - 64) * 256)); AdjustStopperFlag(item, item->itemFlags[0] + 0x8000, 0); } } break; } } void PushableBlockCollision(short itemNum, ITEM_INFO* l, COLL_INFO* coll) { ITEM_INFO* item = &g_Level.Items[itemNum]; short roomNumber = item->roomNumber; FLOOR_INFO* floor = GetFloor(item->pos.xPos, item->pos.yPos - 256, item->pos.zPos, &roomNumber); PUSHABLE_INFO* pushable = GET_PUSHABLEINFO(item); int blockHeight = GetStackHeight(item); if ((!(TrInput & IN_ACTION) || l->currentAnimState != LS_STOP || l->animNumber != LA_STAND_IDLE || l->gravityStatus || Lara.gunStatus || item->status == ITEM_INVISIBLE || item->triggerFlags < 0) && (!Lara.isMoving || Lara.generalPtr != item)) { if ((l->currentAnimState != LS_PUSHABLE_GRAB || (l->frameNumber != g_Level.Anims[LA_PUSHABLE_GRAB].frameBase + 19) || Lara.cornerX != (int)item)) { if (!pushable->hasFloorCeiling) ObjectCollision(itemNum, l, coll); return; } 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 (!TestBlockPush(item, blockHeight, quadrant) || pushable->disablePush) return; l->goalAnimState = LS_PUSHABLE_PUSH; } else if (TrInput & IN_BACK) { if (!TestBlockPull(item, blockHeight, quadrant) || pushable->disablePull) return; l->goalAnimState = LS_PUSHABLE_PULL; } else { return; } item->status = ITEM_ACTIVE; AddActiveItem(itemNum); Lara.headYrot = 0; Lara.headXrot = 0; Lara.torsoYrot = 0; Lara.torsoXrot = 0; pushable->moveX = item->pos.xPos; pushable->moveZ = item->pos.zPos; if (pushable->hasFloorCeiling) { //AlterFloorHeight(item, ((item->triggerFlags - 64) * 256)); AdjustStopperFlag(item, item->itemFlags[0], 0); } } else { BOUNDING_BOX* bounds = GetBoundsAccurate(item); 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 = item->pos.yRot; item->pos.yRot = (l->pos.yRot + ANGLE(45)) & 0xC000; if (TestLaraPosition(&PushableBlockBounds, item, l)) { unsigned short quadrant = (unsigned short)((item->pos.yRot / 0x4000) + ((rot + 0x2000) / 0x4000)); if (quadrant & 1) PushableBlockPos.z = bounds->X1 - 35; else PushableBlockPos.z = bounds->Z1 - 35; if (pushable->hasFloorCeiling) { // For now don't use auto-align function because it can collide with climb up moves of Lara LaraItem->pos.xRot = item->pos.xRot; LaraItem->pos.yRot = item->pos.yRot; LaraItem->pos.zRot = item->pos.zRot; l->animNumber = LA_PUSHABLE_GRAB; l->frameNumber = g_Level.Anims[l->animNumber].frameBase; l->currentAnimState = LS_PUSHABLE_GRAB; l->goalAnimState = LS_PUSHABLE_GRAB; Lara.isMoving = false; Lara.gunStatus = LG_HANDS_BUSY; Lara.cornerX = (int)item; item->pos.yRot = rot; } else { if (MoveLaraPosition(&PushableBlockPos, item, l)) { l->animNumber = LA_PUSHABLE_GRAB; l->frameNumber = g_Level.Anims[l->animNumber].frameBase; l->currentAnimState = LS_PUSHABLE_GRAB; l->goalAnimState = LS_PUSHABLE_GRAB; Lara.isMoving = false; Lara.gunStatus = LG_HANDS_BUSY; Lara.cornerX = (int)item; item->pos.yRot = rot; } else { Lara.generalPtr = item; item->pos.yRot = rot; } } } else { if (Lara.isMoving && Lara.generalPtr == item) { Lara.isMoving = false; Lara.gunStatus = LG_NO_ARMS; } item->pos.yRot = rot; } } } 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) { short roomNumber = item->roomNumber; FLOOR_INFO* floor = GetFloor(item->pos.xPos, item->pos.yPos, item->pos.zPos, &roomNumber); if (floor->floor == NO_HEIGHT / 256) return 1; if (floor->floor * 256 != item->pos.yPos - blokhite) return 0; return 1; } int TestBlockPush(ITEM_INFO* item, int blockhite, unsigned short quadrant) { int x = item->pos.xPos; int y = item->pos.yPos; int z = item->pos.zPos; switch (quadrant) { case NORTH: z += 1024; break; case EAST: x += 1024; break; case SOUTH: z -= 1024; break; case WEST: x -= 1024; break; } short roomNum = item->roomNumber; FLOOR_INFO* floor = GetFloor(x, y - blockhite, z, &roomNum); ROOM_INFO* r = &g_Level.Rooms[roomNum]; if (XZ_GET_SECTOR(r, x - r->x, z - r->z).stopper) return 0; int floorHeight = GetFloorHeight(floor, x, y - blockhite, z); if (HeightType) return 0; if (GET_PUSHABLEINFO(item)->canFall) { if (floorHeight < y) return 0; } else { if (floorHeight != y) return 0; } int ceiling = y - blockhite + 100; floor = GetFloor(x, ceiling, z, &roomNum); if (GetCeiling(floor, x, ceiling, z) > ceiling) return 0; int oldX = item->pos.xPos; int oldZ = item->pos.zPos; item->pos.xPos = x; item->pos.zPos = z; GetCollidedObjects(item, 256, 1, &CollidedItems[0], 0, 1); item->pos.xPos = oldX; item->pos.zPos = oldZ; int i = 0; while (CollidedItems[i] != NULL) { if (Objects[CollidedItems[i]->objectNumber].floor == NULL) return 0; else { const auto& object = Objects[CollidedItems[i]->objectNumber]; int collidedIndex = CollidedItems[i] - g_Level.Items.data(); // index of CollidedItems[i] int xCol = CollidedItems[i]->pos.xPos; int yCol = CollidedItems[i]->pos.yPos; int zCol = CollidedItems[i]->pos.zPos; // check if floor function returns nullopt if (object.floor(collidedIndex, xCol, yCol, zCol) == std::nullopt) return 0; } i++; } return 1; } int TestBlockPull(ITEM_INFO* item, int blockhite, short quadrant) { int xadd = 0; int zadd = 0; switch (quadrant) { case NORTH: zadd = -1024; break; case EAST: xadd = -1024; break; case SOUTH: zadd = 1024; break; case WEST: xadd = 1024; break; } int x = item->pos.xPos + xadd; int y = item->pos.yPos; int z = item->pos.zPos + zadd; short roomNum = item->roomNumber; FLOOR_INFO* floor = GetFloor(x, y - blockhite, z, &roomNum); ROOM_INFO* r = &g_Level.Rooms[roomNum]; if (XZ_GET_SECTOR(r, x - r->x, z - r->z).stopper) return 0; if (GetFloorHeight(floor, x, y - blockhite, z) != y) return 0; if (HeightType) return 0; int ceiling = y - blockhite + 100; floor = GetFloor(x, ceiling, z, &roomNum); if (GetCeiling(floor, x, ceiling, z) > ceiling) return 0; int oldX = item->pos.xPos; int oldZ = item->pos.zPos; item->pos.xPos = x; item->pos.zPos = z; GetCollidedObjects(item, 256, 1, &CollidedItems[0], 0, 1); item->pos.xPos = oldX; item->pos.zPos = oldZ; int i = 0; while (CollidedItems[i] != NULL) { if (Objects[CollidedItems[i]->objectNumber].floor == NULL) return 0; else { const auto& object = Objects[CollidedItems[i]->objectNumber]; int collidedIndex = CollidedItems[i] - g_Level.Items.data(); int xCol = CollidedItems[i]->pos.xPos; int yCol = CollidedItems[i]->pos.yPos; int zCol = CollidedItems[i]->pos.zPos; if (object.floor(collidedIndex, xCol, yCol, zCol) == std::nullopt) return 0; } 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->pos.xPos + xadd + xAddLara; z = LaraItem->pos.zPos + zadd + zAddLara; roomNum = LaraItem->roomNumber; floor = GetFloor(x, y - LARA_HITE, z, &roomNum); r = &g_Level.Rooms[roomNum]; if (XZ_GET_SECTOR(r, x - r->x, z - r->z).stopper) return 0; if (GetFloorHeight(floor, x, y - LARA_HITE, z) != y) return 0; if (floor->ceiling * 256 > y - LARA_HITE) return 0; oldX = LaraItem->pos.xPos; oldZ = LaraItem->pos.zPos; LaraItem->pos.xPos = x; LaraItem->pos.zPos = z; GetCollidedObjects(LaraItem, 256, 1, &CollidedItems[0], 0, 1); LaraItem->pos.xPos = oldX; LaraItem->pos.zPos = 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 0; else { const auto& object = Objects[CollidedItems[i]->objectNumber]; int collidedIndex = CollidedItems[i] - g_Level.Items.data(); int xCol = CollidedItems[i]->pos.xPos; int yCol = CollidedItems[i]->pos.yPos; int zCol = CollidedItems[i]->pos.zPos; if (object.floor(collidedIndex, xCol, yCol, zCol) == std::nullopt) return 0; } } i++; } return 1; } void MoveStackXZ(short itemNum) { auto item = &g_Level.Items[itemNum]; short newRoomNumber = item->roomNumber; GetFloor(item->pos.xPos, item->pos.yPos, item->pos.zPos, &newRoomNumber); 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; newRoomNumber = stackItem->roomNumber; GetFloor(stackItem->pos.xPos, stackItem->pos.yPos, stackItem->pos.zPos, &newRoomNumber); if (newRoomNumber != stackItem->roomNumber) ItemNewRoom(stackIndex, newRoomNumber); } } void MoveStackY(short itemNum, int y) { auto item = &g_Level.Items[itemNum]; short newRoomNumber = item->roomNumber; GetFloor(item->pos.xPos, item->pos.yPos, item->pos.zPos, &newRoomNumber); 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; short newRoomNumber = item->roomNumber; GetFloor(item->pos.xPos, item->pos.yPos, item->pos.zPos, &newRoomNumber); if (newRoomNumber != item->roomNumber) ItemNewRoom(stackIndex, newRoomNumber); } } void AddBridgeStack(short itemNum) { auto item = &g_Level.Items[itemNum]; if (GET_PUSHABLEINFO(item)->hasFloorCeiling) T5M::Floordata::AddBridge(itemNum); int stackIndex = g_Level.Items[itemNum].itemFlags[1]; while (stackIndex != NO_ITEM) { auto stackItem = &g_Level.Items[stackIndex]; if (GET_PUSHABLEINFO(stackItem)->hasFloorCeiling) T5M::Floordata::AddBridge(stackIndex); stackIndex = g_Level.Items[stackIndex].itemFlags[1]; } } void RemoveBridgeStack(short itemNum) { auto item = &g_Level.Items[itemNum]; if (GET_PUSHABLEINFO(item)->hasFloorCeiling) T5M::Floordata::RemoveBridge(itemNum); int stackIndex = g_Level.Items[itemNum].itemFlags[1]; while (stackIndex != NO_ITEM) { auto stackItem = &g_Level.Items[stackIndex]; if (GET_PUSHABLEINFO(stackItem)->hasFloorCeiling) T5M::Floordata::RemoveBridge(stackIndex); stackIndex = g_Level.Items[stackIndex].itemFlags[1]; } } void RemoveFromStack(short 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(short 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 = GET_PUSHABLEINFO(item)->height; auto stackItem = item; while (stackItem->itemFlags[1] != NO_ITEM) { stackItem = &g_Level.Items[stackItem->itemFlags[1]]; height += GET_PUSHABLEINFO(stackItem)->height; } return height; } bool CheckStackLimit(ITEM_INFO* item) { int limit = GET_PUSHABLEINFO(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 false; } return true; } std::optional PushableBlockFloor(short itemNumber, int x, int y, int z) { const auto& item = g_Level.Items[itemNumber]; const auto& pushable = *(PUSHABLE_INFO*)item.data; if (item.status != ITEM_INVISIBLE && pushable.hasFloorCeiling) { const auto height = item.pos.yPos - (item.triggerFlags & 0x1F) * CLICK(1); return std::optional{height}; } return std::nullopt; } std::optional PushableBlockCeiling(short itemNumber, int x, int y, int z) { const auto& item = g_Level.Items[itemNumber]; const auto& pushable = *(PUSHABLE_INFO*)item.data; if (item.status != ITEM_INVISIBLE && pushable.hasFloorCeiling) return std::optional{item.pos.yPos}; return std::nullopt; } int PushableBlockFloorBorder(short itemNumber) { const auto& item = g_Level.Items[itemNumber]; const auto height = item.pos.yPos - (item.triggerFlags & 0x1F) * CLICK(1); return height; } int PushableBlockCeilingBorder(short itemNumber) { const auto& item = g_Level.Items[itemNumber]; return item.pos.yPos; }