mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46376960d9 | ||
|
|
8b2ead8a2b | ||
|
|
4f89bb7d77 | ||
|
|
0d8741e082 | ||
|
|
6ce6bea309 | ||
|
|
6027c7cf2e |
@@ -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"
|
||||
|
||||
@@ -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 | - |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]];
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user