Compare commits

...

47 commits

Author SHA1 Message Date
Marcin Kurczewski
5e3fb42a25 docs: improve release process documentation
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / TR1 (Linux) (push) Waiting to run
Publish a pre-release / TR1 (Windows) (push) Waiting to run
Publish a pre-release / TR1 (Mac) (push) Waiting to run
Publish a pre-release / TR2 (Linux) (push) Waiting to run
Publish a pre-release / TR2 (Windows) (push) Waiting to run
Publish a pre-release / TR2 (Mac) (push) Waiting to run
Publish a pre-release / Create a prerelease (push) Blocked by required conditions
2025-04-28 14:49:50 +02:00
Marcin Kurczewski
6d5bdd89a3 ui: improve modal sizing
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / TR1 (Linux) (push) Waiting to run
Publish a pre-release / TR1 (Windows) (push) Waiting to run
Publish a pre-release / TR1 (Mac) (push) Waiting to run
Publish a pre-release / TR2 (Linux) (push) Waiting to run
Publish a pre-release / TR2 (Windows) (push) Waiting to run
Publish a pre-release / TR2 (Mac) (push) Waiting to run
Publish a pre-release / Create a prerelease (push) Blocked by required conditions
2025-04-28 11:46:57 +02:00
lahm86
a859d668f9 lara: test alignment position height validity in TR1
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / TR1 (Windows) (push) Waiting to run
Publish a pre-release / TR1 (Mac) (push) Waiting to run
Publish a pre-release / TR2 (Linux) (push) Waiting to run
Publish a pre-release / TR2 (Windows) (push) Waiting to run
Publish a pre-release / Create a prerelease (push) Blocked by required conditions
Publish a pre-release / TR1 (Linux) (push) Waiting to run
Publish a pre-release / TR2 (Mac) (push) Waiting to run
This applies a fix introduced in TR2 to TR1 so that Lara doesn't become
clamped under a steeply sloped ceiling if picking up an item there.

Resolves #2879.
2025-04-27 21:53:55 +01:00
lahm86
e03c65ca0f lara/common: move Lara_AlignPosition to TRX
This merges Lara_AlignPosition (Item_AlignPosition) into TRX.
2025-04-27 18:57:03 +01:00
lahm86
ff86b5e712 tr2/objects: use common OBJECT_BOUNDS
This updates all relevant TR2 objects to use OBJECT_BOUNDS rather than
an arbitrary array of values for testing bounds. The approach is
equivalent to TR1. Lara_TestPosition has also been moved to TRX, and
kept in Lara's module rather than in items.c, as it's only used by
Lara.
2025-04-27 18:57:03 +01:00
lahm86
cdfb5942c1 tr2/data: fix Living Quarters flame deactivation
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / TR2 (Linux) (push) Waiting to run
Publish a pre-release / TR2 (Windows) (push) Waiting to run
Publish a pre-release / TR2 (Mac) (push) Waiting to run
Publish a pre-release / Create a prerelease (push) Blocked by required conditions
Publish a pre-release / TR1 (Linux) (push) Waiting to run
Publish a pre-release / TR1 (Windows) (push) Waiting to run
Publish a pre-release / TR1 (Mac) (push) Waiting to run
Resolves #2851.
2025-04-27 15:03:53 +01:00
lahm86
8b78a7f001 room: fix abyss height check
As Lara's position is stored as int32, we need to use the same in the
abyss height check function, otherwise internal casting yields invalid
results.

Resolves #2874.
2025-04-27 13:30:38 +01:00
Marcin Kurczewski
2cd998673f
Merge branch 'stable' into develop
Some checks are pending
Publish a pre-release / TR2 (Windows) (push) Waiting to run
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / TR1 (Linux) (push) Waiting to run
Publish a pre-release / TR1 (Windows) (push) Waiting to run
Publish a pre-release / TR1 (Mac) (push) Waiting to run
Publish a pre-release / TR2 (Linux) (push) Waiting to run
Publish a pre-release / TR2 (Mac) (push) Waiting to run
Publish a pre-release / Create a prerelease (push) Blocked by required conditions
2025-04-26 19:57:08 +02:00
Marcin Kurczewski
1f89b14a46
docs/tr2: release 1.0.2
Some checks failed
Run code linters / Run code linters (push) Has been cancelled
2025-04-26 19:55:58 +02:00
Marcin Kurczewski
9c0c0160df
game-strings: fix memory leak 2025-04-26 19:54:03 +02:00
Marcin Kurczewski
3e4017d338
game-strings: fix crash with -l/--level 2025-04-26 19:54:02 +02:00
Marcin Kurczewski
0c5b5dbb7b
tr2/inventory: fix button mashing loading game instead of saving
Resolves #2863.
2025-04-26 19:53:58 +02:00
Marcin Kurczewski
a7269bbe8a game-strings: fix memory leak 2025-04-26 20:53:15 +03:00
Marcin Kurczewski
f27d0435a9 game-strings: fix crash with -l/--level 2025-04-26 20:53:15 +03:00
Marcin Kurczewski
88f41c5a75 tr2/inventory: fix button mashing loading game instead of saving
Resolves #2863.
2025-04-26 20:43:42 +03:00
Marcin Kurczewski
d97edaf1eb
audio: fix clicks in sample decoding
Resolves #2846.
2025-04-26 19:19:32 +02:00
Marcin Kurczewski
820fc307d2
audio: split into smaller functions 2025-04-26 19:19:31 +02:00
Marcin Kurczewski
24b81007ba
audio: fix wrong benchmark times
The decoder benchmark was formatting the wrong variable and produced
meaningless results in the logs.
2025-04-26 19:19:28 +02:00
Marcin Kurczewski
0ba717edd5 audio: fix clicks in sample decoding
Resolves #2846.
2025-04-26 20:17:21 +03:00
Marcin Kurczewski
3696e84925 audio: split into smaller functions 2025-04-26 20:17:21 +03:00
Marcin Kurczewski
e439371c66 audio: fix wrong benchmark times
The decoder benchmark was formatting the wrong variable and produced
meaningless results in the logs.
2025-04-26 20:17:21 +03:00
Marcin Kurczewski
864589bf0a
game-strings: use OG JSON as a fallback in expansions
Resolves #2847.
2025-04-26 19:05:01 +02:00
lahm86
5a50de02ed
door: move door module to TRX
This moves the door module fully to TRX as the logic is identical in
both games.
2025-04-26 19:04:13 +02:00
lahm86
d6fc167749
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.
2025-04-26 19:03:52 +02:00
Marcin Kurczewski
2f2f0c6842
tr2/viewport: fix screenshots at wrong resolution
Resolves #2845.
2025-04-26 19:03:16 +02:00
lahm86
4035fe6411
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.
2025-04-26 19:03:09 +02:00
Marcin Kurczewski
e32a4c270f
docs: fix formatting and mistyped option name 2025-04-26 19:02:57 +02:00
Marcin Kurczewski
92ff6e12c7 game-strings: use OG JSON as a fallback in expansions
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / TR1 (Linux) (push) Waiting to run
Publish a pre-release / TR1 (Windows) (push) Waiting to run
Publish a pre-release / TR1 (Mac) (push) Waiting to run
Publish a pre-release / TR2 (Linux) (push) Waiting to run
Publish a pre-release / TR2 (Windows) (push) Waiting to run
Publish a pre-release / TR2 (Mac) (push) Waiting to run
Publish a pre-release / Create a prerelease (push) Blocked by required conditions
Resolves #2847.
2025-04-26 17:55:25 +03:00
Marcin Kurczewski
73da4c2945 shell: do not create logs with --help 2025-04-26 17:53:26 +03:00
Marcin Kurczewski
dc41197cd6 shell: improve support for CLI options
- Adds documentation
- Adds a --help switch
- Tidies short/long option form conventions
2025-04-26 17:53:26 +03:00
lahm86
890c7f76bb door: move door module to TRX
This moves the door module fully to TRX as the logic is identical in
both games.
2025-04-26 12:39:42 +01:00
lahm86
83ac9514cb 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.

Resovles #2848.
2025-04-26 12:39:42 +01:00
Marcin Kurczewski
96b86b1605
docs/tr2: fix changelog merge mistake 2025-04-26 13:18:45 +02:00
Marcin Kurczewski
607ac811f0 tr2/viewport: fix screenshots at wrong resolution
Resolves #2845.
2025-04-26 14:07:54 +03:00
lahm86
15b758c57d 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.
2025-04-26 11:34:52 +01:00
Marcin Kurczewski
eec8f16d5f tools: download GM assets too 2025-04-26 13:06:08 +03:00
Marcin Kurczewski
2536ff55c1 docs: fix formatting and mistyped option name 2025-04-26 12:26:50 +03:00
Marcin Kurczewski
10b9bcc780 option: port sound settings dialog to ui
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / Create a prerelease (push) Blocked by required conditions
Publish a pre-release / TR2 (Windows) (push) Waiting to run
Publish a pre-release / TR2 (Mac) (push) Waiting to run
Publish a pre-release / TR1 (Linux) (push) Waiting to run
Publish a pre-release / TR1 (Windows) (push) Waiting to run
Publish a pre-release / TR1 (Mac) (push) Waiting to run
Publish a pre-release / TR2 (Linux) (push) Waiting to run
2025-04-26 10:31:36 +03:00
Marcin Kurczewski
c00d0627cf
tr2: release 1.0.1
Some checks failed
Publish a pre-release / TR1 (Linux) (push) Has been cancelled
Publish a pre-release / TR1 (Windows) (push) Has been cancelled
Publish a pre-release / TR1 (Mac) (push) Has been cancelled
Publish a pre-release / TR2 (Linux) (push) Has been cancelled
Publish a pre-release / TR2 (Windows) (push) Has been cancelled
Publish a pre-release / TR2 (Mac) (push) Has been cancelled
Run code linters / Run code linters (push) Has been cancelled
Publish a pre-release / Create a prerelease (push) Has been cancelled
2025-04-24 23:37:15 +02:00
Marcin Kurczewski
a6ebaf5c38
docs/tr2: release 1.0.1 2025-04-24 23:37:10 +02:00
Marcin Kurczewski
3acab0dc34 tr2/ui: improve graphic settings dialog sizing
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / TR1 (Linux) (push) Waiting to run
Publish a pre-release / TR1 (Windows) (push) Waiting to run
Publish a pre-release / TR1 (Mac) (push) Waiting to run
Publish a pre-release / TR2 (Linux) (push) Waiting to run
Publish a pre-release / TR2 (Windows) (push) Waiting to run
Publish a pre-release / TR2 (Mac) (push) Waiting to run
Publish a pre-release / Create a prerelease (push) Blocked by required conditions
Resolves #2841.
2025-04-24 10:36:15 +03:00
lahm86
c590824944 game: prioritize save over load
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / TR1 (Linux) (push) Waiting to run
Publish a pre-release / TR1 (Windows) (push) Waiting to run
Publish a pre-release / TR1 (Mac) (push) Waiting to run
Publish a pre-release / TR2 (Linux) (push) Waiting to run
Publish a pre-release / TR2 (Windows) (push) Waiting to run
Publish a pre-release / TR2 (Mac) (push) Waiting to run
Publish a pre-release / Create a prerelease (push) Blocked by required conditions
This matches OG behaviour where save is preferred over load when both
inputs are detected on the same frame.

Resolves #2833.
2025-04-23 21:43:08 +01:00
lahm86
f590c9243c graphic_settings: add scroll wraparound option
This makes the scroll wraparound option available in both games'
graphics dialogs. TR1's dialog is also updated to allow scrolling until
such times as it's moved to the new UI framework.

Resolves #2834.
2025-04-23 20:43:21 +01:00
lahm86
3030d694a5 tr2/shell: reload background image on aspect change
Resolves #2832.
2025-04-23 20:40:01 +01:00
lahm86
4d36177247 tr2/shell: fix PSX FOV option not being applied immediately
Resolves  #2831.
2025-04-23 20:40:01 +01:00
lahm86
b935707b5b tr2/option_controls: fix selected layout not saving
Resolves #2830.
2025-04-23 20:40:01 +01:00
Marcin Kurczewski
4d5040d15c misc: fix build warnings
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / TR1 (Linux) (push) Waiting to run
Publish a pre-release / TR1 (Windows) (push) Waiting to run
Publish a pre-release / TR1 (Mac) (push) Waiting to run
Publish a pre-release / TR2 (Linux) (push) Waiting to run
Publish a pre-release / TR2 (Windows) (push) Waiting to run
Publish a pre-release / TR2 (Mac) (push) Waiting to run
Publish a pre-release / Create a prerelease (push) Blocked by required conditions
2025-04-23 11:37:52 +02:00
98 changed files with 1403 additions and 1443 deletions

7
.gitignore vendored
View file

@ -38,4 +38,11 @@ src/tr2/subprojects/dwarfstack.wrap
data/tr1/ship/data/images/
data/tr2/ship/data/images/
data/tr2/ship/data/level1.tr2
data/tr2/ship/data/level2.tr2
data/tr2/ship/data/level3.tr2
data/tr2/ship/data/level4.tr2
data/tr2/ship/data/level5.tr2
data/tr2/ship/data/main_gm.sfx
data/tr2/ship/data/title_gm.tr2
data/tr2/ship/music/

View file

@ -369,6 +369,7 @@
"DETAIL_TITLE": "Graphic Options",
"DETAIL_TRAPEZOID_FILTER": "Trapezoid filter",
"DETAIL_UI_BAR_SCALE": "UI bar scale",
"DETAIL_UI_SCROLL_WRAPAROUND": "UI scroll wrap",
"DETAIL_UI_TEXT_SCALE": "UI text scale",
"DETAIL_VSYNC": "VSync",
"DETAIL_WATER_COLOR_B": "Water color (B)",
@ -530,7 +531,9 @@
"PHOTO_MODE_ROTATE_PROMPT": "Rotate camera",
"PHOTO_MODE_SNAP_PROMPT": "Take picture",
"PHOTO_MODE_TITLE": "Photo Mode",
"SOUND_SET_VOLUMES": "Set Volumes",
"SOUND_DIALOG_MUSIC": "\\{icon sound} Sound",
"SOUND_DIALOG_SOUND": "\\{icon music} Music",
"SOUND_DIALOG_TITLE": "Set Volumes",
"STATS_AMMO": "AMMO HITS/USED",
"STATS_BASIC_FMT": "%d",
"STATS_BONUS_STATISTICS": "Bonus Statistics",

View file

@ -217,6 +217,7 @@
],
"injections": [
"data/injections/living_deck_goon_sfx.bin",
"data/injections/living_fd.bin",
"data/injections/living_pickup_meshes.bin",
"data/injections/seaweed_collision.bin",
],

View file

@ -496,6 +496,7 @@
"DETAIL_TITLE": "Graphic Options",
"DETAIL_TRAPEZOID_FILTER": "Trapezoid filter",
"DETAIL_UI_BAR_SCALE": "UI bar scale",
"DETAIL_UI_SCROLL_WRAPAROUND": "UI scroll wrap",
"DETAIL_UI_TEXT_SCALE": "UI text scale",
"DETAIL_USE_PSX_FOV": "Use PSX FOV",
"DETAIL_WATER_COLOR_B": "Water color (B)",
@ -654,7 +655,9 @@
"PHOTO_MODE_ROTATE_PROMPT": "Rotate camera",
"PHOTO_MODE_SNAP_PROMPT": "Take picture",
"PHOTO_MODE_TITLE": "Photo Mode",
"SOUND_SET_VOLUMES": "Set Volumes",
"SOUND_DIALOG_MUSIC": "\\{icon sound} Sound",
"SOUND_DIALOG_SOUND": "\\{icon music} Music",
"SOUND_DIALOG_TITLE": "Set Volumes",
"STATS_AMMO_HITS": "Hits",
"STATS_AMMO_USED": "Ammo Used",
"STATS_ASSAULT_FINISH": "Finish",

Binary file not shown.

20
docs/COMMAND_LINE.md Normal file
View file

@ -0,0 +1,20 @@
# Command line options
Currently the following command line interface options are available:
`-g/--gold` (legacy: `-gold`):
Runs the Unfinished Business or the Golden Mask expansion pack, depending
on the game.
`--demo-pc` (TR1X only, legacy: `-demo_pc`):
Runs the PC demo level.
`-l/--level <path>`:
Runs the game immediately launching it into the specified level.
The path should be absolute. Internally, this option uses
`TR*X_gameflow_level.json5` as a template instructing it how to run the
game.
`-s/--save <num>`:
Runs the game immediately loading a specific save slot. The first save
starts at `num=1`. This option can be combined with `-l/--level`.

View file

