object: move bridge, drawbridge and trapdoor to TRX

This moves the three bridges, drawbridge and trapdoor implementations
to TRX, and makes the config option to fix collision issues available
in TR2.

Resolves #2752.
Resolves #2294.
This commit is contained in:
lahm86 2025-04-13 11:29:15 +01:00
parent 5d096fe311
commit ecab0d92e6
25 changed files with 49 additions and 415 deletions

View file

@ -20,6 +20,8 @@
- fixed the final two levels not allowing for secrets to be counted in the statistics (#1582)
- fixed Lara's holsters being empty if a game flow level removes all weapons but also re-adds the pistols (#2677)
- fixed the console opening when remapping its key (#2641)
- fixed collision issues with drawbridges, trapdoors, and bridges when stacked over each other, over slopes, and near the ground (#2752)
- fixed the drawbridge producing dynamic light when open (#2294)
- fixed sprites rendering black if no shade value is assigned in the level (#2701, regression from 0.8)
- fixed a crash if an image was missing
- fixed a crash on level load if an animation has no frames (#2746, regression from 0.8)

View file

@ -219,6 +219,7 @@ as Notepad.
- fixed the game crashing if a cinematic is triggered but the level contains no cinematic frames
- fixed smashed windows blocking enemy pathing after loading a save
- fixed Lara getting stuck in a T-pose after jumping/falling and then dying before reaching fast fall speed
- fixed collision issues with drawbridges, trapdoors, and bridges when stacked over each other, over slopes, and near the ground
- fixed several issues with pushblocks:
- fixed an invisible wall above stacked pushblocks if near a ceiling portal
- fixed floor height issues with pushblocks poised to fall in various scenarios
@ -289,6 +290,7 @@ as Notepad.
- fixed bubbles spawning from flares if Lara is in shallow water
- fixed the inventory up arrow at times overlapping the health bar
- fixed blood spawning on Lara from gunshots using incorrect positioning data
- fixed the drawbridge producing dynamic light when open
- improved FMV mode behavior - stopped switching screen resolutions
- improved vertex movement when looking through water portals
- improved support for non-4:3 aspect ratios

View file

@ -14,12 +14,8 @@ static bool M_IsOnWalkable(
const SECTOR *const sector, const int32_t x, const int32_t y,
const int32_t z, const int32_t room_height)
{
#if TR_VERSION == 1
return g_Config.gameplay.fix_bridge_collision
&& Room_IsOnWalkable(sector, x, y, z, room_height);
#elif TR_VERSION >= 2
return false;
#endif
}
int32_t Collide_GetSpheres(
@ -291,11 +287,7 @@ void Collide_GetCollisionInfo(
coll->side_front.ceiling = ceiling;
coll->side_front.type = Room_GetHeightType();
#if TR_VERSION == 1
is_on_walkable = M_IsOnWalkable(sector, x, y_top, z, room_height);
#elif TR_VERSION >= 2
is_on_walkable = false;
#endif
if (!is_on_walkable) {
if (coll->slopes_are_walls && coll->side_front.type == HT_BIG_SLOPE
&& coll->side_front.floor < 0) {

View file

@ -1,18 +1,16 @@
#include "game/objects/general/bridge_common.h"
#include "game/items.h"
#include "game/room.h"
#include "global/const.h"
#include "config.h"
#include "game/rooms.h"
#include "utils.h"
#include <libtrx/config.h>
#include <libtrx/utils.h>
bool Bridge_IsSameSector(int32_t x, int32_t z, const ITEM *item)
bool Bridge_IsSameSector(
const int32_t x, const int32_t z, const ITEM *const item)
{
int32_t sector_x = x / WALL_L;
int32_t sector_z = z / WALL_L;
int32_t item_sector_x = item->pos.x / WALL_L;
int32_t item_sector_z = item->pos.z / WALL_L;
const int32_t sector_x = x / WALL_L;
const int32_t sector_z = z / WALL_L;
const int32_t item_sector_x = item->pos.x / WALL_L;
const int32_t item_sector_z = item->pos.z / WALL_L;
return sector_x == item_sector_x && sector_z == item_sector_z;
}
@ -74,9 +72,9 @@ void Bridge_FixEmbeddedPosition(int16_t item_num)
// and moves them up.
ITEM *const item = Item_Get(item_num);
int32_t x = item->pos.x;
int32_t y = item->pos.y;
int32_t z = item->pos.z;
const int32_t x = item->pos.x;
const int32_t y = item->pos.y;
const int32_t z = item->pos.z;
int16_t room_num = item->room_num;
const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item);

View file

@ -1,7 +1,8 @@
#include "config.h"
#include "game/const.h"
#include "game/objects.h"
#include "game/objects/general/bridge_common.h"
#include <libtrx/config.h>
static int16_t M_GetFloorHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static int16_t M_GetCeilingHeight(

View file

@ -1,7 +1,8 @@
#include "config.h"
#include "game/const.h"
#include "game/objects.h"
#include "game/objects/general/bridge_common.h"
#include <libtrx/config.h>
static int16_t M_GetFloorHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static int16_t M_GetCeilingHeight(

View file

@ -1,7 +1,8 @@
#include "config.h"
#include "game/const.h"
#include "game/objects.h"
#include "game/objects/general/bridge_common.h"
#include <libtrx/config.h>
static int16_t M_GetFloorHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static int16_t M_GetCeilingHeight(

View file

@ -1,8 +1,8 @@
#include "config.h"
#include "game/items.h"
#include "game/objects.h"
#include "game/objects/general/door.h"
#include "game/room.h"
#include <libtrx/config.h>
#include "game/rooms.h"
typedef enum {
DRAWBRIDGE_STATE_CLOSED = DOOR_STATE_CLOSED,

View file

@ -1,4 +1,5 @@
#include "game/items.h"
#include "game/const.h"
#include "game/objects.h"
typedef enum {
TRAPDOOR_STATE_CLOSED,

View file

@ -1,6 +1,6 @@
#pragma once
#include "global/types.h"
#include "../../items.h"
bool Bridge_IsSameSector(int32_t x, int32_t z, const ITEM *item);
int32_t Bridge_GetOffset(const ITEM *item, int32_t x, int32_t y, int32_t z);

View file

@ -0,0 +1,10 @@
#pragma once
#include "../../collision.h"
typedef enum {
DOOR_STATE_CLOSED = 0,
DOOR_STATE_OPEN = 1,
} DOOR_STATE;
extern void Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);

View file

@ -169,6 +169,12 @@ sources = [
'game/matrix.c',
'game/music.c',
'game/objects/common.c',
'game/objects/general/bridge_common.c',
'game/objects/general/bridge_flat.c',
'game/objects/general/bridge_tilt1.c',
'game/objects/general/bridge_tilt2.c',
'game/objects/general/drawbridge.c',
'game/objects/general/trapdoor.c',
'game/objects/names.c',
'game/objects/creatures/bear.c',
'game/objects/creatures/wolf.c',

View file

@ -1,5 +1,3 @@
#include "game/objects/general/door.h"
#include "game/box.h"
#include "game/items.h"
#include "game/lara/common.h"
@ -9,6 +7,7 @@
#include <libtrx/game/collision.h>
#include <libtrx/game/game_buf.h>
#include <libtrx/game/objects/general/door.h>
#include <libtrx/utils.h>
typedef struct {

View file

@ -1,10 +0,0 @@
#pragma once
#include "global/types.h"
typedef enum {
DOOR_STATE_CLOSED,
DOOR_STATE_OPEN,
} DOOR_STATE;
void Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);

View file

@ -1,113 +0,0 @@
#include "game/items.h"
#include "global/const.h"
#include <libtrx/utils.h>
typedef enum {
TRAPDOOR_STATE_CLOSED,
TRAPDOOR_STATE_OPEN,
} TRAPDOOR_STATE;
static bool M_IsItemOnTop(const ITEM *item, int32_t x, int32_t z);
static void M_Setup(OBJECT *obj);
static void M_Control(int16_t item_num);
static int16_t M_GetFloorHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static int16_t M_GetCeilingHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static bool M_IsItemOnTop(const ITEM *item, int32_t x, int32_t z)
{
const BOUNDS_16 *const orig_bounds = &Item_GetBestFrame(item)->bounds;
if (orig_bounds == nullptr) {
return false;
}
BOUNDS_16 fixed_bounds = {};
// Bounds need to change in order to account for 2 sector trapdoors
// and the trapdoor angle.
if (item->rot.y == 0) {
fixed_bounds.min.x = orig_bounds->min.x;
fixed_bounds.max.x = orig_bounds->max.x;
fixed_bounds.min.z = orig_bounds->min.z;
fixed_bounds.max.z = orig_bounds->max.z;
} else if (item->rot.y == DEG_90) {
fixed_bounds.min.x = orig_bounds->min.z;
fixed_bounds.max.x = orig_bounds->max.z;
fixed_bounds.min.z = -orig_bounds->max.x;
fixed_bounds.max.z = -orig_bounds->min.x;
} else if (item->rot.y == -DEG_180) {
fixed_bounds.min.x = -orig_bounds->max.x;
fixed_bounds.max.x = -orig_bounds->min.x;
fixed_bounds.min.z = -orig_bounds->max.z;
fixed_bounds.max.z = -orig_bounds->min.z;
} else if (item->rot.y == -DEG_90) {
fixed_bounds.min.x = -orig_bounds->max.z;
fixed_bounds.max.x = -orig_bounds->min.z;
fixed_bounds.min.z = orig_bounds->min.x;
fixed_bounds.max.z = orig_bounds->max.x;
}
if (x <= item->pos.x + fixed_bounds.max.x
&& x >= item->pos.x + fixed_bounds.min.x
&& z <= item->pos.z + fixed_bounds.max.z
&& z >= item->pos.z + fixed_bounds.min.z) {
return true;
}
return false;
}
static void M_Setup(OBJECT *const obj)
{
obj->control_func = M_Control;
obj->floor_height_func = M_GetFloorHeight;
obj->ceiling_height_func = M_GetCeilingHeight;
obj->save_anim = 1;
obj->save_flags = 1;
}
static void M_Control(const int16_t item_num)
{
ITEM *const item = Item_Get(item_num);
if (Item_IsTriggerActive(item)) {
if (item->current_anim_state == TRAPDOOR_STATE_CLOSED) {
item->goal_anim_state = TRAPDOOR_STATE_OPEN;
}
} else if (item->current_anim_state == TRAPDOOR_STATE_OPEN) {
item->goal_anim_state = TRAPDOOR_STATE_CLOSED;
}
Item_Animate(item);
}
static int16_t M_GetFloorHeight(
const ITEM *item, const int32_t x, const int32_t y, const int32_t z,
const int16_t height)
{
if (!M_IsItemOnTop(item, x, z)) {
return height;
}
if (item->current_anim_state == TRAPDOOR_STATE_OPEN || y > item->pos.y
|| item->pos.y >= height) {
return height;
}
return item->pos.y;
}
static int16_t M_GetCeilingHeight(
const ITEM *item, const int32_t x, const int32_t y, const int32_t z,
const int16_t height)
{
if (!M_IsItemOnTop(item, x, z)) {
return height;
}
if (item->current_anim_state == TRAPDOOR_STATE_OPEN || y <= item->pos.y
|| item->pos.y <= height) {
return height;
}
return item->pos.y + STEP_L;
}
REGISTER_OBJECT(O_TRAPDOOR_TYPE_1, M_Setup)
REGISTER_OBJECT(O_TRAPDOOR_TYPE_2, M_Setup)

View file

@ -194,15 +194,10 @@ sources = [
'game/objects/effects/splash.c',
'game/objects/effects/twinkle.c',
'game/objects/general/boat.c',
'game/objects/general/bridge_common.c',
'game/objects/general/bridge_flat.c',
'game/objects/general/bridge_tilt1.c',
'game/objects/general/bridge_tilt2.c',
'game/objects/general/cabin.c',
'game/objects/general/camera_target.c',
'game/objects/general/cog.c',
'game/objects/general/door.c',
'game/objects/general/drawbridge.c',
'game/objects/general/earthquake.c',
'game/objects/general/keyhole.c',
'game/objects/general/moving_bar.c',
@ -214,7 +209,6 @@ sources = [
'game/objects/general/scion4.c',
'game/objects/general/scion_holder.c',
'game/objects/general/switch.c',
'game/objects/general/trapdoor.c',
'game/objects/general/waterfall.c',
'game/objects/setup.c',
'game/objects/traps/damocles_sword.c',

View file

@ -1,16 +0,0 @@
#include "game/objects/general/bridge_common.h"
int32_t Bridge_GetOffset(
const ITEM *const item, const int32_t x, const int32_t z)
{
switch (item->rot.y) {
case 0:
return (WALL_L - x) & (WALL_L - 1);
case -DEG_180:
return x & (WALL_L - 1);
case DEG_90:
return z & (WALL_L - 1);
default:
return (WALL_L - z) & (WALL_L - 1);
}
}

View file

@ -1,5 +0,0 @@
#pragma once
#include "global/types.h"
int32_t Bridge_GetOffset(const ITEM *item, int32_t x, int32_t z);

View file

@ -1,35 +0,0 @@
#include "game/objects/general/bridge_common.h"
static int16_t M_GetFloorHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static int16_t M_GetCeilingHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static void M_Setup(OBJECT *obj);
static int16_t M_GetFloorHeight(
const ITEM *const item, const int32_t x, const int32_t y, const int32_t z,
const int16_t height)
{
if (y > item->pos.y) {
return height;
}
return item->pos.y;
}
static int16_t M_GetCeilingHeight(
const ITEM *const item, const int32_t x, const int32_t y, const int32_t z,
const int16_t height)
{
if (y <= item->pos.y) {
return height;
}
return item->pos.y + STEP_L;
}
static void M_Setup(OBJECT *const obj)
{
obj->floor_height_func = M_GetFloorHeight;
obj->ceiling_height_func = M_GetCeilingHeight;
}
REGISTER_OBJECT(O_BRIDGE_FLAT, M_Setup)

View file

@ -1,39 +0,0 @@
#include "game/objects/general/bridge_common.h"
static int16_t M_GetFloorHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static int16_t M_GetCeilingHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static void M_Setup(OBJECT *obj);
static int16_t M_GetFloorHeight(
const ITEM *const item, const int32_t x, const int32_t y, const int32_t z,
const int16_t height)
{
const int32_t offset_height =
item->pos.y + (Bridge_GetOffset(item, x, z) / 4);
if (y > offset_height) {
return height;
}
return offset_height;
}
static int16_t M_GetCeilingHeight(
const ITEM *const item, const int32_t x, const int32_t y, const int32_t z,
const int16_t height)
{
const int32_t offset_height =
item->pos.y + (Bridge_GetOffset(item, x, z) / 4);
if (y <= offset_height) {
return height;
}
return offset_height + STEP_L;
}
static void M_Setup(OBJECT *const obj)
{
obj->floor_height_func = M_GetFloorHeight;
obj->ceiling_height_func = M_GetCeilingHeight;
}
REGISTER_OBJECT(O_BRIDGE_TILT_1, M_Setup)

View file

@ -1,39 +0,0 @@
#include "game/objects/general/bridge_common.h"
static int16_t M_GetFloorHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static int16_t M_GetCeilingHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static void M_Setup(OBJECT *obj);
static int16_t M_GetFloorHeight(
const ITEM *const item, const int32_t x, const int32_t y, const int32_t z,
const int16_t height)
{
const int32_t offset_height =
item->pos.y + (Bridge_GetOffset(item, x, z) / 2);
if (y > offset_height) {
return height;
}
return offset_height;
}
static int16_t M_GetCeilingHeight(
const ITEM *const item, const int32_t x, const int32_t y, const int32_t z,
const int16_t height)
{
const int32_t offset_height =
item->pos.y + (Bridge_GetOffset(item, x, z) / 2);
if (y <= offset_height) {
return height;
}
return offset_height + STEP_L;
}
static void M_Setup(OBJECT *const obj)
{
obj->floor_height_func = M_GetFloorHeight;
obj->ceiling_height_func = M_GetCeilingHeight;
}
REGISTER_OBJECT(O_BRIDGE_TILT_2, M_Setup)

View file

@ -1,5 +1,3 @@
#include "game/objects/general/door.h"
#include "game/box.h"
#include "game/items.h"
#include "game/objects/common.h"
@ -9,6 +7,7 @@
#include <libtrx/game/collision.h>
#include <libtrx/game/game_buf.h>
#include <libtrx/game/lara/common.h>
#include <libtrx/game/objects/general/door.h>
typedef struct {
DOORPOS_DATA d1;

View file

@ -1,10 +0,0 @@
#pragma once
#include "global/types.h"
typedef enum {
DOOR_STATE_CLOSED = 0,
DOOR_STATE_OPEN = 1,
} DOOR_STATE;
void Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);

View file

@ -1,99 +0,0 @@
#include "game/objects/general/door.h"
#include "game/objects/general/general.h"
typedef enum {
DRAWBRIDGE_STATE_CLOSED = DOOR_STATE_CLOSED,
DRAWBRIDGE_STATE_OPEN = DOOR_STATE_OPEN,
} DRAWBRIDGE_STATE;
static int16_t M_GetFloorHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static int16_t M_GetCeilingHeight(
const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height);
static bool M_IsItemOnTop(const ITEM *item, int32_t z, int32_t x);
static void M_Setup(OBJECT *obj);
static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);
static int16_t M_GetFloorHeight(
const ITEM *const item, const int32_t x, const int32_t y, const int32_t z,
const int16_t height)
{
if (item->current_anim_state != DRAWBRIDGE_STATE_OPEN) {
return height;
} else if (!M_IsItemOnTop(item, z, x)) {
return height;
} else if (item->pos.y < y) {
return height;
}
return item->pos.y;
}
static int16_t M_GetCeilingHeight(
const ITEM *const item, const int32_t x, const int32_t y, const int32_t z,
const int16_t height)
{
if (item->current_anim_state != DRAWBRIDGE_STATE_OPEN) {
return height;
} else if (!M_IsItemOnTop(item, z, x)) {
return height;
} else if (item->pos.y >= y) {
return height;
}
return item->pos.y + STEP_L;
}
static bool M_IsItemOnTop(
const ITEM *const item, const int32_t z, const int32_t x)
{
// drawbridge sector
const XZ_32 obj = {
.x = item->pos.x >> WALL_SHIFT,
.z = item->pos.z >> WALL_SHIFT,
};
// test sector
const XZ_32 test = {
.x = x >> WALL_SHIFT,
.z = z >> WALL_SHIFT,
};
switch (item->rot.y) {
case 0:
return test.x == obj.x && (test.z == obj.z - 1 || test.z == obj.z - 2);
case -DEG_180:
return test.x == obj.x && (test.z == obj.z + 1 || test.z == obj.z + 2);
case -DEG_90:
return test.z == obj.z && (test.x == obj.x + 1 || test.x == obj.x + 2);
case DEG_90:
return test.z == obj.z && (test.x == obj.x - 1 || test.x == obj.x - 2);
}
return false;
}
static void M_Setup(OBJECT *const obj)
{
if (!obj->loaded) {
return;
}
obj->control_func = General_Control;
obj->collision_func = M_Collision;
obj->floor_height_func = M_GetFloorHeight;
obj->ceiling_height_func = M_GetCeilingHeight;
obj->save_flags = 1;
obj->save_anim = 1;
}
static void M_Collision(
const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll)
{
const ITEM *const item = Item_Get(item_num);
if (item->current_anim_state == DRAWBRIDGE_STATE_CLOSED) {
Door_Collision(item_num, lara_item, coll);
}
}
REGISTER_OBJECT(O_DRAWBRIDGE, M_Setup)

View file

@ -189,10 +189,6 @@ sources = [
'game/objects/general/bell.c',
'game/objects/general/big_bowl.c',
'game/objects/general/bird_tweeter.c',
'game/objects/general/bridge_common.c',
'game/objects/general/bridge_flat.c',
'game/objects/general/bridge_tilt_1.c',
'game/objects/general/bridge_tilt_2.c',
'game/objects/general/camera_target.c',
'game/objects/general/clock_chimes.c',
'game/objects/general/copter.c',
@ -200,7 +196,6 @@ sources = [
'game/objects/general/detonator.c',
'game/objects/general/ding_dong.c',
'game/objects/general/door.c',
'game/objects/general/drawbridge.c',
'game/objects/general/earthquake.c',
'game/objects/general/final_cutscene.c',
'game/objects/general/final_level_counter.c',
@ -218,7 +213,6 @@ sources = [
'game/objects/general/puzzle_hole.c',
'game/objects/general/sphere_of_doom.c',
'game/objects/general/switch.c',
'game/objects/general/trapdoor.c',
'game/objects/general/waterfall.c',
'game/objects/general/window.c',
'game/objects/general/zipline.c',