diff --git a/.gitignore b/.gitignore index 0e5438a15..a7b8c9b27 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/data/tr1/ship/cfg/TR1X_strings.json5 b/data/tr1/ship/cfg/TR1X_strings.json5 index b8ea28438..b49fdf18d 100644 --- a/data/tr1/ship/cfg/TR1X_strings.json5 +++ b/data/tr1/ship/cfg/TR1X_strings.json5 @@ -428,7 +428,11 @@ "OSD_AMBIGUOUS_INPUT_2": "Ambiguous input: %s and %s", "OSD_AMBIGUOUS_INPUT_3": "Ambiguous input: %s, %s, ...", "OSD_COMMAND_BAD_INVOCATION": "Invalid invocation: %s", + "OSD_COMMAND_BOOL": "on, off", + "OSD_COMMAND_DECIMAL": "[decimal]", + "OSD_COMMAND_INTEGER": "[integer]", "OSD_COMMAND_UNAVAILABLE": "This command is not currently available", + "OSD_COMMAND_VALID_VALUES": "Valid values: %s", "OSD_COMPLETE_LEVEL": "Level complete!", "OSD_CONFIG_OPTION_GET": "%s is currently set to %s", "OSD_CONFIG_OPTION_SET": "%s changed to %s", @@ -531,7 +535,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", diff --git a/data/tr2/ship/cfg/TR2X_gameflow.json5 b/data/tr2/ship/cfg/TR2X_gameflow.json5 index 5cfda3031..2649e668d 100644 --- a/data/tr2/ship/cfg/TR2X_gameflow.json5 +++ b/data/tr2/ship/cfg/TR2X_gameflow.json5 @@ -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", ], @@ -397,6 +398,7 @@ {"type": "total_stats", "background_path": "data/images/end.webp"}, ], "injections": [ + "data/injections/explosion.bin", "data/injections/house_itemrots.bin", ], }, diff --git a/data/tr2/ship/cfg/TR2X_strings.json5 b/data/tr2/ship/cfg/TR2X_strings.json5 index abc97764d..5d08c0f49 100644 --- a/data/tr2/ship/cfg/TR2X_strings.json5 +++ b/data/tr2/ship/cfg/TR2X_strings.json5 @@ -547,7 +547,11 @@ "OSD_BILINEAR_FILTER_OFF": "Bilinear filter: off", "OSD_BILINEAR_FILTER_ON": "Bilinear filter: on", "OSD_COMMAND_BAD_INVOCATION": "Invalid invocation: %s", + "OSD_COMMAND_BOOL": "on, off", + "OSD_COMMAND_DECIMAL": "[decimal]", + "OSD_COMMAND_INTEGER": "[integer]", "OSD_COMMAND_UNAVAILABLE": "This command is not currently available", + "OSD_COMMAND_VALID_VALUES": "Valid values: %s", "OSD_COMPLETE_LEVEL": "Level complete!", "OSD_CONFIG_OPTION_GET": "%s is currently set to %s", "OSD_CONFIG_OPTION_SET": "%s changed to %s", @@ -655,7 +659,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", diff --git a/data/tr2/ship/data/injections/explosion.bin b/data/tr2/ship/data/injections/explosion.bin new file mode 100644 index 000000000..9972c25aa Binary files /dev/null and b/data/tr2/ship/data/injections/explosion.bin differ diff --git a/data/tr2/ship/data/injections/living_fd.bin b/data/tr2/ship/data/injections/living_fd.bin new file mode 100644 index 000000000..d23df437d Binary files /dev/null and b/data/tr2/ship/data/injections/living_fd.bin differ diff --git a/docs/COMMAND_LINE.md b/docs/COMMAND_LINE.md new file mode 100644 index 000000000..9fca2d295 --- /dev/null +++ b/docs/COMMAND_LINE.md @@ -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 `: + 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 `: + Runs the game immediately loading a specific save slot. The first save + starts at `num=1`. This option can be combined with `-l/--level`. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7ea40e63f..efcdc6a13 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -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 diff --git a/docs/MIGRATING.md b/docs/MIGRATING.md index aa61e581f..1c9f2deb8 100644 --- a/docs/MIGRATING.md +++ b/docs/MIGRATING.md @@ -6,10 +6,11 @@ 1. **Update fog configuration** If you wish to force your fog settings on player: - - Rename `draw_distance_min` to `fog_start` + - Rename `draw_distance_fade` to `fog_start` - Rename `draw_distance_max` to `fog_end` + If you wish to give the player agency to change the fog: - - Remove `draw_distance_min` and `draw_distance_max` + - Remove `draw_distance_fade` and `draw_distance_max` ### Version 4.7 to 4.8 diff --git a/docs/tr1/CHANGELOG.md b/docs/tr1/CHANGELOG.md index 818130279..596504d82 100644 --- a/docs/tr1/CHANGELOG.md +++ b/docs/tr1/CHANGELOG.md @@ -5,12 +5,16 @@ - added support for antitriggers, like TR2+ (#2580) - added support for aspect ratio-specific images (#1840) - added an option to wraparound when scrolling UI dialogs, such as save/load (#2834) -- changed the `draw_distance_min` and `draw_distance_max` to `fog_start` and `fog_end` +- 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) @@ -21,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) @@ -35,9 +40,14 @@ - 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) +- fixed game crashing if the music folder was not present (#2887, regression from 4.9) +- fixed game crashing on unknown sequencer events - improved bubble appearance (#2672) - improved rendering performance - improved pause exit dialog - it can now be canceled with escape +- improved the `/set` console command to display available options if given an unknown argument - removed the pretty pixels options (it's now always enabled, #2258) ## [4.9](https://github.com/LostArtefacts/TRX/compare/tr1-4.8.3...tr1-4.9) - 2025-03-31 diff --git a/docs/tr1/README.md b/docs/tr1/README.md index d128fb4b3..e49c78f4e 100644 --- a/docs/tr1/README.md +++ b/docs/tr1/README.md @@ -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 diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index 1577ff805..d6b197278 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -1,4 +1,23 @@ ## [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) +- added explosion sprites to Home Sweet Home (#1569) +- changed the sound dialog appearance (repositioned, added text labels and arrows) +- changed the installer to always allow downloading music files (#2891) +- 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) +- fixed Lara snapping to face forwards if she has a slight angle and action is pressed after using an airlock door (#2215) +- fixed the game crashing on unknown sequencer events +- improved the `/set` console command to display available options if given an unknown argument + +## [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) diff --git a/docs/tr2/README.md b/docs/tr2/README.md index 9faec4fd4..fd4246446 100644 --- a/docs/tr2/README.md +++ b/docs/tr2/README.md @@ -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,10 @@ 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 +- fixed Lara snapping to face forwards if she has a slight angle and action is pressed after using an airlock door - improved the animation of Lara's braid #### Cheats diff --git a/src/libtrx/engine/audio_sample.c b/src/libtrx/engine/audio_sample.c index 0d3a4afb8..5dee0147b 100644 --- a/src/libtrx/engine/audio_sample.c +++ b/src/libtrx/engine/audio_sample.c @@ -1,5 +1,6 @@ #include "audio_internal.h" +#include "benchmark.h" #include "debug.h" #include "log.h" #include "memory.h" @@ -21,12 +22,21 @@ #include #include #include -#include + +typedef struct { + struct { + int32_t format; + AVChannelLayout ch_layout; + int32_t sample_rate; + } src, dst; + SwrContext *ctx; + size_t working_buffer_size; + uint8_t *working_buffer; +} M_SWR_CONTEXT; typedef struct { char *original_data; size_t original_size; - float *sample_data; int32_t channels; int32_t num_samples; @@ -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; diff --git a/src/libtrx/engine/audio_stream.c b/src/libtrx/engine/audio_stream.c index bca11ca75..ba80da5f7 100644 --- a/src/libtrx/engine/audio_stream.c +++ b/src/libtrx/engine/audio_stream.c @@ -84,17 +84,32 @@ static void M_SeekToStart(AUDIO_STREAM_SOUND *stream) ASSERT(stream != nullptr); stream->timestamp = stream->start_at; + int32_t error_code; if (stream->start_at <= 0.0) { // reset to start of file avio_seek(stream->av.format_ctx->pb, 0, SEEK_SET); - avformat_seek_file( + error_code = avformat_seek_file( stream->av.format_ctx, -1, 0, 0, 0, AVSEEK_FLAG_FRAME); } else { // seek to specific timestamp - const double time_base_sec = av_q2d(stream->av.stream->time_base); - av_seek_frame( - stream->av.format_ctx, 0, stream->start_at / time_base_sec, - AVSEEK_FLAG_ANY); + AVFormatContext *const fmt = stream->av.format_ctx; + if (fmt->pb != nullptr && (fmt->pb->seekable & AVIO_SEEKABLE_NORMAL)) { + const int64_t ts = (int64_t)(stream->start_at * AV_TIME_BASE); + error_code = avformat_seek_file( + fmt, stream->av.stream->index, INT64_MIN, ts, INT64_MAX, + AVSEEK_FLAG_BACKWARD); + } else { + // fallback to stream-based seek + const double time_base_sec = av_q2d(stream->av.stream->time_base); + error_code = av_seek_frame( + fmt, stream->av.stream->index, + (int64_t)(stream->start_at / time_base_sec), AVSEEK_FLAG_ANY); + } + } + if (error_code < 0) { + LOG_ERROR( + "seek failed for timestamp %f: %s", stream->timestamp, + av_err2str(error_code)); } } @@ -257,6 +272,10 @@ static bool M_InitialiseFromPath(int32_t sound_id, const char *file_path) int32_t error_code; char *full_path = File_GetFullPath(file_path); + if (full_path == nullptr) { + error_code = AVERROR(ENOENT); + goto cleanup; + } AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id]; @@ -680,26 +699,51 @@ double Audio_Stream_GetDuration(int32_t sound_id) return duration; } -bool Audio_Stream_SeekTimestamp(int32_t sound_id, double timestamp) +bool Audio_Stream_SeekTimestamp(const int32_t sound_id, const double timestamp) { if (!g_AudioDeviceID || sound_id < 0 || sound_id >= AUDIO_MAX_ACTIVE_STREAMS) { return false; } - if (m_Streams[sound_id].is_playing) { - SDL_LockAudioDevice(g_AudioDeviceID); - AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id]; - const double time_base_sec = av_q2d(stream->av.stream->time_base); - av_seek_frame( - stream->av.format_ctx, 0, timestamp / time_base_sec, - AVSEEK_FLAG_ANY); - avcodec_flush_buffers(stream->av.codec_ctx); + AUDIO_STREAM_SOUND *const stream = &m_Streams[sound_id]; + stream->start_at = timestamp; + if (!stream->is_used) { + return false; + } + ASSERT(stream->av.format_ctx != nullptr); + ASSERT(stream->av.codec_ctx != nullptr); + ASSERT(stream->av.stream != nullptr); + + SDL_LockAudioDevice(g_AudioDeviceID); + + const double time_base_sec = av_q2d(stream->av.stream->time_base); + if (time_base_sec <= 0.0) { + LOG_ERROR( + "Audio_Stream_SeekTimestamp: invalid time_base %f", time_base_sec); SDL_UnlockAudioDevice(g_AudioDeviceID); - return true; + return false; } - return false; + const int32_t stream_index = stream->av.stream->index; + const int64_t seek_target = (int64_t)(timestamp / time_base_sec); + const int32_t error_code = av_seek_frame( + stream->av.format_ctx, stream_index, seek_target, AVSEEK_FLAG_ANY); + if (error_code < 0) { + LOG_ERROR( + "seek failed for timestamp %f: %s", timestamp, + av_err2str(error_code)); + } + + avcodec_flush_buffers(stream->av.codec_ctx); + if (stream->sdl.stream) { + SDL_AudioStreamFlush(stream->sdl.stream); + } + + stream->timestamp = timestamp; + + SDL_UnlockAudioDevice(g_AudioDeviceID); + return true; } bool Audio_Stream_SetStartTimestamp(int32_t sound_id, double timestamp) diff --git a/src/libtrx/enum_map.c b/src/libtrx/enum_map.c index b140bfc30..88cf557f3 100644 --- a/src/libtrx/enum_map.c +++ b/src/libtrx/enum_map.c @@ -121,3 +121,25 @@ void EnumMap_Shutdown(void) } } } + +VECTOR *EnumMap_ListValues(const char *const enum_name) +{ + if (enum_name == nullptr) { + return nullptr; + } + + // Compare the prefix to find the matching enum values. + const size_t prefix_len = strlen(enum_name) + 1; + + VECTOR *const results = Vector_Create(sizeof(char *)); + M_INVERSE_ENTRY *entry; + M_INVERSE_ENTRY *tmp; + HASH_ITER(hh, m_InverseMap, entry, tmp) + { + if (strncmp(entry->key, enum_name, prefix_len - 1) == 0 + && entry->key[prefix_len - 1] == '|') { + Vector_Add(results, &entry->str_value); + } + } + return results; +} diff --git a/src/libtrx/game/console/cmd/config.c b/src/libtrx/game/console/cmd/config.c index 4eccfe895..bd2f31bca 100644 --- a/src/libtrx/game/console/cmd/config.c +++ b/src/libtrx/game/console/cmd/config.c @@ -14,6 +14,7 @@ static const char *M_Resolve(const char *option_name); static bool M_SameKey(const char *key1, const char *key2); +static char *M_GetAvailableOptions(const CONFIG_OPTION *option); static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx); @@ -76,6 +77,56 @@ cleanup: return result; } +// Return a comma-delimited list of valid values for the option. +// Caller must free the result via Memory_FreePointer. +static char *M_GetAvailableOptions(const CONFIG_OPTION *const option) +{ + if (option == nullptr) { + return nullptr; + } + + switch (option->type) { + case COT_BOOL: + return Memory_DupStr(GS(OSD_COMMAND_BOOL)); + + case COT_INT32: + return Memory_DupStr(GS(OSD_COMMAND_INTEGER)); + + case COT_DOUBLE: + case COT_FLOAT: + return Memory_DupStr(GS(OSD_COMMAND_DECIMAL)); + + case COT_ENUM: { + const char *enum_name = (const char *)option->param; + VECTOR *const values = EnumMap_ListValues(enum_name); + if (values == nullptr) { + return nullptr; + } + // Join vector items into a comma-separated string + size_t total_len = 1; + const char *const sep = ", "; + for (int32_t i = 0; i < values->count; i++) { + const char *const s = *(char **)Vector_Get(values, i); + total_len += strlen(s) + (i + 1 < values->count ? strlen(sep) : 0); + } + char *const result = Memory_Alloc(total_len); + char *ptr = result; + for (int32_t i = 0; i < values->count; i++) { + const char *const s = *(char **)Vector_Get(values, i); + strcat(ptr, s); + if (i + 1 < values->count) { + strcat(ptr, sep); + } + } + Vector_Free(values); + return result; + } + + default: + return nullptr; + } +} + char *Console_Cmd_Config_NormalizeKey(const char *key) { // TODO: Once we support arbitrary glyphs, this conversion should @@ -261,18 +312,16 @@ COMMAND_RESULT Console_Cmd_Config_Helper( char *normalized_name = Console_Cmd_Config_NormalizeKey(option->name); - COMMAND_RESULT result = CR_BAD_INVOCATION; if (new_value == nullptr || String_IsEmpty(new_value)) { char cur_value[128]; if (Console_Cmd_Config_GetCurrentValue(option, cur_value, 128)) { Console_Log(GS(OSD_CONFIG_OPTION_GET), normalized_name, cur_value); - result = CR_SUCCESS; - } else { - result = CR_FAILURE; + return CR_SUCCESS; } - return result; + return CR_FAILURE; } + COMMAND_RESULT result; if (Console_Cmd_Config_SetCurrentValue(option, new_value)) { Config_Write(); @@ -280,6 +329,15 @@ COMMAND_RESULT Console_Cmd_Config_Helper( ASSERT(Console_Cmd_Config_GetCurrentValue(option, final_value, 128)); Console_Log(GS(OSD_CONFIG_OPTION_SET), normalized_name, final_value); result = CR_SUCCESS; + } else { + // Report bad invocation on the provided new value + Console_Log(GS(OSD_COMMAND_BAD_INVOCATION), new_value); + char *available_options = M_GetAvailableOptions(option); + if (available_options != nullptr) { + Console_Log(GS(OSD_COMMAND_VALID_VALUES), available_options); + Memory_FreePointer(&available_options); + } + result = CR_FAILURE; } cleanup: diff --git a/src/libtrx/game/game_flow/reader.c b/src/libtrx/game/game_flow/reader.c index 77ff36e2e..507e13e64 100644 --- a/src/libtrx/game/game_flow/reader.c +++ b/src/libtrx/game/game_flow/reader.c @@ -281,6 +281,10 @@ static size_t M_LoadSequenceEvent( const char *const type_str = JSON_ObjectGetString(event_obj, "type", ""); const GF_SEQUENCE_EVENT_TYPE type = ENUM_MAP_GET(GF_SEQUENCE_EVENT_TYPE, type_str, -1); + if (type == (GF_SEQUENCE_EVENT_TYPE)-1) { + Shell_ExitSystemFmt( + "Unknown game flow sequence event type: '%s'", type_str); + } const M_SEQUENCE_EVENT_HANDLER *handler = M_GetSequenceEventHandlers(); while (handler->event_type != (GF_SEQUENCE_EVENT_TYPE)-1 @@ -288,11 +292,6 @@ static size_t M_LoadSequenceEvent( handler++; } - if (handler->event_type != type) { - Shell_ExitSystemFmt( - "Unknown game flow sequence event type: '%s'", type); - } - int32_t extra_data_size = 0; if (handler->handler_func != nullptr) { extra_data_size = handler->handler_func( diff --git a/src/libtrx/game/game_string_table/common.c b/src/libtrx/game/game_string_table/common.c index d3f8327dc..3d31c1738 100644 --- a/src/libtrx/game/game_string_table/common.c +++ b/src/libtrx/game/game_string_table/common.c @@ -1,9 +1,11 @@ #include "debug.h" +#include "filesystem.h" #include "game/game_flow.h" #include "game/game_string.h" #include "game/game_string_table.h" #include "game/game_string_table/priv.h" #include "game/objects/names.h" +#include "game/shell.h" #include "log.h" #include "memory.h" @@ -11,8 +13,6 @@ typedef void (*M_LOAD_STRING_FUNC)(const char *, const char *); -GS_FILE g_GST_File = {}; - static struct { GAME_OBJECT_ID target_object_id; GAME_OBJECT_ID source_object_id; @@ -27,6 +27,8 @@ static struct { { .target_object_id = NO_OBJECT }, }; +static VECTOR *m_GST_Layers = nullptr; + static void M_Apply(const GS_TABLE *table); static void M_ApplyLevelTitles( const GS_FILE *gs_file, GF_LEVEL_TABLE_TYPE level_table_type); @@ -90,17 +92,19 @@ static void M_ApplyLevelTitles( GF_GetLevelTable(level_table_type); const GS_LEVEL_TABLE *const gs_level_table = &gs_file->level_tables[level_table_type]; + if (gs_level_table->count == 0) { + return; + } + ASSERT(gs_level_table->count == level_table->count); for (int32_t i = 0; i < level_table->count; i++) { GF_SetLevelTitle( &level_table->levels[i], gs_level_table->entries[i].title); } } -void GameStringTable_Apply(const GF_LEVEL *const level) +static void M_ApplyLayer( + const GF_LEVEL *const level, const GS_FILE *const gs_file) { - const GS_FILE *const gs_file = &g_GST_File; - - Object_ResetNames(); M_Apply(&gs_file->global); for (int32_t i = 0; i < GFLT_NUMBER_OF; i++) { @@ -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); } diff --git a/src/libtrx/game/game_string_table/priv.c b/src/libtrx/game/game_string_table/priv.c index 0b62cc41b..a9bada043 100644 --- a/src/libtrx/game/game_string_table/priv.c +++ b/src/libtrx/game/game_string_table/priv.c @@ -52,4 +52,5 @@ void GS_File_Free(GS_FILE *const gs_file) for (int32_t i = 0; i < GFLT_NUMBER_OF; i++) { M_FreeLevelsTable(&gs_file->level_tables[i]); } + Memory_Free(gs_file); } diff --git a/src/libtrx/game/game_string_table/priv.h b/src/libtrx/game/game_string_table/priv.h index 00d69b7f0..9533c5989 100644 --- a/src/libtrx/game/game_string_table/priv.h +++ b/src/libtrx/game/game_string_table/priv.h @@ -1,6 +1,7 @@ #pragma once #include "game/game_flow/enum.h" +#include "vector.h" #include @@ -35,7 +36,7 @@ typedef struct { GS_LEVEL_TABLE level_tables[GFLT_NUMBER_OF]; } GS_FILE; -extern GS_FILE g_GST_File; - void GS_Table_Free(GS_TABLE *gs_table); + +GS_FILE *GS_File_CreateFromString(const char *data, bool load_levels); void GS_File_Free(GS_FILE *gs_file); diff --git a/src/libtrx/game/game_string_table/reader.c b/src/libtrx/game/game_string_table/reader.c index 4cec681d4..5ded75216 100644 --- a/src/libtrx/game/game_string_table/reader.c +++ b/src/libtrx/game/game_string_table/reader.c @@ -1,4 +1,3 @@ -#include "filesystem.h" #include "game/game_flow.h" #include "game/game_string_table.h" #include "game/game_string_table/priv.h" @@ -130,24 +129,14 @@ static void M_LoadLevelsFromJSON( } } -void GameStringTable_LoadFromFile(const char *const path) +GS_FILE *GS_File_CreateFromString( + const char *const data, const bool load_levels) { - char *data = nullptr; - if (!File_Load(path, &data, nullptr)) { - Shell_ExitSystemFmt("failed to open strings file (path: %d)", path); - } - GameStringTable_LoadFromString(data); - Memory_FreePointer(&data); -} - -void GameStringTable_LoadFromString(const char *const data) -{ - GameStringTable_Shutdown(); - - JSON_VALUE *root = nullptr; + GS_FILE *const gs_file = Memory_Alloc(sizeof(GS_FILE)); JSON_PARSE_RESULT parse_result; - root = JSON_ParseEx( + + JSON_VALUE *root = JSON_ParseEx( data, strlen(data), JSON_PARSE_FLAGS_ALLOW_JSON5, nullptr, nullptr, &parse_result); if (root == nullptr) { @@ -157,15 +146,16 @@ void GameStringTable_LoadFromString(const char *const data) parse_result.error_line_no, parse_result.error_row_no, data); } - GS_FILE *const gs_file = &g_GST_File; JSON_OBJECT *root_obj = JSON_ValueAsObject(root); M_LoadTableFromJSON(root_obj, &gs_file->global); - M_LoadLevelsFromJSON(root_obj, gs_file, "levels", GFLT_MAIN); - M_LoadLevelsFromJSON(root_obj, gs_file, "demos", GFLT_DEMOS); - M_LoadLevelsFromJSON(root_obj, gs_file, "cutscenes", GFLT_CUTSCENES); - + if (load_levels) { + M_LoadLevelsFromJSON(root_obj, gs_file, "levels", GFLT_MAIN); + M_LoadLevelsFromJSON(root_obj, gs_file, "demos", GFLT_DEMOS); + M_LoadLevelsFromJSON(root_obj, gs_file, "cutscenes", GFLT_CUTSCENES); + } if (root != nullptr) { JSON_ValueFree(root); root = nullptr; } + return gs_file; } diff --git a/src/libtrx/game/items.c b/src/libtrx/game/items.c index a2698b37b..f92b67299 100644 --- a/src/libtrx/game/items.c +++ b/src/libtrx/game/items.c @@ -219,15 +219,11 @@ int32_t Item_GlobalReplace( { int32_t changed = 0; - for (int32_t i = 0; i < Room_GetCount(); i++) { - int16_t item_num = Room_Get(i)->item_num; - while (item_num != NO_ITEM) { - ITEM *const item = &m_Items[item_num]; - if (item->object_id == src_obj_id) { - item->object_id = dst_obj_id; - changed++; - } - item_num = item->next_item; + for (int32_t item_num = 0; item_num < m_MaxUsedItemCount; item_num++) { + ITEM *const item = &m_Items[item_num]; + if (item->object_id == src_obj_id) { + item->object_id = dst_obj_id; + changed++; } } diff --git a/src/libtrx/game/lara/common.c b/src/libtrx/game/lara/common.c index 2f2274ede..ad6136f5a 100644 --- a/src/libtrx/game/lara/common.c +++ b/src/libtrx/game/lara/common.c @@ -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; +} diff --git a/src/libtrx/game/music.c b/src/libtrx/game/music.c index c3e60a6e6..19d259fdb 100644 --- a/src/libtrx/game/music.c +++ b/src/libtrx/game/music.c @@ -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++) { diff --git a/src/tr1/game/objects/general/door.c b/src/libtrx/game/objects/general/door.c similarity index 73% rename from src/tr1/game/objects/general/door.c rename to src/libtrx/game/objects/general/door.c index 5cbe0bfcc..e81c4ca39 100644 --- a/src/tr1/game/objects/general/door.c +++ b/src/libtrx/game/objects/general/door.c @@ -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 -#include -#include -#include +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); } } diff --git a/src/libtrx/game/rooms/common.c b/src/libtrx/game/rooms/common.c index b10c7ceb7..0dc525142 100644 --- a/src/libtrx/game/rooms/common.c +++ b/src/libtrx/game/rooms/common.c @@ -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; } diff --git a/src/libtrx/game/shell/main.c b/src/libtrx/game/shell/main.c index 1a29106ab..54a5a0653 100644 --- a/src/libtrx/game/shell/main.c +++ b/src/libtrx/game/shell/main.c @@ -6,28 +6,18 @@ #include -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; } diff --git a/src/libtrx/game/ui/common.c b/src/libtrx/game/ui/common.c index 2ffa2c32b..159eb7190 100644 --- a/src/libtrx/game/ui/common.c +++ b/src/libtrx/game/ui/common.c @@ -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 @@ -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); +} diff --git a/src/libtrx/game/ui/dialogs/photo_mode.c b/src/libtrx/game/ui/dialogs/photo_mode.c index 512f4b3e1..903fe6a11 100644 --- a/src/libtrx/game/ui/dialogs/photo_mode.c +++ b/src/libtrx/game/ui/dialogs/photo_mode.c @@ -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); diff --git a/src/libtrx/game/ui/dialogs/sound_settings.c b/src/libtrx/game/ui/dialogs/sound_settings.c new file mode 100644 index 000000000..b5f382308 --- /dev/null +++ b/src/libtrx/game/ui/dialogs/sound_settings.c @@ -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(); +} diff --git a/src/libtrx/game/ui/elements/modal.c b/src/libtrx/game/ui/elements/modal.c index 9aec8632e..f74030b87 100644 --- a/src/libtrx/game/ui/elements/modal.c +++ b/src/libtrx/game/ui/elements/modal.c @@ -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) diff --git a/src/libtrx/game/ui/elements/stack.c b/src/libtrx/game/ui/elements/stack.c index d8a460a33..6638b775b 100644 --- a/src/libtrx/game/ui/elements/stack.c +++ b/src/libtrx/game/ui/elements/stack.c @@ -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; diff --git a/src/libtrx/include/libtrx/enum_map.h b/src/libtrx/include/libtrx/enum_map.h index d0fecff85..12382e49f 100644 --- a/src/libtrx/include/libtrx/enum_map.h +++ b/src/libtrx/include/libtrx/enum_map.h @@ -1,4 +1,4 @@ -#include +#include "vector.h" #define ENUM_MAP_DEFINE(enum_name, enum_value, str_value) \ EnumMap_Define(ENUM_MAP_NAME(enum_name), enum_value, str_value); @@ -18,6 +18,13 @@ extern void EnumMap_Init(void); void EnumMap_Shutdown(void); +// Returns a vector of valid string values for the given enum_name. +// +// The returned vector must be freed via Vector_Free(). The string pointers +// within the vector are owned by the enum map and should not be freed by the +// caller. Returns nullptr if the enum_name is not valid. +VECTOR *EnumMap_ListValues(const char *enum_name); + void EnumMap_Define( const char *enum_name, int32_t enum_value, const char *str_value); int32_t EnumMap_Get( diff --git a/src/libtrx/include/libtrx/game/game_string.def b/src/libtrx/include/libtrx/game/game_string.def index 63a70fe0d..7ce161412 100644 --- a/src/libtrx/include/libtrx/game/game_string.def +++ b/src/libtrx/include/libtrx/game/game_string.def @@ -34,6 +34,10 @@ GS_DEFINE(OSD_PLAY_MUSIC_TRACK, "Playing music track %d") GS_DEFINE(OSD_INVALID_MUSIC_TRACK, "Invalid music track") GS_DEFINE(OSD_UNKNOWN_COMMAND, "Unknown command: %s") GS_DEFINE(OSD_COMMAND_BAD_INVOCATION, "Invalid invocation: %s") +GS_DEFINE(OSD_COMMAND_VALID_VALUES, "Valid values: %s") +GS_DEFINE(OSD_COMMAND_BOOL, "on, off") +GS_DEFINE(OSD_COMMAND_INTEGER, "[integer]") +GS_DEFINE(OSD_COMMAND_DECIMAL, "[decimal]") GS_DEFINE(OSD_COMMAND_UNAVAILABLE, "This command is not currently available") GS_DEFINE(OSD_INVALID_ROOM, "Invalid room: %d. Valid rooms are 0-%d") GS_DEFINE(OSD_POS_SET_POS, "Teleported to position: %.3f %.3f %.3f") @@ -132,7 +136,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") diff --git a/src/libtrx/include/libtrx/game/game_string_table.h b/src/libtrx/include/libtrx/game/game_string_table.h index b14a99c97..cc33f3fe4 100644 --- a/src/libtrx/include/libtrx/game/game_string_table.h +++ b/src/libtrx/include/libtrx/game/game_string_table.h @@ -2,7 +2,8 @@ #include -void GameStringTable_LoadFromFile(const char *path); -void GameStringTable_LoadFromString(const char *data); -void GameStringTable_Apply(const GF_LEVEL *level); +void GameStringTable_Init(void); void GameStringTable_Shutdown(void); + +void GameStringTable_Load(const char *path, bool load_levels); +void GameStringTable_Apply(const GF_LEVEL *level); diff --git a/src/libtrx/include/libtrx/game/lara/common.h b/src/libtrx/include/libtrx/game/lara/common.h index 068634600..82e2f7357 100644 --- a/src/libtrx/include/libtrx/game/lara/common.h +++ b/src/libtrx/include/libtrx/game/lara/common.h @@ -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); diff --git a/src/libtrx/include/libtrx/game/music/common.h b/src/libtrx/include/libtrx/game/music/common.h index 68c3c3838..1e6ee1f85 100644 --- a/src/libtrx/include/libtrx/game/music/common.h +++ b/src/libtrx/include/libtrx/game/music/common.h @@ -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); diff --git a/src/libtrx/include/libtrx/game/objects/common.h b/src/libtrx/include/libtrx/game/objects/common.h index f65578f14..18de887cd 100644 --- a/src/libtrx/include/libtrx/game/objects/common.h +++ b/src/libtrx/include/libtrx/game/objects/common.h @@ -36,6 +36,7 @@ void Object_SwapMesh( ANIM *Object_GetAnim(const OBJECT *obj, int32_t anim_idx); ANIM_BONE *Object_GetBone(const OBJECT *obj, int32_t bone_idx); +extern void Object_DrawUnclippedItem(const ITEM *item); extern void Object_DrawMesh(int32_t mesh_idx, int32_t clip, bool interpolated); void Object_DrawInterpolatedObject( diff --git a/src/libtrx/include/libtrx/game/objects/types.h b/src/libtrx/include/libtrx/game/objects/types.h index 88b289dcd..c22f3c549 100644 --- a/src/libtrx/include/libtrx/game/objects/types.h +++ b/src/libtrx/include/libtrx/game/objects/types.h @@ -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 diff --git a/src/libtrx/include/libtrx/game/rooms/common.h b/src/libtrx/include/libtrx/game/rooms/common.h index 1d24b62b7..19539f1ce 100644 --- a/src/libtrx/include/libtrx/game/rooms/common.h +++ b/src/libtrx/include/libtrx/game/rooms/common.h @@ -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( diff --git a/src/libtrx/include/libtrx/game/shell.h b/src/libtrx/include/libtrx/game/shell.h index 3128bda38..1ed37747f 100644 --- a/src/libtrx/include/libtrx/game/shell.h +++ b/src/libtrx/include/libtrx/game/shell.h @@ -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); diff --git a/src/libtrx/include/libtrx/game/sound/common.h b/src/libtrx/include/libtrx/game/sound/common.h index 374320fe5..144a322d8 100644 --- a/src/libtrx/include/libtrx/game/sound/common.h +++ b/src/libtrx/include/libtrx/game/sound/common.h @@ -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); diff --git a/src/libtrx/include/libtrx/game/ui.h b/src/libtrx/include/libtrx/game/ui.h index 811c320db..fd8a7a341 100644 --- a/src/libtrx/include/libtrx/game/ui.h +++ b/src/libtrx/include/libtrx/game/ui.h @@ -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" diff --git a/src/libtrx/include/libtrx/game/ui/common.h b/src/libtrx/include/libtrx/game/ui/common.h index 6400264f1..015dfc5f3 100644 --- a/src/libtrx/include/libtrx/game/ui/common.h +++ b/src/libtrx/include/libtrx/game/ui/common.h @@ -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); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/sound_settings.h b/src/libtrx/include/libtrx/game/ui/dialogs/sound_settings.h new file mode 100644 index 000000000..d3bbd128d --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/dialogs/sound_settings.h @@ -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); diff --git a/src/libtrx/meson.build b/src/libtrx/meson.build index d3866bca2..d93000c31 100644 --- a/src/libtrx/meson.build +++ b/src/libtrx/meson.build @@ -176,6 +176,7 @@ sources = [ 'game/objects/general/bridge_flat.c', 'game/objects/general/bridge_tilt1.c', 'game/objects/general/bridge_tilt2.c', + 'game/objects/general/door.c', 'game/objects/general/drawbridge.c', 'game/objects/general/trapdoor.c', 'game/objects/names.c', @@ -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', diff --git a/src/tr1/game/inventory_ring/control.c b/src/tr1/game/inventory_ring/control.c index 760b66a9b..bb5803ccb 100644 --- a/src/tr1/game/inventory_ring/control.c +++ b/src/tr1/game/inventory_ring/control.c @@ -694,7 +694,6 @@ static GF_COMMAND M_Control(INV_RING *const ring) InvRing_MotionSetup(ring, RNG_CLOSING_ITEM, RNG_DESELECT, 0); g_Input = (INPUT_STATE) {}; g_InputDB = (INPUT_STATE) {}; - if (ring->mode == INV_LOAD_MODE || ring->mode == INV_SAVE_MODE || ring->mode == INV_SAVE_CRYSTAL_MODE) { InvRing_MotionSetup( @@ -713,7 +712,7 @@ static GF_COMMAND M_Control(INV_RING *const ring) } if (ring->mode == INV_TITLE_MODE - && ((inv_item->object_id == O_DETAIL_OPTION) + && (inv_item->object_id == O_DETAIL_OPTION || inv_item->object_id == O_SOUND_OPTION || inv_item->object_id == O_CONTROL_OPTION || inv_item->object_id == O_GAMMA_OPTION)) { diff --git a/src/tr1/game/items.c b/src/tr1/game/items.c index bcc068aa1..fdb607cbb 100644 --- a/src/tr1/game/items.c +++ b/src/tr1/game/items.c @@ -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) { diff --git a/src/tr1/game/items.h b/src/tr1/game/items.h index a9bc07b68..c94893dfc 100644 --- a/src/tr1/game/items.h +++ b/src/tr1/game/items.h @@ -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); diff --git a/src/tr1/game/lara/common.c b/src/tr1/game/lara/common.c index 4e1c95406..6596d42fc 100644 --- a/src/tr1/game/lara/common.c +++ b/src/tr1/game/lara/common.c @@ -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 diff --git a/src/tr1/game/lara/common.h b/src/tr1/game/lara/common.h index 291ab20e9..620b0c31e 100644 --- a/src/tr1/game/lara/common.h +++ b/src/tr1/game/lara/common.h @@ -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); diff --git a/src/tr1/game/music.c b/src/tr1/game/music.c index c4700d9f1..6a0467c8a 100644 --- a/src/tr1/game/music.c +++ b/src/tr1/game/music.c @@ -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) { diff --git a/src/tr1/game/music.h b/src/tr1/game/music.h index 97de0c9ad..32957af65 100644 --- a/src/tr1/game/music.h +++ b/src/tr1/game/music.h @@ -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); diff --git a/src/tr1/game/objects/common.h b/src/tr1/game/objects/common.h index dd39f97e8..5b9da9fdc 100644 --- a/src/tr1/game/objects/common.h +++ b/src/tr1/game/objects/common.h @@ -12,7 +12,6 @@ void Object_DrawDummyItem(const ITEM *item); void Object_DrawSpriteItem(const ITEM *item); void Object_DrawPickupItem(const ITEM *item); void Object_DrawAnimatingItem(const ITEM *item); -void Object_DrawUnclippedItem(const ITEM *item); void Object_SetMeshReflective( GAME_OBJECT_ID obj_id, int32_t mesh_idx, bool enabled); void Object_SetReflective(GAME_OBJECT_ID obj_id, bool enabled); diff --git a/src/tr1/game/option/option.c b/src/tr1/game/option/option.c index 957cc80d6..ca778692f 100644 --- a/src/tr1/game/option/option.c +++ b/src/tr1/game/option/option.c @@ -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: diff --git a/src/tr1/game/option/option_sound.c b/src/tr1/game/option/option_sound.c index 0af6b0a3c..0777a6d6f 100644 --- a/src/tr1/game/option/option_sound.c +++ b/src/tr1/game/option/option_sound.c @@ -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 +#include -#include +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); } diff --git a/src/tr1/game/option/option_sound.h b/src/tr1/game/option/option_sound.h index 9128617d5..ee5f63ba7 100644 --- a/src/tr1/game/option/option_sound.h +++ b/src/tr1/game/option/option_sound.h @@ -3,4 +3,5 @@ #include void Option_Sound_Control(INVENTORY_ITEM *inv_item, bool is_busy); +void Option_Sound_Draw(INVENTORY_ITEM *inv_item); void Option_Sound_Shutdown(void); diff --git a/src/tr1/game/output.c b/src/tr1/game/output.c index 9bed2681b..c63c73c94 100644 --- a/src/tr1/game/output.c +++ b/src/tr1/game/output.c @@ -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(); diff --git a/src/tr1/game/shell.c b/src/tr1/game/shell.c index f892719ba..da1fdea06 100644 --- a/src/tr1/game/shell.c +++ b/src/tr1/game/shell.c @@ -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 : launch a specific level file."); + puts("-s/--save : 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) diff --git a/src/tr1/game/sound.c b/src/tr1/game/sound.c index 3f479d368..7728f2465 100644 --- a/src/tr1/game/sound.c +++ b/src/tr1/game/sound.c @@ -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(); diff --git a/src/tr1/game/sound.h b/src/tr1/game/sound.h index 4cd892222..9a403a6c6 100644 --- a/src/tr1/game/sound.h +++ b/src/tr1/game/sound.h @@ -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); diff --git a/src/tr1/game/ui/common.c b/src/tr1/game/ui/common.c deleted file mode 100644 index b3cdf81f9..000000000 --- a/src/tr1/game/ui/common.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "game/screen.h" - -#include -#include - -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; -} diff --git a/src/tr1/global/types.h b/src/tr1/global/types.h index 54dd99625..6b7384d57 100644 --- a/src/tr1/global/types.h +++ b/src/tr1/global/types.h @@ -123,12 +123,6 @@ typedef struct { }; } PHD_VBUF; -typedef struct { - SECTOR *sector; - SECTOR old_sector; - int16_t block; -} DOORPOS_DATA; - typedef struct { PASSPORT_MODE passport_selection; int32_t select_save_slot; diff --git a/src/tr1/meson.build b/src/tr1/meson.build index 277258aec..a047f9ce8 100644 --- a/src/tr1/meson.build +++ b/src/tr1/meson.build @@ -197,7 +197,6 @@ sources = [ 'game/objects/general/cabin.c', 'game/objects/general/camera_target.c', 'game/objects/general/cog.c', - 'game/objects/general/door.c', 'game/objects/general/earthquake.c', 'game/objects/general/keyhole.c', 'game/objects/general/moving_bar.c', @@ -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', diff --git a/src/tr2/game/inventory_ring/control.c b/src/tr2/game/inventory_ring/control.c index 900198691..5f1b04969 100644 --- a/src/tr2/game/inventory_ring/control.c +++ b/src/tr2/game/inventory_ring/control.c @@ -639,10 +639,10 @@ static GF_COMMAND M_Control(INV_RING *const ring) if (g_InputDB.menu_confirm) { g_Inv_Chosen = inv_item->object_id; - if (ring->type != RT_MAIN) { - g_InvRing_Source[RT_OPTION].current = ring->current_object; - } else { + if (ring->type == RT_MAIN) { g_InvRing_Source[RT_MAIN].current = ring->current_object; + } else { + g_InvRing_Source[RT_OPTION].current = ring->current_object; } if (ring->mode == INV_TITLE_MODE && (inv_item->object_id == O_DETAIL_OPTION @@ -783,7 +783,7 @@ INV_RING *InvRing_Open(const INVENTORY_MODE mode) } for (int32_t i = 0; i < 8; i++) { - g_Inv_ExtraData[i] = 0; + g_Inv_ExtraData[i] = -1; } g_InvRing_Source[RT_MAIN].current = 0; diff --git a/src/tr2/game/items.c b/src/tr2/game/items.c index a66bb7eb3..09c7b16ca 100644 --- a/src/tr2/game/items.c +++ b/src/tr2/game/items.c @@ -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); diff --git a/src/tr2/game/items.h b/src/tr2/game/items.h index 6a26aa675..c82d6ec0c 100644 --- a/src/tr2/game/items.h +++ b/src/tr2/game/items.h @@ -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); diff --git a/src/tr2/game/objects/common.h b/src/tr2/game/objects/common.h index 15a9e8cc0..65911e138 100644 --- a/src/tr2/game/objects/common.h +++ b/src/tr2/game/objects/common.h @@ -6,7 +6,6 @@ void Object_DrawDummyItem(const ITEM *item); void Object_DrawAnimatingItem(const ITEM *item); -void Object_DrawUnclippedItem(const ITEM *item); void Object_DrawSpriteItem(const ITEM *item); void Object_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); diff --git a/src/tr2/game/objects/general/detonator.c b/src/tr2/game/objects/general/detonator.c index 50a4980e6..e0b0120a3 100644 --- a/src/tr2/game/objects/general/detonator.c +++ b/src/tr2/game/objects/general/detonator.c @@ -10,33 +10,37 @@ #include "game/sound.h" #include "global/vars.h" +#include + #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) { diff --git a/src/tr2/game/objects/general/door.c b/src/tr2/game/objects/general/door.c deleted file mode 100644 index 819c8fb5f..000000000 --- a/src/tr2/game/objects/general/door.c +++ /dev/null @@ -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 -#include -#include -#include - -typedef struct { - DOORPOS_DATA d1; - DOORPOS_DATA d1flip; - DOORPOS_DATA d2; - DOORPOS_DATA d2flip; -} DOOR_DATA; - -static SECTOR *M_GetRoomRelSector( - const ROOM *room, const ITEM *item, int32_t sector_dx, int32_t sector_dz); -static void M_InitialisePortal( - const ROOM *room, const ITEM *item, int32_t sector_dx, int32_t sector_dz, - DOORPOS_DATA *door_pos); -static 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) diff --git a/src/tr2/game/objects/general/flare_item.c b/src/tr2/game/objects/general/flare_item.c index 0a89767cb..fe893694e 100644 --- a/src/tr2/game/objects/general/flare_item.c +++ b/src/tr2/game/objects/general/flare_item.c @@ -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; diff --git a/src/tr2/game/objects/general/keyhole.c b/src/tr2/game/objects/general/keyhole.c index 359d23110..c9b44a88f 100644 --- a/src/tr2/game/objects/general/keyhole.c +++ b/src/tr2/game/objects/general/keyhole.c @@ -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; } diff --git a/src/tr2/game/objects/general/movable_block.c b/src/tr2/game/objects/general/movable_block.c index 63f0f1b2b..3dbea88e8 100644 --- a/src/tr2/game/objects/general/movable_block.c +++ b/src/tr2/game/objects/general/movable_block.c @@ -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; } diff --git a/src/tr2/game/objects/general/pickup.c b/src/tr2/game/objects/general/pickup.c index 5d0910541..2f1853f3f 100644 --- a/src/tr2/game/objects/general/pickup.c +++ b/src/tr2/game/objects/general/pickup.c @@ -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) { diff --git a/src/tr2/game/objects/general/pickup.h b/src/tr2/game/objects/general/pickup.h index dfac91fc3..1b1a51fff 100644 --- a/src/tr2/game/objects/general/pickup.h +++ b/src/tr2/game/objects/general/pickup.h @@ -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); diff --git a/src/tr2/game/objects/general/puzzle_hole.c b/src/tr2/game/objects/general/puzzle_hole.c index 174755780..f63db4c5c 100644 --- a/src/tr2/game/objects/general/puzzle_hole.c +++ b/src/tr2/game/objects/general/puzzle_hole.c @@ -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; } diff --git a/src/tr2/game/objects/general/switch.c b/src/tr2/game/objects/general/switch.c index 3af8f70e2..382425e6f 100644 --- a/src/tr2/game/objects/general/switch.c +++ b/src/tr2/game/objects/general/switch.c @@ -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,30 @@ 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) { + lara_item->rot.y = switch_item->rot.y; 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,33 +140,35 @@ 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; } - lara_item->rot.y = item->rot.y; - if (item->object_id == O_SWITCH_TYPE_AIRLOCK && item->current_anim_state == SWITCH_STATE_ON) { return; @@ -193,6 +196,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 +205,7 @@ static void M_CollisionUW( return; } - if (!Item_TestPosition(m_SwitchBoundsUW, item, lara_item)) { + if (!Lara_TestPosition(item, obj->bounds_func())) { return; } diff --git a/src/tr2/game/objects/general/zipline.c b/src/tr2/game/objects/general/zipline.c index 6dd04d867..0effd9bb6 100644 --- a/src/tr2/game/objects/general/zipline.c +++ b/src/tr2/game/objects/general/zipline.c @@ -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; diff --git a/src/tr2/game/option/option_passport.c b/src/tr2/game/option/option_passport.c index 1c56e5f78..1da73aebc 100644 --- a/src/tr2/game/option/option_passport.c +++ b/src/tr2/game/option/option_passport.c @@ -66,6 +66,8 @@ static void M_LoadGame(INVENTORY_ITEM *inv_item); static void M_SaveGame(INVENTORY_ITEM *inv_item); static void M_NewGame(void); static void M_PlayAnyLevel(INVENTORY_ITEM *inv_item); +static int32_t M_GetCurrentPage(const INVENTORY_ITEM *inv_item); +static bool M_IsFlipping(const INVENTORY_ITEM *inv_item); static void M_FlipLeft(INVENTORY_ITEM *inv_item); static void M_FlipRight(INVENTORY_ITEM *inv_item); static void M_Close(INVENTORY_ITEM *inv_item); @@ -318,6 +320,17 @@ static void M_PlayAnyLevel(INVENTORY_ITEM *const inv_item) } } +static int32_t M_GetCurrentPage(const INVENTORY_ITEM *const inv_item) +{ + const int32_t frame = inv_item->goal_frame - inv_item->open_frame; + return frame % 5 == 0 ? frame / 5 : -1; +} + +static bool M_IsFlipping(const INVENTORY_ITEM *const inv_item) +{ + return M_GetCurrentPage(inv_item) == -1; +} + static void M_FlipLeft(INVENTORY_ITEM *const inv_item) { M_RemoveAllText(); @@ -413,18 +426,19 @@ void Option_Passport_Control(INVENTORY_ITEM *const item, const bool is_busy) InvRing_RemoveAllText(); - const int32_t frame = item->goal_frame - item->open_frame; - const int32_t page = frame % 5 == 0 ? frame / 5 : -1; - const bool is_flipping = page == -1; - if (is_flipping) { + if (M_IsFlipping(item)) { return; } - m_State.current_page = page; + m_State.current_page = M_GetCurrentPage(item); if (m_State.current_page < m_State.active_page) { M_FlipRight(item); + g_Input = (INPUT_STATE) {}; + g_InputDB = (INPUT_STATE) {}; } else if (m_State.current_page > m_State.active_page) { M_FlipLeft(item); + g_Input = (INPUT_STATE) {}; + g_InputDB = (INPUT_STATE) {}; } else { m_State.is_ready = true; M_ShowPage(item); diff --git a/src/tr2/game/option/option_sound.c b/src/tr2/game/option/option_sound.c index f6cfab736..f5dec720f 100644 --- a/src/tr2/game/option/option_sound.c +++ b/src/tr2/game/option/option_sound.c @@ -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 -#include +#include -#include +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); } diff --git a/src/tr2/game/render/common.c b/src/tr2/game/render/common.c index bcfb617ff..82b232443 100644 --- a/src/tr2/game/render/common.c +++ b/src/tr2/game/render/common.c @@ -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); diff --git a/src/tr2/game/shell/common.c b/src/tr2/game/shell/common.c index f7d00b73d..b40b7e758 100644 --- a/src/tr2/game/shell/common.c +++ b/src/tr2/game/shell/common.c @@ -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 : launch a specific level file."); + puts("-s/--save : launch from a specific save slot (starts at 1)."); } static void M_LoadConfig(void) @@ -433,10 +416,39 @@ static void M_HandleConfigChange(const EVENT *const event, void *const data) } } -// TODO: refactor the hell out of me -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_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 +int32_t Shell_Main(void) +{ + LOG_INFO("Game directory: %s", File_GetGameDirectory()); if (m_Args.mod == M_MOD_GM) { Object_Get(O_MONK_3)->setup_func = Monk3_Setup; @@ -465,7 +477,7 @@ void Shell_Main(void) if (!M_CreateGameWindow()) { Shell_ExitSystem("Failed to create game window"); - return; + return 1; } Random_Seed(); @@ -478,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(); @@ -556,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(); @@ -578,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(); @@ -591,6 +610,7 @@ void Shell_Shutdown(void) GameBuf_Shutdown(); Config_Shutdown(); EnumMap_Shutdown(); + GameString_Shutdown(); } const char *Shell_GetConfigPath(void) diff --git a/src/tr2/game/sound.h b/src/tr2/game/sound.h index 8b0fc32ad..53b5e88ca 100644 --- a/src/tr2/game/sound.h +++ b/src/tr2/game/sound.h @@ -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); diff --git a/src/tr2/game/ui/common.c b/src/tr2/game/ui/common.c deleted file mode 100644 index f4b10ec67..000000000 --- a/src/tr2/game/ui/common.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "global/vars.h" - -#include -#include -#include - -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); -} diff --git a/src/tr2/game/viewport.c b/src/tr2/game/viewport.c index 245e1c11c..424039f73 100644 --- a/src/tr2/game/viewport.c +++ b/src/tr2/game/viewport.c @@ -125,20 +125,23 @@ void Viewport_Reset(void) VIEWPORT *const vp = &m_Viewport; switch (g_Config.rendering.aspect_mode) { case AM_4_3: - vp->render_ar = 4.0 / 3.0; + vp->render_ar.w = 4; + vp->render_ar.h = 3; break; case AM_16_9: - vp->render_ar = 16.0 / 9.0; + vp->render_ar.w = 16; + vp->render_ar.h = 9; break; case AM_ANY: - vp->render_ar = size.w / (double)size.h; + vp->render_ar.w = size.w; + vp->render_ar.h = size.h; break; } vp->width = size.w / g_Config.rendering.scaler; vp->height = size.h / g_Config.rendering.scaler; if (g_Config.rendering.aspect_mode != AM_ANY) { - vp->width = vp->height * vp->render_ar; + vp->width = vp->height * vp->render_ar.w / vp->render_ar.h; } vp->near_z = Output_GetNearZ() >> W2V_SHIFT; diff --git a/src/tr2/game/viewport.h b/src/tr2/game/viewport.h index 9af7d0105..8ec3d7485 100644 --- a/src/tr2/game/viewport.h +++ b/src/tr2/game/viewport.h @@ -8,7 +8,10 @@ typedef struct { int32_t near_z; int32_t far_z; int16_t view_angle; - double render_ar; + struct { + int32_t w; + int32_t h; + } render_ar; // TODO: remove most of these variables if possible struct { diff --git a/src/tr2/global/types_decomp.h b/src/tr2/global/types_decomp.h index 8427402b1..59ecb25d0 100644 --- a/src/tr2/global/types_decomp.h +++ b/src/tr2/global/types_decomp.h @@ -120,12 +120,6 @@ typedef enum { GFE_REMOVE_AMMO = 22, } GF_EVENTS; -typedef struct { - SECTOR *sector; - SECTOR old_sector; - int16_t block; -} DOORPOS_DATA; - typedef enum { TRAP_SET = 0, TRAP_ACTIVATE = 1, @@ -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; diff --git a/src/tr2/meson.build b/src/tr2/meson.build index ea203d6bf..b3d748095 100644 --- a/src/tr2/meson.build +++ b/src/tr2/meson.build @@ -195,7 +195,6 @@ sources = [ 'game/objects/general/cutscene_player.c', 'game/objects/general/detonator.c', 'game/objects/general/ding_dong.c', - 'game/objects/general/door.c', 'game/objects/general/earthquake.c', 'game/objects/general/final_cutscene.c', 'game/objects/general/final_level_counter.c', @@ -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', diff --git a/tools/download_assets b/tools/download_assets index ecc2292c1..6b20c3b9f 100755 --- a/tools/download_assets +++ b/tools/download_assets @@ -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__": diff --git a/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs b/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs index f98813d91..b7ed06ad7 100644 --- a/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs +++ b/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs @@ -15,15 +15,10 @@ public abstract class GenericInstallSource : BaseInstallSource }; public override bool IsDownloadingMusicNeeded(string sourceDirectory) - { - return !Directory.Exists(Path.Combine(sourceDirectory, "audio")) - && !Directory.Exists(Path.Combine(sourceDirectory, "music")); - } + => true; public override bool IsDownloadingExpansionNeeded(string sourceDirectory) - { - return true; - } + => true; public override async Task CopyOriginalGameFiles( string sourceDirectory,