Compare commits

..

No commits in common. "develop" and "tr2-1.0.2" have entirely different histories.

72 changed files with 901 additions and 999 deletions

7
.gitignore vendored
View file

@ -38,11 +38,4 @@ src/tr2/subprojects/dwarfstack.wrap
data/tr1/ship/data/images/ data/tr1/ship/data/images/
data/tr2/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/ data/tr2/ship/music/

View file

@ -428,11 +428,7 @@
"OSD_AMBIGUOUS_INPUT_2": "Ambiguous input: %s and %s", "OSD_AMBIGUOUS_INPUT_2": "Ambiguous input: %s and %s",
"OSD_AMBIGUOUS_INPUT_3": "Ambiguous input: %s, %s, ...", "OSD_AMBIGUOUS_INPUT_3": "Ambiguous input: %s, %s, ...",
"OSD_COMMAND_BAD_INVOCATION": "Invalid invocation: %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_UNAVAILABLE": "This command is not currently available",
"OSD_COMMAND_VALID_VALUES": "Valid values: %s",
"OSD_COMPLETE_LEVEL": "Level complete!", "OSD_COMPLETE_LEVEL": "Level complete!",
"OSD_CONFIG_OPTION_GET": "%s is currently set to %s", "OSD_CONFIG_OPTION_GET": "%s is currently set to %s",
"OSD_CONFIG_OPTION_SET": "%s changed to %s", "OSD_CONFIG_OPTION_SET": "%s changed to %s",
@ -535,9 +531,7 @@
"PHOTO_MODE_ROTATE_PROMPT": "Rotate camera", "PHOTO_MODE_ROTATE_PROMPT": "Rotate camera",
"PHOTO_MODE_SNAP_PROMPT": "Take picture", "PHOTO_MODE_SNAP_PROMPT": "Take picture",
"PHOTO_MODE_TITLE": "Photo Mode", "PHOTO_MODE_TITLE": "Photo Mode",
"SOUND_DIALOG_MUSIC": "\\{icon sound} Sound", "SOUND_SET_VOLUMES": "Set Volumes",
"SOUND_DIALOG_SOUND": "\\{icon music} Music",
"SOUND_DIALOG_TITLE": "Set Volumes",
"STATS_AMMO": "AMMO HITS/USED", "STATS_AMMO": "AMMO HITS/USED",
"STATS_BASIC_FMT": "%d", "STATS_BASIC_FMT": "%d",
"STATS_BONUS_STATISTICS": "Bonus Statistics", "STATS_BONUS_STATISTICS": "Bonus Statistics",

View file

@ -217,7 +217,6 @@
], ],
"injections": [ "injections": [
"data/injections/living_deck_goon_sfx.bin", "data/injections/living_deck_goon_sfx.bin",
"data/injections/living_fd.bin",
"data/injections/living_pickup_meshes.bin", "data/injections/living_pickup_meshes.bin",
"data/injections/seaweed_collision.bin", "data/injections/seaweed_collision.bin",
], ],
@ -398,7 +397,6 @@
{"type": "total_stats", "background_path": "data/images/end.webp"}, {"type": "total_stats", "background_path": "data/images/end.webp"},
], ],
"injections": [ "injections": [
"data/injections/explosion.bin",
"data/injections/house_itemrots.bin", "data/injections/house_itemrots.bin",
], ],
}, },

View file

@ -547,11 +547,7 @@
"OSD_BILINEAR_FILTER_OFF": "Bilinear filter: off", "OSD_BILINEAR_FILTER_OFF": "Bilinear filter: off",
"OSD_BILINEAR_FILTER_ON": "Bilinear filter: on", "OSD_BILINEAR_FILTER_ON": "Bilinear filter: on",
"OSD_COMMAND_BAD_INVOCATION": "Invalid invocation: %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_UNAVAILABLE": "This command is not currently available",
"OSD_COMMAND_VALID_VALUES": "Valid values: %s",
"OSD_COMPLETE_LEVEL": "Level complete!", "OSD_COMPLETE_LEVEL": "Level complete!",
"OSD_CONFIG_OPTION_GET": "%s is currently set to %s", "OSD_CONFIG_OPTION_GET": "%s is currently set to %s",
"OSD_CONFIG_OPTION_SET": "%s changed to %s", "OSD_CONFIG_OPTION_SET": "%s changed to %s",
@ -659,9 +655,7 @@
"PHOTO_MODE_ROTATE_PROMPT": "Rotate camera", "PHOTO_MODE_ROTATE_PROMPT": "Rotate camera",
"PHOTO_MODE_SNAP_PROMPT": "Take picture", "PHOTO_MODE_SNAP_PROMPT": "Take picture",
"PHOTO_MODE_TITLE": "Photo Mode", "PHOTO_MODE_TITLE": "Photo Mode",
"SOUND_DIALOG_MUSIC": "\\{icon sound} Sound", "SOUND_SET_VOLUMES": "Set Volumes",
"SOUND_DIALOG_SOUND": "\\{icon music} Music",
"SOUND_DIALOG_TITLE": "Set Volumes",
"STATS_AMMO_HITS": "Hits", "STATS_AMMO_HITS": "Hits",
"STATS_AMMO_USED": "Ammo Used", "STATS_AMMO_USED": "Ammo Used",
"STATS_ASSAULT_FINISH": "Finish", "STATS_ASSAULT_FINISH": "Finish",

