Compare commits

..
Author SHA1 Message Date
Mike Klaas 80bdd7fdb0 remove isIniatlized() 2026-05-29 18:51:53 -07:00
Mike Klaas 4eb9e869df outdent one more place 2026-05-27 08:34:15 -07:00
Mike Klaas 49a24f6925 Use ScopeConfig for function-scoped Config uses
Eliminates the free, which sometimes required gotos.

Left configRead in a couple places that return different values if the file is missing (0) vs hard error (-1). With more careful analysis it's possible we could fold the read into the ScopeConfig constructor in these cases too.

Also, doing this makes it feel like a ScopeConfig::get(section, value, &out) function would feel nice.
2026-05-27 08:19:52 -07:00
83 changed files with 1473 additions and 7892 deletions
-6
View File
@@ -614,10 +614,6 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
target_sources(mapper-ce PUBLIC
${FALLOUT_ENGINE_SOURCES}
${FALLOUT_PLATFORM_SOURCES}
"src/proto_txt.cc"
"src/proto_txt.h"
"src/mapper/map_edge_setup.cc"
"src/mapper/map_edge_setup.h"
"src/mapper/map_func.cc"
"src/mapper/map_func.h"
"src/mapper/mapper.cc"
@@ -630,8 +626,6 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
"src/mapper/mp_scrpt.h"
"src/mapper/mp_targt.cc"
"src/mapper/mp_targt.h"
"src/mapper/mp_utils.cc"
"src/mapper/mp_utils.h"
"src/mapper/mp_text.cc"
"src/mapper/mp_text.h"
)
+5 -13
View File
@@ -76,7 +76,7 @@ For example:
## `ce-frm2png` CLI Tool
There is also a small FRM conversion tool in this repo for converting Fallout art between `.frm` and `.png`.
There is also a small FRM conversion tool in this repo for exporting Fallout `.frm` art to `.png`.
From the repo root, build it with your normal CMake workflow, targeting `ce-frm2png`:
@@ -89,26 +89,18 @@ Basic usage:
1. `./<BUILD_DIR>/ce-frm2png <input.frm> [output.png]`
2. `./<BUILD_DIR>/ce-frm2png <input.frm> [output.png] --palette <path-to-color.pal>`
3. `./<BUILD_DIR>/ce-frm2png <input.frm> [output.png] --frame <index> --direction <index>`
4. `./<BUILD_DIR>/ce-frm2png <input.png> [output.frm] --palette <path-to-color.pal>`
5. `./<BUILD_DIR>/ce-frm2png <input.png> [output.frm] --fps 10 --action-frame 0 --x-offset 0 --y-offset 0`
6. `./<BUILD_DIR>/ce-frm2png <input.png> <output.png> --palette <path-to-color.pal>`
7. `./<BUILD_DIR>/ce-frm2png - [output.png|-] --from-frm --palette <path-to-color.pal>`
8. `./<BUILD_DIR>/ce-frm2png - [output.frm|-] --from-png --palette <path-to-color.pal>`
4. `./<BUILD_DIR>/ce-frm2png - [output.png|-] --palette <path-to-color.pal>`
`-` can be used to take input from stdin (e.g. from `ce-dat-tool cat`) or write to stdout. When reading from stdin, pass `--from-frm` or `--from-png`. Output defaults to PNG for FRM input and FRM for PNG input when output is omitted or `-`; use `--to-png` for PNG output to stdout from PNG input.
`-` can be used to take input from stdin (e.g. from `ce-dat-tool cat` or write to stdout).
Output file extensions are validated against the selected conversion. For example, PNG-to-FRM output must use `.frm`, and PNG output must use `.png`.
If `--palette` isn't specified, it will pick up `color.pal` in the input file's directory or cwd.
If `--palette` isn't specified, it will pick up `color.pal` in the .frm's directory or cwd.
Notes:
- The tool expects a raw `color.pal` file. It does not read palettes directly from `master.dat`, so extract `color.pal` first if needed.
- A typical extraction flow is: `ce-dat-tool master.dat extract --lower . 'color.pal'`
- By default palette index `0` becomes transparent in the output PNG. Pass `--opaque` to keep it opaque.
- PNG output is always palette-indexed and preserves Fallout palette indices when possible.
- For `png -> frm`, transparent pixels (alpha below 128) map to palette index `0` by default. Pass `--opaque` to quantize every pixel instead.
- The current `png -> frm` implementation writes a single-frame FRM and points all six direction slots at the same frame payload.
- The current implementation writes RGBA PNGs. It does not preserve the original Fallout palette indices as a palette-indexed PNG.
## Updating SDL
-9
View File
@@ -153,7 +153,6 @@ In time this stuff will receive in-game interface, right now you have to do it m
* Auto open doors
* 44.1 kHz stereo sound/music supported, in .ogg and .wav format as well as legacy .acm
* Last used save slot is remembered
* Item/Corpse/Container/Critter highlighting (configure using [mods/sfall-mods.ini](https://github.com/sfall-team/sfall/blob/master/artifacts/config_files/sfall-mods.ini))
## Contributing
@@ -163,14 +162,6 @@ Integrating Sfall goodies is the top priority. Quality of life updates are OK to
For current sfall compatibility status and the remaining work needed to close gaps, see [SFALL_COMPATIBILITY.md](SFALL_COMPATIBILITY.md).
## For modders
* Robust (though not 100%) Sfall opcode and hook support
* .ogg/.wav support for map music
* .png support for static assets (must be 8bit indexed)
* .zip support for .dat archives
* Working BIS mapper
## License
The source code is this repository is available under the [Sustainable Use License](LICENSE.md).
+1 -1
View File
@@ -73,7 +73,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| 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 | âś… except block_combat, force_aimed_shots, disable_aimed_shots, get_last_target, get_last_attacker, set_spray_settings | - |
| 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 | - |
| Car | set_car_current_town<br>car_gas_amount<br>set_car_intface_art | implemented: all except set_car_intface_art | - |
| Interface / Windows and images | art_frame_data<br>interface_art_draw<br>interface_print<br>draw_image<br>draw_image_scaled<br>get_window_under_mouse<br>create_win<br>get_window_attribute<br>message_box<br>set_window_flag<br>win_fill_color<br>interface_overlay<br>dialog_message<br>get_text_width<br>hide_window<br>show_window<br>create_message_window | implemented: only message_box, get_text_width, show_window, create_message_window | - |
| Interface / Outline | outlined_object<br>get_outline<br>set_outline | âś… | - |
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
+2 -12
View File
@@ -119,25 +119,15 @@ numbers_in_dialogue=0
;Set to 2 to also skip the splash screen
skip_opening_movies=0
splash_screen_size=1
;Set to 1 to scale movies to fit the screen while preserving aspect ratio.
;Set to 0 to keep movies at their native size, centered on screen.
movie_aspect_fit=1
;Maximum number of columns shown in the main inventory and loot/steal windows (valid range: 1..2)
;2-column Loot/steal window requires minimum screen width of 673px
inventory_columns=2
;Set to 0 to keep the main menu art and controls at native size.
;Set to 1 to scale only the main menu background image to fit the screen while maintaining aspect ratio.
;Set to 2 to also scale the main menu button panel, red buttons, and button labels with the menu art.
main_menu_scale_mode=1
[qol]
; Automatically open doors that are unlocked and has no script attached.
auto_open_doors=0
; Overrides distance at which walk animation is used when using an object. 0 disables walking
; This is the number of empty tiles between the player and the target
use_walk_distance=3
; Overrides distance at which walk animation is used when using an object.
use_walk_distance=5
; Allow opening party member inventory from the long-press world menu.
party_trade_from_menu=1
; Allow switching to companions' inventories in loot screens (and barter, in the future)
@@ -60,14 +60,12 @@ procedure start begin
display_msg("Testing combat/inventory/object helpers...");
call assertEquals("metarule attack_is_aimed", metarule_exist("attack_is_aimed"), 1);
call assertEquals("metarule get_combat_free_move", metarule_exist("get_combat_free_move"), 1);
call assertEquals("metarule set_combat_free_move", metarule_exist("set_combat_free_move"), 1);
call assertEquals("metarule item_weight", metarule_exist("item_weight"), 1);
call assertEquals("metarule obj_is_openable", metarule_exist("obj_is_openable"), 1);
call assertEquals("opcode get_critter_current_ap", opcode_exists(OP_GET_CRITTER_CURRENT_AP), 1);
call assertEquals("opcode set_critter_current_ap", opcode_exists(OP_SET_CRITTER_CURRENT_AP), 1);
call assertEquals("attack_is_aimed defaults to false", attack_is_aimed, 0);
testItem := create_object_sid(TEST_ITEM_PID, 0, 0, -1);
call assertEquals("item_weight matches proto weight for test item", item_weight(testItem), obj_weight(testItem));
-2
View File
@@ -1043,8 +1043,6 @@ int _action_climb_ladder(Object* critter, Object* ladder)
animationRequestOptions |= ANIMATION_REQUEST_NO_STAND;
reg_anim_begin(animationRequestOptions);
// note: distance is checked to ladder tile, but movement is to SE tile, so the walk/run choice
// could be slightly off
int tile = tileGetTileInDirection(ladder->tile, ROTATION_SE, 1);
if (actionPoints != -1 || objectWithinWalkDistance(critter, ladder)) {
animationRegisterMoveToTile(critter, tile, ladder->elevation, actionPoints, 0);
+9 -196
View File
@@ -1,9 +1,6 @@
#include "art.h"
#include <lodepng.h>
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -137,7 +134,6 @@ static int* gArtCritterFidShoudRunData;
static std::unordered_map<std::string, std::shared_ptr<NamedCacheEntry>> gNamedArtCache;
constexpr int kNamedCacheMaxBytes = 32 * 1024 * 1024; // 32MB soft limit
constexpr size_t kMaxNamedPngPixels = 16 * 1024 * 1024;
static unsigned int gNamedArtCacheMruCounter = 0;
static int gNamedArtCacheCurrentBytes = 0;
@@ -367,12 +363,6 @@ void artToggleObjectTypeHidden(int objectType)
}
}
// art_total
int art_total(int objectType)
{
return objectType >= 0 && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].fileNamesLength : 0;
}
// 0x418F7C
int artGetFidgetCount(int headFid)
{
@@ -1126,7 +1116,7 @@ static int artReadFrameData(unsigned char* data, File* stream, int count, int* p
// 0x419E1C
static int artReadHeader(Art* art, File* stream)
{
if (fileReadInt32(stream, &(art->version)) == -1) return -1;
if (fileReadInt32(stream, &(art->field_0)) == -1) return -1;
if (fileReadInt16(stream, &(art->framesPerSecond)) == -1) return -1;
if (fileReadInt16(stream, &(art->actionFrame)) == -1) return -1;
if (fileReadInt16(stream, &(art->frameCount)) == -1) return -1;
@@ -1143,166 +1133,13 @@ static int artReadHeader(Art* art, File* stream)
return 0;
}
static bool artPathHasExtension(const char* path, const char* extension)
{
size_t pathLength = strlen(path);
size_t extensionLength = strlen(extension);
if (pathLength < extensionLength) {
return false;
}
return compat_stricmp(path + pathLength - extensionLength, extension) == 0;
}
static bool artReadFile(const char* path, std::vector<unsigned char>& data)
{
int size = 0;
if (dbGetFileSize(path, &size) != 0 || size <= 0) {
return false;
}
data.resize(size);
return dbGetFileContents(path, data.data()) == 0;
}
static bool artUnpackIndexedPngPixels(const std::vector<unsigned char>& indexedData, unsigned width, unsigned height, unsigned bitdepth, unsigned char* output)
{
if (bitdepth != 8) {
return false;
}
size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
if (indexedData.size() < pixelCount) {
return false;
}
memcpy(output, indexedData.data(), pixelCount);
return true;
}
static bool artValidateIndexedPngHeader(const char* path, unsigned width, unsigned height, const LodePNGColorMode& color)
{
if (color.colortype != LCT_PALETTE) {
debugPrint("ART: PNG is not palette-indexed: %s\n", path);
return false;
}
if (color.bitdepth != 8) {
debugPrint("ART: indexed PNG bit depth must be 8: %s\n", path);
return false;
}
if (width == 0 || height == 0 || width > SHRT_MAX || height > SHRT_MAX) {
debugPrint("ART: invalid indexed PNG dimensions for %s: %ux%u\n", path, width, height);
return false;
}
size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
if (pixelCount > kMaxNamedPngPixels || pixelCount > INT_MAX) {
debugPrint("ART: indexed PNG is too large: %s\n", path);
return false;
}
return true;
}
static bool artIndexedPngHasSupportedTransparency(const LodePNGColorMode& color)
{
if (!lodepng_has_palette_alpha(&color)) {
return true;
}
for (size_t index = 0; index < color.palettesize; index++) {
unsigned char alpha = color.palette[index * 4 + 3];
if (index == 0) {
// palette index 0 is allowed to be either fully transparent or fully opaque
if (alpha != 0 && alpha != 255) {
return false;
}
} else if (alpha != 255) {
return false;
}
}
return true;
}
static Art* artLoadIndexedPng(const char* path)
{
std::vector<unsigned char> encoded;
if (!artReadFile(path, encoded)) {
return nullptr;
}
lodepng::State state;
state.decoder.color_convert = 0;
unsigned width = 0;
unsigned height = 0;
unsigned error = lodepng_inspect(&width, &height, &state, encoded.data(), encoded.size());
if (error != 0) {
debugPrint("ART: failed to inspect indexed PNG %s: %s\n", path, lodepng_error_text(error));
return nullptr;
}
if (!artValidateIndexedPngHeader(path, width, height, state.info_png.color)) {
return nullptr;
}
std::vector<unsigned char> indexedData;
error = lodepng::decode(indexedData, width, height, state, encoded);
if (error != 0) {
debugPrint("ART: failed to decode indexed PNG %s: %s\n", path, lodepng_error_text(error));
return nullptr;
}
if (!artIndexedPngHasSupportedTransparency(state.info_png.color)) {
debugPrint("ART: indexed PNG transparency is unsupported, reserve palette index 0 instead: %s\n", path);
return nullptr;
}
size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
Art header = {};
header.version = 4;
header.framesPerSecond = 10;
header.actionFrame = 0;
header.frameCount = 1;
header.dataSize = sizeof(ArtFrame) + static_cast<int>(pixelCount);
int currentPadding = paddingForSize(sizeof(Art));
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
header.dataOffsets[rotation] = 0;
header.padding[rotation] = currentPadding;
}
int dataSize = artGetDataSize(&header);
unsigned char* data = reinterpret_cast<unsigned char*>(internal_malloc(dataSize));
if (data == nullptr) {
return nullptr;
}
memset(data, 0, dataSize);
Art* art = reinterpret_cast<Art*>(data);
*art = header;
ArtFrame* frame = reinterpret_cast<ArtFrame*>(data + sizeof(Art) + art->padding[0]);
frame->width = static_cast<short>(width);
frame->height = static_cast<short>(height);
frame->size = static_cast<int>(pixelCount);
frame->x = 0;
frame->y = 0;
if (!artUnpackIndexedPngPixels(indexedData, width, height, state.info_png.color.bitdepth, reinterpret_cast<unsigned char*>(frame) + sizeof(ArtFrame))) {
debugPrint("ART: failed to read indexed PNG pixels: %s\n", path);
internal_free(data);
return nullptr;
}
return art;
}
static Art* artLoadFrm(const char* path)
// NOTE: Original function was slightly different, but never used. Basically
// it's a memory allocating variant of `artRead` (which reads data into given
// buffer). This function is useful to load custom `frm` files since `Art` now
// needs more memory then it's on-disk size (due to memory padding).
//
// 0x419EC0
Art* artLoad(const char* path)
{
File* stream = fileOpen(path, "rb");
if (stream == nullptr) {
@@ -1330,30 +1167,6 @@ static Art* artLoadFrm(const char* path)
return reinterpret_cast<Art*>(data);
}
// NOTE: Original function was slightly different, but never used. Basically
// it's a memory allocating variant of `artRead` (which reads data into given
// buffer). This function is useful to load custom `frm` files since `Art` now
// needs more memory then it's on-disk size (due to memory padding).
//
// 0x419EC0
Art* artLoad(const char* path)
{
if (path == nullptr) {
return nullptr;
}
if (artPathHasExtension(path, ".png")) {
return artLoadIndexedPng(path);
}
Art* art = artLoadFrm(path);
if (art != nullptr) {
return art;
}
return nullptr;
}
static Art* artLoadLocalized(const char* path)
{
const char* localizedPath;
@@ -1426,7 +1239,7 @@ int artWriteFrameData(unsigned char* data, File* stream, int count)
// 0x41A138
int artWriteHeader(Art* art, File* stream)
{
if (fileWriteInt32(stream, art->version) == -1) return -1;
if (fileWriteInt32(stream, art->field_0) == -1) return -1;
if (fileWriteInt16(stream, art->framesPerSecond) == -1) return -1;
if (fileWriteInt16(stream, art->actionFrame) == -1) return -1;
if (fileWriteInt16(stream, art->frameCount) == -1) return -1;
+1 -2
View File
@@ -69,7 +69,7 @@ typedef enum Background {
} Background;
typedef struct Art {
int version;
int field_0;
short framesPerSecond;
short actionFrame;
short frameCount;
@@ -123,7 +123,6 @@ void artExit();
char* artGetObjectTypeName(int objectType);
int artIsObjectTypeHidden(int objectType);
void artToggleObjectTypeHidden(int objectType);
int art_total(int objectType);
int artGetFidgetCount(int headFid);
void artRender(int fid, unsigned char* dest, int width, int height, int pitch);
int art_list_str(int fid, char* name);
+78 -88
View File
@@ -6254,79 +6254,75 @@ static void criticalsInit()
}
if (mode == 1 || mode == 3) {
Config criticalsConfig;
if (configInit(&criticalsConfig)) {
char* criticalsConfigFilePath;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_OVERRIDE_CRITICALS_FILE_KEY, &criticalsConfigFilePath);
if (criticalsConfigFilePath != nullptr && *criticalsConfigFilePath == '\0') {
criticalsConfigFilePath = nullptr;
}
char* criticalsConfigFilePath;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_OVERRIDE_CRITICALS_FILE_KEY, &criticalsConfigFilePath);
if (criticalsConfigFilePath != nullptr && *criticalsConfigFilePath == '\0') {
criticalsConfigFilePath = nullptr;
}
if (configRead(&criticalsConfig, criticalsConfigFilePath, false)) {
if (mode == 1) {
char sectionKey[16];
ScopedConfig criticalsConfig(criticalsConfigFilePath, false);
if (criticalsConfig) {
if (mode == 1) {
char sectionKey[16];
// Read original kill types (19) plus one for the player.
for (int killType = 0; killType < KILL_TYPE_COUNT + 1; killType++) {
for (int hitLocation = 0; hitLocation < HIT_LOCATION_COUNT; hitLocation++) {
for (int effect = 0; effect < CRTICIAL_EFFECT_COUNT; effect++) {
snprintf(sectionKey, sizeof(sectionKey), "c_%02d_%d_%d", killType, hitLocation, effect);
// Read original kill types (19) plus one for the player.
for (int killType = 0; killType < KILL_TYPE_COUNT + 1; killType++) {
for (int hitLocation = 0; hitLocation < HIT_LOCATION_COUNT; hitLocation++) {
for (int effect = 0; effect < CRTICIAL_EFFECT_COUNT; effect++) {
snprintf(sectionKey, sizeof(sectionKey), "c_%02d_%d_%d", killType, hitLocation, effect);
// Update player kill type if needed.
int newKillType = killType == KILL_TYPE_COUNT ? SFALL_KILL_TYPE_COUNT : killType;
for (int dataMember = 0; dataMember < CRIT_DATA_MEMBER_COUNT; dataMember++) {
int value = criticalsGetValue(newKillType, hitLocation, effect, dataMember);
if (configGetInt(&criticalsConfig, sectionKey, gCritDataMemberKeys[dataMember], &value)) {
criticalsSetValue(newKillType, hitLocation, effect, dataMember, value);
}
// Update player kill type if needed.
int newKillType = killType == KILL_TYPE_COUNT ? SFALL_KILL_TYPE_COUNT : killType;
for (int dataMember = 0; dataMember < CRIT_DATA_MEMBER_COUNT; dataMember++) {
int value = criticalsGetValue(newKillType, hitLocation, effect, dataMember);
if (configGetInt(criticalsConfig.get(), sectionKey, gCritDataMemberKeys[dataMember], &value)) {
criticalsSetValue(newKillType, hitLocation, effect, dataMember, value);
}
}
}
}
} else if (mode == 3) {
char ktSectionKey[32];
char hitLocationSectionKey[32];
char key[32];
}
} else if (mode == 3) {
char ktSectionKey[32];
char hitLocationSectionKey[32];
char key[32];
// Read Sfall kill types (38) plus one for the player.
for (int killType = 0; killType < SFALL_KILL_TYPE_COUNT + 1; killType++) {
snprintf(ktSectionKey, sizeof(ktSectionKey), "c_%02d", killType);
// Read Sfall kill types (38) plus one for the player.
for (int killType = 0; killType < SFALL_KILL_TYPE_COUNT + 1; killType++) {
snprintf(ktSectionKey, sizeof(ktSectionKey), "c_%02d", killType);
int enabled = 0;
configGetInt(&criticalsConfig, ktSectionKey, "Enabled", &enabled);
if (enabled == 0) {
continue;
int enabled = 0;
configGetInt(criticalsConfig.get(), ktSectionKey, "Enabled", &enabled);
if (enabled == 0) {
continue;
}
for (int hitLocation = 0; hitLocation < HIT_LOCATION_COUNT; hitLocation++) {
if (enabled < 2) {
bool hitLocationChanged = false;
snprintf(key, sizeof(key), "Part_%d", hitLocation);
configGetBool(criticalsConfig.get(), ktSectionKey, key, &hitLocationChanged);
if (!hitLocationChanged) {
continue;
}
}
for (int hitLocation = 0; hitLocation < HIT_LOCATION_COUNT; hitLocation++) {
if (enabled < 2) {
bool hitLocationChanged = false;
snprintf(hitLocationSectionKey, sizeof(hitLocationSectionKey), "c_%02d_%d", killType, hitLocation);
snprintf(key, sizeof(key), "Part_%d", hitLocation);
configGetBool(&criticalsConfig, ktSectionKey, key, &hitLocationChanged);
if (!hitLocationChanged) {
continue;
}
}
snprintf(hitLocationSectionKey, sizeof(hitLocationSectionKey), "c_%02d_%d", killType, hitLocation);
for (int effect = 0; effect < CRTICIAL_EFFECT_COUNT; effect++) {
for (int dataMember = 0; dataMember < CRIT_DATA_MEMBER_COUNT; dataMember++) {
int value = criticalsGetValue(killType, hitLocation, effect, dataMember);
snprintf(key, sizeof(key), "e%d_%s", effect, gCritDataMemberKeys[dataMember]);
if (configGetInt(&criticalsConfig, hitLocationSectionKey, key, &value)) {
criticalsSetValue(killType, hitLocation, effect, dataMember, value);
}
for (int effect = 0; effect < CRTICIAL_EFFECT_COUNT; effect++) {
for (int dataMember = 0; dataMember < CRIT_DATA_MEMBER_COUNT; dataMember++) {
int value = criticalsGetValue(killType, hitLocation, effect, dataMember);
snprintf(key, sizeof(key), "e%d_%s", effect, gCritDataMemberKeys[dataMember]);
if (configGetInt(criticalsConfig.get(), hitLocationSectionKey, key, &value)) {
criticalsSetValue(killType, hitLocation, effect, dataMember, value);
}
}
}
}
}
}
configFree(&criticalsConfig);
}
}
@@ -6600,46 +6596,40 @@ static void unarmedInitCustom()
{
char* unarmedFileName = nullptr;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_UNARMED_FILE_KEY, &unarmedFileName);
if (unarmedFileName != nullptr && *unarmedFileName == '\0') {
unarmedFileName = nullptr;
}
if (unarmedFileName == nullptr) {
if (unarmedFileName == nullptr || *unarmedFileName == '\0') {
return;
}
Config unarmedConfig;
if (configInit(&unarmedConfig)) {
if (configRead(&unarmedConfig, unarmedFileName, false)) {
char section[4];
char statKey[6];
ScopedConfig unarmedConfig(unarmedFileName, false);
if (!unarmedConfig) {
return;
}
for (int hitMode = 0; hitMode < HIT_MODE_COUNT; hitMode++) {
if (!isUnarmedHitMode(hitMode)) {
continue;
}
char section[4];
char statKey[6];
UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions[hitMode]);
snprintf(section, sizeof(section), "%d", hitMode);
configGetInt(&unarmedConfig, section, "ReqLevel", &(hitDescription->requiredLevel));
configGetInt(&unarmedConfig, section, "SkillLevel", &(hitDescription->requiredSkill));
configGetInt(&unarmedConfig, section, "MinDamage", &(hitDescription->minDamage));
configGetInt(&unarmedConfig, section, "MaxDamage", &(hitDescription->maxDamage));
configGetInt(&unarmedConfig, section, "BonusDamage", &(hitDescription->bonusDamage));
configGetInt(&unarmedConfig, section, "BonusCrit", &(hitDescription->bonusCriticalChance));
configGetInt(&unarmedConfig, section, "APCost", &(hitDescription->actionPointCost));
configGetBool(&unarmedConfig, section, "BonusDamage", &(hitDescription->isPenetrate));
configGetBool(&unarmedConfig, section, "Secondary", &(hitDescription->isSecondary));
for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) {
snprintf(statKey, sizeof(statKey), "Stat%d", stat);
configGetInt(&unarmedConfig, section, statKey, &(hitDescription->requiredStats[stat]));
}
}
for (int hitMode = 0; hitMode < HIT_MODE_COUNT; hitMode++) {
if (!isUnarmedHitMode(hitMode)) {
continue;
}
configFree(&unarmedConfig);
UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions[hitMode]);
snprintf(section, sizeof(section), "%d", hitMode);
configGetInt(unarmedConfig.get(), section, "ReqLevel", &(hitDescription->requiredLevel));
configGetInt(unarmedConfig.get(), section, "SkillLevel", &(hitDescription->requiredSkill));
configGetInt(unarmedConfig.get(), section, "MinDamage", &(hitDescription->minDamage));
configGetInt(unarmedConfig.get(), section, "MaxDamage", &(hitDescription->maxDamage));
configGetInt(unarmedConfig.get(), section, "BonusDamage", &(hitDescription->bonusDamage));
configGetInt(unarmedConfig.get(), section, "BonusCrit", &(hitDescription->bonusCriticalChance));
configGetInt(unarmedConfig.get(), section, "APCost", &(hitDescription->actionPointCost));
configGetBool(unarmedConfig.get(), section, "BonusDamage", &(hitDescription->isPenetrate));
configGetBool(unarmedConfig.get(), section, "Secondary", &(hitDescription->isSecondary));
for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) {
snprintf(statKey, sizeof(statKey), "Stat%d", stat);
configGetInt(unarmedConfig.get(), section, statKey, &(hitDescription->requiredStats[stat]));
}
}
}
+57 -112
View File
@@ -3,7 +3,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unordered_set>
#include "actions.h"
#include "animation.h"
@@ -28,6 +27,7 @@
#include "proto.h"
#include "proto_instance.h"
#include "random.h"
#include "scripts.h"
#include "settings.h"
#include "sfall_script_hooks.h"
#include "skill.h"
@@ -51,7 +51,6 @@ static constexpr int kChemUseAnytimeChance = 75;
static constexpr int kChemUseAlwaysChance = 100;
static constexpr int kRandomDrugPickingArraySize = 3;
static std::unordered_set<Object*> burstDisabledCritters;
typedef struct AiMessageRange {
int start;
@@ -379,81 +378,77 @@ int aiInit()
gAiPacketsLength = 0;
Config config;
if (!configInit(&config)) {
ScopedConfig config("data\\ai.txt", true);
if (!config) {
return -1;
}
if (!configRead(&config, "data\\ai.txt", true)) {
return -1;
}
gAiPackets = (AiPacket*)internal_malloc(sizeof(*gAiPackets) * config.entriesLength);
gAiPackets = (AiPacket*)internal_malloc(sizeof(*gAiPackets) * config.get()->entriesLength);
if (gAiPackets == nullptr) {
goto err;
}
for (index = 0; index < config.entriesLength; index++) {
for (index = 0; index < config.get()->entriesLength; index++) {
aiPacketInit(&(gAiPackets[index]));
}
for (index = 0; index < config.entriesLength; index++) {
DictionaryEntry* sectionEntry = &(config.entries[index]);
for (index = 0; index < config.get()->entriesLength; index++) {
DictionaryEntry* sectionEntry = &(config.get()->entries[index]);
AiPacket* ai = &(gAiPackets[index]);
char* stringValue;
ai->name = internal_strdup(sectionEntry->key);
if (!configGetInt(&config, sectionEntry->key, "packet_num", &(ai->packet_num))) goto err;
if (!configGetInt(&config, sectionEntry->key, "max_dist", &(ai->max_dist))) goto err;
if (!configGetInt(&config, sectionEntry->key, "min_to_hit", &(ai->min_to_hit))) goto err;
if (!configGetInt(&config, sectionEntry->key, "min_hp", &(ai->min_hp))) goto err;
if (!configGetInt(&config, sectionEntry->key, "aggression", &(ai->aggression))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "packet_num", &(ai->packet_num))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "max_dist", &(ai->max_dist))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "min_to_hit", &(ai->min_to_hit))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "min_hp", &(ai->min_hp))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "aggression", &(ai->aggression))) goto err;
if (configGetString(&config, sectionEntry->key, "hurt_too_much", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "hurt_too_much", &stringValue)) {
_parse_hurt_str(stringValue, &(ai->hurt_too_much));
}
if (!configGetInt(&config, sectionEntry->key, "secondary_freq", &(ai->secondary_freq))) goto err;
if (!configGetInt(&config, sectionEntry->key, "called_freq", &(ai->called_freq))) goto err;
if (!configGetInt(&config, sectionEntry->key, "font", &(ai->font))) goto err;
if (!configGetInt(&config, sectionEntry->key, "color", &(ai->color))) goto err;
if (!configGetInt(&config, sectionEntry->key, "outline_color", &(ai->outline_color))) goto err;
if (!configGetInt(&config, sectionEntry->key, "chance", &(ai->chance))) goto err;
if (!configGetInt(&config, sectionEntry->key, "run_start", &(ai->run.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "run_end", &(ai->run.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "move_start", &(ai->move.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "move_end", &(ai->move.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "attack_start", &(ai->attack.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "attack_end", &(ai->attack.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "miss_start", &(ai->miss.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "miss_end", &(ai->miss.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_head_start", &(ai->hit[HIT_LOCATION_HEAD].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_head_end", &(ai->hit[HIT_LOCATION_HEAD].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_arm_start", &(ai->hit[HIT_LOCATION_LEFT_ARM].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_arm_end", &(ai->hit[HIT_LOCATION_LEFT_ARM].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_arm_start", &(ai->hit[HIT_LOCATION_RIGHT_ARM].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_arm_end", &(ai->hit[HIT_LOCATION_RIGHT_ARM].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_torso_start", &(ai->hit[HIT_LOCATION_TORSO].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_torso_end", &(ai->hit[HIT_LOCATION_TORSO].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_leg_start", &(ai->hit[HIT_LOCATION_RIGHT_LEG].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_leg_end", &(ai->hit[HIT_LOCATION_RIGHT_LEG].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_leg_start", &(ai->hit[HIT_LOCATION_LEFT_LEG].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_leg_end", &(ai->hit[HIT_LOCATION_LEFT_LEG].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_eyes_start", &(ai->hit[HIT_LOCATION_EYES].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_eyes_end", &(ai->hit[HIT_LOCATION_EYES].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_groin_start", &(ai->hit[HIT_LOCATION_GROIN].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_groin_end", &(ai->hit[HIT_LOCATION_GROIN].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "secondary_freq", &(ai->secondary_freq))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "called_freq", &(ai->called_freq))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "font", &(ai->font))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "color", &(ai->color))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "outline_color", &(ai->outline_color))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "chance", &(ai->chance))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "run_start", &(ai->run.start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "run_end", &(ai->run.end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "move_start", &(ai->move.start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "move_end", &(ai->move.end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "attack_start", &(ai->attack.start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "attack_end", &(ai->attack.end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "miss_start", &(ai->miss.start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "miss_end", &(ai->miss.end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_head_start", &(ai->hit[HIT_LOCATION_HEAD].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_head_end", &(ai->hit[HIT_LOCATION_HEAD].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_left_arm_start", &(ai->hit[HIT_LOCATION_LEFT_ARM].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_left_arm_end", &(ai->hit[HIT_LOCATION_LEFT_ARM].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_right_arm_start", &(ai->hit[HIT_LOCATION_RIGHT_ARM].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_right_arm_end", &(ai->hit[HIT_LOCATION_RIGHT_ARM].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_torso_start", &(ai->hit[HIT_LOCATION_TORSO].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_torso_end", &(ai->hit[HIT_LOCATION_TORSO].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_right_leg_start", &(ai->hit[HIT_LOCATION_RIGHT_LEG].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_right_leg_end", &(ai->hit[HIT_LOCATION_RIGHT_LEG].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_left_leg_start", &(ai->hit[HIT_LOCATION_LEFT_LEG].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_left_leg_end", &(ai->hit[HIT_LOCATION_LEFT_LEG].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_eyes_start", &(ai->hit[HIT_LOCATION_EYES].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_eyes_end", &(ai->hit[HIT_LOCATION_EYES].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_groin_start", &(ai->hit[HIT_LOCATION_GROIN].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_groin_end", &(ai->hit[HIT_LOCATION_GROIN].end))) goto err;
ai->hit[HIT_LOCATION_GROIN].end++;
if (configGetString(&config, sectionEntry->key, "area_attack_mode", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "area_attack_mode", &stringValue)) {
_cai_match_str_to_list(stringValue, gAreaAttackModeKeys, AREA_ATTACK_MODE_COUNT, &(ai->area_attack_mode));
} else {
ai->area_attack_mode = -1;
}
if (configGetString(&config, sectionEntry->key, "run_away_mode", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "run_away_mode", &stringValue)) {
_cai_match_str_to_list(stringValue, gRunAwayModeKeys, RUN_AWAY_MODE_COUNT, &(ai->run_away_mode));
if (ai->run_away_mode >= 0) {
@@ -461,49 +456,47 @@ int aiInit()
}
}
if (configGetString(&config, sectionEntry->key, "best_weapon", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "best_weapon", &stringValue)) {
_cai_match_str_to_list(stringValue, gBestWeaponKeys, BEST_WEAPON_COUNT, &(ai->best_weapon));
}
if (configGetString(&config, sectionEntry->key, "distance", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "distance", &stringValue)) {
_cai_match_str_to_list(stringValue, gDistanceModeKeys, DISTANCE_COUNT, &(ai->distance));
}
if (configGetString(&config, sectionEntry->key, "attack_who", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "attack_who", &stringValue)) {
_cai_match_str_to_list(stringValue, gAttackWhoKeys, ATTACK_WHO_COUNT, &(ai->attack_who));
}
if (configGetString(&config, sectionEntry->key, "chem_use", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "chem_use", &stringValue)) {
_cai_match_str_to_list(stringValue, gChemUseKeys, CHEM_USE_COUNT, &(ai->chem_use));
}
configGetIntList(&config, sectionEntry->key, "chem_primary_desire", ai->chem_primary_desire, AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT);
configGetIntList(config.get(), sectionEntry->key, "chem_primary_desire", ai->chem_primary_desire, AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT);
if (configGetString(&config, sectionEntry->key, "disposition", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "disposition", &stringValue)) {
_cai_match_str_to_list(stringValue, gDispositionKeys, DISPOSITION_COUNT, &(ai->disposition));
ai->disposition--;
}
if (configGetString(&config, sectionEntry->key, "body_type", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "body_type", &stringValue)) {
ai->body_type = internal_strdup(stringValue);
} else {
ai->body_type = nullptr;
}
if (configGetString(&config, sectionEntry->key, "general_type", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "general_type", &stringValue)) {
ai->general_type = internal_strdup(stringValue);
} else {
ai->general_type = nullptr;
}
}
if (index < config.entriesLength) {
if (index < config.get()->entriesLength) {
goto err;
}
gAiPacketsLength = config.entriesLength;
configFree(&config);
gAiPacketsLength = config.get()->entriesLength;
gAiInitialized = true;
@@ -512,7 +505,7 @@ int aiInit()
err:
if (gAiPackets != nullptr) {
for (index = 0; index < config.entriesLength; index++) {
for (index = 0; index < config.get()->entriesLength; index++) {
AiPacket* ai = &(gAiPackets[index]);
if (ai->name != nullptr) {
internal_free(ai->name);
@@ -526,15 +519,12 @@ err:
debugPrint("Error processing ai.txt");
configFree(&config);
return -1;
}
// 0x4279F8
void aiReset()
{
burstDisabledCritters.clear();
}
// 0x4279FC
@@ -617,14 +607,7 @@ int aiSave(File* stream)
// 0x427BC8
static int aiPacketRead(File* stream, AiPacket* ai)
{
int packetNum;
if (fileReadInt32(stream, &packetNum) == -1) return -1;
// Consume the serialized packet number to preserve save compatibility,
// but do not overwrite the value in AiPacket to avoid corrupted data on save loads.
// This might happen if saves disagree on packet_num and packets are loaded from AI.TXT only on game init.
(void)packetNum;
if (fileReadInt32(stream, &(ai->packet_num)) == -1) return -1;
if (fileReadInt32(stream, &(ai->max_dist)) == -1) return -1;
if (fileReadInt32(stream, &(ai->min_to_hit)) == -1) return -1;
if (fileReadInt32(stream, &(ai->min_hp)) == -1) return -1;
@@ -898,37 +881,6 @@ int aiSetChemUse(Object* critter, int chemUse)
return 0;
}
bool aiIsBurstDisabled(Object* critter)
{
if (critter == nullptr) {
return false;
}
return burstDisabledCritters.find(critter) != burstDisabledCritters.end();
}
void aiSetBurstDisabled(Object* critter, bool disable)
{
if (critter == nullptr || FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER || critter == gDude) {
return;
}
if (disable) {
burstDisabledCritters.insert(critter);
} else {
burstDisabledCritters.erase(critter);
}
}
void aiRemoveBurstDisabled(Object* critter)
{
if (critter == nullptr) {
return;
}
burstDisabledCritters.erase(critter);
}
// 0x428340
int aiGetDisposition(Object* obj)
{
@@ -2326,13 +2278,6 @@ static int _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defender)
return HIT_MODE_RIGHT_WEAPON_PRIMARY;
}
if (aiIsBurstDisabled(attacker)) {
int secondaryAnimation = weaponGetAnimationForHitMode(weapon, HIT_MODE_RIGHT_WEAPON_SECONDARY);
if (secondaryAnimation == ANIM_FIRE_BURST || secondaryAnimation == ANIM_FIRE_CONTINUOUS) {
return HIT_MODE_RIGHT_WEAPON_PRIMARY;
}
}
bool useSecondaryMode = false;
AiPacket* ai = aiGetPacket(attacker);
-3
View File
@@ -45,9 +45,6 @@ int aiSetBestWeapon(Object* critter, int bestWeapon);
int aiSetDistance(Object* critter, int distance);
int aiSetAttackWho(Object* critter, int attackWho);
int aiSetChemUse(Object* critter, int chemUse);
bool aiIsBurstDisabled(Object* critter);
void aiSetBurstDisabled(Object* critter, bool disable);
void aiRemoveBurstDisabled(Object* critter);
int aiGetDisposition(Object* obj);
int aiSetDisposition(Object* obj, int a2);
int _caiSetupTeamCombat(Object* attackerTeam, Object* defenderTeam);
+2 -5
View File
@@ -851,6 +851,7 @@ bool configSetBool(Config* config, const char* sectionKey, const char* key, bool
return configSetInt(config, sectionKey, key, value ? 1 : 0);
}
// ScopedConfig is a RAII wrapper around Config that makes cleanup easier.
ScopedConfig::ScopedConfig()
{
_loaded = configInit(&_config);
@@ -871,11 +872,7 @@ ScopedConfig::~ScopedConfig()
}
}
bool ScopedConfig::isInitialized() const
{
return _config.isInitialized();
}
// get returns a pointer to be useful in the config* API above; it cannot be nullptr
Config* ScopedConfig::get()
{
return &_config;
-24
View File
@@ -144,30 +144,6 @@ File* fileOpen(const char* filename, const char* mode)
return xfileOpen(filename, mode);
}
File* fileOpenDirect(const char* filename, const char* mode)
{
return xfileOpenDirect(filename, mode);
}
const char* buildDataPath(const char* root, const char* relative)
{
static char path[COMPAT_MAX_PATH];
size_t rootLen = strlen(root);
bool rootHasSeparator = rootLen > 0 && (root[rootLen - 1] == '\\' || root[rootLen - 1] == '/');
snprintf(path, sizeof(path), "%s%s%s", root, rootHasSeparator ? "" : "\\", relative);
char dir[COMPAT_MAX_PATH];
snprintf(dir, sizeof(dir), "%s", path);
char* separator = strrchr(dir, '\\');
if (separator != nullptr) {
*separator = '\0';
compat_mkdir_recursive(dir);
}
return path;
}
// 0x4C5ED0 db_fprintf
int filePrintFormatted(File* stream, const char* format, ...)
{
-5
View File
@@ -18,7 +18,6 @@ int dbGetFileSize(const char* filePath, int* sizePtr);
int dbGetFileContents(const char* filePath, void* ptr);
int fileClose(File* stream);
File* fileOpen(const char* filename, const char* mode);
File* fileOpenDirect(const char* filename, const char* mode);
int filePrintFormatted(File* stream, const char* format, ...);
int fileReadChar(File* stream);
char* fileReadString(char* str, size_t size, File* stream);
@@ -112,10 +111,6 @@ void fileNameListFree(char*** fileNames, int unused);
int fileGetSize(File* stream);
void fileSetReadProgressHandler(FileReadProgressHandler* handler, int size);
// Joins [root] and [relative] into a save path and creates its directory.
// Returns a pointer to a static buffer.
const char* buildDataPath(const char* root, const char* relative);
} // namespace fallout
#endif /* DB_H */

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