From e32a4c270f26946b70505a3625a7f46bb91e89d5 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 26 Apr 2025 10:05:27 +0200 Subject: [PATCH 01/13] docs: fix formatting and mistyped option name --- docs/MIGRATING.md | 5 +++-- docs/tr1/CHANGELOG.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/MIGRATING.md b/docs/MIGRATING.md index aa61e581f..1c9f2deb8 100644 --- a/docs/MIGRATING.md +++ b/docs/MIGRATING.md @@ -6,10 +6,11 @@ 1. **Update fog configuration** If you wish to force your fog settings on player: - - Rename `draw_distance_min` to `fog_start` + - Rename `draw_distance_fade` to `fog_start` - Rename `draw_distance_max` to `fog_end` + If you wish to give the player agency to change the fog: - - Remove `draw_distance_min` and `draw_distance_max` + - Remove `draw_distance_fade` and `draw_distance_max` ### Version 4.7 to 4.8 diff --git a/docs/tr1/CHANGELOG.md b/docs/tr1/CHANGELOG.md index 818130279..4121e32f7 100644 --- a/docs/tr1/CHANGELOG.md +++ b/docs/tr1/CHANGELOG.md @@ -5,7 +5,7 @@ - added support for antitriggers, like TR2+ (#2580) - added support for aspect ratio-specific images (#1840) - added an option to wraparound when scrolling UI dialogs, such as save/load (#2834) -- changed the `draw_distance_min` and `draw_distance_max` to `fog_start` and `fog_end` +- changed the `draw_distance_fade` and `draw_distance_max` to `fog_start` and `fog_end` - changed `Select Detail` dialog title to `Graphic Options` - changed the number of static mesh slots from 50 to 256 (#2734) - changed the "enable EIDOS logo" option to disable the Core Design and Bink Video Codec FMVs as well; renamed to "enable legal" (#2741) From 4035fe64113613b7e3afe9fd843b7d044559f204 Mon Sep 17 00:00:00 2001 From: lahm86 <33758420+lahm86@users.noreply.github.com> Date: Sat, 26 Apr 2025 09:57:06 +0100 Subject: [PATCH 02/13] items: replace items by index rather than room Carried items use NO_ROOM so were not included when replacing guns with ammo. This ensures everything is checked when replacing IDs. Resolves #2850. Resolves #2856. --- docs/tr2/CHANGELOG.md | 2 ++ docs/tr2/README.md | 1 + src/libtrx/game/items.c | 14 +++++--------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index 1577ff805..fbb4edb46 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -1,4 +1,6 @@ ## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...develop) - ××××-××-×× +- fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level (#2856) +- fixed guns carried by enemies not being converted to ammo if Lara starts the level with the gun and the game has later been reloaded (#2850, regression from 1.0) ## [1.0.1](https://github.com/LostArtefacts/TRX/compare/tr2-1.0...tr2-1.0.1) - 2025-04-24 - added an option to wraparound when scrolling UI dialogs, such as save/load (#2834) diff --git a/docs/tr2/README.md b/docs/tr2/README.md index 9faec4fd4..d0fe8a2f2 100644 --- a/docs/tr2/README.md +++ b/docs/tr2/README.md @@ -254,6 +254,7 @@ However, you can easily download them manually from these urls: - fixed Floating Islands mystic plaque inventory rotation - fixed pushblocks being rotated when Lara grabs them, most noticeable if asymmetric textures have been used - fixed being able to use hotkeys in the end-level statistics screen +- fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level - fixed destroyed gondolas appearing embedded in the ground after loading a save - improved the animation of Lara's braid diff --git a/src/libtrx/game/items.c b/src/libtrx/game/items.c index a2698b37b..f92b67299 100644 --- a/src/libtrx/game/items.c +++ b/src/libtrx/game/items.c @@ -219,15 +219,11 @@ int32_t Item_GlobalReplace( { int32_t changed = 0; - for (int32_t i = 0; i < Room_GetCount(); i++) { - int16_t item_num = Room_Get(i)->item_num; - while (item_num != NO_ITEM) { - ITEM *const item = &m_Items[item_num]; - if (item->object_id == src_obj_id) { - item->object_id = dst_obj_id; - changed++; - } - item_num = item->next_item; + for (int32_t item_num = 0; item_num < m_MaxUsedItemCount; item_num++) { + ITEM *const item = &m_Items[item_num]; + if (item->object_id == src_obj_id) { + item->object_id = dst_obj_id; + changed++; } } From 2f2f0c6842ff800b9685b1d732fb1393b3dcacfa Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 26 Apr 2025 11:55:23 +0200 Subject: [PATCH 03/13] tr2/viewport: fix screenshots at wrong resolution Resolves #2845. --- docs/tr2/CHANGELOG.md | 1 + src/tr2/game/viewport.c | 11 +++++++---- src/tr2/game/viewport.h | 5 ++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index fbb4edb46..9362fd2ce 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -1,6 +1,7 @@ ## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...develop) - ××××-××-×× - fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level (#2856) - fixed guns carried by enemies not being converted to ammo if Lara starts the level with the gun and the game has later been reloaded (#2850, regression from 1.0) +- fixed 1920x1080 screenshots in 16:9 aspect mode being saved as 1919x1080 (#2845, regression from 0.8) ## [1.0.1](https://github.com/LostArtefacts/TRX/compare/tr2-1.0...tr2-1.0.1) - 2025-04-24 - added an option to wraparound when scrolling UI dialogs, such as save/load (#2834) diff --git a/src/tr2/game/viewport.c b/src/tr2/game/viewport.c index 245e1c11c..424039f73 100644 --- a/src/tr2/game/viewport.c +++ b/src/tr2/game/viewport.c @@ -125,20 +125,23 @@ void Viewport_Reset(void) VIEWPORT *const vp = &m_Viewport; switch (g_Config.rendering.aspect_mode) { case AM_4_3: - vp->render_ar = 4.0 / 3.0; + vp->render_ar.w = 4; + vp->render_ar.h = 3; break; case AM_16_9: - vp->render_ar = 16.0 / 9.0; + vp->render_ar.w = 16; + vp->render_ar.h = 9; break; case AM_ANY: - vp->render_ar = size.w / (double)size.h; + vp->render_ar.w = size.w; + vp->render_ar.h = size.h; break; } vp->width = size.w / g_Config.rendering.scaler; vp->height = size.h / g_Config.rendering.scaler; if (g_Config.rendering.aspect_mode != AM_ANY) { - vp->width = vp->height * vp->render_ar; + vp->width = vp->height * vp->render_ar.w / vp->render_ar.h; } vp->near_z = Output_GetNearZ() >> W2V_SHIFT; diff --git a/src/tr2/game/viewport.h b/src/tr2/game/viewport.h index 9af7d0105..8ec3d7485 100644 --- a/src/tr2/game/viewport.h +++ b/src/tr2/game/viewport.h @@ -8,7 +8,10 @@ typedef struct { int32_t near_z; int32_t far_z; int16_t view_angle; - double render_ar; + struct { + int32_t w; + int32_t h; + } render_ar; // TODO: remove most of these variables if possible struct { From d6fc167749d18f2cce17904686e3d4561e6c909c Mon Sep 17 00:00:00 2001 From: lahm86 <33758420+lahm86@users.noreply.github.com> Date: Sat, 26 Apr 2025 10:20:06 +0100 Subject: [PATCH 04/13] tr2/objects/door: prevent Lara voiding in closed doors This uses the same approach as TR1 to avoid Lara voiding in closing/ closed doors that are not placed on portals. Resolves #2848. --- docs/tr2/CHANGELOG.md | 1 + docs/tr2/README.md | 1 + src/tr2/game/objects/general/door.c | 59 ++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index 9362fd2ce..ad9328daf 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -1,4 +1,5 @@ ## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...develop) - ××××-××-×× +- fixed Lara voiding if she stops on a tile with a closing door, and the door isn't on a portal (#2848) - fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level (#2856) - fixed guns carried by enemies not being converted to ammo if Lara starts the level with the gun and the game has later been reloaded (#2850, regression from 1.0) - fixed 1920x1080 screenshots in 16:9 aspect mode being saved as 1919x1080 (#2845, regression from 0.8) diff --git a/docs/tr2/README.md b/docs/tr2/README.md index d0fe8a2f2..54d53df6a 100644 --- a/docs/tr2/README.md +++ b/docs/tr2/README.md @@ -256,6 +256,7 @@ However, you can easily download them manually from these urls: - fixed being able to use hotkeys in the end-level statistics screen - fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level - fixed destroyed gondolas appearing embedded in the ground after loading a save +- fixed Lara voiding if she stops on a tile with a closing door, and the door isn't on a portal - improved the animation of Lara's braid #### Cheats diff --git a/src/tr2/game/objects/general/door.c b/src/tr2/game/objects/general/door.c index 819c8fb5f..e97ec0cd5 100644 --- a/src/tr2/game/objects/general/door.c +++ b/src/tr2/game/objects/general/door.c @@ -21,6 +21,8 @@ static SECTOR *M_GetRoomRelSector( static void M_InitialisePortal( const ROOM *room, const ITEM *item, int32_t sector_dx, int32_t sector_dz, DOORPOS_DATA *door_pos); +static bool M_LaraDoorCollision(const SECTOR *sector); +static void M_Check(DOORPOS_DATA *d); static void M_Shut(DOORPOS_DATA *d); static void M_Open(DOORPOS_DATA *d); static void M_Setup(OBJECT *obj); @@ -38,7 +40,32 @@ static SECTOR *M_GetRoomRelSector( return Room_GetUnitSector(room, sector.x, sector.z); } -static void Door_Shut(DOORPOS_DATA *const d) +static bool M_LaraDoorCollision(const SECTOR *const sector) +{ + // Check if Lara is on the same tile as the invisible block. + const ITEM *const lara = Lara_GetItem(); + if (lara == nullptr) { + return false; + } + + int16_t room_num = lara->room_num; + const SECTOR *const lara_sector = + Room_GetSector(lara->pos.x, lara->pos.y, lara->pos.z, &room_num); + return lara_sector == sector; +} + +static void M_Check(DOORPOS_DATA *const d) +{ + // Forcefully remove the invisible block if Lara happens to occupy the same + // tile. This ensures that Lara doesn't void if a timed door happens to + // close right on her, or the player loads the game while standing on a + // closed door's block tile. + if (M_LaraDoorCollision(d->sector)) { + M_Open(d); + } +} + +static void M_Shut(DOORPOS_DATA *const d) { SECTOR *const sector = d->sector; if (d->sector == nullptr) { @@ -61,7 +88,7 @@ static void Door_Shut(DOORPOS_DATA *const d) } } -static void Door_Open(DOORPOS_DATA *const d) +static void M_Open(DOORPOS_DATA *const d) { if (d->sector == nullptr) { return; @@ -137,8 +164,8 @@ static void M_Initialise(const int16_t item_num) } room_num = door->d1.sector->portal_room.wall; - Door_Shut(&door->d1); - Door_Shut(&door->d1flip); + M_Shut(&door->d1); + M_Shut(&door->d1flip); if (room_num == NO_ROOM) { door->d2.sector = nullptr; @@ -153,8 +180,8 @@ static void M_Initialise(const int16_t item_num) M_InitialisePortal(room, item, 0, 0, &door->d2flip); } - Door_Shut(&door->d2); - Door_Shut(&door->d2flip); + M_Shut(&door->d2); + M_Shut(&door->d2flip); const int16_t prev_room = item->room_num; Item_NewRoom(item_num, room_num); @@ -171,22 +198,26 @@ static void M_Control(const int16_t item_num) if (item->current_anim_state == DOOR_STATE_CLOSED) { item->goal_anim_state = DOOR_STATE_OPEN; } else { - Door_Open(&data->d1); - Door_Open(&data->d2); - Door_Open(&data->d1flip); - Door_Open(&data->d2flip); + M_Open(&data->d1); + M_Open(&data->d2); + M_Open(&data->d1flip); + M_Open(&data->d2flip); } } else { if (item->current_anim_state == DOOR_STATE_OPEN) { item->goal_anim_state = DOOR_STATE_CLOSED; } else { - Door_Shut(&data->d1); - Door_Shut(&data->d2); - Door_Shut(&data->d1flip); - Door_Shut(&data->d2flip); + M_Shut(&data->d1); + M_Shut(&data->d2); + M_Shut(&data->d1flip); + M_Shut(&data->d2flip); } } + M_Check(&data->d1); + M_Check(&data->d2); + M_Check(&data->d1flip); + M_Check(&data->d2flip); Item_Animate(item); } From 5a50de02ed1cbf5de02c4bf59765a90a4dd4ebfa Mon Sep 17 00:00:00 2001 From: lahm86 <33758420+lahm86@users.noreply.github.com> Date: Sat, 26 Apr 2025 10:21:03 +0100 Subject: [PATCH 05/13] door: move door module to TRX This moves the door module fully to TRX as the logic is identical in both games. --- .../game/objects/general/door.c | 60 +++-- .../include/libtrx/game/objects/common.h | 1 + src/libtrx/meson.build | 1 + src/tr1/game/objects/common.h | 1 - src/tr1/game/objects/general/door.c | 253 ------------------ src/tr1/global/types.h | 6 - src/tr1/meson.build | 1 - src/tr2/game/objects/common.h | 1 - src/tr2/global/types_decomp.h | 6 - src/tr2/meson.build | 1 - 10 files changed, 34 insertions(+), 297 deletions(-) rename src/{tr2 => libtrx}/game/objects/general/door.c (86%) delete mode 100644 src/tr1/game/objects/general/door.c diff --git a/src/tr2/game/objects/general/door.c b/src/libtrx/game/objects/general/door.c similarity index 86% rename from src/tr2/game/objects/general/door.c rename to src/libtrx/game/objects/general/door.c index e97ec0cd5..e81c4ca39 100644 --- a/src/tr2/game/objects/general/door.c +++ b/src/libtrx/game/objects/general/door.c @@ -1,13 +1,16 @@ -#include "game/box.h" -#include "game/items.h" -#include "game/objects/common.h" -#include "game/room.h" -#include "global/vars.h" +#include "game/objects/general/door.h" -#include -#include -#include -#include +#include "game/game_buf.h" +#include "game/lara/common.h" +#include "game/objects/common.h" +#include "game/pathing.h" +#include "game/rooms.h" + +typedef struct { + SECTOR *sector; + SECTOR old_sector; + int16_t box_num; +} DOORPOS_DATA; typedef struct { DOORPOS_DATA d1; @@ -82,7 +85,7 @@ static void M_Shut(DOORPOS_DATA *const d) sector->portal_room.pit = NO_ROOM_NEG; sector->portal_room.wall = NO_ROOM; - const int16_t box_num = d->block; + const int16_t box_num = d->box_num; if (box_num != NO_BOX) { Box_GetBox(box_num)->overlap_index |= BOX_BLOCKED; } @@ -96,7 +99,7 @@ static void M_Open(DOORPOS_DATA *const d) *d->sector = d->old_sector; - const int16_t box_num = d->block; + const int16_t box_num = d->box_num; if (box_num != NO_BOX) { Box_GetBox(box_num)->overlap_index &= ~BOX_BLOCKED; } @@ -117,10 +120,11 @@ static void M_InitialisePortal( } int16_t box_num = sector->box; - if (!(Box_GetBox(box_num)->overlap_index & BOX_BLOCKABLE)) { + const BOX_INFO *const box = Box_GetBox(box_num); + if ((box->overlap_index & BOX_BLOCKABLE) == 0) { box_num = NO_BOX; } - door_pos->block = box_num; + door_pos->box_num = box_num; door_pos->old_sector = *door_pos->sector; } @@ -192,32 +196,32 @@ static void M_Initialise(const int16_t item_num) static void M_Control(const int16_t item_num) { ITEM *const item = Item_Get(item_num); - DOOR_DATA *const data = item->data; + DOOR_DATA *const door = item->data; if (Item_IsTriggerActive(item)) { if (item->current_anim_state == DOOR_STATE_CLOSED) { item->goal_anim_state = DOOR_STATE_OPEN; } else { - M_Open(&data->d1); - M_Open(&data->d2); - M_Open(&data->d1flip); - M_Open(&data->d2flip); + M_Open(&door->d1); + M_Open(&door->d2); + M_Open(&door->d1flip); + M_Open(&door->d2flip); } } else { if (item->current_anim_state == DOOR_STATE_OPEN) { item->goal_anim_state = DOOR_STATE_CLOSED; } else { - M_Shut(&data->d1); - M_Shut(&data->d2); - M_Shut(&data->d1flip); - M_Shut(&data->d2flip); + M_Shut(&door->d1); + M_Shut(&door->d2); + M_Shut(&door->d1flip); + M_Shut(&door->d2flip); } } - M_Check(&data->d1); - M_Check(&data->d2); - M_Check(&data->d1flip); - M_Check(&data->d2flip); + M_Check(&door->d1); + M_Check(&door->d2); + M_Check(&door->d1flip); + M_Check(&door->d2flip); Item_Animate(item); } @@ -237,8 +241,8 @@ void Door_Collision( if (coll->enable_baddie_push) { Lara_Push( item, coll, - item->current_anim_state != item->goal_anim_state ? coll->enable_hit - : false, + coll->enable_hit + && item->current_anim_state != item->goal_anim_state, true); } } diff --git a/src/libtrx/include/libtrx/game/objects/common.h b/src/libtrx/include/libtrx/game/objects/common.h index f65578f14..18de887cd 100644 --- a/src/libtrx/include/libtrx/game/objects/common.h +++ b/src/libtrx/include/libtrx/game/objects/common.h @@ -36,6 +36,7 @@ void Object_SwapMesh( ANIM *Object_GetAnim(const OBJECT *obj, int32_t anim_idx); ANIM_BONE *Object_GetBone(const OBJECT *obj, int32_t bone_idx); +extern void Object_DrawUnclippedItem(const ITEM *item); extern void Object_DrawMesh(int32_t mesh_idx, int32_t clip, bool interpolated); void Object_DrawInterpolatedObject( diff --git a/src/libtrx/meson.build b/src/libtrx/meson.build index d3866bca2..78793400b 100644 --- a/src/libtrx/meson.build +++ b/src/libtrx/meson.build @@ -176,6 +176,7 @@ sources = [ 'game/objects/general/bridge_flat.c', 'game/objects/general/bridge_tilt1.c', 'game/objects/general/bridge_tilt2.c', + 'game/objects/general/door.c', 'game/objects/general/drawbridge.c', 'game/objects/general/trapdoor.c', 'game/objects/names.c', diff --git a/src/tr1/game/objects/common.h b/src/tr1/game/objects/common.h index dd39f97e8..5b9da9fdc 100644 --- a/src/tr1/game/objects/common.h +++ b/src/tr1/game/objects/common.h @@ -12,7 +12,6 @@ void Object_DrawDummyItem(const ITEM *item); void Object_DrawSpriteItem(const ITEM *item); void Object_DrawPickupItem(const ITEM *item); void Object_DrawAnimatingItem(const ITEM *item); -void Object_DrawUnclippedItem(const ITEM *item); void Object_SetMeshReflective( GAME_OBJECT_ID obj_id, int32_t mesh_idx, bool enabled); void Object_SetReflective(GAME_OBJECT_ID obj_id, bool enabled); diff --git a/src/tr1/game/objects/general/door.c b/src/tr1/game/objects/general/door.c deleted file mode 100644 index 5cbe0bfcc..000000000 --- a/src/tr1/game/objects/general/door.c +++ /dev/null @@ -1,253 +0,0 @@ -#include "game/box.h" -#include "game/items.h" -#include "game/lara/common.h" -#include "game/objects/common.h" -#include "game/room.h" -#include "global/vars.h" - -#include -#include -#include -#include - -typedef struct { - DOORPOS_DATA d1; - DOORPOS_DATA d1flip; - DOORPOS_DATA d2; - DOORPOS_DATA d2flip; -} DOOR_DATA; - -static SECTOR *M_GetRoomRelSector( - const ROOM *room, const ITEM *item, int32_t sector_dx, int32_t sector_dz); -static void M_InitialisePortal( - const ROOM *room, const ITEM *item, int32_t sector_dx, int32_t sector_dz, - DOORPOS_DATA *door_pos); - -static bool M_LaraDoorCollision(const SECTOR *sector); -static void M_Check(DOORPOS_DATA *d); -static void M_Shut(DOORPOS_DATA *d); -static void M_Open(DOORPOS_DATA *d); -static void M_Setup(OBJECT *obj); -static void M_Initialise(int16_t item_num); -static void M_Control(int16_t item_num); - -static SECTOR *M_GetRoomRelSector( - const ROOM *const room, const ITEM *item, const int32_t sector_dx, - const int32_t sector_dz) -{ - const XZ_32 sector = { - .x = ((item->pos.x - room->pos.x) >> WALL_SHIFT) + sector_dx, - .z = ((item->pos.z - room->pos.z) >> WALL_SHIFT) + sector_dz, - }; - return Room_GetUnitSector(room, sector.x, sector.z); -} - -static void M_InitialisePortal( - const ROOM *const room, const ITEM *const item, const int32_t sector_dx, - const int32_t sector_dz, DOORPOS_DATA *const door_pos) -{ - door_pos->sector = M_GetRoomRelSector(room, item, sector_dx, sector_dz); - - const SECTOR *sector = door_pos->sector; - - const int16_t room_num = sector->portal_room.wall; - if (room_num != NO_ROOM) { - sector = - M_GetRoomRelSector(Room_Get(room_num), item, sector_dx, sector_dz); - } - - int16_t box_num = sector->box; - if (!(Box_GetBox(box_num)->overlap_index & BOX_BLOCKABLE)) { - box_num = NO_BOX; - } - door_pos->block = box_num; - door_pos->old_sector = *door_pos->sector; -} - -static bool M_LaraDoorCollision(const SECTOR *const sector) -{ - // Check if Lara is on the same tile as the invisible block. - if (g_LaraItem == nullptr) { - return false; - } - - int16_t room_num = g_LaraItem->room_num; - const SECTOR *const lara_sector = Room_GetSector( - g_LaraItem->pos.x, g_LaraItem->pos.y, g_LaraItem->pos.z, &room_num); - return lara_sector == sector; -} - -static void M_Check(DOORPOS_DATA *const d) -{ - // Forcefully remove the invisible block if Lara happens to occupy the same - // tile. This ensures that Lara doesn't void if a timed door happens to - // close right on her, or the player loads the game while standing on a - // closed door's block tile. - if (M_LaraDoorCollision(d->sector)) { - M_Open(d); - } -} - -static void M_Shut(DOORPOS_DATA *const d) -{ - // Change the level geometry so that the door tile is impassable. - SECTOR *const sector = d->sector; - if (sector == nullptr) { - return; - } - - sector->box = NO_BOX; - sector->floor.height = NO_HEIGHT; - sector->ceiling.height = NO_HEIGHT; - sector->floor.tilt = 0; - sector->ceiling.tilt = 0; - sector->portal_room.sky = NO_ROOM; - sector->portal_room.pit = NO_ROOM; - sector->portal_room.wall = NO_ROOM; - - const int16_t box_num = d->block; - if (box_num != NO_BOX) { - Box_GetBox(box_num)->overlap_index |= BOX_BLOCKED; - } -} - -static void M_Open(DOORPOS_DATA *const d) -{ - // Restore the level geometry so that the door tile is passable. - SECTOR *const sector = d->sector; - if (!sector) { - return; - } - - *sector = d->old_sector; - - const int16_t box_num = d->block; - if (box_num != NO_BOX) { - Box_GetBox(box_num)->overlap_index &= ~BOX_BLOCKED; - } -} - -static void M_Setup(OBJECT *const obj) -{ - obj->initialise_func = M_Initialise; - obj->control_func = M_Control; - obj->draw_func = Object_DrawUnclippedItem; - obj->collision_func = Door_Collision; - obj->save_anim = 1; - obj->save_flags = 1; -} - -static void M_Initialise(const int16_t item_num) -{ - ITEM *const item = Item_Get(item_num); - DOOR_DATA *const door = GameBuf_Alloc(sizeof(DOOR_DATA), GBUF_ITEM_DATA); - item->data = door; - - int32_t dx = 0; - int32_t dz = 0; - if (item->rot.y == 0) { - dz = -1; - } else if (item->rot.y == -DEG_180) { - dz = 1; - } else if (item->rot.y == DEG_90) { - dx = -1; - } else { - dx = 1; - } - - int16_t room_num = item->room_num; - const ROOM *room = Room_Get(room_num); - M_InitialisePortal(room, item, dx, dz, &door->d1); - - if (room->flipped_room == -1) { - door->d1flip.sector = nullptr; - } else { - room = Room_Get(room->flipped_room); - M_InitialisePortal(room, item, dx, dz, &door->d1flip); - } - - room_num = door->d1.sector->portal_room.wall; - M_Shut(&door->d1); - M_Shut(&door->d1flip); - - if (room_num == NO_ROOM) { - door->d2.sector = nullptr; - door->d2flip.sector = nullptr; - return; - } - - room = Room_Get(room_num); - M_InitialisePortal(room, item, 0, 0, &door->d2); - if (room->flipped_room == -1) { - door->d2flip.sector = nullptr; - } else { - room = Room_Get(room->flipped_room); - M_InitialisePortal(room, item, 0, 0, &door->d2flip); - } - - M_Shut(&door->d2); - M_Shut(&door->d2flip); - - const int16_t prev_room = item->room_num; - Item_NewRoom(item_num, room_num); - item->room_num = prev_room; -} - -static void M_Control(const int16_t item_num) -{ - ITEM *const item = Item_Get(item_num); - DOOR_DATA *door = item->data; - - if (Item_IsTriggerActive(item)) { - if (item->current_anim_state == DOOR_STATE_CLOSED) { - item->goal_anim_state = DOOR_STATE_OPEN; - } else { - M_Open(&door->d1); - M_Open(&door->d2); - M_Open(&door->d1flip); - M_Open(&door->d2flip); - } - } else { - if (item->current_anim_state == DOOR_STATE_OPEN) { - item->goal_anim_state = DOOR_STATE_CLOSED; - } else { - M_Shut(&door->d1); - M_Shut(&door->d2); - M_Shut(&door->d1flip); - M_Shut(&door->d2flip); - } - } - - M_Check(&door->d1); - M_Check(&door->d2); - M_Check(&door->d1flip); - M_Check(&door->d2flip); - Item_Animate(item); -} - -void Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll) -{ - ITEM *const item = Item_Get(item_num); - if (!Lara_TestBoundsCollide(item, coll->radius)) { - return; - } - if (!Collide_TestCollision(item, lara_item)) { - return; - } - if (coll->enable_baddie_push) { - if (item->current_anim_state != item->goal_anim_state) { - Lara_Push(item, coll, coll->enable_hit, true); - } else { - Lara_Push(item, coll, false, true); - } - } -} - -REGISTER_OBJECT(O_DOOR_TYPE_1, M_Setup) -REGISTER_OBJECT(O_DOOR_TYPE_2, M_Setup) -REGISTER_OBJECT(O_DOOR_TYPE_3, M_Setup) -REGISTER_OBJECT(O_DOOR_TYPE_4, M_Setup) -REGISTER_OBJECT(O_DOOR_TYPE_5, M_Setup) -REGISTER_OBJECT(O_DOOR_TYPE_6, M_Setup) -REGISTER_OBJECT(O_DOOR_TYPE_7, M_Setup) -REGISTER_OBJECT(O_DOOR_TYPE_8, M_Setup) diff --git a/src/tr1/global/types.h b/src/tr1/global/types.h index 54dd99625..6b7384d57 100644 --- a/src/tr1/global/types.h +++ b/src/tr1/global/types.h @@ -123,12 +123,6 @@ typedef struct { }; } PHD_VBUF; -typedef struct { - SECTOR *sector; - SECTOR old_sector; - int16_t block; -} DOORPOS_DATA; - typedef struct { PASSPORT_MODE passport_selection; int32_t select_save_slot; diff --git a/src/tr1/meson.build b/src/tr1/meson.build index 277258aec..71f6c3254 100644 --- a/src/tr1/meson.build +++ b/src/tr1/meson.build @@ -197,7 +197,6 @@ sources = [ 'game/objects/general/cabin.c', 'game/objects/general/camera_target.c', 'game/objects/general/cog.c', - 'game/objects/general/door.c', 'game/objects/general/earthquake.c', 'game/objects/general/keyhole.c', 'game/objects/general/moving_bar.c', diff --git a/src/tr2/game/objects/common.h b/src/tr2/game/objects/common.h index 15a9e8cc0..65911e138 100644 --- a/src/tr2/game/objects/common.h +++ b/src/tr2/game/objects/common.h @@ -6,7 +6,6 @@ void Object_DrawDummyItem(const ITEM *item); void Object_DrawAnimatingItem(const ITEM *item); -void Object_DrawUnclippedItem(const ITEM *item); void Object_DrawSpriteItem(const ITEM *item); void Object_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); diff --git a/src/tr2/global/types_decomp.h b/src/tr2/global/types_decomp.h index 8427402b1..3d1825df8 100644 --- a/src/tr2/global/types_decomp.h +++ b/src/tr2/global/types_decomp.h @@ -120,12 +120,6 @@ typedef enum { GFE_REMOVE_AMMO = 22, } GF_EVENTS; -typedef struct { - SECTOR *sector; - SECTOR old_sector; - int16_t block; -} DOORPOS_DATA; - typedef enum { TRAP_SET = 0, TRAP_ACTIVATE = 1, diff --git a/src/tr2/meson.build b/src/tr2/meson.build index ea203d6bf..80c5e9c77 100644 --- a/src/tr2/meson.build +++ b/src/tr2/meson.build @@ -195,7 +195,6 @@ sources = [ 'game/objects/general/cutscene_player.c', 'game/objects/general/detonator.c', 'game/objects/general/ding_dong.c', - 'game/objects/general/door.c', 'game/objects/general/earthquake.c', 'game/objects/general/final_cutscene.c', 'game/objects/general/final_level_counter.c', From 864589bf0a65b1434ea40f54c1e212fc0f337d6f Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 26 Apr 2025 11:23:46 +0200 Subject: [PATCH 06/13] game-strings: use OG JSON as a fallback in expansions Resolves #2847. --- docs/tr1/CHANGELOG.md | 1 + docs/tr2/CHANGELOG.md | 1 + src/libtrx/game/game_string_table/common.c | 52 ++++++++++++++++--- src/libtrx/game/game_string_table/priv.h | 5 +- src/libtrx/game/game_string_table/reader.c | 32 ++++-------- .../include/libtrx/game/game_string_table.h | 7 +-- src/tr1/game/shell.c | 8 ++- src/tr2/game/shell/common.c | 11 +++- 8 files changed, 81 insertions(+), 36 deletions(-) diff --git a/docs/tr1/CHANGELOG.md b/docs/tr1/CHANGELOG.md index 4121e32f7..0c6d393bc 100644 --- a/docs/tr1/CHANGELOG.md +++ b/docs/tr1/CHANGELOG.md @@ -11,6 +11,7 @@ - changed the "enable EIDOS logo" option to disable the Core Design and Bink Video Codec FMVs as well; renamed to "enable legal" (#2741) - changed sprite pickups to respect the water tint if placed underwater (#2673) - changed save to take priority over load when both inputs are held on the same frame, in line with OG (#2833) +- changed The Unfinished Business strings to default to the OG strings file for the main tables (#2847) - fixed the bilinear filter to not readjust the UVs (#2258) - fixed disabling the cutscenes causing the game to exit (#2743, regression from 4.8) - fixed anisotropy filter causing black lines on certain GPUs (#902) diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index ad9328daf..dc2942abc 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -1,4 +1,5 @@ ## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...develop) - ××××-××-×× +- changed The Golden Mask strings to default to the OG strings file for the main tables (#2847) - fixed Lara voiding if she stops on a tile with a closing door, and the door isn't on a portal (#2848) - fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level (#2856) - fixed guns carried by enemies not being converted to ammo if Lara starts the level with the gun and the game has later been reloaded (#2850, regression from 1.0) diff --git a/src/libtrx/game/game_string_table/common.c b/src/libtrx/game/game_string_table/common.c index d3f8327dc..9e40ae432 100644 --- a/src/libtrx/game/game_string_table/common.c +++ b/src/libtrx/game/game_string_table/common.c @@ -1,9 +1,11 @@ #include "debug.h" +#include "filesystem.h" #include "game/game_flow.h" #include "game/game_string.h" #include "game/game_string_table.h" #include "game/game_string_table/priv.h" #include "game/objects/names.h" +#include "game/shell.h" #include "log.h" #include "memory.h" @@ -11,8 +13,6 @@ typedef void (*M_LOAD_STRING_FUNC)(const char *, const char *); -GS_FILE g_GST_File = {}; - static struct { GAME_OBJECT_ID target_object_id; GAME_OBJECT_ID source_object_id; @@ -27,6 +27,8 @@ static struct { { .target_object_id = NO_OBJECT }, }; +static VECTOR *m_GST_Layers = nullptr; + static void M_Apply(const GS_TABLE *table); static void M_ApplyLevelTitles( const GS_FILE *gs_file, GF_LEVEL_TABLE_TYPE level_table_type); @@ -90,17 +92,19 @@ static void M_ApplyLevelTitles( GF_GetLevelTable(level_table_type); const GS_LEVEL_TABLE *const gs_level_table = &gs_file->level_tables[level_table_type]; + if (gs_level_table->count == 0) { + return; + } + ASSERT(gs_level_table->count == level_table->count); for (int32_t i = 0; i < level_table->count; i++) { GF_SetLevelTitle( &level_table->levels[i], gs_level_table->entries[i].title); } } -void GameStringTable_Apply(const GF_LEVEL *const level) +static void M_ApplyLayer( + const GF_LEVEL *const level, const GS_FILE *const gs_file) { - const GS_FILE *const gs_file = &g_GST_File; - - Object_ResetNames(); M_Apply(&gs_file->global); for (int32_t i = 0; i < GFLT_NUMBER_OF; i++) { @@ -126,10 +130,44 @@ void GameStringTable_Apply(const GF_LEVEL *const level) M_Apply(&gs_level_table->entries[level->num].table); } } +} + +void GameStringTable_Apply(const GF_LEVEL *const level) +{ + Object_ResetNames(); + ASSERT(m_GST_Layers != nullptr); + for (int32_t i = 0; i < m_GST_Layers->count; i++) { + const GS_FILE *const gs_file = Vector_Get(m_GST_Layers, i); + M_ApplyLayer(level, gs_file); + } M_DoObjectAliases(); } +void GameStringTable_Init(void) +{ + m_GST_Layers = Vector_Create(sizeof(GS_FILE)); +} + void GameStringTable_Shutdown(void) { - GS_File_Free(&g_GST_File); + if (m_GST_Layers != nullptr) { + for (int32_t i = 0; i < m_GST_Layers->count; i++) { + GS_FILE *const gs_file = Vector_Get(m_GST_Layers, i); + GS_File_Free(gs_file); + } + Vector_Free(m_GST_Layers); + m_GST_Layers = nullptr; + } +} + +void GameStringTable_Load(const char *const path, const bool load_levels) +{ + char *data = nullptr; + if (!File_Load(path, &data, nullptr)) { + Shell_ExitSystemFmt("failed to open strings file (path: %d)", path); + } + GS_FILE *gs_file = GS_File_CreateFromString(data, load_levels); + ASSERT(m_GST_Layers != nullptr); + Vector_Add(m_GST_Layers, gs_file); + Memory_FreePointer(&data); } diff --git a/src/libtrx/game/game_string_table/priv.h b/src/libtrx/game/game_string_table/priv.h index 00d69b7f0..9533c5989 100644 --- a/src/libtrx/game/game_string_table/priv.h +++ b/src/libtrx/game/game_string_table/priv.h @@ -1,6 +1,7 @@ #pragma once #include "game/game_flow/enum.h" +#include "vector.h" #include @@ -35,7 +36,7 @@ typedef struct { GS_LEVEL_TABLE level_tables[GFLT_NUMBER_OF]; } GS_FILE; -extern GS_FILE g_GST_File; - void GS_Table_Free(GS_TABLE *gs_table); + +GS_FILE *GS_File_CreateFromString(const char *data, bool load_levels); void GS_File_Free(GS_FILE *gs_file); diff --git a/src/libtrx/game/game_string_table/reader.c b/src/libtrx/game/game_string_table/reader.c index 4cec681d4..5ded75216 100644 --- a/src/libtrx/game/game_string_table/reader.c +++ b/src/libtrx/game/game_string_table/reader.c @@ -1,4 +1,3 @@ -#include "filesystem.h" #include "game/game_flow.h" #include "game/game_string_table.h" #include "game/game_string_table/priv.h" @@ -130,24 +129,14 @@ static void M_LoadLevelsFromJSON( } } -void GameStringTable_LoadFromFile(const char *const path) +GS_FILE *GS_File_CreateFromString( + const char *const data, const bool load_levels) { - char *data = nullptr; - if (!File_Load(path, &data, nullptr)) { - Shell_ExitSystemFmt("failed to open strings file (path: %d)", path); - } - GameStringTable_LoadFromString(data); - Memory_FreePointer(&data); -} - -void GameStringTable_LoadFromString(const char *const data) -{ - GameStringTable_Shutdown(); - - JSON_VALUE *root = nullptr; + GS_FILE *const gs_file = Memory_Alloc(sizeof(GS_FILE)); JSON_PARSE_RESULT parse_result; - root = JSON_ParseEx( + + JSON_VALUE *root = JSON_ParseEx( data, strlen(data), JSON_PARSE_FLAGS_ALLOW_JSON5, nullptr, nullptr, &parse_result); if (root == nullptr) { @@ -157,15 +146,16 @@ void GameStringTable_LoadFromString(const char *const data) parse_result.error_line_no, parse_result.error_row_no, data); } - GS_FILE *const gs_file = &g_GST_File; JSON_OBJECT *root_obj = JSON_ValueAsObject(root); M_LoadTableFromJSON(root_obj, &gs_file->global); - M_LoadLevelsFromJSON(root_obj, gs_file, "levels", GFLT_MAIN); - M_LoadLevelsFromJSON(root_obj, gs_file, "demos", GFLT_DEMOS); - M_LoadLevelsFromJSON(root_obj, gs_file, "cutscenes", GFLT_CUTSCENES); - + if (load_levels) { + M_LoadLevelsFromJSON(root_obj, gs_file, "levels", GFLT_MAIN); + M_LoadLevelsFromJSON(root_obj, gs_file, "demos", GFLT_DEMOS); + M_LoadLevelsFromJSON(root_obj, gs_file, "cutscenes", GFLT_CUTSCENES); + } if (root != nullptr) { JSON_ValueFree(root); root = nullptr; } + return gs_file; } diff --git a/src/libtrx/include/libtrx/game/game_string_table.h b/src/libtrx/include/libtrx/game/game_string_table.h index b14a99c97..cc33f3fe4 100644 --- a/src/libtrx/include/libtrx/game/game_string_table.h +++ b/src/libtrx/include/libtrx/game/game_string_table.h @@ -2,7 +2,8 @@ #include -void GameStringTable_LoadFromFile(const char *path); -void GameStringTable_LoadFromString(const char *data); -void GameStringTable_Apply(const GF_LEVEL *level); +void GameStringTable_Init(void); void GameStringTable_Shutdown(void); + +void GameStringTable_Load(const char *path, bool load_levels); +void GameStringTable_Apply(const GF_LEVEL *level); diff --git a/src/tr1/game/shell.c b/src/tr1/game/shell.c index f892719ba..b8f190715 100644 --- a/src/tr1/game/shell.c +++ b/src/tr1/game/shell.c @@ -152,6 +152,8 @@ void Shell_Shutdown(void) Console_Shutdown(); GameBuf_Shutdown(); Savegame_Shutdown(); + + GameStringTable_Shutdown(); GF_Shutdown(); Output_Shutdown(); @@ -206,7 +208,11 @@ void Shell_Main(void) GF_Init(); GF_LoadFromFile(m_ModPaths[m_Args.mod].game_flow_path); - GameStringTable_LoadFromFile(m_ModPaths[m_Args.mod].game_strings_path); + GameStringTable_Init(); + if (m_Args.mod != M_MOD_OG) { + GameStringTable_Load(m_ModPaths[M_MOD_OG].game_strings_path, false); + } + GameStringTable_Load(m_ModPaths[m_Args.mod].game_strings_path, true); GameStringTable_Apply(nullptr); Savegame_Init(); diff --git a/src/tr2/game/shell/common.c b/src/tr2/game/shell/common.c index f7d00b73d..d0f32b1ab 100644 --- a/src/tr2/game/shell/common.c +++ b/src/tr2/game/shell/common.c @@ -478,7 +478,12 @@ void Shell_Main(void) GF_Init(); GF_LoadFromFile(m_ModPaths[m_Args.mod].game_flow_path); - GameStringTable_LoadFromFile(m_ModPaths[m_Args.mod].game_strings_path); + + GameStringTable_Init(); + if (m_Args.mod != M_MOD_OG) { + GameStringTable_Load(m_ModPaths[M_MOD_OG].game_strings_path, false); + } + GameStringTable_Load(m_ModPaths[m_Args.mod].game_strings_path, true); GameStringTable_Apply(nullptr); GameBuf_Init(); @@ -582,8 +587,9 @@ void Shell_Main(void) void Shell_Shutdown(void) { + GameStringTable_Shutdown(); GF_Shutdown(); - GameString_Shutdown(); + Console_Shutdown(); Render_Shutdown(); Text_Shutdown(); @@ -591,6 +597,7 @@ void Shell_Shutdown(void) GameBuf_Shutdown(); Config_Shutdown(); EnumMap_Shutdown(); + GameString_Shutdown(); } const char *Shell_GetConfigPath(void) From 24b81007ba5b4336acf72a485340a2d949267297 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 26 Apr 2025 16:00:16 +0200 Subject: [PATCH 07/13] audio: fix wrong benchmark times The decoder benchmark was formatting the wrong variable and produced meaningless results in the logs. --- src/libtrx/engine/audio_sample.c | 105 +++++++++++++++++++------------ 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/src/libtrx/engine/audio_sample.c b/src/libtrx/engine/audio_sample.c index 0d3a4afb8..a4632faa2 100644 --- a/src/libtrx/engine/audio_sample.c +++ b/src/libtrx/engine/audio_sample.c @@ -1,5 +1,6 @@ #include "audio_internal.h" +#include "benchmark.h" #include "debug.h" #include "log.h" #include "memory.h" @@ -21,7 +22,6 @@ #include #include #include -#include typedef struct { char *original_data; @@ -50,8 +50,8 @@ typedef struct { } AUDIO_SAMPLE_SOUND; typedef struct { - const char *data; - const char *ptr; + const uint8_t *data; + const uint8_t *ptr; int32_t size; int32_t remaining; } AUDIO_AV_BUFFER; @@ -64,7 +64,7 @@ static double M_DecibelToMultiplier(double db_gain); static bool M_RecalculateChannelVolumes(int32_t sound_id); static int32_t M_ReadAVBuffer(void *opaque, uint8_t *dst, int32_t dst_size); static int64_t M_SeekAVBuffer(void *opaque, int64_t offset, int32_t whence); -static bool M_Convert(const int32_t sample_id); +static bool M_ConvertSample(const int32_t sample_id); static double M_DecibelToMultiplier(double db_gain) { @@ -135,18 +135,13 @@ static int64_t M_SeekAVBuffer(void *opaque, int64_t offset, int32_t whence) return src->ptr - src->data; } -static bool M_Convert(const int32_t sample_id) +static bool M_ConvertRawData( + const uint8_t *const original_data, const int32_t original_size, + const int32_t dst_sample_rate, const int32_t dst_format, + const int32_t dst_channel_count, uint8_t **const out_sample_data, + size_t *const out_size, size_t *const out_sample_count) { - ASSERT(sample_id >= 0 && sample_id < m_LoadedSamplesCount); - bool result = false; - AUDIO_SAMPLE *const sample = &m_LoadedSamples[sample_id]; - - if (sample->sample_data != nullptr) { - return true; - } - - const clock_t time_start = clock(); size_t working_buffer_size = 0; float *working_buffer = nullptr; @@ -188,10 +183,10 @@ static bool M_Convert(const int32_t sample_id) } AUDIO_AV_BUFFER av_buf = { - .data = sample->original_data, - .ptr = sample->original_data, - .size = sample->original_size, - .remaining = sample->original_size, + .data = original_data, + .ptr = original_data, + .size = original_size, + .remaining = original_size, }; av.avio_context = avio_alloc_context( @@ -248,7 +243,7 @@ static bool M_Convert(const int32_t sample_id) } av.packet = av_packet_alloc(); - if (!av.packet) { + if (av.packet == nullptr) { error_code = AVERROR(ENOMEM); goto cleanup; } @@ -283,7 +278,7 @@ static bool M_Convert(const int32_t sample_id) swr.src.ch_layout = av.codec_ctx->ch_layout; swr.src.format = av.codec_ctx->sample_fmt; swr.dst.sample_rate = AUDIO_WORKING_RATE; - av_channel_layout_default(&swr.dst.ch_layout, 1); + av_channel_layout_default(&swr.dst.ch_layout, dst_channel_count); swr.dst.format = Audio_GetAVAudioFormat(AUDIO_WORKING_FORMAT); swr_alloc_set_opts2( &swr.ctx, &swr.dst.ch_layout, swr.dst.format, @@ -351,25 +346,24 @@ static bool M_Convert(const int32_t sample_id) av_packet_unref(av.packet); } - int32_t sample_format_bytes = av_get_bytes_per_sample(swr.dst.format); - sample->num_samples = working_buffer_size / sample_format_bytes - / swr.dst.ch_layout.nb_channels; - sample->channels = swr.src.ch_layout.nb_channels; - sample->sample_data = working_buffer; + if (out_size != nullptr) { + *out_size = working_buffer_size; + } + if (out_sample_count != nullptr) { + *out_sample_count = (int32_t)(working_buffer_size + / av_get_bytes_per_sample(swr.dst.format)) + / swr.dst.ch_layout.nb_channels; + } + if (out_sample_data != nullptr) { + *out_sample_data = (uint8_t *)working_buffer; + } else { + Memory_FreePointer(&working_buffer); + } result = true; - const clock_t time_end = clock(); - const double time_delta = - (((double)(time_end - time_start)) / CLOCKS_PER_SEC) * 1000.0f; - LOG_DEBUG( - "Sample %d decoded (%.0f ms)", sample_id, sample->original_size, - time_delta); - cleanup: if (error_code != 0) { - LOG_ERROR( - "Error while opening sample ID %d: %s", sample_id, - av_err2str(error_code)); + LOG_ERROR("Error while decoding sample: %s", av_err2str(error_code)); } if (swr.ctx) { @@ -387,11 +381,15 @@ cleanup: av.codec = nullptr; if (!result) { - sample->sample_data = nullptr; - sample->original_data = nullptr; - sample->original_size = 0; - sample->num_samples = 0; - sample->channels = 0; + if (out_size != nullptr) { + *out_size = 0; + } + if (out_sample_count != nullptr) { + *out_sample_count = 0; + } + if (out_sample_data != nullptr) { + *out_sample_data = nullptr; + } Memory_FreePointer(&working_buffer); } @@ -411,6 +409,31 @@ cleanup: return result; } +static bool M_ConvertSample(const int32_t sample_id) +{ + ASSERT(sample_id >= 0 && sample_id < m_LoadedSamplesCount); + AUDIO_SAMPLE *const sample = &m_LoadedSamples[sample_id]; + if (sample->sample_data != nullptr) { + return true; + } + + size_t num_samples; + BENCHMARK benchmark = Benchmark_Start(); + + const bool result = M_ConvertRawData( + (uint8_t *)sample->original_data, sample->original_size, + AUDIO_WORKING_RATE, Audio_GetAVAudioFormat(AUDIO_WORKING_FORMAT), 1, + (uint8_t **)&sample->sample_data, nullptr, &num_samples); + + char buffer[80]; + sprintf(buffer, "sample %d decoded", sample_id); + Benchmark_End(&benchmark, buffer); + + sample->channels = 1; + sample->num_samples = num_samples; + return result; +} + void Audio_Sample_Init(void) { for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_SAMPLES; @@ -553,7 +576,7 @@ int32_t Audio_Sample_Play( continue; } - M_Convert(sample_id); + M_ConvertSample(sample_id); sound->is_used = true; sound->is_playing = true; From 820fc307d20327a923226b7dfdb6aedc3de9f01c Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 26 Apr 2025 16:19:47 +0200 Subject: [PATCH 08/13] audio: split into smaller functions --- docs/tr1/CHANGELOG.md | 1 + docs/tr2/CHANGELOG.md | 1 + src/libtrx/engine/audio_sample.c | 267 ++++++++++++++++--------------- 3 files changed, 140 insertions(+), 129 deletions(-) diff --git a/docs/tr1/CHANGELOG.md b/docs/tr1/CHANGELOG.md index 0c6d393bc..e0a5f7868 100644 --- a/docs/tr1/CHANGELOG.md +++ b/docs/tr1/CHANGELOG.md @@ -36,6 +36,7 @@ - fixed Story So Far not playing the opening FMV, `cafe.rpl` (#2779, regression from 2.10) - fixed Lara at times ending up in incorrect rooms when using the teleport cheat (#2486, regression from 3.0) - fixed the `/pos` console command reporting the base room number when Lara is actually in a flipped room (#2487, regression from 3.0) +- fixed clicks in audio sounds (#2846, regression from 2.0) - improved bubble appearance (#2672) - improved rendering performance - improved pause exit dialog - it can now be canceled with escape diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index dc2942abc..42602984e 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -4,6 +4,7 @@ - fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level (#2856) - fixed guns carried by enemies not being converted to ammo if Lara starts the level with the gun and the game has later been reloaded (#2850, regression from 1.0) - fixed 1920x1080 screenshots in 16:9 aspect mode being saved as 1919x1080 (#2845, regression from 0.8) +- fixed clicks in audio sounds (#2846, regression from 0.2) ## [1.0.1](https://github.com/LostArtefacts/TRX/compare/tr2-1.0...tr2-1.0.1) - 2025-04-24 - added an option to wraparound when scrolling UI dialogs, such as save/load (#2834) diff --git a/src/libtrx/engine/audio_sample.c b/src/libtrx/engine/audio_sample.c index a4632faa2..74de9fcc8 100644 --- a/src/libtrx/engine/audio_sample.c +++ b/src/libtrx/engine/audio_sample.c @@ -23,10 +23,20 @@ #include #include +typedef struct { + struct { + int32_t format; + AVChannelLayout ch_layout; + int32_t sample_rate; + } src, dst; + SwrContext *ctx; + size_t working_buffer_size; + uint8_t *working_buffer; +} M_SWR_CONTEXT; + typedef struct { char *original_data; size_t original_size; - float *sample_data; int32_t channels; int32_t num_samples; @@ -135,6 +145,76 @@ static int64_t M_SeekAVBuffer(void *opaque, int64_t offset, int32_t whence) return src->ptr - src->data; } +static int32_t M_OutputAudioFrame( + M_SWR_CONTEXT *const swr, AVFrame *const frame) +{ + uint8_t *out_buffer = nullptr; + const int32_t out_samples = + swr_get_out_samples(swr->ctx, frame->nb_samples); + av_samples_alloc( + &out_buffer, nullptr, swr->dst.ch_layout.nb_channels, out_samples, + swr->dst.format, 1); + int32_t resampled_size = swr_convert( + swr->ctx, &out_buffer, out_samples, (const uint8_t **)frame->data, + frame->nb_samples); + while (resampled_size > 0) { + int32_t out_buffer_size = av_samples_get_buffer_size( + nullptr, swr->dst.ch_layout.nb_channels, resampled_size, + swr->dst.format, 1); + + if (out_buffer_size > 0) { + swr->working_buffer = Memory_Realloc( + swr->working_buffer, + swr->working_buffer_size + out_buffer_size); + if (out_buffer) { + memcpy( + swr->working_buffer + swr->working_buffer_size, out_buffer, + out_buffer_size); + } + swr->working_buffer_size += out_buffer_size; + } + + resampled_size = + swr_convert(swr->ctx, &out_buffer, out_samples, nullptr, 0); + } + + av_freep(&out_buffer); + return 0; +} + +static int32_t M_DecodePacket( + AVCodecContext *const dec, const AVPacket *const pkt, AVFrame *frame, + M_SWR_CONTEXT *const swr) +{ + // Submit the packet to the decoder + int32_t ret = avcodec_send_packet(dec, pkt); + if (ret < 0) { + LOG_ERROR( + "Error submitting a packet for decoding (%s)\n", av_err2str(ret)); + return ret; + } + + // Get all the available frames from the decoder + while (ret >= 0) { + ret = avcodec_receive_frame(dec, frame); + if (ret < 0) { + // those two return values are special and mean there is no output + // frame available, but there were no errors during decoding + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { + return 0; + } + LOG_ERROR( + "Error receiving a frame for decoding (%s)\n", av_err2str(ret)); + return ret; + } + + ret = M_OutputAudioFrame(swr, frame); + av_frame_unref(frame); + } + + return ret; +} + static bool M_ConvertRawData( const uint8_t *const original_data, const int32_t original_size, const int32_t dst_sample_rate, const int32_t dst_format, @@ -142,8 +222,6 @@ static bool M_ConvertRawData( size_t *const out_size, size_t *const out_sample_count) { bool result = false; - size_t working_buffer_size = 0; - float *working_buffer = nullptr; struct { size_t read_buffer_size; @@ -165,19 +243,11 @@ static bool M_ConvertRawData( .frame = nullptr, }; - struct { - struct { - int32_t format; - AVChannelLayout ch_layout; - int32_t sample_rate; - } src, dst; - SwrContext *ctx; - } swr = {}; - + M_SWR_CONTEXT swr = {}; int32_t error_code; - unsigned char *read_buffer = av_malloc(av.read_buffer_size); - if (!read_buffer) { + uint8_t *const read_buffer = av_malloc(av.read_buffer_size); + if (read_buffer == nullptr) { error_code = AVERROR(ENOMEM); goto cleanup; } @@ -195,8 +265,7 @@ static bool M_ConvertRawData( av.format_ctx = avformat_alloc_context(); av.format_ctx->pb = av.avio_context; - error_code = - avformat_open_input(&av.format_ctx, "dummy_filename", nullptr, nullptr); + error_code = avformat_open_input(&av.format_ctx, "mem:", nullptr, nullptr); if (error_code != 0) { goto cleanup; } @@ -214,19 +283,19 @@ static bool M_ConvertRawData( break; } } - if (!av.stream) { + if (av.stream == nullptr) { error_code = AVERROR_STREAM_NOT_FOUND; goto cleanup; } av.codec = avcodec_find_decoder(av.stream->codecpar->codec_id); - if (!av.codec) { + if (av.codec == nullptr) { error_code = AVERROR_DEMUXER_NOT_FOUND; goto cleanup; } av.codec_ctx = avcodec_alloc_context3(av.codec); - if (!av.codec_ctx) { + if (av.codec_ctx == nullptr) { error_code = AVERROR(ENOMEM); goto cleanup; } @@ -249,115 +318,62 @@ static bool M_ConvertRawData( } av.frame = av_frame_alloc(); - if (!av.frame) { + if (av.frame == nullptr) { error_code = AVERROR(ENOMEM); goto cleanup; } - while (1) { - error_code = av_read_frame(av.format_ctx, av.packet); - if (error_code == AVERROR_EOF) { - av_packet_unref(av.packet); - error_code = 0; + swr.src.sample_rate = av.codec_ctx->sample_rate; + swr.src.ch_layout = av.codec_ctx->ch_layout; + swr.src.format = av.codec_ctx->sample_fmt; + swr.dst.sample_rate = AUDIO_WORKING_RATE; + av_channel_layout_default(&swr.dst.ch_layout, dst_channel_count); + swr.dst.format = Audio_GetAVAudioFormat(AUDIO_WORKING_FORMAT); + swr_alloc_set_opts2( + &swr.ctx, &swr.dst.ch_layout, swr.dst.format, swr.dst.sample_rate, + &swr.src.ch_layout, swr.src.format, swr.src.sample_rate, 0, 0); + if (swr.ctx == nullptr) { + av_packet_unref(av.packet); + error_code = AVERROR(ENOMEM); + goto cleanup; + } + + error_code = swr_init(swr.ctx); + if (error_code != 0) { + av_packet_unref(av.packet); + goto cleanup; + } + + while ((error_code = av_read_frame(av.format_ctx, av.packet)) >= 0) { + M_DecodePacket(av.codec_ctx, av.packet, av.frame, &swr); + av_packet_unref(av.packet); + if (error_code < 0) { break; } + } - if (error_code < 0) { - av_packet_unref(av.packet); - goto cleanup; - } + if (av.codec_ctx != nullptr) { + M_DecodePacket(av.codec_ctx, nullptr, av.frame, &swr); + } - error_code = avcodec_send_packet(av.codec_ctx, av.packet); - if (error_code < 0) { - av_packet_unref(av.packet); - goto cleanup; - } - - if (swr.ctx == nullptr) { - swr.src.sample_rate = av.codec_ctx->sample_rate; - swr.src.ch_layout = av.codec_ctx->ch_layout; - swr.src.format = av.codec_ctx->sample_fmt; - swr.dst.sample_rate = AUDIO_WORKING_RATE; - av_channel_layout_default(&swr.dst.ch_layout, dst_channel_count); - swr.dst.format = Audio_GetAVAudioFormat(AUDIO_WORKING_FORMAT); - swr_alloc_set_opts2( - &swr.ctx, &swr.dst.ch_layout, swr.dst.format, - swr.dst.sample_rate, &swr.src.ch_layout, swr.src.format, - swr.src.sample_rate, 0, 0); - if (swr.ctx == nullptr) { - av_packet_unref(av.packet); - error_code = AVERROR(ENOMEM); - goto cleanup; - } - - error_code = swr_init(swr.ctx); - if (error_code != 0) { - av_packet_unref(av.packet); - goto cleanup; - } - } - - while (1) { - error_code = avcodec_receive_frame(av.codec_ctx, av.frame); - if (error_code == AVERROR(EAGAIN)) { - av_frame_unref(av.frame); - break; - } - - if (error_code < 0) { - av_packet_unref(av.packet); - av_frame_unref(av.frame); - goto cleanup; - } - - uint8_t *out_buffer = nullptr; - const int32_t out_samples = - swr_get_out_samples(swr.ctx, av.frame->nb_samples); - av_samples_alloc( - &out_buffer, nullptr, swr.dst.ch_layout.nb_channels, - out_samples, swr.dst.format, 1); - int32_t resampled_size = swr_convert( - swr.ctx, &out_buffer, out_samples, - (const uint8_t **)av.frame->data, av.frame->nb_samples); - while (resampled_size > 0) { - int32_t out_buffer_size = av_samples_get_buffer_size( - nullptr, swr.dst.ch_layout.nb_channels, resampled_size, - swr.dst.format, 1); - - if (out_buffer_size > 0) { - working_buffer = Memory_Realloc( - working_buffer, working_buffer_size + out_buffer_size); - if (out_buffer) { - memcpy( - (uint8_t *)working_buffer + working_buffer_size, - out_buffer, out_buffer_size); - } - working_buffer_size += out_buffer_size; - } - - resampled_size = - swr_convert(swr.ctx, &out_buffer, out_samples, nullptr, 0); - } - - av_freep(&out_buffer); - av_frame_unref(av.frame); - } - - av_packet_unref(av.packet); + if (error_code == AVERROR_EOF) { + error_code = 0; + } else if (error_code < 0) { + goto cleanup; } if (out_size != nullptr) { - *out_size = working_buffer_size; + *out_size = swr.working_buffer_size; } if (out_sample_count != nullptr) { - *out_sample_count = (int32_t)(working_buffer_size - / av_get_bytes_per_sample(swr.dst.format)) + *out_sample_count = (int32_t)swr.working_buffer_size + / av_get_bytes_per_sample(swr.dst.format) / swr.dst.ch_layout.nb_channels; } if (out_sample_data != nullptr) { - *out_sample_data = (uint8_t *)working_buffer; + *out_sample_data = swr.working_buffer; } else { - Memory_FreePointer(&working_buffer); + Memory_FreePointer(&swr.working_buffer); } result = true; @@ -366,20 +382,6 @@ cleanup: LOG_ERROR("Error while decoding sample: %s", av_err2str(error_code)); } - if (swr.ctx) { - swr_free(&swr.ctx); - } - - if (av.frame) { - av_frame_free(&av.frame); - } - - if (av.packet) { - av_packet_free(&av.packet); - } - - av.codec = nullptr; - if (!result) { if (out_size != nullptr) { *out_size = 0; @@ -390,22 +392,29 @@ cleanup: if (out_sample_data != nullptr) { *out_sample_data = nullptr; } - Memory_FreePointer(&working_buffer); + Memory_FreePointer(&swr.working_buffer); } + if (swr.ctx) { + swr_free(&swr.ctx); + } + if (av.frame) { + av_frame_free(&av.frame); + } + if (av.packet) { + av_packet_free(&av.packet); + } + av.codec = nullptr; if (av.codec_ctx) { avcodec_free_context(&av.codec_ctx); } - if (av.format_ctx) { avformat_close_input(&av.format_ctx); } - if (av.avio_context) { av_freep(&av.avio_context->buffer); avio_context_free(&av.avio_context); } - return result; } From d97edaf1eb8391bdfc049c0bb6c38eb96322b001 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 26 Apr 2025 16:38:55 +0200 Subject: [PATCH 09/13] audio: fix clicks in sample decoding Resolves #2846. --- src/libtrx/engine/audio_sample.c | 51 +++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/libtrx/engine/audio_sample.c b/src/libtrx/engine/audio_sample.c index 74de9fcc8..5dee0147b 100644 --- a/src/libtrx/engine/audio_sample.c +++ b/src/libtrx/engine/audio_sample.c @@ -148,34 +148,49 @@ static int64_t M_SeekAVBuffer(void *opaque, int64_t offset, int32_t whence) static int32_t M_OutputAudioFrame( M_SWR_CONTEXT *const swr, AVFrame *const frame) { + // Determine the maximum number of output samples this call can produce, + // based on the current delay already inside the resampler plus the new + // input. Using av_rescale_rnd() keeps everything in integer domain and + // avoids cumulative rounding errors. + const int64_t delay = swr_get_delay(swr->ctx, swr->src.sample_rate); + const int32_t out_samples = (int32_t)av_rescale_rnd( + delay + frame->nb_samples, swr->dst.sample_rate, swr->src.sample_rate, + AV_ROUND_UP); + if (out_samples <= 0) { + return 0; // nothing to do + } + uint8_t *out_buffer = nullptr; - const int32_t out_samples = - swr_get_out_samples(swr->ctx, frame->nb_samples); - av_samples_alloc( - &out_buffer, nullptr, swr->dst.ch_layout.nb_channels, out_samples, - swr->dst.format, 1); - int32_t resampled_size = swr_convert( + if (av_samples_alloc( + &out_buffer, nullptr, swr->dst.ch_layout.nb_channels, out_samples, + swr->dst.format, 1) + < 0) { + return AVERROR(ENOMEM); + } + + // Convert – we do *not* drain the resampler here. + const int32_t converted = swr_convert( swr->ctx, &out_buffer, out_samples, (const uint8_t **)frame->data, frame->nb_samples); - while (resampled_size > 0) { - int32_t out_buffer_size = av_samples_get_buffer_size( - nullptr, swr->dst.ch_layout.nb_channels, resampled_size, - swr->dst.format, 1); + if (converted < 0) { + av_freep(&out_buffer); + return converted; // propagate error + } + + if (converted > 0) { + const int32_t out_buffer_size = av_samples_get_buffer_size( + nullptr, swr->dst.ch_layout.nb_channels, converted, swr->dst.format, + 1); if (out_buffer_size > 0) { swr->working_buffer = Memory_Realloc( swr->working_buffer, swr->working_buffer_size + out_buffer_size); - if (out_buffer) { - memcpy( - swr->working_buffer + swr->working_buffer_size, out_buffer, - out_buffer_size); - } + memcpy( + swr->working_buffer + swr->working_buffer_size, out_buffer, + out_buffer_size); swr->working_buffer_size += out_buffer_size; } - - resampled_size = - swr_convert(swr->ctx, &out_buffer, out_samples, nullptr, 0); } av_freep(&out_buffer); From 0c5b5dbb7b1e7a40f9f5c90e8392c2fa65e88fa4 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 26 Apr 2025 17:59:35 +0200 Subject: [PATCH 10/13] tr2/inventory: fix button mashing loading game instead of saving Resolves #2863. --- docs/tr2/CHANGELOG.md | 1 + src/tr1/game/inventory_ring/control.c | 3 +-- src/tr2/game/inventory_ring/control.c | 8 ++++---- src/tr2/game/option/option_passport.c | 24 +++++++++++++++++++----- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index 42602984e..511ab2bb4 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -2,6 +2,7 @@ - changed The Golden Mask strings to default to the OG strings file for the main tables (#2847) - fixed Lara voiding if she stops on a tile with a closing door, and the door isn't on a portal (#2848) - fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level (#2856) +- fixed button mashing triggering load instead of save on a specific passport animation frame (#2863, regression from 1.0) - fixed guns carried by enemies not being converted to ammo if Lara starts the level with the gun and the game has later been reloaded (#2850, regression from 1.0) - fixed 1920x1080 screenshots in 16:9 aspect mode being saved as 1919x1080 (#2845, regression from 0.8) - fixed clicks in audio sounds (#2846, regression from 0.2) diff --git a/src/tr1/game/inventory_ring/control.c b/src/tr1/game/inventory_ring/control.c index 760b66a9b..bb5803ccb 100644 --- a/src/tr1/game/inventory_ring/control.c +++ b/src/tr1/game/inventory_ring/control.c @@ -694,7 +694,6 @@ static GF_COMMAND M_Control(INV_RING *const ring) InvRing_MotionSetup(ring, RNG_CLOSING_ITEM, RNG_DESELECT, 0); g_Input = (INPUT_STATE) {}; g_InputDB = (INPUT_STATE) {}; - if (ring->mode == INV_LOAD_MODE || ring->mode == INV_SAVE_MODE || ring->mode == INV_SAVE_CRYSTAL_MODE) { InvRing_MotionSetup( @@ -713,7 +712,7 @@ static GF_COMMAND M_Control(INV_RING *const ring) } if (ring->mode == INV_TITLE_MODE - && ((inv_item->object_id == O_DETAIL_OPTION) + && (inv_item->object_id == O_DETAIL_OPTION || inv_item->object_id == O_SOUND_OPTION || inv_item->object_id == O_CONTROL_OPTION || inv_item->object_id == O_GAMMA_OPTION)) { diff --git a/src/tr2/game/inventory_ring/control.c b/src/tr2/game/inventory_ring/control.c index 900198691..5f1b04969 100644 --- a/src/tr2/game/inventory_ring/control.c +++ b/src/tr2/game/inventory_ring/control.c @@ -639,10 +639,10 @@ static GF_COMMAND M_Control(INV_RING *const ring) if (g_InputDB.menu_confirm) { g_Inv_Chosen = inv_item->object_id; - if (ring->type != RT_MAIN) { - g_InvRing_Source[RT_OPTION].current = ring->current_object; - } else { + if (ring->type == RT_MAIN) { g_InvRing_Source[RT_MAIN].current = ring->current_object; + } else { + g_InvRing_Source[RT_OPTION].current = ring->current_object; } if (ring->mode == INV_TITLE_MODE && (inv_item->object_id == O_DETAIL_OPTION @@ -783,7 +783,7 @@ INV_RING *InvRing_Open(const INVENTORY_MODE mode) } for (int32_t i = 0; i < 8; i++) { - g_Inv_ExtraData[i] = 0; + g_Inv_ExtraData[i] = -1; } g_InvRing_Source[RT_MAIN].current = 0; diff --git a/src/tr2/game/option/option_passport.c b/src/tr2/game/option/option_passport.c index 1c56e5f78..1da73aebc 100644 --- a/src/tr2/game/option/option_passport.c +++ b/src/tr2/game/option/option_passport.c @@ -66,6 +66,8 @@ static void M_LoadGame(INVENTORY_ITEM *inv_item); static void M_SaveGame(INVENTORY_ITEM *inv_item); static void M_NewGame(void); static void M_PlayAnyLevel(INVENTORY_ITEM *inv_item); +static int32_t M_GetCurrentPage(const INVENTORY_ITEM *inv_item); +static bool M_IsFlipping(const INVENTORY_ITEM *inv_item); static void M_FlipLeft(INVENTORY_ITEM *inv_item); static void M_FlipRight(INVENTORY_ITEM *inv_item); static void M_Close(INVENTORY_ITEM *inv_item); @@ -318,6 +320,17 @@ static void M_PlayAnyLevel(INVENTORY_ITEM *const inv_item) } } +static int32_t M_GetCurrentPage(const INVENTORY_ITEM *const inv_item) +{ + const int32_t frame = inv_item->goal_frame - inv_item->open_frame; + return frame % 5 == 0 ? frame / 5 : -1; +} + +static bool M_IsFlipping(const INVENTORY_ITEM *const inv_item) +{ + return M_GetCurrentPage(inv_item) == -1; +} + static void M_FlipLeft(INVENTORY_ITEM *const inv_item) { M_RemoveAllText(); @@ -413,18 +426,19 @@ void Option_Passport_Control(INVENTORY_ITEM *const item, const bool is_busy) InvRing_RemoveAllText(); - const int32_t frame = item->goal_frame - item->open_frame; - const int32_t page = frame % 5 == 0 ? frame / 5 : -1; - const bool is_flipping = page == -1; - if (is_flipping) { + if (M_IsFlipping(item)) { return; } - m_State.current_page = page; + m_State.current_page = M_GetCurrentPage(item); if (m_State.current_page < m_State.active_page) { M_FlipRight(item); + g_Input = (INPUT_STATE) {}; + g_InputDB = (INPUT_STATE) {}; } else if (m_State.current_page > m_State.active_page) { M_FlipLeft(item); + g_Input = (INPUT_STATE) {}; + g_InputDB = (INPUT_STATE) {}; } else { m_State.is_ready = true; M_ShowPage(item); From 3e4017d3380be71aee41c47e422e1fbbc1c6354a Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 26 Apr 2025 19:41:37 +0200 Subject: [PATCH 11/13] game-strings: fix crash with -l/--level --- src/libtrx/game/game_string_table/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtrx/game/game_string_table/common.c b/src/libtrx/game/game_string_table/common.c index 9e40ae432..f44260c6c 100644 --- a/src/libtrx/game/game_string_table/common.c +++ b/src/libtrx/game/game_string_table/common.c @@ -124,7 +124,7 @@ static void M_ApplyLayer( } } - if (gs_level_table != nullptr) { + if (gs_level_table != nullptr && gs_level_table->count != 0) { ASSERT(level->num >= 0); ASSERT(level->num < gs_level_table->count); M_Apply(&gs_level_table->entries[level->num].table); From 9c0c0160df66b5308674d10c4b7f3cbd4a87e9ca Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 26 Apr 2025 19:41:51 +0200 Subject: [PATCH 12/13] game-strings: fix memory leak --- src/libtrx/game/game_string_table/common.c | 8 ++++---- src/libtrx/game/game_string_table/priv.c | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libtrx/game/game_string_table/common.c b/src/libtrx/game/game_string_table/common.c index f44260c6c..3d31c1738 100644 --- a/src/libtrx/game/game_string_table/common.c +++ b/src/libtrx/game/game_string_table/common.c @@ -137,7 +137,7 @@ void GameStringTable_Apply(const GF_LEVEL *const level) Object_ResetNames(); ASSERT(m_GST_Layers != nullptr); for (int32_t i = 0; i < m_GST_Layers->count; i++) { - const GS_FILE *const gs_file = Vector_Get(m_GST_Layers, i); + const GS_FILE *const gs_file = *(GS_FILE **)Vector_Get(m_GST_Layers, i); M_ApplyLayer(level, gs_file); } M_DoObjectAliases(); @@ -145,14 +145,14 @@ void GameStringTable_Apply(const GF_LEVEL *const level) void GameStringTable_Init(void) { - m_GST_Layers = Vector_Create(sizeof(GS_FILE)); + m_GST_Layers = Vector_Create(sizeof(GS_FILE *)); } void GameStringTable_Shutdown(void) { if (m_GST_Layers != nullptr) { for (int32_t i = 0; i < m_GST_Layers->count; i++) { - GS_FILE *const gs_file = Vector_Get(m_GST_Layers, i); + GS_FILE *const gs_file = *(GS_FILE **)Vector_Get(m_GST_Layers, i); GS_File_Free(gs_file); } Vector_Free(m_GST_Layers); @@ -168,6 +168,6 @@ void GameStringTable_Load(const char *const path, const bool load_levels) } GS_FILE *gs_file = GS_File_CreateFromString(data, load_levels); ASSERT(m_GST_Layers != nullptr); - Vector_Add(m_GST_Layers, gs_file); + Vector_Add(m_GST_Layers, &gs_file); Memory_FreePointer(&data); } diff --git a/src/libtrx/game/game_string_table/priv.c b/src/libtrx/game/game_string_table/priv.c index 0b62cc41b..a9bada043 100644 --- a/src/libtrx/game/game_string_table/priv.c +++ b/src/libtrx/game/game_string_table/priv.c @@ -52,4 +52,5 @@ void GS_File_Free(GS_FILE *const gs_file) for (int32_t i = 0; i < GFLT_NUMBER_OF; i++) { M_FreeLevelsTable(&gs_file->level_tables[i]); } + Memory_Free(gs_file); } From 1f89b14a46855347bb9c7d80ae85b43107d9e997 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 26 Apr 2025 19:55:58 +0200 Subject: [PATCH 13/13] docs/tr2: release 1.0.2 --- docs/tr2/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index 511ab2bb4..44a0cb21e 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -1,4 +1,6 @@ -## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...develop) - ××××-××-×× +## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.2...develop) - ××××-××-×× + +## [1.0.2](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...tr2-1.0.2) - 2025-04-26 - changed The Golden Mask strings to default to the OG strings file for the main tables (#2847) - fixed Lara voiding if she stops on a tile with a closing door, and the door isn't on a portal (#2848) - fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level (#2856)