View file

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

View file

@ -266,63 +266,30 @@ request number, but it's important to carefully review the body field, as it
often includes unwanted content. 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 ### Tooling
Internal tools are typically coded in a reasonably recent version of Python, Internal tools are typically coded in a reasonably recent version of Python,
while avoiding the use of bash, shell, and similar languages. 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 ### Releasing a new version
New version releases are published automatically whenever a new tag is pushed New version releases happen automatically whenever a new tag is pushed to the
to the `stable` branch with the help of GitHub actions. `stable` branch with the help of GitHub actions. In general this is accompanied
The general workflow is this: with a special commit `docs: release X.Y.Z` that also adjusts the changelog.
See git history for details.
```console
TR_VERSION=...
RELEASE_VERSION=...
# Switch to the stable branch.
git checkout stable
# Merge `develop` into it.
git merge develop
# Create a special commit `docs: release X.Y.Z` marking the release in the
# relevant changelog file. Then tag it with `tr1-X.Y.Z` or `tr2-X.Y.Z`.
# You can do that by hand, or run the command below:
tools/release commit ${TR_VERSION} ${RELEASE_VERSION}
# Review the changelog content.
# Switch back to develop.
git checkout develop
# Merge stable using fast-forward.
git merge --ff stable
# Review both branches and changes. If everything is okay, push to GitHub.
# You can do this by hand: git push origin develop stable tr1-X.Y.Z, or:
# tools/release push ${TR_VERSION} ${RELEASE_VERSION}
```
### Hotfixes
Hotfix releases are a bit different as we try to not include non-bugfix changes
in them. Here instead of merging `develop` to `stable` we cherry-pick relevant
changes, resolving conflicts along the way.
### Versioning
We increase the major version for significant releases based on judgment,
typically defaulting to increasing the minor version. Hotfixes increase the
patch version.
## Glossary ## Glossary

View file

