Compare commits

...
Author SHA1 Message Date
Mike Klaas 46376960d9 WIP Et tu working-ish 2026-05-04 21:43:45 -07:00
Mike Klaas 8b2ead8a2b Sfall-like sound system (#429)
* Sfall-like sound system

(I would like a better name for this system, but the opcodes themselves are called `play|stop_sfall_sound` and I can't think of a better name)

This is a sound management system very similar to Sfall's.  With a few differences:
* No support for new sound formats yet.  Support for at least ogg+wav will come next
* Does support reading from .dat files, hence playing any sound effect in the base game
* Support mode=3 for speech volume.  This exists in Sfall but is ignored when called from the opcode.

Also, change GaplessMusic to use this system to play the "wind2" loading sound.  This restores the "vanilla" feel map transitions, so I think it's reasonable to make GaplessMusic=1 the default.  I can't imagine someone wanting to change it to 0 unless they are ultra purists
2026-05-04 07:21:37 -07:00
Mike Klaas 4f89bb7d77 Companions can be the "looter" in loot screen (#435) 2026-05-03 09:57:25 -07:00
Vlad K 0d8741e082 Debug logging improvements (#434)
* debug log buffering to catch early logs, always duplicate log to stdout, const char* for debug fns

* AI nitpicking fixes
2026-05-03 09:15:13 +00:00
Vlad Kandgithub-actions[bot] 6ce6bea309 Sfall Saved Arrays (#431)
* Sfall saved arrays support

* Add "...all_arrays..." special case for a list of saved arrays

* Adjust error message on load fail

* Fix crash in GetArrayKey when index == size

* chore: auto-format with clang-format

* Update sfall compatibility md

* Add keyIndex for Assoc Arrays for O(1) key lookups

* Review feedback, more error logging

* Fix build, increase ARRAY_MAX_STRING to 1024 to match sfall, fix gl_test_arrays triggering dynamic string block merge failure, prevent potential string buffer corruption on very long strings

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-03 01:24:30 +02:00
Mike Klaas 6027c7cf2e Vertically center inventory above interface, isntead of related to screen (#428) 2026-05-01 12:53:02 -07:00
51 changed files with 7486 additions and 424 deletions
+34
View File
@@ -25,6 +25,9 @@ if(MSVC)
endif()
option(FALLOUT_VENDORED "Use vendored third-party libraries" ON)
option(FALLOUT_AUDIO_OGG "Enable OGG decoding with stb_vorbis when available" ON)
set(FALLOUT_STB_VORBIS_DIR "" CACHE PATH "Directory containing stb_vorbis.c")
if(ANDROID)
add_library(${EXECUTABLE_NAME} SHARED)
@@ -180,6 +183,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/obj_types.h"
"src/object.cc"
"src/object.h"
"src/ogg_decoder.cc"
"src/ogg_decoder.h"
"src/opcode_context.cc"
"src/opcode_context.h"
"src/options.cc"
@@ -297,6 +302,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/sfall_opcodes.cc"
"src/sfall_opcodes.h"
"src/sfall_script_hooks.cc"
"src/script_sound.cc"
"src/script_sound.h"
"src/sfall_arrays.cc"
"src/sfall_arrays.h"
"src/sfall_animation.cc"
@@ -311,6 +318,33 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
target_include_directories(${EXECUTABLE_NAME} PUBLIC "third_party/lodepng")
set(FALLOUT_HAVE_STB_VORBIS OFF)
if(FALLOUT_AUDIO_OGG)
if(EXISTS "${CMAKE_SOURCE_DIR}/third_party/stb_vorbis/stb_vorbis.c")
set(_fallout_stb_vorbis_dir "${CMAKE_SOURCE_DIR}/third_party/stb_vorbis")
else()
set(_fallout_stb_vorbis_dir "${FALLOUT_STB_VORBIS_DIR}")
if(NOT _fallout_stb_vorbis_dir)
find_path(_fallout_stb_vorbis_dir
NAMES "stb_vorbis.c"
DOC "Directory containing stb_vorbis.c")
endif()
endif()
if(_fallout_stb_vorbis_dir AND EXISTS "${_fallout_stb_vorbis_dir}/stb_vorbis.c")
set(FALLOUT_HAVE_STB_VORBIS ON)
target_include_directories(${EXECUTABLE_NAME} PRIVATE "${_fallout_stb_vorbis_dir}")
else()
message(STATUS "OGG support disabled: stb_vorbis.c not found")
endif()
endif()
if(FALLOUT_HAVE_STB_VORBIS)
target_compile_definitions(${EXECUTABLE_NAME} PRIVATE HAVE_STB_VORBIS=1)
else()
target_compile_definitions(${EXECUTABLE_NAME} PRIVATE HAVE_STB_VORBIS=0)
endif()
if(IOS)
target_sources(${EXECUTABLE_NAME} PUBLIC
"src/platform/ios/paths.h"
+2 -2
View File
@@ -43,7 +43,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Combat / Knockback | set_weapon_knockback<br>set_target_knockback<br>set_attacker_knockback<br>remove_weapon_knockback<br>remove_target_knockback<br>remove_attacker_knockback | not implemented | - |
| Maps and encounters | in_world_map<br>force_encounter<br>force_encounter_with_flags<br>set_map_time_multi<br>get/set_map_enter_position<br>exec_map_update_scripts<br>get/set_terrain_name<br>set_town_title<br>get/set_can_rest_on_map<br>set_rest_heal_time<br>set_rest_mode<br>set_worldmap_heal_time | implemented: in_world_map, force_encounter, force_encounter_with_flags, set_map_time_multi | - |
| Maps and encounters / Worldmap | get_world_map_x/y_pos<br>set_world_map_pos | âś… | - |
| Audio | eax_available<br>set_eax_environment<br>play_sfall_sound<br>stop_sfall_sound | not implemented | *eax* opcodes will not be implemented |
| Audio | play_sfall_sound<br>stop_sfall_sound | âś… | `play_sfall_sound` currently supports `.acm`, `.wav`, `.ogg` formats, and can load from `.dat` archives. `.mp3` is not yet supported. |
| Combat / Weapons and ammo | get/set_weapon_ammo_pid<br>get/set_weapon_ammo_count | âś… | - |
| Sfall / Version | sfall_ver_major<br>sfall_ver_minor<br>sfall_ver_build | âś… | CE currently reports `4.3.4` |
| Utility / Math | log, exponent, round, sqrt, abs, sin, cos, tan, arctan, ceil, ^, floor2, div | âś… | - |
@@ -58,7 +58,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Interface / Tags | show_iface_tag<br>hide_iface_tag<br>is_iface_tag_active<br>set_iface_tag_text<br>add_iface_tag | âś… except set_iface_tag_text, add_iface_tag | CE only handles built-in interface tags here; custom tag creation/text is not supported yet. |
| Global variables | set_sfall_global<br>get_sfall_global_int<br>get_sfall_global_float | âś… except get_sfall_global_float | Current CE storage is int-backed; `set_sfall_global` stores integer values |
| Hooks / Hook functions | init_hook<br>get_sfall_arg<br>get_sfall_args<br>get_sfall_arg_at<br>set_sfall_return<br>set_sfall_arg<br>register_hook<br>register_hook_proc<br>register_hook_proc_spec | âś… | See below for implemented hooks. `init_hook` is deprecated and will not be implemented. register_hook_proc and register_hook_proc_spec both add hooks to the *end* of the hook list, instead of beginning and end, respectively. |
| Arrays / Array functions | create_array<br>temp_array<br>fix_array<br>get/set_array<br>resize_array<br>free_array<br>scan_array<br>len_array<br>save/load_array<br>array_key<br>arrayexpr | âś… except save_array, load_array | - |
| Arrays / Array functions | create_array<br>temp_array<br>fix_array<br>get/set_array<br>resize_array<br>free_array<br>scan_array<br>len_array<br>save/load_array<br>array_key<br>arrayexpr | âś… | - |
| Perks and traits / NPC perks | set_fake_perk_npc<br>set_fake_trait_npc<br>set_selectable_perk_npc<br>has_fake_perk_npc<br>has_fake_trait_npc | not implemented | - |
| Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | âś… except available_global_script_types | - |
| Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | implemented: get_attack_type, get/set_bodypart_hit_modifier, combat_data, get/set_critter_current_ap, get/set_combat_free_move | - |
+2
View File
@@ -120,3 +120,5 @@ inventory_columns=2
auto_open_doors=0
; Overrides distance at which walk animation is used when using an object.
use_walk_distance=5
; Allow switching to companions' inventories in loot screens (and barter, in the future)
party_loot_and_barter=1
+3 -6
View File
@@ -4,7 +4,7 @@
#define ARRAY_MAX_STRING (255)
#define ARRAY_MAX_STRING (1024)
#define ARRAY_MAX_SIZE (100000)
procedure array_test_suite begin
@@ -82,8 +82,8 @@ procedure array_test_suite begin
call assertEquals("string_split", get_array(string_split("this+is+good", "+"), 2), "good");
call assertEquals("string_split 2", len_array(string_split("advice", "")), 6);
s := "";
for (i := 0; i < ARRAY_MAX_STRING+30; i+=10) begin
s += "Verbosity.";
for (i := 0; i < ARRAY_MAX_STRING+1; i+=100) begin
s += "Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.";
end
arr[0] := s;
call assertEquals("array max string", strlen(arr[0]), ARRAY_MAX_STRING-1);
@@ -141,8 +141,6 @@ procedure array_test_suite begin
end
call assertEquals("foreach 2", s, "ar2=name:John;hp:25;0:5.50000;");
/*
display_msg("Testing save/load...");
arr := [2,1];
arr2 := {1:2};
@@ -163,7 +161,6 @@ procedure array_test_suite begin
call assertEquals("unsave array 1", load_array(0.1), 0);
call assertEquals("unsave array 2", len_array(arr), 2);
call assertEquals("saved arrays 3", len_array(list_saved_arrays), len_array(arr2) - 1);
*/
display_msg("Testing nested expressions...");
arr := [["one", "two"]];
+78
View File
@@ -0,0 +1,78 @@
#include "sfall.h"
#include "dik.h"
variable loop_sound_id := 0;
variable quiet_loop_sound_id := 0;
variable music_sound_id := 0;
procedure stop_if_active(variable sound_id, variable label) begin
if (sound_id != 0) then begin
stop_sfall_sound(sound_id);
display_msg(label + " stopped");
end else begin
display_msg(label + " is not active");
end
end
procedure keypress_handler begin
variable pressed := get_sfall_arg_at(0);
variable key := get_sfall_arg_at(1);
variable ignored;
if (not pressed) then return;
if (key == DIK_K) then begin
display_msg("play_sfall_sound mode 0 one-shot: sound\\sfx\\pistol.ACM");
ignored := play_sfall_sound("sound\\sfx\\pistol.ACM", 0);
end else if (key == DIK_N) then begin
if (loop_sound_id != 0) then begin
display_msg(string_format1("loop already active id=%d", loop_sound_id));
return;
end
loop_sound_id := play_sfall_sound("sound\\music\\20CAR.ACM", 1);
display_msg(string_format1("mode 1 loop id=%d", loop_sound_id));
end else if (key == DIK_M) then begin
call stop_if_active(loop_sound_id, "mode 1 loop");
loop_sound_id := 0;
end else if (key == DIK_C) then begin
if (quiet_loop_sound_id != 0) then begin
display_msg(string_format1("quiet loop already active id=%d", quiet_loop_sound_id));
return;
end
quiet_loop_sound_id := play_sfall_sound("sound\\music\\20CAR.ACM", 0x30000001);
display_msg(string_format1("quiet mode 1 loop id=%d", quiet_loop_sound_id));
end else if (key == DIK_X) then begin
call stop_if_active(quiet_loop_sound_id, "quiet mode 1 loop");
quiet_loop_sound_id := 0;
end else if (key == DIK_G) then begin
if (music_sound_id != 0) then begin
display_msg(string_format1("mode 2 music replacement already active id=%d", music_sound_id));
return;
end
music_sound_id := play_sfall_sound("sound\\music\\20CAR.ACM", 2);
display_msg(string_format1("mode 2 music replacement id=%d", music_sound_id));
end else if (key == DIK_H) then begin
call stop_if_active(music_sound_id, "mode 2 music replacement");
music_sound_id := 0;
end else if (key == DIK_V) then begin
display_msg("play_sfall_sound mode 3 speech-volume one-shot: sound\\sfx\\pistol.ACM");
ignored := play_sfall_sound("sound\\sfx\\pistol.ACM", 3);
end else if (key == DIK_B) then begin
display_msg("play_sfall_sound mode 0 one-shot wav: smw_1-up.wav"); // needs a wav file in the game dir
ignored := play_sfall_sound("smw_1-up.wav", 0);
end
end
procedure start begin
if (not game_loaded) then return;
display_msg("sfall_sound manual test ready");
display_msg("One-shots use master.dat SFX and loose-file wav; loops/replacement use music");
display_msg("K one-shot ACM mode0, B one-shot WAV mode0, N start loop mode1, M stop loop");
display_msg("C start quiet loop mode1, X stop quiet loop");
display_msg("G start mode2 music replacement, H stop mode2 replacement, V mode3 one-shot");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
end
+21 -4
View File
@@ -13,6 +13,7 @@
#include "memory.h"
#include "proto.h"
#include "settings.h"
#include "sfall_config.h"
#include <algorithm>
#include <memory>
@@ -199,16 +200,32 @@ int artInit()
// SFALL: Modify player model settings.
char* jumpsuitMaleFileName = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_male_default", &jumpsuitMaleFileName, gDefaultJumpsuitMaleFileName);
if (!configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, "MaleDefaultModel", &jumpsuitMaleFileName, nullptr)
|| jumpsuitMaleFileName == nullptr
|| jumpsuitMaleFileName[0] == '\0') {
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_male_default", &jumpsuitMaleFileName, gDefaultJumpsuitMaleFileName);
}
char* jumpsuitFemaleFileName = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_female_default", &jumpsuitFemaleFileName, gDefaultJumpsuitFemaleFileName);
if (!configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, "FemaleDefaultModel", &jumpsuitFemaleFileName, nullptr)
|| jumpsuitFemaleFileName == nullptr
|| jumpsuitFemaleFileName[0] == '\0') {
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_female_default", &jumpsuitFemaleFileName, gDefaultJumpsuitFemaleFileName);
}
char* tribalMaleFileName = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_male", &tribalMaleFileName, gDefaultTribalMaleFileName);
if (!configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, "MaleStartModel", &tribalMaleFileName, nullptr)
|| tribalMaleFileName == nullptr
|| tribalMaleFileName[0] == '\0') {
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_male", &tribalMaleFileName, gDefaultTribalMaleFileName);
}
char* tribalFemaleFileName = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_female", &tribalFemaleFileName, gDefaultTribalFemaleFileName);
if (!configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, "FemaleStartModel", &tribalFemaleFileName, nullptr)
|| tribalFemaleFileName == nullptr
|| tribalFemaleFileName[0] == '\0') {
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_female", &tribalFemaleFileName, gDefaultTribalFemaleFileName);
}
char* critterFileNames = gArtListDescriptions[OBJ_TYPE_CRITTER].fileNames;
for (int critterIndex = 0; critterIndex < gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {
+212 -11
View File
@@ -4,9 +4,13 @@
#include <stdio.h>
#include <string.h>
#include <SDL.h>
#include "db.h"
#include "debug.h"
#include "memory_manager.h"
#include "ogg_decoder.h"
#include "platform_compat.h"
#include "sound.h"
#include "sound_decoder.h"
@@ -15,20 +19,33 @@ namespace fallout {
typedef enum AudioFlags {
AUDIO_IN_USE = 0x01,
AUDIO_COMPRESSED = 0x02,
// CE: Decoded formats like WAV/OGG are fully loaded into memory up front.
AUDIO_MEMORY = 0x04,
} AudioFileFlags;
typedef struct Audio {
int flags;
File* stream;
SoundDecoder* soundDecoder;
unsigned char* data;
int fileSize;
int sampleRate;
int channels;
int bitsPerSample;
int position;
} Audio;
typedef enum AudioOpenMode {
AUDIO_OPEN_MODE_RAW = 0,
AUDIO_OPEN_MODE_COMPRESSED = 1, // ACM
AUDIO_OPEN_MODE_WAV = 2,
AUDIO_OPEN_MODE_OGG = 3,
} AudioOpenMode;
static bool defaultCompressionFunc(char* filePath);
static int audioSoundDecoderReadHandler(void* data, void* buf, unsigned int size);
static bool audioIsWavePath(const char* filePath);
static bool audioDecodeWave(File* stream, AudioFileInfo* info, unsigned char** dataPtr, int* sizePtr);
// 0x5108BC
static AudioQueryCompressedFunc* queryCompressedFunc = defaultCompressionFunc;
@@ -56,18 +73,128 @@ static int audioSoundDecoderReadHandler(void* data, void* buffer, unsigned int s
return fileRead(buffer, 1, size, reinterpret_cast<File*>(data));
}
static bool audioIsWavePath(const char* filePath)
{
const char* dot = strrchr(filePath, '.');
return dot != nullptr && compat_stricmp(dot + 1, "wav") == 0;
}
static bool audioDecodeWave(File* stream, AudioFileInfo* info, unsigned char** dataPtr, int* sizePtr)
{
bool success = false;
unsigned char* fileData = nullptr;
Uint8* loadedData = nullptr;
Uint8* convertedData = nullptr;
SDL_RWops* rw = nullptr;
SDL_AudioSpec spec = {};
Uint32 loadedLength = 0;
int channels = 0;
SDL_AudioCVT cvt;
memset(&cvt, 0, sizeof(cvt));
int convertedLength = 0;
bool convertedDataNeedsFree = false;
bool loadedDataNeedsFree = false;
int fileSize = fileGetSize(stream);
if (fileSize <= 0) {
return false;
}
fileData = reinterpret_cast<unsigned char*>(internal_malloc_safe(fileSize, __FILE__, __LINE__));
if (fileRead(fileData, 1, fileSize, stream) != fileSize) {
goto done;
}
rw = SDL_RWFromConstMem(fileData, fileSize);
if (rw == nullptr) {
goto done;
}
if (SDL_LoadWAV_RW(rw, 1, &spec, &loadedData, &loadedLength) == nullptr) {
rw = nullptr;
goto done;
}
rw = nullptr;
loadedDataNeedsFree = true;
channels = spec.channels == 1 ? 1 : 2;
if (SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq, AUDIO_S16SYS, channels, spec.freq) < 0) {
goto done;
}
convertedLength = static_cast<int>(loadedLength);
if (cvt.needed != 0) {
cvt.len = static_cast<int>(loadedLength);
cvt.buf = reinterpret_cast<Uint8*>(SDL_malloc(cvt.len * cvt.len_mult));
if (cvt.buf == nullptr) {
goto done;
}
memcpy(cvt.buf, loadedData, loadedLength);
if (SDL_ConvertAudio(&cvt) != 0) {
SDL_free(cvt.buf);
goto done;
}
convertedData = cvt.buf;
convertedLength = cvt.len_cvt;
convertedDataNeedsFree = true;
} else {
convertedData = loadedData;
}
if (info != nullptr) {
info->channels = channels;
info->sampleRate = spec.freq;
info->bitsPerSample = 16;
}
if (dataPtr != nullptr && sizePtr != nullptr) {
*dataPtr = reinterpret_cast<unsigned char*>(internal_malloc_safe(convertedLength, __FILE__, __LINE__));
memcpy(*dataPtr, convertedData, convertedLength);
*sizePtr = convertedLength;
}
success = true;
done:
if (rw != nullptr) {
SDL_RWclose(rw);
}
if (convertedData != nullptr) {
if (convertedDataNeedsFree) {
SDL_free(convertedData);
}
}
if (loadedDataNeedsFree && loadedData != nullptr) {
SDL_FreeWAV(loadedData);
}
if (fileData != nullptr) {
internal_free_safe(fileData, __FILE__, __LINE__);
}
return success;
}
// AudioOpen
// 0x41A2EC
int audioOpen(const char* fname, int* sampleRate)
int audioOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr)
{
char path[80];
char path[COMPAT_MAX_PATH];
snprintf(path, sizeof(path), "%s", fname);
int compression;
if (queryCompressedFunc(path)) {
compression = 2;
AudioOpenMode openMode;
if (audioIsWavePath(path)) {
openMode = AUDIO_OPEN_MODE_WAV;
} else if (oggDecoderIsFilePath(path)) {
openMode = AUDIO_OPEN_MODE_OGG;
} else if (queryCompressedFunc(path)) {
openMode = AUDIO_OPEN_MODE_COMPRESSED;
} else {
compression = 0;
openMode = AUDIO_OPEN_MODE_RAW;
}
File* stream = fileOpen(path, "rb");
@@ -93,17 +220,65 @@ int audioOpen(const char* fname, int* sampleRate)
}
Audio* audioFile = &(gAudioList[index]);
memset(audioFile, 0, sizeof(*audioFile));
audioFile->flags = AUDIO_IN_USE;
audioFile->stream = stream;
if (compression == 2) {
if (openMode == AUDIO_OPEN_MODE_WAV || openMode == AUDIO_OPEN_MODE_OGG) {
AudioFileInfo decodedInfo = {};
audioFile->flags |= AUDIO_MEMORY;
bool decoded = false;
if (openMode == AUDIO_OPEN_MODE_WAV) {
decoded = audioDecodeWave(stream, &decodedInfo, &(audioFile->data), &(audioFile->fileSize));
} else {
AudioFileInfo audioFileInfo = {};
decoded = oggDecoderDecode(stream, &audioFileInfo, &(audioFile->data), &(audioFile->fileSize));
decodedInfo.sampleRate = audioFileInfo.sampleRate;
decodedInfo.channels = audioFileInfo.channels;
decodedInfo.bitsPerSample = audioFileInfo.bitsPerSample;
}
if (!decoded) {
fileClose(stream);
memset(audioFile, 0, sizeof(*audioFile));
debugPrint("AudioOpen: Couldn't decode %s\n", path);
return -1;
}
fileClose(stream);
audioFile->stream = nullptr;
audioFile->sampleRate = decodedInfo.sampleRate;
audioFile->channels = decodedInfo.channels;
audioFile->bitsPerSample = decodedInfo.bitsPerSample;
if (info != nullptr) {
*info = decodedInfo;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = true;
}
} else if (openMode == AUDIO_OPEN_MODE_COMPRESSED) {
audioFile->flags |= AUDIO_COMPRESSED;
audioFile->soundDecoder = soundDecoderInit(audioSoundDecoderReadHandler, audioFile->stream, &(audioFile->channels), &(audioFile->sampleRate), &(audioFile->fileSize));
audioFile->fileSize *= 2;
audioFile->bitsPerSample = 16;
*sampleRate = audioFile->sampleRate;
if (info != nullptr) {
info->sampleRate = audioFile->sampleRate;
info->channels = audioFile->channels;
info->bitsPerSample = audioFile->bitsPerSample;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = false;
}
} else {
audioFile->fileSize = fileGetSize(stream);
audioFile->bitsPerSample = 8;
if (info != nullptr) {
info->bitsPerSample = audioFile->bitsPerSample;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = false;
}
}
audioFile->position = 0;
@@ -115,7 +290,13 @@ int audioOpen(const char* fname, int* sampleRate)
int audioClose(int handle)
{
Audio* audioFile = &(gAudioList[handle - 1]);
fileClose(audioFile->stream);
if ((audioFile->flags & AUDIO_MEMORY) != 0) {
if (audioFile->data != nullptr) {
internal_free_safe(audioFile->data, __FILE__, __LINE__);
}
} else if (audioFile->stream != nullptr) {
fileClose(audioFile->stream);
}
if ((audioFile->flags & AUDIO_COMPRESSED) != 0) {
soundDecoderFree(audioFile->soundDecoder);
@@ -132,7 +313,16 @@ int audioRead(int handle, void* buffer, unsigned int size)
Audio* audioFile = &(gAudioList[handle - 1]);
int bytesRead;
if ((audioFile->flags & AUDIO_COMPRESSED) != 0) {
if ((audioFile->flags & AUDIO_MEMORY) != 0) {
bytesRead = audioFile->fileSize - audioFile->position;
if (bytesRead > static_cast<int>(size)) {
bytesRead = size;
}
if (bytesRead > 0) {
memcpy(buffer, audioFile->data + audioFile->position, bytesRead);
}
} else if ((audioFile->flags & AUDIO_COMPRESSED) != 0) {
bytesRead = soundDecoderDecode(audioFile->soundDecoder, buffer, size);
} else {
bytesRead = fileRead(buffer, 1, size, audioFile->stream);
@@ -166,7 +356,18 @@ long audioSeek(int handle, long offset, int origin)
assert(false && "Should be unreachable");
}
if ((audioFile->flags & AUDIO_COMPRESSED) != 0) {
if ((audioFile->flags & AUDIO_MEMORY) != 0) {
if (pos < 0) {
pos = 0;
}
if (pos > audioFile->fileSize) {
pos = audioFile->fileSize;
}
audioFile->position = pos;
return audioFile->position;
} else if ((audioFile->flags & AUDIO_COMPRESSED) != 0) {
if (pos < audioFile->position) {
soundDecoderFree(audioFile->soundDecoder);
fileSeek(audioFile->stream, 0, SEEK_SET);
+3 -1
View File
@@ -1,11 +1,13 @@
#ifndef AUDIO_H
#define AUDIO_H
#include "sound.h"
namespace fallout {
typedef bool(AudioQueryCompressedFunc)(char* filePath);
int audioOpen(const char* fname, int* sampleRate);
int audioOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr);
int audioClose(int handle);
int audioRead(int handle, void* buffer, unsigned int size);
long audioSeek(int handle, long offset, int origin);
+15 -3
View File
@@ -57,7 +57,7 @@ static int audioFileSoundDecoderReadHandler(void* data, void* buffer, unsigned i
}
// 0x41A88C
int audioFileOpen(const char* fname, int* sampleRate)
int audioFileOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr)
{
char path[COMPAT_MAX_PATH];
strcpy(path, fname);
@@ -98,10 +98,22 @@ int audioFileOpen(const char* fname, int* sampleRate)
audioFile->flags |= AUDIO_FILE_COMPRESSED;
audioFile->soundDecoder = soundDecoderInit(audioFileSoundDecoderReadHandler, audioFile->stream, &(audioFile->channels), &(audioFile->sampleRate), &(audioFile->fileSize));
audioFile->fileSize *= 2;
*sampleRate = audioFile->sampleRate;
if (info != nullptr) {
info->sampleRate = audioFile->sampleRate;
info->channels = audioFile->channels;
info->bitsPerSample = 16;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = false;
}
} else {
audioFile->fileSize = getFileSize(stream);
if (info != nullptr) {
info->bitsPerSample = 8;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = false;
}
}
audioFile->position = 0;
+3 -1
View File
@@ -1,11 +1,13 @@
#ifndef AUDIO_FILE_H
#define AUDIO_FILE_H
#include "sound.h"
namespace fallout {
typedef bool(AudioFileQueryCompressedFunc)(char* filePath);
int audioFileOpen(const char* fname, int* sampleRate);
int audioFileOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr);
int audioFileClose(int handle);
int audioFileRead(int handle, void* buf, unsigned int size);
long audioFileSeek(int handle, long offset, int origin);
+51 -16
View File
@@ -1,11 +1,11 @@
#include "debug.h"
#include <SDL.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include <string>
#include "memory.h"
#include "platform_compat.h"
@@ -15,11 +15,18 @@ namespace fallout {
static int _debug_puts(char* string);
static void _debug_clear();
static int _debug_mono(char* string);
static int _debug_log(char* string);
static int _debug_screen(char* string);
static int _debug_mono(const char* string);
static int _debug_log(const char* string);
static int _debug_screen(const char* string);
static void _debug_putc(int ch);
static void _debug_scroll();
static void debugFlushBuffer();
// Messages logged before any debug proc is registered are held here and
// flushed when the first proc is registered.
static std::string debugBuffer;
static constexpr size_t kDebugBufferMaxSize = 64 * 1024;
static bool debugBufferDisabled = false;
// 0x51DEF8
static FILE* _fd = nullptr;
@@ -35,6 +42,12 @@ static DebugPrintProc* gDebugPrintProc = nullptr;
void debugModeInit(const char* debugMode)
{
debugBufferDisabled = true;
if (debugMode == nullptr) {
return;
}
// CE: Handle debug mode (exactly as seen in `mapper2.exe`).
if (compat_stricmp(debugMode, "environment") == 0) {
_debug_register_env();
@@ -47,6 +60,10 @@ void debugModeInit(const char* debugMode)
} else if (compat_stricmp(debugMode, "gnw") == 0) {
_debug_register_func(_win_debug);
}
if (gDebugPrintProc == nullptr) {
debugBuffer.clear();
}
}
// 0x4C6CD0
@@ -66,6 +83,7 @@ void _debug_register_mono()
gDebugPrintProc = _debug_mono;
_debug_clear();
debugFlushBuffer();
}
}
@@ -79,6 +97,7 @@ void _debug_register_log(const char* fileName, const char* mode)
_fd = compat_fopen(fileName, mode);
gDebugPrintProc = _debug_log;
debugFlushBuffer();
}
}
@@ -92,6 +111,7 @@ void _debug_register_screen()
}
gDebugPrintProc = _debug_screen;
debugFlushBuffer();
}
}
@@ -137,6 +157,7 @@ void _debug_register_func(DebugPrintProc* proc)
}
gDebugPrintProc = proc;
debugFlushBuffer();
}
}
@@ -146,25 +167,39 @@ int debugPrint(const char* format, ...)
va_list args;
va_start(args, format);
char string[260];
int len = vsnprintf(string, sizeof(string), format, args);
if (len < 0) {
string[0] = '\0';
}
va_end(args);
int rc;
if (gDebugPrintProc != nullptr) {
char string[260];
vsnprintf(string, sizeof(string), format, args);
rc = gDebugPrintProc(string);
} else {
#ifndef NDEBUG
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, format, args);
#endif
if (!debugBufferDisabled && debugBuffer.size() + strlen(string) <= kDebugBufferMaxSize) {
debugBuffer += string;
}
rc = -1;
}
va_end(args);
#ifndef NDEBUG
SDL_Log("%s", string);
#endif
return rc;
}
static void debugFlushBuffer()
{
if (debugBuffer.empty() || gDebugPrintProc == nullptr) {
return;
}
gDebugPrintProc(debugBuffer.c_str());
debugBuffer.clear();
}
// 0x4C6F94
static int _debug_puts(char* string)
{
@@ -203,7 +238,7 @@ static void _debug_clear()
}
// 0x4C7004
static int _debug_mono(char* string)
static int _debug_mono(const char* string)
{
if (gDebugPrintProc == _debug_mono) {
while (*string != '\0') {
@@ -215,7 +250,7 @@ static int _debug_mono(char* string)
}
// 0x4C7028
static int _debug_log(char* string)
static int _debug_log(const char* string)
{
if (gDebugPrintProc == _debug_log) {
if (_fd == nullptr) {
@@ -235,7 +270,7 @@ static int _debug_log(char* string)
}
// 0x4C7068
static int _debug_screen(char* string)
static int _debug_screen(const char* string)
{
if (gDebugPrintProc == _debug_screen) {
printf("%s", string);
+1 -1
View File
@@ -3,7 +3,7 @@
namespace fallout {
typedef int(DebugPrintProc)(char* string);
typedef int(DebugPrintProc)(const char* string);
void debugModeInit(const char* debugMode);
void _GNW_debug_init();
+2
View File
@@ -152,6 +152,8 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int fl
settingsInit(isMapper, argc, argv);
debugModeInit(settings.debug.mode.c_str());
gIsMapper = isMapper;
if (gameDbInit() == -1) {
-5
View File
@@ -119,11 +119,6 @@ bool gameConfigInit(bool isMapper, int argc, char** argv)
configRead(&gGameConfig, gGameConfigFilePath, false);
// Init debug mode ASAP to catch early debug messages.
char* debugMode;
configGetString(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_MODE_KEY, &debugMode);
debugModeInit(debugMode);
debugPrint("Game config loaded from %s.\n", gGameConfigFilePath);
if (!isMapper && gameConfigMigrateFromF2Res(gGameConfigFilePath, &gGameConfig)) {
+17 -1
View File
@@ -16,6 +16,7 @@
#include "palette.h"
#include "platform_compat.h"
#include "settings.h"
#include "sfall_config.h"
#include "svga.h"
#include "text_font.h"
#include "touch.h"
@@ -32,7 +33,7 @@ static char* gameMovieBuildSubtitlesFilePath(char* movieFilePath);
static const float flt_50352A = 0.032258064f;
// 0x518DA0
static const char* gMovieFileNames[MOVIE_COUNT] = {
static const char* gMovieDefaultFileNames[MOVIE_COUNT] = {
"iplogo.mve",
"intro.mve",
"elder.mve",
@@ -52,6 +53,8 @@ static const char* gMovieFileNames[MOVIE_COUNT] = {
"credits.mve",
};
static char gMovieFileNames[MOVIE_COUNT][65];
// 0x518DE4
static const char* gMoviePaletteFilePaths[MOVIE_COUNT] = {
nullptr,
@@ -98,6 +101,19 @@ int gameMoviesInit()
movieSetBuildSubtitleFilePathProc(gameMovieBuildSubtitlesFilePath);
for (int movie = 0; movie < MOVIE_COUNT; movie++) {
strcpy(gMovieFileNames[movie], gMovieDefaultFileNames[movie]);
char key[16];
snprintf(key, sizeof(key), "Movie%d", movie + 1);
char* movieFileName = nullptr;
if (configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, key, &movieFileName) && movieFileName[0] != '\0') {
strncpy(gMovieFileNames[movie], movieFileName, sizeof(gMovieFileNames[movie]) - 1);
gMovieFileNames[movie][sizeof(gMovieFileNames[movie]) - 1] = '\0';
}
}
memset(gGameMoviesSeen, 0, sizeof(gGameMoviesSeen));
gGameMovieIsPlaying = false;
+127 -144
View File
@@ -20,6 +20,7 @@
#include "proto.h"
#include "queue.h"
#include "random.h"
#include "script_sound.h"
#include "settings.h"
#include "sound_effects_cache.h"
#include "stat.h"
@@ -148,7 +149,6 @@ static char gBackgroundSoundFileName[270];
static void soundEffectsEnable();
static void soundEffectsDisable();
static int soundEffectsIsEnabled();
static int soundEffectsGetVolume();
static void backgroundSoundDisable();
static void backgroundSoundEnable();
static int backgroundSoundGetDuration();
@@ -158,7 +158,7 @@ static int _gsound_speech_volume_get_set(int volume);
static void speechPause();
static void speechResume();
static void _gsound_bkg_proc();
static int gameSoundFileOpen(const char* fname, int* sampleRate);
static int gameSoundFileOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr);
static long _gsound_write_();
static long gameSoundFileTellNotImplemented(int handle);
static int gameSoundFileWrite(int handle, const void* buf, unsigned int size);
@@ -183,6 +183,30 @@ static Sound* _gsound_get_sound_ready_for_effect();
static bool _gsound_file_exists_f(const char* fname);
static int _gsound_setup_paths();
// Legacy music backend: streams through audio_file for copied/compressed ACM handling.
const SoundFileIO gGameSoundAudioFileIO = {
audioFileOpen,
audioFileClose,
audioFileRead,
nullptr,
audioFileSeek,
gameSoundFileTellNotImplemented,
audioFileGetSize,
-1,
};
// Generic decoded backend: supports arbitrary script/speech paths via audio decoders.
const SoundFileIO gGameSoundAudioIO = {
audioOpen,
audioClose,
audioRead,
audioWrite,
audioSeek,
audioTell,
audioGetSize,
-1,
};
// 0x44FC70
int gameSoundInit()
{
@@ -360,6 +384,8 @@ void gameSoundReset()
_gsound_background_fade = 0;
scriptSoundExit();
soundDeleteAll();
soundEffectsCacheFlush();
@@ -621,8 +647,6 @@ int backgroundSoundGetDuration()
*/
int backgroundSoundLoad(const char* fileName, GameSoundReadLimitMode readLimitMode, GameSoundStorageType storageType, GameSoundLoopingMode loopingMode)
{
int rc;
_background_storage_requested = storageType;
_background_loop_requested = loopingMode;
@@ -644,41 +668,8 @@ int backgroundSoundLoad(const char* fileName, GameSoundReadLimitMode readLimitMo
backgroundSoundDelete();
rc = _gsound_background_allocate(&gBackgroundSound, storageType, loopingMode);
if (rc != 0) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because sound could not be allocated.\n");
}
gBackgroundSound = nullptr;
return -1;
}
rc = soundSetFileIO(gBackgroundSound, audioFileOpen, audioFileClose, audioFileRead, nullptr, audioFileSeek, gameSoundFileTellNotImplemented, audioFileGetSize);
if (rc != 0) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because file IO could not be set for compression.\n");
}
soundDelete(gBackgroundSound);
gBackgroundSound = nullptr;
return -1;
}
rc = soundSetChannels(gBackgroundSound, 3);
if (rc != 0) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because the channel could not be set.\n");
}
soundDelete(gBackgroundSound);
gBackgroundSound = nullptr;
return -1;
}
char path[COMPAT_MAX_PATH + 1];
int rc;
if (storageType == GSOUND_MEMORY) {
rc = gameSoundFindBackgroundSoundPath(path, fileName);
} else if (storageType == GSOUND_STREAM) {
@@ -690,63 +681,24 @@ int backgroundSoundLoad(const char* fileName, GameSoundReadLimitMode readLimitMo
debugPrint("'failed because the file could not be found.\n");
}
soundDelete(gBackgroundSound);
gBackgroundSound = nullptr;
return -1;
}
if (loopingMode == GSOUND_LOOP) {
rc = soundSetLooping(gBackgroundSound, 0xFFFF);
if (rc != SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because looping could not be set.\n");
}
GameSoundLoadOptions loadOptions = {
readLimitMode,
storageType,
loopingMode,
3,
backgroundSoundCallback,
nullptr,
};
soundDelete(gBackgroundSound);
gBackgroundSound = nullptr;
return -1;
}
}
rc = soundSetCallback(gBackgroundSound, backgroundSoundCallback, nullptr);
if (rc != SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("soundSetCallback failed for background sound\n");
}
}
if (readLimitMode == GSOUND_LIMIT_BEFORE) {
rc = soundSetReadLimit(gBackgroundSound, 0x40000);
if (rc != SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("unable to set read limit ");
}
}
}
rc = soundLoad(gBackgroundSound, path);
if (rc != SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("failed on call to soundLoad.\n");
}
soundDelete(gBackgroundSound);
rc = gameSoundLoadSound(&gBackgroundSound, path, &gGameSoundAudioFileIO, &loadOptions);
if (rc != 0) {
gBackgroundSound = nullptr;
return -1;
}
if (readLimitMode != GSOUND_LIMIT_BEFORE) {
rc = soundSetReadLimit(gBackgroundSound, 0x40000);
if (rc != 0) {
if (gGameSoundDebugEnabled) {
debugPrint("unable to set read limit ");
}
}
}
if (readLimitMode == GSOUND_LOAD_NO_PLAY) {
return 0;
}
@@ -832,6 +784,84 @@ bool backgoundSoundIsPlaying()
return gBackgroundSound != nullptr;
}
int gameSoundLoadSound(Sound** soundPtr, const char* path, const SoundFileIO* fileIO, const GameSoundLoadOptions* options)
{
if (soundPtr == nullptr || path == nullptr || fileIO == nullptr || options == nullptr) {
return -1;
}
Sound* sound = nullptr;
if (_gsound_background_allocate(&sound, options->storageType, options->loopingMode) != 0) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because sound could not be allocated.\n");
}
return -1;
}
if (soundSetFileIO(sound,
fileIO->open,
fileIO->close,
fileIO->read,
fileIO->write,
fileIO->seek,
fileIO->tell,
fileIO->filelength)
!= SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because file IO could not be set.\n");
}
soundDelete(sound);
return -1;
}
if (options->channels != 0 && soundSetChannels(sound, options->channels) != SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because the channel could not be set.\n");
}
soundDelete(sound);
return -1;
}
if (options->loopingMode == GSOUND_LOOP && soundSetLooping(sound, 0xFFFF) != SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because looping could not be set.\n");
}
soundDelete(sound);
return -1;
}
if (options->callback != nullptr && soundSetCallback(sound, options->callback, options->callbackUserData) != SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("soundSetCallback failed.\n");
}
}
if (options->readLimitMode == GSOUND_LIMIT_BEFORE && soundSetReadLimit(sound, 0x40000) != SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("unable to set read limit ");
}
}
char pathCopy[COMPAT_MAX_PATH + 1];
snprintf(pathCopy, sizeof(pathCopy), "%s", path);
if (soundLoad(sound, pathCopy) != SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("failed on call to soundLoad.\n");
}
soundDelete(sound);
return -1;
}
if (options->readLimitMode != GSOUND_LIMIT_BEFORE && soundSetReadLimit(sound, 0x40000) != SOUND_NO_ERROR) {
if (gGameSoundDebugEnabled) {
debugPrint("unable to set read limit ");
}
}
*soundPtr = sound;
return 0;
}
// NOTE: Inlined.
//
// 0x450B78
@@ -932,74 +962,27 @@ int speechLoad(const char* fileName, GameSoundReadLimitMode readLimitMode, GameS
// uninline
speechDelete();
if (_gsound_background_allocate(&gSpeechSound, storageType, loopingMode)) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because sound could not be allocated.\n");
}
gSpeechSound = nullptr;
return -1;
}
if (soundSetFileIO(gSpeechSound, audioOpen, audioClose, audioRead, nullptr, audioSeek, gameSoundFileTellNotImplemented, audioGetSize)) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because file IO could not be set for compression.\n");
}
soundDelete(gSpeechSound);
gSpeechSound = nullptr;
return -1;
}
if (gameSoundFindSpeechSoundPath(path, fileName)) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because the file could not be found.\n");
}
soundDelete(gSpeechSound);
gSpeechSound = nullptr;
return -1;
}
if (loopingMode == GSOUND_LOOP) {
if (soundSetLooping(gSpeechSound, 0xFFFF)) {
if (gGameSoundDebugEnabled) {
debugPrint("failed because looping could not be set.\n");
}
soundDelete(gSpeechSound);
gSpeechSound = nullptr;
return -1;
}
}
GameSoundLoadOptions loadOptions = {
readLimitMode,
storageType,
loopingMode,
0,
speechCallback,
nullptr,
};
if (soundSetCallback(gSpeechSound, speechCallback, nullptr)) {
if (gGameSoundDebugEnabled) {
debugPrint("soundSetCallback failed for speech sound\n");
}
}
if (readLimitMode == GSOUND_LIMIT_BEFORE) {
if (soundSetReadLimit(gSpeechSound, 0x40000)) {
if (gGameSoundDebugEnabled) {
debugPrint("unable to set read limit ");
}
}
}
if (soundLoad(gSpeechSound, path)) {
if (gGameSoundDebugEnabled) {
debugPrint("failed on call to soundLoad.\n");
}
soundDelete(gSpeechSound);
if (gameSoundLoadSound(&gSpeechSound, path, &gGameSoundAudioIO, &loadOptions) != 0) {
gSpeechSound = nullptr;
return -1;
}
if (readLimitMode != GSOUND_LIMIT_BEFORE) {
if (soundSetReadLimit(gSpeechSound, 0x40000)) {
if (gGameSoundDebugEnabled) {
debugPrint("unable to set read limit ");
}
}
}
if (readLimitMode == GSOUND_LOAD_NO_PLAY) {
return 0;
}
@@ -1590,7 +1573,7 @@ void _gsound_bkg_proc()
}
// 0x451A08
int gameSoundFileOpen(const char* fname, int* sampleRate)
int gameSoundFileOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr)
{
File* stream = fileOpen(fname, "rb");
if (stream == nullptr) {
+13
View File
@@ -50,9 +50,20 @@ typedef enum GameSoundLoopingMode {
GSOUND_LOOP = 16,
} GameSoundLoopingMode;
typedef struct GameSoundLoadOptions {
GameSoundReadLimitMode readLimitMode;
GameSoundStorageType storageType;
GameSoundLoopingMode loopingMode;
int channels;
SoundCallback* callback;
void* callbackUserData;
} GameSoundLoadOptions;
typedef void(SoundEndCallback)();
extern int gMusicVolume;
extern const SoundFileIO gGameSoundAudioFileIO;
extern const SoundFileIO gGameSoundAudioIO;
int gameSoundInit();
void gameSoundReset();
@@ -60,6 +71,7 @@ int gameSoundExit();
int gameSoundSetMasterVolume(int value);
int gameSoundGetMasterVolume();
int soundEffectsSetVolume(int value);
int soundEffectsGetVolume();
int backgroundSoundIsEnabled();
void backgroundSoundSetVolume(int value);
int backgroundSoundGetVolume();
@@ -72,6 +84,7 @@ void backgroundSoundRestart(GameSoundReadLimitMode readLimitMode);
void backgroundSoundPause();
void backgroundSoundResume();
bool backgoundSoundIsPlaying();
int gameSoundLoadSound(Sound** soundPtr, const char* path, const SoundFileIO* fileIO, const GameSoundLoadOptions* options);
int speechIsEnabled();
void speechSetVolume(int value);
int speechGetVolume();
+33 -7
View File
@@ -28,7 +28,7 @@ typedef struct ProgramListNode {
static unsigned int _defaultTimerFunc();
static unsigned int getInterpreterTime();
static char* defaultFilename(char* path);
static int outputString(char* string);
static int outputString(const char* string);
static int checkWait(Program* program);
static char* programGetCurrentProcedureName(Program* program);
opcode_t stackReadInt16(unsigned char* data, int pos);
@@ -130,6 +130,8 @@ static void doEvents();
static void programListNodeFree(ProgramListNode* programListNode);
static void interpreterPrintStats();
constexpr int kDynamicStringsMaxBlockSize = 32766;
// 0x50942C
static char interpreterMissingProcedureName[] = "<couldn't find proc>";
@@ -150,7 +152,7 @@ static unsigned int interpreterTimerTick = 1000;
static char* (*interpreterFilenameMangler)(char*) = defaultFilename;
// 0x51904C
static int (*interpreterOutputFunc)(char*) = outputString;
static int (*interpreterOutputFunc)(const char*) = outputString;
// 0x519050
static int interpreterCpuBurstSize = 10;
@@ -195,7 +197,7 @@ char* _interpretMangleName(char* s)
}
// 0x4670C0 (unused)
static int outputString(char* /*string*/)
static int outputString(const char*)
{
return 1;
}
@@ -207,7 +209,7 @@ static int checkWait(Program* program)
}
// 0x4670FC
void _interpretOutputFunc(int (*func)(char*))
void _interpretOutputFunc(int (*func)(const char*))
{
interpreterOutputFunc = func;
}
@@ -590,7 +592,7 @@ static void programMarkHeap(Program* program)
next_len = *(short*)next_ptr;
if (next_len < 0) {
diff = 4 - next_len;
if (diff + len < 32766) {
if (diff + len < kDynamicStringsMaxBlockSize) {
len += diff;
*(short*)ptr += next_len - 4;
} else {
@@ -625,6 +627,11 @@ int programPushString(Program* program, const char* const string)
bufferLength++;
}
if (bufferLength > kDynamicStringsMaxBlockSize) {
debugPrint("programPushString: string too long (%d bytes), truncating to %d\n", bufferLength, kDynamicStringsMaxBlockSize);
bufferLength = kDynamicStringsMaxBlockSize;
}
if (program->dynamicStrings != nullptr) {
// TODO: Needs testing, lots of pointer stuff.
unsigned char* heap = program->dynamicStrings + 4;
@@ -648,7 +655,8 @@ int programPushString(Program* program, const char* const string)
}
*(short*)(heap + 2) = 0;
strcpy((char*)(heap + 4), string);
strncpy((char*)(heap + 4), string, bufferLength - 1);
((char*)(heap + 4))[bufferLength - 1] = '\0';
*(heap + bufferLength + 3) = '\0';
return (heap + 4) - (program->dynamicStrings + 4);
@@ -675,7 +683,8 @@ int programPushString(Program* program, const char* const string)
*(short*)(newBlock) = bufferLength;
*(short*)(newBlock + 2) = 0;
strcpy((char*)(newBlock + 4), string);
strncpy((char*)(newBlock + 4), string, bufferLength - 1);
((char*)(newBlock + 4))[bufferLength - 1] = '\0';
newTerminator = newBlock + bufferLength;
*(newTerminator + 3) = '\0';
@@ -3407,6 +3416,23 @@ const char* ProgramValue::asString(Program* program) const
return programGetString(program, opcode, integerValue);
}
const char* ProgramValue::typeDebugString() const
{
switch (opcode) {
case VALUE_TYPE_INT:
return "INTEGER";
case VALUE_TYPE_FLOAT:
return "FLOAT";
case VALUE_TYPE_STRING:
case VALUE_TYPE_DYNAMIC_STRING:
return "STRING";
case VALUE_TYPE_PTR:
return "POINTER";
default:
return "(UNKNOWN)";
}
}
// CE
ProgramValue programMakeString(Program* program, const char* str)
{
+2 -1
View File
@@ -175,6 +175,7 @@ public:
int asInt() const;
Object* asObject() const;
const char* asString(Program* program) const;
const char* typeDebugString() const;
};
typedef std::vector<ProgramValue> ProgramStack;
@@ -216,7 +217,7 @@ extern OpcodeHandler* gInterpreterOpcodeHandlers[OPCODE_MAX_COUNT];
extern int _TimeOut;
char* _interpretMangleName(char* s);
void _interpretOutputFunc(int (*func)(char*));
void _interpretOutputFunc(int (*func)(const char*));
int _interpretOutput(const char* format, ...);
[[noreturn]] void programFatalError(const char* str, ...);
void programPrintError(const char* format, ...);
+214 -84
View File
@@ -165,6 +165,8 @@ constexpr int kTradeSlotCount = 3;
#define INVENTORY_LOOT_RIGHT_SCROLLER_X_PAD (INVENTORY_LOOT_RIGHT_SCROLLER_X + INVENTORY_SLOT_PADDING)
#define INVENTORY_LOOT_RIGHT_SCROLLER_Y_PAD (INVENTORY_LOOT_RIGHT_SCROLLER_Y + INVENTORY_SLOT_PADDING)
#define INVENTORY_LOOT_CRITTER_TOGGLE_Y (INVENTORY_LOOT_LEFT_BODY_VIEW_Y + INVENTORY_BODY_VIEW_HEIGHT + 24)
#define INVENTORY_TRADE_LEFT_SCROLLER_X_PAD (INVENTORY_TRADE_LEFT_SCROLLER_Y + INVENTORY_SLOT_PADDING)
#define INVENTORY_TRADE_LEFT_SCROLLER_Y_PAD (INVENTORY_TRADE_LEFT_SCROLLER_Y + INVENTORY_SLOT_PADDING)
@@ -322,6 +324,7 @@ static void inventoryRenderSummary();
static int _inven_from_button(int keyCode, Object** outItem, Object*** outItemSlot, Object** outOwner);
static void inventoryRenderItemDescription(const char* string);
static void inventoryExamineItem(Object* critter, Object* item);
static void inventorySetLootLeftPaneCritter(Object* critter, Object* target);
static void inventoryWindowOpenContextMenu(int eventCode, int inventoryWindowType);
static InventoryMoveResult _move_inventory(Object* item, int slotIndex, Object* targetObj, bool isPlanting);
static std::pair<int, int> barterComputeTablesValue(Object* dude, Object* npc, bool offerButton = false);
@@ -353,6 +356,7 @@ static int inventoryLootGetSlotX(bool targetInventory, int slotIndex);
static int inventoryLootGetSlotY(int slotIndex);
static bool inventoryLootMouseHitTestScroller(bool targetInventory);
static int inventoryComputeAlignedMaxOffset(int length, int visibleSlots, int scrollStep);
static int inventoryGetCenteredWindowY(int windowHeight);
// 0x46E6D0
static const int gSummaryStats[7] = {
@@ -600,6 +604,10 @@ static Inventory* _target_pud;
// 0x59E97C barter_back_win
static int gInventoryBarterBackgroundWindow;
// Weight of equipped items stripped from the left/right loot-pane critter.
// Non-zero only while inventoryOpenLooting is active.
static int inventoryLootRightEquippedWeight = 0;
static FrmImage _inventoryFrmImages[INVENTORY_FRM_COUNT];
static FrmImage _moveFrmImages[8];
@@ -689,6 +697,11 @@ static int inventoryGetWindowHeight(int inventoryWindowType)
return gInventoryWindowDescriptions[inventoryWindowType].height;
}
static int inventoryGetCenteredWindowY(int windowHeight)
{
return (screenGetVisibleHeight() - windowHeight) / 2;
}
static int inventoryChooseColumns(FrmImage& image, int expandedWidth, int col1FrmId, const char* col2Name)
{
if (settings.ui.inventory_columns <= 1) {
@@ -729,7 +742,6 @@ static void inventoryLootApplyLayout(int columns)
int leftBodyViewShift = extraColumns * (47 - INVENTORY_LOOT_LEFT_BODY_VIEW_X);
int rightBodyViewShift = extraColumns * (563 - INVENTORY_LOOT_RIGHT_BODY_VIEW_X);
int rightPaneShift = extraColumns * (612 - 476);
int critterButtonsShift = extraColumns * (504 - 436);
inventoryLootLayout.columns = columns;
inventoryLootLayout.visibleSlots = INVENTORY_ROWS * columns;
@@ -747,8 +759,10 @@ static void inventoryLootApplyLayout(int columns)
inventoryLootLayout.rightScrollButtonX = 379 + rightPaneShift;
inventoryLootLayout.takeAllButtonX = 432 + rightPaneShift;
inventoryLootLayout.doneButtonX = 476 + rightPaneShift;
inventoryLootLayout.prevCritterButtonX = 436 + critterButtonsShift;
inventoryLootLayout.nextCritterButtonX = 456 + critterButtonsShift;
// Center the two 20px-wide critter-nav arrows below the right avatar
int critterBtnCenterX = inventoryLootLayout.rightBodyViewX + INVENTORY_BODY_VIEW_WIDTH / 2;
inventoryLootLayout.prevCritterButtonX = critterBtnCenterX - 20;
inventoryLootLayout.nextCritterButtonX = critterBtnCenterX;
}
static void inventoryNormalLayoutUpdate()
@@ -1106,7 +1120,7 @@ static bool _setup_inventory(int inventoryWindowType)
: (screenGetWidth() - windowWidth) / 2;
int inventoryWindowY = preserveVanillaY
? INVENTORY_WINDOW_Y
: (screenGetHeight() - windowHeight) / 2;
: inventoryGetCenteredWindowY(windowHeight);
gInventoryWindow = windowCreate(inventoryWindowX,
inventoryWindowY,
windowWidth,
@@ -1836,7 +1850,7 @@ static void _display_inventory(int stackOffset, int dragSlotIndex, int inventory
int inventoryWeight = objectGetInventoryWeight(object);
snprintf(formattedText, sizeof(formattedText), "%d/%d", inventoryWeight, carryWeight);
if (critterIsEncumbered(object)) {
if (inventoryWeight > carryWeight) {
color = _colorTable[31744];
}
} else {
@@ -1955,11 +1969,11 @@ static void _display_target_inventory(int stackOffset, int dragSlotIndex, Invent
int color = _colorTable[992];
if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {
int currentWeight = objectGetInventoryWeight(object);
int currentWeight = objectGetInventoryWeight(object) + inventoryLootRightEquippedWeight;
int maxWeight = critterGetStat(object, STAT_CARRY_WEIGHT);
snprintf(formattedText, sizeof(formattedText), "%d/%d", currentWeight, maxWeight);
if (critterIsEncumbered(object)) {
if (currentWeight > maxWeight) {
color = _colorTable[31744];
}
} else if (PID_TYPE(object->pid) == OBJ_TYPE_ITEM) {
@@ -2175,6 +2189,21 @@ static void _display_body(int fid, int inventoryWindowType)
windowBuffer + windowPitch * (rect.top + (INVENTORY_BODY_VIEW_HEIGHT - frameHeight) / 2) + (INVENTORY_BODY_VIEW_WIDTH - frameWidth) / 2 + rect.left,
windowPitch);
// Draw party member name at bottom of left avatar in loot mode.
if (index == 0 && inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT && _stack[0] != gDude) {
const char* name = critterGetName(_stack[0]);
if (name != nullptr) {
// TODO: clean up text rendering
int oldFont = fontGetCurrent();
fontSetCurrent(101);
int nameWidth = std::min(fontGetStringWidth(name), INVENTORY_BODY_VIEW_WIDTH);
int nameX = rect.left + INVENTORY_BODY_VIEW_WIDTH / 2 - nameWidth / 2;
int nameY = rect.bottom - fontGetLineHeight() - 1;
fontDrawText(windowBuffer + windowPitch * nameY + nameX, name, INVENTORY_BODY_VIEW_WIDTH, windowPitch, _colorTable[992]);
fontSetCurrent(oldFont);
}
}
win = gInventoryWindow;
}
windowRefreshRect(win, &rect);
@@ -2890,6 +2919,105 @@ Object* critterGetArmor(Object* critter)
return nullptr;
}
// Critter inventories are displayed without equipped items in almost all cases. This is accomplished by
// temporarily removing them when inventory is viewed.
CritterEquipped critterStripEquipped(Object* critter)
{
CritterEquipped equipped;
Inventory* inv = &critter->data.inventory;
for (int i = 0; i < inv->length; i++) {
Object* item = inv->items[i].item;
if ((item->flags & OBJECT_IN_LEFT_HAND) != 0) {
if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) {
equipped.rightHand = item;
}
equipped.leftHand = item;
} else if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) {
equipped.rightHand = item;
} else if ((item->flags & OBJECT_WORN) != 0) {
equipped.armor = item;
}
}
if (equipped.leftHand != nullptr) {
equipped.weight += itemGetWeight(equipped.leftHand);
itemRemove(critter, equipped.leftHand, 1);
}
if (equipped.rightHand != nullptr && equipped.rightHand != equipped.leftHand) {
equipped.weight += itemGetWeight(equipped.rightHand);
itemRemove(critter, equipped.rightHand, 1);
}
if (equipped.armor != nullptr) {
equipped.weight += itemGetWeight(equipped.armor);
itemRemove(critter, equipped.armor, 1);
}
return equipped;
}
void critterRestoreEquipped(Object* critter, CritterEquipped& equipped)
{
if (equipped.leftHand != nullptr) {
equipped.leftHand->flags |= OBJECT_IN_LEFT_HAND;
if (equipped.leftHand == equipped.rightHand) equipped.leftHand->flags |= OBJECT_IN_RIGHT_HAND;
itemAdd(critter, equipped.leftHand, 1);
}
if (equipped.rightHand != nullptr && equipped.rightHand != equipped.leftHand) {
equipped.rightHand->flags |= OBJECT_IN_RIGHT_HAND;
itemAdd(critter, equipped.rightHand, 1);
}
if (equipped.armor != nullptr) {
equipped.armor->flags |= OBJECT_WORN;
itemAdd(critter, equipped.armor, 1);
}
equipped = {};
}
static void inventoryStripEquippedToGlobals(Object* critter)
{
CritterEquipped equipped = critterStripEquipped(critter);
gInventoryLeftHandItem = equipped.leftHand;
gInventoryRightHandItem = equipped.rightHand;
gInventoryArmor = equipped.armor;
}
static void inventoryRestoreEquippedFromGlobals(Object* critter)
{
CritterEquipped equipped { gInventoryLeftHandItem, gInventoryRightHandItem, gInventoryArmor };
critterRestoreEquipped(critter, equipped);
gInventoryLeftHandItem = nullptr;
gInventoryRightHandItem = nullptr;
gInventoryArmor = nullptr;
}
static void inventorySetLootLeftPaneCritter(Object* critter, Object* target)
{
assert(critter != nullptr);
assert(target != nullptr);
inventoryRestoreEquippedFromGlobals(_inven_dude);
inventoryStripEquippedToGlobals(critter);
_inven_dude = critter;
_curr_stack = 0;
_pud = &(critter->data.inventory);
_stack[0] = critter;
_stack_offset[0] = 0;
int animationCode = 0;
Object* itemInHand = interfaceGetCurrentHand() == HAND_RIGHT ? gInventoryRightHandItem : gInventoryLeftHandItem;
if (itemInHand != nullptr) {
Proto* proto = nullptr;
if (protoGetProto(itemInHand->pid, &proto) != -1
&& proto != nullptr
&& proto->item.type == ITEM_TYPE_WEAPON) {
animationCode = proto->item.data.weapon.animationCode;
}
}
gInventoryWindowDudeFid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, animationCode, 0);
gInventoryWindowDudeRotationTimestamp = 0;
_display_inventory(0, -1, INVENTORY_WINDOW_TYPE_LOOT);
_display_body(target->fid, INVENTORY_WINDOW_TYPE_LOOT);
}
// 0x471CA0
Object* objectGetCarriedObjectByPid(Object* obj, int pid)
{
@@ -4139,12 +4267,8 @@ static void inventoryWindowOpenContextMenu(int keyCode, int inventoryWindowType)
// 0x473904
int inventoryOpenLooting(Object* looter, Object* target)
{
int arrowFrmIds[INVENTORY_ARROW_FRM_COUNT];
FrmImage arrowFrmImages[INVENTORY_ARROW_FRM_COUNT];
MessageListItem messageListItem;
memcpy(arrowFrmIds, gInventoryArrowFrmIds, sizeof(gInventoryArrowFrmIds));
if (looter != _inven_dude) {
return 0;
}
@@ -4209,25 +4333,10 @@ int inventoryOpenLooting(Object* looter, Object* target)
itemMoveAllHidden(target, hiddenBox);
Object* item1 = nullptr;
Object* item2 = nullptr;
Object* armor = nullptr;
CritterEquipped stealTargetEquipped;
if (_gIsSteal) {
item1 = critterGetItem1(target);
if (item1 != nullptr) {
itemRemove(target, item1, 1);
}
item2 = critterGetItem2(target);
if (item2 != nullptr) {
itemRemove(target, item2, 1);
}
armor = critterGetArmor(target);
if (armor != nullptr) {
itemRemove(target, armor, 1);
}
stealTargetEquipped = critterStripEquipped(target);
inventoryLootRightEquippedWeight = stealTargetEquipped.weight;
}
bool isoWasEnabled = _setup_inventory(INVENTORY_WINDOW_TYPE_LOOT);
@@ -4258,43 +4367,22 @@ int inventoryOpenLooting(Object* looter, Object* target)
}
if (critterCount > 1) {
int fid;
int btn;
// Setup left arrow button.
fid = buildFid(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_UP], 0, 0, 0);
arrowFrmImages[INVENTORY_ARROW_FRM_LEFT_ARROW_UP].lock(fid);
fid = buildFid(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN], 0, 0, 0);
arrowFrmImages[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN].lock(fid);
if (arrowFrmImages[INVENTORY_ARROW_FRM_LEFT_ARROW_UP].isLocked() && arrowFrmImages[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN].isLocked()) {
btn = buttonCreate(gInventoryWindow,
inventoryLootLayout.prevCritterButtonX, 162, 20, 18,
-1, -1, KEY_PAGE_UP, -1,
arrowFrmImages[INVENTORY_ARROW_FRM_LEFT_ARROW_UP].getData(),
arrowFrmImages[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN].getData());
if (btn != -1) {
buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release);
}
int btn = buttonCreateWithFrm(gInventoryWindow,
inventoryLootLayout.prevCritterButtonX, INVENTORY_LOOT_CRITTER_TOGGLE_Y,
-1, -1, KEY_PAGE_UP, -1,
FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_UP]),
FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN]));
if (btn != -1) {
buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release);
}
// Setup right arrow button.
fid = buildFid(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP], 0, 0, 0);
arrowFrmImages[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP].lock(fid);
fid = buildFid(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN], 0, 0, 0);
arrowFrmImages[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN].lock(fid);
if (arrowFrmImages[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP].isLocked() && arrowFrmImages[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN].isLocked()) {
btn = buttonCreate(gInventoryWindow,
inventoryLootLayout.nextCritterButtonX, 162, 20, 18,
-1, -1, KEY_PAGE_DOWN, -1,
arrowFrmImages[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP].getData(),
arrowFrmImages[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN].getData());
if (btn != -1) {
buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release);
}
btn = buttonCreateWithFrm(gInventoryWindow,
inventoryLootLayout.nextCritterButtonX, INVENTORY_LOOT_CRITTER_TOGGLE_Y,
-1, -1, KEY_PAGE_DOWN, -1,
FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP]),
FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN]));
if (btn != -1) {
buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release);
}
for (int index = 0; index < critterCount; index++) {
@@ -4306,6 +4394,43 @@ int inventoryOpenLooting(Object* looter, Object* target)
}
}
// Party member navigation: left/right arrows below the left (player) avatar
// switch whose inventory is shown on the left pane. It works when looting,
// or "stealing" from a companion (which is just quick looting)
Object* const playerObj = _inven_dude;
int savedDudeFid = gInventoryWindowDudeFid;
std::vector<Object*> partyTargets = { _inven_dude };
if (settings.qol.party_loot_and_barter && (!_gIsSteal || objectIsPartyMember(target))) {
for (Object* pm : get_all_party_members_objects(false)) {
if (pm != gDude && pm != target) {
partyTargets.push_back(pm);
}
}
}
int partyTargetIndex = 0;
if (partyTargets.size() > 1) {
const int btnCenterX = inventoryLootLayout.leftBodyViewX + INVENTORY_BODY_VIEW_WIDTH / 2;
int btn = buttonCreateWithFrm(gInventoryWindow,
btnCenterX - 20, INVENTORY_LOOT_CRITTER_TOGGLE_Y,
-1, -1, KEY_ARROW_LEFT, -1,
FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_UP]),
FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN]));
if (btn != -1) {
buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release);
}
btn = buttonCreateWithFrm(gInventoryWindow,
btnCenterX, INVENTORY_LOOT_CRITTER_TOGGLE_Y,
-1, -1, KEY_ARROW_RIGHT, -1,
FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP]),
FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN]));
if (btn != -1) {
buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release);
}
}
_display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT);
_display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);
gInventoryWindowDudeRotationTimestamp = 0;
@@ -4341,11 +4466,11 @@ int inventoryOpenLooting(Object* looter, Object* target)
if (keyCode == KEY_LOWERCASE_A) {
soundPlayFile("ib1p1xx1");
}
int maxCarryWeight = critterGetStat(looter, STAT_CARRY_WEIGHT);
int currentWeight = objectGetInventoryWeight(looter);
int maxCarryWeight = critterGetStat(_inven_dude, STAT_CARRY_WEIGHT);
int currentWeight = objectGetInventoryWeight(_inven_dude);
int newInventoryWeight = objectGetInventoryWeight(target);
if (newInventoryWeight <= maxCarryWeight - currentWeight) {
itemMoveAll(target, looter);
itemMoveAll(target, _inven_dude);
_display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT);
_display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);
} else {
@@ -4356,11 +4481,25 @@ int inventoryOpenLooting(Object* looter, Object* target)
}
}
}
// change selected companion
} else if (keyCode == KEY_ARROW_LEFT) {
if (partyTargets.size() > 1) {
partyTargetIndex = (partyTargetIndex > 0) ? partyTargetIndex - 1 : (int)partyTargets.size() - 1;
inventorySetLootLeftPaneCritter(partyTargets[partyTargetIndex], target);
}
} else if (keyCode == KEY_ARROW_RIGHT) {
if (partyTargets.size() > 1) {
partyTargetIndex = (partyTargetIndex < (int)partyTargets.size() - 1) ? partyTargetIndex + 1 : 0;
inventorySetLootLeftPaneCritter(partyTargets[partyTargetIndex], target);
}
} else if (keyCode == KEY_ARROW_UP) {
if (_stack_offset[_curr_stack] > 0) {
inventoryLootScrollBy(_stack_offset[_curr_stack], -1, _pud->length);
_display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);
}
} else if (keyCode == KEY_PAGE_UP) {
if (critterCount != 0) {
if (critterIndex > 0) {
@@ -4508,20 +4647,7 @@ int inventoryOpenLooting(Object* looter, Object* target)
}
if (_gIsSteal) {
if (item1 != nullptr) {
item1->flags |= OBJECT_IN_LEFT_HAND;
itemAdd(target, item1, 1);
}
if (item2 != nullptr) {
item2->flags |= OBJECT_IN_RIGHT_HAND;
itemAdd(target, item2, 1);
}
if (armor != nullptr) {
armor->flags |= OBJECT_WORN;
itemAdd(target, armor, 1);
}
critterRestoreEquipped(target, stealTargetEquipped);
}
itemMoveAll(hiddenBox, target);
@@ -4544,7 +4670,11 @@ int inventoryOpenLooting(Object* looter, Object* target)
}
}
inventoryLootRightEquippedWeight = 0;
gInventoryWindowDudeFid = savedDudeFid;
_exit_inventory(isoWasEnabled);
_inven_dude = playerObj;
// NOTE: Uninline.
inventoryCommonFree();
@@ -4646,7 +4776,7 @@ static InventoryMoveResult _move_inventory(Object* item, int slotIndex, Object*
}
if (quantityToMove != -1) {
if (_gIsSteal) {
if (_gIsSteal && _inven_dude == gDude) {
if (skillsPerformStealing(_inven_dude, targetObj, item, true) == 0) {
result = INVENTORY_MOVE_RESULT_CAUGHT_STEALING;
}
@@ -4673,7 +4803,7 @@ static InventoryMoveResult _move_inventory(Object* item, int slotIndex, Object*
}
if (quantityToMove != -1) {
if (_gIsSteal) {
if (_gIsSteal && _inven_dude == gDude) {
if (skillsPerformStealing(_inven_dude, targetObj, item, false) == 0) {
result = INVENTORY_MOVE_RESULT_CAUGHT_STEALING;
}
@@ -5856,7 +5986,7 @@ static int inventoryQuantityWindowInit(int inventoryWindowType, Object* item)
? (screenGetWidth() - windowDescription->width) / 2
: windowDescription->x;
int quantityWindowY = screenGetHeight() != 480
? (screenGetHeight() - windowDescription->height) / 2
? inventoryGetCenteredWindowY(windowDescription->height)
: windowDescription->y;
_mt_wid = windowCreate(quantityWindowX, quantityWindowY, windowDescription->width, windowDescription->height, 257, WINDOW_MODAL | WINDOW_MOVE_ON_TOP);
unsigned char* windowBuffer = windowGetBuffer(_mt_wid);

Some files were not shown because too many files have changed in this diff Show More