Compare commits

..
Author SHA1 Message Date
github-actions[bot] 07939225c6 chore: auto-format with clang-format 2026-06-23 05:27:36 +00:00
Mike Klaas b10526330c SFALL_COMPAT 2026-06-22 22:26:13 -07:00
Mike Klaas 4c1cad7122 scriptWindow opcodes 2026-06-22 22:26:02 -07:00
Mike Klaas b76ff8257e fix item_weight 2026-06-22 22:24:46 -07:00
Mike Klaas 6edbd70d60 Let FrmImage reference single-frames from multi-frame frms, and support pcx 2026-06-22 22:04:33 -07:00
Mike Klaas 15917dccb3 inventory_refresh, display_stats 2026-06-22 20:56:19 -07:00
Mike Klaas 33aae49066 character editor display_stats 2026-06-22 20:46:55 -07:00
Mike Klaas bfd41bb40c opcode registration 2026-06-22 20:44:36 -07:00
Mike Klaas 0ccaef5ef0 get windows 2026-06-22 20:34:11 -07:00
69 changed files with 1675 additions and 3324 deletions
+2 -3
View File
@@ -66,9 +66,8 @@ Available commands:
1. `./<BUILD_DIR>/ce-dat-tool <archive.dat> list [pattern]`
2. `ce-dat-tool <archive.dat> info [pattern]`
3. `ce-dat-tool <archive.dat> extract [--lower] <output-dir> [pattern]`
4. `ce-dat-tool <archive.dat> extract [--lower] (--file-list|--files-from) <list-file> <output-dir>`
5. `ce-dat-tool <archive.dat> cat <entry>`
6. `ce-dat-tool create <input-dir> <archive.dat>`
4. `ce-dat-tool <archive.dat> cat <entry>`
5. `ce-dat-tool create <input-dir> <archive.dat>`
Use `--lower` with `extract` when you want every extracted file and directory name forced to lowercase.
For example:
+4 -4
View File
@@ -144,13 +144,13 @@ In time this stuff will receive in-game interface, right now you have to do it m
* Expanded 4-row barter screen
* Expanded AP bar
* Party members can loot and barter in place of PC
* Increased pathfinding nodes 5x for more accurate pathfinding, and use accurate path length for walk vs run
* Increased pathfinding nodes 5x for more accurate pathfinding
* Ctrl-click to quickly move items when bartering, looting, or stealing
* _a_ to select "all" when selecting item quantity, and for `Take All` when looting
* When bartering, caps default to the right amount to balance the trade
* _a_ to select "all" when selecting item quantity
* _a_ to `Take All` when looting
* When bartering, caps default to the right amount to balance the trade (if possible)
* Music continues playing between maps
* Auto open doors
* Integrated "HELP" menu
* 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))
+5 -5
View File
@@ -53,7 +53,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Perks and traits | set_perk_image<br>set_perk_*<br>set_pyromaniac_mod<br>apply_heaveho_fix<br>set_swiftlearner_mod<br>has/set_fake_perk<br>has/set_fake_trait<br>set_selectable_perk<br>set_perkbox_title<br>show/hide_real_perks<br>perk_add_mode<br>clear_selectable_perks<br>add/remove_trait<br>seq_perk_freq | not implemented | - |
| Virtual file system | fs_create<br>fs_copy<br>fs_find<br>fs_read/write_*<br>fs_delete<br>fs_size<br>fs_pos<br>fs_seek<br>fs_resize | đźš« | Open an issue if you have a use case for these |
| Combat / Knockback | set_weapon_knockback<br>set_target_knockback<br>set_attacker_knockback<br>remove_weapon_knockback<br>remove_target_knockback<br>remove_attacker_knockback | not implemented | - |
| Maps and encounters | in_world_map<br>force_encounter{_with_flags}<br>set_map_time_multi<br>get/set_map_enter_position<br>exec_map_update_scripts<br>get/set_terrain_name<br>set_town_title<br>get/set_can_rest_on_map<br>set_rest_heal_time<br>set_rest_mode<br>set_worldmap_heal_time | implemented: in_world_map, force_encounter, force_encounter_with_flags, set_map_time_multi | - |
| Maps and encounters | in_world_map<br>force_encounter<br>force_encounter_with_flags<br>set_map_time_multi<br>get/set_map_enter_position<br>exec_map_update_scripts<br>get/set_terrain_name<br>set_town_title<br>get/set_can_rest_on_map<br>set_rest_heal_time<br>set_rest_mode<br>set_worldmap_heal_time | implemented: in_world_map, force_encounter, force_encounter_with_flags, set_map_time_multi | - |
| Maps and encounters / Worldmap | get_world_map_x/y_pos<br>set_world_map_pos | âś… | - |
| Audio | play_sfall_sound<br>stop_sfall_sound | âś… | `play_sfall_sound` currently supports `.acm`, `.wav`, `.ogg` formats, and can load from `.dat` archives. `.mp3` is not yet supported. |
| Combat / Weapons and ammo | get/set_weapon_ammo_pid<br>get/set_weapon_ammo_count | âś… | - |
@@ -63,7 +63,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Lists | list_begin<br>list_next<br>list_end<br>list_as_array<br>party_member_list | âś… | - |
| Explosions | set_attack_explosion_pattern<br>set_attack_explosion_art<br>set_attack_explosion_radius<br>set_attack_is_explosion_fire<br>set_explosion_radius<br>set_dynamite_damage<br>set_plastic_damage<br>get_explosion_damage<br>set_explosion_max_targets<br>item_make_explosive | âś… except item_make_explosive | - |
| Animations | reg_anim_combat_check<br>reg_anim_destroy<br>reg_anim_animate_and_hide<br>reg_anim_light<br>reg_anim_change_fid<br>reg_anim_take_out<br>reg_anim_turn_towards<br>reg_anim_callback<br>reg_anim_animate_and_move | âś… except reg_anim_callback | - |
| Art and appearance | art_exists<br>art_frame_data<br>refresh_pc_art<br>art_cache_clear | âś… | - |
| Art and appearance | art_exists<br>refresh_pc_art<br>art_cache_clear<br>set_hero_race<br>set_hero_style | implemented: art_exists, refresh_pc_art, art_cache_clear | - |
| Tiles and paths | get_tile_fid<br>tile_under_cursor<br>tile_light<br>tile_get_objs<br>tile_refresh_display<br>obj_blocking_tile<br>tile_by_position<br>get_tile_ground_fid<br>get_tile_roof_fid<br>obj_blocking_line<br>path_find_to<br>objects_in_radius | âś… | - |
| Utility | sprintf<br>typeof<br>atoi<br>atof | âś… | - |
| Utility / Strings | string_split<br>substr<br>strlen<br>charcode<br>get_string_pointer<br>string_find<br>string_find_from<br>string_format<br>string_format_array<br>string_replace<br>string_to_case<br>string_compare | âś… | `get_string_pointer` is deprecated and intentionally omitted. |
@@ -75,18 +75,18 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| 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 | - |
| 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 | interface_art_draw<br>interface_print<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 | âś… except interface_print, interface_overlay | - |
| 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 | âś… except interface_print, draw_image, draw_image_scaled, interface_overlay | - |
| Interface / Outline | outlined_object<br>get_outline<br>set_outline | âś… | - |
| Interface / Main interface | intface_is_hidden<br>intface_redraw<br>intface_hide<br>intface_show<br>set_quest_failure_value | implemented: only intface_redraw | `intface_redraw` only supports the zero-argument form; the optional-argument path is explicitly unimplemented. |
| Interface / Inventory | display_stats<br>inventory_redraw<br>critter_inven_obj2<br>get_current_inven_size<br>item_weight | âś… except get_current_inven_size | - |
| Interface / Cursor | get/set_cursor_mode | âś… | - |
| Locks | lock_is_jammed<br>unjam_lock<br>set_unjam_locks_time | not implemented | - |
| INI settings | get_ini_setting<br>get_ini_string<br>get_ini_section<br>get_ini_sections<br>get_ini_config<br>get_ini_config_db<br>set_ini_setting | âś… | `modified_ini` is intentionally omitted as deprecated. |
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>get/set_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get/set_object_data<br>get/set_flags<br>set_unique_id<br>set_scr_name<br>obj_is_openable<br>get/set_proto_data<br>get_object_ai_data | implemented: set_self, real_dude_obj, get/set/remove_script, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, set_unique_id, obj_is_openable, get_proto_data, set_proto_data | get_object_data assumes raw memory offsets which may return different values in CE |
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>get/set_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get/set_object_data<br>get/set_flags<br>set_unique_id<br>set_scr_name<br>obj_is_openable<br>get/set_proto_data<br>get_object_ai_data | implemented: set_self, get/set/remove_script, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, set_unique_id, obj_is_openable, get_proto_data, set_proto_data | - |
| Other / Game management | set_movie_path<br>stop/resume_game<br>mark_movie_played<br>game_loaded<br>get_game_mode<br>get_uptime<br>signal_close_game | implemented: game_loaded, get_game_mode, get_uptime, signal_close_game | - |
| Gameplay tweaks | set_pickpocket_max<br>set_hit_chance_max<br>set_xp_mod<br>set_critter_hit_chance_mod<br>set_base_hit_chance_mod<br>set_hp_per_level_mod<br>gdialog_get_barter_mod<br>get/set_unspent_ap_bonus<br>get/set_unspent_ap_perk_bonus<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>get/set_inven_ap_cost<br>set_drugs_data<br>get_kill_counter<br>mod_kill_counter<br>set_pipboy_available | implemented: gdialog_get_barter_mod, get/set_unspent_ap{_perk}_bonus, get/set_inven_ap_cost | - |
| NPCs | inc_npc_level<br>get_npc_level<br>npc_engine_level_up | not implemented | - |
| Hero Appearance | set_dm/df_model<br>hero_select_win<br>set_hero_race<br>set_hero_style | not implemented | - |
| Hero Appearange | set_dm/df_model<br>hero_select_win | not implemented | - |
| Events | add_g_timer_event<br>remove_timer_event<br>create_spatial<br>spatial_radius | not implemented | - |
| Other | get_year<br>active_hand<br>toggle_active_hand<br>get/set_viewport_x/y<br>get_light_level<br>message_str_game<br>sneak_success<br>unwield_slot<br>add_extra_msg_file<br>get_metarule_table<br>metarule_exist<br> | âś… except get/set_viewport_x/y, sneak_success | `input_funcs_available`, `nb_create_char` are deprecated in sfall and intentionally absent in CE. `add_extra_msg_file` does not support the explicit `fileNumber` form in CE. |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.
-1
View File
@@ -1 +0,0 @@
{0}{}{HELP}
-1
View File
@@ -1 +0,0 @@
{0}{}{AIDE}
-1
View File
@@ -1 +0,0 @@
{0}{}{HILFE}
-1
View File
@@ -1 +0,0 @@
{0}{}{AIUTO}
-1
View File
@@ -1 +0,0 @@
{0}{}{POMOC}
-1
View File
@@ -1 +0,0 @@
{0}{}{AJUDA}
-1
View File
@@ -1 +0,0 @@
{0}{}{ПОМОЩЬ}
-1
View File
@@ -1 +0,0 @@
{0}{}{AYUDA}
-2
View File
@@ -130,8 +130,6 @@ inventory_columns=2
;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
;Set to 1 to show a Help option in the in-game options menu which opens the F1 help screen.
in_game_menu_help=1
[qol]
-10
View File
@@ -1,6 +1,5 @@
#include "lib.arrays.h" // For arrays_equal, len_array, etc.
#include "test_utils.h" // For assertEquals, VALTYPE_STR, report_test_results
#include "sfall.h"
// To test: copy test.ini into the game folder before running
procedure ini_test_suite begin
@@ -30,15 +29,6 @@ procedure ini_test_suite begin
call assertEquals("TC2 get_ini_setting empty int", get_ini_setting("test.ini|ValidSection|EmptyValue"), 0);
call assertEquals("TC2 get_ini_string empty string", get_ini_string("test.ini|ValidSection|EmptyValue"), "");
// Regression: sfall resolves leading-backslash paths relative to the game root.
result_array := get_ini_section("\\test.ini", "ValidSection");
call assertEquals("TC2B Size", len_array(result_array), 3);
call assertEquals("TC2B Key1", result_array["Key1"], "Value1");
call assertEquals("TC2B get_ini_setting int", get_ini_setting("\\test.ini|ValidSection|Key2"), 2);
call assertEquals("TC2B get_ini_string string", get_ini_string("\\test.ini|ValidSection|Key1"), "Value1");
result_array := get_ini_sections("\\test.ini");
call assertEquals("TC2B get_ini_sections size", len_array(result_array), 3);
// INI file not found
result_array := get_ini_section("nonexistent.ini", "AnySection");
call assertEquals("TC3 Size", len_array(result_array), 0);
+12 -42
View File
@@ -1,8 +1,5 @@
#include "animation.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <stdio.h>
#include <string.h>
@@ -40,13 +37,9 @@
namespace fallout {
// sfall exposes this as [Misc] AnimationsAtOnceLimit. Keep CE aligned with
// sfall's default 64 total / 52 non-reserved slots for now; this can become
// configurable later.
#define ANIMATION_SEQUENCE_LIST_CAPACITY 64
#define ANIMATION_SEQUENCE_LIST_CAPACITY 32
#define ANIMATION_DESCRIPTION_LIST_CAPACITY 55
#define ANIMATION_SAD_LIST_CAPACITY 64
#define ANIMATION_NON_RESERVED_LIST_CAPACITY 52
#define ANIMATION_SAD_LIST_CAPACITY 24
#define ANIMATION_SEQUENCE_FORCED 0x01
@@ -82,11 +75,6 @@ typedef enum AnimationKind {
ANIM_KIND_SET_LIGHT_INTENSITY,
} AnimationKind;
static bool animationKindIsCallback(int kind)
{
return kind == ANIM_KIND_CALLBACK || kind == ANIM_KIND_CALLBACK3;
}
typedef enum AnimationSequenceFlags {
// Specifies that the animation sequence has high priority, it cannot be
// cleared.
@@ -345,7 +333,7 @@ static AnimationSad gAnimationSads[ANIMATION_SAD_LIST_CAPACITY];
static PathNode gClosedPathNodeList[PATH_NODE_CAPACITY];
// 0x54CC14 anim_set
static AnimationSequence gAnimationSequences[ANIMATION_SEQUENCE_LIST_CAPACITY];
static AnimationSequence gAnimationSequences[32];
// 0x561814 seen_tile
static unsigned char gPathfinderProcessedTiles[5000];
@@ -449,7 +437,7 @@ static int _anim_free_slot(int requestOptions)
}
return -1;
} else if ((requestOptions & ANIMATION_REQUEST_RESERVED) != 0 || v2 < ANIMATION_NON_RESERVED_LIST_CAPACITY) {
} else if ((requestOptions & ANIMATION_REQUEST_RESERVED) != 0 || v2 < 20) {
return v1;
}
@@ -484,7 +472,7 @@ int reg_anim_clear(Object* a1)
int animationDescriptionIndex;
for (animationDescriptionIndex = 0; animationDescriptionIndex < animationSequence->length; animationDescriptionIndex++) {
AnimationDescription* animationDescription = &(animationSequence->animations[animationDescriptionIndex]);
if (a1 != animationDescription->owner || animationKindIsCallback(animationDescription->kind)) {
if (a1 != animationDescription->owner || animationDescription->kind == 11) {
continue;
}
@@ -598,7 +586,7 @@ static int _check_registry(Object* obj)
if (animationSequenceIndex != gAnimationSequenceCurrentIndex && animationSequence->step != ANIM_COMPLETE) {
for (int animationDescriptionIndex = 0; animationDescriptionIndex < animationSequence->length; animationDescriptionIndex++) {
AnimationDescription* animationDescription = &(animationSequence->animations[animationDescriptionIndex]);
if (obj == animationDescription->owner && !animationKindIsCallback(animationDescription->kind)) {
if (obj == animationDescription->owner && animationDescription->kind != 11) {
if ((animationSequence->flags & ANIM_SEQ_INSIGNIFICANT) == 0) {
return -1;
}
@@ -630,7 +618,7 @@ int animationIsBusy(Object* a1)
continue;
}
if (animationKindIsCallback(animationDescription->kind)) {
if (animationDescription->kind == ANIM_KIND_CALLBACK) {
continue;
}
@@ -1620,25 +1608,12 @@ static int _anim_set_end(int animationSequenceIndex)
}
}
std::array<Object*, ANIMATION_DESCRIPTION_LIST_CAPACITY> destroyedOwners = {};
std::size_t destroyedOwnersLength = 0;
auto ownerWasDestroyed = [&destroyedOwners, &destroyedOwnersLength](Object* owner) {
return std::find(destroyedOwners.begin(), destroyedOwners.begin() + destroyedOwnersLength, owner) != destroyedOwners.begin() + destroyedOwnersLength;
};
for (i = 0; i < animationSequence->length; i++) {
animationDescription = &(animationSequence->animations[i]);
if (animationDescription->kind == ANIM_KIND_HIDE && ((i < animationSequence->animationIndex) || (animationDescription->extendedFlags & ANIMATION_SEQUENCE_FORCED))) {
Object* owner = animationDescription->owner;
if (ownerWasDestroyed(owner)) {
continue;
}
destroyedOwners[destroyedOwnersLength++] = owner;
Rect rect;
int elevation = owner->elevation;
objectDestroy(owner, &rect);
int elevation = animationDescription->owner->elevation;
objectDestroy(animationDescription->owner, &rect);
tileWindowRefreshRect(&rect, elevation);
}
}
@@ -1647,23 +1622,18 @@ static int _anim_set_end(int animationSequenceIndex)
animationDescription = &(animationSequence->animations[i]);
if (animationDescription->artCacheKey) {
artUnlock(animationDescription->artCacheKey);
animationDescription->artCacheKey = nullptr;
}
if (!animationKindIsCallback(animationDescription->kind)) {
if (animationDescription->kind != 11 && animationDescription->kind != 12) {
// TODO: Check.
if (animationDescription->kind != ANIM_KIND_PING) {
Object* owner = animationDescription->owner;
if (ownerWasDestroyed(owner)) {
continue;
}
if (FID_TYPE(owner->fid) == OBJ_TYPE_CRITTER) {
int j = 0;
for (; j < i; j++) {
AnimationDescription* ad = &(animationSequence->animations[j]);
if (owner == ad->owner) {
if (!animationKindIsCallback(ad->kind)) {
if (ad->kind != ANIM_KIND_CALLBACK && ad->kind != ANIM_KIND_CALLBACK3) {
break;
}
}
@@ -3375,7 +3345,7 @@ static unsigned int animationComputeTicksPerFrame(Object* object, int fid)
}
}
return std::max(1000 / fps, 1);
return 1000 / fps;
}
int animationRegisterSetLightIntensity(Object* owner, int lightDistance, int lightIntensity, int delay)
+54 -140
View File
@@ -47,20 +47,6 @@ static int artReadFrameData(unsigned char* data, File* stream, int count, int* p
static int artReadHeader(Art* art, File* stream);
static int artGetDataSize(const Art* art);
static int paddingForSize(int size);
static char artGetCritterWeaponCode(int weaponType);
// A frame is laid out like [ArtFrame header][pixel bytes][padding].
// These functions return a pointer to the pixel bytes, but must be given a pointer to a frame header,
// not any ArtFrame pointer.
static unsigned char* artFrameData(ArtFrame* frame)
{
return reinterpret_cast<unsigned char*>(frame) + sizeof(*frame);
}
static const unsigned char* artFrameData(const ArtFrame* frame)
{
return reinterpret_cast<const unsigned char*>(frame) + sizeof(*frame);
}
// 0x5002D8 str2
static char gDefaultJumpsuitMaleFileName[] = "hmjmps";
@@ -532,7 +518,7 @@ unsigned char* artLockFrameData(int fid, int frame, int direction, CacheEntry**
frm = artGetFrame(art, frame, direction);
if (frm != nullptr) {
return artFrameData(frm);
return (unsigned char*)frm + sizeof(*frm);
}
}
@@ -584,7 +570,7 @@ int _art_get_code(int animation, int weaponType, char* weaponCodePtr, char* anim
return -1;
}
*weaponCodePtr = artGetCritterWeaponCode(weaponType);
*weaponCodePtr = 'd' + (weaponType - 1);
return 0;
} else if (animation == ANIM_PRONE_TO_STANDING) {
*animationCodePtr = 'h';
@@ -626,7 +612,7 @@ int _art_get_code(int animation, int weaponType, char* weaponCodePtr, char* anim
*weaponCodePtr = 'a';
*animationCodePtr = 'n';
} else {
*weaponCodePtr = artGetCritterWeaponCode(weaponType);
*weaponCodePtr = 'd' + (weaponType - 1);
*animationCodePtr = 'e';
}
return 0;
@@ -634,7 +620,7 @@ int _art_get_code(int animation, int weaponType, char* weaponCodePtr, char* anim
*animationCodePtr = 'a' + animation;
if (animation <= ANIM_WALK && weaponType > 0) {
*weaponCodePtr = artGetCritterWeaponCode(weaponType);
*weaponCodePtr = 'd' + (weaponType - 1);
return 0;
}
*weaponCodePtr = 'a';
@@ -642,24 +628,6 @@ int _art_get_code(int animation, int weaponType, char* weaponCodePtr, char* anim
return 0;
}
static char artGetCritterWeaponCode(int weaponType)
{
switch (weaponType) {
case WEAPON_ANIMATION_SFALL_S:
return 's';
case WEAPON_ANIMATION_SFALL_O:
return 'o';
case WEAPON_ANIMATION_SFALL_P:
return 'p';
case WEAPON_ANIMATION_SFALL_Q:
return 'q';
case WEAPON_ANIMATION_SFALL_T:
return 't';
default:
return 'd' + (weaponType - 1);
}
}
// 0x419428
char* artBuildFilePath(int fid)
{
@@ -839,9 +807,19 @@ int artGetSize(Art* art, int frame, int direction, int* widthPtr, int* heightPtr
}
// 0x419820
int artGetFrameOffsets(const Art* art, int frame, int direction, int* xPtr, int* yPtr)
int artGetFrameOffsets(Art* art, int frame, int direction, int* xPtr, int* yPtr)
{
return artGetFrameData(art, frame, direction, nullptr, nullptr, xPtr, yPtr) != nullptr ? 0 : -1;
ArtFrame* frm;
frm = artGetFrame(art, frame, direction);
if (frm == nullptr) {
return -1;
}
*xPtr = frm->x;
*yPtr = frm->y;
return 0;
}
// 0x41984C
@@ -860,33 +838,14 @@ int artGetRotationOffsets(Art* art, int rotation, int* xPtr, int* yPtr)
// 0x419870
unsigned char* artGetFrameData(Art* art, int frame, int direction)
{
return artGetFrameData(art, frame, direction, nullptr, nullptr, nullptr, nullptr);
}
ArtFrame* frm;
unsigned char* artGetFrameData(const Art* art, int frame, int direction, int* widthPtr, int* heightPtr, int* xOffsetPtr, int* yOffsetPtr)
{
ArtFrame* frm = artGetFrame(art, frame, direction);
frm = artGetFrame(art, frame, direction);
if (frm == nullptr) {
return nullptr;
}
if (widthPtr != nullptr) {
*widthPtr = frm->width;
}
if (heightPtr != nullptr) {
*heightPtr = frm->height;
}
if (xOffsetPtr != nullptr) {
*xOffsetPtr = frm->x;
}
if (yOffsetPtr != nullptr) {
*yOffsetPtr = frm->y;
}
return artFrameData(frm);
return (unsigned char*)frm + sizeof(*frm);
}
// 0x419880
@@ -913,10 +872,11 @@ ArtFrame* artGetFrame(const Art* art, int frame, int rotation)
ConstBuffer2D artGetFrameBuffer(const Art* art, int frame, int direction)
{
int width = 0;
int height = 0;
unsigned char* data = artGetFrameData(art, frame, direction, &width, &height, nullptr, nullptr);
return { data, width, height };
ArtFrame* frm = artGetFrame(art, frame, direction);
if (frm == nullptr) {
return { nullptr, 0, 0 };
}
return { reinterpret_cast<unsigned char*>(frm) + sizeof(*frm), frm->width, frm->height };
}
// 0x4198C8
@@ -976,11 +936,9 @@ int artAliasFid(int fid)
int anim = FID_ANIM_TYPE(fid);
if (type == OBJ_TYPE_CRITTER) {
if (anim == ANIM_ELECTRIFY
|| anim == ANIM_CHARRED_BODY
|| anim == ANIM_BURNED_TO_NOTHING
|| anim == ANIM_ELECTRIFIED_TO_NOTHING
|| anim == ANIM_ELECTRIFY_SF
|| anim == ANIM_CHARRED_BODY_SF
|| anim == ANIM_BURNED_TO_NOTHING_SF
|| anim == ANIM_ELECTRIFIED_TO_NOTHING_SF
|| anim == ANIM_FIRE_DANCE
@@ -1031,21 +989,10 @@ static int artCacheGetFileSizeImpl(int fid, int* sizePtr)
Art art;
if (artReadHeader(&art, stream) == 0) {
*sizePtr = artGetDataSize(&art);
if (*sizePtr < 0) {
debugPrint("ART ERROR: fid %d path %s returned negative data size %d\n", fid, artFilePath, *sizePtr);
} else if (*sizePtr > 0x10000) {
debugPrint("ART INFO: fid %d path %s has large data size %d\n", fid, artFilePath, *sizePtr);
}
result = 0;
} else {
debugPrint("ART ERROR: artReadHeader failed for fid %d path %s\n", fid, artFilePath);
}
fileClose(stream);
} else {
debugPrint("ART ERROR: could not open file for fid %d path %s\n", fid, artFilePath);
}
} else {
debugPrint("ART ERROR: could not build path for fid %d\n", fid);
}
return result;
@@ -1063,32 +1010,19 @@ static int artCacheReadDataImpl(int fid, int* sizePtr, unsigned char* data)
if (artGetLocalizedPath(artFileName, &localizedPath)) {
if (artRead(localizedPath, data) == 0) {
loaded = true;
} else {
debugPrint("ART INFO: artRead localized failed for fid %d path %s\n", fid, localizedPath);
}
}
if (!loaded) {
if (artRead(artFileName, data) == 0) {
loaded = true;
} else {
debugPrint("ART INFO: artRead fallback failed for fid %d path %s\n", fid, artFileName);
}
}
if (loaded) {
*sizePtr = artGetDataSize((Art*)data);
if (*sizePtr < 0) {
debugPrint("ART ERROR: fid %d path %s read data returned negative size %d\n", fid, artFileName, *sizePtr);
} else if (*sizePtr > 0x10000) {
debugPrint("ART INFO: fid %d path %s read large data size %d\n", fid, artFileName, *sizePtr);
}
result = 0;
} else {
debugPrint("ART ERROR: failed to load ART data for fid %d path %s\n", fid, artFileName);
}
} else {
debugPrint("ART ERROR: could not build path for fid %d\n", fid);
}
return result;
@@ -1173,20 +1107,6 @@ static int artReadHeader(Art* art, File* stream)
art->dataSize = fileGetSize(stream);
}
if (art->frameCount < 0) {
debugPrint("ART WARNING: negative frameCount %d in header\n", art->frameCount);
}
if (art->dataSize < 0) {
debugPrint("ART WARNING: negative dataSize %d in header\n", art->dataSize);
}
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
if (art->dataOffsets[rotation] < 0) {
debugPrint("ART WARNING: negative dataOffset[%d] %d in header\n", rotation, art->dataOffsets[rotation]);
}
}
return 0;
}
@@ -1285,18 +1205,12 @@ static Art* artAllocateSingleFrame(int width, int height, unsigned char** frameD
return nullptr;
}
if (pixelCount > static_cast<size_t>(INT_MAX) - sizeof(ArtFrame)) {
return nullptr;
}
Art header = {};
header.version = 4;
header.framesPerSecond = 10;
header.actionFrame = 0;
header.frameCount = 1;
// FRM dataSize excludes in-memory alignment padding; artGetDataSize adds
// the padding needed by artGetFrame's adjusted frame offsets.
header.dataSize = static_cast<int>(sizeof(ArtFrame) + pixelCount);
header.dataSize = sizeof(ArtFrame) + static_cast<int>(pixelCount);
int currentPadding = paddingForSize(sizeof(Art));
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
@@ -1321,7 +1235,7 @@ static Art* artAllocateSingleFrame(int width, int height, unsigned char** frameD
frame->x = 0;
frame->y = 0;
*frameDataPtr = artFrameData(frame);
*frameDataPtr = reinterpret_cast<unsigned char*>(frame) + sizeof(ArtFrame);
return art;
}
@@ -1477,13 +1391,6 @@ int artRead(const char* path, unsigned char* data)
return -3;
}
int totalAllocSize = artGetDataSize(art);
if (totalAllocSize <= 0) {
debugPrint("ART ERROR: artRead computed invalid totalAllocSize %d for %s\n", totalAllocSize, path);
fileClose(stream);
return -5;
}
int currentPadding = paddingForSize(sizeof(Art));
int previousPadding = 0;
@@ -1493,14 +1400,7 @@ int artRead(const char* path, unsigned char* data)
if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) {
art->padding[index] += previousPadding;
currentPadding += previousPadding;
long long frameDataOffset = static_cast<long long>(sizeof(Art)) + art->dataOffsets[index] + art->padding[index];
if (frameDataOffset < 0 || frameDataOffset >= totalAllocSize) {
debugPrint("ART ERROR: invalid frame data destination for %s at rotation %d: offset=%lld base=%p size=%d\n", path, index, frameDataOffset, data, totalAllocSize);
fileClose(stream);
return -5;
}
unsigned char* frameDataPtr = data + frameDataOffset;
if (artReadFrameData(frameDataPtr, stream, art->frameCount, &previousPadding) != 0) {
if (artReadFrameData(data + sizeof(Art) + art->dataOffsets[index] + art->padding[index], stream, art->frameCount, &previousPadding) != 0) {
fileClose(stream);
return -5;
}
@@ -1610,6 +1510,8 @@ public:
const Art* art() const { return _art.get(); }
ArtFrame* frame(int frame, int direction) const;
unsigned int mru = 0;
private:
@@ -1621,6 +1523,11 @@ NamedCacheEntry::NamedCacheEntry(ArtPtr&& art)
{
}
ArtFrame* NamedCacheEntry::frame(int frame, int direction) const
{
return artGetFrame(_art.get(), frame, direction);
}
std::shared_ptr<NamedCacheEntry> artLockNamedFrameData(const char* path)
{
auto it = gNamedArtCache.find(path);
@@ -1701,6 +1608,7 @@ ObjectType FrmId::objectType() const
FrmImage::FrmImage()
{
_key = nullptr;
_frame = nullptr;
_data = nullptr;
_width = 0;
_height = 0;
@@ -1716,6 +1624,7 @@ FrmImage::~FrmImage()
FrmImage::FrmImage(FrmImage&& other) noexcept
: _namedKey(std::move(other._namedKey))
, _key(other._key)
, _frame(other._frame)
, _data(other._data)
, _width(other._width)
, _height(other._height)
@@ -1731,6 +1640,7 @@ FrmImage& FrmImage::operator=(FrmImage&& other) noexcept
unlock();
_namedKey = std::move(other._namedKey);
_key = other._key;
_frame = other._frame;
_data = other._data;
_width = other._width;
_height = other._height;
@@ -1776,11 +1686,18 @@ bool FrmImage::lock(unsigned int fid, int frame, int direction)
return false;
}
if (!setFrame(art, frame, direction)) {
_frame = artGetFrame(art, frame, direction);
if (_frame == nullptr) {
unlock();
return false;
}
_data = reinterpret_cast<unsigned char*>(_frame) + sizeof(ArtFrame);
_width = _frame->width;
_height = _frame->height;
_xOffset = _frame->x;
_yOffset = _frame->y;
return true;
}
@@ -1798,11 +1715,18 @@ bool FrmImage::lock(const char* frmPath, int frame, int direction)
_namedKey = artLockNamedFrameData(frmPath);
if (!_namedKey) return false;
if (!setFrame(_namedKey->art(), frame, direction)) {
_frame = _namedKey->frame(frame, direction);
if (_frame == nullptr) {
unlock();
return false;
}
_data = reinterpret_cast<unsigned char*>(_frame) + sizeof(ArtFrame);
_width = _frame->width;
_height = _frame->height;
_xOffset = _frame->x;
_yOffset = _frame->y;
return true;
}
@@ -1832,6 +1756,7 @@ void FrmImage::resetInternal()
{
_namedKey = nullptr;
_key = nullptr;
_frame = nullptr;
_data = nullptr;
_width = 0;
_height = 0;
@@ -1839,15 +1764,4 @@ void FrmImage::resetInternal()
_yOffset = 0;
}
bool FrmImage::setFrame(const Art* art, int frame, int direction)
{
unsigned char* data = artGetFrameData(art, frame, direction, &_width, &_height, &_xOffset, &_yOffset);
if (data == nullptr) {
return false;
}
_data = data;
return true;
}
} // namespace fallout
+2 -8
View File
@@ -100,11 +100,6 @@ typedef enum WeaponAnimation {
WEAPON_ANIMATION_LASER_RIFLE, // k
WEAPON_ANIMATION_MINIGUN, // l
WEAPON_ANIMATION_LAUNCHER, // m
WEAPON_ANIMATION_SFALL_S, // s
WEAPON_ANIMATION_SFALL_O, // o
WEAPON_ANIMATION_SFALL_P, // p
WEAPON_ANIMATION_SFALL_Q, // q
WEAPON_ANIMATION_SFALL_T, // t
WEAPON_ANIMATION_COUNT,
} WeaponAnimation;
@@ -144,10 +139,9 @@ int artGetFrameCount(Art* art);
int artGetWidth(Art* art, int frame, int direction);
int artGetHeight(Art* art, int frame, int direction);
int artGetSize(Art* art, int frame, int direction, int* out_width, int* out_height);
int artGetFrameOffsets(const Art* art, int frame, int direction, int* xPtr, int* yPtr);
int artGetFrameOffsets(Art* art, int frame, int direction, int* xPtr, int* yPtr);
int artGetRotationOffsets(Art* art, int rotation, int* out_offset_x, int* out_offset_y);
unsigned char* artGetFrameData(Art* art, int frame, int direction);
unsigned char* artGetFrameData(const Art* art, int frame, int direction, int* widthPtr, int* heightPtr, int* xOffsetPtr, int* yOffsetPtr);
ArtFrame* artGetFrame(const Art* art, int frame, int direction);
ConstBuffer2D artGetFrameBuffer(const Art* art, int frame, int direction);
bool artExists(int fid);
@@ -226,10 +220,10 @@ public:
private:
void resetInternal();
bool setFrame(const Art* art, int frame, int direction);
std::shared_ptr<NamedCacheEntry> _namedKey;
CacheEntry* _key = nullptr;
ArtFrame* _frame = nullptr;
unsigned char* _data = nullptr;
int _width = 0;
int _height = 0;
+3 -18
View File
@@ -48,11 +48,6 @@ static int _copy_file_data(File* stream1, File* stream2, int length);
static int gAutomapWindow = -1;
static bool automapEntryIsValid(int map, int elevation)
{
return map >= 0 && map < AUTOMAP_MAP_COUNT && elevationIsValid(elevation);
}
typedef enum AutomapFrm {
AUTOMAP_FRM_BACKGROUND,
AUTOMAP_FRM_BUTTON_UP,
@@ -298,10 +293,6 @@ int automapSave(File* stream)
// 0x41B8B4 automapDisplayMap
int _automapDisplayMap(int map)
{
if (map < 0 || map >= AUTOMAP_MAP_COUNT) {
return -1;
}
return _displayMapList[map];
}
@@ -552,13 +543,13 @@ static void automapRenderInMapWindow(int window, int elevation, unsigned char* b
continue;
}
if (object->pid == PROTO_ID_EXIT_GRID_MAP_MARKER) {
if (object->pid == PROTO_ID_0x2000031) {
objectColor = _colorTable[32328];
} else if (objectType == OBJ_TYPE_WALL) {
objectColor = _colorTable[992];
} else if (objectType == OBJ_TYPE_SCENERY
&& (flags & AUTOMAP_WTH_HIGH_DETAILS) != 0
&& object->pid != PROTO_ID_BLOCK_HEX_AUTO_INVISO) {
&& object->pid != PROTO_ID_0x2000158) {
objectColor = _colorTable[480];
} else if (object == gDude) {
objectColor = _colorTable[31744];
@@ -705,9 +696,6 @@ int automapSaveCurrent()
{
int map = mapGetCurrentMap();
int elevation = gElevation;
if (!automapEntryIsValid(map, elevation)) {
return 0;
}
int entryOffset = gAutomapHeader.offsets[map][elevation];
if (entryOffset < 0) {
@@ -953,9 +941,6 @@ err:
static int automapLoadEntry(int map, int elevation)
{
gAutomapEntry.compressedData = nullptr;
if (!automapEntryIsValid(map, elevation)) {
return -1;
}
char path[COMPAT_MAX_PATH];
snprintf(path, sizeof(path), "%s\\%s", "MAPS", AUTOMAP_DB);
@@ -1105,7 +1090,7 @@ static void _decode_map_data(int elevation)
int contentType;
int objectType = FID_TYPE(object->fid);
if (objectType == OBJ_TYPE_SCENERY && object->pid != PROTO_ID_BLOCK_HEX_AUTO_INVISO) {
if (objectType == OBJ_TYPE_SCENERY && object->pid != PROTO_ID_0x2000158) {
contentType = 2;
} else if (objectType == OBJ_TYPE_WALL) {
contentType = 1;

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