@ -5,15 +5,12 @@
- added support for antitriggers, like TR2+ (#2580) - added support for antitriggers, like TR2+ (#2580)
- added support for aspect ratio-specific images (#1840) - added support for aspect ratio-specific images (#1840)
- added an option to wraparound when scrolling UI dialogs, such as save/load (#2834) - added an option to wraparound when scrolling UI dialogs, such as save/load (#2834)
- added aliases to CLI options (`-gold` becomes `-g/--gold`, `-demo_pc` becomes `--demo-pc`)
- added a `--help` CLI option (may not output anything on Windows machines  OS bug)
- changed the `draw_distance_fade` and `draw_distance_max` to `fog_start` and `fog_end` - changed the `draw_distance_fade` and `draw_distance_max` to `fog_start` and `fog_end`
- changed `Select Detail` dialog title to `Graphic Options` - changed `Select Detail` dialog title to `Graphic Options`
- changed the number of static mesh slots from 50 to 256 (#2734) - 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 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 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 save to take priority over load when both inputs are held on the same frame, in line with OG (#2833)
- changed the sound dialog appearance (repositioned and added text labels)
- changed The Unfinished Business strings to default to the OG strings file for the main tables (#2847) - changed The 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 the bilinear filter to not readjust the UVs (#2258)
- fixed disabling the cutscenes causing the game to exit (#2743, regression from 4.8) - fixed disabling the cutscenes causing the game to exit (#2743, regression from 4.8)
@ -25,7 +22,6 @@
- fixed the scale of the four keys in St. Francis' Folly (#2652) - 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 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 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 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 trapezoid filter warping on faces close to the camera (#2629, regression from 4.9)
- fixed Mac builds crashing upon start (regression from 4.9) - fixed Mac builds crashing upon start (regression from 4.9)
@ -41,13 +37,9 @@
- fixed Lara at times ending up in incorrect rooms when using the teleport cheat (#2486, regression from 3.0) - 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 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 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 bubble appearance (#2672)
- improved rendering performance - improved rendering performance
- improved pause exit dialog - it can now be canceled with escape - 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) - 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 ## [4.9](https://github.com/LostArtefacts/TRX/compare/tr1-4.8.3...tr1-4.9) - 2025-03-31

View file

@ -485,7 +485,6 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det
- fixed being able to shoot the scion multiple times if save/load is used while it blows up - fixed 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 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 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 #### Cheats
- added a fly cheat - added a fly cheat

View file

@ -1,14 +1,4 @@
## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...develop) - ××××-××-×× ## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.2...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 ## [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) - changed The Golden Mask strings to default to the OG strings file for the main tables (#2847)

View file

@ -221,7 +221,6 @@ However, you can easily download them manually from these urls:
- fixed the following floor data issues: - fixed the following floor data issues:
- **Opera House**: fixed the trigger under item 203 to trigger it rather than item 204 - **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 - **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 - **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) - **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) - **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)
@ -258,7 +257,6 @@ 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 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 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 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 - improved the animation of Lara's braid
#### Cheats #### Cheats

View file

@ -84,32 +84,17 @@ static void M_SeekToStart(AUDIO_STREAM_SOUND *stream)
ASSERT(stream != nullptr); ASSERT(stream != nullptr);
stream->timestamp = stream->start_at; stream->timestamp = stream->start_at;
int32_t error_code;
if (stream->start_at <= 0.0) { if (stream->start_at <= 0.0) {
// reset to start of file // reset to start of file
avio_seek(stream->av.format_ctx->pb, 0, SEEK_SET); avio_seek(stream->av.format_ctx->pb, 0, SEEK_SET);
error_code = avformat_seek_file( avformat_seek_file(
stream->av.format_ctx, -1, 0, 0, 0, AVSEEK_FLAG_FRAME); stream->av.format_ctx, -1, 0, 0, 0, AVSEEK_FLAG_FRAME);
} else { } else {
// seek to specific timestamp // seek to specific timestamp
AVFormatContext *const fmt = stream->av.format_ctx; const double time_base_sec = av_q2d(stream->av.stream->time_base);
if (fmt->pb != nullptr && (fmt->pb->seekable & AVIO_SEEKABLE_NORMAL)) { av_seek_frame(
const int64_t ts = (int64_t)(stream->start_at * AV_TIME_BASE); stream->av.format_ctx, 0, stream->start_at / time_base_sec,
error_code = avformat_seek_file( AVSEEK_FLAG_ANY);
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));
} }
} }
@ -272,10 +257,6 @@ static bool M_InitialiseFromPath(int32_t sound_id, const char *file_path)
int32_t error_code; int32_t error_code;
char *full_path = File_GetFullPath(file_path); 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]; AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id];
@ -699,51 +680,26 @@ double Audio_Stream_GetDuration(int32_t sound_id)
return duration; return duration;
} }
bool Audio_Stream_SeekTimestamp(const int32_t sound_id, const double timestamp) bool Audio_Stream_SeekTimestamp(int32_t sound_id, double timestamp)
{ {
if (!g_AudioDeviceID || sound_id < 0 if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) { || sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false; return false;
} }
AUDIO_STREAM_SOUND *const stream = &m_Streams[sound_id]; if (m_Streams[sound_id].is_playing) {
stream->start_at = timestamp; SDL_LockAudioDevice(g_AudioDeviceID);
if (!stream->is_used) { AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id];
return false; const double time_base_sec = av_q2d(stream->av.stream->time_base);
} av_seek_frame(
ASSERT(stream->av.format_ctx != nullptr); stream->av.format_ctx, 0, timestamp / time_base_sec,
ASSERT(stream->av.codec_ctx != nullptr); AVSEEK_FLAG_ANY);
ASSERT(stream->av.stream != nullptr); avcodec_flush_buffers(stream->av.codec_ctx);
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); SDL_UnlockAudioDevice(g_AudioDeviceID);
return false; return true;
} }
const int32_t stream_index = stream->av.stream->index; return false;
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) bool Audio_Stream_SetStartTimestamp(int32_t sound_id, double timestamp)

View file

@ -121,25 +121,3 @@ void EnumMap_Shutdown(void)
} }
} }
} }
VECTOR *EnumMap_ListValues(const char *const enum_name)
{
if (enum_name == nullptr) {
return nullptr;
}
// Compare the prefix to find the matching enum values.
const size_t prefix_len = strlen(enum_name) + 1;
VECTOR *const results = Vector_Create(sizeof(char *));
M_INVERSE_ENTRY *entry;
M_INVERSE_ENTRY *tmp;
HASH_ITER(hh, m_InverseMap, entry, tmp)
{
if (strncmp(entry->key, enum_name, prefix_len - 1) == 0
&& entry->key[prefix_len - 1] == '|') {
Vector_Add(results, &entry->str_value);
}
}
return results;
}

