diff --git a/.gitignore b/.gitignore index a7b8c9b27..0e5438a15 100644 --- a/.gitignore +++ b/.gitignore @@ -38,11 +38,4 @@ 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 b49fdf18d..b8ea28438 100644 --- a/data/tr1/ship/cfg/TR1X_strings.json5 +++ b/data/tr1/ship/cfg/TR1X_strings.json5 @@ -428,11 +428,7 @@ "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", @@ -535,9 +531,7 @@ "PHOTO_MODE_ROTATE_PROMPT": "Rotate camera", "PHOTO_MODE_SNAP_PROMPT": "Take picture", "PHOTO_MODE_TITLE": "Photo Mode", - "SOUND_DIALOG_MUSIC": "\\{icon sound} Sound", - "SOUND_DIALOG_SOUND": "\\{icon music} Music", - "SOUND_DIALOG_TITLE": "Set Volumes", + "SOUND_SET_VOLUMES": "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 2649e668d..5cfda3031 100644 --- a/data/tr2/ship/cfg/TR2X_gameflow.json5 +++ b/data/tr2/ship/cfg/TR2X_gameflow.json5 @@ -217,7 +217,6 @@ ], "injections": [ "data/injections/living_deck_goon_sfx.bin", - "data/injections/living_fd.bin", "data/injections/living_pickup_meshes.bin", "data/injections/seaweed_collision.bin", ], @@ -398,7 +397,6 @@ {"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 5d08c0f49..abc97764d 100644 --- a/data/tr2/ship/cfg/TR2X_strings.json5 +++ b/data/tr2/ship/cfg/TR2X_strings.json5 @@ -547,11 +547,7 @@ "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", @@ -659,9 +655,7 @@ "PHOTO_MODE_ROTATE_PROMPT": "Rotate camera", "PHOTO_MODE_SNAP_PROMPT": "Take picture", "PHOTO_MODE_TITLE": "Photo Mode", - "SOUND_DIALOG_MUSIC": "\\{icon sound} Sound", - "SOUND_DIALOG_SOUND": "\\{icon music} Music", - "SOUND_DIALOG_TITLE": "Set Volumes", + "SOUND_SET_VOLUMES": "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 deleted file mode 100644 index 9972c25aa..000000000 Binary files a/data/tr2/ship/data/injections/explosion.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/living_fd.bin b/data/tr2/ship/data/injections/living_fd.bin deleted file mode 100644 index d23df437d..000000000 Binary files a/data/tr2/ship/data/injections/living_fd.bin and /dev/null differ diff --git a/docs/COMMAND_LINE.md b/docs/COMMAND_LINE.md deleted file mode 100644 index 9fca2d295..000000000 --- a/docs/COMMAND_LINE.md +++ /dev/null @@ -1,20 +0,0 @@ -# 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 efcdc6a13..7ea40e63f 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -266,63 +266,30 @@ 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 are published automatically whenever a new tag is pushed -to the `stable` branch with the help of GitHub actions. -The general workflow is this: +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. -```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 1c9f2deb8..aa61e581f 100644 --- a/docs/MIGRATING.md +++ b/docs/MIGRATING.md @@ -6,11 +6,10 @@ 1. **Update fog configuration** If you wish to force your fog settings on player: - - Rename `draw_distance_fade` to `fog_start` + - Rename `draw_distance_min` 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_fade` and `draw_distance_max` + - Remove `draw_distance_min` and `draw_distance_max` ### Version 4.7 to 4.8 diff --git a/docs/tr1/CHANGELOG.md b/docs/tr1/CHANGELOG.md index 1b54ffcd9..818130279 100644 --- a/docs/tr1/CHANGELOG.md +++ b/docs/tr1/CHANGELOG.md @@ -5,17 +5,12 @@ - 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) -- 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 the `draw_distance_min` 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) -- changed the dev console to no longer add duplicate entries to the history - 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) @@ -26,7 +21,6 @@ - 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) @@ -41,14 +35,9 @@ - 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 e49c78f4e..d128fb4b3 100644 --- a/docs/tr1/README.md +++ b/docs/tr1/README.md @@ -485,7 +485,6 @@ 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 9b8593c9f..1577ff805 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -1,24 +1,4 @@ ## [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) -- changed the dev console to no longer add duplicate entries to the history -- 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 fd4246446..9faec4fd4 100644 --- a/docs/tr2/README.md +++ b/docs/tr2/README.md @@ -221,7 +221,6 @@ 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) @@ -255,10 +254,7 @@ However, you can easily download them manually from these urls: - fixed Floating Islands mystic plaque inventory rotation - fixed pushblocks being rotated when Lara grabs them, most noticeable if asymmetric textures have been used - fixed being able to use hotkeys in the end-level statistics screen -- fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level - fixed destroyed gondolas appearing embedded in the ground after loading a save -- 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 5dee0147b..0d3a4afb8 100644 --- a/src/libtrx/engine/audio_sample.c +++ b/src/libtrx/engine/audio_sample.c @@ -1,6 +1,5 @@ #include "audio_internal.h" -#include "benchmark.h" #include "debug.h" #include "log.h" #include "memory.h" @@ -22,21 +21,12 @@ #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; +#include typedef struct { char *original_data; size_t original_size; + float *sample_data; int32_t channels; int32_t num_samples; @@ -60,8 +50,8 @@ typedef struct { } AUDIO_SAMPLE_SOUND; typedef struct { - const uint8_t *data; - const uint8_t *ptr; + const char *data; + const char *ptr; int32_t size; int32_t remaining; } AUDIO_AV_BUFFER; @@ -74,7 +64,7 @@ static double M_DecibelToMultiplier(double db_gain); static bool M_RecalculateChannelVolumes(int32_t sound_id); static int32_t M_ReadAVBuffer(void *opaque, uint8_t *dst, int32_t dst_size); static int64_t M_SeekAVBuffer(void *opaque, int64_t offset, int32_t whence); -static bool M_ConvertSample(const int32_t sample_id); +static bool M_Convert(const int32_t sample_id); static double M_DecibelToMultiplier(double db_gain) { @@ -145,98 +135,20 @@ static int64_t M_SeekAVBuffer(void *opaque, int64_t offset, int32_t whence) return src->ptr - src->data; } -static int32_t M_OutputAudioFrame( - M_SWR_CONTEXT *const swr, AVFrame *const frame) +static bool M_Convert(const int32_t sample_id) { - // 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 - } + ASSERT(sample_id >= 0 && sample_id < m_LoadedSamplesCount); - 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; + AUDIO_SAMPLE *const sample = &m_LoadedSamples[sample_id]; + + if (sample->sample_data != nullptr) { + return true; + } + + const clock_t time_start = clock(); + size_t working_buffer_size = 0; + float *working_buffer = nullptr; struct { size_t read_buffer_size; @@ -258,20 +170,28 @@ static bool M_ConvertRawData( .frame = nullptr, }; - M_SWR_CONTEXT swr = {}; + struct { + struct { + int32_t format; + AVChannelLayout ch_layout; + int32_t sample_rate; + } src, dst; + SwrContext *ctx; + } swr = {}; + int32_t error_code; - uint8_t *const read_buffer = av_malloc(av.read_buffer_size); - if (read_buffer == nullptr) { + unsigned char *read_buffer = av_malloc(av.read_buffer_size); + if (!read_buffer) { error_code = AVERROR(ENOMEM); goto cleanup; } AUDIO_AV_BUFFER av_buf = { - .data = original_data, - .ptr = original_data, - .size = original_size, - .remaining = original_size, + .data = sample->original_data, + .ptr = sample->original_data, + .size = sample->original_size, + .remaining = sample->original_size, }; av.avio_context = avio_alloc_context( @@ -280,7 +200,8 @@ static bool M_ConvertRawData( av.format_ctx = avformat_alloc_context(); av.format_ctx->pb = av.avio_context; - error_code = avformat_open_input(&av.format_ctx, "mem:", nullptr, nullptr); + error_code = + avformat_open_input(&av.format_ctx, "dummy_filename", nullptr, nullptr); if (error_code != 0) { goto cleanup; } @@ -298,19 +219,19 @@ static bool M_ConvertRawData( break; } } - if (av.stream == nullptr) { + if (!av.stream) { error_code = AVERROR_STREAM_NOT_FOUND; goto cleanup; } av.codec = avcodec_find_decoder(av.stream->codecpar->codec_id); - if (av.codec == nullptr) { + if (!av.codec) { error_code = AVERROR_DEMUXER_NOT_FOUND; goto cleanup; } av.codec_ctx = avcodec_alloc_context3(av.codec); - if (av.codec_ctx == nullptr) { + if (!av.codec_ctx) { error_code = AVERROR(ENOMEM); goto cleanup; } @@ -327,134 +248,166 @@ static bool M_ConvertRawData( } av.packet = av_packet_alloc(); - if (av.packet == nullptr) { + if (!av.packet) { error_code = AVERROR(ENOMEM); goto cleanup; } av.frame = av_frame_alloc(); - if (av.frame == nullptr) { + if (!av.frame) { error_code = AVERROR(ENOMEM); goto cleanup; } - swr.src.sample_rate = av.codec_ctx->sample_rate; - swr.src.ch_layout = av.codec_ctx->ch_layout; - swr.src.format = av.codec_ctx->sample_fmt; - swr.dst.sample_rate = AUDIO_WORKING_RATE; - av_channel_layout_default(&swr.dst.ch_layout, dst_channel_count); - swr.dst.format = Audio_GetAVAudioFormat(AUDIO_WORKING_FORMAT); - swr_alloc_set_opts2( - &swr.ctx, &swr.dst.ch_layout, swr.dst.format, swr.dst.sample_rate, - &swr.src.ch_layout, swr.src.format, swr.src.sample_rate, 0, 0); - if (swr.ctx == nullptr) { - av_packet_unref(av.packet); - error_code = AVERROR(ENOMEM); - goto cleanup; - } - - error_code = swr_init(swr.ctx); - if (error_code != 0) { - av_packet_unref(av.packet); - goto cleanup; - } - - while ((error_code = av_read_frame(av.format_ctx, av.packet)) >= 0) { - M_DecodePacket(av.codec_ctx, av.packet, av.frame, &swr); - av_packet_unref(av.packet); - if (error_code < 0) { + 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); + } + + av_packet_unref(av.packet); } - 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); - } + 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; + const clock_t time_end = clock(); + const double time_delta = + (((double)(time_end - time_start)) / CLOCKS_PER_SEC) * 1000.0f; + LOG_DEBUG( + "Sample %d decoded (%.0f ms)", sample_id, sample->original_size, + time_delta); + cleanup: if (error_code != 0) { - LOG_ERROR("Error while 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); + LOG_ERROR( + "Error while opening sample ID %d: %s", sample_id, + av_err2str(error_code)); } if (swr.ctx) { swr_free(&swr.ctx); } + if (av.frame) { av_frame_free(&av.frame); } + if (av.packet) { av_packet_free(&av.packet); } + av.codec = nullptr; + + if (!result) { + 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; } @@ -600,7 +553,7 @@ int32_t Audio_Sample_Play( continue; } - M_ConvertSample(sample_id); + M_Convert(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 ba80da5f7..bca11ca75 100644 --- a/src/libtrx/engine/audio_stream.c +++ b/src/libtrx/engine/audio_stream.c @@ -84,32 +84,17 @@ 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); - error_code = avformat_seek_file( + avformat_seek_file( stream->av.format_ctx, -1, 0, 0, 0, AVSEEK_FLAG_FRAME); } else { // seek to specific timestamp - 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)); + 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); } } @@ -272,10 +257,6 @@ 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]; @@ -699,51 +680,26 @@ double Audio_Stream_GetDuration(int32_t sound_id) return duration; } -bool Audio_Stream_SeekTimestamp(const int32_t sound_id, const double timestamp) +bool Audio_Stream_SeekTimestamp(int32_t sound_id, double timestamp) { if (!g_AudioDeviceID || sound_id < 0 || sound_id >= AUDIO_MAX_ACTIVE_STREAMS) { return false; } - 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); + 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); SDL_UnlockAudioDevice(g_AudioDeviceID); - return false; + return true; } - 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; + return false; } 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 88cf557f3..b140bfc30 100644 --- a/src/libtrx/enum_map.c +++ b/src/libtrx/enum_map.c @@ -121,25 +121,3 @@ 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 bd2f31bca..4eccfe895 100644 --- a/src/libtrx/game/console/cmd/config.c +++ b/src/libtrx/game/console/cmd/config.c @@ -14,7 +14,6 @@ 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); @@ -77,56 +76,6 @@ 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 @@ -312,16 +261,18 @@ 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); - return CR_SUCCESS; + result = CR_SUCCESS; + } else { + result = CR_FAILURE; } - return CR_FAILURE; + return result; } - COMMAND_RESULT result; if (Console_Cmd_Config_SetCurrentValue(option, new_value)) { Config_Write(); @@ -329,15 +280,6 @@ 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/console/history.c b/src/libtrx/game/console/history.c index 07dbb7af1..8a5921078 100644 --- a/src/libtrx/game/console/history.c +++ b/src/libtrx/game/console/history.c @@ -5,8 +5,6 @@ #include "utils.h" #include "vector.h" -#include - #define MAX_HISTORY_ENTRIES 30 VECTOR *m_History = nullptr; @@ -88,21 +86,12 @@ void Console_History_Clear(void) void Console_History_Append(const char *const prompt) { - for (int32_t i = m_History->count - 1; i >= 0; i--) { - char *const entry = *(char **)Vector_Get(m_History, i); - if (strcmp(entry, prompt) == 0) { - Memory_Free(entry); - Vector_RemoveAt(m_History, i); - } - } - if (m_History->count == MAX_HISTORY_ENTRIES) { - char *const oldest = *(char **)Vector_Get(m_History, 0); - Memory_Free(oldest); + char *const prompt = *(char **)Vector_Get(m_History, 0); + Memory_Free(prompt); Vector_RemoveAt(m_History, 0); } - - char *const prompt_copy = Memory_DupStr(prompt); + char *prompt_copy = Memory_DupStr(prompt); Vector_Add(m_History, &prompt_copy); } diff --git a/src/libtrx/game/game_flow/reader.c b/src/libtrx/game/game_flow/reader.c index 507e13e64..77ff36e2e 100644 --- a/src/libtrx/game/game_flow/reader.c +++ b/src/libtrx/game/game_flow/reader.c @@ -281,10 +281,6 @@ 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 @@ -292,6 +288,11 @@ 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 3d31c1738..d3f8327dc 100644 --- a/src/libtrx/game/game_string_table/common.c +++ b/src/libtrx/game/game_string_table/common.c @@ -1,11 +1,9 @@ #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" @@ -13,6 +11,8 @@ 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,8 +27,6 @@ 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); @@ -92,19 +90,17 @@ 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); } } -static void M_ApplyLayer( - const GF_LEVEL *const level, const GS_FILE *const gs_file) +void GameStringTable_Apply(const GF_LEVEL *const level) { + 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++) { @@ -124,50 +120,16 @@ static void M_ApplyLayer( } } - if (gs_level_table != nullptr && gs_level_table->count != 0) { + if (gs_level_table != nullptr) { 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) { - 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); + GS_File_Free(&g_GST_File); } diff --git a/src/libtrx/game/game_string_table/priv.c b/src/libtrx/game/game_string_table/priv.c index a9bada043..0b62cc41b 100644 --- a/src/libtrx/game/game_string_table/priv.c +++ b/src/libtrx/game/game_string_table/priv.c @@ -52,5 +52,4 @@ 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 9533c5989..00d69b7f0 100644 --- a/src/libtrx/game/game_string_table/priv.h +++ b/src/libtrx/game/game_string_table/priv.h @@ -1,7 +1,6 @@ #pragma once #include "game/game_flow/enum.h" -#include "vector.h" #include @@ -36,7 +35,7 @@ typedef struct { GS_LEVEL_TABLE level_tables[GFLT_NUMBER_OF]; } GS_FILE; -void GS_Table_Free(GS_TABLE *gs_table); +extern GS_FILE g_GST_File; -GS_FILE *GS_File_CreateFromString(const char *data, bool load_levels); +void GS_Table_Free(GS_TABLE *gs_table); 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 5ded75216..4cec681d4 100644 --- a/src/libtrx/game/game_string_table/reader.c +++ b/src/libtrx/game/game_string_table/reader.c @@ -1,3 +1,4 @@ +#include "filesystem.h" #include "game/game_flow.h" #include "game/game_string_table.h" #include "game/game_string_table/priv.h" @@ -129,14 +130,24 @@ static void M_LoadLevelsFromJSON( } } -GS_FILE *GS_File_CreateFromString( - const char *const data, const bool load_levels) +void GameStringTable_LoadFromFile(const char *const path) { - GS_FILE *const gs_file = Memory_Alloc(sizeof(GS_FILE)); + 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; JSON_PARSE_RESULT parse_result; - - JSON_VALUE *root = JSON_ParseEx( + root = JSON_ParseEx( data, strlen(data), JSON_PARSE_FLAGS_ALLOW_JSON5, nullptr, nullptr, &parse_result); if (root == nullptr) { @@ -146,16 +157,15 @@ GS_FILE *GS_File_CreateFromString( 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); - 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); - } + 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 f92b67299..a2698b37b 100644 --- a/src/libtrx/game/items.c +++ b/src/libtrx/game/items.c @@ -219,11 +219,15 @@ int32_t Item_GlobalReplace( { int32_t changed = 0; - 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++; + 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; } } diff --git a/src/libtrx/game/lara/common.c b/src/libtrx/game/lara/common.c index ad6136f5a..2f2274ede 100644 --- a/src/libtrx/game/lara/common.c +++ b/src/libtrx/game/lara/common.c @@ -2,9 +2,7 @@ #include "game/const.h" #include "game/item_actions.h" -#include "game/lara/const.h" -#include "game/matrix.h" -#include "game/rooms.h" +#include "game/rooms/const.h" void Lara_Animate(ITEM *const item) { @@ -158,88 +156,3 @@ 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 19d259fdb..c3e60a6e6 100644 --- a/src/libtrx/game/music.c +++ b/src/libtrx/game/music.c @@ -2,16 +2,6 @@ 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/libtrx/game/rooms/common.c b/src/libtrx/game/rooms/common.c index 0dc525142..b10c7ceb7 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 int32_t height) +bool Room_IsAbyssHeight(const int16_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 54a5a0653..1a29106ab 100644 --- a/src/libtrx/game/shell/main.c +++ b/src/libtrx/game/shell/main.c @@ -6,18 +6,28 @@ #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(); - int32_t exit_code = Shell_Main(); - Shell_Terminate(exit_code); - return exit_code; + Shell_Main(); + Shell_Terminate(0); + return 0; } diff --git a/src/libtrx/game/ui/common.c b/src/libtrx/game/ui/common.c index 159eb7190..2ffa2c32b 100644 --- a/src/libtrx/game/ui/common.c +++ b/src/libtrx/game/ui/common.c @@ -4,10 +4,8 @@ #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 @@ -203,23 +201,3 @@ 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 903fe6a11..512f4b3e1 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, 8.0f); + UI_BeginPad(8.0f, 10.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 deleted file mode 100644 index b5f382308..000000000 --- a/src/libtrx/game/ui/dialogs/sound_settings.c +++ /dev/null @@ -1,176 +0,0 @@ -#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 f74030b87..9aec8632e 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(); + node->measure_h = UI_GetCanvasHeight() - TEXT_HEIGHT_FIXED; } 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 6638b775b..d8a460a33 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 != nullptr) { + while (child != NULL) { 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 12382e49f..d0fecff85 100644 --- a/src/libtrx/include/libtrx/enum_map.h +++ b/src/libtrx/include/libtrx/enum_map.h @@ -1,4 +1,4 @@ -#include "vector.h" +#include #define ENUM_MAP_DEFINE(enum_name, enum_value, str_value) \ EnumMap_Define(ENUM_MAP_NAME(enum_name), enum_value, str_value); @@ -18,13 +18,6 @@ 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 7ce161412..63a70fe0d 100644 --- a/src/libtrx/include/libtrx/game/game_string.def +++ b/src/libtrx/include/libtrx/game/game_string.def @@ -34,10 +34,6 @@ 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") @@ -136,9 +132,7 @@ 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_DIALOG_TITLE, "Set Volumes") -GS_DEFINE(SOUND_DIALOG_SOUND, "\\{icon music} Music") -GS_DEFINE(SOUND_DIALOG_MUSIC, "\\{icon sound} Sound") +GS_DEFINE(SOUND_SET_VOLUMES, "Set Volumes") 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 cc33f3fe4..b14a99c97 100644 --- a/src/libtrx/include/libtrx/game/game_string_table.h +++ b/src/libtrx/include/libtrx/game/game_string_table.h @@ -2,8 +2,7 @@ #include -void GameStringTable_Init(void); -void GameStringTable_Shutdown(void); - -void GameStringTable_Load(const char *path, bool load_levels); +void GameStringTable_LoadFromFile(const char *path); +void GameStringTable_LoadFromString(const char *data); void GameStringTable_Apply(const GF_LEVEL *level); +void GameStringTable_Shutdown(void); diff --git a/src/libtrx/include/libtrx/game/lara/common.h b/src/libtrx/include/libtrx/game/lara/common.h index 82e2f7357..068634600 100644 --- a/src/libtrx/include/libtrx/game/lara/common.h +++ b/src/libtrx/include/libtrx/game/lara/common.h @@ -15,5 +15,3 @@ 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 1e6ee1f85..68c3c3838 100644 --- a/src/libtrx/include/libtrx/game/music/common.h +++ b/src/libtrx/include/libtrx/game/music/common.h @@ -31,15 +31,3 @@ 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 18de887cd..f65578f14 100644 --- a/src/libtrx/include/libtrx/game/objects/common.h +++ b/src/libtrx/include/libtrx/game/objects/common.h @@ -36,7 +36,6 @@ 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 c22f3c549..88b289dcd 100644 --- a/src/libtrx/include/libtrx/game/objects/types.h +++ b/src/libtrx/include/libtrx/game/objects/types.h @@ -38,12 +38,14 @@ 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; @@ -64,8 +66,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); - const OBJECT_BOUNDS *(*bounds_func)(void); #if TR_VERSION == 1 + const OBJECT_BOUNDS *(*bounds_func)(void); 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 19539f1ce..1d24b62b7 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(int32_t height); +bool Room_IsAbyssHeight(int16_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 1ed37747f..3128bda38 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 int32_t Shell_Main(void); +extern void 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 144a322d8..374320fe5 100644 --- a/src/libtrx/include/libtrx/game/sound/common.h +++ b/src/libtrx/include/libtrx/game/sound/common.h @@ -14,11 +14,6 @@ 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 fd8a7a341..811c320db 100644 --- a/src/libtrx/include/libtrx/game/ui.h +++ b/src/libtrx/include/libtrx/game/ui.h @@ -10,7 +10,6 @@ #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 015dfc5f3..6400264f1 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) -int32_t UI_GetCanvasWidth(void); -int32_t UI_GetCanvasHeight(void); -float UI_ScaleX(float x); -float UI_ScaleY(float y); +extern int32_t UI_GetCanvasWidth(void); +extern int32_t UI_GetCanvasHeight(void); +extern float UI_ScaleX(float x); +extern 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 deleted file mode 100644 index d3bbd128d..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/sound_settings.h +++ /dev/null @@ -1,21 +0,0 @@ -// 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 d93000c31..d3866bca2 100644 --- a/src/libtrx/meson.build +++ b/src/libtrx/meson.build @@ -176,7 +176,6 @@ 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', @@ -217,7 +216,6 @@ 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 bb5803ccb..760b66a9b 100644 --- a/src/tr1/game/inventory_ring/control.c +++ b/src/tr1/game/inventory_ring/control.c @@ -694,6 +694,7 @@ 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( @@ -712,7 +713,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 fdb607cbb..bcc068aa1 100644 --- a/src/tr1/game/items.c +++ b/src/tr1/game/items.c @@ -196,6 +196,70 @@ 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 c94893dfc..a9bc07b68 100644 --- a/src/tr1/game/items.h +++ b/src/tr1/game/items.h @@ -11,6 +11,9 @@ 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 6596d42fc..4e1c95406 100644 --- a/src/tr1/game/lara/common.c +++ b/src/tr1/game/lara/common.c @@ -701,6 +701,16 @@ 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 620b0c31e..291ab20e9 100644 --- a/src/tr1/game/lara/common.h +++ b/src/tr1/game/lara/common.h @@ -26,6 +26,8 @@ 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 6a0467c8a..c4700d9f1 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); } -int32_t Music_GetVolume(void) +int16_t Music_GetVolume(void) { return m_Volume; } -void Music_SetVolume(int32_t volume) +void Music_SetVolume(int16_t volume) { if (volume != m_Volume) { m_Volume = volume; @@ -195,6 +195,16 @@ void Music_SetVolume(int32_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 32957af65..97de0c9ad 100644 --- a/src/tr1/game/music.h +++ b/src/tr1/game/music.h @@ -19,6 +19,18 @@ 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 5b9da9fdc..dd39f97e8 100644 --- a/src/tr1/game/objects/common.h +++ b/src/tr1/game/objects/common.h @@ -12,6 +12,7 @@ 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/libtrx/game/objects/general/door.c b/src/tr1/game/objects/general/door.c similarity index 73% rename from src/libtrx/game/objects/general/door.c rename to src/tr1/game/objects/general/door.c index e81c4ca39..5cbe0bfcc 100644 --- a/src/libtrx/game/objects/general/door.c +++ b/src/tr1/game/objects/general/door.c @@ -1,16 +1,14 @@ -#include "game/objects/general/door.h" - -#include "game/game_buf.h" +#include "game/box.h" +#include "game/items.h" #include "game/lara/common.h" #include "game/objects/common.h" -#include "game/pathing.h" -#include "game/rooms.h" +#include "game/room.h" +#include "global/vars.h" -typedef struct { - SECTOR *sector; - SECTOR old_sector; - int16_t box_num; -} DOORPOS_DATA; +#include +#include +#include +#include typedef struct { DOORPOS_DATA d1; @@ -24,6 +22,7 @@ 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); @@ -43,17 +42,38 @@ 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. - const ITEM *const lara = Lara_GetItem(); - if (lara == nullptr) { + if (g_LaraItem == nullptr) { return false; } - int16_t room_num = lara->room_num; - const SECTOR *const lara_sector = - Room_GetSector(lara->pos.x, lara->pos.y, lara->pos.z, &room_num); + int16_t room_num = g_LaraItem->room_num; + const SECTOR *const lara_sector = Room_GetSector( + g_LaraItem->pos.x, g_LaraItem->pos.y, g_LaraItem->pos.z, &room_num); return lara_sector == sector; } @@ -70,22 +90,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 (d->sector == nullptr) { + if (sector == nullptr) { return; } - sector->idx = 0; sector->box = NO_BOX; - sector->ceiling.height = NO_HEIGHT; sector->floor.height = NO_HEIGHT; + sector->ceiling.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.sky = NO_ROOM; + sector->portal_room.pit = NO_ROOM; sector->portal_room.wall = NO_ROOM; - const int16_t box_num = d->box_num; + const int16_t box_num = d->block; if (box_num != NO_BOX) { Box_GetBox(box_num)->overlap_index |= BOX_BLOCKED; } @@ -93,55 +113,34 @@ static void M_Shut(DOORPOS_DATA *const d) static void M_Open(DOORPOS_DATA *const d) { - if (d->sector == nullptr) { + // Restore the level geometry so that the door tile is passable. + SECTOR *const sector = d->sector; + if (!sector) { return; } - *d->sector = d->old_sector; + *sector = d->old_sector; - const int16_t box_num = d->box_num; + 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; - 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_flags = 1; obj->save_anim = 1; + obj->save_flags = 1; } static void M_Initialise(const int16_t item_num) { ITEM *const item = Item_Get(item_num); - DOOR_DATA *door = GameBuf_Alloc(sizeof(DOOR_DATA), GBUF_ITEM_DATA); + DOOR_DATA *const door = GameBuf_Alloc(sizeof(DOOR_DATA), GBUF_ITEM_DATA); item->data = door; int32_t dx = 0; @@ -160,7 +159,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 == NO_ROOM_NEG) { + if (room->flipped_room == -1) { door->d1flip.sector = nullptr; } else { room = Room_Get(room->flipped_room); @@ -174,29 +173,30 @@ static void M_Initialise(const int16_t item_num) 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); - } - - 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; + return; } + + room = Room_Get(room_num); + M_InitialisePortal(room, item, 0, 0, &door->d2); + if (room->flipped_room == -1) { + door->d2flip.sector = nullptr; + } else { + room = Room_Get(room->flipped_room); + M_InitialisePortal(room, item, 0, 0, &door->d2flip); + } + + M_Shut(&door->d2); + M_Shut(&door->d2flip); + + const int16_t prev_room = item->room_num; + Item_NewRoom(item_num, room_num); + item->room_num = prev_room; } static void M_Control(const int16_t item_num) { ITEM *const item = Item_Get(item_num); - DOOR_DATA *const door = item->data; + DOOR_DATA *door = item->data; if (Item_IsTriggerActive(item)) { if (item->current_anim_state == DOOR_STATE_CLOSED) { @@ -225,25 +225,21 @@ static void M_Control(const int16_t item_num) Item_Animate(item); } -void Door_Collision( - const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) +void Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll) { ITEM *const item = Item_Get(item_num); - - if (!Item_TestBoundsCollide(item, lara_item, coll->radius)) { + if (!Lara_TestBoundsCollide(item, coll->radius)) { return; } - if (!Collide_TestCollision(item, lara_item)) { return; } - if (coll->enable_baddie_push) { - Lara_Push( - item, coll, - coll->enable_hit - && item->current_anim_state != item->goal_anim_state, - true); + if (item->current_anim_state != item->goal_anim_state) { + Lara_Push(item, coll, coll->enable_hit, true); + } else { + Lara_Push(item, coll, false, true); + } } } diff --git a/src/tr1/game/option/option.c b/src/tr1/game/option/option.c index ca778692f..957cc80d6 100644 --- a/src/tr1/game/option/option.c +++ b/src/tr1/game/option/option.c @@ -168,9 +168,6 @@ 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 0777a6d6f..0af6b0a3c 100644 --- a/src/tr1/game/option/option_sound.c +++ b/src/tr1/game/option/option_sound.c @@ -1,48 +1,170 @@ #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 -typedef struct { - UI_SOUND_SETTINGS_STATE *ui; -} M_PRIV; +#include -static M_PRIV 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 void M_Init(M_PRIV *const p) +static TEXTSTRING *m_Text[TEXT_NUMBER_OF] = {}; + +static void M_InitText(void); + +static void M_InitText(void) { - p->ui = UI_SoundSettings_Init(); -} + char buf[20]; -static void M_Shutdown(M_PRIV *const p) -{ - if (p->ui != nullptr) { - UI_SoundSettings_Free(p->ui); - p->ui = nullptr; + 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); } } -void Option_Sound_Control(INVENTORY_ITEM *const inv_item, const bool is_busy) +void Option_Sound_Control(INVENTORY_ITEM *inv_item, const bool is_busy) { - 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); + char buf[20]; + + if (!m_Text[TEXT_MUSIC_VOLUME]) { + M_InitText(); + } + + 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_Shutdown(void) { - M_Shutdown(&m_Priv); + for (int i = 0; i < TEXT_NUMBER_OF; i++) { + Text_Remove(m_Text[i]); + m_Text[i] = nullptr; + } } diff --git a/src/tr1/game/option/option_sound.h b/src/tr1/game/option/option_sound.h index ee5f63ba7..9128617d5 100644 --- a/src/tr1/game/option/option_sound.h +++ b/src/tr1/game/option/option_sound.h @@ -3,5 +3,4 @@ #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 c63c73c94..9bed2681b 100644 --- a/src/tr1/game/output.c +++ b/src/tr1/game/output.c @@ -49,7 +49,6 @@ 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 }; @@ -386,11 +385,6 @@ 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; @@ -412,11 +406,6 @@ 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 da1fdea06..f892719ba 100644 --- a/src/tr1/game/shell.c +++ b/src/tr1/game/shell.c @@ -81,18 +81,37 @@ static SHELL_ARGS m_Args = { static const char *m_CurrentGameFlowPath; -static void M_ShowHelp(void); +static void M_ParseArgs(SHELL_ARGS *out_args); static void M_LoadConfig(void); static void M_HandleConfigChange(const EVENT *event, void *data); -static void M_ShowHelp(void) +static void M_ParseArgs(SHELL_ARGS *const out_args) { - 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)."); + 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--; + } + } + } } static void M_HandleConfigChange(const EVENT *const event, void *const data) @@ -133,8 +152,6 @@ void Shell_Shutdown(void) Console_Shutdown(); GameBuf_Shutdown(); Savegame_Shutdown(); - - GameStringTable_Shutdown(); GF_Shutdown(); Output_Shutdown(); @@ -157,40 +174,10 @@ const char *Shell_GetGameFlowPath(void) return m_ModPaths[m_Args.mod].game_flow_path; } -bool Shell_ParseArgs(const int32_t arg_count, const char **args) +void Shell_Main(void) { - SHELL_ARGS *const out_args = &m_Args; - out_args->mod = M_MOD_OG; + M_ParseArgs(&m_Args); - 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(); @@ -213,17 +200,13 @@ int32_t Shell_Main(void) if (!Output_Init()) { Shell_ExitSystem("Could not initialise video system"); - return 1; + return; } Screen_Init(); GF_Init(); GF_LoadFromFile(m_ModPaths[m_Args.mod].game_flow_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_LoadFromFile(m_ModPaths[m_Args.mod].game_strings_path); GameStringTable_Apply(nullptr); Savegame_Init(); @@ -329,7 +312,6 @@ int32_t 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 7728f2465..3f479d368 100644 --- a/src/tr1/game/sound.c +++ b/src/tr1/game/sound.c @@ -528,6 +528,18 @@ 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; @@ -538,18 +550,6 @@ 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 9a403a6c6..4cd892222 100644 --- a/src/tr1/game/sound.h +++ b/src/tr1/game/sound.h @@ -13,6 +13,10 @@ 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 new file mode 100644 index 000000000..b3cdf81f9 --- /dev/null +++ b/src/tr1/game/ui/common.c @@ -0,0 +1,24 @@ +#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 6b7384d57..54dd99625 100644 --- a/src/tr1/global/types.h +++ b/src/tr1/global/types.h @@ -123,6 +123,12 @@ 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 a047f9ce8..277258aec 100644 --- a/src/tr1/meson.build +++ b/src/tr1/meson.build @@ -197,6 +197,7 @@ 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', @@ -260,6 +261,7 @@ 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 5f1b04969..900198691 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_MAIN].current = ring->current_object; - } else { + if (ring->type != RT_MAIN) { g_InvRing_Source[RT_OPTION].current = ring->current_object; + } else { + g_InvRing_Source[RT_MAIN].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] = -1; + g_Inv_ExtraData[i] = 0; } g_InvRing_Source[RT_MAIN].current = 0; diff --git a/src/tr2/game/items.c b/src/tr2/game/items.c index 09c7b16ca..a66bb7eb3 100644 --- a/src/tr2/game/items.c +++ b/src/tr2/game/items.c @@ -20,6 +20,40 @@ 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(); @@ -154,6 +188,95 @@ 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 c82d6ec0c..6a26aa675 100644 --- a/src/tr2/game/items.h +++ b/src/tr2/game/items.h @@ -8,6 +8,10 @@ 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 65911e138..15a9e8cc0 100644 --- a/src/tr2/game/objects/common.h +++ b/src/tr2/game/objects/common.h @@ -6,6 +6,7 @@ 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 e0b0120a3..50a4980e6 100644 --- a/src/tr2/game/objects/general/detonator.c +++ b/src/tr2/game/objects/general/detonator.c @@ -10,37 +10,33 @@ #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 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 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_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(); @@ -68,14 +64,12 @@ 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; } @@ -108,7 +102,6 @@ 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; @@ -124,11 +117,16 @@ static void M_Collision( goto normal_collision; } - if (!Lara_TestPosition(item, obj->bounds_func())) { - goto normal_collision; - } - if (item->object_id == O_DETONATOR_1) { - item->rot = old_rot; + 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 (g_Inv_Chosen == NO_OBJECT) { @@ -140,7 +138,7 @@ static void M_Collision( } Inv_RemoveItem(O_KEY_OPTION_2); - Lara_AlignPosition(item, &m_DetonatorPosition); + Item_AlignPosition(&m_DetonatorPosition, item, lara_item); 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 new file mode 100644 index 000000000..819c8fb5f --- /dev/null +++ b/src/tr2/game/objects/general/door.c @@ -0,0 +1,222 @@ +#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 fe893694e..0a89767cb 100644 --- a/src/tr2/game/objects/general/flare_item.c +++ b/src/tr2/game/objects/general/flare_item.c @@ -6,7 +6,6 @@ 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 c9b44a88f..359d23110 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 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 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_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); - Lara_AlignPosition(keyhole_item, &m_KeyholePosition); + Item_AlignPosition(&m_KeyholePosition, keyhole_item, lara_item); lara_item->goal_anim_state = LS_USE_KEY; do { Lara_Animate(lara_item); @@ -71,7 +71,6 @@ 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; } @@ -83,13 +82,12 @@ 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 (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_KeyholeBounds, item, lara_item)) { return; } diff --git a/src/tr2/game/objects/general/movable_block.c b/src/tr2/game/objects/general/movable_block.c index 3dbea88e8..63f0f1b2b 100644 --- a/src/tr2/game/objects/general/movable_block.c +++ b/src/tr2/game/objects/general/movable_block.c @@ -20,18 +20,21 @@ typedef enum { MOVABLE_BLOCK_STATE_PULL = 3, } MOVABLE_BLOCK_STATE; -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 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_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); @@ -44,11 +47,6 @@ 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) { @@ -201,7 +199,6 @@ 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; @@ -306,7 +303,7 @@ static void M_Collision( break; } - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_MovableBlockBounds, item, lara_item)) { return; } @@ -349,7 +346,7 @@ static void M_Collision( } else if ( Item_TestAnimEqual(lara_item, LA_PUSHABLE_GRAB) && Item_TestFrameEqual(lara_item, LF_PPREADY)) { - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_MovableBlockBounds, item, lara_item)) { return; } diff --git a/src/tr2/game/objects/general/pickup.c b/src/tr2/game/objects/general/pickup.c index 2f1853f3f..5d0910541 100644 --- a/src/tr2/game/objects/general/pickup.c +++ b/src/tr2/game/objects/general/pickup.c @@ -26,29 +26,41 @@ #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 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 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 void M_DoPickup(int16_t item_num); @@ -95,14 +107,13 @@ 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 (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(g_PickupBounds, item, lara_item)) { goto cleanup; } @@ -134,7 +145,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 { - Lara_AlignPosition(item, &m_PickupPosition); + Item_AlignPosition(&m_PickupPosition, item, lara_item); lara_item->goal_anim_state = LS_PICKUP; do { Lara_Animate(lara_item); @@ -152,14 +163,13 @@ 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 (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_PickupBoundsUW, item, lara_item)) { goto cleanup; } @@ -214,7 +224,6 @@ 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; @@ -334,15 +343,6 @@ 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 1b1a51fff..dfac91fc3 100644 --- a/src/tr2/game/objects/general/pickup.h +++ b/src/tr2/game/objects/general/pickup.h @@ -2,6 +2,7 @@ #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 f63db4c5c..174755780 100644 --- a/src/tr2/game/objects/general/puzzle_hole.c +++ b/src/tr2/game/objects/general/puzzle_hole.c @@ -17,18 +17,23 @@ static XYZ_32 m_PuzzleHolePosition = { .z = WALL_L / 2 - LARA_RADIUS - 85, }; -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 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_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); @@ -38,11 +43,6 @@ 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); - Lara_AlignPosition(puzzle_hole_item, &m_PuzzleHolePosition); + Item_AlignPosition(&m_PuzzleHolePosition, puzzle_hole_item, lara_item); lara_item->goal_anim_state = LS_USE_PUZZLE; do { Lara_Animate(lara_item); @@ -82,7 +82,6 @@ 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; } @@ -104,11 +103,10 @@ 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 - || !Lara_TestPosition(item, obj->bounds_func()) + || !Item_TestPosition(m_PuzzleHoleBounds, item, lara_item) || !Item_TestFrameEqual(lara_item, LF_USE_PUZZLE)) { return; } @@ -122,7 +120,7 @@ static void M_Collision( return; } - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_PuzzleHoleBounds, item, lara_item)) { return; } diff --git a/src/tr2/game/objects/general/switch.c b/src/tr2/game/objects/general/switch.c index 382425e6f..3af8f70e2 100644 --- a/src/tr2/game/objects/general/switch.c +++ b/src/tr2/game/objects/general/switch.c @@ -17,30 +17,40 @@ 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 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_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_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 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_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); @@ -52,30 +62,19 @@ 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: - Lara_AlignPosition(switch_item, &m_AirlockPosition); + Item_AlignPosition(&m_AirlockPosition, switch_item, lara_item); break; case O_SWITCH_TYPE_SMALL: - Lara_AlignPosition(switch_item, &g_SmallSwitchPosition); + Item_AlignPosition(&g_SmallSwitchPosition, switch_item, lara_item); break; case O_SWITCH_TYPE_BUTTON: - Lara_AlignPosition(switch_item, &g_PushSwitchPosition); + Item_AlignPosition(&g_PushSwitchPosition, switch_item, lara_item); break; } } @@ -140,35 +139,33 @@ 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 - || !Lara_TestPosition(item, obj->bounds_func())) { + || !Item_TestPosition(m_SwitchBounds, item, lara_item)) { return; } + lara_item->rot.y = item->rot.y; + if (item->object_id == O_SWITCH_TYPE_AIRLOCK && item->current_anim_state == SWITCH_STATE_ON) { return; @@ -196,7 +193,6 @@ 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 @@ -205,7 +201,7 @@ static void M_CollisionUW( return; } - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_SwitchBoundsUW, item, lara_item)) { return; } diff --git a/src/tr2/game/objects/general/zipline.c b/src/tr2/game/objects/general/zipline.c index 0effd9bb6..6dd04d867 100644 --- a/src/tr2/game/objects/general/zipline.c +++ b/src/tr2/game/objects/general/zipline.c @@ -22,35 +22,33 @@ static XYZ_32 m_ZiplineHandlePosition = { .y = 0, .z = WALL_L / 2 - 141, }; - -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 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_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; @@ -151,12 +149,11 @@ static void M_Collision( return; } - const OBJECT *const obj = Object_Get(item->object_id); - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_ZiplineHandleBounds, item, lara_item)) { return; } - Lara_AlignPosition(item, &m_ZiplineHandlePosition); + Item_AlignPosition(&m_ZiplineHandlePosition, item, lara_item); 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 1da73aebc..1c56e5f78 100644 --- a/src/tr2/game/option/option_passport.c +++ b/src/tr2/game/option/option_passport.c @@ -66,8 +66,6 @@ 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); @@ -320,17 +318,6 @@ 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(); @@ -426,19 +413,18 @@ void Option_Passport_Control(INVENTORY_ITEM *const item, const bool is_busy) InvRing_RemoveAllText(); - if (M_IsFlipping(item)) { + 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) { return; } - m_State.current_page = M_GetCurrentPage(item); + m_State.current_page = page; 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 f5dec720f..f6cfab736 100644 --- a/src/tr2/game/option/option_sound.c +++ b/src/tr2/game/option/option_sound.c @@ -1,48 +1,132 @@ +#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 -typedef struct { - UI_SOUND_SETTINGS_STATE *ui; -} M_PRIV; +#include -static M_PRIV m_Priv = {}; +static TEXTSTRING *m_SoundText[4]; -static void M_Init(M_PRIV *const p) +static void M_InitText(void); +static void M_ShutdownText(void); + +static void M_InitText(void) { - p->ui = UI_SoundSettings_Init(); -} + CLAMPG(g_Config.audio.music_volume, 10); + CLAMPG(g_Config.audio.sound_volume, 10); -static void M_Shutdown(M_PRIV *const p) -{ - if (p->ui != nullptr) { - UI_SoundSettings_Free(p->ui); - p->ui = nullptr; + 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); } } -void Option_Sound_Control(INVENTORY_ITEM *const inv_item, const bool is_busy) +static void M_ShutdownText(void) { - 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); + for (int32_t i = 0; i < 4; i++) { + Text_Remove(m_SoundText[i]); + m_SoundText[i] = nullptr; } } void Option_Sound_Shutdown(void) { - M_Shutdown(&m_Priv); + 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) +{ } diff --git a/src/tr2/game/render/common.c b/src/tr2/game/render/common.c index 82b232443..bcfb617ff 100644 --- a/src/tr2/game/render/common.c +++ b/src/tr2/game/render/common.c @@ -45,6 +45,7 @@ static RENDERER *M_GetRenderer(void) } else if (g_Config.rendering.render_mode == RM_HARDWARE) { r = &m_Renderer_HW; } + ASSERT(r != nullptr); return r; } @@ -123,6 +124,7 @@ 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 b40b7e758..f7d00b73d 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_ShowHelp(void); +static void M_ParseArgs(SHELL_ARGS *out_args); static void M_LoadConfig(void); static void M_HandleConfigChange(const EVENT *event, void *data); @@ -342,13 +342,30 @@ static bool M_CreateGameWindow(void) return true; } -static void M_ShowHelp(void) +static void M_ParseArgs(SHELL_ARGS *const out_args) { - 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)."); + 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--; + } + } + } } static void M_LoadConfig(void) @@ -416,39 +433,10 @@ static void M_HandleConfigChange(const EVENT *const event, void *const data) } } -bool Shell_ParseArgs(const int32_t arg_count, const char **args) -{ - SHELL_ARGS *const out_args = &m_Args; - out_args->mod = M_MOD_OG; - - for (int32_t i = 0; i < arg_count; i++) { - if (!strcmp(args[i], "-h") || !strcmp(args[i], "--help")) { - M_ShowHelp(); - return false; - } - if (!strcmp(args[i], "-g") || !strcmp(args[i], "--gold") - || !strcmp(args[i], "-gold")) { - out_args->mod = M_MOD_GM; - } - if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level")) - && i + 1 < arg_count) { - out_args->level_to_play = args[i + 1]; - out_args->mod = M_MOD_CUSTOM_LEVEL; - } - if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save")) - && i + 1 < arg_count) { - if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) { - out_args->save_to_load--; - } - } - } - return true; -} - // TODO: refactor the hell out of me -int32_t Shell_Main(void) +void Shell_Main(void) { - LOG_INFO("Game directory: %s", File_GetGameDirectory()); + M_ParseArgs(&m_Args); if (m_Args.mod == M_MOD_GM) { Object_Get(O_MONK_3)->setup_func = Monk3_Setup; @@ -477,7 +465,7 @@ int32_t Shell_Main(void) if (!M_CreateGameWindow()) { Shell_ExitSystem("Failed to create game window"); - return 1; + return; } Random_Seed(); @@ -490,12 +478,7 @@ int32_t Shell_Main(void) GF_Init(); GF_LoadFromFile(m_ModPaths[m_Args.mod].game_flow_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_LoadFromFile(m_ModPaths[m_Args.mod].game_strings_path); GameStringTable_Apply(nullptr); GameBuf_Init(); @@ -573,7 +556,7 @@ int32_t Shell_Main(void) if (gf_cmd.action == GF_NOOP || gf_cmd.action == GF_EXIT_TO_TITLE) { Shell_ExitSystem("Title disabled & no replacement"); - return 1; + return; } } else { gf_cmd = GF_RunTitle(); @@ -595,14 +578,12 @@ int32_t 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(); @@ -610,7 +591,6 @@ 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 53b5e88ca..8b0fc32ad 100644 --- a/src/tr2/game/sound.h +++ b/src/tr2/game/sound.h @@ -9,6 +9,9 @@ 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 new file mode 100644 index 000000000..f4b10ec67 --- /dev/null +++ b/src/tr2/game/ui/common.c @@ -0,0 +1,25 @@ +#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 424039f73..245e1c11c 100644 --- a/src/tr2/game/viewport.c +++ b/src/tr2/game/viewport.c @@ -125,23 +125,20 @@ void Viewport_Reset(void) VIEWPORT *const vp = &m_Viewport; switch (g_Config.rendering.aspect_mode) { case AM_4_3: - vp->render_ar.w = 4; - vp->render_ar.h = 3; + vp->render_ar = 4.0 / 3.0; break; case AM_16_9: - vp->render_ar.w = 16; - vp->render_ar.h = 9; + vp->render_ar = 16.0 / 9.0; break; case AM_ANY: - vp->render_ar.w = size.w; - vp->render_ar.h = size.h; + vp->render_ar = size.w / (double)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.w / vp->render_ar.h; + vp->width = vp->height * vp->render_ar; } vp->near_z = Output_GetNearZ() >> W2V_SHIFT; diff --git a/src/tr2/game/viewport.h b/src/tr2/game/viewport.h index 8ec3d7485..9af7d0105 100644 --- a/src/tr2/game/viewport.h +++ b/src/tr2/game/viewport.h @@ -8,10 +8,7 @@ typedef struct { int32_t near_z; int32_t far_z; int16_t view_angle; - struct { - int32_t w; - int32_t h; - } render_ar; + double 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 59ecb25d0..8427402b1 100644 --- a/src/tr2/global/types_decomp.h +++ b/src/tr2/global/types_decomp.h @@ -120,6 +120,12 @@ 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, @@ -151,6 +157,13 @@ 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 b3d748095..ea203d6bf 100644 --- a/src/tr2/meson.build +++ b/src/tr2/meson.build @@ -195,6 +195,7 @@ 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', @@ -266,6 +267,7 @@ 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 6b20c3b9f..ecc2292c1 100755 --- a/tools/download_assets +++ b/tools/download_assets @@ -42,33 +42,41 @@ def extract_zip(zip_path: Path, dest_dir: Path) -> None: z.extractall(dest_dir) -def download_assets(asset_urls: list[str], target_dir: Path) -> None: +def download_assets(assets: list[tuple[str, Path]]) -> None: with tempfile.TemporaryDirectory() as tmpdir_str: tmpdir = Path(tmpdir_str) - for url in asset_urls: + for url, dest in assets: filename = Path(url).name local_zip = tmpdir / filename download_to_file(url, local_zip) - extract_zip(local_zip, target_dir) + extract_zip(local_zip, dest) print("Asset download and extraction complete.") def main() -> None: args = parse_args() - asset_urls_map: dict[int, list[str]] = { - 1: ["https://lostartefacts.dev/aux/tr1x/main.zip"], + assets: dict[int, list[tuple[str, Path]]] = { + 1: [ + ( + "https://lostartefacts.dev/aux/tr1x/main.zip", + Path("data/tr1/ship"), + ) + ], 2: [ - "https://lostartefacts.dev/aux/tr2x/main.zip", - "https://lostartefacts.dev/aux/tr2x/trgm.zip", + ( + "https://lostartefacts.dev/aux/tr2x/main.zip", + Path("data/tr2/ship"), + ) ], } - - 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, - ) + 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]) if __name__ == "__main__": diff --git a/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs b/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs index b7ed06ad7..f98813d91 100644 --- a/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs +++ b/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs @@ -15,10 +15,15 @@ public abstract class GenericInstallSource : BaseInstallSource }; public override bool IsDownloadingMusicNeeded(string sourceDirectory) - => true; + { + return !Directory.Exists(Path.Combine(sourceDirectory, "audio")) + && !Directory.Exists(Path.Combine(sourceDirectory, "music")); + } public override bool IsDownloadingExpansionNeeded(string sourceDirectory) - => true; + { + return true; + } public override async Task CopyOriginalGameFiles( string sourceDirectory,