Compare commits

...
Author SHA1 Message Date
Harley Folgado 8ff3bd9964 Add optional ammo counter display 2026-07-01 19:44:16 -04:00
Mike Klaas f3caac0bda Refactor inventory.cc (#491)
This adds major simplification and deduplication to inventory.cc, including the start of an "InventoryScroller" class that can manage inventory events without duplicate handling everywhere.  This is needed as we add more complexity to the page.
2026-06-29 21:38:38 -07:00
Mike Klaas fec5df8981 Fix missing argument to format string (#511)
* Add program->name to scriptError

This is @jirik2077 's fix for a segfault, and I agree with the fix.

Fixes #509
2026-06-29 20:41:50 -07:00
Mike Klaas 4715e0b349 Add in-game HELP menu (#498)
Also: fix alignment of skilldex, menu, charset buttons to look visibly depressed.
2026-06-28 22:23:07 -07:00
Mike Klaas 8ce4361365 Windows opcodes (#505)
* create_win / hide_window
* get_window_attributes
* get_window_under_mouse
* set_window_flag
* dialog_message
* display_stats
2026-06-28 15:18:06 -07:00
Mike Klaas 2447593e9c Fix crash due to corrupted whoHitMe (#493)
* Fix crash due to corrupted `whoHitMe`

It's possible for some object's `whoHitMe` to contain `-1`.  It's supposed to be resolved to `nullptr`, but doesn't reliably happen in all cases, esp outside of combat.  The main fix here is to normalize to nullptr when loading the object, instead of just map load, or combat paths.

At some point I thought the problem might have been attacker being a non-critter.  Added some guards against that regardless.  Also, found several bugs and messiness in `apply_damage` which are now fixed.

Fixes https://github.com/alexbatalov/fallout2-ce/issues/368
2026-06-24 20:36:55 -07:00
46 changed files with 1872 additions and 1138 deletions
+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
* Increased pathfinding nodes 5x for more accurate pathfinding, and use accurate path length for walk vs run
* Ctrl-click to quickly move items when bartering, looting, or stealing
* _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)
* _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
* 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))
+2 -2
View File
@@ -75,10 +75,10 @@ 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 | 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 / 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: get_window_under_mouse, create_win, get_window_attribute, message_box, set_window_flag, dialog_message, get_text_width, hide_window, show_window, create_message_window | - |
| 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 | implemented: critter_inven_obj2, item_weight | - |
| 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. |
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+1
View File
@@ -0,0 +1 @@
{0}{}{HELP}
+1
View File
@@ -0,0 +1 @@
{0}{}{AIDE}
+1
View File
@@ -0,0 +1 @@
{0}{}{HILFE}
+1
View File
@@ -0,0 +1 @@
{0}{}{AIUTO}
+1
View File
@@ -0,0 +1 @@
{0}{}{POMOC}
+1
View File
@@ -0,0 +1 @@
{0}{}{AJUDA}
+1
View File
@@ -0,0 +1 @@
{0}{}{ПОМОЩЬ}
+1
View File
@@ -0,0 +1 @@
{0}{}{AYUDA}
+10 -1
View File
@@ -39,7 +39,16 @@ IFACE_BAR_SIDES_ORI=0
;if IFACE_BAR_WIDTH=800 - Interface bar will use 800pix wide asset from f2_res.dat. Note
IFACE_BAR_WIDTH=640
; Controls ammo display in the HP/AC cluster.
; 0 = stock 1-pixel ammo bar only.
; 1 = numeric current/max ammo counter only.
; 2 = HRP 2-pixel ammo bar only.
; 3 = hidden, no stock bar and no numeric counter.
; 4 = stock 1-pixel ammo bar plus numeric counter.
; 5 = HRP 2-pixel ammo bar plus numeric counter.
IFACE_AMMO_COUNTER=0
;##################################################################################################################
[STATIC_SCREENS]
+2
View File
@@ -130,6 +130,8 @@ 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]
+9
View File
@@ -46,6 +46,8 @@ static void _decode_map_data(int elevation);
static int automapCreate();
static int _copy_file_data(File* stream1, File* stream2, int length);
static int gAutomapWindow = -1;
typedef enum AutomapFrm {
AUTOMAP_FRM_BACKGROUND,
AUTOMAP_FRM_BUTTON_UP,
@@ -326,6 +328,7 @@ void automapShow(bool isInGame, bool isUsingScanner)
int automapWindowY = (screenGetHeight() - AUTOMAP_WINDOW_HEIGHT) / 2;
// adding WINDOW_TRANSPARENT and WINDOW_DRAGGABLE_BY_BACKGROUND for testing temporarily
int window = windowCreate(automapWindowX, automapWindowY, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, color, WINDOW_MODAL | WINDOW_MOVE_ON_TOP | WINDOW_TRANSPARENT | WINDOW_DRAGGABLE_BY_BACKGROUND);
gAutomapWindow = window;
int scannerBtn = buttonCreate(window,
111,
@@ -493,10 +496,16 @@ void automapShow(bool isInGame, bool isUsingScanner)
}
windowDestroy(window);
gAutomapWindow = -1;
fontSetCurrent(oldFont);
touch_set_touchscreen_mode(false);
}
int automapGetWindow()
{
return windowGetWindow(gAutomapWindow) != nullptr ? gAutomapWindow : -1;
}
// Renders automap in Map window.
//
// 0x41BD1C draw_top_down_map
+1
View File
@@ -55,6 +55,7 @@ void automapShow(bool isInGame, bool isUsingScanner);
int automapRenderInPipboyWindow(int win, int map, int elevation);
int automapSaveCurrent();
int automapGetHeader(AutomapHeader** automapHeaderPtr);
int automapGetWindow();
void automapSetDisplayMap(int map, bool available);
+36 -10
View File
@@ -663,7 +663,7 @@ static int gCharacterEditorPrimaryStatMinusBtns[7];
static unsigned char* gCharacterEditorWindowBuffer;
// 0x57060C edit_win
static int gCharacterEditorWindow;
static int gCharacterEditorWindow = -1;
// + stats buttons
//
@@ -1436,24 +1436,27 @@ static int characterEditorWindowInit()
str = getmsg(&gCharacterEditorMessageList, &gCharacterEditorMessageListItem, 111);
strcpy(kills, str);
int tabLabelY = 5;
int inactiveTabLabelY = tabLabelY + 1;
// perks selected
len = fontGetStringWidth(perks);
fontDrawText(
gCharacterEditorFrmCopy[46] + 5 * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 61 - len / 2,
gCharacterEditorFrmCopy[46] + tabLabelY * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 61 - len / 2,
perks,
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
_colorTable[18979]);
len = fontGetStringWidth(karma);
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED] + 5 * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 159 - len / 2,
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED] + inactiveTabLabelY * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 159 - len / 2,
karma,
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
_colorTable[14723]);
len = fontGetStringWidth(kills);
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED] + 5 * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 257 - len / 2,
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED] + inactiveTabLabelY * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 257 - len / 2,
kills,
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
@@ -1461,21 +1464,21 @@ static int characterEditorWindowInit()
// karma selected
len = fontGetStringWidth(perks);
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + 5 * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 61 - len / 2,
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + inactiveTabLabelY * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 61 - len / 2,
perks,
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
_colorTable[14723]);
len = fontGetStringWidth(karma);
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + 5 * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 159 - len / 2,
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + tabLabelY * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 159 - len / 2,
karma,
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
_colorTable[18979]);
len = fontGetStringWidth(kills);
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + 5 * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 257 - len / 2,
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + inactiveTabLabelY * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 257 - len / 2,
kills,
_editorFrmImages[46].getWidth(),
_editorFrmImages[46].getWidth(),
@@ -1483,21 +1486,21 @@ static int characterEditorWindowInit()
// kills selected
len = fontGetStringWidth(perks);
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + 5 * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 61 - len / 2,
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + inactiveTabLabelY * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 61 - len / 2,
perks,
_editorFrmImages[46].getWidth(),
_editorFrmImages[46].getWidth(),
_colorTable[14723]);
len = fontGetStringWidth(karma);
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + 5 * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 159 - len / 2,
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + inactiveTabLabelY * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 159 - len / 2,
karma,
_editorFrmImages[46].getWidth(),
_editorFrmImages[46].getWidth(),
_colorTable[14723]);
len = fontGetStringWidth(kills);
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + 5 * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 257 - len / 2,
fontDrawText(gCharacterEditorFrmCopy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + tabLabelY * _editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth() + 257 - len / 2,
kills,
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
_editorFrmImages[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].getWidth(),
@@ -5680,6 +5683,29 @@ void characterEditorReset()
gCharacterEditorLastLevel = 1;
}
int characterEditorGetWindow()
{
return windowGetWindow(gCharacterEditorWindow) != nullptr ? gCharacterEditorWindow : -1;
}
void characterEditorDisplayStats()
{
if (windowGetWindow(gCharacterEditorWindow) == nullptr) {
return;
}
critterUpdateDerivedStats(gDude);
critterAdjustHitPoints(gDude, 0);
characterEditorDrawSkills(0);
characterEditorDrawPrimaryStat(RENDER_ALL_STATS, 0, 0);
characterEditorDrawPcStats();
characterEditorDrawDerivedStats();
characterEditorDrawFolders();
characterEditorDrawCard();
windowRefresh(gCharacterEditorWindow);
}
// level up if needed
//
// 0x43C228 UpdateLevel
+2
View File
@@ -14,6 +14,8 @@ char* _strmfe(char* dest, const char* name, const char* ext);
int characterEditorSave(File* stream);
int characterEditorLoad(File* stream);
void characterEditorReset();
int characterEditorGetWindow();
void characterEditorDisplayStats();
} // namespace fallout
+53 -64
View File
@@ -3537,7 +3537,7 @@ void attackInit(Attack* attack, Object* attacker, Object* defender, int hitMode,
attack->defenderFlags = 0;
attack->defenderKnockback = 0;
attack->extrasLength = 0;
attack->oops = defender;
attack->intendedTarget = defender;
}
// 0x422F3C
@@ -4336,7 +4336,7 @@ static int attackComputeCriticalFailure(Attack* attack)
int rounds = attackType == ATTACK_TYPE_RANGED ? attack->ammoQuantity : 1;
attackComputeDamage(attack, rounds, 2);
} else {
attack->defender = attack->oops;
attack->defender = attack->intendedTarget;
}
if (attack->defender != nullptr) {
@@ -4758,31 +4758,28 @@ void _apply_damage(Attack* attack, bool animated)
{
Object* attacker = attack->attacker;
bool attackerIsCritter = attacker != nullptr && FID_TYPE(attacker->fid) == OBJ_TYPE_CRITTER;
bool v5 = attack->defender != attack->oops;
bool hitUnintendedTarget = attack->defender != attack->intendedTarget;
if (attackerIsCritter && (attacker->data.critter.combat.results & DAM_DEAD) == 0) {
_set_new_results(attacker, attack->attackerFlags);
// TODO: Not sure about "attack->defender == attack->oops".
_damage_object(attacker, attack->attackerDamage, animated, attack->defender == attack->oops, attacker);
_damage_object(attacker, attack->attackerDamage, animated, hitUnintendedTarget, attacker);
}
Object* v7 = attack->oops;
if (v7 != nullptr && v7 != attack->defender) {
_combatai_notify_onlookers(v7);
// bystanders might flee
if (attack->intendedTarget != nullptr && hitUnintendedTarget) {
_combatai_notify_onlookers(attack->intendedTarget);
}
Object* defender = attack->defender;
bool defenderIsCritter = defender != nullptr && FID_TYPE(defender->fid) == OBJ_TYPE_CRITTER;
if (!defenderIsCritter && !v5) {
bool v9 = objectIsPartyMember(attack->defender) && objectIsPartyMember(attack->attacker) ? false : true;
if (v9) {
if (defender != nullptr) {
if (defender->sid != -1) {
scriptSetFixedParam(defender->sid, attack->attackerDamage);
scriptSetObjects(defender->sid, attack->attacker, attack->weapon);
scriptExecProc(defender->sid, SCRIPT_PROC_DAMAGE);
}
if (!defenderIsCritter && !hitUnintendedTarget) {
bool shouldRunDamageProc = !objectIsPartyMember(attack->defender) || !objectIsPartyMember(attack->attacker);
if (shouldRunDamageProc) {
if (defender != nullptr && defender->sid != -1) {
scriptSetFixedParam(defender->sid, attack->attackerDamage);
scriptSetObjects(defender->sid, attack->attacker, attack->weapon);
scriptExecProc(defender->sid, SCRIPT_PROC_DAMAGE);
}
}
}
@@ -4790,24 +4787,17 @@ void _apply_damage(Attack* attack, bool animated)
if (defenderIsCritter && (defender->data.critter.combat.results & DAM_DEAD) == 0) {
_set_new_results(defender, attack->defenderFlags);
if (defenderIsCritter) {
if (defenderIsCritter) {
if ((defender->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) {
if (!v5 || defender != gDude) {
critterSetWhoHitMe(defender, attack->attacker);
}
} else if (defender == attack->oops || defender->data.critter.combat.team != attack->attacker->data.critter.combat.team) {
_combatai_check_retaliation(defender, attack->attacker);
}
if ((defender->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) {
if (!hitUnintendedTarget || defender != gDude) {
critterSetWhoHitMe(defender, attack->attacker);
}
} else if (attackerIsCritter && (defender == attack->intendedTarget || defender->data.critter.combat.team != attack->attacker->data.critter.combat.team)) {
_combatai_check_retaliation(defender, attack->attacker);
}
scriptSetObjects(defender->sid, attack->attacker, attack->weapon);
_damage_object(defender, attack->defenderDamage, animated, attack->defender != attack->oops, attacker);
if (defenderIsCritter) {
_combatai_notify_onlookers(defender);
}
_damage_object(defender, attack->defenderDamage, animated, hitUnintendedTarget, attacker);
_combatai_notify_onlookers(defender);
if (attack->defenderDamage >= 0 && (attack->attackerFlags & DAM_HIT) != 0) {
scriptSetObjects(attack->attacker->sid, nullptr, attack->defender);
@@ -4824,14 +4814,13 @@ void _apply_damage(Attack* attack, bool animated)
if (defenderIsCritter) {
if ((obj->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) {
critterSetWhoHitMe(obj, attack->attacker);
} else if (obj->data.critter.combat.team != attack->attacker->data.critter.combat.team) {
} else if (attackerIsCritter && obj->data.critter.combat.team != attack->attacker->data.critter.combat.team) {
_combatai_check_retaliation(obj, attack->attacker);
}
}
scriptSetObjects(obj->sid, attack->attacker, attack->weapon);
// TODO: Not sure about defender == oops.
_damage_object(obj, attack->extrasDamage[index], animated, attack->defender == attack->oops, attack->attacker);
_damage_object(obj, attack->extrasDamage[index], animated, hitUnintendedTarget, attack->attacker);
_combatai_notify_onlookers(obj);
if (attack->extrasDamage[index] >= 0) {
@@ -4900,18 +4889,18 @@ static void _set_new_results(Object* critter, int flags)
}
}
// 0x425020
static void _damage_object(Object* a1, int damage, bool animated, int a4, Object* a5)
// 0x425020 damage_object
static void _damage_object(Object* target, int damage, bool animated, int hitUnintendedTarget, Object* damageSource)
{
if (a1 == nullptr) {
if (target == nullptr) {
return;
}
if (FID_TYPE(a1->fid) != OBJ_TYPE_CRITTER) {
if (FID_TYPE(target->fid) != OBJ_TYPE_CRITTER) {
return;
}
if (critterFlagCheck(a1->pid, CRITTER_INVULNERABLE)) {
if (critterFlagCheck(target->pid, CRITTER_INVULNERABLE)) {
return;
}
@@ -4919,50 +4908,50 @@ static void _damage_object(Object* a1, int damage, bool animated, int a4, Object
return;
}
critterAdjustHitPoints(a1, -damage);
critterAdjustHitPoints(target, -damage);
if (a1 == gDude) {
if (target == gDude) {
interfaceRenderHitPoints(animated);
}
a1->data.critter.combat.damageLastTurn += damage;
target->data.critter.combat.damageLastTurn += damage;
if (!a4) {
if (!hitUnintendedTarget) {
// TODO: Not sure about this one.
if (!objectIsPartyMember(a1) || !objectIsPartyMember(a5)) {
scriptSetFixedParam(a1->sid, damage);
scriptExecProc(a1->sid, SCRIPT_PROC_DAMAGE);
if (!objectIsPartyMember(target) || !objectIsPartyMember(damageSource)) {
scriptSetFixedParam(target->sid, damage);
scriptExecProc(target->sid, SCRIPT_PROC_DAMAGE);
}
}
if ((a1->data.critter.combat.results & DAM_DEAD) != 0) {
scriptSetObjects(a1->sid, a1->data.critter.combat.whoHitMe, nullptr);
scriptExecProc(a1->sid, SCRIPT_PROC_DESTROY);
itemDestroyAllHidden(a1);
if ((target->data.critter.combat.results & DAM_DEAD) != 0) {
scriptSetObjects(target->sid, target->data.critter.combat.whoHitMe, nullptr);
scriptExecProc(target->sid, SCRIPT_PROC_DESTROY);
itemDestroyAllHidden(target);
if (a1 != gDude) {
Object* whoHitMe = a1->data.critter.combat.whoHitMe;
if (target != gDude) {
Object* whoHitMe = target->data.critter.combat.whoHitMe;
if (whoHitMe == gDude || (whoHitMe != nullptr && whoHitMe->data.critter.combat.team == gDude->data.critter.combat.team)) {
bool scriptOverrides = false;
Script* scr;
if (scriptGetScript(a1->sid, &scr) != -1) {
if (scriptGetScript(target->sid, &scr) != -1) {
scriptOverrides = scr->scriptOverrides;
}
if (!scriptOverrides) {
_combat_exps += critterGetExp(a1);
killsIncByType(critterGetKillType(a1));
_combat_exps += critterGetExp(target);
killsIncByType(critterGetKillType(target));
}
}
}
if (a1->sid != -1) {
scriptRemove(a1->sid);
a1->sid = -1;
if (target->sid != -1) {
scriptRemove(target->sid);
target->sid = -1;
}
partyMemberRemove(a1);
scriptHooks_OnDeath(a1);
partyMemberRemove(target);
scriptHooks_OnDeath(target);
}
}
@@ -5034,11 +5023,11 @@ void _combat_display(Attack* attack)
char text[280];
if (attack->defender != nullptr
&& attack->oops != nullptr
&& attack->defender != attack->oops
&& attack->intendedTarget != nullptr
&& attack->defender != attack->intendedTarget
&& (attack->attackerFlags & DAM_HIT) != 0) {
if (FID_TYPE(attack->defender->fid) == OBJ_TYPE_CRITTER) {
if (attack->oops == gDude) {
if (attack->intendedTarget == gDude) {
// 608 (male) - Oops! %s was hit instead of you!
// 708 (female) - Oops! %s was hit instead of you!
messageListItem.num = baseMessageId + 8;
@@ -5048,7 +5037,7 @@ void _combat_display(Attack* attack)
} else {
// 509 (male) - Oops! %s were hit instead of %s!
// 559 (female) - Oops! %s were hit instead of %s!
const char* name = objectGetName(attack->oops);
const char* name = objectGetName(attack->intendedTarget);
messageListItem.num = baseMessageId + 9;
if (messageListGetItem(&gCombatMessageList, &messageListItem)) {
snprintf(text, sizeof(text), messageListItem.text, mainCritterName, name);
+2 -2
View File
@@ -103,7 +103,7 @@ typedef struct Attack {
Object* attacker;
int hitMode;
Object* weapon;
int attackHitLocation;
int attackHitLocation; // UNUSED?
int attackerDamage;
int attackerFlags;
int ammoQuantity;
@@ -114,7 +114,7 @@ typedef struct Attack {
int defenderDamage;
int defenderFlags;
int defenderKnockback;
Object* oops;
Object* intendedTarget; // mainTarget
int extrasLength;
Object* extras[EXPLOSION_TARGET_COUNT];
int extrasHitLocation[EXPLOSION_TARGET_COUNT];
+14 -4
View File
@@ -89,7 +89,6 @@ namespace fallout {
static int gameTakeScreenshot(int width, int height, unsigned char* buffer, unsigned char* palette);
static void gameFreeGlobalVars();
static bool tryLoadBaseCEModAtPath(const char* path, bool* found, bool* openFailed);
static void showHelp();
static int gameDbInit();
static void showSplash();
@@ -1186,12 +1185,21 @@ static void gameFreeGlobalVars()
}
// 0x443F74
static void showHelp()
void showHelp()
{
ScopedGameMode gm(GameMode::kHelp);
bool isoWasEnabled = isoDisable();
gameMouseObjectsHide();
bool gameMouseWasVisible;
if (isoWasEnabled) {
gameMouseWasVisible = gameMouseObjectsIsVisible();
} else {
gameMouseWasVisible = false;
}
if (gameMouseWasVisible) {
gameMouseObjectsHide();
}
gameMouseSetCursor(MOUSE_CURSOR_NONE);
@@ -1257,7 +1265,9 @@ static void showHelp()
colorCycleEnable();
}
gameMouseObjectsShow();
if (gameMouseWasVisible) {
gameMouseObjectsShow();
}
if (isoWasEnabled) {
isoEnable();

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