diff --git a/.gitignore b/.gitignore index 0e5438a15..a7b8c9b27 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,11 @@ src/tr2/subprojects/dwarfstack.wrap data/tr1/ship/data/images/ data/tr2/ship/data/images/ +data/tr2/ship/data/level1.tr2 +data/tr2/ship/data/level2.tr2 +data/tr2/ship/data/level3.tr2 +data/tr2/ship/data/level4.tr2 +data/tr2/ship/data/level5.tr2 +data/tr2/ship/data/main_gm.sfx +data/tr2/ship/data/title_gm.tr2 data/tr2/ship/music/ diff --git a/data/tr1/ship/cfg/TR1X_strings.json5 b/data/tr1/ship/cfg/TR1X_strings.json5 index b8ea28438..b49fdf18d 100644 --- a/data/tr1/ship/cfg/TR1X_strings.json5 +++ b/data/tr1/ship/cfg/TR1X_strings.json5 @@ -428,7 +428,11 @@ "OSD_AMBIGUOUS_INPUT_2": "Ambiguous input: %s and %s", "OSD_AMBIGUOUS_INPUT_3": "Ambiguous input: %s, %s, ...", "OSD_COMMAND_BAD_INVOCATION": "Invalid invocation: %s", + "OSD_COMMAND_BOOL": "on, off", + "OSD_COMMAND_DECIMAL": "[decimal]", + "OSD_COMMAND_INTEGER": "[integer]", "OSD_COMMAND_UNAVAILABLE": "This command is not currently available", + "OSD_COMMAND_VALID_VALUES": "Valid values: %s", "OSD_COMPLETE_LEVEL": "Level complete!", "OSD_CONFIG_OPTION_GET": "%s is currently set to %s", "OSD_CONFIG_OPTION_SET": "%s changed to %s", @@ -531,7 +535,9 @@ "PHOTO_MODE_ROTATE_PROMPT": "Rotate camera", "PHOTO_MODE_SNAP_PROMPT": "Take picture", "PHOTO_MODE_TITLE": "Photo Mode", - "SOUND_SET_VOLUMES": "Set Volumes", + "SOUND_DIALOG_MUSIC": "\\{icon sound} Sound", + "SOUND_DIALOG_SOUND": "\\{icon music} Music", + "SOUND_DIALOG_TITLE": "Set Volumes", "STATS_AMMO": "AMMO HITS/USED", "STATS_BASIC_FMT": "%d", "STATS_BONUS_STATISTICS": "Bonus Statistics", diff --git a/data/tr2/ship/cfg/TR2X_gameflow.json5 b/data/tr2/ship/cfg/TR2X_gameflow.json5 index 5cfda3031..2649e668d 100644 --- a/data/tr2/ship/cfg/TR2X_gameflow.json5 +++ b/data/tr2/ship/cfg/TR2X_gameflow.json5 @@ -217,6 +217,7 @@ ], "injections": [ "data/injections/living_deck_goon_sfx.bin", + "data/injections/living_fd.bin", "data/injections/living_pickup_meshes.bin", "data/injections/seaweed_collision.bin", ], @@ -397,6 +398,7 @@ {"type": "total_stats", "background_path": "data/images/end.webp"}, ], "injections": [ + "data/injections/explosion.bin", "data/injections/house_itemrots.bin", ], }, diff --git a/data/tr2/ship/cfg/TR2X_strings.json5 b/data/tr2/ship/cfg/TR2X_strings.json5 index abc97764d..5d08c0f49 100644 --- a/data/tr2/ship/cfg/TR2X_strings.json5 +++ b/data/tr2/ship/cfg/TR2X_strings.json5 @@ -547,7 +547,11 @@ "OSD_BILINEAR_FILTER_OFF": "Bilinear filter: off", "OSD_BILINEAR_FILTER_ON": "Bilinear filter: on", "OSD_COMMAND_BAD_INVOCATION": "Invalid invocation: %s", + "OSD_COMMAND_BOOL": "on, off", + "OSD_COMMAND_DECIMAL": "[decimal]", + "OSD_COMMAND_INTEGER": "[integer]", "OSD_COMMAND_UNAVAILABLE": "This command is not currently available", + "OSD_COMMAND_VALID_VALUES": "Valid values: %s", "OSD_COMPLETE_LEVEL": "Level complete!", "OSD_CONFIG_OPTION_GET": "%s is currently set to %s", "OSD_CONFIG_OPTION_SET": "%s changed to %s", @@ -655,7 +659,9 @@ "PHOTO_MODE_ROTATE_PROMPT": "Rotate camera", "PHOTO_MODE_SNAP_PROMPT": "Take picture", "PHOTO_MODE_TITLE": "Photo Mode", - "SOUND_SET_VOLUMES": "Set Volumes", + "SOUND_DIALOG_MUSIC": "\\{icon sound} Sound", + "SOUND_DIALOG_SOUND": "\\{icon music} Music", + "SOUND_DIALOG_TITLE": "Set Volumes", "STATS_AMMO_HITS": "Hits", "STATS_AMMO_USED": "Ammo Used", "STATS_ASSAULT_FINISH": "Finish", diff --git a/data/tr2/ship/data/injections/explosion.bin b/data/tr2/ship/data/injections/explosion.bin new file mode 100644 index 000000000..9972c25aa Binary files /dev/null and b/data/tr2/ship/data/injections/explosion.bin differ diff --git a/data/tr2/ship/data/injections/living_fd.bin b/data/tr2/ship/data/injections/living_fd.bin new file mode 100644 index 000000000..d23df437d Binary files /dev/null and b/data/tr2/ship/data/injections/living_fd.bin differ diff --git a/docs/COMMAND_LINE.md b/docs/COMMAND_LINE.md new file mode 100644 index 000000000..9fca2d295 --- /dev/null +++ b/docs/COMMAND_LINE.md @@ -0,0 +1,20 @@ +# Command line options + +Currently the following command line interface options are available: + +`-g/--gold` (legacy: `-gold`): + Runs the Unfinished Business or the Golden Mask expansion pack, depending + on the game. + +`--demo-pc` (TR1X only, legacy: `-demo_pc`): + Runs the PC demo level. + +`-l/--level `: + Runs the game immediately launching it into the specified level. + The path should be absolute. Internally, this option uses + `TR*X_gameflow_level.json5` as a template instructing it how to run the + game. + +`-s/--save `: + Runs the game immediately loading a specific save slot. The first save + starts at `num=1`. This option can be combined with `-l/--level`. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7ea40e63f..efcdc6a13 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -266,30 +266,63 @@ request number, but it's important to carefully review the body field, as it often includes unwanted content. -### Branching model - -We have two branches: `develop` and `stable`. `develop` is where all changes -about to be published in the next release land. `stable` is the latest release. -We avoid creating merge commits between these two – they should always point to -the same HEAD when applicable. This means that any hotfixes that need to be -released ahead of unpublished work in `develop` are merged directly to -`stable`, and `develop` needs to be then rebased on top of the now-patched -`stable`. - - ### Tooling Internal tools are typically coded in a reasonably recent version of Python, while avoiding the use of bash, shell, and similar languages. +### Branching model + +We have two branches: `develop` and `stable`. `develop` is where all changes +about to be published in the next release land. `stable` is the latest release. + + ### Releasing a new version -New version releases happen automatically whenever a new tag is pushed to the -`stable` branch with the help of GitHub actions. In general this is accompanied -with a special commit `docs: release X.Y.Z` that also adjusts the changelog. -See git history for details. +New version releases are published automatically whenever a new tag is pushed +to the `stable` branch with the help of GitHub actions. +The general workflow is this: +```console +TR_VERSION=... +RELEASE_VERSION=... + +# Switch to the stable branch. +git checkout stable + +# Merge `develop` into it. +git merge develop + +# Create a special commit `docs: release X.Y.Z` marking the release in the +# relevant changelog file. Then tag it with `tr1-X.Y.Z` or `tr2-X.Y.Z`. +# You can do that by hand, or run the command below: +tools/release commit ${TR_VERSION} ${RELEASE_VERSION} + +# Review the changelog content. + +# Switch back to develop. +git checkout develop + +# Merge stable using fast-forward. +git merge --ff stable + +# Review both branches and changes. If everything is okay, push to GitHub. +# You can do this by hand: git push origin develop stable tr1-X.Y.Z, or: +# tools/release push ${TR_VERSION} ${RELEASE_VERSION} +``` + +### Hotfixes + +Hotfix releases are a bit different as we try to not include non-bugfix changes +in them. Here instead of merging `develop` to `stable` we cherry-pick relevant +changes, resolving conflicts along the way. + +### Versioning + +We increase the major version for significant releases based on judgment, +typically defaulting to increasing the minor version. Hotfixes increase the +patch version. ## Glossary diff --git a/docs/tr1/CHANGELOG.md b/docs/tr1/CHANGELOG.md index e0a5f7868..596504d82 100644 --- a/docs/tr1/CHANGELOG.md +++ b/docs/tr1/CHANGELOG.md @@ -5,12 +5,15 @@ - 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 `Select Detail` dialog title to `Graphic Options` - changed the number of static mesh slots from 50 to 256 (#2734) - changed the "enable EIDOS logo" option to disable the Core Design and Bink Video Codec FMVs as well; renamed to "enable legal" (#2741) - changed sprite pickups to respect the water tint if placed underwater (#2673) - changed save to take priority over load when both inputs are held on the same frame, in line with OG (#2833) +- changed the sound dialog appearance (repositioned and added text labels) - changed The Unfinished Business strings to default to the OG strings file for the main tables (#2847) - fixed the bilinear filter to not readjust the UVs (#2258) - fixed disabling the cutscenes causing the game to exit (#2743, regression from 4.8) @@ -22,6 +25,7 @@ - fixed the scale of the four keys in St. Francis' Folly (#2652) - fixed the panther at times not making a sound when it dies, and restored Skate Kid's death SFX (#2647) - fixed pushblocks being rotated when Lara grabs them, most noticeable if asymmetric textures have been used (#2776) +- fixed Lara becoming clamped if she picks up an item under a steeply sloped ceiling (#2879) - fixed a crash when 3D pickups are disabled and Lara crosses a trigger to look at a pickup item (#2711, regression from 4.8) - fixed trapezoid filter warping on faces close to the camera (#2629, regression from 4.9) - fixed Mac builds crashing upon start (regression from 4.9) @@ -37,9 +41,13 @@ - fixed Lara at times ending up in incorrect rooms when using the teleport cheat (#2486, regression from 3.0) - fixed the `/pos` console command reporting the base room number when Lara is actually in a flipped room (#2487, regression from 3.0) - fixed clicks in audio sounds (#2846, regression from 2.0) +- fixed Lara being killed if she enters the void in a level that uses the `disable_floor` sequence in the game flow (#2874, regression from 4.9) +- fixed game crashing if the music folder was not present (#2887, regression from 4.9) +- fixed game crashing on unknown sequencer events - improved bubble appearance (#2672) - improved rendering performance - improved pause exit dialog - it can now be canceled with escape +- improved the `/set` console command to display available options if given an unknown argument - removed the pretty pixels options (it's now always enabled, #2258) ## [4.9](https://github.com/LostArtefacts/TRX/compare/tr1-4.8.3...tr1-4.9) - 2025-03-31 diff --git a/docs/tr1/README.md b/docs/tr1/README.md index d128fb4b3..e49c78f4e 100644 --- a/docs/tr1/README.md +++ b/docs/tr1/README.md @@ -485,6 +485,7 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det - fixed being able to shoot the scion multiple times if save/load is used while it blows up - fixed the game crashing if a cinematic is triggered but the level contains no cinematic frames - fixed pushblocks being rotated when Lara grabs them, most noticeable if asymmetric textures have been used +- fixed Lara becoming clamped if she picks up an item under a steeply sloped ceiling #### Cheats - added a fly cheat diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index 44a0cb21e..d6b197278 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -1,4 +1,14 @@ -## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.2...develop) - ××××-××-×× +## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...develop) - ××××-××-×× +- added aliases to CLI options (`-gold` becomes `-g/--gold`) +- added a `--help` CLI option (may not output anything on Windows machines – OS bug) +- added explosion sprites to Home Sweet Home (#1569) +- changed the sound dialog appearance (repositioned, added text labels and arrows) +- changed the installer to always allow downloading music files (#2891) +- fixed Lara being killed if she enters the void in a level that uses the `disable_floor` sequence in the game flow (#2874, regression from 0.10) +- fixed flame emitter 23 in room 6 not being deactivated when the lever in room 1 is used (#2851) +- fixed Lara snapping to face forwards if she has a slight angle and action is pressed after using an airlock door (#2215) +- fixed the game crashing on unknown sequencer events +- improved the `/set` console command to display available options if given an unknown argument ## [1.0.2](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...tr2-1.0.2) - 2025-04-26 - changed The Golden Mask strings to default to the OG strings file for the main tables (#2847) diff --git a/docs/tr2/README.md b/docs/tr2/README.md index 54d53df6a..fd4246446 100644 --- a/docs/tr2/README.md +++ b/docs/tr2/README.md @@ -221,6 +221,7 @@ However, you can easily download them manually from these urls: - fixed the following floor data issues: - **Opera House**: fixed the trigger under item 203 to trigger it rather than item 204 - **Wreck of the Maria Doria**: fixed room 98 not having water + - **Living Quarters** - fixed flame emitter 23 in room 6 not being deactivated when the lever in room 1 is used - **The Deck**: fixed invalid portals between rooms 17 and 104, which could result in Lara seeing enemies in disconnected rooms - **Tibetan Foothills**: added missing triggers for the drawbridge in room 96 (after the flipmap) - **Catacombs of the Talion**: changed some music triggers to pads near the first yeti, and added missing triggers and ladder in room 116 (after the flipmap) @@ -257,6 +258,7 @@ However, you can easily download them manually from these urls: - 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_stream.c b/src/libtrx/engine/audio_stream.c index bca11ca75..ba80da5f7 100644 --- a/src/libtrx/engine/audio_stream.c +++ b/src/libtrx/engine/audio_stream.c @@ -84,17 +84,32 @@ static void M_SeekToStart(AUDIO_STREAM_SOUND *stream) ASSERT(stream != nullptr); stream->timestamp = stream->start_at; + int32_t error_code; if (stream->start_at <= 0.0) { // reset to start of file avio_seek(stream->av.format_ctx->pb, 0, SEEK_SET); - avformat_seek_file( + error_code = avformat_seek_file( stream->av.format_ctx, -1, 0, 0, 0, AVSEEK_FLAG_FRAME); } else { // seek to specific timestamp - const double time_base_sec = av_q2d(stream->av.stream->time_base); - av_seek_frame( - stream->av.format_ctx, 0, stream->start_at / time_base_sec, - AVSEEK_FLAG_ANY); + AVFormatContext *const fmt = stream->av.format_ctx; + if (fmt->pb != nullptr && (fmt->pb->seekable & AVIO_SEEKABLE_NORMAL)) { + const int64_t ts = (int64_t)(stream->start_at * AV_TIME_BASE); + error_code = avformat_seek_file( + fmt, stream->av.stream->index, INT64_MIN, ts, INT64_MAX, + AVSEEK_FLAG_BACKWARD); + } else { + // fallback to stream-based seek + const double time_base_sec = av_q2d(stream->av.stream->time_base); + error_code = av_seek_frame( + fmt, stream->av.stream->index, + (int64_t)(stream->start_at / time_base_sec), AVSEEK_FLAG_ANY); + } + } + if (error_code < 0) { + LOG_ERROR( + "seek failed for timestamp %f: %s", stream->timestamp, + av_err2str(error_code)); } } @@ -257,6 +272,10 @@ static bool M_InitialiseFromPath(int32_t sound_id, const char *file_path) int32_t error_code; char *full_path = File_GetFullPath(file_path); + if (full_path == nullptr) { + error_code = AVERROR(ENOENT); + goto cleanup; + } AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id]; @@ -680,26 +699,51 @@ double Audio_Stream_GetDuration(int32_t sound_id) return duration; } -bool Audio_Stream_SeekTimestamp(int32_t sound_id, double timestamp) +bool Audio_Stream_SeekTimestamp(const int32_t sound_id, const double timestamp) { if (!g_AudioDeviceID || sound_id < 0 || sound_id >= AUDIO_MAX_ACTIVE_STREAMS) { return false; } - if (m_Streams[sound_id].is_playing) { - SDL_LockAudioDevice(g_AudioDeviceID); - AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id]; - const double time_base_sec = av_q2d(stream->av.stream->time_base); - av_seek_frame( - stream->av.format_ctx, 0, timestamp / time_base_sec, - AVSEEK_FLAG_ANY); - avcodec_flush_buffers(stream->av.codec_ctx); + AUDIO_STREAM_SOUND *const stream = &m_Streams[sound_id]; + stream->start_at = timestamp; + if (!stream->is_used) { + return false; + } + ASSERT(stream->av.format_ctx != nullptr); + ASSERT(stream->av.codec_ctx != nullptr); + ASSERT(stream->av.stream != nullptr); + + SDL_LockAudioDevice(g_AudioDeviceID); + + const double time_base_sec = av_q2d(stream->av.stream->time_base); + if (time_base_sec <= 0.0) { + LOG_ERROR( + "Audio_Stream_SeekTimestamp: invalid time_base %f", time_base_sec); SDL_UnlockAudioDevice(g_AudioDeviceID); - return true; + return false; } - return false; + const int32_t stream_index = stream->av.stream->index; + const int64_t seek_target = (int64_t)(timestamp / time_base_sec); + const int32_t error_code = av_seek_frame( + stream->av.format_ctx, stream_index, seek_target, AVSEEK_FLAG_ANY); + if (error_code < 0) { + LOG_ERROR( + "seek failed for timestamp %f: %s", timestamp, + av_err2str(error_code)); + } + + avcodec_flush_buffers(stream->av.codec_ctx); + if (stream->sdl.stream) { + SDL_AudioStreamFlush(stream->sdl.stream); + } + + stream->timestamp = timestamp; + + SDL_UnlockAudioDevice(g_AudioDeviceID); + return true; } bool Audio_Stream_SetStartTimestamp(int32_t sound_id, double timestamp) diff --git a/src/libtrx/enum_map.c b/src/libtrx/enum_map.c index b140bfc30..88cf557f3 100644 --- a/src/libtrx/enum_map.c +++ b/src/libtrx/enum_map.c @@ -121,3 +121,25 @@ void EnumMap_Shutdown(void) } } } + +VECTOR *EnumMap_ListValues(const char *const enum_name) +{ + if (enum_name == nullptr) { + return nullptr; + } + + // Compare the prefix to find the matching enum values. + const size_t prefix_len = strlen(enum_name) + 1; + + VECTOR *const results = Vector_Create(sizeof(char *)); + M_INVERSE_ENTRY *entry; + M_INVERSE_ENTRY *tmp; + HASH_ITER(hh, m_InverseMap, entry, tmp) + { + if (strncmp(entry->key, enum_name, prefix_len - 1) == 0 + && entry->key[prefix_len - 1] == '|') { + Vector_Add(results, &entry->str_value); + } + } + return results; +} diff --git a/src/libtrx/game/console/cmd/config.c b/src/libtrx/game/console/cmd/config.c index 4eccfe895..bd2f31bca 100644 --- a/src/libtrx/game/console/cmd/config.c +++ b/src/libtrx/game/console/cmd/config.c @@ -14,6 +14,7 @@ static const char *M_Resolve(const char *option_name); static bool M_SameKey(const char *key1, const char *key2); +static char *M_GetAvailableOptions(const CONFIG_OPTION *option); static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx); @@ -76,6 +77,56 @@ cleanup: return result; } +// Return a comma-delimited list of valid values for the option. +// Caller must free the result via Memory_FreePointer. +static char *M_GetAvailableOptions(const CONFIG_OPTION *const option) +{ + if (option == nullptr) { + return nullptr; + } + + switch (option->type) { + case COT_BOOL: + return Memory_DupStr(GS(OSD_COMMAND_BOOL)); + + case COT_INT32: + return Memory_DupStr(GS(OSD_COMMAND_INTEGER)); + + case COT_DOUBLE: + case COT_FLOAT: + return Memory_DupStr(GS(OSD_COMMAND_DECIMAL)); + + case COT_ENUM: { + const char *enum_name = (const char *)option->param; + VECTOR *const values = EnumMap_ListValues(enum_name); + if (values == nullptr) { + return nullptr; + } + // Join vector items into a comma-separated string + size_t total_len = 1; + const char *const sep = ", "; + for (int32_t i = 0; i < values->count; i++) { + const char *const s = *(char **)Vector_Get(values, i); + total_len += strlen(s) + (i + 1 < values->count ? strlen(sep) : 0); + } + char *const result = Memory_Alloc(total_len); + char *ptr = result; + for (int32_t i = 0; i < values->count; i++) { + const char *const s = *(char **)Vector_Get(values, i); + strcat(ptr, s); + if (i + 1 < values->count) { + strcat(ptr, sep); + } + } + Vector_Free(values); + return result; + } + + default: + return nullptr; + } +} + char *Console_Cmd_Config_NormalizeKey(const char *key) { // TODO: Once we support arbitrary glyphs, this conversion should @@ -261,18 +312,16 @@ COMMAND_RESULT Console_Cmd_Config_Helper( char *normalized_name = Console_Cmd_Config_NormalizeKey(option->name); - COMMAND_RESULT result = CR_BAD_INVOCATION; if (new_value == nullptr || String_IsEmpty(new_value)) { char cur_value[128]; if (Console_Cmd_Config_GetCurrentValue(option, cur_value, 128)) { Console_Log(GS(OSD_CONFIG_OPTION_GET), normalized_name, cur_value); - result = CR_SUCCESS; - } else { - result = CR_FAILURE; + return CR_SUCCESS; } - return result; + return CR_FAILURE; } + COMMAND_RESULT result; if (Console_Cmd_Config_SetCurrentValue(option, new_value)) { Config_Write(); @@ -280,6 +329,15 @@ COMMAND_RESULT Console_Cmd_Config_Helper( ASSERT(Console_Cmd_Config_GetCurrentValue(option, final_value, 128)); Console_Log(GS(OSD_CONFIG_OPTION_SET), normalized_name, final_value); result = CR_SUCCESS; + } else { + // Report bad invocation on the provided new value + Console_Log(GS(OSD_COMMAND_BAD_INVOCATION), new_value); + char *available_options = M_GetAvailableOptions(option); + if (available_options != nullptr) { + Console_Log(GS(OSD_COMMAND_VALID_VALUES), available_options); + Memory_FreePointer(&available_options); + } + result = CR_FAILURE; } cleanup: diff --git a/src/libtrx/game/game_flow/reader.c b/src/libtrx/game/game_flow/reader.c index 77ff36e2e..507e13e64 100644 --- a/src/libtrx/game/game_flow/reader.c +++ b/src/libtrx/game/game_flow/reader.c @@ -281,6 +281,10 @@ static size_t M_LoadSequenceEvent( const char *const type_str = JSON_ObjectGetString(event_obj, "type", ""); const GF_SEQUENCE_EVENT_TYPE type = ENUM_MAP_GET(GF_SEQUENCE_EVENT_TYPE, type_str, -1); + if (type == (GF_SEQUENCE_EVENT_TYPE)-1) { + Shell_ExitSystemFmt( + "Unknown game flow sequence event type: '%s'", type_str); + } const M_SEQUENCE_EVENT_HANDLER *handler = M_GetSequenceEventHandlers(); while (handler->event_type != (GF_SEQUENCE_EVENT_TYPE)-1 @@ -288,11 +292,6 @@ static size_t M_LoadSequenceEvent( handler++; } - if (handler->event_type != type) { - Shell_ExitSystemFmt( - "Unknown game flow sequence event type: '%s'", type); - } - int32_t extra_data_size = 0; if (handler->handler_func != nullptr) { extra_data_size = handler->handler_func( diff --git a/src/libtrx/game/lara/common.c b/src/libtrx/game/lara/common.c index 2f2274ede..ad6136f5a 100644 --- a/src/libtrx/game/lara/common.c +++ b/src/libtrx/game/lara/common.c @@ -2,7 +2,9 @@ #include "game/const.h" #include "game/item_actions.h" -#include "game/rooms/const.h" +#include "game/lara/const.h" +#include "game/matrix.h" +#include "game/rooms.h" void Lara_Animate(ITEM *const item) { @@ -156,3 +158,88 @@ bool Lara_TestBoundsCollide(const ITEM *const item, const int32_t radius) { return Item_TestBoundsCollide(item, Lara_GetItem(), radius); } + +bool Lara_TestPosition( + const ITEM *const item, const OBJECT_BOUNDS *const bounds) +{ + const ITEM *const lara = Lara_GetItem(); + const XYZ_16 rot = { + .x = lara->rot.x - item->rot.x, + .y = lara->rot.y - item->rot.y, + .z = lara->rot.z - item->rot.z, + }; + const XYZ_32 dist = { + .x = lara->pos.x - item->pos.x, + .y = lara->pos.y - item->pos.y, + .z = lara->pos.z - item->pos.z, + }; + + // clang-format off + if (rot.x < bounds->rot.min.x || + rot.x > bounds->rot.max.x || + rot.y < bounds->rot.min.y || + rot.y > bounds->rot.max.y || + rot.z < bounds->rot.min.z || + rot.z > bounds->rot.max.z + ) { + return false; + } + // clang-format on + + Matrix_PushUnit(); + Matrix_Rot16(item->rot); + const MATRIX *const m = g_MatrixPtr; + const XYZ_32 shift = { + .x = (dist.x * m->_00 + dist.y * m->_10 + dist.z * m->_20) >> W2V_SHIFT, + .y = (dist.x * m->_01 + dist.y * m->_11 + dist.z * m->_21) >> W2V_SHIFT, + .z = (dist.x * m->_02 + dist.y * m->_12 + dist.z * m->_22) >> W2V_SHIFT, + }; + Matrix_Pop(); + + // clang-format off + return ( + shift.x >= bounds->shift.min.x && + shift.x <= bounds->shift.max.x && + shift.y >= bounds->shift.min.y && + shift.y <= bounds->shift.max.y && + shift.z >= bounds->shift.min.z && + shift.z <= bounds->shift.max.z + ); + // clang-format on +} + +void Lara_AlignPosition(const ITEM *const item, const XYZ_32 *const vec) +{ + ITEM *const lara = Lara_GetItem(); + lara->rot = item->rot; + Matrix_PushUnit(); + Matrix_Rot16(item->rot); + const MATRIX *const m = g_MatrixPtr; + const XYZ_32 shift = { + .x = (vec->x * m->_00 + vec->y * m->_01 + vec->z * m->_02) >> W2V_SHIFT, + .y = (vec->x * m->_10 + vec->y * m->_11 + vec->z * m->_12) >> W2V_SHIFT, + .z = (vec->x * m->_20 + vec->y * m->_21 + vec->z * m->_22) >> W2V_SHIFT, + }; + Matrix_Pop(); + + const XYZ_32 new_pos = { + .x = item->pos.x + shift.x, + .y = item->pos.y + shift.y, + .z = item->pos.z + shift.z, + }; + + int16_t room_num = lara->room_num; + const SECTOR *const sector = + Room_GetSector(new_pos.x, new_pos.y, new_pos.z, &room_num); + const int32_t height = + Room_GetHeight(sector, new_pos.x, new_pos.y, new_pos.z); + const int32_t ceiling = + Room_GetCeiling(sector, new_pos.x, new_pos.y, new_pos.z); + + if (ABS(height - lara->pos.y) > STEP_L + || ABS(ceiling - lara->pos.y) < LARA_HEIGHT) { + return; + } + + lara->pos = new_pos; +} diff --git a/src/libtrx/game/music.c b/src/libtrx/game/music.c index c3e60a6e6..19d259fdb 100644 --- a/src/libtrx/game/music.c +++ b/src/libtrx/game/music.c @@ -2,6 +2,16 @@ static uint16_t m_MusicTrackFlags[MAX_MUSIC_TRACKS] = {}; +int32_t Music_GetMinVolume(void) +{ + return 0; +} + +int32_t Music_GetMaxVolume(void) +{ + return 10; +} + void Music_ResetTrackFlags(void) { for (int32_t i = 0; i < MAX_MUSIC_TRACKS; i++) { diff --git a/src/libtrx/game/rooms/common.c b/src/libtrx/game/rooms/common.c index b10c7ceb7..0dc525142 100644 --- a/src/libtrx/game/rooms/common.c +++ b/src/libtrx/game/rooms/common.c @@ -550,7 +550,7 @@ void Room_SetAbyssHeight(const int16_t height) CLAMPG(m_AbyssMaxHeight, MAX_HEIGHT - STEP_L); } -bool Room_IsAbyssHeight(const int16_t height) +bool Room_IsAbyssHeight(const int32_t height) { return m_AbyssMinHeight != 0 && height >= m_AbyssMinHeight; } diff --git a/src/libtrx/game/shell/main.c b/src/libtrx/game/shell/main.c index 1a29106ab..54a5a0653 100644 --- a/src/libtrx/game/shell/main.c +++ b/src/libtrx/game/shell/main.c @@ -6,28 +6,18 @@ #include -static int m_ArgCount = 0; -static const char **m_ArgStrings = nullptr; - -void Shell_GetCommandLine(int *arg_count, const char ***args) -{ - *arg_count = m_ArgCount; - *args = m_ArgStrings; -} - int main(int argc, char *argv[]) { + if (!Shell_ParseArgs(argc, (const char **)argv)) { + return 0; + } + char *log_path = File_GetFullPath(PROJECT_NAME ".log"); Log_Init(log_path); Memory_FreePointer(&log_path); - LOG_INFO("Game directory: %s", File_GetGameDirectory()); - - m_ArgCount = argc; - m_ArgStrings = (const char **)argv; - Shell_Setup(); - Shell_Main(); - Shell_Terminate(0); - return 0; + int32_t exit_code = Shell_Main(); + Shell_Terminate(exit_code); + return exit_code; } diff --git a/src/libtrx/game/ui/common.c b/src/libtrx/game/ui/common.c index 2ffa2c32b..159eb7190 100644 --- a/src/libtrx/game/ui/common.c +++ b/src/libtrx/game/ui/common.c @@ -4,8 +4,10 @@ #include "debug.h" #include "game/console/common.h" #include "game/game_string.h" +#include "game/scaler.h" #include "game/ui/elements/anchor.h" #include "game/ui/events.h" +#include "game/viewport.h" #include "memory.h" #include @@ -201,3 +203,23 @@ void UI_HandleTextEdit(const char *const text) UI_FireEvent((EVENT) { .name = "text_edit", .sender = nullptr, .data = (void *)text }); } + +int32_t UI_GetCanvasWidth(void) +{ + return Scaler_CalcInverse(Viewport_GetWidth(), SCALER_TARGET_GENERIC); +} + +int32_t UI_GetCanvasHeight(void) +{ + return Scaler_CalcInverse(Viewport_GetHeight(), SCALER_TARGET_GENERIC); +} + +float UI_ScaleX(const float x) +{ + return Scaler_Calc(x, SCALER_TARGET_GENERIC); +} + +float UI_ScaleY(const float y) +{ + return Scaler_Calc(y, SCALER_TARGET_GENERIC); +} diff --git a/src/libtrx/game/ui/dialogs/photo_mode.c b/src/libtrx/game/ui/dialogs/photo_mode.c index 512f4b3e1..903fe6a11 100644 --- a/src/libtrx/game/ui/dialogs/photo_mode.c +++ b/src/libtrx/game/ui/dialogs/photo_mode.c @@ -21,7 +21,7 @@ void UI_PhotoMode(void) char tmp[50]; UI_BeginModal(0.0f, 0.0f); - UI_BeginPad(8.0f, 10.0f); + UI_BeginPad(8.0f, 8.0f); UI_BeginFrame(UI_FRAME_DIALOG_BACKGROUND); UI_BeginPad(8.0, 6.0); diff --git a/src/libtrx/game/ui/dialogs/sound_settings.c b/src/libtrx/game/ui/dialogs/sound_settings.c new file mode 100644 index 000000000..b5f382308 --- /dev/null +++ b/src/libtrx/game/ui/dialogs/sound_settings.c @@ -0,0 +1,176 @@ +#include "game/ui/dialogs/sound_settings.h" + +#include "config.h" +#include "game/game_string.h" +#include "game/input.h" +#include "game/music.h" +#include "game/sound.h" +#include "game/ui/elements/anchor.h" +#include "game/ui/elements/hide.h" +#include "game/ui/elements/label.h" +#include "game/ui/elements/modal.h" +#include "game/ui/elements/requester.h" +#include "game/ui/elements/resize.h" +#include "game/ui/elements/spacer.h" +#include "game/ui/elements/stack.h" +#include "memory.h" +#include "strings.h" +#include "utils.h" + +typedef struct UI_SOUND_SETTINGS_STATE { + UI_REQUESTER_STATE req; +} UI_SOUND_SETTINGS_STATE; + +typedef enum { + M_ROW_MUSIC = 0, + M_ROW_SOUND = 1, + M_ROW_COUNT = 2, +} M_ROW; + +static const GAME_STRING_ID m_Labels[M_ROW_COUNT] = { + GS_ID(SOUND_DIALOG_SOUND), + GS_ID(SOUND_DIALOG_MUSIC), +}; + +static char *M_FormatRowValue(int32_t row); +static bool M_CanChange(int32_t row, int32_t dir); +static bool M_RequestChange(int32_t row, int32_t dir); + +static char *M_FormatRowValue(const int32_t row) +{ + switch (row) { + case M_ROW_MUSIC: + return String_Format("%2d", g_Config.audio.music_volume); + case M_ROW_SOUND: + return String_Format("%2d", g_Config.audio.sound_volume); + default: + return nullptr; + } +} + +static bool M_CanChange(const int32_t row, const int32_t dir) +{ + switch (row) { + case M_ROW_MUSIC: + if (dir < 0) { + return g_Config.audio.music_volume > Music_GetMinVolume(); + } else if (dir > 0) { + return g_Config.audio.music_volume < Music_GetMaxVolume(); + } + break; + case M_ROW_SOUND: + if (dir < 0) { + return g_Config.audio.sound_volume > Sound_GetMinVolume(); + } else if (dir > 0) { + return g_Config.audio.sound_volume < Sound_GetMaxVolume(); + } + break; + } + return false; +} + +static bool M_RequestChange(const int32_t row, const int32_t dir) +{ + if (!M_CanChange(row, dir)) { + return false; + } + switch (row) { + case M_ROW_MUSIC: + g_Config.audio.music_volume += dir; + Music_SetVolume(g_Config.audio.music_volume); + break; + case M_ROW_SOUND: + g_Config.audio.sound_volume += dir; + Sound_SetMasterVolume(g_Config.audio.sound_volume); + break; + default: + return false; + } + Config_Write(); + Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); + return true; +} + +UI_SOUND_SETTINGS_STATE *UI_SoundSettings_Init(void) +{ + UI_SOUND_SETTINGS_STATE *s = Memory_Alloc(sizeof(UI_SOUND_SETTINGS_STATE)); + UI_Requester_Init(&s->req, M_ROW_COUNT, M_ROW_COUNT, true); + s->req.row_pad = 2.0f; + return s; +} + +void UI_SoundSettings_Free(UI_SOUND_SETTINGS_STATE *const s) +{ + UI_Requester_Free(&s->req); + Memory_Free(s); +} + +bool UI_SoundSettings_Control(UI_SOUND_SETTINGS_STATE *const s) +{ + const int32_t choice = UI_Requester_Control(&s->req); + if (choice == UI_REQUESTER_CANCEL) { + return true; + } + const int32_t sel = UI_Requester_GetCurrentRow(&s->req); + if (g_InputDB.menu_left && sel >= 0) { + M_RequestChange(sel, -1); + } else if (g_InputDB.menu_right && sel >= 0) { + M_RequestChange(sel, +1); + } + return false; +} + +void UI_SoundSettings(UI_SOUND_SETTINGS_STATE *const s) +{ + const int32_t sel = UI_Requester_GetCurrentRow(&s->req); + UI_BeginModal(0.5f, 0.6f); + UI_BeginRequester(&s->req, GS(SOUND_DIALOG_TITLE)); + + // Measure the maximum width of the value label to prevent the entire + // dialog from changing its size as the player changes the sound levels. + float value_w = -1.0f; + UI_Label_Measure("10", &value_w, nullptr); + + for (int32_t i = 0; i < s->req.max_rows; ++i) { + if (!UI_Requester_IsRowVisible(&s->req, i)) { + UI_BeginResize(-1.0f, 0.0f); + } else { + UI_BeginResize(-1.0f, -1.0f); + } + UI_BeginRequesterRow(&s->req, i); + + UI_BeginStackEx((UI_STACK_SETTINGS) { + .orientation = UI_STACK_HORIZONTAL, + .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE }, + }); + UI_Label(GameString_Get(m_Labels[i])); + UI_Spacer(20.0f, 0.0f); + + UI_BeginStackEx((UI_STACK_SETTINGS) { + .orientation = UI_STACK_HORIZONTAL, + .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE }, + .spacing = { .h = 5.0f }, + }); + UI_BeginHide(i != sel || !M_CanChange(i, -1)); + UI_Label("\\{button left}"); + UI_EndHide(); + + UI_BeginResize(value_w, -1.0f); + UI_BeginAnchor(0.5f, 0.5f); + UI_Label(M_FormatRowValue(i)); + UI_EndAnchor(); + UI_EndResize(); + + UI_BeginHide(i != sel || !M_CanChange(i, +1)); + UI_Label("\\{button right}"); + UI_EndHide(); + UI_EndStack(); + UI_EndStack(); + + UI_EndRequesterRow(&s->req, i); + UI_EndResize(); + } + + UI_EndRequester(&s->req); + UI_EndModal(); +} diff --git a/src/libtrx/game/ui/elements/modal.c b/src/libtrx/game/ui/elements/modal.c index 9aec8632e..f74030b87 100644 --- a/src/libtrx/game/ui/elements/modal.c +++ b/src/libtrx/game/ui/elements/modal.c @@ -15,7 +15,7 @@ static const UI_WIDGET_OPS m_Ops = { static void M_Measure(UI_NODE *const node) { node->measure_w = UI_GetCanvasWidth(); - node->measure_h = UI_GetCanvasHeight() - TEXT_HEIGHT_FIXED; + node->measure_h = UI_GetCanvasHeight(); } void UI_BeginModal(const float x, const float y) diff --git a/src/libtrx/game/ui/elements/stack.c b/src/libtrx/game/ui/elements/stack.c index d8a460a33..6638b775b 100644 --- a/src/libtrx/game/ui/elements/stack.c +++ b/src/libtrx/game/ui/elements/stack.c @@ -114,7 +114,7 @@ static void M_Layout( int32_t child_count = 0; float total_child_main_size = 0.0f; UI_NODE *child = node->first_child; - while (child != NULL) { + while (child != nullptr) { switch (data->settings.orientation) { case UI_STACK_HORIZONTAL: total_child_main_size += child->measure_w; diff --git a/src/libtrx/include/libtrx/enum_map.h b/src/libtrx/include/libtrx/enum_map.h index d0fecff85..12382e49f 100644 --- a/src/libtrx/include/libtrx/enum_map.h +++ b/src/libtrx/include/libtrx/enum_map.h @@ -1,4 +1,4 @@ -#include +#include "vector.h" #define ENUM_MAP_DEFINE(enum_name, enum_value, str_value) \ EnumMap_Define(ENUM_MAP_NAME(enum_name), enum_value, str_value); @@ -18,6 +18,13 @@ extern void EnumMap_Init(void); void EnumMap_Shutdown(void); +// Returns a vector of valid string values for the given enum_name. +// +// The returned vector must be freed via Vector_Free(). The string pointers +// within the vector are owned by the enum map and should not be freed by the +// caller. Returns nullptr if the enum_name is not valid. +VECTOR *EnumMap_ListValues(const char *enum_name); + void EnumMap_Define( const char *enum_name, int32_t enum_value, const char *str_value); int32_t EnumMap_Get( diff --git a/src/libtrx/include/libtrx/game/game_string.def b/src/libtrx/include/libtrx/game/game_string.def index 63a70fe0d..7ce161412 100644 --- a/src/libtrx/include/libtrx/game/game_string.def +++ b/src/libtrx/include/libtrx/game/game_string.def @@ -34,6 +34,10 @@ GS_DEFINE(OSD_PLAY_MUSIC_TRACK, "Playing music track %d") GS_DEFINE(OSD_INVALID_MUSIC_TRACK, "Invalid music track") GS_DEFINE(OSD_UNKNOWN_COMMAND, "Unknown command: %s") GS_DEFINE(OSD_COMMAND_BAD_INVOCATION, "Invalid invocation: %s") +GS_DEFINE(OSD_COMMAND_VALID_VALUES, "Valid values: %s") +GS_DEFINE(OSD_COMMAND_BOOL, "on, off") +GS_DEFINE(OSD_COMMAND_INTEGER, "[integer]") +GS_DEFINE(OSD_COMMAND_DECIMAL, "[decimal]") GS_DEFINE(OSD_COMMAND_UNAVAILABLE, "This command is not currently available") GS_DEFINE(OSD_INVALID_ROOM, "Invalid room: %d. Valid rooms are 0-%d") GS_DEFINE(OSD_POS_SET_POS, "Teleported to position: %.3f %.3f %.3f") @@ -132,7 +136,9 @@ GS_DEFINE(PASSPORT_MODE_NEW_GAME_JP_PLUS, "Japanese NG+") GS_DEFINE(PASSPORT_STORY_SO_FAR, "Story so far...") GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_1, "Legacy saves do not") GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_2, "support this feature.") -GS_DEFINE(SOUND_SET_VOLUMES, "Set Volumes") +GS_DEFINE(SOUND_DIALOG_TITLE, "Set Volumes") +GS_DEFINE(SOUND_DIALOG_SOUND, "\\{icon music} Music") +GS_DEFINE(SOUND_DIALOG_MUSIC, "\\{icon sound} Sound") GS_DEFINE(OSD_TRAPEZOID_FILTER_ON, "Trapezoid filter enabled") GS_DEFINE(OSD_TRAPEZOID_FILTER_OFF, "Trapezoid filter disabled") GS_DEFINE(DETAIL_INTEGER_FMT, "%d") diff --git a/src/libtrx/include/libtrx/game/lara/common.h b/src/libtrx/include/libtrx/game/lara/common.h index 068634600..82e2f7357 100644 --- a/src/libtrx/include/libtrx/game/lara/common.h +++ b/src/libtrx/include/libtrx/game/lara/common.h @@ -15,3 +15,5 @@ void Lara_TakeDamage(int16_t damage, bool hit_status); bool Lara_TestBoundsCollide(const ITEM *item, int32_t radius); void Lara_Push(const ITEM *item, COLL_INFO *coll, bool hit_on, bool big_push); +bool Lara_TestPosition(const ITEM *item, const OBJECT_BOUNDS *bounds); +void Lara_AlignPosition(const ITEM *item, const XYZ_32 *vec); diff --git a/src/libtrx/include/libtrx/game/music/common.h b/src/libtrx/include/libtrx/game/music/common.h index 68c3c3838..1e6ee1f85 100644 --- a/src/libtrx/include/libtrx/game/music/common.h +++ b/src/libtrx/include/libtrx/game/music/common.h @@ -31,3 +31,15 @@ extern void Music_Unpause(void); void Music_ResetTrackFlags(void); uint16_t Music_GetTrackFlags(int32_t track_idx); void Music_SetTrackFlags(int32_t track, uint16_t flags); + +// Gets the minimum possible game volume. +extern int32_t Music_GetMinVolume(void); + +// Gets the maximum possible game volume. +extern int32_t Music_GetMaxVolume(void); + +// Gets the game volume. +extern int32_t Music_GetVolume(void); + +// Sets the game volume. +extern void Music_SetVolume(int32_t volume); diff --git a/src/libtrx/include/libtrx/game/objects/types.h b/src/libtrx/include/libtrx/game/objects/types.h index 88b289dcd..c22f3c549 100644 --- a/src/libtrx/include/libtrx/game/objects/types.h +++ b/src/libtrx/include/libtrx/game/objects/types.h @@ -38,14 +38,12 @@ typedef struct { bool disable_lighting; } OBJECT_MESH; -#if TR_VERSION == 1 typedef struct { struct { XYZ_16 min; XYZ_16 max; } shift, rot; } OBJECT_BOUNDS; -#endif typedef struct OBJECT { int16_t mesh_count; @@ -66,8 +64,8 @@ typedef struct OBJECT { void (*activate_func)(ITEM *item); void (*handle_flip_func)(ITEM *item, ROOM_FLIP_STATUS flip_status); void (*handle_save_func)(ITEM *item, SAVEGAME_STAGE stage); -#if TR_VERSION == 1 const OBJECT_BOUNDS *(*bounds_func)(void); +#if TR_VERSION == 1 bool (*is_usable_func)(int16_t item_num); #endif diff --git a/src/libtrx/include/libtrx/game/rooms/common.h b/src/libtrx/include/libtrx/game/rooms/common.h index 1d24b62b7..19539f1ce 100644 --- a/src/libtrx/include/libtrx/game/rooms/common.h +++ b/src/libtrx/include/libtrx/game/rooms/common.h @@ -43,7 +43,7 @@ SECTOR *Room_GetPitSector(const SECTOR *sector, int32_t x, int32_t z); SECTOR *Room_GetSkySector(const SECTOR *sector, int32_t x, int32_t z); void Room_SetAbyssHeight(int16_t height); -bool Room_IsAbyssHeight(int16_t height); +bool Room_IsAbyssHeight(int32_t height); HEIGHT_TYPE Room_GetHeightType(void); int16_t Room_GetHeight(const SECTOR *sector, int32_t x, int32_t y, int32_t z); int16_t Room_GetHeightEx( diff --git a/src/libtrx/include/libtrx/game/shell.h b/src/libtrx/include/libtrx/game/shell.h index 3128bda38..1ed37747f 100644 --- a/src/libtrx/include/libtrx/game/shell.h +++ b/src/libtrx/include/libtrx/game/shell.h @@ -11,13 +11,13 @@ typedef struct { extern void Shell_Shutdown(void); extern SDL_Window *Shell_GetWindow(void); +extern bool Shell_ParseArgs(int32_t arg_count, const char **args); void Shell_Setup(void); -extern void Shell_Main(void); +extern int32_t Shell_Main(void); void Shell_Terminate(int32_t exit_code); void Shell_ExitSystem(const char *message); void Shell_ExitSystemFmt(const char *fmt, ...); -void Shell_GetCommandLine(int *arg_count, const char ***args); void Shell_ScheduleExit(void); bool Shell_IsExiting(void); diff --git a/src/libtrx/include/libtrx/game/sound/common.h b/src/libtrx/include/libtrx/game/sound/common.h index 374320fe5..144a322d8 100644 --- a/src/libtrx/include/libtrx/game/sound/common.h +++ b/src/libtrx/include/libtrx/game/sound/common.h @@ -14,6 +14,11 @@ int16_t *Sound_GetSampleLUT(void); SAMPLE_INFO *Sound_GetSampleInfo(SOUND_EFFECT_ID sfx_num); SAMPLE_INFO *Sound_GetSampleInfoByIdx(int32_t info_idx); +extern int32_t Sound_GetMinVolume(void); +extern int32_t Sound_GetMaxVolume(void); +extern int32_t Sound_GetMasterVolume(void); +extern void Sound_SetMasterVolume(int32_t volume); + void Sound_ResetSources(void); void Sound_PauseAll(void); void Sound_UnpauseAll(void); diff --git a/src/libtrx/include/libtrx/game/ui.h b/src/libtrx/include/libtrx/game/ui.h index 811c320db..fd8a7a341 100644 --- a/src/libtrx/include/libtrx/game/ui.h +++ b/src/libtrx/include/libtrx/game/ui.h @@ -10,6 +10,7 @@ #include "./ui/dialogs/play_any_level.h" #include "./ui/dialogs/save_slot.h" #include "./ui/dialogs/select_level.h" +#include "./ui/dialogs/sound_settings.h" #include "./ui/dialogs/stats.h" #include "./ui/elements/anchor.h" #include "./ui/elements/fade.h" diff --git a/src/libtrx/include/libtrx/game/ui/common.h b/src/libtrx/include/libtrx/game/ui/common.h index 6400264f1..015dfc5f3 100644 --- a/src/libtrx/include/libtrx/game/ui/common.h +++ b/src/libtrx/include/libtrx/game/ui/common.h @@ -50,10 +50,10 @@ typedef struct UI_NODE { // Dimensions in virtual pixels of the screen area // (640x480 for any 4:3 resolution on 1.00 text scaling) -extern int32_t UI_GetCanvasWidth(void); -extern int32_t UI_GetCanvasHeight(void); -extern float UI_ScaleX(float x); -extern float UI_ScaleY(float y); +int32_t UI_GetCanvasWidth(void); +int32_t UI_GetCanvasHeight(void); +float UI_ScaleX(float x); +float UI_ScaleY(float y); // Public API for scene management void UI_BeginScene(void); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/sound_settings.h b/src/libtrx/include/libtrx/game/ui/dialogs/sound_settings.h new file mode 100644 index 000000000..d3bbd128d --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/dialogs/sound_settings.h @@ -0,0 +1,21 @@ +// UI dialog for adjusting music and sound volumes +#pragma once + +#include "../common.h" + +typedef struct UI_SOUND_SETTINGS_STATE UI_SOUND_SETTINGS_STATE; + +// state functions +// Initialize the sound settings dialog state +UI_SOUND_SETTINGS_STATE *UI_SoundSettings_Init(void); + +// Free resources used by the sound settings dialog +void UI_SoundSettings_Free(UI_SOUND_SETTINGS_STATE *s); + +// Handle input/control for the sound settings dialog +// Returns true if the dialog should be closed +bool UI_SoundSettings_Control(UI_SOUND_SETTINGS_STATE *s); + +// draw functions +// Render the sound settings dialog +void UI_SoundSettings(UI_SOUND_SETTINGS_STATE *s); diff --git a/src/libtrx/meson.build b/src/libtrx/meson.build index 78793400b..d93000c31 100644 --- a/src/libtrx/meson.build +++ b/src/libtrx/meson.build @@ -217,6 +217,7 @@ sources = [ 'game/ui/dialogs/play_any_level.c', 'game/ui/dialogs/save_slot.c', 'game/ui/dialogs/select_level.c', + 'game/ui/dialogs/sound_settings.c', 'game/ui/dialogs/stats.c', 'game/ui/elements/anchor.c', 'game/ui/elements/fade.c', diff --git a/src/tr1/game/items.c b/src/tr1/game/items.c index bcc068aa1..fdb607cbb 100644 --- a/src/tr1/game/items.c +++ b/src/tr1/game/items.c @@ -196,70 +196,6 @@ bool Item_Test3DRange(int32_t x, int32_t y, int32_t z, int32_t range) && (SQUARE(x) + SQUARE(y) + SQUARE(z) < SQUARE(range)); } -bool Item_TestPosition( - const ITEM *const src_item, const ITEM *const dst_item, - const OBJECT_BOUNDS *const bounds) -{ - const XYZ_16 rot = { - .x = src_item->rot.x - dst_item->rot.x, - .y = src_item->rot.y - dst_item->rot.y, - .z = src_item->rot.z - dst_item->rot.z, - }; - if (rot.x < bounds->rot.min.x || rot.x > bounds->rot.max.x - || rot.y < bounds->rot.min.y || rot.y > bounds->rot.max.y - || rot.z < bounds->rot.min.z || rot.z > bounds->rot.max.z) { - return false; - } - - const XYZ_32 dist = { - .x = src_item->pos.x - dst_item->pos.x, - .y = src_item->pos.y - dst_item->pos.y, - .z = src_item->pos.z - dst_item->pos.z, - }; - - Matrix_PushUnit(); - Matrix_Rot16(dst_item->rot); - MATRIX *mptr = g_MatrixPtr; - const XYZ_32 shift = { - .x = (mptr->_00 * dist.x + mptr->_10 * dist.y + mptr->_20 * dist.z) - >> W2V_SHIFT, - .y = (mptr->_01 * dist.x + mptr->_11 * dist.y + mptr->_21 * dist.z) - >> W2V_SHIFT, - .z = (mptr->_02 * dist.x + mptr->_12 * dist.y + mptr->_22 * dist.z) - >> W2V_SHIFT, - }; - Matrix_Pop(); - - if (shift.x < bounds->shift.min.x || shift.x > bounds->shift.max.x - || shift.y < bounds->shift.min.y || shift.y > bounds->shift.max.y - || shift.z < bounds->shift.min.z || shift.z > bounds->shift.max.z) { - return false; - } - - return true; -} - -void Item_AlignPosition(ITEM *src_item, ITEM *dst_item, XYZ_32 *vec) -{ - src_item->rot.x = dst_item->rot.x; - src_item->rot.y = dst_item->rot.y; - src_item->rot.z = dst_item->rot.z; - - Matrix_PushUnit(); - Matrix_Rot16(dst_item->rot); - MATRIX *mptr = g_MatrixPtr; - src_item->pos.x = dst_item->pos.x - + ((mptr->_00 * vec->x + mptr->_01 * vec->y + mptr->_02 * vec->z) - >> W2V_SHIFT); - src_item->pos.y = dst_item->pos.y - + ((mptr->_10 * vec->x + mptr->_11 * vec->y + mptr->_12 * vec->z) - >> W2V_SHIFT); - src_item->pos.z = dst_item->pos.z - + ((mptr->_20 * vec->x + mptr->_21 * vec->y + mptr->_22 * vec->z) - >> W2V_SHIFT); - Matrix_Pop(); -} - bool Item_MovePosition( ITEM *item, const ITEM *ref_item, const XYZ_32 *vec, int32_t velocity) { diff --git a/src/tr1/game/items.h b/src/tr1/game/items.h index a9bc07b68..c94893dfc 100644 --- a/src/tr1/game/items.h +++ b/src/tr1/game/items.h @@ -11,9 +11,6 @@ int16_t Item_Spawn(const ITEM *item, GAME_OBJECT_ID obj_id); bool Item_IsNearItem(const ITEM *item, const XYZ_32 *pos, int32_t distance); bool Item_Test3DRange(int32_t x, int32_t y, int32_t z, int32_t range); -bool Item_TestPosition( - const ITEM *src_item, const ITEM *dst_item, const OBJECT_BOUNDS *bounds); -void Item_AlignPosition(ITEM *src_item, ITEM *dst_item, XYZ_32 *vec); bool Item_MovePosition( ITEM *src_item, const ITEM *dst_item, const XYZ_32 *vec, int32_t velocity); void Item_ShiftCol(ITEM *item, COLL_INFO *coll); diff --git a/src/tr1/game/lara/common.c b/src/tr1/game/lara/common.c index 4e1c95406..6596d42fc 100644 --- a/src/tr1/game/lara/common.c +++ b/src/tr1/game/lara/common.c @@ -701,16 +701,6 @@ bool Lara_IsNearItem(const XYZ_32 *pos, int32_t distance) return Item_IsNearItem(g_LaraItem, pos, distance); } -bool Lara_TestPosition(const ITEM *item, const OBJECT_BOUNDS *const bounds) -{ - return Item_TestPosition(g_LaraItem, item, bounds); -} - -void Lara_AlignPosition(ITEM *item, XYZ_32 *vec) -{ - Item_AlignPosition(g_LaraItem, item, vec); -} - bool Lara_MovePosition(ITEM *item, XYZ_32 *vec) { int32_t velocity = g_Config.gameplay.enable_walk_to_items diff --git a/src/tr1/game/lara/common.h b/src/tr1/game/lara/common.h index 291ab20e9..620b0c31e 100644 --- a/src/tr1/game/lara/common.h +++ b/src/tr1/game/lara/common.h @@ -26,8 +26,6 @@ void Lara_SwapMeshExtra(void); bool Lara_IsNearItem(const XYZ_32 *pos, int32_t distance); void Lara_UseItem(GAME_OBJECT_ID obj_id); -bool Lara_TestPosition(const ITEM *item, const OBJECT_BOUNDS *bounds); -void Lara_AlignPosition(ITEM *item, XYZ_32 *vec); bool Lara_MovePosition(ITEM *item, XYZ_32 *vec); void Lara_RevertToPistolsIfNeeded(void); diff --git a/src/tr1/game/music.c b/src/tr1/game/music.c index c4700d9f1..6a0467c8a 100644 --- a/src/tr1/game/music.c +++ b/src/tr1/game/music.c @@ -182,12 +182,12 @@ void Music_Unmute(void) M_SyncVolume(m_AudioStreamID); } -int16_t Music_GetVolume(void) +int32_t Music_GetVolume(void) { return m_Volume; } -void Music_SetVolume(int16_t volume) +void Music_SetVolume(int32_t volume) { if (volume != m_Volume) { m_Volume = volume; @@ -195,16 +195,6 @@ void Music_SetVolume(int16_t volume) } } -int16_t Music_GetMinVolume(void) -{ - return 0; -} - -int16_t Music_GetMaxVolume(void) -{ - return 10; -} - void Music_Pause(void) { if (m_AudioStreamID < 0) { diff --git a/src/tr1/game/music.h b/src/tr1/game/music.h index 97de0c9ad..32957af65 100644 --- a/src/tr1/game/music.h +++ b/src/tr1/game/music.h @@ -19,18 +19,6 @@ void Music_Mute(void); // Unmutes the game music. Doesn't change the music volume. void Music_Unmute(void); -// Gets the game volume. -int16_t Music_GetVolume(void); - -// Sets the game volume. Value can be 0-10. -void Music_SetVolume(int16_t volume); - -// Gets the minimum possible game volume. -int16_t Music_GetMinVolume(void); - -// Gets the maximum possible game volume. -int16_t Music_GetMaxVolume(void); - // Returns the currently playing track. Includes looped music. MUSIC_TRACK_ID Music_GetCurrentPlayingTrack(void); diff --git a/src/tr1/game/option/option.c b/src/tr1/game/option/option.c index 957cc80d6..ca778692f 100644 --- a/src/tr1/game/option/option.c +++ b/src/tr1/game/option/option.c @@ -168,6 +168,9 @@ void Option_Draw(INVENTORY_ITEM *const inv_item) case O_COMPASS_OPTION: Option_Compass_Draw(); break; + case O_SOUND_OPTION: + Option_Sound_Draw(inv_item); + break; case O_PICKUP_OPTION_1: case O_PICKUP_OPTION_2: diff --git a/src/tr1/game/option/option_sound.c b/src/tr1/game/option/option_sound.c index 0af6b0a3c..0777a6d6f 100644 --- a/src/tr1/game/option/option_sound.c +++ b/src/tr1/game/option/option_sound.c @@ -1,170 +1,48 @@ #include "game/option/option_sound.h" -#include "game/game_string.h" -#include "game/input.h" -#include "game/music.h" -#include "game/sound.h" -#include "game/text.h" -#include "global/vars.h" - #include +#include -#include +typedef struct { + UI_SOUND_SETTINGS_STATE *ui; +} M_PRIV; -typedef enum { - TEXT_MUSIC_VOLUME = 0, - TEXT_SOUND_VOLUME = 1, - TEXT_TITLE = 2, - TEXT_TITLE_BORDER = 3, - TEXT_LEFT_ARROW = 4, - TEXT_RIGHT_ARROW = 5, - TEXT_NUMBER_OF = 6, - TEXT_OPTION_MIN = TEXT_MUSIC_VOLUME, - TEXT_OPTION_MAX = TEXT_SOUND_VOLUME, -} SOUND_TEXT; +static M_PRIV m_Priv = {}; -static TEXTSTRING *m_Text[TEXT_NUMBER_OF] = {}; - -static void M_InitText(void); - -static void M_InitText(void) +static void M_Init(M_PRIV *const p) { - char buf[20]; + p->ui = UI_SoundSettings_Init(); +} - m_Text[TEXT_LEFT_ARROW] = Text_Create(-45, 0, "\\{button left}"); - m_Text[TEXT_RIGHT_ARROW] = Text_Create(40, 0, "\\{button right}"); - - m_Text[TEXT_TITLE_BORDER] = Text_Create(0, -32, " "); - m_Text[TEXT_TITLE] = Text_Create(0, -30, GS(SOUND_SET_VOLUMES)); - - if (g_Config.audio.music_volume > 10) { - g_Config.audio.music_volume = 10; - } - sprintf(buf, "\\{icon music} %2d", g_Config.audio.music_volume); - m_Text[TEXT_MUSIC_VOLUME] = Text_Create(0, 0, buf); - - if (g_Config.audio.sound_volume > 10) { - g_Config.audio.sound_volume = 10; - } - sprintf(buf, "\\{icon sound} %2d", g_Config.audio.sound_volume); - m_Text[TEXT_SOUND_VOLUME] = Text_Create(0, 25, buf); - - Text_AddBackground(m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED); - Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED); - Text_AddBackground(m_Text[TEXT_TITLE], 136, 0, 0, 0, TS_HEADING); - Text_AddOutline(m_Text[TEXT_TITLE], TS_HEADING); - Text_AddBackground(m_Text[TEXT_TITLE_BORDER], 140, 85, 0, 0, TS_BACKGROUND); - Text_AddOutline(m_Text[TEXT_TITLE_BORDER], TS_BACKGROUND); - - for (int i = 0; i < TEXT_NUMBER_OF; i++) { - Text_CentreH(m_Text[i], 1); - Text_CentreV(m_Text[i], 1); +static void M_Shutdown(M_PRIV *const p) +{ + if (p->ui != nullptr) { + UI_SoundSettings_Free(p->ui); + p->ui = nullptr; } } -void Option_Sound_Control(INVENTORY_ITEM *inv_item, const bool is_busy) +void Option_Sound_Control(INVENTORY_ITEM *const inv_item, const bool is_busy) { + M_PRIV *const p = &m_Priv; if (is_busy) { return; } - - char buf[20]; - - if (!m_Text[TEXT_MUSIC_VOLUME]) { - M_InitText(); + if (p->ui == nullptr) { + M_Init(p); } + UI_SoundSettings_Control(p->ui); +} - if (g_InputDB.menu_up && g_OptionSelected > TEXT_OPTION_MIN) { - Text_RemoveOutline(m_Text[g_OptionSelected]); - Text_RemoveBackground(m_Text[g_OptionSelected]); - --g_OptionSelected; - Text_AddBackground( - m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED); - Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED); - Text_SetPos(m_Text[TEXT_LEFT_ARROW], -45, 0); - Text_SetPos(m_Text[TEXT_RIGHT_ARROW], 40, 0); - } - - if (g_InputDB.menu_down && g_OptionSelected < TEXT_OPTION_MAX) { - Text_RemoveOutline(m_Text[g_OptionSelected]); - Text_RemoveBackground(m_Text[g_OptionSelected]); - ++g_OptionSelected; - Text_AddBackground( - m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED); - Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED); - Text_SetPos(m_Text[TEXT_LEFT_ARROW], -45, 25); - Text_SetPos(m_Text[TEXT_RIGHT_ARROW], 40, 25); - } - - switch (g_OptionSelected) { - case TEXT_MUSIC_VOLUME: - if (g_InputDB.menu_left - && g_Config.audio.music_volume > Music_GetMinVolume()) { - g_Config.audio.music_volume--; - Config_Write(); - Music_SetVolume(g_Config.audio.music_volume); - Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); - sprintf(buf, "\\{icon music} %2d", g_Config.audio.music_volume); - Text_ChangeText(m_Text[TEXT_MUSIC_VOLUME], buf); - } else if ( - g_InputDB.menu_right - && g_Config.audio.music_volume < Music_GetMaxVolume()) { - g_Config.audio.music_volume++; - Config_Write(); - Music_SetVolume(g_Config.audio.music_volume); - Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); - sprintf(buf, "\\{icon music} %2d", g_Config.audio.music_volume); - Text_ChangeText(m_Text[TEXT_MUSIC_VOLUME], buf); - } - - Text_Hide( - m_Text[TEXT_LEFT_ARROW], - g_Config.audio.music_volume == Music_GetMinVolume()); - Text_Hide( - m_Text[TEXT_RIGHT_ARROW], - g_Config.audio.music_volume == Music_GetMaxVolume()); - - break; - - case TEXT_SOUND_VOLUME: - if (g_InputDB.menu_left - && g_Config.audio.sound_volume > Sound_GetMinVolume()) { - g_Config.audio.sound_volume--; - Config_Write(); - Sound_SetMasterVolume(g_Config.audio.sound_volume); - Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); - sprintf(buf, "\\{icon sound} %2d", g_Config.audio.sound_volume); - Text_ChangeText(m_Text[TEXT_SOUND_VOLUME], buf); - } else if ( - g_InputDB.menu_right - && g_Config.audio.sound_volume < Sound_GetMaxVolume()) { - g_Config.audio.sound_volume++; - Config_Write(); - Sound_SetMasterVolume(g_Config.audio.sound_volume); - Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); - sprintf(buf, "\\{icon sound} %2d", g_Config.audio.sound_volume); - Text_ChangeText(m_Text[TEXT_SOUND_VOLUME], buf); - } - - Text_Hide( - m_Text[TEXT_LEFT_ARROW], - g_Config.audio.sound_volume == Sound_GetMinVolume()); - Text_Hide( - m_Text[TEXT_RIGHT_ARROW], - g_Config.audio.sound_volume == Sound_GetMaxVolume()); - - break; - } - - if (g_InputDB.menu_confirm || g_InputDB.menu_back) { - Option_Sound_Shutdown(); +void Option_Sound_Draw(INVENTORY_ITEM *const inv_item) +{ + M_PRIV *const p = &m_Priv; + if (p->ui != nullptr) { + UI_SoundSettings(p->ui); } } void Option_Sound_Shutdown(void) { - for (int i = 0; i < TEXT_NUMBER_OF; i++) { - Text_Remove(m_Text[i]); - m_Text[i] = nullptr; - } + M_Shutdown(&m_Priv); } diff --git a/src/tr1/game/option/option_sound.h b/src/tr1/game/option/option_sound.h index 9128617d5..ee5f63ba7 100644 --- a/src/tr1/game/option/option_sound.h +++ b/src/tr1/game/option/option_sound.h @@ -3,4 +3,5 @@ #include void Option_Sound_Control(INVENTORY_ITEM *inv_item, bool is_busy); +void Option_Sound_Draw(INVENTORY_ITEM *inv_item); void Option_Sound_Shutdown(void); diff --git a/src/tr1/game/output.c b/src/tr1/game/output.c index 9bed2681b..c63c73c94 100644 --- a/src/tr1/game/output.c +++ b/src/tr1/game/output.c @@ -49,6 +49,7 @@ typedef struct { int32_t thickness; } LIGHTNING; +static bool m_Initialized = false; static int32_t m_LightningCount = 0; static LIGHTNING m_LightningTable[MAX_LIGHTNINGS]; static int32_t m_TextureMap[GFX_MAX_TEXTURES] = { GFX_NO_TEXTURE }; @@ -385,6 +386,11 @@ static void M_DrawSprite( bool Output_Init(void) { + if (m_Initialized) { + return true; + } + m_Initialized = true; + for (int32_t i = 0; i < GFX_MAX_TEXTURES; i++) { m_TextureMap[i] = GFX_NO_TEXTURE; m_TextureSurfaces[i] = nullptr; @@ -406,6 +412,11 @@ bool Output_Init(void) void Output_Shutdown(void) { + if (!m_Initialized) { + return; + } + m_Initialized = false; + Output_Meshes_Shutdown(); Output_Sprites_Shutdown(); Output_Textures_Shutdown(); diff --git a/src/tr1/game/shell.c b/src/tr1/game/shell.c index b8f190715..da1fdea06 100644 --- a/src/tr1/game/shell.c +++ b/src/tr1/game/shell.c @@ -81,37 +81,18 @@ static SHELL_ARGS m_Args = { static const char *m_CurrentGameFlowPath; -static void M_ParseArgs(SHELL_ARGS *out_args); +static void M_ShowHelp(void); static void M_LoadConfig(void); static void M_HandleConfigChange(const EVENT *event, void *data); -static void M_ParseArgs(SHELL_ARGS *const out_args) +static void M_ShowHelp(void) { - const char **args = nullptr; - int32_t arg_count = 0; - Shell_GetCommandLine(&arg_count, &args); - - out_args->mod = M_MOD_OG; - - for (int32_t i = 0; i < arg_count; i++) { - if (!strcmp(args[i], "-gold")) { - out_args->mod = M_MOD_UB; - } - if (!strcmp(args[i], "-demo_pc")) { - out_args->mod = M_MOD_DEMO_PC; - } - if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level")) - && i + 1 < arg_count) { - out_args->level_to_play = args[i + 1]; - out_args->mod = M_MOD_CUSTOM_LEVEL; - } - if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save")) - && i + 1 < arg_count) { - if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) { - out_args->save_to_load--; - } - } - } + puts("Currently available options:"); + puts(""); + puts("-g/--gold: launch The Unfinished Business expansion pack."); + puts(" --demo-pc: launch the PC demo level file."); + puts("-l/--level : launch a specific level file."); + puts("-s/--save : launch from a specific save slot (starts at 1)."); } static void M_HandleConfigChange(const EVENT *const event, void *const data) @@ -176,10 +157,40 @@ const char *Shell_GetGameFlowPath(void) return m_ModPaths[m_Args.mod].game_flow_path; } -void Shell_Main(void) +bool Shell_ParseArgs(const int32_t arg_count, const char **args) { - M_ParseArgs(&m_Args); + SHELL_ARGS *const out_args = &m_Args; + out_args->mod = M_MOD_OG; + for (int32_t i = 0; i < arg_count; i++) { + if (!strcmp(args[i], "-h") || !strcmp(args[i], "--help")) { + M_ShowHelp(); + return false; + } + if (!strcmp(args[i], "-g") || !strcmp(args[i], "--gold") + || !strcmp(args[i], "-gold")) { + out_args->mod = M_MOD_UB; + } + if (!strcmp(args[i], "--demo-pc") || !strcmp(args[i], "-demo_pc")) { + out_args->mod = M_MOD_DEMO_PC; + } + if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level")) + && i + 1 < arg_count) { + out_args->level_to_play = args[i + 1]; + out_args->mod = M_MOD_CUSTOM_LEVEL; + } + if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save")) + && i + 1 < arg_count) { + if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) { + out_args->save_to_load--; + } + } + } + return true; +} + +int32_t Shell_Main(void) +{ GameString_Init(); EnumMap_Init(); Config_Init(); @@ -202,7 +213,7 @@ void Shell_Main(void) if (!Output_Init()) { Shell_ExitSystem("Could not initialise video system"); - return; + return 1; } Screen_Init(); @@ -318,6 +329,7 @@ void Shell_Main(void) if (m_Args.level_to_play != nullptr) { Memory_FreePointer(&g_GameFlow.level_tables[GFLT_MAIN].levels[0].path); } + return 0; } void Shell_ProcessInput(void) diff --git a/src/tr1/game/sound.c b/src/tr1/game/sound.c index 3f479d368..7728f2465 100644 --- a/src/tr1/game/sound.c +++ b/src/tr1/game/sound.c @@ -528,18 +528,6 @@ void Sound_StopAll(void) Audio_Sample_CloseAll(); } -void Sound_SetMasterVolume(int8_t volume) -{ - int8_t raw_volume = volume ? 6 * volume + 3 : 0; - m_MasterVolumeDefault = raw_volume & 0x3F; - m_MasterVolume = raw_volume & 0x3F; -} - -int8_t Sound_GetMasterVolume(void) -{ - return (m_MasterVolume - 3) / 6; -} - int32_t Sound_GetMinVolume(void) { return 0; @@ -550,6 +538,18 @@ int32_t Sound_GetMaxVolume(void) return 10; } +void Sound_SetMasterVolume(int32_t volume) +{ + int8_t raw_volume = volume ? 6 * volume + 3 : 0; + m_MasterVolumeDefault = raw_volume & 0x3F; + m_MasterVolume = raw_volume & 0x3F; +} + +int32_t Sound_GetMasterVolume(void) +{ + return (m_MasterVolume - 3) / 6; +} + void Sound_ResetAmbient(void) { M_ResetAmbientLoudness(); diff --git a/src/tr1/game/sound.h b/src/tr1/game/sound.h index 4cd892222..9a403a6c6 100644 --- a/src/tr1/game/sound.h +++ b/src/tr1/game/sound.h @@ -13,10 +13,6 @@ bool Sound_StopEffect(SOUND_EFFECT_ID sfx_num, const XYZ_32 *pos); void Sound_UpdateEffects(void); void Sound_ResetEffects(void); void Sound_StopAmbientSounds(void); -int8_t Sound_GetMasterVolume(void); -void Sound_SetMasterVolume(int8_t volume); -int32_t Sound_GetMinVolume(void); -int32_t Sound_GetMaxVolume(void); void Sound_LoadSamples( size_t num_samples, const char **sample_pointers, size_t *sizes); int32_t Sound_GetMaxSamples(void); diff --git a/src/tr1/game/ui/common.c b/src/tr1/game/ui/common.c deleted file mode 100644 index b3cdf81f9..000000000 --- a/src/tr1/game/ui/common.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "game/screen.h" - -#include -#include - -int32_t UI_GetCanvasWidth(void) -{ - return Screen_GetResHeightDownscaled(RSR_GENERIC) * 16 / 9; -} - -int32_t UI_GetCanvasHeight(void) -{ - return Screen_GetResHeightDownscaled(RSR_GENERIC); -} - -float UI_ScaleX(const float x) -{ - return Screen_GetRenderScale(x * 0x10000, RSR_GENERIC) / (float)0x10000; -} - -float UI_ScaleY(const float y) -{ - return Screen_GetRenderScale(y * 0x10000, RSR_GENERIC) / (float)0x10000; -} diff --git a/src/tr1/meson.build b/src/tr1/meson.build index 71f6c3254..a047f9ce8 100644 --- a/src/tr1/meson.build +++ b/src/tr1/meson.build @@ -260,7 +260,6 @@ sources = [ 'game/spawn.c', 'game/stats/common.c', 'game/text.c', - 'game/ui/common.c', 'game/ui/dialogs/stats.c', 'game/viewport.c', 'global/enum_map.c', diff --git a/src/tr2/game/items.c b/src/tr2/game/items.c index a66bb7eb3..09c7b16ca 100644 --- a/src/tr2/game/items.c +++ b/src/tr2/game/items.c @@ -20,40 +20,6 @@ static BOUNDS_16 m_NullBounds = {}; static BOUNDS_16 m_InterpolatedBounds = {}; -static OBJECT_BOUNDS M_ConvertBounds(const int16_t *bounds_in); - -static OBJECT_BOUNDS M_ConvertBounds(const int16_t *const bounds_in) -{ - // TODO: remove this conversion utility once we gain control over its - // incoming arguments - return (OBJECT_BOUNDS) { - .shift = { - .min = { - .x = bounds_in[0], - .y = bounds_in[2], - .z = bounds_in[4], - }, - .max = { - .x = bounds_in[1], - .y = bounds_in[3], - .z = bounds_in[5], - }, - }, - .rot = { - .min = { - .x = bounds_in[6], - .y = bounds_in[8], - .z = bounds_in[10], - }, - .max = { - .x = bounds_in[7], - .y = bounds_in[9], - .z = bounds_in[11], - }, - }, - }; -} - void Item_Control(void) { int16_t item_num = Item_GetNextActive(); @@ -188,95 +154,6 @@ int16_t Item_GetHeight(const ITEM *const item) return height; } -int32_t Item_TestPosition( - const int16_t *const bounds_in, const ITEM *const src_item, - const ITEM *const dst_item) -{ - const OBJECT_BOUNDS bounds = M_ConvertBounds(bounds_in); - - const XYZ_16 rot = { - .x = dst_item->rot.x - src_item->rot.x, - .y = dst_item->rot.y - src_item->rot.y, - .z = dst_item->rot.z - src_item->rot.z, - }; - const XYZ_32 dist = { - .x = dst_item->pos.x - src_item->pos.x, - .y = dst_item->pos.y - src_item->pos.y, - .z = dst_item->pos.z - src_item->pos.z, - }; - - // clang-format off - if (rot.x < bounds.rot.min.x || - rot.x > bounds.rot.max.x || - rot.y < bounds.rot.min.y || - rot.y > bounds.rot.max.y || - rot.z < bounds.rot.min.z || - rot.z > bounds.rot.max.z - ) { - return false; - } - // clang-format on - - Matrix_PushUnit(); - Matrix_Rot16(src_item->rot); - const MATRIX *const m = g_MatrixPtr; - const XYZ_32 shift = { - .x = (dist.x * m->_00 + dist.y * m->_10 + dist.z * m->_20) >> W2V_SHIFT, - .y = (dist.x * m->_01 + dist.y * m->_11 + dist.z * m->_21) >> W2V_SHIFT, - .z = (dist.x * m->_02 + dist.y * m->_12 + dist.z * m->_22) >> W2V_SHIFT, - }; - Matrix_Pop(); - - // clang-format off - return ( - shift.x >= bounds.shift.min.x && - shift.x <= bounds.shift.max.x && - shift.y >= bounds.shift.min.y && - shift.y <= bounds.shift.max.y && - shift.z >= bounds.shift.min.z && - shift.z <= bounds.shift.max.z - ); - // clang-format on -} - -void Item_AlignPosition( - const XYZ_32 *const vec, const ITEM *const src_item, ITEM *const dst_item) -{ - dst_item->rot = src_item->rot; - Matrix_PushUnit(); - Matrix_Rot16(src_item->rot); - const MATRIX *const m = g_MatrixPtr; - const XYZ_32 shift = { - .x = (vec->x * m->_00 + vec->y * m->_01 + vec->z * m->_02) >> W2V_SHIFT, - .y = (vec->x * m->_10 + vec->y * m->_11 + vec->z * m->_12) >> W2V_SHIFT, - .z = (vec->x * m->_20 + vec->y * m->_21 + vec->z * m->_22) >> W2V_SHIFT, - }; - Matrix_Pop(); - - const XYZ_32 new_pos = { - .x = src_item->pos.x + shift.x, - .y = src_item->pos.y + shift.y, - .z = src_item->pos.z + shift.z, - }; - - int16_t room_num = dst_item->room_num; - const SECTOR *const sector = - Room_GetSector(new_pos.x, new_pos.y, new_pos.z, &room_num); - const int32_t height = - Room_GetHeight(sector, new_pos.x, new_pos.y, new_pos.z); - const int32_t ceiling = - Room_GetCeiling(sector, new_pos.x, new_pos.y, new_pos.z); - - if (ABS(height - dst_item->pos.y) > STEP_L - || ABS(ceiling - dst_item->pos.y) < LARA_HEIGHT) { - return; - } - - dst_item->pos.x = new_pos.x; - dst_item->pos.y = new_pos.y; - dst_item->pos.z = new_pos.z; -} - int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frames[], int32_t *rate) { const ANIM *const anim = Item_GetAnim(item); diff --git a/src/tr2/game/items.h b/src/tr2/game/items.h index 6a26aa675..c82d6ec0c 100644 --- a/src/tr2/game/items.h +++ b/src/tr2/game/items.h @@ -8,10 +8,6 @@ void Item_Control(void); void Item_ClearKilled(void); void Item_ShiftCol(ITEM *item, COLL_INFO *coll); void Item_UpdateRoom(ITEM *item, int32_t height); -int32_t Item_TestPosition( - const int16_t *bounds, const ITEM *src_item, const ITEM *dst_item); -void Item_AlignPosition( - const XYZ_32 *vec, const ITEM *src_item, ITEM *dst_item); int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate); bool Item_IsNearItem(const ITEM *item, const XYZ_32 *pos, int32_t distance); diff --git a/src/tr2/game/objects/general/detonator.c b/src/tr2/game/objects/general/detonator.c index 50a4980e6..e0b0120a3 100644 --- a/src/tr2/game/objects/general/detonator.c +++ b/src/tr2/game/objects/general/detonator.c @@ -10,33 +10,37 @@ #include "game/sound.h" #include "global/vars.h" +#include + #define EXPLOSION_START_FRAME 76 #define EXPLOSION_END_FRAME 99 #define EXPLOSION_ACTION_FRAME 80 static XYZ_32 m_DetonatorPosition = { .x = 0, .y = 0, .z = 0 }; -static int16_t m_GongBounds[12] = { - -WALL_L / 2, - +WALL_L, - -100, - +100, - -WALL_L / 2 - 300, - -WALL_L / 2 + 100, - -30 * DEG_1, - +30 * DEG_1, - +0, - +0, - +0, - +0, +static const OBJECT_BOUNDS m_GongBounds = { + .shift = { + .min = { .x = -WALL_L / 2, .y = -100, .z = -WALL_L / 2 - 300, }, + .max = { .x = +WALL_L, .y = +100, .z = -WALL_L / 2 + 100, }, + }, + .rot = { + .min = { .x = -30 * DEG_1, .y = 0, .z = 0, }, + .max = { .x = +30 * DEG_1, .y = 0, .z = 0, }, + }, }; +static const OBJECT_BOUNDS *M_Bounds(void); static void M_CreateGongBonger(ITEM *lara_item); static void M_Setup1(OBJECT *obj); static void M_Setup2(OBJECT *obj); static void M_Control(int16_t item_num); static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +static const OBJECT_BOUNDS *M_Bounds(void) +{ + return &m_GongBounds; +} + static void M_CreateGongBonger(ITEM *const lara_item) { const int16_t item_gong_bonger_num = Item_Create(); @@ -64,12 +68,14 @@ static void M_CreateGongBonger(ITEM *const lara_item) static void M_Setup1(OBJECT *const obj) { obj->collision_func = M_Collision; + obj->bounds_func = M_Bounds; } static void M_Setup2(OBJECT *const obj) { obj->collision_func = M_Collision; obj->control_func = M_Control; + obj->bounds_func = Pickup_Bounds; obj->save_flags = 1; obj->save_anim = 1; } @@ -102,6 +108,7 @@ static void M_Collision( } ITEM *const item = Item_Get(item_num); + const OBJECT *const obj = Object_Get(item->object_id); const XYZ_16 old_rot = item->rot; const int16_t x = item->rot.x; const int16_t y = item->rot.y; @@ -117,16 +124,11 @@ static void M_Collision( goto normal_collision; } - if (item->object_id == O_DETONATOR_2) { - if (!Item_TestPosition(g_PickupBounds, item, lara_item)) { - goto normal_collision; - } - } else { - if (!Item_TestPosition(m_GongBounds, item, lara_item)) { - goto normal_collision; - } else { - item->rot = old_rot; - } + if (!Lara_TestPosition(item, obj->bounds_func())) { + goto normal_collision; + } + if (item->object_id == O_DETONATOR_1) { + item->rot = old_rot; } if (g_Inv_Chosen == NO_OBJECT) { @@ -138,7 +140,7 @@ static void M_Collision( } Inv_RemoveItem(O_KEY_OPTION_2); - Item_AlignPosition(&m_DetonatorPosition, item, lara_item); + Lara_AlignPosition(item, &m_DetonatorPosition); Item_SwitchToObjAnim(lara_item, LA_EXTRA_BREATH, 0, O_LARA_EXTRA); lara_item->current_anim_state = LA_EXTRA_BREATH; if (item->object_id == O_DETONATOR_2) { diff --git a/src/tr2/game/objects/general/flare_item.c b/src/tr2/game/objects/general/flare_item.c index 0a89767cb..fe893694e 100644 --- a/src/tr2/game/objects/general/flare_item.c +++ b/src/tr2/game/objects/general/flare_item.c @@ -6,6 +6,7 @@ static void M_Setup(OBJECT *obj); static void M_Setup(OBJECT *const obj) { obj->collision_func = Pickup_Collision; + obj->bounds_func = Pickup_Bounds; obj->control_func = Flare_Control; obj->draw_func = Flare_DrawInAir; obj->save_position = 1; diff --git a/src/tr2/game/objects/general/keyhole.c b/src/tr2/game/objects/general/keyhole.c index 359d23110..c9b44a88f 100644 --- a/src/tr2/game/objects/general/keyhole.c +++ b/src/tr2/game/objects/general/keyhole.c @@ -17,29 +17,29 @@ static XYZ_32 m_KeyholePosition = { .z = WALL_L / 2 - LARA_RADIUS - 50, }; -static int16_t m_KeyholeBounds[12] = { - // clang-format off - -200, - +200, - +0, - +0, - +WALL_L / 2 - 200, - +WALL_L / 2, - -10 * DEG_1, - +10 * DEG_1, - -30 * DEG_1, - +30 * DEG_1, - -10 * DEG_1, - +10 * DEG_1, - // clang-format on +static const OBJECT_BOUNDS m_KeyHoleBounds = { + .shift = { + .min = { .x = -200, .y = +0, .z = +WALL_L / 2 - 200, }, + .max = { .x = +200, .y = +0, .z = +WALL_L / 2, }, + }, + .rot = { + .min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, }, + .max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, }, + }, }; +static const OBJECT_BOUNDS *M_Bounds(void); static void M_Consume( ITEM *lara_item, ITEM *keyhole_item, GAME_OBJECT_ID key_obj_id); static void M_Refuse(const ITEM *lara_item); static void M_Setup(OBJECT *obj); static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +static const OBJECT_BOUNDS *M_Bounds(void) +{ + return &m_KeyHoleBounds; +} + static void M_Refuse(const ITEM *const lara_item) { if (lara_item->pos.x == g_InteractPosition.x @@ -57,7 +57,7 @@ static void M_Consume( const GAME_OBJECT_ID key_obj_id) { Inv_RemoveItem(key_obj_id); - Item_AlignPosition(&m_KeyholePosition, keyhole_item, lara_item); + Lara_AlignPosition(keyhole_item, &m_KeyholePosition); lara_item->goal_anim_state = LS_USE_KEY; do { Lara_Animate(lara_item); @@ -71,6 +71,7 @@ static void M_Consume( static void M_Setup(OBJECT *const obj) { obj->collision_func = M_Collision; + obj->bounds_func = M_Bounds; obj->save_flags = 1; } @@ -82,12 +83,13 @@ static void M_Collision( } ITEM *const item = Item_Get(item_num); + const OBJECT *const obj = Object_Get(item->object_id); if ((g_Inv_Chosen == NO_OBJECT && !g_Input.action) || g_Lara.gun_status != LGS_ARMLESS || lara_item->gravity) { return; } - if (!Item_TestPosition(m_KeyholeBounds, item, lara_item)) { + if (!Lara_TestPosition(item, obj->bounds_func())) { return; } diff --git a/src/tr2/game/objects/general/movable_block.c b/src/tr2/game/objects/general/movable_block.c index 63f0f1b2b..3dbea88e8 100644 --- a/src/tr2/game/objects/general/movable_block.c +++ b/src/tr2/game/objects/general/movable_block.c @@ -20,21 +20,18 @@ typedef enum { MOVABLE_BLOCK_STATE_PULL = 3, } MOVABLE_BLOCK_STATE; -static int16_t m_MovableBlockBounds[12] = { - -300, - +300, - +0, - +0, - -WALL_L / 2 - LARA_RADIUS - 80, - -WALL_L / 2, - -10 * DEG_1, - +10 * DEG_1, - -30 * DEG_1, - +30 * DEG_1, - -10 * DEG_1, - +10 * DEG_1, +static const OBJECT_BOUNDS m_MovableBlockBounds = { + .shift = { + .min = { .x = -300, .y = 0, .z = -WALL_L / 2 - LARA_RADIUS - 80, }, + .max = { .x = +300, .y = 0, .z = -WALL_L / 2, }, + }, + .rot = { + .min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, }, + .max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, }, + }, }; +static const OBJECT_BOUNDS *M_Bounds(void); static bool M_TestDestination(const ITEM *item, int32_t block_height); static bool M_TestPush( const ITEM *item, int32_t block_height, DIRECTION quadrant); @@ -47,6 +44,11 @@ static void M_Draw(const ITEM *item); static void M_Control(int16_t item_num); static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +static const OBJECT_BOUNDS *M_Bounds(void) +{ + return &m_MovableBlockBounds; +} + static bool M_TestDestination( const ITEM *const item, const int32_t block_height) { @@ -199,6 +201,7 @@ static void M_Setup(OBJECT *const obj) obj->handle_save_func = M_HandleSave; obj->control_func = M_Control; obj->collision_func = M_Collision; + obj->bounds_func = M_Bounds; obj->draw_func = M_Draw; obj->save_position = 1; obj->save_flags = 1; @@ -303,7 +306,7 @@ static void M_Collision( break; } - if (!Item_TestPosition(m_MovableBlockBounds, item, lara_item)) { + if (!Lara_TestPosition(item, obj->bounds_func())) { return; } @@ -346,7 +349,7 @@ static void M_Collision( } else if ( Item_TestAnimEqual(lara_item, LA_PUSHABLE_GRAB) && Item_TestFrameEqual(lara_item, LF_PPREADY)) { - if (!Item_TestPosition(m_MovableBlockBounds, item, lara_item)) { + if (!Lara_TestPosition(item, obj->bounds_func())) { return; } diff --git a/src/tr2/game/objects/general/pickup.c b/src/tr2/game/objects/general/pickup.c index 5d0910541..2f1853f3f 100644 --- a/src/tr2/game/objects/general/pickup.c +++ b/src/tr2/game/objects/general/pickup.c @@ -26,41 +26,29 @@ #define LF_PICKUP_FLARE_UW 20 #define LF_PICKUP_UW 18 -int16_t g_PickupBounds[12] = { - // clang-format off - -WALL_L / 4, - +WALL_L / 4, - -100, - +100, - -WALL_L / 4, - +WALL_L / 4, - -10 * DEG_1, - +10 * DEG_1, - +0, - +0, - +0, - +0, - // clang-format on -}; - static XYZ_32 m_PickupPosition = { .x = 0, .y = 0, .z = -100 }; static XYZ_32 m_PickupPositionUW = { .x = 0, .y = -200, .z = -350 }; -static int16_t m_PickupBoundsUW[12] = { - // clang-format off - -WALL_L / 2, - +WALL_L / 2, - -WALL_L / 2, - +WALL_L / 2, - -WALL_L / 2, - +WALL_L / 2, - -45 * DEG_1, - +45 * DEG_1, - -45 * DEG_1, - +45 * DEG_1, - -45 * DEG_1, - +45 * DEG_1, - // clang-format on +static const OBJECT_BOUNDS m_PickUpBounds = { + .shift = { + .min = { .x = -WALL_L / 4, .y = -100, .z = -WALL_L / 4, }, + .max = { .x = +WALL_L / 4, .y = +100, .z = +WALL_L / 4, }, + }, + .rot = { + .min = { .x = -10 * DEG_1, .y = 0, .z = 0, }, + .max = { .x = +10 * DEG_1, .y = 0, .z = 0, }, + }, +}; + +static const OBJECT_BOUNDS m_PickUpBoundsUW = { + .shift = { + .min = { .x = -WALL_L / 2, .y = -WALL_L / 2, .z = -WALL_L / 2, }, + .max = { .x = +WALL_L / 2, .y = +WALL_L / 2, .z = +WALL_L / 2, }, + }, + .rot = { + .min = { .x = -45 * DEG_1, .y = -45 * DEG_1, .z = -45 * DEG_1, }, + .max = { .x = +45 * DEG_1, .y = +45 * DEG_1, .z = +45 * DEG_1, }, + }, }; static void M_DoPickup(int16_t item_num); @@ -107,13 +95,14 @@ static void M_DoFlarePickup(const int16_t item_num) static void M_DoAboveWater(const int16_t item_num, ITEM *const lara_item) { ITEM *const item = Item_Get(item_num); + const OBJECT *const obj = Object_Get(item->object_id); const XYZ_16 old_rot = item->rot; item->rot.x = 0; item->rot.y = lara_item->rot.y; item->rot.z = 0; - if (!Item_TestPosition(g_PickupBounds, item, lara_item)) { + if (!Lara_TestPosition(item, obj->bounds_func())) { goto cleanup; } @@ -145,7 +134,7 @@ static void M_DoAboveWater(const int16_t item_num, ITEM *const lara_item) lara_item->goal_anim_state = LS_STOP; g_Lara.gun_status = LGS_HANDS_BUSY; } else { - Item_AlignPosition(&m_PickupPosition, item, lara_item); + Lara_AlignPosition(item, &m_PickupPosition); lara_item->goal_anim_state = LS_PICKUP; do { Lara_Animate(lara_item); @@ -163,13 +152,14 @@ cleanup: static void M_DoUnderwater(const int16_t item_num, ITEM *const lara_item) { ITEM *const item = Item_Get(item_num); + const OBJECT *const obj = Object_Get(item->object_id); const XYZ_16 old_rot = item->rot; item->rot.x = -25 * DEG_1; item->rot.y = lara_item->rot.y; item->rot.z = 0; - if (!Item_TestPosition(m_PickupBoundsUW, item, lara_item)) { + if (!Lara_TestPosition(item, obj->bounds_func())) { goto cleanup; } @@ -224,6 +214,7 @@ static void M_Setup(OBJECT *const obj) obj->handle_save_func = M_HandleSave; obj->activate_func = M_Activate; obj->collision_func = Pickup_Collision; + obj->bounds_func = Pickup_Bounds; obj->draw_func = M_Draw; obj->save_position = 1; obj->save_flags = 1; @@ -343,6 +334,15 @@ static void M_Draw(const ITEM *const item) Matrix_Pop(); } +const OBJECT_BOUNDS *Pickup_Bounds(void) +{ + if (g_Lara.water_status == LWS_UNDERWATER) { + return &m_PickUpBoundsUW; + } else { + return &m_PickUpBounds; + } +} + void Pickup_Collision( const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) { diff --git a/src/tr2/game/objects/general/pickup.h b/src/tr2/game/objects/general/pickup.h index dfac91fc3..1b1a51fff 100644 --- a/src/tr2/game/objects/general/pickup.h +++ b/src/tr2/game/objects/general/pickup.h @@ -2,7 +2,6 @@ #include "global/types.h" -extern int16_t g_PickupBounds[]; - void Pickup_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); bool Pickup_Trigger(int16_t item_num); +const OBJECT_BOUNDS *Pickup_Bounds(void); diff --git a/src/tr2/game/objects/general/puzzle_hole.c b/src/tr2/game/objects/general/puzzle_hole.c index 174755780..f63db4c5c 100644 --- a/src/tr2/game/objects/general/puzzle_hole.c +++ b/src/tr2/game/objects/general/puzzle_hole.c @@ -17,23 +17,18 @@ static XYZ_32 m_PuzzleHolePosition = { .z = WALL_L / 2 - LARA_RADIUS - 85, }; -static int16_t m_PuzzleHoleBounds[12] = { - // clang-format off - -200, - +200, - +0, - +0, - +WALL_L / 2 - 200, - +WALL_L / 2, - -10 * DEG_1, - +10 * DEG_1, - -30 * DEG_1, - +30 * DEG_1, - -10 * DEG_1, - +10 * DEG_1, - // clang-format on +static const OBJECT_BOUNDS m_PuzzleHoleBounds = { + .shift = { + .min = { .x = -200, .y = 0, .z = WALL_L / 2 - 200, }, + .max = { .x = +200, .y = 0, .z = WALL_L / 2, }, + }, + .rot = { + .min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, }, + .max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, }, + }, }; +static const OBJECT_BOUNDS *M_Bounds(void); static void M_Refuse(const ITEM *lara_item); static void M_Consume( ITEM *lara_item, ITEM *puzzle_hole_item, GAME_OBJECT_ID puzzle_obj_id); @@ -43,6 +38,11 @@ static void M_SetupDone(OBJECT *obj); static void M_HandleSave(ITEM *item, SAVEGAME_STAGE stage); static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +static const OBJECT_BOUNDS *M_Bounds(void) +{ + return &m_PuzzleHoleBounds; +} + static void M_Refuse(const ITEM *const lara_item) { if (lara_item->pos.x != g_InteractPosition.x @@ -58,7 +58,7 @@ static void M_Consume( const GAME_OBJECT_ID puzzle_obj_id) { Inv_RemoveItem(puzzle_obj_id); - Item_AlignPosition(&m_PuzzleHolePosition, puzzle_hole_item, lara_item); + Lara_AlignPosition(puzzle_hole_item, &m_PuzzleHolePosition); lara_item->goal_anim_state = LS_USE_PUZZLE; do { Lara_Animate(lara_item); @@ -82,6 +82,7 @@ static void M_SetupEmpty(OBJECT *const obj) { obj->collision_func = M_Collision; obj->handle_save_func = M_HandleSave; + obj->bounds_func = M_Bounds; obj->save_flags = 1; } @@ -103,10 +104,11 @@ static void M_Collision( const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) { ITEM *const item = Item_Get(item_num); + const OBJECT *const obj = Object_Get(item->object_id); if (lara_item->current_anim_state != LS_STOP) { if (lara_item->current_anim_state != LS_USE_PUZZLE - || !Item_TestPosition(m_PuzzleHoleBounds, item, lara_item) + || !Lara_TestPosition(item, obj->bounds_func()) || !Item_TestFrameEqual(lara_item, LF_USE_PUZZLE)) { return; } @@ -120,7 +122,7 @@ static void M_Collision( return; } - if (!Item_TestPosition(m_PuzzleHoleBounds, item, lara_item)) { + if (!Lara_TestPosition(item, obj->bounds_func())) { return; } diff --git a/src/tr2/game/objects/general/switch.c b/src/tr2/game/objects/general/switch.c index 3af8f70e2..382425e6f 100644 --- a/src/tr2/game/objects/general/switch.c +++ b/src/tr2/game/objects/general/switch.c @@ -17,40 +17,30 @@ static XYZ_32 g_PushSwitchPosition = { .x = 0, .y = 0, .z = 292 }; static XYZ_32 m_AirlockPosition = { .x = 0, .y = 0, .z = 212 }; static XYZ_32 m_SwitchUWPosition = { .x = 0, .y = 0, .z = 108 }; -static int16_t m_SwitchBounds[12] = { - // clang-format off - -220, - +220, - +0, - +0, - +WALL_L / 2 - 220, - +WALL_L / 2, - -10 * DEG_1, - +10 * DEG_1, - -30 * DEG_1, - +30 * DEG_1, - -10 * DEG_1, - +10 * DEG_1, - // clang-format on +static const OBJECT_BOUNDS m_SwitchBounds = { + .shift = { + .min = { .x = -220, .y = +0, .z = +WALL_L / 2 - 220, }, + .max = { .x = +220, .y = +0, .z = +WALL_L / 2, }, + }, + .rot = { + .min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, }, + .max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, }, + }, }; -static int16_t m_SwitchBoundsUW[12] = { - // clang-format off - -WALL_L, - +WALL_L, - -WALL_L, - +WALL_L, - -WALL_L, - +WALL_L / 2, - -80 * DEG_1, - +80 * DEG_1, - -80 * DEG_1, - +80 * DEG_1, - -80 * DEG_1, - +80 * DEG_1, - // clang-format on +static const OBJECT_BOUNDS m_SwitchBoundsUW = { + .shift = { + .min = { .x = -WALL_L, .y = -WALL_L, .z = -WALL_L, }, + .max = { .x = +WALL_L, .y = +WALL_L, .z = +WALL_L / 2, }, + }, + .rot = { + .min = { .x = -80 * DEG_1, .y = -80 * DEG_1, .z = -80 * DEG_1, }, + .max = { .x = +80 * DEG_1, .y = +80 * DEG_1, .z = +80 * DEG_1, }, + }, }; +static const OBJECT_BOUNDS *M_Bounds(void); +static const OBJECT_BOUNDS *M_BoundsUW(void); static void M_AlignLara(ITEM *lara_item, ITEM *switch_item); static void M_SwitchOn(ITEM *switch_item, ITEM *lara_item); static void M_SwitchOff(ITEM *switch_item, ITEM *lara_item); @@ -62,19 +52,30 @@ static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); static void M_CollisionUW(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); static void M_Control(int16_t item_num); +static const OBJECT_BOUNDS *M_Bounds(void) +{ + return &m_SwitchBounds; +} + +static const OBJECT_BOUNDS *M_BoundsUW(void) +{ + return &m_SwitchBoundsUW; +} + static void M_AlignLara(ITEM *const lara_item, ITEM *const switch_item) { + lara_item->rot.y = switch_item->rot.y; switch (switch_item->object_id) { case O_SWITCH_TYPE_AIRLOCK: - Item_AlignPosition(&m_AirlockPosition, switch_item, lara_item); + Lara_AlignPosition(switch_item, &m_AirlockPosition); break; case O_SWITCH_TYPE_SMALL: - Item_AlignPosition(&g_SmallSwitchPosition, switch_item, lara_item); + Lara_AlignPosition(switch_item, &g_SmallSwitchPosition); break; case O_SWITCH_TYPE_BUTTON: - Item_AlignPosition(&g_PushSwitchPosition, switch_item, lara_item); + Lara_AlignPosition(switch_item, &g_PushSwitchPosition); break; } } @@ -139,33 +140,35 @@ static void M_Setup(OBJECT *const obj) { M_SetupBase(obj); obj->collision_func = M_Collision; + obj->bounds_func = M_Bounds; } static void M_SetupPushButton(OBJECT *const obj) { M_Setup(obj); obj->enable_interpolation = false; + obj->bounds_func = M_Bounds; } static void M_SetupUW(OBJECT *const obj) { M_SetupBase(obj); obj->collision_func = M_CollisionUW; + obj->bounds_func = M_BoundsUW; } static void M_Collision( const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) { ITEM *const item = Item_Get(item_num); + const OBJECT *const obj = Object_Get(item->object_id); if (!g_Input.action || item->status != IS_INACTIVE || g_Lara.gun_status != LGS_ARMLESS || lara_item->gravity || lara_item->current_anim_state != LS_STOP - || !Item_TestPosition(m_SwitchBounds, item, lara_item)) { + || !Lara_TestPosition(item, obj->bounds_func())) { return; } - lara_item->rot.y = item->rot.y; - if (item->object_id == O_SWITCH_TYPE_AIRLOCK && item->current_anim_state == SWITCH_STATE_ON) { return; @@ -193,6 +196,7 @@ static void M_CollisionUW( const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) { ITEM *const item = Item_Get(item_num); + const OBJECT *const obj = Object_Get(item->object_id); if (!g_Input.action || item->status != IS_INACTIVE || g_Lara.water_status != LWS_UNDERWATER @@ -201,7 +205,7 @@ static void M_CollisionUW( return; } - if (!Item_TestPosition(m_SwitchBoundsUW, item, lara_item)) { + if (!Lara_TestPosition(item, obj->bounds_func())) { return; } diff --git a/src/tr2/game/objects/general/zipline.c b/src/tr2/game/objects/general/zipline.c index 6dd04d867..0effd9bb6 100644 --- a/src/tr2/game/objects/general/zipline.c +++ b/src/tr2/game/objects/general/zipline.c @@ -22,33 +22,35 @@ static XYZ_32 m_ZiplineHandlePosition = { .y = 0, .z = WALL_L / 2 - 141, }; -static int16_t m_ZiplineHandleBounds[12] = { - // clang-format off - -WALL_L / 4, - +WALL_L / 4, - -100, - +100, - +WALL_L / 4, - +WALL_L / 2, - +0, - +0, - -25 * DEG_1, - +25 * DEG_1, - +0, - +0, - // clang-format on + +static const OBJECT_BOUNDS m_ZiplineHandleBounds = { + .shift = { + .min = { .x = -WALL_L / 4, .y = -100, .z = +WALL_L / 4, }, + .max = { .x = +WALL_L / 4, .y = +100, .z = +WALL_L / 2, }, + }, + .rot = { + .min = { .x = +0, .y = -25 * DEG_1, .z = +0, }, + .max = { .x = +0, .y = +25 * DEG_1, .z = +0, }, + }, }; +static const OBJECT_BOUNDS *M_Bounds(void); static void M_Setup(OBJECT *obj); static void M_Initialise(int16_t item_num); static void M_Control(int16_t item_num); static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +static const OBJECT_BOUNDS *M_Bounds(void) +{ + return &m_ZiplineHandleBounds; +} + static void M_Setup(OBJECT *const obj) { obj->initialise_func = M_Initialise; obj->control_func = M_Control; obj->collision_func = M_Collision; + obj->bounds_func = M_Bounds; obj->save_position = 1; obj->save_flags = 1; obj->save_anim = 1; @@ -149,11 +151,12 @@ static void M_Collision( return; } - if (!Item_TestPosition(m_ZiplineHandleBounds, item, lara_item)) { + const OBJECT *const obj = Object_Get(item->object_id); + if (!Lara_TestPosition(item, obj->bounds_func())) { return; } - Item_AlignPosition(&m_ZiplineHandlePosition, item, lara_item); + Lara_AlignPosition(item, &m_ZiplineHandlePosition); g_Lara.gun_status = LGS_HANDS_BUSY; lara_item->goal_anim_state = LS_ZIPLINE; diff --git a/src/tr2/game/option/option_sound.c b/src/tr2/game/option/option_sound.c index f6cfab736..f5dec720f 100644 --- a/src/tr2/game/option/option_sound.c +++ b/src/tr2/game/option/option_sound.c @@ -1,132 +1,48 @@ -#include "game/game_string.h" -#include "game/input.h" -#include "game/inventory_ring.h" -#include "game/music.h" #include "game/option/option.h" -#include "game/sound.h" -#include "game/text.h" -#include "global/vars.h" #include -#include +#include -#include +typedef struct { + UI_SOUND_SETTINGS_STATE *ui; +} M_PRIV; -static TEXTSTRING *m_SoundText[4]; +static M_PRIV m_Priv = {}; -static void M_InitText(void); -static void M_ShutdownText(void); - -static void M_InitText(void) +static void M_Init(M_PRIV *const p) { - CLAMPG(g_Config.audio.music_volume, 10); - CLAMPG(g_Config.audio.sound_volume, 10); + p->ui = UI_SoundSettings_Init(); +} - char text[32]; - sprintf(text, "\\{icon music} %2d", g_Config.audio.music_volume); - m_SoundText[0] = Text_Create(0, 0, text); - Text_AddBackground(m_SoundText[0], 128, 0, 0, 0, TS_REQUESTED); - Text_AddOutline(m_SoundText[0], TS_REQUESTED); - - sprintf(text, "\\{icon sound} %2d", g_Config.audio.sound_volume); - m_SoundText[1] = Text_Create(0, 25, text); - - m_SoundText[2] = Text_Create(0, -32, " "); - Text_AddBackground(m_SoundText[2], 140, 85, 0, 0, TS_BACKGROUND); - Text_AddOutline(m_SoundText[2], TS_BACKGROUND); - - m_SoundText[3] = Text_Create(0, -30, GS(SOUND_SET_VOLUMES)); - Text_AddBackground(m_SoundText[3], 136, 0, 0, 0, TS_HEADING); - Text_AddOutline(m_SoundText[3], TS_HEADING); - - for (int32_t i = 0; i < 4; i++) { - Text_CentreH(m_SoundText[i], true); - Text_CentreV(m_SoundText[i], true); +static void M_Shutdown(M_PRIV *const p) +{ + if (p->ui != nullptr) { + UI_SoundSettings_Free(p->ui); + p->ui = nullptr; } } -static void M_ShutdownText(void) +void Option_Sound_Control(INVENTORY_ITEM *const inv_item, const bool is_busy) { - for (int32_t i = 0; i < 4; i++) { - Text_Remove(m_SoundText[i]); - m_SoundText[i] = nullptr; + M_PRIV *const p = &m_Priv; + if (is_busy) { + return; + } + if (p->ui == nullptr) { + M_Init(p); + } + UI_SoundSettings_Control(p->ui); +} + +void Option_Sound_Draw(INVENTORY_ITEM *const inv_item) +{ + M_PRIV *const p = &m_Priv; + if (p->ui != nullptr) { + UI_SoundSettings(p->ui); } } void Option_Sound_Shutdown(void) { - M_ShutdownText(); -} - -void Option_Sound_Control(INVENTORY_ITEM *const item, const bool is_busy) -{ - if (is_busy) { - return; - } - - char text[32]; - - if (m_SoundText[0] == nullptr) { - M_InitText(); - } - - if (g_InputDB.menu_up && g_SoundOptionLine > 0) { - Text_RemoveOutline(m_SoundText[g_SoundOptionLine]); - Text_RemoveBackground(m_SoundText[g_SoundOptionLine]); - g_SoundOptionLine--; - Text_AddBackground( - m_SoundText[g_SoundOptionLine], 128, 0, 0, 0, TS_REQUESTED); - Text_AddOutline(m_SoundText[g_SoundOptionLine], TS_REQUESTED); - } - - if (g_InputDB.menu_down && g_SoundOptionLine < 1) { - Text_RemoveOutline(m_SoundText[g_SoundOptionLine]); - Text_RemoveBackground(m_SoundText[g_SoundOptionLine]); - g_SoundOptionLine++; - Text_AddBackground( - m_SoundText[g_SoundOptionLine], 128, 0, 0, 0, TS_REQUESTED); - Text_AddOutline(m_SoundText[g_SoundOptionLine], TS_REQUESTED); - } - - if (g_SoundOptionLine) { - bool changed = false; - if (g_InputDB.menu_left && g_Config.audio.sound_volume > 0) { - g_Config.audio.sound_volume--; - changed = true; - } else if (g_InputDB.menu_right && g_Config.audio.sound_volume < 10) { - g_Config.audio.sound_volume++; - changed = true; - } - - if (changed) { - sprintf(text, "\\{icon sound} %2d", g_Config.audio.sound_volume); - Text_ChangeText(m_SoundText[1], text); - Sound_SetMasterVolume(g_Config.audio.sound_volume); - Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); - } - } else { - bool changed = false; - if (g_InputDB.menu_left && g_Config.audio.music_volume > 0) { - g_Config.audio.music_volume--; - changed = true; - } else if (g_InputDB.menu_right && g_Config.audio.music_volume < 10) { - g_Config.audio.music_volume++; - changed = true; - } - - if (changed) { - sprintf(text, "\\{icon music} %2d", g_Config.audio.music_volume); - Text_ChangeText(m_SoundText[0], text); - Music_SetVolume(g_Config.audio.music_volume); - Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); - } - } - - if (g_InputDB.menu_confirm || g_InputDB.menu_back) { - Option_Sound_Shutdown(); - } -} - -void Option_Sound_Draw(INVENTORY_ITEM *const item) -{ + M_Shutdown(&m_Priv); } diff --git a/src/tr2/game/render/common.c b/src/tr2/game/render/common.c index bcfb617ff..82b232443 100644 --- a/src/tr2/game/render/common.c +++ b/src/tr2/game/render/common.c @@ -45,7 +45,6 @@ static RENDERER *M_GetRenderer(void) } else if (g_Config.rendering.render_mode == RM_HARDWARE) { r = &m_Renderer_HW; } - ASSERT(r != nullptr); return r; } @@ -124,7 +123,6 @@ void Render_Init(void) void Render_Shutdown(void) { - LOG_DEBUG(""); RENDERER *const r = M_GetRenderer(); if (r != nullptr) { r->Close(r); diff --git a/src/tr2/game/shell/common.c b/src/tr2/game/shell/common.c index d0f32b1ab..b40b7e758 100644 --- a/src/tr2/game/shell/common.c +++ b/src/tr2/game/shell/common.c @@ -99,7 +99,7 @@ static void M_HandleQuit(void); static void M_ConfigureOpenGL(void); static bool M_CreateGameWindow(void); -static void M_ParseArgs(SHELL_ARGS *out_args); +static void M_ShowHelp(void); static void M_LoadConfig(void); static void M_HandleConfigChange(const EVENT *event, void *data); @@ -342,30 +342,13 @@ static bool M_CreateGameWindow(void) return true; } -static void M_ParseArgs(SHELL_ARGS *const out_args) +static void M_ShowHelp(void) { - const char **args = nullptr; - int32_t arg_count = 0; - Shell_GetCommandLine(&arg_count, &args); - - out_args->mod = M_MOD_OG; - - for (int32_t i = 0; i < arg_count; i++) { - if (!strcmp(args[i], "-gold")) { - out_args->mod = M_MOD_GM; - } - if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level")) - && i + 1 < arg_count) { - out_args->level_to_play = args[i + 1]; - out_args->mod = M_MOD_CUSTOM_LEVEL; - } - if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save")) - && i + 1 < arg_count) { - if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) { - out_args->save_to_load--; - } - } - } + puts("Currently available options:"); + puts(""); + puts("-g/--gold: launch The Golden Mask expansion pack."); + puts("-l/--level : launch a specific level file."); + puts("-s/--save : launch from a specific save slot (starts at 1)."); } static void M_LoadConfig(void) @@ -433,10 +416,39 @@ static void M_HandleConfigChange(const EVENT *const event, void *const data) } } -// TODO: refactor the hell out of me -void Shell_Main(void) +bool Shell_ParseArgs(const int32_t arg_count, const char **args) { - M_ParseArgs(&m_Args); + SHELL_ARGS *const out_args = &m_Args; + out_args->mod = M_MOD_OG; + + for (int32_t i = 0; i < arg_count; i++) { + if (!strcmp(args[i], "-h") || !strcmp(args[i], "--help")) { + M_ShowHelp(); + return false; + } + if (!strcmp(args[i], "-g") || !strcmp(args[i], "--gold") + || !strcmp(args[i], "-gold")) { + out_args->mod = M_MOD_GM; + } + if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level")) + && i + 1 < arg_count) { + out_args->level_to_play = args[i + 1]; + out_args->mod = M_MOD_CUSTOM_LEVEL; + } + if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save")) + && i + 1 < arg_count) { + if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) { + out_args->save_to_load--; + } + } + } + return true; +} + +// TODO: refactor the hell out of me +int32_t Shell_Main(void) +{ + LOG_INFO("Game directory: %s", File_GetGameDirectory()); if (m_Args.mod == M_MOD_GM) { Object_Get(O_MONK_3)->setup_func = Monk3_Setup; @@ -465,7 +477,7 @@ void Shell_Main(void) if (!M_CreateGameWindow()) { Shell_ExitSystem("Failed to create game window"); - return; + return 1; } Random_Seed(); @@ -561,7 +573,7 @@ void Shell_Main(void) if (gf_cmd.action == GF_NOOP || gf_cmd.action == GF_EXIT_TO_TITLE) { Shell_ExitSystem("Title disabled & no replacement"); - return; + return 1; } } else { gf_cmd = GF_RunTitle(); @@ -583,6 +595,7 @@ void Shell_Main(void) if (m_Args.level_to_play != nullptr) { Memory_FreePointer(&g_GameFlow.level_tables[GFLT_MAIN].levels[0].path); } + return 0; } void Shell_Shutdown(void) diff --git a/src/tr2/game/sound.h b/src/tr2/game/sound.h index 8b0fc32ad..53b5e88ca 100644 --- a/src/tr2/game/sound.h +++ b/src/tr2/game/sound.h @@ -9,9 +9,6 @@ void Sound_Init(void); void Sound_Shutdown(void); -void Sound_SetMasterVolume(int32_t volume); void Sound_UpdateEffects(void); void Sound_StopEffect(SOUND_EFFECT_ID sample_id); void Sound_EndScene(void); -int32_t Sound_GetMinVolume(void); -int32_t Sound_GetMaxVolume(void); diff --git a/src/tr2/game/ui/common.c b/src/tr2/game/ui/common.c deleted file mode 100644 index f4b10ec67..000000000 --- a/src/tr2/game/ui/common.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "global/vars.h" - -#include -#include -#include - -int32_t UI_GetCanvasWidth(void) -{ - return Scaler_CalcInverse(g_PhdWinWidth, SCALER_TARGET_GENERIC); -} - -int32_t UI_GetCanvasHeight(void) -{ - return Scaler_CalcInverse(g_PhdWinHeight, SCALER_TARGET_GENERIC); -} - -float UI_ScaleX(const float x) -{ - return Scaler_Calc(x, SCALER_TARGET_GENERIC); -} - -float UI_ScaleY(const float y) -{ - return Scaler_Calc(y, SCALER_TARGET_GENERIC); -} diff --git a/src/tr2/global/types_decomp.h b/src/tr2/global/types_decomp.h index 3d1825df8..59ecb25d0 100644 --- a/src/tr2/global/types_decomp.h +++ b/src/tr2/global/types_decomp.h @@ -151,13 +151,6 @@ typedef struct { int32_t pitch; } SKIDOO_INFO; -typedef struct { - struct { - XYZ_16 min; - XYZ_16 max; - } shift, rot; -} OBJECT_BOUNDS; - typedef struct { int32_t xv; int32_t yv; diff --git a/src/tr2/meson.build b/src/tr2/meson.build index 80c5e9c77..b3d748095 100644 --- a/src/tr2/meson.build +++ b/src/tr2/meson.build @@ -266,7 +266,6 @@ sources = [ 'game/spawn.c', 'game/stats.c', 'game/text.c', - 'game/ui/common.c', 'game/ui/dialogs/graphic_settings.c', 'game/ui/dialogs/stats.c', 'game/viewport.c', diff --git a/tools/download_assets b/tools/download_assets index ecc2292c1..6b20c3b9f 100755 --- a/tools/download_assets +++ b/tools/download_assets @@ -42,41 +42,33 @@ def extract_zip(zip_path: Path, dest_dir: Path) -> None: z.extractall(dest_dir) -def download_assets(assets: list[tuple[str, Path]]) -> None: +def download_assets(asset_urls: list[str], target_dir: Path) -> None: with tempfile.TemporaryDirectory() as tmpdir_str: tmpdir = Path(tmpdir_str) - for url, dest in assets: + for url in asset_urls: filename = Path(url).name local_zip = tmpdir / filename download_to_file(url, local_zip) - extract_zip(local_zip, dest) + extract_zip(local_zip, target_dir) print("Asset download and extraction complete.") def main() -> None: args = parse_args() - assets: dict[int, list[tuple[str, Path]]] = { - 1: [ - ( - "https://lostartefacts.dev/aux/tr1x/main.zip", - Path("data/tr1/ship"), - ) - ], + asset_urls_map: dict[int, list[str]] = { + 1: ["https://lostartefacts.dev/aux/tr1x/main.zip"], 2: [ - ( - "https://lostartefacts.dev/aux/tr2x/main.zip", - Path("data/tr2/ship"), - ) + "https://lostartefacts.dev/aux/tr2x/main.zip", + "https://lostartefacts.dev/aux/tr2x/trgm.zip", ], } - match str(args.game_version): - case "1": - download_assets(assets[1]) - case "2": - download_assets(assets[2]) - case "all": - download_assets(assets[1]) - download_assets(assets[2]) + + versions = {"1": [1], "2": [2], "all": [1, 2]}[args.game_version] + for version in versions: + download_assets( + asset_urls_map[version], + target_dir=PROJECT_PATHS[version].shipped_data_dir, + ) if __name__ == "__main__": diff --git a/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs b/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs index f98813d91..b7ed06ad7 100644 --- a/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs +++ b/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs @@ -15,15 +15,10 @@ public abstract class GenericInstallSource : BaseInstallSource }; public override bool IsDownloadingMusicNeeded(string sourceDirectory) - { - return !Directory.Exists(Path.Combine(sourceDirectory, "audio")) - && !Directory.Exists(Path.Combine(sourceDirectory, "music")); - } + => true; public override bool IsDownloadingExpansionNeeded(string sourceDirectory) - { - return true; - } + => true; public override async Task CopyOriginalGameFiles( string sourceDirectory,