@ -266,30 +266,63 @@ request number, but it's important to carefully review the body field, as it
often includes unwanted content.
### Branching model
We have two branches: `develop` and `stable`. `develop` is where all changes
about to be published in the next release land. `stable` is the latest release.
We avoid creating merge commits between these two they should always point to
the same HEAD when applicable. This means that any hotfixes that need to be
released ahead of unpublished work in `develop` are merged directly to
`stable`, and `develop` needs to be then rebased on top of the now-patched
`stable`.
### Tooling
Internal tools are typically coded in a reasonably recent version of Python,
while avoiding the use of bash, shell, and similar languages.
### Branching model
We have two branches: `develop` and `stable`. `develop` is where all changes
about to be published in the next release land. `stable` is the latest release.
### Releasing a new version
New version releases happen automatically whenever a new tag is pushed to the
`stable` branch with the help of GitHub actions. In general this is accompanied
with a special commit `docs: release X.Y.Z` that also adjusts the changelog.
See git history for details.
New version releases are published automatically whenever a new tag is pushed
to the `stable` branch with the help of GitHub actions.
The general workflow is this:
```console
TR_VERSION=...
RELEASE_VERSION=...
# Switch to the stable branch.
git checkout stable
# Merge `develop` into it.
git merge develop
# Create a special commit `docs: release X.Y.Z` marking the release in the
# relevant changelog file. Then tag it with `tr1-X.Y.Z` or `tr2-X.Y.Z`.
# You can do that by hand, or run the command below:
tools/release commit ${TR_VERSION} ${RELEASE_VERSION}
# Review the changelog content.
# Switch back to develop.
git checkout develop
# Merge stable using fast-forward.
git merge --ff stable
# Review both branches and changes. If everything is okay, push to GitHub.
# You can do this by hand: git push origin develop stable tr1-X.Y.Z, or:
# tools/release push ${TR_VERSION} ${RELEASE_VERSION}
```
### Hotfixes
Hotfix releases are a bit different as we try to not include non-bugfix changes
in them. Here instead of merging `develop` to `stable` we cherry-pick relevant
changes, resolving conflicts along the way.
### Versioning
We increase the major version for significant releases based on judgment,
typically defaulting to increasing the minor version. Hotfixes increase the
patch version.
## Glossary

View file

@ -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

View file

@ -4,11 +4,17 @@
- added support for a hex water color notation (eg. `#80FFFF`) in the game flow file
- added support for antitriggers, like TR2+ (#2580)
- added support for aspect ratio-specific images (#1840)
- changed the `draw_distance_min` and `draw_distance_max` to `fog_start` and `fog_end`
- added an option to wraparound when scrolling UI dialogs, such as save/load (#2834)
- added aliases to CLI options (`-gold` becomes `-g/--gold`, `-demo_pc` becomes `--demo-pc`)
- added a `--help` CLI option (may not output anything on Windows machines  OS bug)
- 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)
- 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 sound dialog appearance (repositioned and added text labels)
- 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)
@ -19,6 +25,7 @@
- fixed the scale of the four keys in St. Francis' Folly (#2652)
- fixed the panther at times not making a sound when it dies, and restored Skate Kid's death SFX (#2647)
- fixed pushblocks being rotated when Lara grabs them, most noticeable if asymmetric textures have been used (#2776)
- fixed Lara becoming clamped if she picks up an item under a steeply sloped ceiling (#2879)
- fixed a crash when 3D pickups are disabled and Lara crosses a trigger to look at a pickup item (#2711, regression from 4.8)
- fixed trapezoid filter warping on faces close to the camera (#2629, regression from 4.9)
- fixed Mac builds crashing upon start (regression from 4.9)
@ -33,6 +40,8 @@
- 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)
- fixed Lara being killed if she enters the void in a level that uses the `disable_floor` sequence in the game flow (#2874, regression from 4.9)
- improved bubble appearance (#2672)
- improved rendering performance
- improved pause exit dialog - it can now be canceled with escape

View file

@ -485,6 +485,7 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det
- fixed being able to shoot the scion multiple times if save/load is used while it blows up
- fixed the game crashing if a cinematic is triggered but the level contains no cinematic frames
- fixed pushblocks being rotated when Lara grabs them, most noticeable if asymmetric textures have been used
- fixed Lara becoming clamped if she picks up an item under a steeply sloped ceiling
#### Cheats
- added a fly cheat

View file

@ -1,4 +1,26 @@
## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0...develop) - ××××-××-××
## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...develop) - ××××-××-××
- added aliases to CLI options (`-gold` becomes `-g/--gold`)
- added a `--help` CLI option (may not output anything on Windows machines  OS bug)
- changed the sound dialog appearance (repositioned, added text labels and arrows)
- fixed Lara being killed if she enters the void in a level that uses the `disable_floor` sequence in the game flow (#2874, regression from 0.10)
- fixed flame emitter 23 in room 6 not being deactivated when the lever in room 1 is used (#2851)
## [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)
- 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)
## [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)
- changed save to take priority over load when both inputs are held on the same frame, in line with OG (#2833)
- fixed the selected keyboard/controller layout not being saved (#2830, regression from 1.0)
- fixed toggling the PSX FOV option not having an immediate effect (#2831, regression from 1.0)
- fixed changing the aspect ratio not updating the current background image (#2832, regression from 1.0)
- improved graphic settings dialog sizing (#2841)
## [1.0](https://github.com/LostArtefacts/TRX/compare/tr2-0.10...tr2-1.0) - 2025-04-23
- added support for The Golden Mask (#1621)

View file

@ -221,6 +221,7 @@ However, you can easily download them manually from these urls:
- fixed the following floor data issues:
- **Opera House**: fixed the trigger under item 203 to trigger it rather than item 204
- **Wreck of the Maria Doria**: fixed room 98 not having water
- **Living Quarters** - fixed flame emitter 23 in room 6 not being deactivated when the lever in room 1 is used
- **The Deck**: fixed invalid portals between rooms 17 and 104, which could result in Lara seeing enemies in disconnected rooms
- **Tibetan Foothills**: added missing triggers for the drawbridge in room 96 (after the flipmap)
- **Catacombs of the Talion**: changed some music triggers to pads near the first yeti, and added missing triggers and ladder in room 116 (after the flipmap)
@ -254,7 +255,9 @@ 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
- 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

View file

@ -1,5 +1,6 @@
#include "audio_internal.h"
#include "benchmark.h"
#include "debug.h"
#include "log.h"
#include "memory.h"
@ -21,12 +22,21 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
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;
@ -50,8 +60,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 +74,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,20 +145,98 @@ 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 int32_t M_OutputAudioFrame(
M_SWR_CONTEXT *const swr, AVFrame *const frame)
{
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;
// 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
}
const clock_t time_start = clock();
size_t working_buffer_size = 0;
float *working_buffer = nullptr;
uint8_t *out_buffer = nullptr;
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);
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);
memcpy(
swr->working_buffer + swr->working_buffer_size, out_buffer,
out_buffer_size);
swr->working_buffer_size += out_buffer_size;
}
}
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,
const int32_t dst_channel_count, uint8_t **const out_sample_data,
size_t *const out_size, size_t *const out_sample_count)
{
bool result = false;
struct {
size_t read_buffer_size;
@ -170,28 +258,20 @@ static bool M_Convert(const int32_t sample_id)
.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;
}
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(
@ -200,8 +280,7 @@ static bool M_Convert(const int32_t sample_id)
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;
}
@ -219,19 +298,19 @@ static bool M_Convert(const int32_t sample_id)
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;
}
@ -248,166 +327,134 @@ 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;
}
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;
break;
}
if (error_code < 0) {
av_packet_unref(av.packet);
goto cleanup;
}
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, 1);
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);
}
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;
}
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;
result = true;
error_code = swr_init(swr.ctx);
if (error_code != 0) {
av_packet_unref(av.packet);
goto cleanup;
}
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);
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 (av.codec_ctx != nullptr) {
M_DecodePacket(av.codec_ctx, nullptr, av.frame, &swr);
}
if (error_code == AVERROR_EOF) {
error_code = 0;
} else if (error_code < 0) {
goto cleanup;
}
if (out_size != nullptr) {
*out_size = swr.working_buffer_size;
}
if (out_sample_count != nullptr) {
*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 = swr.working_buffer;
} else {
Memory_FreePointer(&swr.working_buffer);
}
result = true;
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 (!result) {
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(&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 (!result) {
sample->sample_data = nullptr;
sample->original_data = nullptr;
sample->original_size = 0;
sample->num_samples = 0;
sample->channels = 0;
Memory_FreePointer(&working_buffer);
}
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;
}
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;
}
@ -553,7 +600,7 @@ int32_t Audio_Sample_Play(
continue;
}
M_Convert(sample_id);
M_ConvertSample(sample_id);
sound->is_used = true;
sound->is_playing = true;

View file

@ -283,56 +283,56 @@ MYFILE *File_Open(const char *path, FILE_OPEN_MODE mode)
return file;
}
void File_ReadData(MYFILE *const file, void *const data, const size_t size)
bool File_ReadData(MYFILE *const file, void *const data, const size_t size)
{
fread(data, size, 1, file->fp);
return fread(data, size, 1, file->fp) == 1;
}
void File_ReadItems(
bool File_ReadItems(
MYFILE *const file, void *data, const size_t count, const size_t item_size)
{
fread(data, item_size, count, file->fp);
return fread(data, item_size, count, file->fp) == count;
}
int8_t File_ReadS8(MYFILE *const file)
{
int8_t result;
fread(&result, sizeof(result), 1, file->fp);
File_ReadData(file, &result, sizeof(result));
return result;
}
int16_t File_ReadS16(MYFILE *const file)
{
int16_t result;
fread(&result, sizeof(result), 1, file->fp);
File_ReadData(file, &result, sizeof(result));
return result;
}
int32_t File_ReadS32(MYFILE *const file)
{
int32_t result;
fread(&result, sizeof(result), 1, file->fp);
File_ReadData(file, &result, sizeof(result));
return result;
}
uint8_t File_ReadU8(MYFILE *const file)
{
uint8_t result;
fread(&result, sizeof(result), 1, file->fp);
File_ReadData(file, &result, sizeof(result));
return result;
}
uint16_t File_ReadU16(MYFILE *const file)
{
uint16_t result;
fread(&result, sizeof(result), 1, file->fp);
File_ReadData(file, &result, sizeof(result));
return result;
}
uint32_t File_ReadU32(MYFILE *const file)
{
uint32_t result;
fread(&result, sizeof(result), 1, file->fp);
File_ReadData(file, &result, sizeof(result));
return result;
}

View file

@ -108,12 +108,11 @@ static void M_LoadCommonSettings(
if (tmp_value != nullptr && tmp_value->type == JSON_TYPE_ARRAY) {
const JSON_ARRAY *const tmp_arr = JSON_ValueAsArray(tmp_value);
const RGB_F color = {
JSON_ArrayGetDouble(tmp_arr, 0, JSON_INVALID_NUMBER),
JSON_ArrayGetDouble(tmp_arr, 1, JSON_INVALID_NUMBER),
JSON_ArrayGetDouble(tmp_arr, 2, JSON_INVALID_NUMBER),
JSON_ArrayGetDouble(tmp_arr, 0, -1.0),
JSON_ArrayGetDouble(tmp_arr, 1, -1.0),
JSON_ArrayGetDouble(tmp_arr, 2, -1.0),
};
if (color.r != JSON_INVALID_NUMBER && color.g != JSON_INVALID_NUMBER
&& color.b != JSON_INVALID_NUMBER) {
if (color.r >= 0.0 && color.g >= 0.0 && color.b >= 0.0) {
settings->water_color.is_present = true;
settings->water_color.value = (RGB_888) {
color.r * 255.0f,
@ -515,7 +514,8 @@ static void M_LoadTitleLevel(JSON_OBJECT *obj, GAME_FLOW *const gf)
JSON_OBJECT *title_obj = JSON_ObjectGetObject(obj, "title");
if (title_obj != nullptr) {
gf->title_level = Memory_Alloc(sizeof(GF_LEVEL));
M_LoadLevel(title_obj, gf, gf->title_level, 0, GFL_TITLE);
M_LoadLevel(
title_obj, gf, gf->title_level, 0, (void *)(intptr_t)GFL_TITLE);
}
}

View file

@ -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++) {
@ -120,16 +124,50 @@ void GameStringTable_Apply(const GF_LEVEL *const level)
}
}
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);
}
}
}
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 = *(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 = *(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);
}

View file

@ -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);
}

View file

@ -1,6 +1,7 @@
#pragma once
#include "game/game_flow/enum.h"
#include "vector.h"
#include <stdint.h>
@ -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);

View file

@ -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;
}

View file

@ -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++;
}
}

View file

@ -2,7 +2,9 @@
#include "game/const.h"
#include "game/item_actions.h"
#include "game/rooms/const.h"
#include "game/lara/const.h"
#include "game/matrix.h"
#include "game/rooms.h"
void Lara_Animate(ITEM *const item)
{
@ -156,3 +158,88 @@ bool Lara_TestBoundsCollide(const ITEM *const item, const int32_t radius)
{
return Item_TestBoundsCollide(item, Lara_GetItem(), radius);
}
bool Lara_TestPosition(
const ITEM *const item, const OBJECT_BOUNDS *const bounds)
{
const ITEM *const lara = Lara_GetItem();
const XYZ_16 rot = {
.x = lara->rot.x - item->rot.x,
.y = lara->rot.y - item->rot.y,
.z = lara->rot.z - item->rot.z,
};
const XYZ_32 dist = {
.x = lara->pos.x - item->pos.x,
.y = lara->pos.y - item->pos.y,
.z = lara->pos.z - item->pos.z,
};
// clang-format off
if (rot.x < bounds->rot.min.x ||
rot.x > bounds->rot.max.x ||
rot.y < bounds->rot.min.y ||
rot.y > bounds->rot.max.y ||
rot.z < bounds->rot.min.z ||
rot.z > bounds->rot.max.z
) {
return false;
}
// clang-format on
Matrix_PushUnit();
Matrix_Rot16(item->rot);
const MATRIX *const m = g_MatrixPtr;
const XYZ_32 shift = {
.x = (dist.x * m->_00 + dist.y * m->_10 + dist.z * m->_20) >> W2V_SHIFT,
.y = (dist.x * m->_01 + dist.y * m->_11 + dist.z * m->_21) >> W2V_SHIFT,
.z = (dist.x * m->_02 + dist.y * m->_12 + dist.z * m->_22) >> W2V_SHIFT,
};
Matrix_Pop();
// clang-format off
return (
shift.x >= bounds->shift.min.x &&
shift.x <= bounds->shift.max.x &&
shift.y >= bounds->shift.min.y &&
shift.y <= bounds->shift.max.y &&
shift.z >= bounds->shift.min.z &&
shift.z <= bounds->shift.max.z
);
// clang-format on
}
void Lara_AlignPosition(const ITEM *const item, const XYZ_32 *const vec)
{
ITEM *const lara = Lara_GetItem();
lara->rot = item->rot;
Matrix_PushUnit();
Matrix_Rot16(item->rot);
const MATRIX *const m = g_MatrixPtr;
const XYZ_32 shift = {
.x = (vec->x * m->_00 + vec->y * m->_01 + vec->z * m->_02) >> W2V_SHIFT,
.y = (vec->x * m->_10 + vec->y * m->_11 + vec->z * m->_12) >> W2V_SHIFT,
.z = (vec->x * m->_20 + vec->y * m->_21 + vec->z * m->_22) >> W2V_SHIFT,
};
Matrix_Pop();
const XYZ_32 new_pos = {
.x = item->pos.x + shift.x,
.y = item->pos.y + shift.y,
.z = item->pos.z + shift.z,
};
int16_t room_num = lara->room_num;
const SECTOR *const sector =
Room_GetSector(new_pos.x, new_pos.y, new_pos.z, &room_num);
const int32_t height =
Room_GetHeight(sector, new_pos.x, new_pos.y, new_pos.z);
const int32_t ceiling =
Room_GetCeiling(sector, new_pos.x, new_pos.y, new_pos.z);
if (ABS(height - lara->pos.y) > STEP_L
|| ABS(ceiling - lara->pos.y) < LARA_HEIGHT) {
return;
}
lara->pos = new_pos;
}

View file

@ -2,6 +2,16 @@
static uint16_t m_MusicTrackFlags[MAX_MUSIC_TRACKS] = {};
int32_t Music_GetMinVolume(void)
{
return 0;
}
int32_t Music_GetMaxVolume(void)
{
return 10;
}
void Music_ResetTrackFlags(void)
{
for (int32_t i = 0; i < MAX_MUSIC_TRACKS; i++) {

View file

@ -1,14 +1,16 @@
#include "game/box.h"
#include "game/items.h"
#include "game/objects/general/door.h"
#include "game/game_buf.h"
#include "game/lara/common.h"
#include "game/objects/common.h"
#include "game/room.h"
#include "global/vars.h"
#include "game/pathing.h"
#include "game/rooms.h"
#include <libtrx/game/collision.h>
#include <libtrx/game/game_buf.h>
#include <libtrx/game/objects/general/door.h>
#include <libtrx/utils.h>
typedef struct {
SECTOR *sector;
SECTOR old_sector;
int16_t box_num;
} DOORPOS_DATA;
typedef struct {
DOORPOS_DATA d1;
@ -22,7 +24,6 @@ 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);
@ -42,38 +43,17 @@ static SECTOR *M_GetRoomRelSector(
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) {
const ITEM *const lara = Lara_GetItem();
if (lara == 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);
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;
}
@ -90,22 +70,22 @@ static void M_Check(DOORPOS_DATA *const 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) {
if (d->sector == nullptr) {
return;
}
sector->idx = 0;
sector->box = NO_BOX;
sector->floor.height = NO_HEIGHT;
sector->ceiling.height = NO_HEIGHT;
sector->floor.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.sky = NO_ROOM_NEG;
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;
}
@ -113,34 +93,55 @@ static void M_Shut(DOORPOS_DATA *const d)
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) {
if (d->sector == nullptr) {
return;
}
*sector = d->old_sector;
*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;
}
}
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 = door_pos->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;
const BOX_INFO *const box = Box_GetBox(box_num);
if ((box->overlap_index & BOX_BLOCKABLE) == 0) {
box_num = NO_BOX;
}
door_pos->box_num = box_num;
door_pos->old_sector = *door_pos->sector;
}
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;
obj->save_anim = 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);
DOOR_DATA *door = GameBuf_Alloc(sizeof(DOOR_DATA), GBUF_ITEM_DATA);
item->data = door;
int32_t dx = 0;
@ -159,7 +160,7 @@ static void M_Initialise(const int16_t item_num)
const ROOM *room = Room_Get(room_num);
M_InitialisePortal(room, item, dx, dz, &door->d1);
if (room->flipped_room == -1) {
if (room->flipped_room == NO_ROOM_NEG) {
door->d1flip.sector = nullptr;
} else {
room = Room_Get(room->flipped_room);
@ -173,30 +174,29 @@ static void M_Initialise(const int16_t item_num)
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);
room = Room_Get(room_num);
M_InitialisePortal(room, item, 0, 0, &door->d2);
if (room->flipped_room == NO_ROOM_NEG) {
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;
}
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;
DOOR_DATA *const door = item->data;
if (Item_IsTriggerActive(item)) {
if (item->current_anim_state == DOOR_STATE_CLOSED) {
@ -225,21 +225,25 @@ static void M_Control(const int16_t item_num)
Item_Animate(item);
}
void Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll)
void Door_Collision(
const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll)
{
ITEM *const item = Item_Get(item_num);
if (!Lara_TestBoundsCollide(item, coll->radius)) {
if (!Item_TestBoundsCollide(item, lara_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);
}
Lara_Push(
item, coll,
coll->enable_hit
&& item->current_anim_state != item->goal_anim_state,
true);
}
}

View file

@ -2,6 +2,7 @@
#include "debug.h"
#include "filesystem.h"
#include "game/output/common.h"
#include "game/viewport.h"
#include "log.h"
#include "memory.h"
@ -198,6 +199,18 @@ bool Output_LoadBackgroundFromFile(const char *const path)
return result;
}
void Output_ReloadBackgroundImage(void)
{
if (m_LastPath == nullptr) {
return;
}
char *prev = Memory_DupStr(m_LastPath);
Output_UnloadBackground();
Output_LoadBackgroundFromFile(prev);
Memory_FreePointer(&prev);
}
char *Output_GetLastBackgroundPath(void)
{
return m_LastPath;

View file

@ -550,7 +550,7 @@ void Room_SetAbyssHeight(const int16_t height)
CLAMPG(m_AbyssMaxHeight, MAX_HEIGHT - STEP_L);
}
bool Room_IsAbyssHeight(const int16_t height)
bool Room_IsAbyssHeight(const int32_t height)
{
return m_AbyssMinHeight != 0 && height >= m_AbyssMinHeight;
}