View file

@ -14,7 +14,6 @@
static const char *M_Resolve(const char *option_name); static const char *M_Resolve(const char *option_name);
static bool M_SameKey(const char *key1, const char *key2); 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); static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
@ -77,56 +76,6 @@ cleanup:
return result; 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) char *Console_Cmd_Config_NormalizeKey(const char *key)
{ {
// TODO: Once we support arbitrary glyphs, this conversion should // TODO: Once we support arbitrary glyphs, this conversion should
@ -312,16 +261,18 @@ COMMAND_RESULT Console_Cmd_Config_Helper(
char *normalized_name = Console_Cmd_Config_NormalizeKey(option->name); char *normalized_name = Console_Cmd_Config_NormalizeKey(option->name);
COMMAND_RESULT result = CR_BAD_INVOCATION;
if (new_value == nullptr || String_IsEmpty(new_value)) { if (new_value == nullptr || String_IsEmpty(new_value)) {
char cur_value[128]; char cur_value[128];
if (Console_Cmd_Config_GetCurrentValue(option, cur_value, 128)) { if (Console_Cmd_Config_GetCurrentValue(option, cur_value, 128)) {
Console_Log(GS(OSD_CONFIG_OPTION_GET), normalized_name, cur_value); Console_Log(GS(OSD_CONFIG_OPTION_GET), normalized_name, cur_value);
return CR_SUCCESS; result = CR_SUCCESS;
} else {
result = CR_FAILURE;
} }
return CR_FAILURE; return result;
} }
COMMAND_RESULT result;
if (Console_Cmd_Config_SetCurrentValue(option, new_value)) { if (Console_Cmd_Config_SetCurrentValue(option, new_value)) {
Config_Write(); Config_Write();
@ -329,15 +280,6 @@ COMMAND_RESULT Console_Cmd_Config_Helper(
ASSERT(Console_Cmd_Config_GetCurrentValue(option, final_value, 128)); ASSERT(Console_Cmd_Config_GetCurrentValue(option, final_value, 128));
Console_Log(GS(OSD_CONFIG_OPTION_SET), normalized_name, final_value); Console_Log(GS(OSD_CONFIG_OPTION_SET), normalized_name, final_value);
result = CR_SUCCESS; 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: cleanup:

View file

@ -281,10 +281,6 @@ static size_t M_LoadSequenceEvent(
const char *const type_str = JSON_ObjectGetString(event_obj, "type", ""); const char *const type_str = JSON_ObjectGetString(event_obj, "type", "");
const GF_SEQUENCE_EVENT_TYPE type = const GF_SEQUENCE_EVENT_TYPE type =
ENUM_MAP_GET(GF_SEQUENCE_EVENT_TYPE, type_str, -1); 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(); const M_SEQUENCE_EVENT_HANDLER *handler = M_GetSequenceEventHandlers();
while (handler->event_type != (GF_SEQUENCE_EVENT_TYPE)-1 while (handler->event_type != (GF_SEQUENCE_EVENT_TYPE)-1
@ -292,6 +288,11 @@ static size_t M_LoadSequenceEvent(
handler++; handler++;
} }
if (handler->event_type != type) {
Shell_ExitSystemFmt(
"Unknown game flow sequence event type: '%s'", type);
}
int32_t extra_data_size = 0; int32_t extra_data_size = 0;
if (handler->handler_func != nullptr) { if (handler->handler_func != nullptr) {
extra_data_size = handler->handler_func( extra_data_size = handler->handler_func(

View file

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

View file

@ -2,16 +2,6 @@
static uint16_t m_MusicTrackFlags[MAX_MUSIC_TRACKS] = {}; 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) void Music_ResetTrackFlags(void)
{ {
for (int32_t i = 0; i < MAX_MUSIC_TRACKS; i++) { for (int32_t i = 0; i < MAX_MUSIC_TRACKS; i++) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
#include "vector.h" #include <stdint.h>
#define ENUM_MAP_DEFINE(enum_name, enum_value, str_value) \ #define ENUM_MAP_DEFINE(enum_name, enum_value, str_value) \
EnumMap_Define(ENUM_MAP_NAME(enum_name), enum_value, str_value); EnumMap_Define(ENUM_MAP_NAME(enum_name), enum_value, str_value);
@ -18,13 +18,6 @@ extern void EnumMap_Init(void);
void EnumMap_Shutdown(void); 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( void EnumMap_Define(
const char *enum_name, int32_t enum_value, const char *str_value); const char *enum_name, int32_t enum_value, const char *str_value);
int32_t EnumMap_Get( int32_t EnumMap_Get(

View file

@ -34,10 +34,6 @@ GS_DEFINE(OSD_PLAY_MUSIC_TRACK, "Playing music track %d")
GS_DEFINE(OSD_INVALID_MUSIC_TRACK, "Invalid music track") GS_DEFINE(OSD_INVALID_MUSIC_TRACK, "Invalid music track")
GS_DEFINE(OSD_UNKNOWN_COMMAND, "Unknown command: %s") GS_DEFINE(OSD_UNKNOWN_COMMAND, "Unknown command: %s")
GS_DEFINE(OSD_COMMAND_BAD_INVOCATION, "Invalid invocation: %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_COMMAND_UNAVAILABLE, "This command is not currently available")
GS_DEFINE(OSD_INVALID_ROOM, "Invalid room: %d. Valid rooms are 0-%d") 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") GS_DEFINE(OSD_POS_SET_POS, "Teleported to position: %.3f %.3f %.3f")
@ -136,9 +132,7 @@ GS_DEFINE(PASSPORT_MODE_NEW_GAME_JP_PLUS, "Japanese NG+")
GS_DEFINE(PASSPORT_STORY_SO_FAR, "Story so far...") GS_DEFINE(PASSPORT_STORY_SO_FAR, "Story so far...")
GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_1, "Legacy saves do not") GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_1, "Legacy saves do not")
GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_2, "support this feature.") GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_2, "support this feature.")
GS_DEFINE(SOUND_DIALOG_TITLE, "Set Volumes") GS_DEFINE(SOUND_SET_VOLUMES, "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_ON, "Trapezoid filter enabled")
GS_DEFINE(OSD_TRAPEZOID_FILTER_OFF, "Trapezoid filter disabled") GS_DEFINE(OSD_TRAPEZOID_FILTER_OFF, "Trapezoid filter disabled")
GS_DEFINE(DETAIL_INTEGER_FMT, "%d") GS_DEFINE(DETAIL_INTEGER_FMT, "%d")

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -14,11 +14,6 @@ int16_t *Sound_GetSampleLUT(void);
SAMPLE_INFO *Sound_GetSampleInfo(SOUND_EFFECT_ID sfx_num); SAMPLE_INFO *Sound_GetSampleInfo(SOUND_EFFECT_ID sfx_num);
SAMPLE_INFO *Sound_GetSampleInfoByIdx(int32_t info_idx); 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_ResetSources(void);
void Sound_PauseAll(void); void Sound_PauseAll(void);
void Sound_UnpauseAll(void); void Sound_UnpauseAll(void);

View file

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

View file

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

View file

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

View file

@ -217,7 +217,6 @@ sources = [
'game/ui/dialogs/play_any_level.c', 'game/ui/dialogs/play_any_level.c',
'game/ui/dialogs/save_slot.c', 'game/ui/dialogs/save_slot.c',
'game/ui/dialogs/select_level.c', 'game/ui/dialogs/select_level.c',
'game/ui/dialogs/sound_settings.c',
'game/ui/dialogs/stats.c', 'game/ui/dialogs/stats.c',
'game/ui/elements/anchor.c', 'game/ui/elements/anchor.c',
'game/ui/elements/fade.c', 'game/ui/elements/fade.c',

View file

@ -196,6 +196,70 @@ bool Item_Test3DRange(int32_t x, int32_t y, int32_t z, int32_t range)
&& (SQUARE(x) + SQUARE(y) + SQUARE(z) < SQUARE(range)); && (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( bool Item_MovePosition(
ITEM *item, const ITEM *ref_item, const XYZ_32 *vec, int32_t velocity) ITEM *item, const ITEM *ref_item, const XYZ_32 *vec, int32_t velocity)
{ {

View file

@ -11,6 +11,9 @@ int16_t Item_Spawn(const ITEM *item, GAME_OBJECT_ID obj_id);
bool Item_IsNearItem(const ITEM *item, const XYZ_32 *pos, int32_t distance); bool Item_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_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( bool Item_MovePosition(
ITEM *src_item, const ITEM *dst_item, const XYZ_32 *vec, int32_t velocity); ITEM *src_item, const ITEM *dst_item, const XYZ_32 *vec, int32_t velocity);
void Item_ShiftCol(ITEM *item, COLL_INFO *coll); void Item_ShiftCol(ITEM *item, COLL_INFO *coll);

View file

@ -701,6 +701,16 @@ bool Lara_IsNearItem(const XYZ_32 *pos, int32_t distance)
return Item_IsNearItem(g_LaraItem, pos, 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) bool Lara_MovePosition(ITEM *item, XYZ_32 *vec)
{ {
int32_t velocity = g_Config.gameplay.enable_walk_to_items int32_t velocity = g_Config.gameplay.enable_walk_to_items

View file

@ -26,6 +26,8 @@ void Lara_SwapMeshExtra(void);
bool Lara_IsNearItem(const XYZ_32 *pos, int32_t distance); bool Lara_IsNearItem(const XYZ_32 *pos, int32_t distance);
void Lara_UseItem(GAME_OBJECT_ID obj_id); 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); bool Lara_MovePosition(ITEM *item, XYZ_32 *vec);
void Lara_RevertToPistolsIfNeeded(void); void Lara_RevertToPistolsIfNeeded(void);

View file

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

View file

@ -19,6 +19,18 @@ void Music_Mute(void);
// Unmutes the game music. Doesn't change the music volume. // Unmutes the game music. Doesn't change the music volume.
void Music_Unmute(void); 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. // Returns the currently playing track. Includes looped music.
MUSIC_TRACK_ID Music_GetCurrentPlayingTrack(void); MUSIC_TRACK_ID Music_GetCurrentPlayingTrack(void);

View file

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

View file

@ -1,48 +1,170 @@
#include "game/option/option_sound.h" #include "game/option/option_sound.h"
#include "game/game_string.h"
#include "game/input.h"
#include "game/music.h"
#include "game/sound.h"
#include "game/text.h"
#include "global/vars.h"
#include <libtrx/config.h> #include <libtrx/config.h>
#include <libtrx/game/ui.h>
typedef struct { #include <stdio.h>
UI_SOUND_SETTINGS_STATE *ui;
} M_PRIV;
static M_PRIV m_Priv = {}; typedef enum {
TEXT_MUSIC_VOLUME = 0,
TEXT_SOUND_VOLUME = 1,
TEXT_TITLE = 2,
TEXT_TITLE_BORDER = 3,
TEXT_LEFT_ARROW = 4,
TEXT_RIGHT_ARROW = 5,
TEXT_NUMBER_OF = 6,
TEXT_OPTION_MIN = TEXT_MUSIC_VOLUME,
TEXT_OPTION_MAX = TEXT_SOUND_VOLUME,
} SOUND_TEXT;
static void M_Init(M_PRIV *const p) static TEXTSTRING *m_Text[TEXT_NUMBER_OF] = {};
static void M_InitText(void);
static void M_InitText(void)
{ {
p->ui = UI_SoundSettings_Init(); char buf[20];
}
static void M_Shutdown(M_PRIV *const p) m_Text[TEXT_LEFT_ARROW] = Text_Create(-45, 0, "\\{button left}");
{ m_Text[TEXT_RIGHT_ARROW] = Text_Create(40, 0, "\\{button right}");
if (p->ui != nullptr) {
UI_SoundSettings_Free(p->ui); m_Text[TEXT_TITLE_BORDER] = Text_Create(0, -32, " ");
p->ui = nullptr; m_Text[TEXT_TITLE] = Text_Create(0, -30, GS(SOUND_SET_VOLUMES));
if (g_Config.audio.music_volume > 10) {
g_Config.audio.music_volume = 10;
}
sprintf(buf, "\\{icon music} %2d", g_Config.audio.music_volume);
m_Text[TEXT_MUSIC_VOLUME] = Text_Create(0, 0, buf);
if (g_Config.audio.sound_volume > 10) {
g_Config.audio.sound_volume = 10;
}
sprintf(buf, "\\{icon sound} %2d", g_Config.audio.sound_volume);
m_Text[TEXT_SOUND_VOLUME] = Text_Create(0, 25, buf);
Text_AddBackground(m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED);
Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED);
Text_AddBackground(m_Text[TEXT_TITLE], 136, 0, 0, 0, TS_HEADING);
Text_AddOutline(m_Text[TEXT_TITLE], TS_HEADING);
Text_AddBackground(m_Text[TEXT_TITLE_BORDER], 140, 85, 0, 0, TS_BACKGROUND);
Text_AddOutline(m_Text[TEXT_TITLE_BORDER], TS_BACKGROUND);
for (int i = 0; i < TEXT_NUMBER_OF; i++) {
Text_CentreH(m_Text[i], 1);
Text_CentreV(m_Text[i], 1);
} }
} }
void Option_Sound_Control(INVENTORY_ITEM *const inv_item, const bool is_busy) void Option_Sound_Control(INVENTORY_ITEM *inv_item, const bool is_busy)
{ {
M_PRIV *const p = &m_Priv;
if (is_busy) { if (is_busy) {
return; return;
} }
if (p->ui == nullptr) {
M_Init(p);
}
UI_SoundSettings_Control(p->ui);
}
void Option_Sound_Draw(INVENTORY_ITEM *const inv_item) char buf[20];
{
M_PRIV *const p = &m_Priv; if (!m_Text[TEXT_MUSIC_VOLUME]) {
if (p->ui != nullptr) { M_InitText();
UI_SoundSettings(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_Shutdown(void) void Option_Sound_Shutdown(void)
{ {
M_Shutdown(&m_Priv); for (int i = 0; i < TEXT_NUMBER_OF; i++) {
Text_Remove(m_Text[i]);
m_Text[i] = nullptr;
}
} }

View file

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

View file

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

View file

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

View file

@ -528,6 +528,18 @@ void Sound_StopAll(void)
Audio_Sample_CloseAll(); 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) int32_t Sound_GetMinVolume(void)
{ {
return 0; return 0;
@ -538,18 +550,6 @@ int32_t Sound_GetMaxVolume(void)
return 10; 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) void Sound_ResetAmbient(void)
{ {
M_ResetAmbientLoudness(); M_ResetAmbientLoudness();

View file

@ -13,6 +13,10 @@ bool Sound_StopEffect(SOUND_EFFECT_ID sfx_num, const XYZ_32 *pos);
void Sound_UpdateEffects(void); void Sound_UpdateEffects(void);
void Sound_ResetEffects(void); void Sound_ResetEffects(void);
void Sound_StopAmbientSounds(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( void Sound_LoadSamples(
size_t num_samples, const char **sample_pointers, size_t *sizes); size_t num_samples, const char **sample_pointers, size_t *sizes);
int32_t Sound_GetMaxSamples(void); int32_t Sound_GetMaxSamples(void);

24
src/tr1/game/ui/common.c Normal file
View file

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

View file

@ -260,6 +260,7 @@ sources = [
'game/spawn.c', 'game/spawn.c',
'game/stats/common.c', 'game/stats/common.c',
'game/text.c', 'game/text.c',
'game/ui/common.c',
'game/ui/dialogs/stats.c', 'game/ui/dialogs/stats.c',
'game/viewport.c', 'game/viewport.c',
'global/enum_map.c', 'global/enum_map.c',

View file

@ -20,6 +20,40 @@
static BOUNDS_16 m_NullBounds = {}; static BOUNDS_16 m_NullBounds = {};
static BOUNDS_16 m_InterpolatedBounds = {}; 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) void Item_Control(void)
{ {
int16_t item_num = Item_GetNextActive(); int16_t item_num = Item_GetNextActive();
@ -154,6 +188,95 @@ int16_t Item_GetHeight(const ITEM *const item)
return height; 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) int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frames[], int32_t *rate)
{ {
const ANIM *const anim = Item_GetAnim(item); const ANIM *const anim = Item_GetAnim(item);

View file

@ -8,6 +8,10 @@ void Item_Control(void);
void Item_ClearKilled(void); void Item_ClearKilled(void);
void Item_ShiftCol(ITEM *item, COLL_INFO *coll); void Item_ShiftCol(ITEM *item, COLL_INFO *coll);
void Item_UpdateRoom(ITEM *item, int32_t height); 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); 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); bool Item_IsNearItem(const ITEM *item, const XYZ_32 *pos, int32_t distance);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -99,7 +99,7 @@ static void M_HandleQuit(void);
static void M_ConfigureOpenGL(void); static void M_ConfigureOpenGL(void);
static bool M_CreateGameWindow(void); static bool M_CreateGameWindow(void);
static void M_ShowHelp(void); static void M_ParseArgs(SHELL_ARGS *out_args);
static void M_LoadConfig(void); static void M_LoadConfig(void);
static void M_HandleConfigChange(const EVENT *event, void *data); static void M_HandleConfigChange(const EVENT *event, void *data);
@ -342,13 +342,30 @@ static bool M_CreateGameWindow(void)
return true; return true;
} }
static void M_ShowHelp(void) static void M_ParseArgs(SHELL_ARGS *const out_args)
{ {
puts("Currently available options:"); const char **args = nullptr;
puts(""); int32_t arg_count = 0;
puts("-g/--gold: launch The Golden Mask expansion pack."); Shell_GetCommandLine(&arg_count, &args);
puts("-l/--level <PATH>: launch a specific level file.");
puts("-s/--save <NUM>: launch from a specific save slot (starts at 1)."); out_args->mod = M_MOD_OG;
for (int32_t i = 0; i < arg_count; i++) {
if (!strcmp(args[i], "-gold")) {
out_args->mod = M_MOD_GM;
}
if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level"))
&& i + 1 < arg_count) {
out_args->level_to_play = args[i + 1];
out_args->mod = M_MOD_CUSTOM_LEVEL;
}
if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save"))
&& i + 1 < arg_count) {
if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) {
out_args->save_to_load--;
}
}
}
} }
static void M_LoadConfig(void) static void M_LoadConfig(void)
@ -416,39 +433,10 @@ static void M_HandleConfigChange(const EVENT *const event, void *const data)
} }
} }
bool Shell_ParseArgs(const int32_t arg_count, const char **args)
{
SHELL_ARGS *const out_args = &m_Args;
out_args->mod = M_MOD_OG;
for (int32_t i = 0; i < arg_count; i++) {
if (!strcmp(args[i], "-h") || !strcmp(args[i], "--help")) {
M_ShowHelp();
return false;
}
if (!strcmp(args[i], "-g") || !strcmp(args[i], "--gold")
|| !strcmp(args[i], "-gold")) {
out_args->mod = M_MOD_GM;
}
if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level"))
&& i + 1 < arg_count) {
out_args->level_to_play = args[i + 1];
out_args->mod = M_MOD_CUSTOM_LEVEL;
}
if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save"))
&& i + 1 < arg_count) {
if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) {
out_args->save_to_load--;
}
}
}
return true;
}
// TODO: refactor the hell out of me // TODO: refactor the hell out of me
int32_t Shell_Main(void) void Shell_Main(void)
{ {
LOG_INFO("Game directory: %s", File_GetGameDirectory()); M_ParseArgs(&m_Args);
if (m_Args.mod == M_MOD_GM) { if (m_Args.mod == M_MOD_GM) {
Object_Get(O_MONK_3)->setup_func = Monk3_Setup; Object_Get(O_MONK_3)->setup_func = Monk3_Setup;
@ -477,7 +465,7 @@ int32_t Shell_Main(void)
if (!M_CreateGameWindow()) { if (!M_CreateGameWindow()) {
Shell_ExitSystem("Failed to create game window"); Shell_ExitSystem("Failed to create game window");
return 1; return;
} }
Random_Seed(); Random_Seed();
@ -573,7 +561,7 @@ int32_t Shell_Main(void)
if (gf_cmd.action == GF_NOOP if (gf_cmd.action == GF_NOOP
|| gf_cmd.action == GF_EXIT_TO_TITLE) { || gf_cmd.action == GF_EXIT_TO_TITLE) {
Shell_ExitSystem("Title disabled & no replacement"); Shell_ExitSystem("Title disabled & no replacement");
return 1; return;
} }
} else { } else {
gf_cmd = GF_RunTitle(); gf_cmd = GF_RunTitle();
@ -595,7 +583,6 @@ int32_t Shell_Main(void)
if (m_Args.level_to_play != nullptr) { if (m_Args.level_to_play != nullptr) {
Memory_FreePointer(&g_GameFlow.level_tables[GFLT_MAIN].levels[0].path); Memory_FreePointer(&g_GameFlow.level_tables[GFLT_MAIN].levels[0].path);
} }
return 0;
} }
void Shell_Shutdown(void) void Shell_Shutdown(void)

View file

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

25
src/tr2/game/ui/common.c Normal file
View file

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

View file

@ -151,6 +151,13 @@ typedef struct {
int32_t pitch; int32_t pitch;
} SKIDOO_INFO; } SKIDOO_INFO;
typedef struct {
struct {
XYZ_16 min;
XYZ_16 max;
} shift, rot;
} OBJECT_BOUNDS;
typedef struct { typedef struct {
int32_t xv; int32_t xv;
int32_t yv; int32_t yv;

View file

@ -266,6 +266,7 @@ sources = [
'game/spawn.c', 'game/spawn.c',
'game/stats.c', 'game/stats.c',
'game/text.c', 'game/text.c',
'game/ui/common.c',
'game/ui/dialogs/graphic_settings.c', 'game/ui/dialogs/graphic_settings.c',
'game/ui/dialogs/stats.c', 'game/ui/dialogs/stats.c',
'game/viewport.c', 'game/viewport.c',

View file

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

View file

@ -15,10 +15,15 @@ public abstract class GenericInstallSource : BaseInstallSource
}; };
public override bool IsDownloadingMusicNeeded(string sourceDirectory) public override bool IsDownloadingMusicNeeded(string sourceDirectory)
=> true; {
return !Directory.Exists(Path.Combine(sourceDirectory, "audio"))
&& !Directory.Exists(Path.Combine(sourceDirectory, "music"));
}
public override bool IsDownloadingExpansionNeeded(string sourceDirectory) public override bool IsDownloadingExpansionNeeded(string sourceDirectory)
=> true; {
return true;
}
public override async Task CopyOriginalGameFiles( public override async Task CopyOriginalGameFiles(
string sourceDirectory, string sourceDirectory,