Compare commits

..
Author SHA1 Message Date
Mike Klaas 1194f0ec73 Generate sound list dynamically
Fixes crash when adding music without modifying the lst.

Contributed by @NovaRain
2026-07-20 20:39:47 -07:00
43 changed files with 509 additions and 990 deletions
+52 -29
View File
@@ -255,38 +255,23 @@ jobs:
path: out/dist/fallout2-ce-linux-${{ matrix.arch }}.tar.gz
retention-days: 7
linux-arm:
name: Linux (${{ matrix.arch }})
linux-armhf:
name: Linux (armhf)
runs-on: ubuntu-22.04-arm
needs: [ce-dat]
strategy:
fail-fast: false
matrix:
arch:
- armhf
- arm64
steps:
- name: Clone
uses: actions/checkout@v4
- name: Dependencies (armhf)
if: matrix.arch == 'armhf'
- name: Dependencies
run: |
sudo dpkg --add-architecture armhf
sudo apt update
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libsdl2-dev:armhf zlib1g-dev:armhf
- name: Dependencies (arm64)
if: matrix.arch == 'arm64'
run: |
sudo apt update
sudo apt install libsdl2-dev zlib1g-dev
- name: Configure (armhf)
if: matrix.arch == 'armhf'
- name: Configure
run: |
cmake \
-B build \
@@ -297,8 +282,46 @@ jobs:
-D CMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++ \
# EOL
- name: Configure (arm64)
if: matrix.arch == 'arm64'
- name: Build
run: cmake --build build
- name: Download ce.dat
uses: actions/download-artifact@v4
with:
name: ce-dat
path: out/dist
- name: Package
run: |
mkdir -p out/dist/fallout2-ce-linux-armhf
cp build/fallout2-ce out/dist/fallout2-ce-linux-armhf/fallout2-ce
cp out/dist/ce.dat out/dist/fallout2-ce-linux-armhf/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-linux-armhf/EXAMPLE_fallout2.cfg
tar -czvf out/dist/fallout2-ce-linux-armhf.tar.gz -C out/dist fallout2-ce-linux-armhf
- name: Upload
uses: actions/upload-artifact@v4
with:
name: fallout2-ce-linux-armhf
path: out/dist/fallout2-ce-linux-armhf.tar.gz
retention-days: 7
linux-arm64:
name: Linux (arm64)
runs-on: ubuntu-22.04-arm
needs: [ce-dat]
steps:
- name: Clone
uses: actions/checkout@v4
- name: Dependencies
run: |
sudo apt update
sudo apt install libsdl2-dev zlib1g-dev
- name: Configure
run: cmake -B build -D CMAKE_BUILD_TYPE=Debug -D FALLOUT_VENDORED=OFF
- name: Build
@@ -312,17 +335,17 @@ jobs:
- name: Package
run: |
mkdir -p out/dist/fallout2-ce-linux-${{ matrix.arch }}
cp build/fallout2-ce out/dist/fallout2-ce-linux-${{ matrix.arch }}/fallout2-ce
cp out/dist/ce.dat out/dist/fallout2-ce-linux-${{ matrix.arch }}/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-linux-${{ matrix.arch }}/EXAMPLE_fallout2.cfg
tar -czvf out/dist/fallout2-ce-linux-${{ matrix.arch }}.tar.gz -C out/dist fallout2-ce-linux-${{ matrix.arch }}
mkdir -p out/dist/fallout2-ce-linux-arm64
cp build/fallout2-ce out/dist/fallout2-ce-linux-arm64/fallout2-ce
cp out/dist/ce.dat out/dist/fallout2-ce-linux-arm64/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-linux-arm64/EXAMPLE_fallout2.cfg
tar -czvf out/dist/fallout2-ce-linux-arm64.tar.gz -C out/dist fallout2-ce-linux-arm64
- name: Upload
uses: actions/upload-artifact@v4
with:
name: fallout2-ce-linux-${{ matrix.arch }}
path: out/dist/fallout2-ce-linux-${{ matrix.arch }}.tar.gz
name: fallout2-ce-linux-arm64
path: out/dist/fallout2-ce-linux-arm64.tar.gz
retention-days: 7
macos:
@@ -428,7 +451,7 @@ jobs:
release:
name: Release Continuous build
runs-on: ubuntu-latest
needs: [android, ios, linux, linux-arm, macos, windows]
needs: [android, ios, linux, linux-armhf, linux-arm64, macos, windows]
permissions:
contents: write
steps:
+1 -1
View File
@@ -59,7 +59,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, get_terrain_name, set_terrain_name | - |
| 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 / 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 | âś… | - |
-10
View File
@@ -165,16 +165,6 @@ 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
; Weight/size indicator for PC, critters and containers
; Set to 0 for vanilla behavior (no weight indicator)
; Set to 1 to show a simple weight indicator; works with all inventory_columns values
; Set to 2 to show a detailed weight indicator; works with inventory_columns > 1 only
loot_weight_indicator=1
; Set to 0 to show container indicator at all times
; Set to XX to show container indicator when size reaches XX%
; Set to 100 to show container indicator when full
loot_container_size_indicator_threshold=50
[qol]
; Automatically open doors that are unlocked and has no script attached.
-27
View File
@@ -1,27 +0,0 @@
#include "test_utils.h"
#include "sfall.h"
procedure start begin
variable defaultName;
display_msg("Testing terrain name metarules...");
call assertEquals("get metarule exists", metarule_exist("get_terrain_name"), 1);
call assertEquals("set metarule exists", metarule_exist("set_terrain_name"), 1);
defaultName := get_terrain_name(0, 0);
call assertNotEquals("default terrain name is not error", defaultName, "Error");
call assertNotEquals("default terrain name is not empty", defaultName, "");
set_terrain_name(0, 0, "CE Terrain Override");
call assertEquals("terrain override is returned", get_terrain_name(0, 0), "CE Terrain Override");
set_terrain_name(0, 0, "CE Terrain Override 2");
call assertEquals("latest terrain override wins", get_terrain_name(0, 0), "CE Terrain Override 2");
call assertEquals("one argument get returns error", sfall_func1("get_terrain_name", 0), -1);
call assertEquals("invalid get returns error", get_terrain_name(-1, 0), "Error");
call assertNotEquals("current terrain name is available", get_current_terrain_name, "Error");
call report_test_results("terrain_name");
signal_close_game;
end
+1 -2
View File
@@ -21,11 +21,10 @@ procedure start begin
call assertEquals("opcode_exists OP_NOOP", opcode_exists(OP_NOOP), 1);
// Probably will never be implemented on fallout2-ce
call assertEquals("opcode_exists OP_WRITE_INT", opcode_exists(OP_WRITE_INT), 0);
call assertEquals("opcode_exists bare index", opcode_exists(0x1B3), 0);
call assertEquals("opcode_exists 0xFFFF", opcode_exists(0xFFFF), 0);
end else begin
display_msg("Opcode exists metarule is not available, skipping tests.");
end
call report_test_results("metarule_exist");
end
end
+1 -1
View File
@@ -3019,7 +3019,7 @@ int _check_move(int* actionPointsPtr)
if (isInCombat()) {
if (*actionPointsPtr != -1) {
if (gPressedPhysicalKeys[SDL_SCANCODE_RCTRL] || gPressedPhysicalKeys[SDL_SCANCODE_LCTRL]) {
HitMode hitMode;
int hitMode;
bool aiming;
interfaceGetCurrentHitMode(&hitMode, &aiming);
+1 -3
View File
@@ -1789,13 +1789,11 @@ void FrmImage::resetInternal()
bool FrmImage::setFrame(const Art* art, int frame, int direction)
{
unsigned char* data = artGetFrameData(art, frame, direction, &_width, &_height, nullptr, nullptr);
unsigned char* data = artGetFrameData(art, frame, direction, &_width, &_height, &_xOffset, &_yOffset);
if (data == nullptr) {
return false;
}
_xOffset = art->xOffsets[direction];
_yOffset = art->yOffsets[direction];
_data = data;
return true;
}
+76 -119
View File
File diff suppressed because it is too large Load Diff
+21 -21
View File
@@ -9,7 +9,7 @@
namespace fallout {
extern int _combatNumTurns;
extern CombatState gCombatState;
extern unsigned int gCombatState;
extern int _combat_free_move;
@@ -19,7 +19,7 @@ void combatExit();
int _find_cid(int a1, int a2, Object** a3, int a4);
int combatLoad(File* stream);
int combatSave(File* stream);
bool _combat_safety_invalidate_weapon(Object* attacker, Object* weapon, HitMode hitMode, Object* defender, int* safeDistancePtr);
bool _combat_safety_invalidate_weapon(Object* attacker, Object* weapon, int hitMode, Object* defender, int* safeDistancePtr);
bool _combatTestIncidentalHit(Object* attacker, Object* defender, Object* attackerFriend, Object* weapon);
Object* _combat_whose_turn();
void _combat_data_init(Object* obj);
@@ -34,19 +34,19 @@ void _combat_over_from_load();
void _combat_give_exps(int exp_points);
void _combat_turn_run();
void _combat(CombatStartData* csd);
void attackInit(Attack* attack, Object* attacker, Object* defender, HitMode hitMode, HitLocation hitLocation);
int _combat_attack(Object* attacker, Object* defender, HitMode hitMode, HitLocation hitLocation);
void attackInit(Attack* attack, Object* attacker, Object* defender, int hitMode, int hitLocation);
int _combat_attack(Object* attacker, Object* defender, int hitMode, int hitLocation);
int _combat_bullet_start(const Object* attacker, const Object* target);
void _compute_explosion_on_extras(Attack* attack, bool isFromAttacker, bool isGrenade, bool noDamage);
int _determine_to_hit(Object* a1, Object* a2, HitLocation hitLocation, HitMode hitMode);
int _determine_to_hit_no_range(Object* attacker, Object* defender, HitLocation hitLocation, HitMode hitMode, unsigned char* a5);
int _determine_to_hit_from_tile(Object* attacker, int tile, Object* defender, HitLocation hitLocation, HitMode hitMode);
int _determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode);
int _determine_to_hit_no_range(Object* attacker, Object* defender, int hitLocation, int hitMode, unsigned char* a5);
int _determine_to_hit_from_tile(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode);
void attackComputeDeathFlags(Attack* attack);
void _apply_damage(Attack* attack, bool animated);
void _combat_display(Attack* attack);
void _combat_anim_begin();
void _combat_anim_finished();
CombatBadShot _combat_check_bad_shot(Object* attacker, Object* defender, HitMode hitMode, bool aiming);
int _combat_check_bad_shot(Object* attacker, Object* defender, int hitMode, bool aiming);
bool _combat_to_hit(Object* target, int* accuracy);
void _combat_attack_this(Object* target);
void _combat_outline_on();
@@ -59,21 +59,21 @@ void _combat_delete_critter(Object* obj);
void _combatKillCritterOutsideCombat(Object* critter_obj, char* msg);
int combatGetTargetHighlight();
int criticalsGetValue(int killType, HitLocation hitLocation, int effect, CriticalHitDataMember dataMember);
void criticalsSetValue(int killType, HitLocation hitLocation, int effect, CriticalHitDataMember dataMember, int value);
void criticalsResetValue(int killType, HitLocation hitLocation, int effect, CriticalHitDataMember dataMember);
int criticalsGetValue(int killType, int hitLocation, int effect, int dataMember);
void criticalsSetValue(int killType, int hitLocation, int effect, int dataMember, int value);
void criticalsResetValue(int killType, int hitLocation, int effect, int dataMember);
bool criticalsNoTimeLimits();
int unarmedGetDamage(HitMode hitMode, int* minDamagePtr, int* maxDamagePtr);
int unarmedGetBonusCriticalChance(HitMode hitMode);
int unarmedGetActionPointCost(HitMode hitMode);
bool unarmedIsPenetrating(HitMode hitMode);
HitMode unarmedGetPunchHitMode(bool isSecondary);
HitMode unarmedGetKickHitMode(bool isSecondary);
bool unarmedIsPenetrating(HitMode hitMode);
int unarmedGetDamage(int hitMode, int* minDamagePtr, int* maxDamagePtr);
int unarmedGetBonusCriticalChance(int hitMode);
int unarmedGetActionPointCost(int hitMode);
bool unarmedIsPenetrating(int hitMode);
int unarmedGetPunchHitMode(bool isSecondary);
int unarmedGetKickHitMode(bool isSecondary);
bool unarmedIsPenetrating(int hitMode);
bool damageModGetBonusHthDamageFix();
bool damageModGetDisplayBonusDamage();
int combat_get_hit_location_penalty(HitLocation hitLocation);
void combat_set_hit_location_penalty(HitLocation hitLocation, int penalty);
int combat_get_hit_location_penalty(int hit_location);
void combat_set_hit_location_penalty(int hit_location, int penalty);
void combat_reset_hit_location_penalty();
Attack* combat_get_data();
@@ -82,7 +82,7 @@ static inline bool isInCombat()
return (gCombatState & COMBAT_STATE_IN_COMBAT) != 0;
}
static inline bool isUnarmedHitMode(HitMode hitMode)
static inline bool isUnarmedHitMode(int hitMode)
{
return hitMode == HIT_MODE_PUNCH
|| hitMode == HIT_MODE_KICK
+24 -30
View File
@@ -93,19 +93,19 @@ static Object* _ai_danger_source(Object* a1);
static bool aiHaveAmmo(Object* critter, Object* weapon, Object** ammoPtr);
static bool _caiHasWeapPrefType(AiPacket* ai, int attackType);
static Object* _ai_best_weapon(Object* a1, Object* a2, Object* a3, Object* a4);
static bool _ai_can_use_weapon(Object* critter, Object* weapon, HitMode hitMode);
static bool _ai_can_use_weapon(Object* critter, Object* weapon, int hitMode);
static bool aiCanUseItem(Object* obj, Object* a2);
static Object* _ai_search_environ(Object* critter, int itemType);
static Object* _ai_retrieve_object(Object* critter, Object* item);
static HitMode _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defender);
static int _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defender);
static int _ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, bool taunt);
static int _ai_move_closer(Object* a1, Object* a2, bool taunt);
static int _cai_retargetTileFromFriendlyFire(Object* source, Object* target, int* tilePtr);
static int _cai_retargetTileFromFriendlyFireSubFunc(AiRetargetData* aiRetargetData, int tile);
static bool _cai_attackWouldIntersect(Object* attacker, Object* defender, Object* attackerFriend, int tile, int* distance);
static int _ai_switch_weapons(Object* a1, HitMode* hitMode, Object** weapon, Object* a4);
static HitLocation _ai_called_shot(Object* attacker, Object* defender, HitMode hitMode);
static int _ai_attack(Object* attacker, Object* defender, HitMode hitMode);
static int _ai_switch_weapons(Object* a1, int* hitMode, Object** weapon, Object* a4);
static int _ai_called_shot(Object* attacker, Object* defender, int hitMode);
static int _ai_attack(Object* attacker, Object* defender, int hitMode);
static int _ai_try_attack(Object* a1, Object* a2);
static int _cai_get_min_hp(AiPacket* ai);
static int _ai_print_msg(Object* critter, int type);
@@ -1619,7 +1619,7 @@ static Object* _ai_danger_source(Object* a1)
// Check if we can attack it. No ammo and out of
// range are ok results, since we can reload weapon
// move closer if necessary.
CombatBadShot badShot = _combat_check_bad_shot(a1, critter, HIT_MODE_RIGHT_WEAPON_PRIMARY, false);
int badShot = _combat_check_bad_shot(a1, critter, HIT_MODE_RIGHT_WEAPON_PRIMARY, false);
if (badShot != COMBAT_BAD_SHOT_OK
&& badShot != COMBAT_BAD_SHOT_NO_AMMO
&& badShot != COMBAT_BAD_SHOT_OUT_OF_RANGE) {
@@ -1975,7 +1975,7 @@ static Object* _ai_best_weapon(Object* attacker, Object* weapon1, Object* weapon
}
// 0x4298EC
static bool _ai_can_use_weapon(Object* critter, Object* weapon, HitMode hitMode)
static bool _ai_can_use_weapon(Object* critter, Object* weapon, int hitMode)
{
bool result = critterCanUseWeapon(critter, weapon, hitMode);
@@ -2260,7 +2260,7 @@ static Object* _ai_retrieve_object(Object* critter, Object* item)
}
// 0x429DB4
static HitMode _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defender)
static int _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defender)
{
if (weapon == nullptr) {
return HIT_MODE_PUNCH;
@@ -2302,19 +2302,19 @@ static HitMode _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defen
break;
case AREA_ATTACK_MODE_BE_SURE:
if (_determine_to_hit(attacker, defender, HIT_LOCATION_TORSO, HIT_MODE_RIGHT_WEAPON_SECONDARY) >= 85
&& !_combat_safety_invalidate_weapon(attacker, weapon, HIT_MODE_RIGHT_WEAPON_SECONDARY, defender, nullptr)) {
&& !_combat_safety_invalidate_weapon(attacker, weapon, 3, defender, nullptr)) {
useSecondaryMode = true;
}
break;
case AREA_ATTACK_MODE_BE_CAREFUL:
if (_determine_to_hit(attacker, defender, HIT_LOCATION_TORSO, HIT_MODE_RIGHT_WEAPON_SECONDARY) >= 50
&& !_combat_safety_invalidate_weapon(attacker, weapon, HIT_MODE_RIGHT_WEAPON_SECONDARY, defender, nullptr)) {
&& !_combat_safety_invalidate_weapon(attacker, weapon, 3, defender, nullptr)) {
useSecondaryMode = true;
}
break;
case AREA_ATTACK_MODE_BE_ABSOLUTELY_SURE:
if (_determine_to_hit(attacker, defender, HIT_LOCATION_TORSO, HIT_MODE_RIGHT_WEAPON_SECONDARY) >= 95
&& !_combat_safety_invalidate_weapon(attacker, weapon, HIT_MODE_RIGHT_WEAPON_SECONDARY, defender, nullptr)) {
&& !_combat_safety_invalidate_weapon(attacker, weapon, 3, defender, nullptr)) {
useSecondaryMode = true;
}
break;
@@ -2574,7 +2574,7 @@ static int _cai_retargetTileFromFriendlyFireSubFunc(AiRetargetData* aiRetargetDa
// 0x42A518
static bool _cai_attackWouldIntersect(Object* attacker, Object* defender, Object* attackerFriend, int tile, int* distance)
{
HitMode hitMode = HIT_MODE_RIGHT_WEAPON_PRIMARY;
int hitMode = HIT_MODE_RIGHT_WEAPON_PRIMARY;
bool aiming = false;
if (attacker == gDude) {
interfaceGetCurrentHitMode(&hitMode, &aiming);
@@ -2601,7 +2601,7 @@ static bool _cai_attackWouldIntersect(Object* attacker, Object* defender, Object
}
// 0x42A5B8
static int _ai_switch_weapons(Object* attacker, HitMode* hitMode, Object** weapon, Object* defender)
static int _ai_switch_weapons(Object* attacker, int* hitMode, Object** weapon, Object* defender)
{
*weapon = nullptr;
*hitMode = HIT_MODE_PUNCH;
@@ -2639,9 +2639,9 @@ static int _ai_switch_weapons(Object* attacker, HitMode* hitMode, Object** weapo
}
// 0x42A670
static HitLocation _ai_called_shot(Object* attacker, Object* defender, HitMode hitMode)
static int _ai_called_shot(Object* attacker, Object* defender, int hitMode)
{
HitLocation hitLocation = HIT_LOCATION_TORSO;
int hitLocation = HIT_LOCATION_TORSO;
if (weaponGetActionPointCost(attacker, hitMode, true) <= attacker->data.critter.combat.ap) {
if (critterCanAim(attacker, hitMode)) {
@@ -2661,8 +2661,8 @@ static HitLocation _ai_called_shot(Object* attacker, Object* defender, HitMode h
}
if (critterGetStat(attacker, STAT_INTELLIGENCE) >= intelligenceRequired) {
hitLocation = static_cast<HitLocation>(randomBetween(HIT_LOCATION_HEAD, HIT_LOCATION_SPECIFIC_COUNT));
int chanceToHit = _determine_to_hit(attacker, defender, hitLocation, hitMode);
hitLocation = randomBetween(0, HIT_LOCATION_SPECIFIC_COUNT);
int chanceToHit = _determine_to_hit(attacker, defender, hitMode, hitLocation);
if (chanceToHit < ai->min_to_hit) {
hitLocation = HIT_LOCATION_TORSO;
}
@@ -2675,7 +2675,7 @@ static HitLocation _ai_called_shot(Object* attacker, Object* defender, HitMode h
}
// 0x42A748
static int _ai_attack(Object* attacker, Object* defender, HitMode hitMode)
static int _ai_attack(Object* attacker, Object* defender, int hitMode)
{
if (attacker->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) {
return -1;
@@ -2686,7 +2686,7 @@ static int _ai_attack(Object* attacker, Object* defender, HitMode hitMode)
reg_anim_end();
_combat_turn_run();
HitLocation hitLocation = _ai_called_shot(attacker, defender, hitMode);
int hitLocation = _ai_called_shot(attacker, defender, hitMode);
if (_combat_attack(attacker, defender, hitMode, hitLocation)) {
return -1;
}
@@ -2709,7 +2709,7 @@ static int _ai_try_attack(Object* attacker, Object* defender)
weapon = nullptr;
}
HitMode hitMode = _ai_pick_hit_mode(attacker, weapon, defender);
int hitMode = _ai_pick_hit_mode(attacker, weapon, defender);
int minToHit = aiGetPacket(attacker)->min_to_hit;
int actionPoints = attacker->data.critter.combat.ap;
@@ -2736,7 +2736,7 @@ static int _ai_try_attack(Object* attacker, Object* defender)
break;
}
CombatBadShot reason = _combat_check_bad_shot(attacker, defender, hitMode, false);
int reason = _combat_check_bad_shot(attacker, defender, hitMode, false);
if (reason == COMBAT_BAD_SHOT_NO_AMMO) {
// If the secondary attack (for example, burst fire) does not have enough ammo,
// try the primary attack, which may have a lower ammo cost.
@@ -2821,6 +2821,9 @@ static int _ai_try_attack(Object* attacker, Object* defender)
}
}
} else if (reason == COMBAT_BAD_SHOT_NOT_ENOUGH_AP || reason == COMBAT_BAD_SHOT_ARM_CRIPPLED || reason == COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED) {
// 3 - not enough action points
// 6 - crippled one arm for two-handed weapon
// 7 - both hands crippled
if (_ai_switch_weapons(attacker, &hitMode, &weapon, defender) == -1) {
return -1;
}
@@ -3590,15 +3593,6 @@ bool isWithinPerception(Object* watcher, Object* target)
// 0x42BB34
static int aiMessageListInit()
{
// Prevent chronic memory leak on preference/language filter reloads.
// If gCombatAiMessageList was already populated from a previous init cascade,
// we must deeply free its entries before re-initializing the layout,
// otherwise the subsequent load will overwrite active pointers and isolate strings in the heap.
if (gCombatAiMessageList.entries != nullptr && gCombatAiMessageList.entries_num > 0) {
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_COMBAT_AI, nullptr);
messageListFree(&gCombatAiMessageList);
}
if (!messageListInit(&gCombatAiMessageList)) {
return -1;
}
+32 -93
View File
@@ -12,38 +12,13 @@
namespace fallout {
enum CombatState : unsigned int {
COMBAT_STATE_OUT_COMBAT = 0x00,
typedef enum CombatState {
COMBAT_STATE_IN_COMBAT = 0x01,
COMBAT_STATE_PLAYER_TURN = 0x02,
COMBAT_STATE_EXIT_REQUESTED = 0x08,
};
} CombatState;
inline fallout::CombatState& operator&=(fallout::CombatState& lhs, unsigned int rhs)
{
lhs = static_cast<fallout::CombatState>(static_cast<unsigned int>(lhs) & rhs);
return lhs;
}
inline constexpr CombatState operator~(CombatState rhs)
{
return static_cast<CombatState>(~static_cast<unsigned int>(rhs));
}
inline CombatState& operator&=(CombatState& lhs, CombatState rhs)
{
lhs = static_cast<CombatState>(static_cast<unsigned int>(lhs) & static_cast<unsigned int>(rhs));
return lhs;
}
inline CombatState& operator|=(CombatState& lhs, CombatState rhs)
{
lhs = static_cast<CombatState>(static_cast<unsigned int>(lhs) | static_cast<unsigned int>(rhs));
return lhs;
}
enum HitMode : int {
HIT_MODE_INVALID = -1,
typedef enum HitMode {
HIT_MODE_LEFT_WEAPON_PRIMARY = 0,
HIT_MODE_LEFT_WEAPON_SECONDARY = 1,
HIT_MODE_RIGHT_WEAPON_PRIMARY = 2,
@@ -95,45 +70,21 @@ enum HitMode : int {
LAST_ADVANCED_KICK_HIT_MODE = HIT_MODE_PIERCING_KICK,
FIRST_ADVANCED_UNARMED_HIT_MODE = FIRST_ADVANCED_PUNCH_HIT_MODE,
LAST_ADVANCED_UNARMED_HIT_MODE = LAST_ADVANCED_KICK_HIT_MODE,
};
} HitMode;
inline static bool hitModeIsValid(int hitMode)
{
return hitMode >= HIT_MODE_LEFT_WEAPON_PRIMARY && hitMode < HIT_MODE_COUNT;
}
inline HitMode operator++(HitMode& e, int)
{
HitMode result = e;
e = static_cast<HitMode>(static_cast<int>(e) + 1);
return result;
}
enum HitLocation : int {
HIT_LOCATION_HEAD = 0,
HIT_LOCATION_LEFT_ARM = 1,
HIT_LOCATION_RIGHT_ARM = 2,
HIT_LOCATION_TORSO = 3,
HIT_LOCATION_RIGHT_LEG = 4,
HIT_LOCATION_LEFT_LEG = 5,
HIT_LOCATION_EYES = 6,
HIT_LOCATION_GROIN = 7,
HIT_LOCATION_UNCALLED = 8,
HIT_LOCATION_COUNT = 9,
typedef enum HitLocation {
HIT_LOCATION_HEAD,
HIT_LOCATION_LEFT_ARM,
HIT_LOCATION_RIGHT_ARM,
HIT_LOCATION_TORSO,
HIT_LOCATION_RIGHT_LEG,
HIT_LOCATION_LEFT_LEG,
HIT_LOCATION_EYES,
HIT_LOCATION_GROIN,
HIT_LOCATION_UNCALLED,
HIT_LOCATION_COUNT,
HIT_LOCATION_SPECIFIC_COUNT = HIT_LOCATION_COUNT - 1,
};
inline static bool hitLocationIsValid(int hitLocation)
{
return hitLocation >= HIT_LOCATION_HEAD && hitLocation < HIT_LOCATION_COUNT;
}
inline HitLocation operator++(HitLocation& e, int)
{
HitLocation result = e;
e = static_cast<HitLocation>(static_cast<int>(e) + 1);
return result;
}
} HitLocation;
typedef struct CombatStartData {
Object* attacker;
@@ -150,16 +101,16 @@ typedef struct CombatStartData {
typedef struct Attack {
Object* attacker;
HitMode hitMode;
int hitMode;
Object* weapon;
HitLocation attackHitLocation; // UNUSED?
int attackHitLocation; // UNUSED?
int attackerDamage;
int attackerFlags;
int ammoQuantity;
int criticalMessageId;
Object* defender;
int tile;
HitLocation defenderHitLocation;
int defenderHitLocation;
int defenderDamage;
int defenderFlags;
int defenderKnockback;
@@ -172,28 +123,16 @@ typedef struct Attack {
int extrasKnockback[EXPLOSION_TARGET_COUNT];
} Attack;
enum CriticalHitDataMember : int {
CRIT_DATA_MEMBER_DAMAGE_MULTIPLIER = 0,
CRIT_DATA_MEMBER_FLAGS = 1,
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_STAT = 2,
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_STAT_MODIFIER = 3,
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_FLAGS = 4,
CRIT_DATA_MEMBER_MESSAGE_ID = 5,
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_MESSAGE_ID = 6,
CRIT_DATA_MEMBER_COUNT = 7,
};
inline static bool criticalHitDataMemberIsValid(int criticalHitDataMember)
{
return criticalHitDataMember >= CRIT_DATA_MEMBER_DAMAGE_MULTIPLIER && criticalHitDataMember < CRIT_DATA_MEMBER_COUNT;
}
inline CriticalHitDataMember operator++(CriticalHitDataMember& e, int)
{
CriticalHitDataMember result = e;
e = static_cast<CriticalHitDataMember>(static_cast<int>(e) + 1);
return result;
}
typedef enum CriticalHitDescriptionDataMember {
CRIT_DATA_MEMBER_DAMAGE_MULTIPLIER,
CRIT_DATA_MEMBER_FLAGS,
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_STAT,
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_STAT_MODIFIER,
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_FLAGS,
CRIT_DATA_MEMBER_MESSAGE_ID,
CRIT_DATA_MEMBER_MASSIVE_CRITICAL_MESSAGE_ID,
CRIT_DATA_MEMBER_COUNT,
} CriticalHitDescriptionDataMember;
// Provides metadata about critical hit effect.
typedef union CriticalHitDescription {
@@ -221,7 +160,7 @@ typedef union CriticalHitDescription {
int values[CRIT_DATA_MEMBER_COUNT];
} CriticalHitDescription;
enum CombatBadShot : int {
typedef enum CombatBadShot {
COMBAT_BAD_SHOT_OK = 0,
COMBAT_BAD_SHOT_NO_AMMO = 1,
COMBAT_BAD_SHOT_OUT_OF_RANGE = 2,
@@ -229,8 +168,8 @@ enum CombatBadShot : int {
COMBAT_BAD_SHOT_ALREADY_DEAD = 4,
COMBAT_BAD_SHOT_AIM_BLOCKED = 5,
COMBAT_BAD_SHOT_ARM_CRIPPLED = 6,
COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED = 7
};
COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED = 7,
} CombatBadShot;
} // namespace fallout
+15 -32
View File
@@ -17,7 +17,7 @@
namespace fallout {
#define CONFIG_FILE_MAX_LINE_LENGTH (2048)
#define CONFIG_FILE_MAX_LINE_LENGTH (1024)
// The initial number of sections (or key-value) pairs in the config.
#define CONFIG_INITIAL_CAPACITY (10)
@@ -32,10 +32,9 @@ struct CaseInsensitiveLess {
typedef std::set<std::string, CaseInsensitiveLess> StringSet;
static bool configParseLine(Config* config, char* string);
static bool configParseKeyValue(char* string, std::string& key, std::string& value);
static bool configParseKeyValue(char* string, char* key, char* value);
static bool configEnsureSectionExists(Config* config, const char* sectionKey);
static bool configTrimString(char* string);
static std::string configGetTrimmedString(const char* string);
static bool configWriteDb(Config* config, const char* filePath);
static bool configWriteStandard(Config* config, const char* filePath);
@@ -121,10 +120,10 @@ bool configParseCommandLineArguments(Config* config, int argc, char** argv)
*pch = '\0';
std::string key;
std::string value;
char key[260];
char value[260];
if (configParseKeyValue(pch + 1, key, value)) {
if (!configSetString(config, sectionKey, key.c_str(), value.c_str())) {
if (!configSetString(config, sectionKey, key, value)) {
*pch = ']';
return false;
}
@@ -684,13 +683,13 @@ static bool configParseLine(Config* config, char* string)
}
}
std::string key;
std::string value;
char key[260];
char value[260];
if (!configParseKeyValue(string, key, value)) {
return false;
}
return configSetString(config, gConfigLastSectionKey, key.c_str(), value.c_str());
return configSetString(config, gConfigLastSectionKey, key, value);
}
// Splits "key=value" pair from [string] and copy appropriate parts into [key]
@@ -699,9 +698,9 @@ static bool configParseLine(Config* config, char* string)
// Both key and value are trimmed.
//
// 0x42C594
static bool configParseKeyValue(char* string, std::string& key, std::string& value)
static bool configParseKeyValue(char* string, char* key, char* value)
{
if (string == nullptr) {
if (string == nullptr || key == nullptr || value == nullptr) {
return false;
}
@@ -713,11 +712,14 @@ static bool configParseKeyValue(char* string, std::string& key, std::string& val
*pch = '\0';
key = configGetTrimmedString(string);
value = configGetTrimmedString(pch + 1);
strcpy(key, string);
strcpy(value, pch + 1);
*pch = '=';
configTrimString(key);
configTrimString(value);
return true;
}
@@ -789,25 +791,6 @@ static bool configTrimString(char* string)
return true;
}
static std::string configGetTrimmedString(const char* string)
{
if (string == nullptr) {
return std::string();
}
const char* start = string;
while (isspace(static_cast<unsigned char>(*start))) {
start++;
}
const char* end = start + strlen(start);
while (end > start && isspace(static_cast<unsigned char>(*(end - 1)))) {
end--;
}
return std::string(start, end);
}
// 0x42C718
bool configGetDouble(Config* config, const char* sectionKey, const char* key, double* valuePtr)
{
+1 -1
View File
@@ -1024,7 +1024,7 @@ int critterGetBodyType(Object* critter)
return proto->critter.data.bodyType;
}
bool critterCanUseWeapon(Object* critter, Object* weapon, HitMode hitMode)
bool critterCanUseWeapon(Object* critter, Object* weapon, int hitMode)
{
if (critter == nullptr || weapon == nullptr || itemGetType(weapon) != ITEM_TYPE_WEAPON) {
return false;
+1 -2
View File
@@ -1,7 +1,6 @@
#ifndef CRITTER_H
#define CRITTER_H
#include "combat_defs.h"
#include "db.h"
#include "obj_types.h"
#include "proto_types.h"
@@ -57,7 +56,7 @@ bool critterIsProne(Object* critter);
int critterGetBodyType(Object* critter);
// Checks physical/art capability only. Callers that expose weapon usability
// decisions must still call scriptHooks_CanUseWeapon with the final result.
bool critterCanUseWeapon(Object* critter, Object* weapon, HitMode hitMode);
bool critterCanUseWeapon(Object* critter, Object* weapon, int hitMode);
int critterBuildGorisFid(Object* critter, int frmId);
int gcdLoad(const char* path);
int protoCritterDataRead(File* stream, CritterProtoData* critterData);
-5
View File
@@ -3500,11 +3500,6 @@ int gameDialogGetWindow()
return windowGetWindow(gGameDialogWindow) != nullptr ? gGameDialogWindow : -1;
}
int gameDialogGetBackgroundWindow()
{
return windowGetWindow(gGameDialogBackgroundWindow) != nullptr ? gGameDialogBackgroundWindow : -1;
}
// 0x448660 gdialog_barter_cleanup_tables
void gameDialogBarterCleanupTables()
{
-1
View File
@@ -41,7 +41,6 @@ int gameDialogBarter(int modifier);
void gameDialogEndBarter();
bool gameDialogIsBarterWindowExpanded();
int gameDialogGetWindow();
int gameDialogGetBackgroundWindow();
} // namespace fallout
+25 -38
View File
@@ -704,24 +704,6 @@ void gameMouseRefresh()
int primaryAction = -1;
switch (FID_TYPE(pointedObject->fid)) {
case OBJ_TYPE_SCENERY:
case OBJ_TYPE_WALL:
case OBJ_TYPE_MISC: {
if (_obj_action_can_use(pointedObject)) {
primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_USE;
break;
}
primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;
// if the pointedObject is scenery/wall/misc object then it finds possible outlined item object behind it
Object* itemObject = gameMouseGetObjectUnderCursor(OBJ_TYPE_ITEM, true, gElevation);
if (itemObject == nullptr) {
break;
}
pointedObject = itemObject;
}
// FALLTHROUGH
case OBJ_TYPE_ITEM:
primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_USE;
if (gGameMouseItemHighlightEnabled) {
@@ -751,6 +733,16 @@ void gameMouseRefresh()
}
}
break;
case OBJ_TYPE_SCENERY:
if (!_obj_action_can_use(pointedObject)) {
primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;
} else {
primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_USE;
}
break;
case OBJ_TYPE_WALL:
primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;
break;
}
if (primaryAction != -1) {
@@ -1001,25 +993,6 @@ void _gmouse_handle_event(int mouseX, int mouseY, int mouseState)
Object* targetObj = gameMouseGetObjectUnderCursor(-1, true, gElevation);
if (targetObj != nullptr) {
switch (FID_TYPE(targetObj->fid)) {
case OBJ_TYPE_WALL:
case OBJ_TYPE_SCENERY:
case OBJ_TYPE_MISC: {
// if the targetObj is wall/scenery/misc object then it allows to pickup outlined items potentially behind
Object* itemObj = gameMouseGetObjectUnderCursor(OBJ_TYPE_ITEM, true, gElevation);
if (!objectIsOutlined(itemObj)) {
if (_obj_action_can_use(targetObj)) {
_action_use_an_object(gDude, targetObj);
}
break;
}
targetObj = itemObj;
if (objectExamine(gDude, targetObj) == -1) {
objectLookAt(gDude, targetObj);
}
}
// FALLTHROUGH
case OBJ_TYPE_ITEM:
actionPickUp(gDude, targetObj);
break;
@@ -1045,6 +1018,20 @@ void _gmouse_handle_event(int mouseX, int mouseY, int mouseState)
}
}
break;
case OBJ_TYPE_SCENERY:
if (_obj_action_can_use(targetObj)) {
_action_use_an_object(gDude, targetObj);
} else {
if (objectExamine(gDude, targetObj) == -1) {
objectLookAt(gDude, targetObj);
}
}
break;
case OBJ_TYPE_WALL:
if (objectExamine(gDude, targetObj) == -1) {
objectLookAt(gDude, targetObj);
}
break;
}
}
return;
@@ -1075,7 +1062,7 @@ void _gmouse_handle_event(int mouseX, int mouseY, int mouseState)
Object* weapon;
if (interfaceGetActiveItem(&weapon) != -1) {
if (isInCombat()) {
HitMode hitMode = interfaceGetCurrentHand()
int hitMode = interfaceGetCurrentHand()
? HIT_MODE_RIGHT_WEAPON_PRIMARY
: HIT_MODE_LEFT_WEAPON_PRIMARY;
+1 -1
View File
@@ -1366,7 +1366,7 @@ char* gameSoundBuildInterfaceName(const char* a1)
// sfx_build_weapon_name
// 0x451760
char* sfxBuildWeaponName(int effectType, Object* weapon, HitMode hitMode, Object* target)
char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* target)
{
int soundVariant;
char weaponSoundCode;
+1 -2
View File
@@ -1,7 +1,6 @@
#ifndef GAME_SOUND_H
#define GAME_SOUND_H
#include "combat_defs.h"
#include "obj_types.h"
#include "sound.h"
@@ -103,7 +102,7 @@ int _gsound_compute_relative_volume(Object* obj);
char* sfxBuildCharName(Object* object, int anim, int weaponAnimationCode);
char* gameSoundBuildAmbientSoundEffectName(const char* name);
char* gameSoundBuildInterfaceName(const char* name);
char* sfxBuildWeaponName(int effectType, Object* weapon, HitMode hitMode, Object* target);
char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* target);
char* sfxBuildSceneryName(int actionType, int action, const char* name);
char* sfxBuildOpenName(Object* object, int action);
void _gsound_red_butt_press(int btn, int keyCode);
+10 -10
View File
@@ -99,8 +99,8 @@ typedef struct InterfaceItemState {
Object* item;
unsigned char isDisabled;
unsigned char isWeapon;
HitMode primaryHitMode;
HitMode secondaryHitMode;
int primaryHitMode;
int secondaryHitMode;
int action;
int itemFid;
} InterfaceItemState;
@@ -1086,7 +1086,7 @@ void interfaceRenderActionPoints(int actionPointsLeft, int bonusActionPoints)
}
// 0x45EF6C intface_get_attack
int interfaceGetCurrentHitMode(HitMode* hitMode, bool* aiming)
int interfaceGetCurrentHitMode(int* hitMode, bool* aiming)
{
if (gInterfaceBarWindow == -1) {
return -1;
@@ -1350,7 +1350,7 @@ void _intface_use_item()
if (ptr->isWeapon != 0) {
if (ptr->action == INTERFACE_ITEM_ACTION_RELOAD) {
if (isInCombat()) {
HitMode hitMode = gInterfaceCurrentHand == HAND_LEFT
int hitMode = gInterfaceCurrentHand == HAND_LEFT
? HIT_MODE_LEFT_WEAPON_RELOAD
: HIT_MODE_RIGHT_WEAPON_RELOAD;
@@ -1668,7 +1668,7 @@ static int interfaceBarRefreshMainAction()
} else {
int primaryFid = -1;
int bullseyeFid = -1;
HitMode hitMode = HIT_MODE_INVALID;
int hitMode = -1;
// NOTE: This value is decremented at 0x45FEAC, probably to build
// jump table.
@@ -2943,7 +2943,7 @@ static void sidePanelsDraw(const char* path, int win, bool isLeading)
// modes, the default is `punch`).
//
// 0x45EF6C intface_get_attack
bool interface_get_current_attack_mode(HitMode* hitMode)
bool interface_get_current_attack_mode(int* hit_mode)
{
if (gInterfaceBarWindow == -1) {
return false;
@@ -2952,19 +2952,19 @@ bool interface_get_current_attack_mode(HitMode* hitMode)
switch (gInterfaceItemStates[gInterfaceCurrentHand].action) {
case INTERFACE_ITEM_ACTION_PRIMARY_AIMING:
case INTERFACE_ITEM_ACTION_PRIMARY:
*hitMode = gInterfaceItemStates[gInterfaceCurrentHand].primaryHitMode;
*hit_mode = gInterfaceItemStates[gInterfaceCurrentHand].primaryHitMode;
break;
case INTERFACE_ITEM_ACTION_SECONDARY_AIMING:
case INTERFACE_ITEM_ACTION_SECONDARY:
*hitMode = gInterfaceItemStates[gInterfaceCurrentHand].secondaryHitMode;
*hit_mode = gInterfaceItemStates[gInterfaceCurrentHand].secondaryHitMode;
break;
case INTERFACE_ITEM_ACTION_RELOAD:
*hitMode = gInterfaceCurrentHand == HAND_LEFT
*hit_mode = gInterfaceCurrentHand == HAND_LEFT
? HIT_MODE_LEFT_WEAPON_RELOAD
: HIT_MODE_RIGHT_WEAPON_RELOAD;
break;
default:
*hitMode = HIT_MODE_PUNCH;
*hit_mode = HIT_MODE_PUNCH;
break;
}

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