View file

@ -6,28 +6,18 @@
#include <string.h>
static int m_ArgCount = 0;
static const char **m_ArgStrings = nullptr;
void Shell_GetCommandLine(int *arg_count, const char ***args)
{
*arg_count = m_ArgCount;
*args = m_ArgStrings;
}
int main(int argc, char *argv[])
{
if (!Shell_ParseArgs(argc, (const char **)argv)) {
return 0;
}
char *log_path = File_GetFullPath(PROJECT_NAME ".log");
Log_Init(log_path);
Memory_FreePointer(&log_path);
LOG_INFO("Game directory: %s", File_GetGameDirectory());
m_ArgCount = argc;
m_ArgStrings = (const char **)argv;
Shell_Setup();
Shell_Main();
Shell_Terminate(0);
return 0;
int32_t exit_code = Shell_Main();
Shell_Terminate(exit_code);
return exit_code;
}

View file

@ -4,8 +4,10 @@
#include "debug.h"
#include "game/console/common.h"
#include "game/game_string.h"
#include "game/scaler.h"
#include "game/ui/elements/anchor.h"
#include "game/ui/events.h"
#include "game/viewport.h"
#include "memory.h"
#include <SDL2/SDL.h>
@ -201,3 +203,23 @@ void UI_HandleTextEdit(const char *const text)
UI_FireEvent((EVENT) {
.name = "text_edit", .sender = nullptr, .data = (void *)text });
}
int32_t UI_GetCanvasWidth(void)
{
return Scaler_CalcInverse(Viewport_GetWidth(), SCALER_TARGET_GENERIC);
}
int32_t UI_GetCanvasHeight(void)
{
return Scaler_CalcInverse(Viewport_GetHeight(), SCALER_TARGET_GENERIC);
}
float UI_ScaleX(const float x)
{
return Scaler_Calc(x, SCALER_TARGET_GENERIC);
}
float UI_ScaleY(const float y)
{
return Scaler_Calc(y, SCALER_TARGET_GENERIC);
}

View file

@ -21,7 +21,7 @@ void UI_PhotoMode(void)
char tmp[50];
UI_BeginModal(0.0f, 0.0f);
UI_BeginPad(8.0f, 10.0f);
UI_BeginPad(8.0f, 8.0f);
UI_BeginFrame(UI_FRAME_DIALOG_BACKGROUND);
UI_BeginPad(8.0, 6.0);

View file

@ -0,0 +1,176 @@
#include "game/ui/dialogs/sound_settings.h"
#include "config.h"
#include "game/game_string.h"
#include "game/input.h"
#include "game/music.h"
#include "game/sound.h"
#include "game/ui/elements/anchor.h"
#include "game/ui/elements/hide.h"
#include "game/ui/elements/label.h"
#include "game/ui/elements/modal.h"
#include "game/ui/elements/requester.h"
#include "game/ui/elements/resize.h"
#include "game/ui/elements/spacer.h"
#include "game/ui/elements/stack.h"
#include "memory.h"
#include "strings.h"
#include "utils.h"
typedef struct UI_SOUND_SETTINGS_STATE {
UI_REQUESTER_STATE req;
} UI_SOUND_SETTINGS_STATE;
typedef enum {
M_ROW_MUSIC = 0,
M_ROW_SOUND = 1,
M_ROW_COUNT = 2,
} M_ROW;
static const GAME_STRING_ID m_Labels[M_ROW_COUNT] = {
GS_ID(SOUND_DIALOG_SOUND),
GS_ID(SOUND_DIALOG_MUSIC),
};
static char *M_FormatRowValue(int32_t row);
static bool M_CanChange(int32_t row, int32_t dir);
static bool M_RequestChange(int32_t row, int32_t dir);
static char *M_FormatRowValue(const int32_t row)
{
switch (row) {
case M_ROW_MUSIC:
return String_Format("%2d", g_Config.audio.music_volume);
case M_ROW_SOUND:
return String_Format("%2d", g_Config.audio.sound_volume);
default:
return nullptr;
}
}
static bool M_CanChange(const int32_t row, const int32_t dir)
{
switch (row) {
case M_ROW_MUSIC:
if (dir < 0) {
return g_Config.audio.music_volume > Music_GetMinVolume();
} else if (dir > 0) {
return g_Config.audio.music_volume < Music_GetMaxVolume();
}
break;
case M_ROW_SOUND:
if (dir < 0) {
return g_Config.audio.sound_volume > Sound_GetMinVolume();
} else if (dir > 0) {
return g_Config.audio.sound_volume < Sound_GetMaxVolume();
}
break;
}
return false;
}
static bool M_RequestChange(const int32_t row, const int32_t dir)
{
if (!M_CanChange(row, dir)) {
return false;
}
switch (row) {
case M_ROW_MUSIC:
g_Config.audio.music_volume += dir;
Music_SetVolume(g_Config.audio.music_volume);
break;
case M_ROW_SOUND:
g_Config.audio.sound_volume += dir;
Sound_SetMasterVolume(g_Config.audio.sound_volume);
break;
default:
return false;
}
Config_Write();
Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS);
return true;
}
UI_SOUND_SETTINGS_STATE *UI_SoundSettings_Init(void)
{
UI_SOUND_SETTINGS_STATE *s = Memory_Alloc(sizeof(UI_SOUND_SETTINGS_STATE));
UI_Requester_Init(&s->req, M_ROW_COUNT, M_ROW_COUNT, true);
s->req.row_pad = 2.0f;
return s;
}
void UI_SoundSettings_Free(UI_SOUND_SETTINGS_STATE *const s)
{
UI_Requester_Free(&s->req);
Memory_Free(s);
}
bool UI_SoundSettings_Control(UI_SOUND_SETTINGS_STATE *const s)
{
const int32_t choice = UI_Requester_Control(&s->req);
if (choice == UI_REQUESTER_CANCEL) {
return true;
}
const int32_t sel = UI_Requester_GetCurrentRow(&s->req);
if (g_InputDB.menu_left && sel >= 0) {
M_RequestChange(sel, -1);
} else if (g_InputDB.menu_right && sel >= 0) {
M_RequestChange(sel, +1);
}
return false;
}
void UI_SoundSettings(UI_SOUND_SETTINGS_STATE *const s)
{
const int32_t sel = UI_Requester_GetCurrentRow(&s->req);
UI_BeginModal(0.5f, 0.6f);
UI_BeginRequester(&s->req, GS(SOUND_DIALOG_TITLE));
// Measure the maximum width of the value label to prevent the entire
// dialog from changing its size as the player changes the sound levels.
float value_w = -1.0f;
UI_Label_Measure("10", &value_w, nullptr);
for (int32_t i = 0; i < s->req.max_rows; ++i) {
if (!UI_Requester_IsRowVisible(&s->req, i)) {
UI_BeginResize(-1.0f, 0.0f);
} else {
UI_BeginResize(-1.0f, -1.0f);
}
UI_BeginRequesterRow(&s->req, i);
UI_BeginStackEx((UI_STACK_SETTINGS) {
.orientation = UI_STACK_HORIZONTAL,
.align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE },
});
UI_Label(GameString_Get(m_Labels[i]));
UI_Spacer(20.0f, 0.0f);
UI_BeginStackEx((UI_STACK_SETTINGS) {
.orientation = UI_STACK_HORIZONTAL,
.align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE },
.spacing = { .h = 5.0f },
});
UI_BeginHide(i != sel || !M_CanChange(i, -1));
UI_Label("\\{button left}");
UI_EndHide();
UI_BeginResize(value_w, -1.0f);
UI_BeginAnchor(0.5f, 0.5f);
UI_Label(M_FormatRowValue(i));
UI_EndAnchor();
UI_EndResize();
UI_BeginHide(i != sel || !M_CanChange(i, +1));
UI_Label("\\{button right}");
UI_EndHide();
UI_EndStack();
UI_EndStack();
UI_EndRequesterRow(&s->req, i);
UI_EndResize();
}
UI_EndRequester(&s->req);
UI_EndModal();
}

View file

