#include "game_sound.h" #include #include #include "animation.h" #include "art.h" #include "audio.h" #include "combat.h" #include "debug.h" #include "game_config.h" #include "input.h" #include "item.h" #include "map.h" #include "memory.h" #include "movie.h" #include "object.h" #include "pointer_registry.h" #include "proto.h" #include "queue.h" #include "random.h" #include "settings.h" #include "sound_effects_cache.h" #include "stat.h" #include "svga.h" #include "window_manager.h" #include "worldmap.h" namespace fallout { typedef enum SoundEffectActionType { SOUND_EFFECT_ACTION_TYPE_ACTIVE, SOUND_EFFECT_ACTION_TYPE_PASSIVE, } SoundEffectActionType; // 0x5035BC static char _aSoundSfx[] = "sound\\sfx\\"; // 0x5035C8 static char _aSoundMusic_0[] = "sound\\music\\"; // 0x5035D8 static char _aSoundSpeech_0[] = "sound\\speech\\"; // 0x518E30 static bool gGameSoundInitialized = false; // 0x518E34 static bool gGameSoundDebugEnabled = false; // 0x518E38 static bool gMusicEnabled = false; // 0x518E3C static int _gsound_background_df_vol = 0; // 0x518E40 static int _gsound_background_fade = 0; // 0x518E44 static bool gSpeechEnabled = false; // 0x518E48 static bool gSoundEffectsEnabled = false; // number of active effects (max 4) static int _gsound_active_effect_counter; // 0x518E50 static Sound* gBackgroundSound = NULL; // 0x518E54 static Sound* gSpeechSound = NULL; // 0x518E58 static SoundEndCallback* gBackgroundSoundEndCallback = NULL; // 0x518E5C static SoundEndCallback* gSpeechEndCallback = NULL; // 0x518E60 static char _snd_lookup_weapon_type[WEAPON_SOUND_EFFECT_COUNT] = { 'R', // Ready 'A', // Attack 'O', // Out of ammo 'F', // Firing 'H', // Hit }; // 0x518E65 static char _snd_lookup_scenery_action[SCENERY_SOUND_EFFECT_COUNT] = { 'O', // Open 'C', // Close 'L', // Lock 'N', // Unlock 'U', // Use }; // 0x518E6C static int _background_storage_requested = -1; // 0x518E70 static int _background_loop_requested = -1; // 0x518E74 static char* _sound_sfx_path = _aSoundSfx; // 0x518E78 static char* _sound_music_path1 = _aSoundMusic_0; // 0x518E7C static char* _sound_music_path2 = _aSoundMusic_0; // 0x518E80 static char* _sound_speech_path = _aSoundSpeech_0; // 0x518E84 static int gMasterVolume = VOLUME_MAX; // 0x518E88 int gMusicVolume = VOLUME_MAX; // 0x518E8C static int gSpeechVolume = VOLUME_MAX; // 0x518E90 static int gSoundEffectsVolume = VOLUME_MAX; // 0x518E94 static int _detectDevices = -1; // 0x518E98 static int _lastTime_1 = 0; // 0x596EB0 static char _background_fname_copied[COMPAT_MAX_PATH]; // 0x596FB5 static char _sfx_file_name[13]; // NOTE: I'm mot sure about it's size. Why not MAX_PATH? // // 0x596FC2 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(); static void speechDisable(); static void speechEnable(); 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 access, ...); static long _gsound_write_(); static long gameSoundFileTellNotImplemented(int handle); static int gameSoundFileWrite(int handle, const void* buf, unsigned int size); static int gameSoundFileClose(int handle); static int gameSoundFileRead(int handle, void* buf, unsigned int size); static long gameSoundFileSeek(int handle, long offset, int origin); static long gameSoundFileTell(int handle); static long gameSoundFileGetSize(int handle); static bool gameSoundIsCompressed(char* filePath); static void speechCallback(void* userData, int a2); static void backgroundSoundCallback(void* userData, int a2); static void soundEffectCallback(void* userData, int a2); static int _gsound_background_allocate(Sound** out_s, int a2, int a3); static int gameSoundFindBackgroundSoundPathWithCopy(char* dest, const char* src); static int gameSoundFindBackgroundSoundPath(char* dest, const char* src); static int gameSoundFindSpeechSoundPath(char* dest, const char* src); static void gameSoundDeleteOldMusicFile(); static int backgroundSoundPlay(); static int speechPlay(); static int _gsound_get_music_path(char** out_value, const char* key); static Sound* _gsound_get_sound_ready_for_effect(); static bool _gsound_file_exists_f(const char* fname); static int _gsound_setup_paths(); // 0x44FC70 int gameSoundInit() { if (gGameSoundInitialized) { if (gGameSoundDebugEnabled) { debugPrint("Trying to initialize gsound twice.\n"); } return -1; } if (!settings.sound.initialize) { return 0; } gGameSoundDebugEnabled = settings.sound.debug; if (gGameSoundDebugEnabled) { debugPrint("Initializing sound system..."); } if (_gsound_get_music_path(&_sound_music_path1, GAME_CONFIG_MUSIC_PATH1_KEY) != 0) { return -1; } if (_gsound_get_music_path(&_sound_music_path2, GAME_CONFIG_MUSIC_PATH2_KEY) != 0) { return -1; } if (strlen(_sound_music_path1) > 247 || strlen(_sound_music_path2) > 247) { if (gGameSoundDebugEnabled) { debugPrint("Music paths way too long.\n"); } return -1; } // gsound_setup_paths if (_gsound_setup_paths() != 0) { return -1; } soundSetMemoryProcs(internal_malloc, internal_realloc, internal_free); // initialize direct sound if (soundInit(_detectDevices, 24, 0x8000, 0x8000, 22050) != 0) { if (gGameSoundDebugEnabled) { debugPrint("failed!\n"); } return -1; } if (gGameSoundDebugEnabled) { debugPrint("success.\n"); } audioFileInit(gameSoundIsCompressed); audioInit(gameSoundIsCompressed); int cacheSize = settings.sound.cache_size; if (cacheSize >= 0x40000) { debugPrint("\n!!! Config file needs adustment. Please remove the "); debugPrint("cache_size line and run fallout again. This will reset "); debugPrint("cache_size to the new default, which is expressed in K.\n"); return -1; } if (soundEffectsCacheInit(cacheSize << 10, _sound_sfx_path) != 0) { if (gGameSoundDebugEnabled) { debugPrint("Unable to initialize sound effects cache.\n"); } } if (soundSetDefaultFileIO(gameSoundFileOpen, gameSoundFileClose, gameSoundFileRead, gameSoundFileWrite, gameSoundFileSeek, gameSoundFileTell, gameSoundFileGetSize) != 0) { if (gGameSoundDebugEnabled) { debugPrint("Failure setting sound I/O calls.\n"); } return -1; } tickersAdd(_gsound_bkg_proc); gGameSoundInitialized = true; // SOUNDS if (gGameSoundDebugEnabled) { debugPrint("Sounds are "); } if (settings.sound.sounds) { // NOTE: Uninline. soundEffectsEnable(); } else { if (gGameSoundDebugEnabled) { debugPrint(" not "); } } if (gGameSoundDebugEnabled) { debugPrint("on.\n"); } // MUSIC if (gGameSoundDebugEnabled) { debugPrint("Music is "); } if (settings.sound.music) { // NOTE: Uninline. backgroundSoundEnable(); } else { if (gGameSoundDebugEnabled) { debugPrint(" not "); } } if (gGameSoundDebugEnabled) { debugPrint("on.\n"); } // SPEEECH if (gGameSoundDebugEnabled) { debugPrint("Speech is "); } if (settings.sound.speech) { // NOTE: Uninline. speechEnable(); } else { if (gGameSoundDebugEnabled) { debugPrint(" not "); } } if (gGameSoundDebugEnabled) { debugPrint("on.\n"); } gMasterVolume = settings.sound.master_volume; gameSoundSetMasterVolume(gMasterVolume); gMusicVolume = settings.sound.music_volume; backgroundSoundSetVolume(gMusicVolume); gSoundEffectsVolume = settings.sound.sndfx_volume; soundEffectsSetVolume(gSoundEffectsVolume); gSpeechVolume = settings.sound.speech_volume; speechSetVolume(gSpeechVolume); _gsound_background_fade = 0; gBackgroundSoundFileName[0] = '\0'; return 0; } // 0x450164 void gameSoundReset() { if (!gGameSoundInitialized) { return; } if (gGameSoundDebugEnabled) { debugPrint("Resetting sound system..."); } // NOTE: Uninline. speechDelete(); if (_gsound_background_df_vol) { // NOTE: Uninline. backgroundSoundEnable(); } backgroundSoundDelete(); _gsound_background_fade = 0; soundDeleteAll(); soundEffectsCacheFlush(); _gsound_active_effect_counter = 0; if (gGameSoundDebugEnabled) { debugPrint("done.\n"); } return; } // 0x450244 int gameSoundExit() { if (!gGameSoundInitialized) { return -1; } tickersRemove(_gsound_bkg_proc); // NOTE: Uninline. speechDelete(); backgroundSoundDelete(); gameSoundDeleteOldMusicFile(); soundExit(); soundEffectsCacheExit(); audioFileExit(); audioExit(); gGameSoundInitialized = false; return 0; } // NOTE: Inlined. // // 0x4502BC void soundEffectsEnable() { if (gGameSoundInitialized) { gSoundEffectsEnabled = true; } } // NOTE: Inlined. // // 0x4502D0 void soundEffectsDisable() { if (gGameSoundInitialized) { gSoundEffectsEnabled = false; } } // 0x4502E4 int soundEffectsIsEnabled() { return gSoundEffectsEnabled; } // 0x4502EC int gameSoundSetMasterVolume(int volume) { if (!gGameSoundInitialized) { return -1; } if (volume < VOLUME_MIN && volume > VOLUME_MAX) { if (gGameSoundDebugEnabled) { debugPrint("Requested master volume out of range.\n"); } return -1; } if (_gsound_background_df_vol && volume != 0 && backgroundSoundGetVolume() != 0) { // NOTE: Uninline. backgroundSoundEnable(); _gsound_background_df_vol = 0; } if (_soundSetMasterVolume(volume) != 0) { if (gGameSoundDebugEnabled) { debugPrint("Error setting master sound volume.\n"); } return -1; } gMasterVolume = volume; if (gMusicEnabled && volume == 0) { // NOTE: Uninline. backgroundSoundDisable(); _gsound_background_df_vol = 1; } return 0; } // 0x450410 int gameSoundGetMasterVolume() { return gMasterVolume; } // 0x450418 int soundEffectsSetVolume(int volume) { if (!gGameSoundInitialized || volume < VOLUME_MIN || volume > VOLUME_MAX) { if (gGameSoundDebugEnabled) { debugPrint("Error setting sfx volume.\n"); } return -1; } gSoundEffectsVolume = volume; return 0; } // 0x450454 int soundEffectsGetVolume() { return gSoundEffectsVolume; } // NOTE: Inlined. // // 0x45045C void backgroundSoundDisable() { if (gGameSoundInitialized) { if (gMusicEnabled) { backgroundSoundDelete(); movieSetVolume(0); gMusicEnabled = false; } } } // NOTE: Inlined. // // 0x450488 void backgroundSoundEnable() { if (gGameSoundInitialized) { if (!gMusicEnabled) { movieSetVolume((int)(gMusicVolume * 0.94)); gMusicEnabled = true; backgroundSoundRestart(12); } } } // 0x4504D4 int backgroundSoundIsEnabled() { return gMusicEnabled; } // 0x4504DC void backgroundSoundSetVolume(int volume) { if (!gGameSoundInitialized) { return; } if (volume < VOLUME_MIN || volume > VOLUME_MAX) { if (gGameSoundDebugEnabled) { debugPrint("Requested background volume out of range.\n"); } return; } gMusicVolume = volume; if (_gsound_background_df_vol) { // NOTE: Uninline. backgroundSoundEnable(); _gsound_background_df_vol = 0; } if (gMusicEnabled) { movieSetVolume((int)(volume * 0.94)); } if (gMusicEnabled) { if (gBackgroundSound != NULL) { soundSetVolume(gBackgroundSound, (int)(gMusicVolume * 0.94)); } } if (gMusicEnabled) { if (volume == 0 || gameSoundGetMasterVolume() == 0) { // NOTE: Uninline. backgroundSoundDisable(); _gsound_background_df_vol = 1; } } } // 0x450618 int backgroundSoundGetVolume() { return gMusicVolume; } // int _gsound_background_volume_get_set(int volume) { int oldMusicVolume = gMusicVolume; backgroundSoundSetVolume(volume); return oldMusicVolume; } // 0x450650 void backgroundSoundSetEndCallback(SoundEndCallback* callback) { gBackgroundSoundEndCallback = callback; } // NOTE: There are no references to this function. // // 0x450670 int backgroundSoundGetDuration() { return soundGetDuration(gBackgroundSound); } // [fileName] is base file name, without path and extension. // // 0x45067C int backgroundSoundLoad(const char* fileName, int a2, int a3, int a4) { int rc; _background_storage_requested = a3; _background_loop_requested = a4; strcpy(gBackgroundSoundFileName, fileName); if (!gGameSoundInitialized) { return -1; } if (!gMusicEnabled) { return -1; } if (gGameSoundDebugEnabled) { debugPrint("Loading background sound file %s%s...", fileName, ".acm"); } backgroundSoundDelete(); rc = _gsound_background_allocate(&gBackgroundSound, a3, a4); if (rc != 0) { if (gGameSoundDebugEnabled) { debugPrint("failed because sound could not be allocated.\n"); } gBackgroundSound = NULL; return -1; } rc = soundSetFileIO(gBackgroundSound, audioFileOpen, audioFileClose, audioFileRead, NULL, audioFileSeek, gameSoundFileTellNotImplemented, audioFileGetSize); if (rc != 0) { if (gGameSoundDebugEnabled) { debugPrint("failed because file IO could not be set for compression.\n"); } soundDelete(gBackgroundSound); gBackgroundSound = NULL; return -1; } rc = soundSetChannels(gBackgroundSound, 3); if (rc != 0) { if (gGameSoundDebugEnabled) { debugPrint("failed because the channel could not be set.\n"); } soundDelete(gBackgroundSound); gBackgroundSound = NULL; return -1; } char path[COMPAT_MAX_PATH + 1]; if (a3 == 13) { rc = gameSoundFindBackgroundSoundPath(path, fileName); } else if (a3 == 14) { rc = gameSoundFindBackgroundSoundPathWithCopy(path, fileName); } if (rc != SOUND_NO_ERROR) { if (gGameSoundDebugEnabled) { debugPrint("'failed because the file could not be found.\n"); } soundDelete(gBackgroundSound); gBackgroundSound = NULL; return -1; } if (a4 == 16) { rc = soundSetLooping(gBackgroundSound, 0xFFFF); if (rc != SOUND_NO_ERROR) { if (gGameSoundDebugEnabled) { debugPrint("failed because looping could not be set.\n"); } soundDelete(gBackgroundSound); gBackgroundSound = NULL; return -1; } } rc = soundSetCallback(gBackgroundSound, backgroundSoundCallback, NULL); if (rc != SOUND_NO_ERROR) { if (gGameSoundDebugEnabled) { debugPrint("soundSetCallback failed for background sound\n"); } } if (a2 == 11) { 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); gBackgroundSound = NULL; return -1; } if (a2 != 11) { rc = soundSetReadLimit(gBackgroundSound, 0x40000); if (rc != 0) { if (gGameSoundDebugEnabled) { debugPrint("unable to set read limit "); } } } if (a2 == 10) { return 0; } rc = backgroundSoundPlay(); if (rc != 0) { if (gGameSoundDebugEnabled) { debugPrint("failed starting to play.\n"); } soundDelete(gBackgroundSound); gBackgroundSound = NULL; return -1; } if (gGameSoundDebugEnabled) { debugPrint("succeeded.\n"); } return 0; } // 0x450A08 int _gsound_background_play_level_music(const char* a1, int a2) { return backgroundSoundLoad(a1, a2, 14, 16); } // 0x450AB4 void backgroundSoundDelete() { if (gGameSoundInitialized && gMusicEnabled && gBackgroundSound) { if (_gsound_background_fade) { if (_soundFade(gBackgroundSound, 2000, 0) == 0) { gBackgroundSound = NULL; return; } } soundDelete(gBackgroundSound); gBackgroundSound = NULL; } } // 0x450B0C void backgroundSoundRestart(int value) { if (gBackgroundSoundFileName[0] != '\0') { if (backgroundSoundLoad(gBackgroundSoundFileName, value, _background_storage_requested, _background_loop_requested) != 0) { if (gGameSoundDebugEnabled) debugPrint(" background restart failed "); } } } // 0x450B50 void backgroundSoundPause() { if (gBackgroundSound != NULL) { soundPause(gBackgroundSound); } } // 0x450B64 void backgroundSoundResume() { if (gBackgroundSound != NULL) { soundResume(gBackgroundSound); } } // NOTE: Inlined. // // 0x450B78 void speechDisable() { if (gGameSoundInitialized) { if (gSpeechEnabled) { speechDelete(); gSpeechEnabled = false; } } } // NOTE: Inlined. // // 0x450BC0 void speechEnable() { if (gGameSoundInitialized) { if (!gSpeechEnabled) { gSpeechEnabled = true; } } } // 0x450BE0 int speechIsEnabled() { return gSpeechEnabled; } // 0x450BE8 void speechSetVolume(int volume) { if (!gGameSoundInitialized) { return; } if (volume < VOLUME_MIN || volume > VOLUME_MAX) { if (gGameSoundDebugEnabled) { debugPrint("Requested speech volume out of range.\n"); } return; } gSpeechVolume = volume; if (gSpeechEnabled) { if (gSpeechSound != NULL) { soundSetVolume(gSpeechSound, (int)(volume * 0.69)); } } } // 0x450C5C int speechGetVolume() { return gSpeechVolume; } // 0x450C64 int _gsound_speech_volume_get_set(int volume) { int oldVolume = gSpeechVolume; speechSetVolume(volume); return oldVolume; } // 0x450C74 void speechSetEndCallback(SoundEndCallback* callback) { gSpeechEndCallback = callback; } // 0x450C94 int speechGetDuration() { return soundGetDuration(gSpeechSound); } // 0x450CA0 int speechLoad(const char* fname, int a2, int a3, int a4) { char path[COMPAT_MAX_PATH + 1]; if (!gGameSoundInitialized) { return -1; } if (!gSpeechEnabled) { return -1; } if (gGameSoundDebugEnabled) { debugPrint("Loading speech sound file %s%s...", fname, ".ACM"); } // uninline speechDelete(); if (_gsound_background_allocate(&gSpeechSound, a3, a4)) { if (gGameSoundDebugEnabled) { debugPrint("failed because sound could not be allocated.\n"); } gSpeechSound = NULL; return -1; } if (soundSetFileIO(gSpeechSound, &audioOpen, &audioClose, &audioRead, NULL, &audioSeek, &gameSoundFileTellNotImplemented, &audioGetSize)) { if (gGameSoundDebugEnabled) { debugPrint("failed because file IO could not be set for compression.\n"); } soundDelete(gSpeechSound); gSpeechSound = NULL; return -1; } if (gameSoundFindSpeechSoundPath(path, fname)) { if (gGameSoundDebugEnabled) { debugPrint("failed because the file could not be found.\n"); } soundDelete(gSpeechSound); gSpeechSound = NULL; return -1; } if (a4 == 16) { if (soundSetLooping(gSpeechSound, 0xFFFF)) { if (gGameSoundDebugEnabled) { debugPrint("failed because looping could not be set.\n"); } soundDelete(gSpeechSound); gSpeechSound = NULL; return -1; } } if (soundSetCallback(gSpeechSound, speechCallback, NULL)) { if (gGameSoundDebugEnabled) { debugPrint("soundSetCallback failed for speech sound\n"); } } if (a2 == 11) { 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); gSpeechSound = NULL; return -1; } if (a2 != 11) { if (soundSetReadLimit(gSpeechSound, 0x40000)) { if (gGameSoundDebugEnabled) { debugPrint("unable to set read limit "); } } } if (a2 == 10) { return 0; } if (speechPlay()) { if (gGameSoundDebugEnabled) { debugPrint("failed starting to play.\n"); } soundDelete(gSpeechSound); gSpeechSound = NULL; return -1; } if (gGameSoundDebugEnabled) { debugPrint("succeeded.\n"); } return 0; } // 0x450F8C int _gsound_speech_play_preloaded() { if (!gGameSoundInitialized) { return -1; } if (!gSpeechEnabled) { return -1; } if (gSpeechSound == NULL) { return -1; } if (soundIsPlaying(gSpeechSound)) { return -1; } if (soundIsPaused(gSpeechSound)) { return -1; } if (_soundDone(gSpeechSound)) { return -1; } if (speechPlay() != 0) { soundDelete(gSpeechSound); gSpeechSound = NULL; return -1; } return 0; } // 0x451024 void speechDelete() { if (gGameSoundInitialized && gSpeechEnabled) { if (gSpeechSound != NULL) { soundDelete(gSpeechSound); gSpeechSound = NULL; } } } // 0x451054 void speechPause() { if (gSpeechSound != NULL) { soundPause(gSpeechSound); } } // 0x451068 void speechResume() { if (gSpeechSound != NULL) { soundResume(gSpeechSound); } } // 0x45108C int _gsound_play_sfx_file_volume(const char* a1, int a2) { Sound* v1; if (!gGameSoundInitialized) { return -1; } if (!gSoundEffectsEnabled) { return -1; } v1 = soundEffectLoadWithVolume(a1, NULL, a2); if (v1 == NULL) { return -1; } soundPlay(v1); return 0; } // 0x4510DC Sound* soundEffectLoad(const char* name, Object* object) { if (!gGameSoundInitialized) { return NULL; } if (!gSoundEffectsEnabled) { return NULL; } if (gGameSoundDebugEnabled) { debugPrint("Loading sound file %s%s...", name, ".ACM"); } if (_gsound_active_effect_counter >= SOUND_EFFECTS_MAX_COUNT) { if (gGameSoundDebugEnabled) { debugPrint("failed because there are already %d active effects.\n", _gsound_active_effect_counter); } return NULL; } Sound* sound = _gsound_get_sound_ready_for_effect(); if (sound == NULL) { if (gGameSoundDebugEnabled) { debugPrint("failed.\n"); } return NULL; } ++_gsound_active_effect_counter; char path[COMPAT_MAX_PATH]; snprintf(path, sizeof(path), "%s%s%s", _sound_sfx_path, name, ".ACM"); if (soundLoad(sound, path) == 0) { if (gGameSoundDebugEnabled) { debugPrint("succeeded.\n"); } return sound; } if (object != NULL) { if (FID_TYPE(object->fid) == OBJ_TYPE_CRITTER && (name[0] == 'H' || name[0] == 'N')) { char v9 = name[1]; if (v9 == 'A' || v9 == 'F' || v9 == 'M') { if (v9 == 'A') { if (critterGetStat(object, STAT_GENDER)) { v9 = 'F'; } else { v9 = 'M'; } } } snprintf(path, sizeof(path), "%sH%cXXXX%s%s", _sound_sfx_path, v9, name + 6, ".ACM"); if (gGameSoundDebugEnabled) { debugPrint("tyring %s ", path + strlen(_sound_sfx_path)); } if (soundLoad(sound, path) == 0) { if (gGameSoundDebugEnabled) { debugPrint("succeeded (with alias).\n"); } return sound; } if (v9 == 'F') { snprintf(path, sizeof(path), "%sHMXXXX%s%s", _sound_sfx_path, name + 6, ".ACM"); if (gGameSoundDebugEnabled) { debugPrint("tyring %s ", path + strlen(_sound_sfx_path)); } if (soundLoad(sound, path) == 0) { if (gGameSoundDebugEnabled) { debugPrint("succeeded (with male alias).\n"); } return sound; } } } } if (strncmp(name, "MALIEU", 6) == 0 || strncmp(name, "MAMTN2", 6) == 0) { snprintf(path, sizeof(path), "%sMAMTNT%s%s", _sound_sfx_path, name + 6, ".ACM"); if (gGameSoundDebugEnabled) { debugPrint("tyring %s ", path + strlen(_sound_sfx_path)); } if (soundLoad(sound, path) == 0) { if (gGameSoundDebugEnabled) { debugPrint("succeeded (with alias).\n"); } return sound; } } --_gsound_active_effect_counter; soundDelete(sound); if (gGameSoundDebugEnabled) { debugPrint("failed.\n"); } return NULL; } // 0x45145C Sound* soundEffectLoadWithVolume(const char* name, Object* object, int volume) { Sound* sound = soundEffectLoad(name, object); if (sound != NULL) { soundSetVolume(sound, (volume * gSoundEffectsVolume) / VOLUME_MAX); } return sound; } // 0x45148C void soundEffectDelete(Sound* sound) { if (!gGameSoundInitialized) { return; } if (!gSoundEffectsEnabled) { return; } if (soundIsPlaying(sound)) { if (gGameSoundDebugEnabled) { debugPrint("Trying to manually delete a sound effect after it has started playing.\n"); } return; } if (soundDelete(sound) != 0) { if (gGameSoundDebugEnabled) { debugPrint("Unable to delete sound effect -- active effect counter may get out of sync.\n"); } return; } --_gsound_active_effect_counter; } // 0x4514F0 int _gsnd_anim_sound(Sound* sound, void* a2) { if (!gGameSoundInitialized) { return 0; } if (!gSoundEffectsEnabled) { return 0; } if (sound == NULL) { return 0; } soundPlay(sound); return 0; } // 0x451510 int soundEffectPlay(Sound* sound) { if (!gGameSoundInitialized) { return -1; } if (!gSoundEffectsEnabled) { return -1; } if (sound == NULL) { return -1; } soundPlay(sound); return 0; } // Probably returns volume dependending on the distance between the specified // object and dude. // // 0x451534 int _gsound_compute_relative_volume(Object* obj) { int type; int v3; Object* v7; Rect v12; Rect v14; Rect iso_win_rect; int distance; int perception; v3 = 0x7FFF; if (obj) { type = FID_TYPE(obj->fid); if (type == 0 || type == 1 || type == 2) { v7 = objectGetOwner(obj); if (!v7) { v7 = obj; } objectGetRect(v7, &v14); windowGetRect(gIsoWindow, &iso_win_rect); if (rectIntersection(&v14, &iso_win_rect, &v12) == -1) { distance = objectGetDistanceBetween(v7, gDude); perception = critterGetStat(gDude, STAT_PERCEPTION); if (distance > perception) { if (distance < 2 * perception) { v3 = 0x7FFF - 0x5554 * (distance - perception) / perception; } else { v3 = 0x2AAA; } } else { v3 = 0x7FFF; } } } } return v3; } // sfx_build_char_name // 0x451604 char* sfxBuildCharName(Object* a1, int anim, int extra) { char v7[13]; char v8; char v9; if (artCopyFileName(FID_TYPE(a1->fid), a1->fid & 0xFFF, v7) == -1) { return NULL; } if (anim == ANIM_TAKE_OUT) { if (_art_get_code(anim, extra, &v8, &v9) == -1) { return NULL; } } else { if (_art_get_code(anim, (a1->fid & 0xF000) >> 12, &v8, &v9) == -1) { return NULL; } } // TODO: Check. if (anim == ANIM_FALL_FRONT || anim == ANIM_FALL_BACK) { if (extra == CHARACTER_SOUND_EFFECT_PASS_OUT) { v8 = 'Y'; } else if (extra == CHARACTER_SOUND_EFFECT_DIE) { v8 = 'Z'; } } else if ((anim == ANIM_THROW_PUNCH || anim == ANIM_KICK_LEG) && extra == CHARACTER_SOUND_EFFECT_CONTACT) { v8 = 'Z'; } snprintf(_sfx_file_name, sizeof(_sfx_file_name), "%s%c%c", v7, v8, v9); compat_strupr(_sfx_file_name); return _sfx_file_name; } // sfx_build_ambient_name // 0x4516F0 char* gameSoundBuildAmbientSoundEffectName(const char* a1) { snprintf(_sfx_file_name, sizeof(_sfx_file_name), "A%6s%1d", a1, 1); compat_strupr(_sfx_file_name); return _sfx_file_name; } // sfx_build_interface_name // 0x451718 char* gameSoundBuildInterfaceName(const char* a1) { snprintf(_sfx_file_name, sizeof(_sfx_file_name), "N%6s%1d", a1, 1); compat_strupr(_sfx_file_name); return _sfx_file_name; } // sfx_build_weapon_name // 0x451760 char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* target) { int v6; char weaponSoundCode; char effectTypeCode; char materialCode; Proto* proto; weaponSoundCode = weaponGetSoundId(weapon); effectTypeCode = _snd_lookup_weapon_type[effectType]; if (effectType != WEAPON_SOUND_EFFECT_READY && effectType != WEAPON_SOUND_EFFECT_OUT_OF_AMMO) { if (hitMode != HIT_MODE_LEFT_WEAPON_PRIMARY && hitMode != HIT_MODE_RIGHT_WEAPON_PRIMARY && hitMode != HIT_MODE_PUNCH) { v6 = 2; } else { v6 = 1; } } else { v6 = 1; } int damageType = weaponGetDamageType(NULL, weapon); // SFALL if (effectTypeCode != 'H' || target == NULL || damageType == explosionGetDamageType() || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP) { materialCode = 'X'; } else { const int type = FID_TYPE(target->fid); int material; switch (type) { case OBJ_TYPE_ITEM: protoGetProto(target->pid, &proto); material = proto->item.material; break; case OBJ_TYPE_SCENERY: protoGetProto(target->pid, &proto); material = proto->scenery.field_2C; break; case OBJ_TYPE_WALL: protoGetProto(target->pid, &proto); material = proto->wall.material; break; default: material = -1; break; } switch (material) { case MATERIAL_TYPE_GLASS: case MATERIAL_TYPE_METAL: case MATERIAL_TYPE_PLASTIC: materialCode = 'M'; break; case MATERIAL_TYPE_WOOD: materialCode = 'W'; break; case MATERIAL_TYPE_DIRT: case MATERIAL_TYPE_STONE: case MATERIAL_TYPE_CEMENT: materialCode = 'S'; break; default: materialCode = 'F'; break; } } snprintf(_sfx_file_name, sizeof(_sfx_file_name), "W%c%c%1d%cXX%1d", effectTypeCode, weaponSoundCode, v6, materialCode, 1); compat_strupr(_sfx_file_name); return _sfx_file_name; } // sfx_build_scenery_name // 0x451898 char* sfxBuildSceneryName(int actionType, int action, const char* name) { char actionTypeCode = actionType == SOUND_EFFECT_ACTION_TYPE_PASSIVE ? 'P' : 'A'; char actionCode = _snd_lookup_scenery_action[action]; snprintf(_sfx_file_name, sizeof(_sfx_file_name), "S%c%c%4s%1d", actionTypeCode, actionCode, name, 1); compat_strupr(_sfx_file_name); return _sfx_file_name; } // sfx_build_open_name // 0x4518D char* sfxBuildOpenName(Object* object, int action) { if (FID_TYPE(object->fid) == OBJ_TYPE_SCENERY) { char scenerySoundId; Proto* proto; if (protoGetProto(object->pid, &proto) != -1) { scenerySoundId = proto->scenery.field_34; } else { scenerySoundId = 'A'; } snprintf(_sfx_file_name, sizeof(_sfx_file_name), "S%cDOORS%c", _snd_lookup_scenery_action[action], scenerySoundId); } else { Proto* proto; protoGetProto(object->pid, &proto); snprintf(_sfx_file_name, sizeof(_sfx_file_name), "I%cCNTNR%c", _snd_lookup_scenery_action[action], proto->item.field_80); } compat_strupr(_sfx_file_name); return _sfx_file_name; } // 0x451970 void _gsound_red_butt_press(int btn, int keyCode) { soundPlayFile("ib1p1xx1"); } // 0x451978 void _gsound_red_butt_release(int btn, int keyCode) { soundPlayFile("ib1lu1x1"); } // 0x451980 void _gsound_toggle_butt_press_(int btn, int keyCode) { soundPlayFile("toggle"); } // 0x451988 void _gsound_med_butt_press(int btn, int keyCode) { soundPlayFile("ib2p1xx1"); } // 0x451990 void _gsound_med_butt_release(int btn, int keyCode) { soundPlayFile("ib2lu1x1"); } // 0x451998 void _gsound_lrg_butt_press(int btn, int keyCode) { soundPlayFile("ib3p1xx1"); } // 0x4519A0 void _gsound_lrg_butt_release(int btn, int keyCode) { soundPlayFile("ib3lu1x1"); } // 0x4519A8 int soundPlayFile(const char* name) { if (!gGameSoundInitialized) { return -1; } if (!gSoundEffectsEnabled) { return -1; } Sound* sound = soundEffectLoad(name, NULL); if (sound == NULL) { return -1; } soundPlay(sound); return 0; } // 0x451A00 void _gsound_bkg_proc() { soundContinueAll(); } // 0x451A08 int gameSoundFileOpen(const char* fname, int flags, ...) { if ((flags & 2) != 0) { return -1; } File* stream = fileOpen(fname, "rb"); if (stream == NULL) { return -1; } return ptrToInt(stream); } // NOTE: Collapsed. // // 0x451A1C long _gsound_write_() { return -1; } // NOTE: Uncollapsed 0x451A1C. // // The purpose of this function is unknown. It simply returns -1 without // actually telling position. This function is used for all game sounds - // background music, speech, and sound effects. There is another function // [gameSoundFileTell] which actually provides position. long gameSoundFileTellNotImplemented(int fileHandle) { return _gsound_write_(); } // NOTE: Uncollapsed 0x451A1C. int gameSoundFileWrite(int fileHandle, const void* buf, unsigned int size) { return _gsound_write_(); } // 0x451A24 int gameSoundFileClose(int fileHandle) { if (fileHandle == -1) { return -1; } return fileClose((File*)intToPtr(fileHandle, true)); } // 0x451A30 int gameSoundFileRead(int fileHandle, void* buffer, unsigned int size) { if (fileHandle == -1) { return -1; } return fileRead(buffer, 1, size, (File*)intToPtr(fileHandle)); } // 0x451A4C long gameSoundFileSeek(int fileHandle, long offset, int origin) { if (fileHandle == -1) { return -1; } if (fileSeek((File*)intToPtr(fileHandle), offset, origin) != 0) { return -1; } return fileTell((File*)intToPtr(fileHandle)); } // 0x451A70 long gameSoundFileTell(int handle) { if (handle == -1) { return -1; } return fileTell((File*)intToPtr(handle)); } // 0x451A7C long gameSoundFileGetSize(int handle) { if (handle == -1) { return -1; } return fileGetSize((File*)intToPtr(handle)); } // 0x451A88 bool gameSoundIsCompressed(char* filePath) { return true; } // 0x451A90 void speechCallback(void* userData, int a2) { if (a2 == 1) { gSpeechSound = NULL; if (gSpeechEndCallback) { gSpeechEndCallback(); } } } // 0x451AB0 void backgroundSoundCallback(void* userData, int a2) { if (a2 == 1) { gBackgroundSound = NULL; if (gBackgroundSoundEndCallback) { gBackgroundSoundEndCallback(); } } } // 0x451AD0 void soundEffectCallback(void* userData, int a2) { if (a2 == 1) { --_gsound_active_effect_counter; } } // 0x451ADC int _gsound_background_allocate(Sound** soundPtr, int a2, int a3) { int v5 = 10; int v6 = 0; if (a2 == 13) { v6 |= 0x01; } else if (a2 == 14) { v6 |= 0x02; } if (a3 == 15) { v6 |= 0x04; } else if (a3 == 16) { v5 = 42; } Sound* sound = soundAllocate(v6, v5); if (sound == NULL) { return -1; } *soundPtr = sound; return 0; } // gsound_background_find_with_copy // 0x451B30 int gameSoundFindBackgroundSoundPathWithCopy(char* dest, const char* src) { size_t len = strlen(src) + strlen(".ACM"); if (strlen(_sound_music_path1) + len > COMPAT_MAX_PATH || strlen(_sound_music_path2) + len > COMPAT_MAX_PATH) { if (gGameSoundDebugEnabled) { debugPrint("Full background path too long.\n"); } return -1; } if (gGameSoundDebugEnabled) { debugPrint(" finding background sound "); } char outPath[COMPAT_MAX_PATH]; snprintf(outPath, sizeof(outPath), "%s%s%s", _sound_music_path1, src, ".ACM"); if (_gsound_file_exists_f(outPath)) { strncpy(dest, outPath, COMPAT_MAX_PATH); dest[COMPAT_MAX_PATH] = '\0'; return 0; } if (gGameSoundDebugEnabled) { debugPrint("by copy "); } gameSoundDeleteOldMusicFile(); char inPath[COMPAT_MAX_PATH]; snprintf(inPath, sizeof(inPath), "%s%s%s", _sound_music_path2, src, ".ACM"); FILE* inStream = compat_fopen(inPath, "rb"); if (inStream == NULL) { if (gGameSoundDebugEnabled) { debugPrint("Unable to find music file %s to copy down.\n", src); } return -1; } FILE* outStream = compat_fopen(outPath, "wb"); if (outStream == NULL) { if (gGameSoundDebugEnabled) { debugPrint("Unable to open music file %s for copying to.", src); } fclose(inStream); return -1; } void* buffer = internal_malloc(0x2000); if (buffer == NULL) { if (gGameSoundDebugEnabled) { debugPrint("Out of memory in gsound_background_find_with_copy.\n", src); } fclose(outStream); fclose(inStream); return -1; } bool err = false; while (!feof(inStream)) { size_t bytesRead = fread(buffer, 1, 0x2000, inStream); if (bytesRead == 0) { break; } if (fwrite(buffer, 1, bytesRead, outStream) != bytesRead) { err = true; break; } } internal_free(buffer); fclose(outStream); fclose(inStream); if (err) { if (gGameSoundDebugEnabled) { debugPrint("Background sound file copy failed on write -- "); debugPrint("likely out of disc space.\n"); } return -1; } strcpy(_background_fname_copied, src); strncpy(dest, outPath, COMPAT_MAX_PATH); dest[COMPAT_MAX_PATH] = '\0'; return 0; } // 0x451E2C int gameSoundFindBackgroundSoundPath(char* dest, const char* src) { char path[COMPAT_MAX_PATH]; size_t len; len = strlen(src) + strlen(".ACM"); if (strlen(_sound_music_path1) + len > COMPAT_MAX_PATH || strlen(_sound_music_path2) + len > COMPAT_MAX_PATH) { if (gGameSoundDebugEnabled) { debugPrint("Full background path too long.\n"); } return -1; } if (gGameSoundDebugEnabled) { debugPrint(" finding background sound "); } snprintf(path, sizeof(path), "%s%s%s", _sound_music_path1, src, ".ACM"); if (_gsound_file_exists_f(path)) { strncpy(dest, path, COMPAT_MAX_PATH); dest[COMPAT_MAX_PATH] = '\0'; return 0; } if (gGameSoundDebugEnabled) { debugPrint("in 2nd path "); } snprintf(path, sizeof(path), "%s%s%s", _sound_music_path2, src, ".ACM"); if (_gsound_file_exists_f(path)) { strncpy(dest, path, COMPAT_MAX_PATH); dest[COMPAT_MAX_PATH] = '\0'; return 0; } if (gGameSoundDebugEnabled) { debugPrint("-- find failed "); } return -1; } // 0x451F94 int gameSoundFindSpeechSoundPath(char* dest, const char* src) { char path[COMPAT_MAX_PATH]; if (strlen(_sound_speech_path) + strlen(".acm") > COMPAT_MAX_PATH) { if (gGameSoundDebugEnabled) { // FIXME: The message is wrong (notes background path, but here // we're dealing with speech path). debugPrint("Full background path too long.\n"); } return -1; } if (gGameSoundDebugEnabled) { debugPrint(" finding speech sound "); } snprintf(path, sizeof(path), "%s%s%s", _sound_speech_path, src, ".ACM"); // Check for existence by getting file size. int fileSize; if (dbGetFileSize(path, &fileSize) != 0) { if (gGameSoundDebugEnabled) { debugPrint("-- find failed "); } return -1; } strncpy(dest, path, COMPAT_MAX_PATH); dest[COMPAT_MAX_PATH] = '\0'; return 0; } // delete old music file // 0x452088 void gameSoundDeleteOldMusicFile() { if (_background_fname_copied[0] != '\0') { char path[COMPAT_MAX_PATH]; snprintf(path, sizeof(path), "%s%s%s", "sound\\music\\", _background_fname_copied, ".ACM"); if (compat_remove(path)) { if (gGameSoundDebugEnabled) { debugPrint("Deleting old music file failed.\n"); } } _background_fname_copied[0] = '\0'; } } // 0x4520EC int backgroundSoundPlay() { int result; if (gGameSoundDebugEnabled) { debugPrint(" playing "); } if (_gsound_background_fade) { soundSetVolume(gBackgroundSound, 1); result = _soundFade(gBackgroundSound, 2000, (int)(gMusicVolume * 0.94)); } else { soundSetVolume(gBackgroundSound, (int)(gMusicVolume * 0.94)); result = soundPlay(gBackgroundSound); } if (result != 0) { if (gGameSoundDebugEnabled) { debugPrint("Unable to play background sound.\n"); } result = -1; } return result; } // 0x45219C int speechPlay() { if (gGameSoundDebugEnabled) { debugPrint(" playing "); } soundSetVolume(gSpeechSound, (int)(gSpeechVolume * 0.69)); if (soundPlay(gSpeechSound) != 0) { if (gGameSoundDebugEnabled) { debugPrint("Unable to play speech sound.\n"); } return -1; } return 0; } // TODO: Refactor to use Settings. // // 0x452208 int _gsound_get_music_path(char** out_value, const char* key) { size_t len; char* copy; char* value; configGetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, key, out_value); value = *out_value; len = strlen(value); if (value[len - 1] == '\\' || value[len - 1] == '/') { return 0; } copy = (char*)internal_malloc(len + 2); if (copy == NULL) { if (gGameSoundDebugEnabled) { debugPrint("Out of memory in gsound_get_music_path.\n"); } return -1; } strcpy(copy, value); copy[len] = '\\'; copy[len + 1] = '\0'; if (configSetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, key, copy) != 1) { if (gGameSoundDebugEnabled) { debugPrint("config_set_string failed in gsound_music_path.\n"); } return -1; } if (configGetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, key, out_value)) { internal_free(copy); return 0; } if (gGameSoundDebugEnabled) { debugPrint("config_get_string failed in gsound_music_path.\n"); } return -1; } // 0x452378 Sound* _gsound_get_sound_ready_for_effect() { int rc; Sound* sound = soundAllocate(5, 10); if (sound == NULL) { if (gGameSoundDebugEnabled) { debugPrint(" Can't allocate sound for effect. "); } if (gGameSoundDebugEnabled) { debugPrint("soundAllocate returned: %d, %s\n", 0, soundGetErrorDescription(0)); } return NULL; } if (soundEffectsCacheInitialized()) { rc = soundSetFileIO(sound, soundEffectsCacheFileOpen, soundEffectsCacheFileClose, soundEffectsCacheFileRead, soundEffectsCacheFileWrite, soundEffectsCacheFileSeek, soundEffectsCacheFileTell, soundEffectsCacheFileLength); } else { rc = soundSetFileIO(sound, audioOpen, audioClose, audioRead, NULL, audioSeek, gameSoundFileTellNotImplemented, audioGetSize); } if (rc != 0) { if (gGameSoundDebugEnabled) { debugPrint("Can't set file IO on sound effect.\n"); } if (gGameSoundDebugEnabled) { debugPrint("soundSetFileIO returned: %d, %s\n", rc, soundGetErrorDescription(rc)); } soundDelete(sound); return NULL; } rc = soundSetCallback(sound, soundEffectCallback, NULL); if (rc != 0) { if (gGameSoundDebugEnabled) { debugPrint("failed because the callback could not be set.\n"); } if (gGameSoundDebugEnabled) { debugPrint("soundSetCallback returned: %d, %s\n", rc, soundGetErrorDescription(rc)); } soundDelete(sound); return NULL; } soundSetVolume(sound, gSoundEffectsVolume); return sound; } // Check file for existence. // // 0x4524E0 bool _gsound_file_exists_f(const char* fname) { FILE* f = compat_fopen(fname, "rb"); if (f == NULL) { return false; } fclose(f); return true; } // gsound_setup_paths // 0x452518 int _gsound_setup_paths() { // TODO: Incomplete. return 0; } // 0x452628 int _gsound_sfx_q_start() { return ambientSoundEffectEventProcess(0, NULL); } // 0x452634 int ambientSoundEffectEventProcess(Object* a1, void* data) { _queue_clear_type(EVENT_TYPE_GSOUND_SFX_EVENT, NULL); AmbientSoundEffectEvent* soundEffectEvent = (AmbientSoundEffectEvent*)data; int ambientSoundEffectIndex = -1; if (soundEffectEvent != NULL) { ambientSoundEffectIndex = soundEffectEvent->ambientSoundEffectIndex; } else { if (wmSfxMaxCount() > 0) { ambientSoundEffectIndex = wmSfxRollNextIdx(); } } AmbientSoundEffectEvent* nextSoundEffectEvent = (AmbientSoundEffectEvent*)internal_malloc(sizeof(*nextSoundEffectEvent)); if (nextSoundEffectEvent == NULL) { return -1; } if (gMapHeader.name[0] == '\0') { return 0; } int delay = 10 * randomBetween(15, 20); if (wmSfxMaxCount() > 0) { nextSoundEffectEvent->ambientSoundEffectIndex = wmSfxRollNextIdx(); if (queueAddEvent(delay, NULL, nextSoundEffectEvent, EVENT_TYPE_GSOUND_SFX_EVENT) == -1) { return -1; } } if (isInCombat()) { ambientSoundEffectIndex = -1; } if (ambientSoundEffectIndex != -1) { char* fileName; if (wmSfxIdxName(ambientSoundEffectIndex, &fileName) == 0) { int v7 = _get_bk_time(); if (getTicksBetween(v7, _lastTime_1) >= 5000) { if (soundPlayFile(fileName) == -1) { debugPrint("\nGsound: playing ambient map sfx: %s. FAILED", fileName); } else { debugPrint("\nGsound: playing ambient map sfx: %s", fileName); } } _lastTime_1 = v7; } } return 0; } } // namespace fallout