mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27805a6c90 | ||
|
|
74769044a6 | ||
|
|
a3f66539fb | ||
|
|
ff75e13430 | ||
|
|
a4a929f620 | ||
|
|
f5ba58f27a | ||
|
|
9bd179167c | ||
|
|
9e88b16d71 | ||
|
|
15a4f4275b | ||
|
|
466f12b22e | ||
|
|
26622f3596 | ||
|
|
629d1f2b7b | ||
|
|
1453a3e294 | ||
|
|
b5c0fbdf86 | ||
|
|
948558c64c | ||
|
|
6412d14a28 | ||
|
|
6835d10590 | ||
|
|
20ad8d8778 | ||
|
|
6782a21185 | ||
|
|
d12dbca951 | ||
|
|
823858f275 | ||
|
|
b93591f5d7 | ||
|
|
a91e40e3cc | ||
|
|
7da7d75f30 | ||
|
|
72b4680ff5 | ||
|
|
99a762b649 | ||
|
|
1afe925cbf | ||
|
|
95c07dba9f | ||
|
|
0fc042ef84 | ||
|
|
29df63dd99 | ||
|
|
6f2693edf7 | ||
|
|
e51004a3d1 | ||
|
|
9aa2ae4bbc | ||
|
|
968534314d | ||
|
|
a38f3436f8 | ||
|
|
15c22ccdfc | ||
|
|
f263075abf | ||
|
|
c95f91c2ff | ||
|
|
db86e53af2 | ||
|
|
349af8fbb8 |
@@ -614,6 +614,10 @@ 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"
|
||||
@@ -624,8 +628,12 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
|
||||
"src/mapper/mp_proto.h"
|
||||
"src/mapper/mp_scrpt.cc"
|
||||
"src/mapper/mp_scrpt.h"
|
||||
"src/mapper/mp_spray.cc"
|
||||
"src/mapper/mp_spray.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"
|
||||
)
|
||||
|
||||
+13
-5
@@ -76,7 +76,7 @@ For example:
|
||||
|
||||
## `ce-frm2png` CLI Tool
|
||||
|
||||
There is also a small FRM conversion tool in this repo for exporting Fallout `.frm` art to `.png`.
|
||||
There is also a small FRM conversion tool in this repo for converting Fallout art between `.frm` and `.png`.
|
||||
|
||||
From the repo root, build it with your normal CMake workflow, targeting `ce-frm2png`:
|
||||
|
||||
@@ -89,18 +89,26 @@ 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 - [output.png|-] --palette <path-to-color.pal>`
|
||||
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>`
|
||||
|
||||
`-` can be used to take input from stdin (e.g. from `ce-dat-tool cat` or write to stdout).
|
||||
`-` 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.
|
||||
|
||||
If `--palette` isn't specified, it will pick up `color.pal` in the .frm's directory or cwd.
|
||||
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.
|
||||
|
||||
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.
|
||||
- The current implementation writes RGBA PNGs. It does not preserve the original Fallout palette indices as a palette-indexed PNG.
|
||||
- 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.
|
||||
|
||||
## Updating SDL
|
||||
|
||||
|
||||
@@ -153,6 +153,7 @@ 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
|
||||
|
||||
@@ -162,6 +163,14 @@ 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).
|
||||
|
||||
@@ -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 | implemented: get_attack_type, get/set_bodypart_hit_modifier, combat_data, get/set_critter_current_ap, get/set_combat_free_move | - |
|
||||
| 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 | - |
|
||||
| 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.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
+12
-2
@@ -119,15 +119,25 @@ 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.
|
||||
use_walk_distance=5
|
||||
; 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
|
||||
; 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,12 +60,14 @@ 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));
|
||||
|
||||
@@ -1043,6 +1043,8 @@ 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);
|
||||
|
||||
+196
-9
@@ -1,6 +1,9 @@
|
||||
#include "art.h"
|
||||
|
||||
#include <lodepng.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -134,6 +137,7 @@ 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;
|
||||
|
||||
@@ -363,6 +367,12 @@ 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)
|
||||
{
|
||||
@@ -1116,7 +1126,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->field_0)) == -1) return -1;
|
||||
if (fileReadInt32(stream, &(art->version)) == -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;
|
||||
@@ -1133,13 +1143,166 @@ static int artReadHeader(Art* art, File* stream)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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)
|
||||
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)
|
||||
{
|
||||
File* stream = fileOpen(path, "rb");
|
||||
if (stream == nullptr) {
|
||||
@@ -1167,6 +1330,30 @@ Art* artLoad(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;
|
||||
@@ -1239,7 +1426,7 @@ int artWriteFrameData(unsigned char* data, File* stream, int count)
|
||||
// 0x41A138
|
||||
int artWriteHeader(Art* art, File* stream)
|
||||
{
|
||||
if (fileWriteInt32(stream, art->field_0) == -1) return -1;
|
||||
if (fileWriteInt32(stream, art->version) == -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;
|
||||
|
||||
@@ -69,7 +69,7 @@ typedef enum Background {
|
||||
} Background;
|
||||
|
||||
typedef struct Art {
|
||||
int field_0;
|
||||
int version;
|
||||
short framesPerSecond;
|
||||
short actionFrame;
|
||||
short frameCount;
|
||||
@@ -123,6 +123,7 @@ 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);
|
||||
|
||||
+88
-78
@@ -6254,75 +6254,79 @@ static void criticalsInit()
|
||||
}
|
||||
|
||||
if (mode == 1 || mode == 3) {
|
||||
char* criticalsConfigFilePath;
|
||||
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_OVERRIDE_CRITICALS_FILE_KEY, &criticalsConfigFilePath);
|
||||
if (criticalsConfigFilePath != nullptr && *criticalsConfigFilePath == '\0') {
|
||||
criticalsConfigFilePath = nullptr;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
ScopedConfig criticalsConfig(criticalsConfigFilePath, false);
|
||||
if (criticalsConfig) {
|
||||
if (mode == 1) {
|
||||
char sectionKey[16];
|
||||
if (configRead(&criticalsConfig, criticalsConfigFilePath, false)) {
|
||||
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.get(), 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, 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.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;
|
||||
}
|
||||
int enabled = 0;
|
||||
configGetInt(&criticalsConfig, ktSectionKey, "Enabled", &enabled);
|
||||
if (enabled == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
snprintf(hitLocationSectionKey, sizeof(hitLocationSectionKey), "c_%02d_%d", killType, hitLocation);
|
||||
for (int hitLocation = 0; hitLocation < HIT_LOCATION_COUNT; hitLocation++) {
|
||||
if (enabled < 2) {
|
||||
bool hitLocationChanged = false;
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configFree(&criticalsConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6596,40 +6600,46 @@ static void unarmedInitCustom()
|
||||
{
|
||||
char* unarmedFileName = nullptr;
|
||||
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_UNARMED_FILE_KEY, &unarmedFileName);
|
||||
if (unarmedFileName == nullptr || *unarmedFileName == '\0') {
|
||||
if (unarmedFileName != nullptr && *unarmedFileName == '\0') {
|
||||
unarmedFileName = nullptr;
|
||||
}
|
||||
|
||||
if (unarmedFileName == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ScopedConfig unarmedConfig(unarmedFileName, false);
|
||||
if (!unarmedConfig) {
|
||||
return;
|
||||
}
|
||||
Config unarmedConfig;
|
||||
if (configInit(&unarmedConfig)) {
|
||||
if (configRead(&unarmedConfig, unarmedFileName, false)) {
|
||||
char section[4];
|
||||
char statKey[6];
|
||||
|
||||
char section[4];
|
||||
char statKey[6];
|
||||
for (int hitMode = 0; hitMode < HIT_MODE_COUNT; hitMode++) {
|
||||
if (!isUnarmedHitMode(hitMode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int hitMode = 0; hitMode < HIT_MODE_COUNT; hitMode++) {
|
||||
if (!isUnarmedHitMode(hitMode)) {
|
||||
continue;
|
||||
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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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]));
|
||||
}
|
||||
configFree(&unarmedConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+112
-57
@@ -3,6 +3,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "actions.h"
|
||||
#include "animation.h"
|
||||
@@ -27,7 +28,6 @@
|
||||
#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,6 +51,7 @@ 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;
|
||||
@@ -378,77 +379,81 @@ int aiInit()
|
||||
|
||||
gAiPacketsLength = 0;
|
||||
|
||||
ScopedConfig config("data\\ai.txt", true);
|
||||
if (!config) {
|
||||
Config config;
|
||||
if (!configInit(&config)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
gAiPackets = (AiPacket*)internal_malloc(sizeof(*gAiPackets) * config.get()->entriesLength);
|
||||
if (!configRead(&config, "data\\ai.txt", true)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
gAiPackets = (AiPacket*)internal_malloc(sizeof(*gAiPackets) * config.entriesLength);
|
||||
if (gAiPackets == nullptr) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (index = 0; index < config.get()->entriesLength; index++) {
|
||||
for (index = 0; index < config.entriesLength; index++) {
|
||||
aiPacketInit(&(gAiPackets[index]));
|
||||
}
|
||||
|
||||
for (index = 0; index < config.get()->entriesLength; index++) {
|
||||
DictionaryEntry* sectionEntry = &(config.get()->entries[index]);
|
||||
for (index = 0; index < config.entriesLength; index++) {
|
||||
DictionaryEntry* sectionEntry = &(config.entries[index]);
|
||||
AiPacket* ai = &(gAiPackets[index]);
|
||||
char* stringValue;
|
||||
|
||||
ai->name = internal_strdup(sectionEntry->key);
|
||||
|
||||
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 (!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 (configGetString(config.get(), sectionEntry->key, "hurt_too_much", &stringValue)) {
|
||||
if (configGetString(&config, sectionEntry->key, "hurt_too_much", &stringValue)) {
|
||||
_parse_hurt_str(stringValue, &(ai->hurt_too_much));
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
ai->hit[HIT_LOCATION_GROIN].end++;
|
||||
|
||||
if (configGetString(config.get(), sectionEntry->key, "area_attack_mode", &stringValue)) {
|
||||
if (configGetString(&config, 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.get(), sectionEntry->key, "run_away_mode", &stringValue)) {
|
||||
if (configGetString(&config, 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) {
|
||||
@@ -456,47 +461,49 @@ int aiInit()
|
||||
}
|
||||
}
|
||||
|
||||
if (configGetString(config.get(), sectionEntry->key, "best_weapon", &stringValue)) {
|
||||
if (configGetString(&config, sectionEntry->key, "best_weapon", &stringValue)) {
|
||||
_cai_match_str_to_list(stringValue, gBestWeaponKeys, BEST_WEAPON_COUNT, &(ai->best_weapon));
|
||||
}
|
||||
|
||||
if (configGetString(config.get(), sectionEntry->key, "distance", &stringValue)) {
|
||||
if (configGetString(&config, sectionEntry->key, "distance", &stringValue)) {
|
||||
_cai_match_str_to_list(stringValue, gDistanceModeKeys, DISTANCE_COUNT, &(ai->distance));
|
||||
}
|
||||
|
||||
if (configGetString(config.get(), sectionEntry->key, "attack_who", &stringValue)) {
|
||||
if (configGetString(&config, sectionEntry->key, "attack_who", &stringValue)) {
|
||||
_cai_match_str_to_list(stringValue, gAttackWhoKeys, ATTACK_WHO_COUNT, &(ai->attack_who));
|
||||
}
|
||||
|
||||
if (configGetString(config.get(), sectionEntry->key, "chem_use", &stringValue)) {
|
||||
if (configGetString(&config, sectionEntry->key, "chem_use", &stringValue)) {
|
||||
_cai_match_str_to_list(stringValue, gChemUseKeys, CHEM_USE_COUNT, &(ai->chem_use));
|
||||
}
|
||||
|
||||
configGetIntList(config.get(), sectionEntry->key, "chem_primary_desire", ai->chem_primary_desire, AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT);
|
||||
configGetIntList(&config, sectionEntry->key, "chem_primary_desire", ai->chem_primary_desire, AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT);
|
||||
|
||||
if (configGetString(config.get(), sectionEntry->key, "disposition", &stringValue)) {
|
||||
if (configGetString(&config, sectionEntry->key, "disposition", &stringValue)) {
|
||||
_cai_match_str_to_list(stringValue, gDispositionKeys, DISPOSITION_COUNT, &(ai->disposition));
|
||||
ai->disposition--;
|
||||
}
|
||||
|
||||
if (configGetString(config.get(), sectionEntry->key, "body_type", &stringValue)) {
|
||||
if (configGetString(&config, sectionEntry->key, "body_type", &stringValue)) {
|
||||
ai->body_type = internal_strdup(stringValue);
|
||||
} else {
|
||||
ai->body_type = nullptr;
|
||||
}
|
||||
|
||||
if (configGetString(config.get(), sectionEntry->key, "general_type", &stringValue)) {
|
||||
if (configGetString(&config, sectionEntry->key, "general_type", &stringValue)) {
|
||||
ai->general_type = internal_strdup(stringValue);
|
||||
} else {
|
||||
ai->general_type = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < config.get()->entriesLength) {
|
||||
if (index < config.entriesLength) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
gAiPacketsLength = config.get()->entriesLength;
|
||||
gAiPacketsLength = config.entriesLength;
|
||||
|
||||
configFree(&config);
|
||||
|
||||
gAiInitialized = true;
|
||||
|
||||
@@ -505,7 +512,7 @@ int aiInit()
|
||||
err:
|
||||
|
||||
if (gAiPackets != nullptr) {
|
||||
for (index = 0; index < config.get()->entriesLength; index++) {
|
||||
for (index = 0; index < config.entriesLength; index++) {
|
||||
AiPacket* ai = &(gAiPackets[index]);
|
||||
if (ai->name != nullptr) {
|
||||
internal_free(ai->name);
|
||||
@@ -519,12 +526,15 @@ err:
|
||||
|
||||
debugPrint("Error processing ai.txt");
|
||||
|
||||
configFree(&config);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 0x4279F8
|
||||
void aiReset()
|
||||
{
|
||||
burstDisabledCritters.clear();
|
||||
}
|
||||
|
||||
// 0x4279FC
|
||||
@@ -607,7 +617,14 @@ int aiSave(File* stream)
|
||||
// 0x427BC8
|
||||
static int aiPacketRead(File* stream, AiPacket* ai)
|
||||
{
|
||||
if (fileReadInt32(stream, &(ai->packet_num)) == -1) return -1;
|
||||
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->max_dist)) == -1) return -1;
|
||||
if (fileReadInt32(stream, &(ai->min_to_hit)) == -1) return -1;
|
||||
if (fileReadInt32(stream, &(ai->min_hp)) == -1) return -1;
|
||||
@@ -881,6 +898,37 @@ 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)
|
||||
{
|
||||
@@ -2278,6 +2326,13 @@ 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);
|
||||
|
||||
@@ -45,6 +45,9 @@ 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);
|
||||
|
||||
+5
-2
@@ -851,7 +851,6 @@ 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);
|
||||
@@ -872,7 +871,11 @@ ScopedConfig::~ScopedConfig()
|
||||
}
|
||||
}
|
||||
|
||||
// get returns a pointer to be useful in the config* API above; it cannot be nullptr
|
||||
bool ScopedConfig::isInitialized() const
|
||||
{
|
||||
return _config.isInitialized();
|
||||
}
|
||||
|
||||
Config* ScopedConfig::get()
|
||||
{
|
||||
return &_config;
|
||||
|
||||
@@ -144,6 +144,30 @@ 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, ...)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ 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);
|
||||
@@ -111,6 +112,10 @@ 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
Reference in New Issue
Block a user