@ -15,7 +15,7 @@ static const UI_WIDGET_OPS m_Ops = {
static void M_Measure(UI_NODE *const node)
{
node->measure_w = UI_GetCanvasWidth();
node->measure_h = UI_GetCanvasHeight() - TEXT_HEIGHT_FIXED;
node->measure_h = UI_GetCanvasHeight();
}
void UI_BeginModal(const float x, const float y)

View file

@ -22,9 +22,9 @@ static void M_DownArrow(const UI_REQUESTER_STATE *s);
static void M_UpArrow(const UI_REQUESTER_STATE *const s)
{
UI_BeginHide(s->vis_row == 0);
UI_Spacer(0.0f, 4.0f);
UI_Spacer(0.0f, TR_VERSION == 2 ? 6.0f : 4.0f);
UI_BeginAnchor(0.5f, 0.5f);
UI_BeginFixed(0.5f, 1.5f);
UI_BeginFixed(0.5f, TR_VERSION == 2 ? 1.25f : 1.5f);
UI_LabelEx("\\{arrow up}", (UI_LABEL_SETTINGS) { .scale = 0.7 });
UI_EndFixed();
UI_EndAnchor();
@ -40,7 +40,7 @@ static void M_DownArrow(const UI_REQUESTER_STATE *const s)
UI_EndFixed();
UI_EndAnchor();
UI_EndHide();
UI_Spacer(0.0f, 4.0f);
UI_Spacer(0.0f, TR_VERSION == 2 ? 6.0f : 4.0f);
}
void UI_Requester_Init(

View file

@ -114,7 +114,7 @@ static void M_Layout(
int32_t child_count = 0;
float total_child_main_size = 0.0f;
UI_NODE *child = node->first_child;
while (child != NULL) {
while (child != nullptr) {
switch (data->settings.orientation) {
case UI_STACK_HORIZONTAL:
total_child_main_size += child->measure_w;

View file

@ -39,8 +39,8 @@ char *File_GuessExtension(const char *path, const char **extensions);
MYFILE *File_Open(const char *path, FILE_OPEN_MODE mode);
void File_ReadData(MYFILE *file, void *data, size_t size);
void File_ReadItems(MYFILE *file, void *data, size_t count, size_t item_size);
bool File_ReadData(MYFILE *file, void *data, size_t size);
bool File_ReadItems(MYFILE *file, void *data, size_t count, size_t item_size);
int8_t File_ReadS8(MYFILE *file);
int16_t File_ReadS16(MYFILE *file);
int32_t File_ReadS32(MYFILE *file);

View file

@ -132,7 +132,9 @@ GS_DEFINE(PASSPORT_MODE_NEW_GAME_JP_PLUS, "Japanese NG+")
GS_DEFINE(PASSPORT_STORY_SO_FAR, "Story so far...")
GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_1, "Legacy saves do not")
GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_2, "support this feature.")
GS_DEFINE(SOUND_SET_VOLUMES, "Set Volumes")
GS_DEFINE(SOUND_DIALOG_TITLE, "Set Volumes")
GS_DEFINE(SOUND_DIALOG_SOUND, "\\{icon music} Music")
GS_DEFINE(SOUND_DIALOG_MUSIC, "\\{icon sound} Sound")
GS_DEFINE(OSD_TRAPEZOID_FILTER_ON, "Trapezoid filter enabled")
GS_DEFINE(OSD_TRAPEZOID_FILTER_OFF, "Trapezoid filter disabled")
GS_DEFINE(DETAIL_INTEGER_FMT, "%d")
@ -150,5 +152,6 @@ GS_DEFINE(DETAIL_TRAPEZOID_FILTER, "Trapezoid filter")
GS_DEFINE(DETAIL_RENDER_MODE, "Render mode")
GS_DEFINE(DETAIL_UI_TEXT_SCALE, "UI text scale")
GS_DEFINE(DETAIL_UI_BAR_SCALE, "UI bar scale")
GS_DEFINE(DETAIL_UI_SCROLL_WRAPAROUND, "UI scroll wrap")
GS_DEFINE(PAGINATION_NAV, "%d / %d")
GS_DEFINE(MISC_EMPTY_SLOT_FMT, "- EMPTY SLOT -")

View file

@ -2,7 +2,8 @@
#include <stdint.h>
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);

View file

@ -15,3 +15,5 @@ void Lara_TakeDamage(int16_t damage, bool hit_status);
bool Lara_TestBoundsCollide(const ITEM *item, int32_t radius);
void Lara_Push(const ITEM *item, COLL_INFO *coll, bool hit_on, bool big_push);
bool Lara_TestPosition(const ITEM *item, const OBJECT_BOUNDS *bounds);
void Lara_AlignPosition(const ITEM *item, const XYZ_32 *vec);

View file

@ -31,3 +31,15 @@ extern void Music_Unpause(void);
void Music_ResetTrackFlags(void);
uint16_t Music_GetTrackFlags(int32_t track_idx);
void Music_SetTrackFlags(int32_t track, uint16_t flags);
// Gets the minimum possible game volume.
extern int32_t Music_GetMinVolume(void);
// Gets the maximum possible game volume.
extern int32_t Music_GetMaxVolume(void);
// Gets the game volume.
extern int32_t Music_GetVolume(void);
// Sets the game volume.
extern void Music_SetVolume(int32_t volume);

View file

@ -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(

View file

@ -38,14 +38,12 @@ typedef struct {
bool disable_lighting;
} OBJECT_MESH;
#if TR_VERSION == 1
typedef struct {
struct {
XYZ_16 min;
XYZ_16 max;
} shift, rot;
} OBJECT_BOUNDS;
#endif
typedef struct OBJECT {
int16_t mesh_count;
@ -66,8 +64,8 @@ typedef struct OBJECT {
void (*activate_func)(ITEM *item);
void (*handle_flip_func)(ITEM *item, ROOM_FLIP_STATUS flip_status);
void (*handle_save_func)(ITEM *item, SAVEGAME_STAGE stage);
#if TR_VERSION == 1
const OBJECT_BOUNDS *(*bounds_func)(void);
#if TR_VERSION == 1
bool (*is_usable_func)(int16_t item_num);
#endif

View file

@ -3,6 +3,7 @@
#include "../../engine/image.h"
bool Output_LoadBackgroundFromFile(const char *path);
void Output_ReloadBackgroundImage(void);
extern bool Output_LoadBackgroundFromImage(const IMAGE *image);
extern void Output_LoadBackgroundFromObject(void);

View file

@ -43,7 +43,7 @@ SECTOR *Room_GetPitSector(const SECTOR *sector, int32_t x, int32_t z);
SECTOR *Room_GetSkySector(const SECTOR *sector, int32_t x, int32_t z);
void Room_SetAbyssHeight(int16_t height);
bool Room_IsAbyssHeight(int16_t height);
bool Room_IsAbyssHeight(int32_t height);
HEIGHT_TYPE Room_GetHeightType(void);
int16_t Room_GetHeight(const SECTOR *sector, int32_t x, int32_t y, int32_t z);
int16_t Room_GetHeightEx(

View file

@ -11,13 +11,13 @@ typedef struct {
extern void Shell_Shutdown(void);
extern SDL_Window *Shell_GetWindow(void);
extern bool Shell_ParseArgs(int32_t arg_count, const char **args);
void Shell_Setup(void);
extern void Shell_Main(void);
extern int32_t Shell_Main(void);
void Shell_Terminate(int32_t exit_code);
void Shell_ExitSystem(const char *message);
void Shell_ExitSystemFmt(const char *fmt, ...);
void Shell_GetCommandLine(int *arg_count, const char ***args);
void Shell_ScheduleExit(void);
bool Shell_IsExiting(void);

View file

@ -14,6 +14,11 @@ int16_t *Sound_GetSampleLUT(void);
SAMPLE_INFO *Sound_GetSampleInfo(SOUND_EFFECT_ID sfx_num);
SAMPLE_INFO *Sound_GetSampleInfoByIdx(int32_t info_idx);
extern int32_t Sound_GetMinVolume(void);
extern int32_t Sound_GetMaxVolume(void);
extern int32_t Sound_GetMasterVolume(void);
extern void Sound_SetMasterVolume(int32_t volume);
void Sound_ResetSources(void);
void Sound_PauseAll(void);
void Sound_UnpauseAll(void);

View file

@ -10,6 +10,7 @@
#include "./ui/dialogs/play_any_level.h"
#include "./ui/dialogs/save_slot.h"
#include "./ui/dialogs/select_level.h"
#include "./ui/dialogs/sound_settings.h"
#include "./ui/dialogs/stats.h"
#include "./ui/elements/anchor.h"
#include "./ui/elements/fade.h"

View file

@ -50,10 +50,10 @@ typedef struct UI_NODE {
// Dimensions in virtual pixels of the screen area
// (640x480 for any 4:3 resolution on 1.00 text scaling)
extern int32_t UI_GetCanvasWidth(void);
extern int32_t UI_GetCanvasHeight(void);
extern float UI_ScaleX(float x);
extern float UI_ScaleY(float y);
int32_t UI_GetCanvasWidth(void);
int32_t UI_GetCanvasHeight(void);
float UI_ScaleX(float x);
float UI_ScaleY(float y);
// Public API for scene management
void UI_BeginScene(void);

View file

@ -0,0 +1,21 @@
// UI dialog for adjusting music and sound volumes
#pragma once
#include "../common.h"
typedef struct UI_SOUND_SETTINGS_STATE UI_SOUND_SETTINGS_STATE;
// state functions
// Initialize the sound settings dialog state
UI_SOUND_SETTINGS_STATE *UI_SoundSettings_Init(void);
// Free resources used by the sound settings dialog
void UI_SoundSettings_Free(UI_SOUND_SETTINGS_STATE *s);
// Handle input/control for the sound settings dialog
// Returns true if the dialog should be closed
bool UI_SoundSettings_Control(UI_SOUND_SETTINGS_STATE *s);
// draw functions
// Render the sound settings dialog
void UI_SoundSettings(UI_SOUND_SETTINGS_STATE *s);

View file

@ -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',
@ -216,6 +217,7 @@ sources = [
'game/ui/dialogs/play_any_level.c',
'game/ui/dialogs/save_slot.c',
'game/ui/dialogs/select_level.c',
'game/ui/dialogs/sound_settings.c',
'game/ui/dialogs/stats.c',
'game/ui/elements/anchor.c',
'game/ui/elements/fade.c',

View file

@ -142,10 +142,10 @@ GF_COMMAND Game_Control(const bool demo_mode)
if (g_Camera.type == CAM_CINEMATIC) {
g_OverlayFlag = 0;
} else if (g_OverlayFlag > 0) {
if (g_Input.load) {
g_OverlayFlag = -1;
} else if (g_Input.save) {
if (g_Input.save) {
g_OverlayFlag = -2;
} else if (g_Input.load) {
g_OverlayFlag = -1;
} else {
g_OverlayFlag = 0;
}

View file

@ -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)) {

View file

@ -196,70 +196,6 @@ bool Item_Test3DRange(int32_t x, int32_t y, int32_t z, int32_t range)
&& (SQUARE(x) + SQUARE(y) + SQUARE(z) < SQUARE(range));
}
bool Item_TestPosition(
const ITEM *const src_item, const ITEM *const dst_item,
const OBJECT_BOUNDS *const bounds)
{
const XYZ_16 rot = {
.x = src_item->rot.x - dst_item->rot.x,
.y = src_item->rot.y - dst_item->rot.y,
.z = src_item->rot.z - dst_item->rot.z,
};
if (rot.x < bounds->rot.min.x || rot.x > bounds->rot.max.x
|| rot.y < bounds->rot.min.y || rot.y > bounds->rot.max.y
|| rot.z < bounds->rot.min.z || rot.z > bounds->rot.max.z) {
return false;
}
const XYZ_32 dist = {
.x = src_item->pos.x - dst_item->pos.x,
.y = src_item->pos.y - dst_item->pos.y,
.z = src_item->pos.z - dst_item->pos.z,
};
Matrix_PushUnit();
Matrix_Rot16(dst_item->rot);
MATRIX *mptr = g_MatrixPtr;
const XYZ_32 shift = {
.x = (mptr->_00 * dist.x + mptr->_10 * dist.y + mptr->_20 * dist.z)
>> W2V_SHIFT,
.y = (mptr->_01 * dist.x + mptr->_11 * dist.y + mptr->_21 * dist.z)
>> W2V_SHIFT,
.z = (mptr->_02 * dist.x + mptr->_12 * dist.y + mptr->_22 * dist.z)
>> W2V_SHIFT,
};
Matrix_Pop();
if (shift.x < bounds->shift.min.x || shift.x > bounds->shift.max.x
|| shift.y < bounds->shift.min.y || shift.y > bounds->shift.max.y
|| shift.z < bounds->shift.min.z || shift.z > bounds->shift.max.z) {
return false;
}
return true;
}
void Item_AlignPosition(ITEM *src_item, ITEM *dst_item, XYZ_32 *vec)
{
src_item->rot.x = dst_item->rot.x;
src_item->rot.y = dst_item->rot.y;
src_item->rot.z = dst_item->rot.z;
Matrix_PushUnit();
Matrix_Rot16(dst_item->rot);
MATRIX *mptr = g_MatrixPtr;
src_item->pos.x = dst_item->pos.x
+ ((mptr->_00 * vec->x + mptr->_01 * vec->y + mptr->_02 * vec->z)
>> W2V_SHIFT);
src_item->pos.y = dst_item->pos.y
+ ((mptr->_10 * vec->x + mptr->_11 * vec->y + mptr->_12 * vec->z)
>> W2V_SHIFT);
src_item->pos.z = dst_item->pos.z
+ ((mptr->_20 * vec->x + mptr->_21 * vec->y + mptr->_22 * vec->z)
>> W2V_SHIFT);
Matrix_Pop();
}
bool Item_MovePosition(
ITEM *item, const ITEM *ref_item, const XYZ_32 *vec, int32_t velocity)
{

View file

@ -11,9 +11,6 @@ int16_t Item_Spawn(const ITEM *item, GAME_OBJECT_ID obj_id);
bool Item_IsNearItem(const ITEM *item, const XYZ_32 *pos, int32_t distance);
bool Item_Test3DRange(int32_t x, int32_t y, int32_t z, int32_t range);
bool Item_TestPosition(
const ITEM *src_item, const ITEM *dst_item, const OBJECT_BOUNDS *bounds);
void Item_AlignPosition(ITEM *src_item, ITEM *dst_item, XYZ_32 *vec);
bool Item_MovePosition(
ITEM *src_item, const ITEM *dst_item, const XYZ_32 *vec, int32_t velocity);
void Item_ShiftCol(ITEM *item, COLL_INFO *coll);

View file

@ -701,16 +701,6 @@ bool Lara_IsNearItem(const XYZ_32 *pos, int32_t distance)
return Item_IsNearItem(g_LaraItem, pos, distance);
}
bool Lara_TestPosition(const ITEM *item, const OBJECT_BOUNDS *const bounds)
{
return Item_TestPosition(g_LaraItem, item, bounds);
}
void Lara_AlignPosition(ITEM *item, XYZ_32 *vec)
{
Item_AlignPosition(g_LaraItem, item, vec);
}
bool Lara_MovePosition(ITEM *item, XYZ_32 *vec)
{
int32_t velocity = g_Config.gameplay.enable_walk_to_items

View file

@ -26,8 +26,6 @@ void Lara_SwapMeshExtra(void);
bool Lara_IsNearItem(const XYZ_32 *pos, int32_t distance);
void Lara_UseItem(GAME_OBJECT_ID obj_id);
bool Lara_TestPosition(const ITEM *item, const OBJECT_BOUNDS *bounds);
void Lara_AlignPosition(ITEM *item, XYZ_32 *vec);
bool Lara_MovePosition(ITEM *item, XYZ_32 *vec);
void Lara_RevertToPistolsIfNeeded(void);

View file

@ -182,12 +182,12 @@ void Music_Unmute(void)
M_SyncVolume(m_AudioStreamID);
}
int16_t Music_GetVolume(void)
int32_t Music_GetVolume(void)
{
return m_Volume;
}
void Music_SetVolume(int16_t volume)
void Music_SetVolume(int32_t volume)
{
if (volume != m_Volume) {
m_Volume = volume;
@ -195,16 +195,6 @@ void Music_SetVolume(int16_t volume)
}
}
int16_t Music_GetMinVolume(void)
{
return 0;
}
int16_t Music_GetMaxVolume(void)
{
return 10;
}
void Music_Pause(void)
{
if (m_AudioStreamID < 0) {

View file

@ -19,18 +19,6 @@ void Music_Mute(void);
// Unmutes the game music. Doesn't change the music volume.
void Music_Unmute(void);
// Gets the game volume.
int16_t Music_GetVolume(void);
// Sets the game volume. Value can be 0-10.
void Music_SetVolume(int16_t volume);
// Gets the minimum possible game volume.
int16_t Music_GetMinVolume(void);
// Gets the maximum possible game volume.
int16_t Music_GetMaxVolume(void);
// Returns the currently playing track. Includes looped music.
MUSIC_TRACK_ID Music_GetCurrentPlayingTrack(void);

View file

@ -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);

View file

@ -168,6 +168,9 @@ void Option_Draw(INVENTORY_ITEM *const inv_item)
case O_COMPASS_OPTION:
Option_Compass_Draw();
break;
case O_SOUND_OPTION:
Option_Sound_Draw(inv_item);
break;
case O_PICKUP_OPTION_1:
case O_PICKUP_OPTION_2:

View file

@ -50,6 +50,7 @@ typedef enum {
OPTION_BRIGHTNESS,
OPTION_UI_TEXT_SCALE,
OPTION_UI_BAR_SCALE,
OPTION_UI_SCROLL_WRAPAROUND,
OPTION_RENDER_MODE,
OPTION_RESOLUTION,
OPTION_TRAPEZOID_FILTER,
@ -95,6 +96,8 @@ static const GRAPHICS_OPTION_ROW m_GfxOptionRows[] = {
GS_ID(DETAIL_FLOAT_FMT) },
{ OPTION_UI_BAR_SCALE, GS_ID(DETAIL_UI_BAR_SCALE),
GS_ID(DETAIL_FLOAT_FMT) },
{ OPTION_UI_SCROLL_WRAPAROUND, GS_ID(DETAIL_UI_SCROLL_WRAPAROUND),
GS_ID(MISC_ON) },
{ OPTION_RENDER_MODE, GS_ID(DETAIL_RENDER_MODE), GS_ID(DETAIL_STRING_FMT) },
{ OPTION_RESOLUTION, GS_ID(DETAIL_RESOLUTION),
GS_ID(DETAIL_RESOLUTION_FMT) },
@ -177,6 +180,8 @@ static void M_MenuUp(void)
}
m_GraphicsMenu.cur_option--;
M_UpdateText();
} else if (g_Config.ui.enable_wraparound) {
M_Reinitialize(m_GfxOptionRows[OPTION_NUMBER_OF - 1].option_name);
}
}
@ -191,6 +196,8 @@ static void M_MenuDown(void)
}
m_GraphicsMenu.cur_option++;
M_UpdateText();
} else if (g_Config.ui.enable_wraparound) {
M_Reinitialize(m_GfxOptionRows[0].option_name);
}
}
@ -323,6 +330,10 @@ static void M_UpdateArrows(
m_HideArrowLeft = g_Config.ui.bar_scale <= CONFIG_MIN_BAR_SCALE;
m_HideArrowRight = g_Config.ui.bar_scale >= CONFIG_MAX_BAR_SCALE;
break;
case OPTION_UI_SCROLL_WRAPAROUND:
m_HideArrowLeft = !g_Config.ui.enable_wraparound;
m_HideArrowRight = g_Config.ui.enable_wraparound;
break;
case OPTION_RENDER_MODE:
local_right_arrow_offset = RIGHT_ARROW_OFFSET_MAX;
m_HideArrowLeft = false;
@ -483,6 +494,11 @@ static void M_ChangeTextOption(
Text_ChangeText(value_text, buf);
break;
case OPTION_UI_SCROLL_WRAPAROUND:
bool is_enabled = g_Config.ui.enable_wraparound;
Text_ChangeText(value_text, is_enabled ? GS(MISC_ON) : GS(MISC_OFF));
break;
case OPTION_RENDER_MODE:
sprintf(
buf, GS(DETAIL_STRING_FMT),
@ -636,6 +652,13 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy)
reset = OPTION_UI_BAR_SCALE;
break;
case OPTION_UI_SCROLL_WRAPAROUND:
if (!g_Config.ui.enable_wraparound) {
g_Config.ui.enable_wraparound = true;
reset = OPTION_UI_SCROLL_WRAPAROUND;
}
break;
case OPTION_RENDER_MODE:
if (g_Config.rendering.render_mode == GFX_RM_LEGACY) {
g_Config.rendering.render_mode = GFX_RM_FRAMEBUFFER;
@ -766,6 +789,13 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy)
reset = OPTION_UI_BAR_SCALE;
break;
case OPTION_UI_SCROLL_WRAPAROUND:
if (g_Config.ui.enable_wraparound) {
g_Config.ui.enable_wraparound = false;
reset = OPTION_UI_SCROLL_WRAPAROUND;
}
break;
case OPTION_RENDER_MODE:
if (g_Config.rendering.render_mode == GFX_RM_LEGACY) {
g_Config.rendering.render_mode = GFX_RM_FRAMEBUFFER;

View file

@ -1,170 +1,48 @@
#include "game/option/option_sound.h"
#include "game/game_string.h"
#include "game/input.h"
#include "game/music.h"
#include "game/sound.h"
#include "game/text.h"
#include "global/vars.h"
#include <libtrx/config.h>
#include <libtrx/game/ui.h>
#include <stdio.h>
typedef struct {
UI_SOUND_SETTINGS_STATE *ui;
} M_PRIV;
typedef enum {
TEXT_MUSIC_VOLUME = 0,
TEXT_SOUND_VOLUME = 1,
TEXT_TITLE = 2,
TEXT_TITLE_BORDER = 3,
TEXT_LEFT_ARROW = 4,
TEXT_RIGHT_ARROW = 5,
TEXT_NUMBER_OF = 6,
TEXT_OPTION_MIN = TEXT_MUSIC_VOLUME,
TEXT_OPTION_MAX = TEXT_SOUND_VOLUME,
} SOUND_TEXT;
static M_PRIV m_Priv = {};
static TEXTSTRING *m_Text[TEXT_NUMBER_OF] = {};
static void M_InitText(void);
static void M_InitText(void)
static void M_Init(M_PRIV *const p)
{
char buf[20];
p->ui = UI_SoundSettings_Init();
}
m_Text[TEXT_LEFT_ARROW] = Text_Create(-45, 0, "\\{button left}");
m_Text[TEXT_RIGHT_ARROW] = Text_Create(40, 0, "\\{button right}");
m_Text[TEXT_TITLE_BORDER] = Text_Create(0, -32, " ");
m_Text[TEXT_TITLE] = Text_Create(0, -30, GS(SOUND_SET_VOLUMES));
if (g_Config.audio.music_volume > 10) {
g_Config.audio.music_volume = 10;
}
sprintf(buf, "\\{icon music} %2d", g_Config.audio.music_volume);
m_Text[TEXT_MUSIC_VOLUME] = Text_Create(0, 0, buf);
if (g_Config.audio.sound_volume > 10) {
g_Config.audio.sound_volume = 10;
}
sprintf(buf, "\\{icon sound} %2d", g_Config.audio.sound_volume);
m_Text[TEXT_SOUND_VOLUME] = Text_Create(0, 25, buf);
Text_AddBackground(m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED);
Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED);
Text_AddBackground(m_Text[TEXT_TITLE], 136, 0, 0, 0, TS_HEADING);
Text_AddOutline(m_Text[TEXT_TITLE], TS_HEADING);
Text_AddBackground(m_Text[TEXT_TITLE_BORDER], 140, 85, 0, 0, TS_BACKGROUND);
Text_AddOutline(m_Text[TEXT_TITLE_BORDER], TS_BACKGROUND);
for (int i = 0; i < TEXT_NUMBER_OF; i++) {
Text_CentreH(m_Text[i], 1);
Text_CentreV(m_Text[i], 1);
static void M_Shutdown(M_PRIV *const p)
{
if (p->ui != nullptr) {
UI_SoundSettings_Free(p->ui);
p->ui = nullptr;
}
}
void Option_Sound_Control(INVENTORY_ITEM *inv_item, const bool is_busy)
void Option_Sound_Control(INVENTORY_ITEM *const inv_item, const bool is_busy)
{
M_PRIV *const p = &m_Priv;
if (is_busy) {
return;
}
char buf[20];
if (!m_Text[TEXT_MUSIC_VOLUME]) {
M_InitText();
if (p->ui == nullptr) {
M_Init(p);
}
UI_SoundSettings_Control(p->ui);
}
if (g_InputDB.menu_up && g_OptionSelected > TEXT_OPTION_MIN) {
Text_RemoveOutline(m_Text[g_OptionSelected]);
Text_RemoveBackground(m_Text[g_OptionSelected]);
--g_OptionSelected;
Text_AddBackground(
m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED);
Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED);
Text_SetPos(m_Text[TEXT_LEFT_ARROW], -45, 0);
Text_SetPos(m_Text[TEXT_RIGHT_ARROW], 40, 0);
}
if (g_InputDB.menu_down && g_OptionSelected < TEXT_OPTION_MAX) {
Text_RemoveOutline(m_Text[g_OptionSelected]);
Text_RemoveBackground(m_Text[g_OptionSelected]);
++g_OptionSelected;
Text_AddBackground(
m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED);
Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED);
Text_SetPos(m_Text[TEXT_LEFT_ARROW], -45, 25);
Text_SetPos(m_Text[TEXT_RIGHT_ARROW], 40, 25);
}
switch (g_OptionSelected) {
case TEXT_MUSIC_VOLUME:
if (g_InputDB.menu_left
&& g_Config.audio.music_volume > Music_GetMinVolume()) {
g_Config.audio.music_volume--;
Config_Write();
Music_SetVolume(g_Config.audio.music_volume);
Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS);
sprintf(buf, "\\{icon music} %2d", g_Config.audio.music_volume);
Text_ChangeText(m_Text[TEXT_MUSIC_VOLUME], buf);
} else if (
g_InputDB.menu_right
&& g_Config.audio.music_volume < Music_GetMaxVolume()) {
g_Config.audio.music_volume++;
Config_Write();
Music_SetVolume(g_Config.audio.music_volume);
Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS);
sprintf(buf, "\\{icon music} %2d", g_Config.audio.music_volume);
Text_ChangeText(m_Text[TEXT_MUSIC_VOLUME], buf);
}
Text_Hide(
m_Text[TEXT_LEFT_ARROW],
g_Config.audio.music_volume == Music_GetMinVolume());
Text_Hide(
m_Text[TEXT_RIGHT_ARROW],
g_Config.audio.music_volume == Music_GetMaxVolume());
break;
case TEXT_SOUND_VOLUME:
if (g_InputDB.menu_left
&& g_Config.audio.sound_volume > Sound_GetMinVolume()) {
g_Config.audio.sound_volume--;
Config_Write();
Sound_SetMasterVolume(g_Config.audio.sound_volume);
Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS);
sprintf(buf, "\\{icon sound} %2d", g_Config.audio.sound_volume);
Text_ChangeText(m_Text[TEXT_SOUND_VOLUME], buf);
} else if (
g_InputDB.menu_right
&& g_Config.audio.sound_volume < Sound_GetMaxVolume()) {
g_Config.audio.sound_volume++;
Config_Write();
Sound_SetMasterVolume(g_Config.audio.sound_volume);
Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS);
sprintf(buf, "\\{icon sound} %2d", g_Config.audio.sound_volume);
Text_ChangeText(m_Text[TEXT_SOUND_VOLUME], buf);
}
Text_Hide(
m_Text[TEXT_LEFT_ARROW],
g_Config.audio.sound_volume == Sound_GetMinVolume());
Text_Hide(
m_Text[TEXT_RIGHT_ARROW],
g_Config.audio.sound_volume == Sound_GetMaxVolume());
break;
}
if (g_InputDB.menu_confirm || g_InputDB.menu_back) {
Option_Sound_Shutdown();
void Option_Sound_Draw(INVENTORY_ITEM *const inv_item)
{
M_PRIV *const p = &m_Priv;
if (p->ui != nullptr) {
UI_SoundSettings(p->ui);
}
}
void Option_Sound_Shutdown(void)
{
for (int i = 0; i < TEXT_NUMBER_OF; i++) {
Text_Remove(m_Text[i]);
m_Text[i] = nullptr;
}
M_Shutdown(&m_Priv);
}

View file

@ -3,4 +3,5 @@
#include <libtrx/game/inventory_ring/types.h>
void Option_Sound_Control(INVENTORY_ITEM *inv_item, bool is_busy);
void Option_Sound_Draw(INVENTORY_ITEM *inv_item);
void Option_Sound_Shutdown(void);

View file

@ -49,6 +49,7 @@ typedef struct {
int32_t thickness;
} LIGHTNING;
static bool m_Initialized = false;
static int32_t m_LightningCount = 0;
static LIGHTNING m_LightningTable[MAX_LIGHTNINGS];
static int32_t m_TextureMap[GFX_MAX_TEXTURES] = { GFX_NO_TEXTURE };
@ -385,6 +386,11 @@ static void M_DrawSprite(
bool Output_Init(void)
{
if (m_Initialized) {
return true;
}
m_Initialized = true;
for (int32_t i = 0; i < GFX_MAX_TEXTURES; i++) {
m_TextureMap[i] = GFX_NO_TEXTURE;
m_TextureSurfaces[i] = nullptr;
@ -406,6 +412,11 @@ bool Output_Init(void)
void Output_Shutdown(void)
{
if (!m_Initialized) {
return;
}
m_Initialized = false;
Output_Meshes_Shutdown();
Output_Sprites_Shutdown();
Output_Textures_Shutdown();

View file

@ -81,37 +81,18 @@ static SHELL_ARGS m_Args = {
static const char *m_CurrentGameFlowPath;
static void M_ParseArgs(SHELL_ARGS *out_args);
static void M_ShowHelp(void);
static void M_LoadConfig(void);
static void M_HandleConfigChange(const EVENT *event, void *data);
static void M_ParseArgs(SHELL_ARGS *const out_args)
static void M_ShowHelp(void)
{
const char **args = nullptr;
int32_t arg_count = 0;
Shell_GetCommandLine(&arg_count, &args);
out_args->mod = M_MOD_OG;
for (int32_t i = 0; i < arg_count; i++) {
if (!strcmp(args[i], "-gold")) {
out_args->mod = M_MOD_UB;
}
if (!strcmp(args[i], "-demo_pc")) {
out_args->mod = M_MOD_DEMO_PC;
}
if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level"))
&& i + 1 < arg_count) {
out_args->level_to_play = args[i + 1];
out_args->mod = M_MOD_CUSTOM_LEVEL;
}
if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save"))
&& i + 1 < arg_count) {
if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) {
out_args->save_to_load--;
}
}
}
puts("Currently available options:");
puts("");
puts("-g/--gold: launch The Unfinished Business expansion pack.");
puts(" --demo-pc: launch the PC demo level file.");
puts("-l/--level <PATH>: launch a specific level file.");
puts("-s/--save <NUM>: launch from a specific save slot (starts at 1).");
}
static void M_HandleConfigChange(const EVENT *const event, void *const data)
@ -152,6 +133,8 @@ void Shell_Shutdown(void)
Console_Shutdown();
GameBuf_Shutdown();
Savegame_Shutdown();
GameStringTable_Shutdown();
GF_Shutdown();
Output_Shutdown();
@ -174,10 +157,40 @@ const char *Shell_GetGameFlowPath(void)
return m_ModPaths[m_Args.mod].game_flow_path;
}
void Shell_Main(void)
bool Shell_ParseArgs(const int32_t arg_count, const char **args)
{
M_ParseArgs(&m_Args);
SHELL_ARGS *const out_args = &m_Args;
out_args->mod = M_MOD_OG;
for (int32_t i = 0; i < arg_count; i++) {
if (!strcmp(args[i], "-h") || !strcmp(args[i], "--help")) {
M_ShowHelp();
return false;
}
if (!strcmp(args[i], "-g") || !strcmp(args[i], "--gold")
|| !strcmp(args[i], "-gold")) {
out_args->mod = M_MOD_UB;
}
if (!strcmp(args[i], "--demo-pc") || !strcmp(args[i], "-demo_pc")) {
out_args->mod = M_MOD_DEMO_PC;
}
if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level"))
&& i + 1 < arg_count) {
out_args->level_to_play = args[i + 1];
out_args->mod = M_MOD_CUSTOM_LEVEL;
}
if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save"))
&& i + 1 < arg_count) {
if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) {
out_args->save_to_load--;
}
}
}
return true;
}
int32_t Shell_Main(void)
{
GameString_Init();
EnumMap_Init();
Config_Init();
@ -200,13 +213,17 @@ void Shell_Main(void)
if (!Output_Init()) {
Shell_ExitSystem("Could not initialise video system");
return;
return 1;
}
Screen_Init();
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();
@ -312,6 +329,7 @@ void Shell_Main(void)
if (m_Args.level_to_play != nullptr) {
Memory_FreePointer(&g_GameFlow.level_tables[GFLT_MAIN].levels[0].path);
}
return 0;
}
void Shell_ProcessInput(void)

View file

@ -528,18 +528,6 @@ void Sound_StopAll(void)
Audio_Sample_CloseAll();
}
void Sound_SetMasterVolume(int8_t volume)
{
int8_t raw_volume = volume ? 6 * volume + 3 : 0;
m_MasterVolumeDefault = raw_volume & 0x3F;
m_MasterVolume = raw_volume & 0x3F;
}
int8_t Sound_GetMasterVolume(void)
{
return (m_MasterVolume - 3) / 6;
}
int32_t Sound_GetMinVolume(void)
{
return 0;
@ -550,6 +538,18 @@ int32_t Sound_GetMaxVolume(void)
return 10;
}
void Sound_SetMasterVolume(int32_t volume)
{
int8_t raw_volume = volume ? 6 * volume + 3 : 0;
m_MasterVolumeDefault = raw_volume & 0x3F;
m_MasterVolume = raw_volume & 0x3F;
}
int32_t Sound_GetMasterVolume(void)
{
return (m_MasterVolume - 3) / 6;
}
void Sound_ResetAmbient(void)
{
M_ResetAmbientLoudness();

View file

@ -13,10 +13,6 @@ bool Sound_StopEffect(SOUND_EFFECT_ID sfx_num, const XYZ_32 *pos);
void Sound_UpdateEffects(void);
void Sound_ResetEffects(void);
void Sound_StopAmbientSounds(void);
int8_t Sound_GetMasterVolume(void);
void Sound_SetMasterVolume(int8_t volume);
int32_t Sound_GetMinVolume(void);
int32_t Sound_GetMaxVolume(void);
void Sound_LoadSamples(
size_t num_samples, const char **sample_pointers, size_t *sizes);
int32_t Sound_GetMaxSamples(void);

View file

@ -1,24 +0,0 @@
#include "game/screen.h"
#include <libtrx/config.h>
#include <libtrx/game/ui/common.h>
int32_t UI_GetCanvasWidth(void)
{
return Screen_GetResHeightDownscaled(RSR_GENERIC) * 16 / 9;
}
int32_t UI_GetCanvasHeight(void)
{
return Screen_GetResHeightDownscaled(RSR_GENERIC);
}
float UI_ScaleX(const float x)
{
return Screen_GetRenderScale(x * 0x10000, RSR_GENERIC) / (float)0x10000;
}
float UI_ScaleY(const float y)
{
return Screen_GetRenderScale(y * 0x10000, RSR_GENERIC) / (float)0x10000;
}

View file

@ -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;

View file

@ -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',
@ -261,7 +260,6 @@ sources = [
'game/spawn.c',
'game/stats/common.c',
'game/text.c',
'game/ui/common.c',
'game/ui/dialogs/stats.c',
'game/viewport.c',
'global/enum_map.c',

View file

@ -114,10 +114,10 @@ GF_COMMAND Game_Control(const bool demo_mode)
if (g_OverlayStatus > 0) {
if (g_GameFlow.load_save_disabled) {
g_OverlayStatus = 0;
} else if (g_Input.load) {
g_OverlayStatus = -1;
} else if (g_Input.save) {
g_OverlayStatus = -2;
} else {
g_OverlayStatus = g_Input.save ? -2 : 0;
g_OverlayStatus = g_Input.load ? -1 : 0;
}
} else {
GF_COMMAND gf_cmd;

View file

@ -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;

View file

@ -20,40 +20,6 @@
static BOUNDS_16 m_NullBounds = {};
static BOUNDS_16 m_InterpolatedBounds = {};
static OBJECT_BOUNDS M_ConvertBounds(const int16_t *bounds_in);
static OBJECT_BOUNDS M_ConvertBounds(const int16_t *const bounds_in)
{
// TODO: remove this conversion utility once we gain control over its
// incoming arguments
return (OBJECT_BOUNDS) {
.shift = {
.min = {
.x = bounds_in[0],
.y = bounds_in[2],
.z = bounds_in[4],
},
.max = {
.x = bounds_in[1],
.y = bounds_in[3],
.z = bounds_in[5],
},
},
.rot = {
.min = {
.x = bounds_in[6],
.y = bounds_in[8],
.z = bounds_in[10],
},
.max = {
.x = bounds_in[7],
.y = bounds_in[9],
.z = bounds_in[11],
},
},
};
}
void Item_Control(void)
{
int16_t item_num = Item_GetNextActive();
@ -188,95 +154,6 @@ int16_t Item_GetHeight(const ITEM *const item)
return height;
}
int32_t Item_TestPosition(
const int16_t *const bounds_in, const ITEM *const src_item,
const ITEM *const dst_item)
{
const OBJECT_BOUNDS bounds = M_ConvertBounds(bounds_in);
const XYZ_16 rot = {
.x = dst_item->rot.x - src_item->rot.x,
.y = dst_item->rot.y - src_item->rot.y,
.z = dst_item->rot.z - src_item->rot.z,
};
const XYZ_32 dist = {
.x = dst_item->pos.x - src_item->pos.x,
.y = dst_item->pos.y - src_item->pos.y,
.z = dst_item->pos.z - src_item->pos.z,
};
// clang-format off
if (rot.x < bounds.rot.min.x ||
rot.x > bounds.rot.max.x ||
rot.y < bounds.rot.min.y ||
rot.y > bounds.rot.max.y ||
rot.z < bounds.rot.min.z ||
rot.z > bounds.rot.max.z
) {
return false;
}
// clang-format on
Matrix_PushUnit();
Matrix_Rot16(src_item->rot);
const MATRIX *const m = g_MatrixPtr;
const XYZ_32 shift = {
.x = (dist.x * m->_00 + dist.y * m->_10 + dist.z * m->_20) >> W2V_SHIFT,
.y = (dist.x * m->_01 + dist.y * m->_11 + dist.z * m->_21) >> W2V_SHIFT,
.z = (dist.x * m->_02 + dist.y * m->_12 + dist.z * m->_22) >> W2V_SHIFT,
};
Matrix_Pop();
// clang-format off
return (
shift.x >= bounds.shift.min.x &&
shift.x <= bounds.shift.max.x &&
shift.y >= bounds.shift.min.y &&
shift.y <= bounds.shift.max.y &&
shift.z >= bounds.shift.min.z &&
shift.z <= bounds.shift.max.z
);
// clang-format on
}
void Item_AlignPosition(
const XYZ_32 *const vec, const ITEM *const src_item, ITEM *const dst_item)
{
dst_item->rot = src_item->rot;
Matrix_PushUnit();
Matrix_Rot16(src_item->rot);
const MATRIX *const m = g_MatrixPtr;
const XYZ_32 shift = {
.x = (vec->x * m->_00 + vec->y * m->_01 + vec->z * m->_02) >> W2V_SHIFT,
.y = (vec->x * m->_10 + vec->y * m->_11 + vec->z * m->_12) >> W2V_SHIFT,
.z = (vec->x * m->_20 + vec->y * m->_21 + vec->z * m->_22) >> W2V_SHIFT,
};
Matrix_Pop();
const XYZ_32 new_pos = {
.x = src_item->pos.x + shift.x,
.y = src_item->pos.y + shift.y,
.z = src_item->pos.z + shift.z,
};
int16_t room_num = dst_item->room_num;
const SECTOR *const sector =
Room_GetSector(new_pos.x, new_pos.y, new_pos.z, &room_num);
const int32_t height =
Room_GetHeight(sector, new_pos.x, new_pos.y, new_pos.z);
const int32_t ceiling =
Room_GetCeiling(sector, new_pos.x, new_pos.y, new_pos.z);
if (ABS(height - dst_item->pos.y) > STEP_L
|| ABS(ceiling - dst_item->pos.y) < LARA_HEIGHT) {
return;
}
dst_item->pos.x = new_pos.x;
dst_item->pos.y = new_pos.y;
dst_item->pos.z = new_pos.z;
}
int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frames[], int32_t *rate)
{
const ANIM *const anim = Item_GetAnim(item);
@ -364,7 +241,7 @@ const BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *const item)
ANIM_FRAME *Item_GetBestFrame(const ITEM *const item)
{
ANIM_FRAME *frames[2];
int32_t rate;
int32_t rate = 0;
const int32_t frac = Item_GetFrames(item, frames, &rate);
return frames[(frac > rate / 2) ? 1 : 0];
}

View file

@ -8,10 +8,6 @@ void Item_Control(void);
void Item_ClearKilled(void);
void Item_ShiftCol(ITEM *item, COLL_INFO *coll);
void Item_UpdateRoom(ITEM *item, int32_t height);
int32_t Item_TestPosition(
const int16_t *bounds, const ITEM *src_item, const ITEM *dst_item);
void Item_AlignPosition(
const XYZ_32 *vec, const ITEM *src_item, ITEM *dst_item);
int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate);
bool Item_IsNearItem(const ITEM *item, const XYZ_32 *pos, int32_t distance);

View file

@ -473,6 +473,13 @@ void Lara_Draw_I(
Matrix_Rot16_ID(mesh_rots_1[LM_UARM_R], mesh_rots_2[LM_UARM_R]);
Output_DrawObjectMesh_I(g_Lara.mesh_ptrs[LM_UARM_R], clip);
// NOTE: gcc wrongly complains about mesh_rots_1 possibly being NULL.
// While this is not the case, it's curious how the pistols subtract the
// frame_base from g_Lara.*_arm.frame_num to access the mesh_rots, and the
// rifles do not.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
M_DrawBodyPart(LM_LARM_R, bone, mesh_rots_1, mesh_rots_2, clip);
M_DrawBodyPart(LM_HAND_R, bone, mesh_rots_1, mesh_rots_2, clip);
@ -486,6 +493,8 @@ void Lara_Draw_I(
M_DrawBodyPart(LM_LARM_L, bone, mesh_rots_1, mesh_rots_2, clip);
M_DrawBodyPart(LM_HAND_L, bone, mesh_rots_1, mesh_rots_2, clip);
#pragma GCC diagnostic pop
if (g_Lara.right_arm.flash_gun) {
*g_MatrixPtr = saved_matrix;
Gun_DrawFlash(gun_type, clip);

View file

@ -143,22 +143,24 @@ static int32_t M_CompareSampleOffsets(const void *const a, const void *const b)
static void M_InitialiseSoundEffects(const char *file_name)
{
BENCHMARK benchmark = Benchmark_Start();
LEVEL_INFO *info = nullptr;
SAMPLE_ENTRY *entries = nullptr;
if (file_name == nullptr) {
file_name = g_GameFlow.settings.sfx_path;
}
const char *full_path =
File_GetFullPath(file_name == nullptr ? DEFAULT_SFX_PATH : file_name);
LOG_DEBUG("Loading samples from %s", full_path);
MYFILE *const fp = File_Open(full_path, FILE_OPEN_READ);
Memory_FreePointer(&full_path);
if (fp == nullptr) {
Shell_ExitSystemFmt("Could not open %s file", file_name);
goto finish;
}
LEVEL_INFO *const info = Level_GetInfo();
info = Level_GetInfo();
const int32_t sample_count = info->samples.offset_count;
entries = Memory_Alloc(sizeof(SAMPLE_ENTRY) * sample_count);
for (int32_t i = 0; i < sample_count; i++) {

View file

@ -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);

View file

@ -10,33 +10,37 @@
#include "game/sound.h"
#include "global/vars.h"
#include <libtrx/game/lara.h>
#define EXPLOSION_START_FRAME 76
#define EXPLOSION_END_FRAME 99
#define EXPLOSION_ACTION_FRAME 80
static XYZ_32 m_DetonatorPosition = { .x = 0, .y = 0, .z = 0 };
static int16_t m_GongBounds[12] = {
-WALL_L / 2,
+WALL_L,
-100,
+100,
-WALL_L / 2 - 300,
-WALL_L / 2 + 100,
-30 * DEG_1,
+30 * DEG_1,
+0,
+0,
+0,
+0,
static const OBJECT_BOUNDS m_GongBounds = {
.shift = {
.min = { .x = -WALL_L / 2, .y = -100, .z = -WALL_L / 2 - 300, },
.max = { .x = +WALL_L, .y = +100, .z = -WALL_L / 2 + 100, },
},
.rot = {
.min = { .x = -30 * DEG_1, .y = 0, .z = 0, },
.max = { .x = +30 * DEG_1, .y = 0, .z = 0, },
},
};
static const OBJECT_BOUNDS *M_Bounds(void);
static void M_CreateGongBonger(ITEM *lara_item);
static void M_Setup1(OBJECT *obj);
static void M_Setup2(OBJECT *obj);
static void M_Control(int16_t item_num);
static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);
static const OBJECT_BOUNDS *M_Bounds(void)
{
return &m_GongBounds;
}
static void M_CreateGongBonger(ITEM *const lara_item)
{
const int16_t item_gong_bonger_num = Item_Create();
@ -64,12 +68,14 @@ static void M_CreateGongBonger(ITEM *const lara_item)
static void M_Setup1(OBJECT *const obj)
{
obj->collision_func = M_Collision;
obj->bounds_func = M_Bounds;
}
static void M_Setup2(OBJECT *const obj)
{
obj->collision_func = M_Collision;
obj->control_func = M_Control;
obj->bounds_func = Pickup_Bounds;
obj->save_flags = 1;
obj->save_anim = 1;
}
@ -102,6 +108,7 @@ static void M_Collision(
}
ITEM *const item = Item_Get(item_num);
const OBJECT *const obj = Object_Get(item->object_id);
const XYZ_16 old_rot = item->rot;
const int16_t x = item->rot.x;
const int16_t y = item->rot.y;
@ -117,16 +124,11 @@ static void M_Collision(
goto normal_collision;
}
if (item->object_id == O_DETONATOR_2) {
if (!Item_TestPosition(g_PickupBounds, item, lara_item)) {
goto normal_collision;
}
} else {
if (!Item_TestPosition(m_GongBounds, item, lara_item)) {
goto normal_collision;
} else {
item->rot = old_rot;
}
if (!Lara_TestPosition(item, obj->bounds_func())) {
goto normal_collision;
}
if (item->object_id == O_DETONATOR_1) {
item->rot = old_rot;
}
if (g_Inv_Chosen == NO_OBJECT) {
@ -138,7 +140,7 @@ static void M_Collision(
}
Inv_RemoveItem(O_KEY_OPTION_2);
Item_AlignPosition(&m_DetonatorPosition, item, lara_item);
Lara_AlignPosition(item, &m_DetonatorPosition);
Item_SwitchToObjAnim(lara_item, LA_EXTRA_BREATH, 0, O_LARA_EXTRA);
lara_item->current_anim_state = LA_EXTRA_BREATH;
if (item->object_id == O_DETONATOR_2) {

View file

@ -1,222 +0,0 @@
#include "game/box.h"
#include "game/items.h"
#include "game/objects/common.h"
#include "game/room.h"
#include "global/vars.h"
#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;
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 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 Door_Shut(DOORPOS_DATA *const d)
{
SECTOR *const sector = d->sector;
if (d->sector == nullptr) {
return;
}
sector->idx = 0;
sector->box = NO_BOX;
sector->ceiling.height = NO_HEIGHT;
sector->floor.height = NO_HEIGHT;
sector->floor.tilt = 0;
sector->ceiling.tilt = 0;
sector->portal_room.sky = NO_ROOM_NEG;
sector->portal_room.pit = NO_ROOM_NEG;
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 Door_Open(DOORPOS_DATA *const d)
{
if (d->sector == nullptr) {
return;
}
*d->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_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 = door_pos->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 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_flags = 1;
obj->save_anim = 1;
}
static void M_Initialise(const int16_t item_num)
{
ITEM *const item = Item_Get(item_num);
DOOR_DATA *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 == NO_ROOM_NEG) {
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;
Door_Shut(&door->d1);
Door_Shut(&door->d1flip);
if (room_num == NO_ROOM) {
door->d2.sector = nullptr;
door->d2flip.sector = nullptr;
} else {
room = Room_Get(room_num);
M_InitialisePortal(room, item, 0, 0, &door->d2);
if (room->flipped_room == NO_ROOM_NEG) {
door->d2flip.sector = nullptr;
} else {
room = Room_Get(room->flipped_room);
M_InitialisePortal(room, item, 0, 0, &door->d2flip);
}
Door_Shut(&door->d2);
Door_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 *const data = item->data;
if (Item_IsTriggerActive(item)) {
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);
}
} 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);
}
}
Item_Animate(item);
}
void Door_Collision(
const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll)
{
ITEM *const item = Item_Get(item_num);
if (!Item_TestBoundsCollide(item, lara_item, coll->radius)) {
return;
}
if (!Collide_TestCollision(item, lara_item)) {
return;
}
if (coll->enable_baddie_push) {
Lara_Push(
item, coll,
item->current_anim_state != item->goal_anim_state ? coll->enable_hit
: 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)

View file

@ -6,6 +6,7 @@ static void M_Setup(OBJECT *obj);
static void M_Setup(OBJECT *const obj)
{
obj->collision_func = Pickup_Collision;
obj->bounds_func = Pickup_Bounds;
obj->control_func = Flare_Control;
obj->draw_func = Flare_DrawInAir;
obj->save_position = 1;

View file

@ -17,29 +17,29 @@ static XYZ_32 m_KeyholePosition = {
.z = WALL_L / 2 - LARA_RADIUS - 50,
};
static int16_t m_KeyholeBounds[12] = {
// clang-format off
-200,
+200,
+0,
+0,
+WALL_L / 2 - 200,
+WALL_L / 2,
-10 * DEG_1,
+10 * DEG_1,
-30 * DEG_1,
+30 * DEG_1,
-10 * DEG_1,
+10 * DEG_1,
// clang-format on
static const OBJECT_BOUNDS m_KeyHoleBounds = {
.shift = {
.min = { .x = -200, .y = +0, .z = +WALL_L / 2 - 200, },
.max = { .x = +200, .y = +0, .z = +WALL_L / 2, },
},
.rot = {
.min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, },
.max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, },
},
};
static const OBJECT_BOUNDS *M_Bounds(void);
static void M_Consume(
ITEM *lara_item, ITEM *keyhole_item, GAME_OBJECT_ID key_obj_id);
static void M_Refuse(const ITEM *lara_item);
static void M_Setup(OBJECT *obj);
static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);
static const OBJECT_BOUNDS *M_Bounds(void)
{
return &m_KeyHoleBounds;
}
static void M_Refuse(const ITEM *const lara_item)
{
if (lara_item->pos.x == g_InteractPosition.x
@ -57,7 +57,7 @@ static void M_Consume(
const GAME_OBJECT_ID key_obj_id)
{
Inv_RemoveItem(key_obj_id);
Item_AlignPosition(&m_KeyholePosition, keyhole_item, lara_item);
Lara_AlignPosition(keyhole_item, &m_KeyholePosition);
lara_item->goal_anim_state = LS_USE_KEY;
do {
Lara_Animate(lara_item);
@ -71,6 +71,7 @@ static void M_Consume(
static void M_Setup(OBJECT *const obj)
{
obj->collision_func = M_Collision;
obj->bounds_func = M_Bounds;
obj->save_flags = 1;
}
@ -82,12 +83,13 @@ static void M_Collision(
}
ITEM *const item = Item_Get(item_num);
const OBJECT *const obj = Object_Get(item->object_id);
if ((g_Inv_Chosen == NO_OBJECT && !g_Input.action)
|| g_Lara.gun_status != LGS_ARMLESS || lara_item->gravity) {
return;
}
if (!Item_TestPosition(m_KeyholeBounds, item, lara_item)) {
if (!Lara_TestPosition(item, obj->bounds_func())) {
return;
}

View file

@ -20,21 +20,18 @@ typedef enum {
MOVABLE_BLOCK_STATE_PULL = 3,
} MOVABLE_BLOCK_STATE;
static int16_t m_MovableBlockBounds[12] = {
-300,
+300,
+0,
+0,
-WALL_L / 2 - LARA_RADIUS - 80,
-WALL_L / 2,
-10 * DEG_1,
+10 * DEG_1,
-30 * DEG_1,
+30 * DEG_1,
-10 * DEG_1,
+10 * DEG_1,
static const OBJECT_BOUNDS m_MovableBlockBounds = {
.shift = {
.min = { .x = -300, .y = 0, .z = -WALL_L / 2 - LARA_RADIUS - 80, },
.max = { .x = +300, .y = 0, .z = -WALL_L / 2, },
},
.rot = {
.min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, },
.max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, },
},
};
static const OBJECT_BOUNDS *M_Bounds(void);
static bool M_TestDestination(const ITEM *item, int32_t block_height);
static bool M_TestPush(
const ITEM *item, int32_t block_height, DIRECTION quadrant);
@ -47,6 +44,11 @@ static void M_Draw(const ITEM *item);
static void M_Control(int16_t item_num);
static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);
static const OBJECT_BOUNDS *M_Bounds(void)
{
return &m_MovableBlockBounds;
}
static bool M_TestDestination(
const ITEM *const item, const int32_t block_height)
{
@ -199,6 +201,7 @@ static void M_Setup(OBJECT *const obj)
obj->handle_save_func = M_HandleSave;
obj->control_func = M_Control;
obj->collision_func = M_Collision;
obj->bounds_func = M_Bounds;
obj->draw_func = M_Draw;
obj->save_position = 1;
obj->save_flags = 1;
@ -303,7 +306,7 @@ static void M_Collision(
break;
}
if (!Item_TestPosition(m_MovableBlockBounds, item, lara_item)) {
if (!Lara_TestPosition(item, obj->bounds_func())) {
return;
}
@ -346,7 +349,7 @@ static void M_Collision(
} else if (
Item_TestAnimEqual(lara_item, LA_PUSHABLE_GRAB)
&& Item_TestFrameEqual(lara_item, LF_PPREADY)) {
if (!Item_TestPosition(m_MovableBlockBounds, item, lara_item)) {
if (!Lara_TestPosition(item, obj->bounds_func())) {
return;
}

View file

@ -26,41 +26,29 @@
#define LF_PICKUP_FLARE_UW 20
#define LF_PICKUP_UW 18
int16_t g_PickupBounds[12] = {
// clang-format off
-WALL_L / 4,
+WALL_L / 4,
-100,
+100,
-WALL_L / 4,
+WALL_L / 4,
-10 * DEG_1,
+10 * DEG_1,
+0,
+0,
+0,
+0,
// clang-format on
};
static XYZ_32 m_PickupPosition = { .x = 0, .y = 0, .z = -100 };
static XYZ_32 m_PickupPositionUW = { .x = 0, .y = -200, .z = -350 };
static int16_t m_PickupBoundsUW[12] = {
// clang-format off
-WALL_L / 2,
+WALL_L / 2,
-WALL_L / 2,
+WALL_L / 2,
-WALL_L / 2,
+WALL_L / 2,
-45 * DEG_1,
+45 * DEG_1,
-45 * DEG_1,
+45 * DEG_1,
-45 * DEG_1,
+45 * DEG_1,
// clang-format on
static const OBJECT_BOUNDS m_PickUpBounds = {
.shift = {
.min = { .x = -WALL_L / 4, .y = -100, .z = -WALL_L / 4, },
.max = { .x = +WALL_L / 4, .y = +100, .z = +WALL_L / 4, },
},
.rot = {
.min = { .x = -10 * DEG_1, .y = 0, .z = 0, },
.max = { .x = +10 * DEG_1, .y = 0, .z = 0, },
},
};
static const OBJECT_BOUNDS m_PickUpBoundsUW = {
.shift = {
.min = { .x = -WALL_L / 2, .y = -WALL_L / 2, .z = -WALL_L / 2, },
.max = { .x = +WALL_L / 2, .y = +WALL_L / 2, .z = +WALL_L / 2, },
},
.rot = {
.min = { .x = -45 * DEG_1, .y = -45 * DEG_1, .z = -45 * DEG_1, },
.max = { .x = +45 * DEG_1, .y = +45 * DEG_1, .z = +45 * DEG_1, },
},
};
static void M_DoPickup(int16_t item_num);
@ -107,13 +95,14 @@ static void M_DoFlarePickup(const int16_t item_num)
static void M_DoAboveWater(const int16_t item_num, ITEM *const lara_item)
{
ITEM *const item = Item_Get(item_num);
const OBJECT *const obj = Object_Get(item->object_id);
const XYZ_16 old_rot = item->rot;
item->rot.x = 0;
item->rot.y = lara_item->rot.y;
item->rot.z = 0;
if (!Item_TestPosition(g_PickupBounds, item, lara_item)) {
if (!Lara_TestPosition(item, obj->bounds_func())) {
goto cleanup;
}
@ -145,7 +134,7 @@ static void M_DoAboveWater(const int16_t item_num, ITEM *const lara_item)
lara_item->goal_anim_state = LS_STOP;
g_Lara.gun_status = LGS_HANDS_BUSY;
} else {
Item_AlignPosition(&m_PickupPosition, item, lara_item);
Lara_AlignPosition(item, &m_PickupPosition);
lara_item->goal_anim_state = LS_PICKUP;
do {
Lara_Animate(lara_item);
@ -163,13 +152,14 @@ cleanup:
static void M_DoUnderwater(const int16_t item_num, ITEM *const lara_item)
{
ITEM *const item = Item_Get(item_num);
const OBJECT *const obj = Object_Get(item->object_id);
const XYZ_16 old_rot = item->rot;
item->rot.x = -25 * DEG_1;
item->rot.y = lara_item->rot.y;
item->rot.z = 0;
if (!Item_TestPosition(m_PickupBoundsUW, item, lara_item)) {
if (!Lara_TestPosition(item, obj->bounds_func())) {
goto cleanup;
}
@ -224,6 +214,7 @@ static void M_Setup(OBJECT *const obj)
obj->handle_save_func = M_HandleSave;
obj->activate_func = M_Activate;
obj->collision_func = Pickup_Collision;
obj->bounds_func = Pickup_Bounds;
obj->draw_func = M_Draw;
obj->save_position = 1;
obj->save_flags = 1;
@ -343,6 +334,15 @@ static void M_Draw(const ITEM *const item)
Matrix_Pop();
}
const OBJECT_BOUNDS *Pickup_Bounds(void)
{
if (g_Lara.water_status == LWS_UNDERWATER) {
return &m_PickUpBoundsUW;
} else {
return &m_PickUpBounds;
}
}
void Pickup_Collision(
const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll)
{

View file

@ -2,7 +2,6 @@
#include "global/types.h"
extern int16_t g_PickupBounds[];
void Pickup_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);
bool Pickup_Trigger(int16_t item_num);
const OBJECT_BOUNDS *Pickup_Bounds(void);

View file

@ -17,23 +17,18 @@ static XYZ_32 m_PuzzleHolePosition = {
.z = WALL_L / 2 - LARA_RADIUS - 85,
};
static int16_t m_PuzzleHoleBounds[12] = {
// clang-format off
-200,
+200,
+0,
+0,
+WALL_L / 2 - 200,
+WALL_L / 2,
-10 * DEG_1,
+10 * DEG_1,
-30 * DEG_1,
+30 * DEG_1,
-10 * DEG_1,
+10 * DEG_1,
// clang-format on
static const OBJECT_BOUNDS m_PuzzleHoleBounds = {
.shift = {
.min = { .x = -200, .y = 0, .z = WALL_L / 2 - 200, },
.max = { .x = +200, .y = 0, .z = WALL_L / 2, },
},
.rot = {
.min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, },
.max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, },
},
};
static const OBJECT_BOUNDS *M_Bounds(void);
static void M_Refuse(const ITEM *lara_item);
static void M_Consume(
ITEM *lara_item, ITEM *puzzle_hole_item, GAME_OBJECT_ID puzzle_obj_id);
@ -43,6 +38,11 @@ static void M_SetupDone(OBJECT *obj);
static void M_HandleSave(ITEM *item, SAVEGAME_STAGE stage);
static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);
static const OBJECT_BOUNDS *M_Bounds(void)
{
return &m_PuzzleHoleBounds;
}
static void M_Refuse(const ITEM *const lara_item)
{
if (lara_item->pos.x != g_InteractPosition.x
@ -58,7 +58,7 @@ static void M_Consume(
const GAME_OBJECT_ID puzzle_obj_id)
{
Inv_RemoveItem(puzzle_obj_id);
Item_AlignPosition(&m_PuzzleHolePosition, puzzle_hole_item, lara_item);
Lara_AlignPosition(puzzle_hole_item, &m_PuzzleHolePosition);
lara_item->goal_anim_state = LS_USE_PUZZLE;
do {
Lara_Animate(lara_item);
@ -82,6 +82,7 @@ static void M_SetupEmpty(OBJECT *const obj)
{
obj->collision_func = M_Collision;
obj->handle_save_func = M_HandleSave;
obj->bounds_func = M_Bounds;
obj->save_flags = 1;
}
@ -103,10 +104,11 @@ static void M_Collision(
const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll)
{
ITEM *const item = Item_Get(item_num);
const OBJECT *const obj = Object_Get(item->object_id);
if (lara_item->current_anim_state != LS_STOP) {
if (lara_item->current_anim_state != LS_USE_PUZZLE
|| !Item_TestPosition(m_PuzzleHoleBounds, item, lara_item)
|| !Lara_TestPosition(item, obj->bounds_func())
|| !Item_TestFrameEqual(lara_item, LF_USE_PUZZLE)) {
return;
}
@ -120,7 +122,7 @@ static void M_Collision(
return;
}
if (!Item_TestPosition(m_PuzzleHoleBounds, item, lara_item)) {
if (!Lara_TestPosition(item, obj->bounds_func())) {
return;
}

View file

@ -17,40 +17,30 @@ static XYZ_32 g_PushSwitchPosition = { .x = 0, .y = 0, .z = 292 };
static XYZ_32 m_AirlockPosition = { .x = 0, .y = 0, .z = 212 };
static XYZ_32 m_SwitchUWPosition = { .x = 0, .y = 0, .z = 108 };
static int16_t m_SwitchBounds[12] = {
// clang-format off
-220,
+220,
+0,
+0,
+WALL_L / 2 - 220,
+WALL_L / 2,
-10 * DEG_1,
+10 * DEG_1,
-30 * DEG_1,
+30 * DEG_1,
-10 * DEG_1,
+10 * DEG_1,
// clang-format on
static const OBJECT_BOUNDS m_SwitchBounds = {
.shift = {
.min = { .x = -220, .y = +0, .z = +WALL_L / 2 - 220, },
.max = { .x = +220, .y = +0, .z = +WALL_L / 2, },
},
.rot = {
.min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, },
.max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, },
},
};
static int16_t m_SwitchBoundsUW[12] = {
// clang-format off
-WALL_L,
+WALL_L,
-WALL_L,
+WALL_L,
-WALL_L,
+WALL_L / 2,
-80 * DEG_1,
+80 * DEG_1,
-80 * DEG_1,
+80 * DEG_1,
-80 * DEG_1,
+80 * DEG_1,
// clang-format on
static const OBJECT_BOUNDS m_SwitchBoundsUW = {
.shift = {
.min = { .x = -WALL_L, .y = -WALL_L, .z = -WALL_L, },
.max = { .x = +WALL_L, .y = +WALL_L, .z = +WALL_L / 2, },
},
.rot = {
.min = { .x = -80 * DEG_1, .y = -80 * DEG_1, .z = -80 * DEG_1, },
.max = { .x = +80 * DEG_1, .y = +80 * DEG_1, .z = +80 * DEG_1, },
},
};
static const OBJECT_BOUNDS *M_Bounds(void);
static const OBJECT_BOUNDS *M_BoundsUW(void);
static void M_AlignLara(ITEM *lara_item, ITEM *switch_item);
static void M_SwitchOn(ITEM *switch_item, ITEM *lara_item);
static void M_SwitchOff(ITEM *switch_item, ITEM *lara_item);
@ -62,19 +52,29 @@ static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);
static void M_CollisionUW(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);
static void M_Control(int16_t item_num);
static const OBJECT_BOUNDS *M_Bounds(void)
{
return &m_SwitchBounds;
}
static const OBJECT_BOUNDS *M_BoundsUW(void)
{
return &m_SwitchBoundsUW;
}
static void M_AlignLara(ITEM *const lara_item, ITEM *const switch_item)
{
switch (switch_item->object_id) {
case O_SWITCH_TYPE_AIRLOCK:
Item_AlignPosition(&m_AirlockPosition, switch_item, lara_item);
Lara_AlignPosition(switch_item, &m_AirlockPosition);
break;
case O_SWITCH_TYPE_SMALL:
Item_AlignPosition(&g_SmallSwitchPosition, switch_item, lara_item);
Lara_AlignPosition(switch_item, &g_SmallSwitchPosition);
break;
case O_SWITCH_TYPE_BUTTON:
Item_AlignPosition(&g_PushSwitchPosition, switch_item, lara_item);
Lara_AlignPosition(switch_item, &g_PushSwitchPosition);
break;
}
}
@ -139,28 +139,32 @@ static void M_Setup(OBJECT *const obj)
{
M_SetupBase(obj);
obj->collision_func = M_Collision;
obj->bounds_func = M_Bounds;
}
static void M_SetupPushButton(OBJECT *const obj)
{
M_Setup(obj);
obj->enable_interpolation = false;
obj->bounds_func = M_Bounds;
}
static void M_SetupUW(OBJECT *const obj)
{
M_SetupBase(obj);
obj->collision_func = M_CollisionUW;
obj->bounds_func = M_BoundsUW;
}
static void M_Collision(
const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll)
{
ITEM *const item = Item_Get(item_num);
const OBJECT *const obj = Object_Get(item->object_id);
if (!g_Input.action || item->status != IS_INACTIVE
|| g_Lara.gun_status != LGS_ARMLESS || lara_item->gravity
|| lara_item->current_anim_state != LS_STOP
|| !Item_TestPosition(m_SwitchBounds, item, lara_item)) {
|| !Lara_TestPosition(item, obj->bounds_func())) {
return;
}
@ -193,6 +197,7 @@ static void M_CollisionUW(
const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll)
{
ITEM *const item = Item_Get(item_num);
const OBJECT *const obj = Object_Get(item->object_id);
if (!g_Input.action || item->status != IS_INACTIVE
|| g_Lara.water_status != LWS_UNDERWATER
@ -201,7 +206,7 @@ static void M_CollisionUW(
return;
}
if (!Item_TestPosition(m_SwitchBoundsUW, item, lara_item)) {
if (!Lara_TestPosition(item, obj->bounds_func())) {
return;
}

View file

@ -22,33 +22,35 @@ static XYZ_32 m_ZiplineHandlePosition = {
.y = 0,
.z = WALL_L / 2 - 141,
};
static int16_t m_ZiplineHandleBounds[12] = {
// clang-format off
-WALL_L / 4,
+WALL_L / 4,
-100,
+100,
+WALL_L / 4,
+WALL_L / 2,
+0,
+0,
-25 * DEG_1,
+25 * DEG_1,
+0,
+0,
// clang-format on
static const OBJECT_BOUNDS m_ZiplineHandleBounds = {
.shift = {
.min = { .x = -WALL_L / 4, .y = -100, .z = +WALL_L / 4, },
.max = { .x = +WALL_L / 4, .y = +100, .z = +WALL_L / 2, },
},
.rot = {
.min = { .x = +0, .y = -25 * DEG_1, .z = +0, },
.max = { .x = +0, .y = +25 * DEG_1, .z = +0, },
},
};
static const OBJECT_BOUNDS *M_Bounds(void);
static void M_Setup(OBJECT *obj);
static void M_Initialise(int16_t item_num);
static void M_Control(int16_t item_num);
static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll);
static const OBJECT_BOUNDS *M_Bounds(void)
{
return &m_ZiplineHandleBounds;
}
static void M_Setup(OBJECT *const obj)
{
obj->initialise_func = M_Initialise;
obj->control_func = M_Control;
obj->collision_func = M_Collision;
obj->bounds_func = M_Bounds;
obj->save_position = 1;
obj->save_flags = 1;
obj->save_anim = 1;
@ -149,11 +151,12 @@ static void M_Collision(
return;
}
if (!Item_TestPosition(m_ZiplineHandleBounds, item, lara_item)) {
const OBJECT *const obj = Object_Get(item->object_id);
if (!Lara_TestPosition(item, obj->bounds_func())) {
return;
}
Item_AlignPosition(&m_ZiplineHandlePosition, item, lara_item);
Lara_AlignPosition(item, &m_ZiplineHandlePosition);
g_Lara.gun_status = LGS_HANDS_BUSY;
lara_item->goal_anim_state = LS_ZIPLINE;

View file

@ -44,10 +44,11 @@ static void M_HandleLayoutChange(const EVENT *event, void *user_data)
const M_PRIV *const p = user_data;
switch (p->ui.state.backend) {
case INPUT_BACKEND_KEYBOARD:
g_Config.input.keyboard_layout = p->ui.state.active_layout;
g_Config.input.keyboard_layout = p->ui.state.editor_state.active_layout;
break;
case INPUT_BACKEND_CONTROLLER:
g_Config.input.controller_layout = p->ui.state.active_layout;
g_Config.input.controller_layout =
p->ui.state.editor_state.active_layout;
break;
default:
break;

View file

@ -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);

View file

@ -1,132 +1,48 @@
#include "game/game_string.h"
#include "game/input.h"
#include "game/inventory_ring.h"
#include "game/music.h"
#include "game/option/option.h"
#include "game/sound.h"
#include "game/text.h"
#include "global/vars.h"
#include <libtrx/config.h>
#include <libtrx/utils.h>
#include <libtrx/game/ui.h>
#include <stdio.h>
typedef struct {
UI_SOUND_SETTINGS_STATE *ui;
} M_PRIV;
static TEXTSTRING *m_SoundText[4];
static M_PRIV m_Priv = {};
static void M_InitText(void);
static void M_ShutdownText(void);
static void M_InitText(void)
static void M_Init(M_PRIV *const p)
{
CLAMPG(g_Config.audio.music_volume, 10);
CLAMPG(g_Config.audio.sound_volume, 10);
p->ui = UI_SoundSettings_Init();
}
char text[32];
sprintf(text, "\\{icon music} %2d", g_Config.audio.music_volume);
m_SoundText[0] = Text_Create(0, 0, text);
Text_AddBackground(m_SoundText[0], 128, 0, 0, 0, TS_REQUESTED);
Text_AddOutline(m_SoundText[0], TS_REQUESTED);
sprintf(text, "\\{icon sound} %2d", g_Config.audio.sound_volume);
m_SoundText[1] = Text_Create(0, 25, text);
m_SoundText[2] = Text_Create(0, -32, " ");
Text_AddBackground(m_SoundText[2], 140, 85, 0, 0, TS_BACKGROUND);
Text_AddOutline(m_SoundText[2], TS_BACKGROUND);
m_SoundText[3] = Text_Create(0, -30, GS(SOUND_SET_VOLUMES));
Text_AddBackground(m_SoundText[3], 136, 0, 0, 0, TS_HEADING);
Text_AddOutline(m_SoundText[3], TS_HEADING);
for (int32_t i = 0; i < 4; i++) {
Text_CentreH(m_SoundText[i], true);
Text_CentreV(m_SoundText[i], true);
static void M_Shutdown(M_PRIV *const p)
{
if (p->ui != nullptr) {
UI_SoundSettings_Free(p->ui);
p->ui = nullptr;
}
}
static void M_ShutdownText(void)
void Option_Sound_Control(INVENTORY_ITEM *const inv_item, const bool is_busy)
{
for (int32_t i = 0; i < 4; i++) {
Text_Remove(m_SoundText[i]);
m_SoundText[i] = nullptr;
M_PRIV *const p = &m_Priv;
if (is_busy) {
return;
}
if (p->ui == nullptr) {
M_Init(p);
}
UI_SoundSettings_Control(p->ui);
}
void Option_Sound_Draw(INVENTORY_ITEM *const inv_item)
{
M_PRIV *const p = &m_Priv;
if (p->ui != nullptr) {
UI_SoundSettings(p->ui);
}
}
void Option_Sound_Shutdown(void)
{
M_ShutdownText();
}
void Option_Sound_Control(INVENTORY_ITEM *const item, const bool is_busy)
{
if (is_busy) {
return;
}
char text[32];
if (m_SoundText[0] == nullptr) {
M_InitText();
}
if (g_InputDB.menu_up && g_SoundOptionLine > 0) {
Text_RemoveOutline(m_SoundText[g_SoundOptionLine]);
Text_RemoveBackground(m_SoundText[g_SoundOptionLine]);
g_SoundOptionLine--;
Text_AddBackground(
m_SoundText[g_SoundOptionLine], 128, 0, 0, 0, TS_REQUESTED);
Text_AddOutline(m_SoundText[g_SoundOptionLine], TS_REQUESTED);
}
if (g_InputDB.menu_down && g_SoundOptionLine < 1) {
Text_RemoveOutline(m_SoundText[g_SoundOptionLine]);
Text_RemoveBackground(m_SoundText[g_SoundOptionLine]);
g_SoundOptionLine++;
Text_AddBackground(
m_SoundText[g_SoundOptionLine], 128, 0, 0, 0, TS_REQUESTED);
Text_AddOutline(m_SoundText[g_SoundOptionLine], TS_REQUESTED);
}
if (g_SoundOptionLine) {
bool changed = false;
if (g_InputDB.menu_left && g_Config.audio.sound_volume > 0) {
g_Config.audio.sound_volume--;
changed = true;
} else if (g_InputDB.menu_right && g_Config.audio.sound_volume < 10) {
g_Config.audio.sound_volume++;
changed = true;
}
if (changed) {
sprintf(text, "\\{icon sound} %2d", g_Config.audio.sound_volume);
Text_ChangeText(m_SoundText[1], text);
Sound_SetMasterVolume(g_Config.audio.sound_volume);
Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS);
}
} else {
bool changed = false;
if (g_InputDB.menu_left && g_Config.audio.music_volume > 0) {
g_Config.audio.music_volume--;
changed = true;
} else if (g_InputDB.menu_right && g_Config.audio.music_volume < 10) {
g_Config.audio.music_volume++;
changed = true;
}
if (changed) {
sprintf(text, "\\{icon music} %2d", g_Config.audio.music_volume);
Text_ChangeText(m_SoundText[0], text);
Music_SetVolume(g_Config.audio.music_volume);
Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS);
}
}
if (g_InputDB.menu_confirm || g_InputDB.menu_back) {
Option_Sound_Shutdown();
}
}
void Option_Sound_Draw(INVENTORY_ITEM *const item)
{
M_Shutdown(&m_Priv);
}

View file

@ -45,7 +45,6 @@ static RENDERER *M_GetRenderer(void)
} else if (g_Config.rendering.render_mode == RM_HARDWARE) {
r = &m_Renderer_HW;
}
ASSERT(r != nullptr);
return r;
}
@ -124,7 +123,6 @@ void Render_Init(void)
void Render_Shutdown(void)
{
LOG_DEBUG("");
RENDERER *const r = M_GetRenderer();
if (r != nullptr) {
r->Close(r);

View file

@ -99,7 +99,7 @@ static void M_HandleQuit(void);
static void M_ConfigureOpenGL(void);
static bool M_CreateGameWindow(void);
static void M_ParseArgs(SHELL_ARGS *out_args);
static void M_ShowHelp(void);
static void M_LoadConfig(void);
static void M_HandleConfigChange(const EVENT *event, void *data);
@ -342,30 +342,13 @@ static bool M_CreateGameWindow(void)
return true;
}
static void M_ParseArgs(SHELL_ARGS *const out_args)
static void M_ShowHelp(void)
{
const char **args = nullptr;
int32_t arg_count = 0;
Shell_GetCommandLine(&arg_count, &args);
out_args->mod = M_MOD_OG;
for (int32_t i = 0; i < arg_count; i++) {
if (!strcmp(args[i], "-gold")) {
out_args->mod = M_MOD_GM;
}
if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level"))
&& i + 1 < arg_count) {
out_args->level_to_play = args[i + 1];
out_args->mod = M_MOD_CUSTOM_LEVEL;
}
if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save"))
&& i + 1 < arg_count) {
if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) {
out_args->save_to_load--;
}
}
}
puts("Currently available options:");
puts("");
puts("-g/--gold: launch The Golden Mask expansion pack.");
puts("-l/--level <PATH>: launch a specific level file.");
puts("-s/--save <NUM>: launch from a specific save slot (starts at 1).");
}
static void M_LoadConfig(void)
@ -398,7 +381,7 @@ static void M_HandleConfigChange(const EVENT *const event, void *const data)
if (CHANGED(window.is_fullscreen) || CHANGED(window.is_maximized)
|| CHANGED(window.width) || CHANGED(window.height)
|| CHANGED(rendering.scaler) || CHANGED(rendering.sizer)
|| CHANGED(rendering.aspect_mode)) {
|| CHANGED(rendering.aspect_mode) || CHANGED(visuals.use_psx_fov)) {
LOG_DEBUG("Change in settings detected");
M_SyncToWindow();
M_RefreshRendererViewport();
@ -427,12 +410,45 @@ static void M_HandleConfigChange(const EVENT *const event, void *const data)
|| CHANGED(visuals.water_color.r)) {
Output_ApplyLevelSettings();
}
if (CHANGED(rendering.aspect_mode)) {
Output_ReloadBackgroundImage();
}
}
bool Shell_ParseArgs(const int32_t arg_count, const char **args)
{
SHELL_ARGS *const out_args = &m_Args;
out_args->mod = M_MOD_OG;
for (int32_t i = 0; i < arg_count; i++) {
if (!strcmp(args[i], "-h") || !strcmp(args[i], "--help")) {
M_ShowHelp();
return false;
}
if (!strcmp(args[i], "-g") || !strcmp(args[i], "--gold")
|| !strcmp(args[i], "-gold")) {
out_args->mod = M_MOD_GM;
}
if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level"))
&& i + 1 < arg_count) {
out_args->level_to_play = args[i + 1];
out_args->mod = M_MOD_CUSTOM_LEVEL;
}
if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save"))
&& i + 1 < arg_count) {
if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) {
out_args->save_to_load--;
}
}
}
return true;
}
// TODO: refactor the hell out of me
void Shell_Main(void)
int32_t Shell_Main(void)
{
M_ParseArgs(&m_Args);
LOG_INFO("Game directory: %s", File_GetGameDirectory());
if (m_Args.mod == M_MOD_GM) {
Object_Get(O_MONK_3)->setup_func = Monk3_Setup;
@ -461,7 +477,7 @@ void Shell_Main(void)
if (!M_CreateGameWindow()) {
Shell_ExitSystem("Failed to create game window");
return;
return 1;
}
Random_Seed();
@ -474,7 +490,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();
@ -552,7 +573,7 @@ void Shell_Main(void)
if (gf_cmd.action == GF_NOOP
|| gf_cmd.action == GF_EXIT_TO_TITLE) {
Shell_ExitSystem("Title disabled & no replacement");
return;
return 1;
}
} else {
gf_cmd = GF_RunTitle();
@ -574,12 +595,14 @@ void Shell_Main(void)
if (m_Args.level_to_play != nullptr) {
Memory_FreePointer(&g_GameFlow.level_tables[GFLT_MAIN].levels[0].path);
}
return 0;
}
void Shell_Shutdown(void)
{
GameStringTable_Shutdown();
GF_Shutdown();
GameString_Shutdown();
Console_Shutdown();
Render_Shutdown();
Text_Shutdown();
@ -587,6 +610,7 @@ void Shell_Shutdown(void)
GameBuf_Shutdown();
Config_Shutdown();
EnumMap_Shutdown();
GameString_Shutdown();
}
const char *Shell_GetConfigPath(void)

View file

@ -9,9 +9,6 @@
void Sound_Init(void);
void Sound_Shutdown(void);
void Sound_SetMasterVolume(int32_t volume);
void Sound_UpdateEffects(void);
void Sound_StopEffect(SOUND_EFFECT_ID sample_id);
void Sound_EndScene(void);
int32_t Sound_GetMinVolume(void);
int32_t Sound_GetMaxVolume(void);

View file

@ -1,25 +0,0 @@
#include "global/vars.h"
#include <libtrx/config.h>
#include <libtrx/game/scaler.h>
#include <libtrx/game/ui/common.h>
int32_t UI_GetCanvasWidth(void)
{
return Scaler_CalcInverse(g_PhdWinWidth, SCALER_TARGET_GENERIC);
}
int32_t UI_GetCanvasHeight(void)
{
return Scaler_CalcInverse(g_PhdWinHeight, SCALER_TARGET_GENERIC);
}
float UI_ScaleX(const float x)
{
return Scaler_Calc(x, SCALER_TARGET_GENERIC);
}
float UI_ScaleY(const float y)
{
return Scaler_Calc(y, SCALER_TARGET_GENERIC);
}

View file

@ -13,12 +13,15 @@
#include <libtrx/game/ui/elements/resize.h>
#include <libtrx/game/ui/elements/spacer.h>
#include <libtrx/game/ui/elements/stack.h>
#include <libtrx/game/viewport.h>
#include <libtrx/memory.h>
#include <libtrx/strings.h>
#include <libtrx/utils.h>
#include <math.h>
#define M_ARROW_SPACING 2.0f
typedef struct {
CONFIG_OPTION_TYPE option_type;
GAME_STRING_ID label_id;
@ -219,6 +222,12 @@ static const M_OPTION m_Options[] = {
.delta_fast = 10,
},
{
.option_type = COT_BOOL,
.label_id = GS_ID(DETAIL_UI_SCROLL_WRAPAROUND),
.target = &g_Config.ui.enable_wraparound,
},
{
.option_type = COT_INT32,
.label_id = GS_ID(DETAIL_SCALER),
@ -244,11 +253,28 @@ static const M_OPTION m_Options[] = {
},
};
static int32_t M_GetVisibleRows(void);
static uint8_t *M_GetColorComponent(const M_OPTION *option);
static M_ENUM_LOOKUP M_GetEnumEntry(const M_OPTION *option);
static char *M_FormatRowValue(int32_t row_idx);
static bool M_CanChangeValue(int32_t row_idx, int32_t dir);
static bool M_RequestChangeValue(int32_t row_idx, int32_t dir);
static float M_GetValueWidth(const UI_GRAPHIC_SETTINGS_STATE *s);
static int32_t M_GetVisibleRows(void)
{
const int32_t res_h =
Scaler_CalcInverse(Viewport_GetHeight(), SCALER_TARGET_TEXT);
if (res_h <= 240) {
return 5;
} else if (res_h <= 384) {
return 7;
} else if (res_h < 480) {
return 10;
} else {
return 12;
}
}
static uint8_t *M_GetColorComponent(const M_OPTION *const option)
{
@ -417,6 +443,26 @@ static bool M_RequestChangeValue(const int32_t row_idx, const int32_t dir)
return true;
}
static float M_GetValueWidth(const UI_GRAPHIC_SETTINGS_STATE *const s)
{
// Measure the maximum width of the value label to prevent the entire
// dialog from changing its size as the player changes the sound levels.
float result = -1.0f;
for (int32_t i = 0; i < s->req.max_rows; i++) {
const char *const value = M_FormatRowValue(i);
float value_w;
UI_Label_Measure(value, &value_w, nullptr);
result = MAX(result, value_w);
}
float arrow_w;
UI_Label_Measure("\\{button left}", &arrow_w, nullptr);
result += arrow_w;
UI_Label_Measure("\\{button right}", &arrow_w, nullptr);
result += arrow_w;
result += M_ARROW_SPACING * 2;
return result;
}
void UI_GraphicSettings_Init(UI_GRAPHIC_SETTINGS_STATE *const s)
{
int32_t row_count = 0;
@ -426,6 +472,7 @@ void UI_GraphicSettings_Init(UI_GRAPHIC_SETTINGS_STATE *const s)
UI_Requester_Init(&s->req, row_count, row_count, true);
s->req.row_pad = 2.0f;
s->req.row_spacing = 0.0f;
s->req.show_arrows = true;
}
void UI_GraphicSettings_Free(UI_GRAPHIC_SETTINGS_STATE *const s)
@ -435,17 +482,7 @@ void UI_GraphicSettings_Free(UI_GRAPHIC_SETTINGS_STATE *const s)
bool UI_GraphicSettings_Control(UI_GRAPHIC_SETTINGS_STATE *const s)
{
const int32_t scale = Scaler_GetScale(SCALER_TARGET_TEXT) * 100;
if (scale >= 190) {
UI_Requester_SetVisibleRows(&s->req, 6);
} else if (scale >= 160) {
UI_Requester_SetVisibleRows(&s->req, 8);
} else if (scale >= 120) {
UI_Requester_SetVisibleRows(&s->req, 12);
} else {
UI_Requester_SetVisibleRows(&s->req, 18);
}
UI_Requester_SetVisibleRows(&s->req, M_GetVisibleRows());
const int32_t choice = UI_Requester_Control(&s->req);
if (choice == UI_REQUESTER_CANCEL) {
return true;
@ -465,6 +502,8 @@ void UI_GraphicSettings(UI_GRAPHIC_SETTINGS_STATE *const s)
UI_BeginModal(0.5f, 0.6f);
UI_BeginRequester(&s->req, GS(DETAIL_TITLE));
const float max_value_w = M_GetValueWidth(s) / g_Config.ui.text_scale;
for (int32_t i = 0; i < s->req.max_rows; i++) {
if (!UI_Requester_IsRowVisible(&s->req, i)) {
UI_BeginResize(-1.0f, 0.0f);
@ -480,19 +519,25 @@ void UI_GraphicSettings(UI_GRAPHIC_SETTINGS_STATE *const s)
UI_Label(GameString_Get(m_Options[i].label_id));
UI_Spacer(20.0f, 0.0f);
UI_BeginResize(max_value_w, -1.0f);
UI_BeginAnchor(1.0f, 0.5f);
UI_BeginStackEx((UI_STACK_SETTINGS) {
.orientation = UI_STACK_HORIZONTAL,
.align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE },
.spacing = { .h = 5.0f },
.spacing = { .h = M_ARROW_SPACING },
});
UI_BeginHide(i != sel_row || !M_CanChangeValue(i, -1));
UI_Label("\\{button left}");
UI_EndHide();
UI_Label(M_FormatRowValue(i));
UI_BeginHide(i != sel_row || !M_CanChangeValue(i, +1));
UI_Label("\\{button right}");
UI_EndHide();
UI_EndStack();
UI_EndAnchor();
UI_EndResize();
UI_EndStack();
UI_EndRequesterRow(&s->req, i);

View file

@ -5,6 +5,8 @@
typedef struct {
UI_REQUESTER_STATE req;
float arrow_spacing;
float value_w;
} UI_GRAPHIC_SETTINGS_STATE;
void UI_GraphicSettings_Init(UI_GRAPHIC_SETTINGS_STATE *s);

View file

@ -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;

View file

@ -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 {

View file

@ -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,
@ -157,13 +151,6 @@ typedef struct {
int32_t pitch;
} SKIDOO_INFO;
typedef struct {
struct {
XYZ_16 min;
XYZ_16 max;
} shift, rot;
} OBJECT_BOUNDS;
typedef struct {
int32_t xv;
int32_t yv;

View file

@ -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',
@ -267,7 +266,6 @@ sources = [
'game/spawn.c',
'game/stats.c',
'game/text.c',
'game/ui/common.c',
'game/ui/dialogs/graphic_settings.c',
'game/ui/dialogs/stats.c',
'game/viewport.c',

View file

@ -42,41 +42,33 @@ def extract_zip(zip_path: Path, dest_dir: Path) -> None:
z.extractall(dest_dir)
def download_assets(assets: list[tuple[str, Path]]) -> None:
def download_assets(asset_urls: list[str], target_dir: Path) -> None:
with tempfile.TemporaryDirectory() as tmpdir_str:
tmpdir = Path(tmpdir_str)
for url, dest in assets:
for url in asset_urls:
filename = Path(url).name
local_zip = tmpdir / filename
download_to_file(url, local_zip)
extract_zip(local_zip, dest)
extract_zip(local_zip, target_dir)
print("Asset download and extraction complete.")
def main() -> None:
args = parse_args()
assets: dict[int, list[tuple[str, Path]]] = {
1: [
(
"https://lostartefacts.dev/aux/tr1x/main.zip",
Path("data/tr1/ship"),
)
],
asset_urls_map: dict[int, list[str]] = {
1: ["https://lostartefacts.dev/aux/tr1x/main.zip"],
2: [
(
"https://lostartefacts.dev/aux/tr2x/main.zip",
Path("data/tr2/ship"),
)
"https://lostartefacts.dev/aux/tr2x/main.zip",
"https://lostartefacts.dev/aux/tr2x/trgm.zip",
],
}
match str(args.game_version):
case "1":
download_assets(assets[1])
case "2":
download_assets(assets[2])
case "all":
download_assets(assets[1])
download_assets(assets[2])
versions = {"1": [1], "2": [2], "all": [1, 2]}[args.game_version]
for version in versions:
download_assets(
asset_urls_map[version],
target_dir=PROJECT_PATHS[version].shipped_data_dir,
)
if __name__ == "__main__":