Compare commits

..
Author SHA1 Message Date
phobos2077 1e3cd38a8f Add GEMINI.md and .gemini to gitignore
Verified to work well for simple tasks with Gemin CLI
2026-04-11 13:31:04 +02:00
phobos2077 50d6b14ff3 Vibe code test: move radiation levels and penalties to external config 2026-04-11 13:26:25 +02:00
phobos2077 4c53a5b85f Refactor settings save/load to use registry pattern
- Some AI work, needs review
2026-04-10 00:41:56 +02:00
45 changed files with 502 additions and 746 deletions
+10
View File
@@ -17,5 +17,15 @@
- *Preview builds are not available for pull requests from forked repositories.* To enable preview builds, submit the PR from a branch within this repository.
- *Formatting issues? You have two options:*
1. **Fix formatting automatically** using the following Docker command: *(If you're using Windows, remove `--user $(id -u):$(id -g)`)*
```bash
docker run --rm \
-v $(pwd):/app --workdir /app \
--user $(id -u):$(id -g) silkeh/clang:18 \
bash -c 'find src -type f -name \*.cc -o -name \*.h | xargs clang-format -i'
```
2. **Skip formatting check** by adding the `skip-formatting` label to this PR.
--->
+4
View File
@@ -399,3 +399,7 @@ FodyWeavers.xsd
# versioning header
src/platform/git_version.h
CMakeUserPresets.json
# Local configs for AI agents
.aider*
.gemini
+44
View File
@@ -0,0 +1,44 @@
# Fallout 2 Community Edition - Gemini Instructions
## Project Overview
Fallout 2 Community Edition is a re-implementation of Fallout 2. It is written in C++ and uses CMake as its build system.
## Project Rules
- Only search for source code and symbols inside the `src/` directory.
## Coding Standards & Conventions
- **Language**: C++17.
- **Style**: Follow WebKit style as defined in `.clang-format`.
- Use `clang-format` before committing any changes.
- Short `if` statements without `else` can be on a single line.
- Fix namespace comments.
- **File Naming**:
- Source files: `.cc`
- Header files: `.h`
- **Architectural Constraint**:
- Don't use complicated and rarely used C++ features, unless they are well encapsulated within reusable code.
- Avoid large-scale refactorings. The project aims to reconcile changes from the Reference Edition, and major structural changes make this difficult.
- **Integration Priority**: Integrating features and fixes from Sfall is a top priority.
## Build System (CMake)
- **Version**: Requires CMake 3.18 or higher.
- **Dependencies**: Uses vendored third-party libraries by default (`FALLOUT_VENDORED=ON`).
- **Target**: The main executable is `fallout2-ce`.
- **Platforms**: Supports Windows, Linux, macOS, Android, iOS, and Emscripten (WebAssembly).
## Project Structure
- `src/`: Contains all core game logic and engine implementation.
- `third_party/`: Vendored dependencies (zlib, SDL2, etc.).
- `os/`: Platform-specific assets and configuration (icons, plists, etc.).
- `cmake/`: Custom CMake scripts.
- `files/`: Default configuration files and data assets.
- `sfall_testing/`: SSL scripts for testing Sfall-related functionality.
## Tool Usage
- **Prioritize CLion MCP**: Always use the CLion MCP tools (e.g., `mcp_clion_search_symbols`, `mcp_clion_get_symbol_info`) for finding code definitions and symbols instead of performing manual text-based searches with `grep_search` or `glob`. Use manual text searches only when symbol-based search is not applicable (e.g., searching for non-code text or specific patterns).
## Development Workflow
1. **Research**: Understand how a feature was implemented in the original engine or Sfall before attempting to port or fix it.
2. **Strategy**: Maintain compatibility with original game data and savegames.
3. **Execution**: Apply surgical changes to `src/`.
4. **Validation**: Ensure changes compile across platforms if possible, or at least don't break the build for the current platform.
+8 -10
View File
@@ -16,7 +16,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Skills | get/set_critter_skill_points<br>get/set_available_skill_points<br>set_skill_max<br>set_critter_skill_mod<br>set_base_skill_mod<br>mod_skill_points_per_level | not implemented | - |
| Graphics | graphics_funcs_available<br>force_graphics_refresh<br>get_screen_width<br>get_screen_height<br>set_palette | implemented: only get_screen_width, get_screen_height | - |
| Shaders | load_shader<br>free_shader<br>activate_shader<br>deactivate_shader<br>set/get_shader_* | đźš« | likely will not implement direct compatibility
| Perks and traits | get_perk_owed<br>set_perk_owed<br>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 | implemented: only get_perk_owed, set_perk_owed | - |
| Perks and traits | set_perk_image<br>set_perk_*<br>set_pyromaniac_mod<br>apply_heaveho_fix<br>set_swiftlearner_mod<br>set_fake_perk<br>set_fake_trait<br>set_selectable_perk<br>set_perkbox_title<br>hide_real_perks<br>show_real_perks<br>perk_add_mode<br>clear_selectable_perks<br>has_fake_perk<br>has_fake_trait<br>add_trait<br>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<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 | - |
@@ -27,7 +27,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Utility / Math | log, exponent, round, sqrt, abs, sin, cos, tan, arctan, ceil, ^, floor2, div | âś… | - |
| Keyboard and mouse | key_pressed<br>tap_key<br>get_mouse_x/y<br>get_mouse_buttons | âś… | - |
| 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 | - |
| Explosions | metarule2_explosions<br>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>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 | âś… except objects_in_radius | - |
@@ -38,21 +38,19 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Hooks / Hook functions | init_hook<br>get_sfall_arg<br>get_sfall_args<br>get_sfall_arg_at<br>set_sfall_return<br>set_sfall_arg<br>register_hook<br>register_hook_proc<br>register_hook_proc_spec | âś… | See below for implemented hooks. `init_hook` is deprecated and will not be implemented. register_hook_proc and register_hook_proc_spec both add hooks to the *end* of the hook list, instead of beginning and end, respectively. |
| Arrays / Array functions | create_array<br>temp_array<br>fix_array<br>get/set_array<br>resize_array<br>free_array<br>scan_array<br>len_array<br>save/load_array<br>array_key<br>arrayexpr | âś… except save_array, load_array | - |
| Perks and traits / NPC perks | set_fake_perk_npc<br>set_fake_trait_npc<br>set_selectable_perk_npc<br>has_fake_perk_npc<br>has_fake_trait_npc | not implemented | - |
| Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | âś… except available_global_script_types | - |
| Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | implemented: all except available_global_script_types | - |
| Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | implemented: only get_attack_type, get_bodypart_hit_modifier, combat_data, set_bodypart_hit_modifier | - |
| Car | set_car_current_town<br>car_gas_amount<br>set_car_intface_art | implemented: all except set_car_intface_art | - |
| Interface / Windows and images | art_frame_data<br>interface_art_draw<br>interface_print<br>draw_image<br>draw_image_scaled<br>get_window_under_mouse<br>create_win<br>get_window_attribute<br>message_box<br>set_window_flag<br>win_fill_color<br>interface_overlay<br>dialog_message<br>get_text_width<br>hide_window<br>show_window<br>create_message_window | implemented: only message_box, get_text_width, show_window, create_message_window | - |
| Interface / Outline | outlined_object<br>get_outline<br>set_outline | âś… except get_outline | - |
| 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 | implemented: only message_box, get_text_width, show_window | - |
| Interface / Outline | outlined_object<br>get_outline<br>set_outline | implemented: all except get_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: only critter_inven_obj2 | - |
| 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 | âś… except get_ini_config, get_ini_config_db | `modified_ini` is intentionally omitted as deprecated. |
| INI settings | modified_ini<br>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 | âś… except modified_ini, get_ini_config, get_ini_config_db | `modified_ini` is intentionally omitted as deprecated. |
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>set_script<br>get_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get_object_data<br>set_object_data<br>get_flags<br>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_script, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, get_proto_data, set_proto_data | - |
| Other / Game management | set_movie_path<br>stop_game<br>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>get_unspent_ap_bonus<br>gdialog_get_barter_mod<br>set_unspent_ap_bonus<br>get/set_unspent_ap_perk_bonus<br>set_inven_ap_cost<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>get_inven_ap_cost<br>set_drugs_data<br>get_kill_counter<br>mod_kill_counter<br>set_pipboy_available | not implemented | - |
| NPCs | inc_npc_level<br>get_npc_level<br>npc_engine_level_up | not implemented | - |
| Other | get_year<br>set_dm/df_model<br>active_hand<br>toggle_active_hand<br>get/set_viewport_x/y<br>hero_select_win<br>get_light_level<br>message_str_game<br>sneak_success<br>create_spatial<br>unwield_slot<br>add_g_timer_event<br>add_extra_msg_file<br>get_metarule_table<br>metarule_exist<br>remove_timer_event<br>spatial_radius | implemented: get_year, active_hand, toggle_active_hand, message_str_game, add_extra_msg_file, metarule_exist | `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. |
| Other / Game management | set_movie_path<br>stop_game<br>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 | - |
| Other | input_funcs_available<br>get_year<br>set_dm_model<br>set_df_model<br>set_pipboy_available<br>get_kill_counter<br>mod_kill_counter<br>active_hand<br>toggle_active_hand<br>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>inc_npc_level<br>get_npc_level<br>get/set_viewport_x/y<br>set_hp_per_level_mod<br>get/set_unspent_ap_bonus<br>get/set_unspent_ap_perk_bonus<br>hero_select_win<br>create_message_window<br>get_light_level<br>gdialog_get_barter_mod<br>set_inven_ap_cost<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>message_str_game<br>sneak_success<br>create_spatial<br>unwield_slot<br>get_inven_ap_cost<br>add_g_timer_event<br>add_extra_msg_file<br>get_metarule_table<br>metarule_exist<br>npc_engine_level_up<br>remove_timer_event<br>set_drugs_data<br>spatial_radius | implemented: get_year, active_hand, toggle_active_hand, create_message_window, message_str_game, add_extra_msg_file, metarule_exist | `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. |
## Hooks
+15
View File
@@ -0,0 +1,15 @@
[main]
RadiationEffectCount=8
RadiationLevelCount=6
RadiationEffectPrimaryStatCount=6
RadiationThresholds=99,199,399,599,999
RadiationEnduranceModifiers=2,0,-2,-4,-6,-8
RadiationEffectStats=0,1,2,3,4,5,6,7
[RadiationEffectPenalties]
Level0=0,0,0,0,0,0,0,0
Level1=-1,0,0,0,0,0,0,0
Level2=-1,0,0,0,0,-1,0,-3
Level3=-2,0,-1,0,0,-2,-5,-5
Level4=-4,-3,-3,-3,-1,-5,-15,-10
Level5=-6,-5,-5,-5,-3,-6,-20,-10
+1 -23
View File
@@ -1,25 +1,5 @@
#include "test_utils.h"
#include "sfall.h"
#include "dik.h"
variable signal_close_game_test_done := false;
procedure keypress_handler begin
variable pressed := get_sfall_arg_at(0);
variable key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key != DIK_J) then return;
if (signal_close_game_test_done) then begin
display_msg("signal_close_game test already triggered");
return;
end
signal_close_game_test_done := true;
display_msg("signal_close_game test triggered");
signal_close_game;
end
procedure start begin
// TODO: move this to a more suitable place, once we have more functions implemented
@@ -30,8 +10,6 @@ procedure start begin
call assertEquals("active_hand toggled", active_hand, 1 - hand);
toggle_active_hand;
call assertEquals("active_hand toggled", active_hand, hand);
call report_test_results("misc");
display_msg("signal_close_game manual test ready: press J");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
end
+1 -1
View File
@@ -3063,7 +3063,7 @@ void _dude_fidget()
// 0x56C7E0
static Object* candidates[100];
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit != 0) {
return;
}
+1 -1
View File
@@ -475,7 +475,7 @@ void automapShow(bool isInGame, bool isUsingScanner)
break;
}
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit != 0) {
break;
}
+39 -159
View File
@@ -37,7 +37,6 @@
#include "platform_compat.h"
#include "proto.h"
#include "scripts.h"
#include "settings.h"
#include "sfall_config.h"
#include "skill.h"
#include "stat.h"
@@ -145,11 +144,6 @@ enum {
EDITOR_BUTTONS_COUNT = EDITOR_FIRST_TRAIT + TRAIT_COUNT,
};
static constexpr int kMaxPerkOwed = 250;
// nb. this is number of perks, not total perk ranks. This only prevents level ups from granting more perks.
static constexpr int kMaxSelectablePerks = 37;
static constexpr int kPrimaryStatDescriptionWidth = 100;
enum {
EDITOR_GRAPHIC_BIG_NUMBERS,
EDITOR_GRAPHIC_AGE_MASK,
@@ -288,8 +282,6 @@ static int perkDialogDrawTraits(int a1);
static int perkDialogOptionCompare(const void* a1, const void* a2);
static int perkDialogDrawCard(int frmId, const char* name, const char* rank, char* description);
static void _pop_perks();
static int characterEditorGetLevelsPerPerk();
static void characterEditorGrantPerkAtLevel(int level);
static int _is_supper_bonus();
static int characterEditorFolderViewInit();
static void characterEditorFolderViewScroll(int direction);
@@ -777,7 +769,7 @@ static int gCharacterEditorTaggedSkillCount;
static int gCharacterEditorTempTaggedSkills[NUM_TAGGED_SKILLS];
// 0x570A28
static unsigned char gCharacterEditorHasFreePerkBackup;
static char gCharacterEditorHasFreePerkBackup;
// 0x570A29
static unsigned char gCharacterEditorHasFreePerk;
@@ -927,7 +919,7 @@ int characterEditorShow(bool isCreationMode)
} else if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {
showQuitConfirmationDialog();
windowRefresh(gCharacterEditorWindow);
} else if (keyCode == 502 || keyCode == KEY_ESCAPE || keyCode == KEY_UPPERCASE_C || keyCode == KEY_LOWERCASE_C || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
} else if (keyCode == 502 || keyCode == KEY_ESCAPE || keyCode == KEY_UPPERCASE_C || keyCode == KEY_LOWERCASE_C || _game_user_wants_to_quit != 0) {
windowRefresh(gCharacterEditorWindow);
rc = 1;
} else if (gCharacterEditorIsCreationMode && (keyCode == 517 || keyCode == KEY_UPPERCASE_N || keyCode == KEY_LOWERCASE_N)) {
@@ -1958,7 +1950,7 @@ static int _get_input_str(int win, int cancelKeyCode, char* text, int maxLength,
} else if (keyCode == KEY_RETURN) {
soundPlayFile("ib1p1xx1");
rc = 0;
} else if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
} else if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != 0) {
rc = -1;
} else {
if ((keyCode == KEY_DELETE || keyCode == KEY_BACKSPACE) && nameLength >= 1) {
@@ -2482,7 +2474,7 @@ static void characterEditorDrawPrimaryStat(int stat, bool animate, int previousV
characterEditorDrawBigNumber(58, gCharacterEditorPrimaryStatY[stat], flags, value, previousValue, gCharacterEditorWindow);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, kPrimaryStatDescriptionWidth, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, 40, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
messageListItemId = critterGetStat(gDude, stat) + 199;
if (messageListItemId > 210) {
@@ -2494,7 +2486,7 @@ static void characterEditorDrawPrimaryStat(int stat, bool animate, int previousV
} else {
value = critterGetStat(gDude, stat);
characterEditorDrawBigNumber(58, gCharacterEditorPrimaryStatY[stat], 0, value, 0, gCharacterEditorWindow);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, kPrimaryStatDescriptionWidth, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, 40, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
value = critterGetStat(gDude, stat);
if (value > 10) {
@@ -3443,7 +3435,7 @@ static int characterEditorEditAge()
windowDestroy(win);
return 0;
} else if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
} else if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != 0) {
break;
} else if (keyCode == 501) {
age = critterGetStat(gDude, STAT_AGE);
@@ -3543,7 +3535,7 @@ static int characterEditorEditAge()
}
keyCode = inputGetInput();
if (keyCode == 503 || keyCode == 504 || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (keyCode == 503 || keyCode == 504 || _game_user_wants_to_quit != 0) {
break;
}
@@ -3675,7 +3667,7 @@ static void characterEditorEditGender()
break;
}
if (eventCode == KEY_ESCAPE || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (eventCode == KEY_ESCAPE || _game_user_wants_to_quit != 0) {
critterSetBaseStat(gDude, STAT_GENDER, savedGender);
characterEditorDrawPrimaryStat(RENDER_ALL_STATS, 0, 0);
characterEditorDrawDerivedStats();
@@ -3899,7 +3891,7 @@ static int characterEditorShowOptions()
int keyCode = inputGetInput();
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit != 0) {
rc = 2;
} else if (keyCode == 504) {
rc = 2;
@@ -5676,117 +5668,11 @@ int characterEditorLoad(File* stream)
return 0;
}
int characterEditorGetPerkOwed()
{
return gCharacterEditorHasFreePerk;
}
void characterEditorSetPerkOwed(int value)
{
unsigned int maskedValue = static_cast<unsigned int>(value) & 0xFF;
if (maskedValue <= kMaxPerkOwed) {
gCharacterEditorHasFreePerk = maskedValue;
}
}
// External interface: called when level up occurs
void characterEditorHandleLevelUp(int level)
{
characterEditorGrantPerkAtLevel(level);
}
// compute the character level that the next perk selection should use
int characterEditorGetPerkSelectionLevel()
{
int currentLevel = pcGetStat(PC_STAT_LEVEL);
if (settings.gameplay.perk_carryover != PERK_CARRY_OVER_MODE_ON) {
return currentLevel;
}
int owed = characterEditorGetPerkOwed();
if (owed <= 1) {
// If a single perk is owed, use the current level. This prevents odd effects in
// saves where perks have been skipped and is a general safe fallback.
return currentLevel;
}
int levelsPerPerk = characterEditorGetLevelsPerPerk();
// This therefore uses the number of perk thresholds strictly before the
// current level, then compares that against the total perk ranks already
// spent. In the common non-scripted case:
// - level 6 with 0 spent perks yields one deferred threshold (level 3)
// - after spending one perk, there is no older backlog left, so the next
// owed perk uses level 6 requirements
// - level 9 with 2 spent perks has no older backlog, so owed perks are
// immediately spendable at level 9
int previousThresholdsReached = (currentLevel - 1) / levelsPerPerk;
int spentPerks = 0;
for (int perk = 0; perk < PERK_COUNT; perk++) {
spentPerks += perkGetRank(gDude, perk);
}
// This is a heuristic for the 99.9% case where perk ranks come from normal
// perk picks. If enough perk ranks have already been spent to cover all
// earlier thresholds, there is no deferred backlog to force in order.
if (spentPerks >= previousThresholdsReached) {
return currentLevel;
}
// The next deferred pick corresponds to the earliest previously missed
// threshold after the already-spent perk ranks.
int thresholdIndex = spentPerks + 1;
int selectionLevel = std::max(3, thresholdIndex * levelsPerPerk);
if (selectionLevel <= 0 || selectionLevel > currentLevel) {
return currentLevel;
}
return selectionLevel;
}
// 0x43C20C
void characterEditorReset()
{
gCharacterEditorRemainingCharacterPoints = 5;
gCharacterEditorLastLevel = 1;
characterEditorSetPerkOwed(0);
}
static int characterEditorGetLevelsPerPerk()
{
int progression = 3;
if (traitIsSelected(TRAIT_SKILLED)) {
progression += 1;
}
return progression;
}
static void characterEditorGrantPerkAtLevel(int level)
{
if (level <= 0 || level % characterEditorGetLevelsPerPerk() != 0) {
return;
}
int selectedPerksCount = 0;
for (int perk = 0; perk < PERK_COUNT; perk++) {
if (perkGetRank(gDude, perk) != 0) {
selectedPerksCount += 1;
if (selectedPerksCount >= kMaxSelectablePerks) {
return;
}
}
}
switch (settings.gameplay.perk_carryover) {
case PERK_CARRY_OVER_MODE_OFF:
characterEditorSetPerkOwed(1);
break;
case PERK_CARRY_OVER_MODE_ON:
case PERK_CARRY_OVER_MODE_SFALL:
characterEditorSetPerkOwed(characterEditorGetPerkOwed() + 1);
break;
}
}
// level up if needed
@@ -5796,13 +5682,6 @@ static int characterEditorUpdateLevel()
{
int level = pcGetStat(PC_STAT_LEVEL);
if (level != gCharacterEditorLastLevel && level <= PC_LEVEL_MAX) {
// Runtime level-up now grants perks immediately. However, older saves
// may still have last_level behind the real level with no owed perks
// recorded, because perk grants used to be deferred until the next
// character screen visit. Replay perk grants only for that legacy
// catch-up case.
bool shouldReplayPerkGrants = characterEditorGetPerkOwed() == 0;
for (int nextLevel = gCharacterEditorLastLevel + 1; nextLevel <= level; nextLevel++) {
int sp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS);
sp += 5;
@@ -5821,13 +5700,30 @@ static int characterEditorUpdateLevel()
pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, sp);
if (shouldReplayPerkGrants) {
characterEditorGrantPerkAtLevel(nextLevel);
int selectedPerksCount = 0;
for (int perk = 0; perk < PERK_COUNT; perk++) {
if (perkGetRank(gDude, perk) != 0) {
selectedPerksCount += 1;
if (selectedPerksCount >= 37) {
break;
}
}
}
if (selectedPerksCount < 37) {
int progression = 3;
if (traitIsSelected(TRAIT_SKILLED)) {
progression += 1;
}
if (nextLevel % progression == 0) {
gCharacterEditorHasFreePerk = 1;
}
}
}
}
while (characterEditorGetPerkOwed() != 0) {
if (gCharacterEditorHasFreePerk != 0) {
characterEditorWindowSelectedFolder = 0;
characterEditorDrawFolders();
windowRefresh(gCharacterEditorWindow);
@@ -5836,15 +5732,11 @@ static int characterEditorUpdateLevel()
if (rc == -1) {
debugPrint("\n *** Error running perks dialog! ***\n");
return -1;
}
if (rc == 0) {
} else if (rc == 0) {
characterEditorDrawFolders();
break;
}
if (rc == 1) {
} else if (rc == 1) {
characterEditorDrawFolders();
gCharacterEditorHasFreePerk = 0;
}
}
@@ -5893,10 +5785,6 @@ static int perkDialogShow()
gPerkDialogCardFrmId = -1;
gPerkDialogCardTitle[0] = '\0';
gPerkDialogCardDrawn = false;
int previousPerkRanks[PERK_COUNT];
for (int perk = 0; perk < PERK_COUNT; perk++) {
previousPerkRanks[perk] = perkGetRank(gDude, perk);
}
int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 86, 0, 0, 0);
if (!_perkDialogBackgroundFrmImage.lock(backgroundFid)) {
@@ -6022,12 +5910,6 @@ static int perkDialogShow()
fontDrawText(gPerkDialogWindowBuffer + PERK_WINDOW_WIDTH * 186 + 171, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, _colorTable[18979]);
int count = perkDialogDrawPerks();
if (count == 0) {
debugPrint("*** No perks available for current owed slot! ***");
_perkDialogBackgroundFrmImage.unlock();
windowDestroy(gPerkDialogWindow);
return -1;
}
// NOTE: Original code is slightly different, but does the same thing.
int perk = gPerkDialogOptionList[gPerkDialogTopLine + gPerkDialogCurrentLine].value;
@@ -6049,8 +5931,7 @@ static int perkDialogShow()
int rc = perkDialogHandleInput(count, perkDialogRefreshPerks);
if (rc == 1) {
int selectionLevel = characterEditorGetPerkSelectionLevel();
if (perkAddAtLevel(gDude, gPerkDialogOptionList[gPerkDialogTopLine + gPerkDialogCurrentLine].value, selectionLevel) == -1) {
if (perkAdd(gDude, gPerkDialogOptionList[gPerkDialogTopLine + gPerkDialogCurrentLine].value) == -1) {
debugPrint("\n*** Unable to add perk! ***\n");
rc = 2;
}
@@ -6059,20 +5940,19 @@ static int perkDialogShow()
rc &= 1;
if (rc != 0) {
characterEditorSetPerkOwed(characterEditorGetPerkOwed() - 1);
if (perkGetRank(gDude, PERK_TAG) != 0 && previousPerkRanks[PERK_TAG] == 0) {
if (perkGetRank(gDude, PERK_TAG) != 0 && gCharacterEditorPerksBackup[PERK_TAG] == 0) {
if (!perkDialogHandleTagPerk()) {
perkRemove(gDude, PERK_TAG);
}
} else if (perkGetRank(gDude, PERK_MUTATE) != 0 && previousPerkRanks[PERK_MUTATE] == 0) {
} else if (perkGetRank(gDude, PERK_MUTATE) != 0 && gCharacterEditorPerksBackup[PERK_MUTATE] == 0) {
if (!perkDialogHandleMutatePerk()) {
perkRemove(gDude, PERK_MUTATE);
}
} else if (perkGetRank(gDude, PERK_LIFEGIVER) != previousPerkRanks[PERK_LIFEGIVER]) {
} else if (perkGetRank(gDude, PERK_LIFEGIVER) != gCharacterEditorPerksBackup[PERK_LIFEGIVER]) {
int maxHp = critterGetBonusStat(gDude, STAT_MAXIMUM_HIT_POINTS);
critterSetBonusStat(gDude, STAT_MAXIMUM_HIT_POINTS, maxHp + 4);
critterAdjustHitPoints(gDude, 4);
} else if (perkGetRank(gDude, PERK_EDUCATED) != previousPerkRanks[PERK_EDUCATED]) {
} else if (perkGetRank(gDude, PERK_EDUCATED) != gCharacterEditorPerksBackup[PERK_EDUCATED]) {
int sp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS);
pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, sp + 2);
}
@@ -6136,7 +6016,7 @@ static int perkDialogHandleInput(int count, void (*refreshProc)())
}
gPerkDialogPreviousCurrentLine = gPerkDialogCurrentLine;
refreshProc();
} else if (keyCode == 502 || keyCode == KEY_ESCAPE || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
} else if (keyCode == 502 || keyCode == KEY_ESCAPE || _game_user_wants_to_quit != 0) {
rc = 2;
} else {
switch (keyCode) {
@@ -6386,7 +6266,7 @@ static int perkDialogDrawPerks()
fontSetCurrent(101);
int perks[PERK_COUNT];
int count = perkGetAvailablePerks(gDude, characterEditorGetPerkSelectionLevel(), perks);
int count = perkGetAvailablePerks(gDude, perks);
if (count == 0) {
return 0;
}
-14
View File
@@ -7,16 +7,6 @@ namespace fallout {
extern int gCharacterEditorRemainingCharacterPoints;
enum PerkCarryOverMode {
// Unspent perks are lost on level up (vanilla)
PERK_CARRY_OVER_MODE_OFF = 0,
// Unspent perks are preserved on level up, but you still spend unspent perks at the appropriate levels.
// E.g. if you reach level 9 and have never spent any perks, you first choose from the level 3 perks, then 6, then 9. (CE)
PERK_CARRY_OVER_MODE_ON = 1,
// Unspent perks are preserved on level up, and you can choose to spend them on perks for the current level.
PERK_CARRY_OVER_MODE_SFALL = 2,
};
int characterEditorShow(bool isCreationMode);
void characterEditorInit();
bool _isdoschar(int ch);
@@ -24,10 +14,6 @@ char* _strmfe(char* dest, const char* name, const char* ext);
int characterEditorSave(File* stream);
int characterEditorLoad(File* stream);
void characterEditorReset();
int characterEditorGetPerkOwed();
void characterEditorSetPerkOwed(int value);
void characterEditorHandleLevelUp(int level);
int characterEditorGetPerkSelectionLevel();
} // namespace fallout
+1 -1
View File
@@ -165,7 +165,7 @@ int characterSelectorOpen()
while (!done) {
sharedFpsLimiter.mark();
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit != 0) {
break;
}
+10 -10
View File
@@ -2762,7 +2762,7 @@ void _combat_update_critter_outline_for_los(Object* critter, bool a2)
// 0x421EFC
static void _combat_over()
{
if (_game_user_wants_to_quit == GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit == 0) {
for (int index = 0; index < _list_com; index++) {
Object* critter = _combat_list[index];
if (critter != gDude) {
@@ -2820,7 +2820,7 @@ static void _combat_over()
interfaceRenderActionPoints(0, 0);
if (_game_user_wants_to_quit == GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit == 0) {
_combat_give_exps(_combat_exps);
}
@@ -3167,7 +3167,7 @@ static int _combat_input()
break;
}
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit != 0) {
break;
}
@@ -3205,8 +3205,8 @@ static int _combat_input()
}
int v4 = _game_user_wants_to_quit;
if (_game_user_wants_to_quit == GAME_QUIT_REQUEST_END_COMBAT) {
_game_user_wants_to_quit = GAME_QUIT_REQUEST_NONE;
if (_game_user_wants_to_quit == 1) {
_game_user_wants_to_quit = 0;
}
if ((gCombatState & COMBAT_STATE_0x08) != 0) {
@@ -3214,7 +3214,7 @@ static int _combat_input()
return -1;
}
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE || v4 != GAME_QUIT_REQUEST_NONE || _combat_end_due_to_load != 0) {
if (_game_user_wants_to_quit != 0 || v4 != 0 || _combat_end_due_to_load != 0) {
return -1;
}
@@ -3273,7 +3273,7 @@ static int _combat_turn(Object* obj, bool reloadedDuringCombat)
scriptOverrides = scr->scriptOverrides;
}
if (_game_user_wants_to_quit == GAME_QUIT_REQUEST_END_COMBAT) {
if (_game_user_wants_to_quit == 1) {
return -1;
}
}
@@ -3511,8 +3511,8 @@ void _combat(CombatStartData* csd)
_combat_end_due_to_load = 0;
if (_game_user_wants_to_quit == GAME_QUIT_REQUEST_END_COMBAT) {
_game_user_wants_to_quit = GAME_QUIT_REQUEST_NONE;
if (_game_user_wants_to_quit == 1) {
_game_user_wants_to_quit = 0;
}
}
}
@@ -5669,7 +5669,7 @@ static int calledShotSelectHitLocation(Object* critter, int* hitLocation, int hi
break;
}
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit != 0) {
break;
}
+68 -93
View File
@@ -1,3 +1,4 @@
#include "config.h"
#include "critter.h"
#include <stdio.h>
@@ -37,37 +38,25 @@ namespace fallout {
#define DUDE_NAME_MAX_LENGTH (32)
// The number of effects caused by radiation.
//
// A radiation effect is an identifier and does not have it's own name. It's
// stat is specified in [gRadiationEffectStats], and it's amount is specified
// in [gRadiationEffectPenalties] for every [RadiationLevel].
#define RADIATION_EFFECT_COUNT 8
static int gRadiationEffectCount = 8;
// Radiation levels.
//
// The names of levels are taken from Fallout 3, comments from Fallout 2.
typedef enum RadiationLevel {
// Very nauseous.
RADIATION_LEVEL_NONE,
// The number of radiation levels.
static int gRadiationLevelCount = 6;
// Slightly fatigued.
RADIATION_LEVEL_MINOR,
// Radiation thresholds.
static int* gRadiationThresholds = nullptr;
// Vomiting does not stop.
RADIATION_LEVEL_ADVANCED,
// Modifiers to endurance for performing radiation damage check.
static int* gRadiationEnduranceModifiers = nullptr;
// Hair is falling out.
RADIATION_LEVEL_CRITICAL,
// List of stats affected by radiation.
static int* gRadiationEffectStats = nullptr;
// Skin is falling off.
RADIATION_LEVEL_DEADLY,
// List of stat modifiers caused by radiation at different radiation levels.
static int** gRadiationEffectPenalties = nullptr;
// Intense agony.
RADIATION_LEVEL_FATAL,
// The number of radiation levels.
RADIATION_LEVEL_COUNT,
} RadiationLevel;
// Denotes how many primary stats at the top of [gRadiationEffectStats] array.
static int gRadiationEffectPrimaryStatCount = 6;
static int _get_rad_damage_level(Object* obj, void* data);
static int critter_kill_count_clear();
@@ -82,58 +71,6 @@ static char byte_501494[] = "";
// 0x51833C
static char* _name_critter = _aCorpse;
// Modifiers to endurance for performing radiation damage check.
//
// 0x518340
static const int gRadiationEnduranceModifiers[RADIATION_LEVEL_COUNT] = {
2,
0,
-2,
-4,
-6,
-8,
};
// List of stats affected by radiation.
//
// The values of this list specify stats that can be affected by radiation.
// The amount of penalty to every stat (identified by index) is stored
// separately in [gRadiationEffectPenalties] per radiation level.
//
// The order of stats is important - primary stats must be at the top. See
// [RADIATION_EFFECT_PRIMARY_STAT_COUNT] for more info.
//
// 0x518358
static const int gRadiationEffectStats[RADIATION_EFFECT_COUNT] = {
STAT_STRENGTH,
STAT_PERCEPTION,
STAT_ENDURANCE,
STAT_CHARISMA,
STAT_INTELLIGENCE,
STAT_AGILITY,
STAT_CURRENT_HIT_POINTS,
STAT_HEALING_RATE,
};
// Denotes how many primary stats at the top of [gRadiationEffectStats] array.
// These stats are used to determine if critter is alive after applying
// radiation effects.
#define RADIATION_EFFECT_PRIMARY_STAT_COUNT 6
// List of stat modifiers caused by radiation at different radiation levels.
//
// 0x518378
static const int gRadiationEffectPenalties[RADIATION_LEVEL_COUNT][RADIATION_EFFECT_COUNT] = {
// clang-format off
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ -1, 0, 0, 0, 0, 0, 0, 0 },
{ -1, 0, 0, 0, 0, -1, 0, -3 },
{ -2, 0, -1, 0, 0, -2, -5, -5 },
{ -4, -3, -3, -3, -1, -5, -15, -10 },
{ -6, -5, -5, -5, -3, -6, -20, -10 },
// clang-format on
};
// 0x518438
static Object* _critterClearObj = nullptr;
@@ -160,6 +97,10 @@ static int oldRadLevel;
// 0x42CF50
int critterInit()
{
if (critterInitRadiationEffectPenalties() == -1) {
return -1;
}
dudeResetName();
// NOTE: Uninline;
@@ -183,6 +124,46 @@ int critterInit()
return 0;
}
// 0x42CF50
int critterInitRadiationEffectPenalties()
{
Config config;
if (!configInit(&config)) {
return -1;
}
if (!configRead(&config, "data/radiation.txt", false)) {
debugPrint("\nError: Could not load radiation.txt");
return -1;
}
if (!configGetInt(&config, "main", "RadiationEffectCount", &gRadiationEffectCount)) gRadiationEffectCount = 8;
if (!configGetInt(&config, "main", "RadiationLevelCount", &gRadiationLevelCount)) gRadiationLevelCount = 6;
if (!configGetInt(&config, "main", "RadiationEffectPrimaryStatCount", &gRadiationEffectPrimaryStatCount)) gRadiationEffectPrimaryStatCount = 6;
gRadiationThresholds = (int*)malloc(gRadiationLevelCount * sizeof(int));
gRadiationEnduranceModifiers = (int*)malloc(gRadiationLevelCount * sizeof(int));
gRadiationEffectStats = (int*)malloc(gRadiationEffectCount * sizeof(int));
gRadiationEffectPenalties = (int**)malloc(gRadiationLevelCount * sizeof(int*));
for (int i = 0; i < gRadiationLevelCount; ++i) {
gRadiationEffectPenalties[i] = (int*)malloc(gRadiationEffectCount * sizeof(int));
}
// Load arrays...
configGetIntList(&config, "main", "RadiationThresholds", gRadiationThresholds, gRadiationLevelCount);
configGetIntList(&config, "main", "RadiationEnduranceModifiers", gRadiationEnduranceModifiers, gRadiationLevelCount);
configGetIntList(&config, "main", "RadiationEffectStats", gRadiationEffectStats, gRadiationEffectCount);
for (int i = 0; i < gRadiationLevelCount; ++i) {
char key[20];
snprintf(key, sizeof(key), "Level%d", i);
configGetIntList(&config, "RadiationEffectPenalties", key, gRadiationEffectPenalties[i], gRadiationEffectCount);
}
configFree(&config);
return 0;
}
// 0x42CFE4
void critterReset()
{
@@ -505,18 +486,11 @@ int critterCheckRadiationEvent(Object* obj)
int radiation = critterGetRadiation(obj);
int radiationLevel;
if (radiation > 999)
radiationLevel = RADIATION_LEVEL_FATAL;
else if (radiation > 599)
radiationLevel = RADIATION_LEVEL_DEADLY;
else if (radiation > 399)
radiationLevel = RADIATION_LEVEL_CRITICAL;
else if (radiation > 199)
radiationLevel = RADIATION_LEVEL_ADVANCED;
else if (radiation > 99)
radiationLevel = RADIATION_LEVEL_MINOR;
else
radiationLevel = RADIATION_LEVEL_NONE;
for (radiationLevel = gRadiationLevelCount - 1; radiationLevel >= 0; --radiationLevel) {
if (radiation > gRadiationThresholds[radiationLevel]) {
break;
}
}
if (statRoll(obj, STAT_ENDURANCE, gRadiationEnduranceModifiers[radiationLevel], nullptr) <= ROLL_FAILURE) {
radiationLevel++;
@@ -568,7 +542,7 @@ void radiationProcess(Object* obj, int radiationLevel, bool isHealing)
{
MessageListItem messageListItem;
if (radiationLevel == RADIATION_LEVEL_NONE) {
if (radiationLevel == 0) {
return;
}
@@ -577,6 +551,7 @@ void radiationProcess(Object* obj, int radiationLevel, bool isHealing)
if (obj == gDude) {
// Radiation level message, higher is worse.
// Assuming original behavior that message indices follow the level order.
messageListItem.num = 1000 + radiationLevelIndex;
// SFALL: Fix radiation message when removing radiation effects.
@@ -590,7 +565,7 @@ void radiationProcess(Object* obj, int radiationLevel, bool isHealing)
}
}
for (int effect = 0; effect < RADIATION_EFFECT_COUNT; effect++) {
for (int effect = 0; effect < gRadiationEffectCount; effect++) {
int value = critterGetBonusStat(obj, gRadiationEffectStats[effect]);
value += modifier * gRadiationEffectPenalties[radiationLevelIndex][effect];
critterSetBonusStat(obj, gRadiationEffectStats[effect], value);
@@ -601,7 +576,7 @@ void radiationProcess(Object* obj, int radiationLevel, bool isHealing)
if ((obj->data.critter.combat.results & DAM_DEAD) == 0) {
// Loop thru effects affecting primary stats. If any of the primary stat
// dropped below minimal value, kill it.
for (int effect = 0; effect < RADIATION_EFFECT_PRIMARY_STAT_COUNT; effect++) {
for (int effect = 0; effect < gRadiationEffectPrimaryStatCount; effect++) {
int base = critterGetBaseStatWithTraitModifier(obj, gRadiationEffectStats[effect]);
int bonus = critterGetBonusStat(obj, gRadiationEffectStats[effect]);
if (base + bonus < PRIMARY_STAT_MIN) {
@@ -912,7 +887,7 @@ void critterKill(Object* critter, int anim, bool refreshRect)
if (critter == gDude) {
endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_DEATH);
_game_user_wants_to_quit = GAME_QUIT_REQUEST_MAIN_MENU;
_game_user_wants_to_quit = 2;
}
}
+1
View File
@@ -14,6 +14,7 @@ typedef enum DudeState {
} DudeState;
int critterInit();
int critterInitRadiationEffectPenalties();
void critterReset();
void critterExit();
int critterLoad(File* stream);
+4 -4
View File
@@ -547,7 +547,7 @@ int showDialogBox(const char* title, const char** body, int bodyLength, int x, i
}
}
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit != 0) {
rc = 1;
}
@@ -890,7 +890,7 @@ int showLoadFileDialog(char* title, char** fileList, char* dest, int fileListLen
delay_ms(delay - (getTicks() - scrollTick));
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit != 0) {
rc = 1;
break;
}
@@ -1338,7 +1338,7 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
unsigned int delay = (scrollCounter > 14.4) ? 1000 / scrollDelay : 1000 / 24;
delay_ms(delay - (getTicks() - scrollTick));
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit != 0) {
rc = 1;
break;
}
@@ -1372,7 +1372,7 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
delay_ms(1000 / 24 - (getTicks() - tick));
}
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
if (_game_user_wants_to_quit != 0) {
rc = 1;
}
+1 -1
View File
@@ -288,7 +288,7 @@ static int endgameEndingHandleContinuePlaying()
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
rc = showDialogBox(messageListItem.text, nullptr, 0, 169, 117, _colorTable[32328], nullptr, _colorTable[32328], DIALOG_BOX_YES_NO);
if (rc == 0) {
_game_user_wants_to_quit = GAME_QUIT_REQUEST_MAIN_MENU;
_game_user_wants_to_quit = 2;
}
} else {
rc = -1;
+6 -6
View File
@@ -114,7 +114,7 @@ int gGameGlobalVarsLength = 0;
const char* asc_5186C8 = _aGame_0;
// 0x5186CC
GameQuitRequest _game_user_wants_to_quit = GAME_QUIT_REQUEST_NONE;
int _game_user_wants_to_quit = 0;
// misc.msg
//
@@ -445,7 +445,7 @@ void gameReset()
_ResetLoadSave();
gameDialogReset();
combatReset();
_game_user_wants_to_quit = GAME_QUIT_REQUEST_NONE;
_game_user_wants_to_quit = 0;
automapReset();
_init_options_menu();
@@ -1274,7 +1274,7 @@ static void showHelp()
windowShow(overlay);
windowShow(win);
while (inputGetInput() == -1 && _game_user_wants_to_quit == GAME_QUIT_REQUEST_NONE) {
while (inputGetInput() == -1 && _game_user_wants_to_quit == 0) {
sharedFpsLimiter.mark();
renderPresent();
sharedFpsLimiter.throttle();
@@ -1342,7 +1342,7 @@ int showQuitConfirmationDialog()
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
rc = showDialogBox(messageListItem.text, nullptr, 0, 169, 117, _colorTable[32328], nullptr, _colorTable[32328], DIALOG_BOX_YES_NO);
if (rc != 0) {
_game_user_wants_to_quit = GAME_QUIT_REQUEST_MAIN_MENU;
_game_user_wants_to_quit = 2;
}
} else {
rc = -1;
@@ -1579,8 +1579,8 @@ int gameShowDeathDialog(const char* message)
int oldCursor = gameMouseGetCursor();
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
GameQuitRequest oldUserWantsToQuit = _game_user_wants_to_quit;
_game_user_wants_to_quit = GAME_QUIT_REQUEST_NONE;
int oldUserWantsToQuit = _game_user_wants_to_quit;
_game_user_wants_to_quit = 0;
int rc = showDialogBox(message, nullptr, 0, 169, 117, _colorTable[32328], nullptr, _colorTable[32328], DIALOG_BOX_LARGE);
+1 -8
View File
@@ -16,17 +16,10 @@ typedef enum GameState {
GAME_STATE_5,
} GameState;
typedef enum GameQuitRequest {
GAME_QUIT_REQUEST_NONE = 0,
GAME_QUIT_REQUEST_END_COMBAT = 1,
GAME_QUIT_REQUEST_MAIN_MENU = 2,
GAME_QUIT_REQUEST_EXIT = 3,
} GameQuitRequest;
extern int* gGameGlobalVars;
extern int gGameGlobalVarsLength;
extern const char* asc_5186C8;
extern GameQuitRequest _game_user_wants_to_quit;
extern int _game_user_wants_to_quit;
extern MessageList gMiscMessageList;
+20 -75
View File
@@ -1,3 +1,4 @@
#include "settings.h"
#include "game_config.h"
#include "debug.h"
#include "game_config_migration.h"
@@ -63,74 +64,7 @@ bool gameConfigInit(bool isMapper, int argc, char** argv)
return false;
}
// TODO: consolidate settings setup in one place; not sure if this is needed when we have settings field initializers
// Initialize defaults.
configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, "game");
configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_DAT_KEY, "master.dat");
configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, "data");
configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_DAT_KEY, "critter.dat");
configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_PATCHES_KEY, "data");
configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, ENGLISH);
configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SCROLL_LOCK_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_INTERRUPT_WALK_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_ART_CACHE_SIZE_KEY, 8);
configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_COLOR_CYCLING_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_HASHING_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SPLASH_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_FREE_SPACE_KEY, 20480);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, 3);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, 2);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEED_KEY, 0);
configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, 3.5);
configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, 1.399994);
configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, 1.0);
configSetDouble(&gGameConfig, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, 1.0);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_INITIALIZE_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DEVICE_KEY, -1);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_PORT_KEY, -1);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_IRQ_KEY, -1);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DMA_KEY, -1);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SOUNDS_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, 22281);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, 22281);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, 22281);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, 22281);
configSetInt(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_CACHE_SIZE_KEY, 448);
configSetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH1_KEY, "sound\\music\\");
configSetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH2_KEY, "sound\\music\\");
configSetString(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_MODE_KEY, "environment");
configSetInt(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_TILE_NUM_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_LOAD_INFO_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_OUTPUT_MAP_DATA_INFO_KEY, 0);
if (isMapper) {
configSetString(&gGameConfig, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, "mapper");
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_OVERRIDE_LIBRARIAN_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_LIBRARIAN_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_USE_ART_NOT_PROTOS_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_REBUILD_PROTOS_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_OBJECTS_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_INVENTORY_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_IGNORE_REBUILD_ERRORS_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SHOW_PID_NUMBERS_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SAVE_TEXT_MAPS_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_RUN_MAPPER_AS_GAME_KEY, 0);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_DEFAULT_F8_AS_GAME_KEY, 1);
configSetInt(&gGameConfig, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SORT_SCRIPT_LIST_KEY, 0);
}
initSettingsRegistry();
// CE: Detect alternative default music directory.
char alternativeMusicPath[COMPAT_MAX_PATH];
@@ -142,11 +76,14 @@ bool gameConfigInit(bool isMapper, int argc, char** argv)
int acmsLength = fileNameListInit(alternativeMusicPath, &acms);
if (acmsLength != -1) {
if (acmsLength > 0) {
// TODO: apply these to settings directly instead of config
configSetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH1_KEY, "data\\sound\\music\\");
configSetString(&gGameConfig, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH2_KEY, "data\\sound\\music\\");
}
fileNameListFree(&acms, 0);
}
settingsApplyDefaultsToConfig();
// SFALL: Custom config file name.
char* customConfigFileName = nullptr;
@@ -198,13 +135,21 @@ bool gameConfigInit(bool isMapper, int argc, char** argv)
configParseCommandLineArguments(&gGameConfig, argc, argv);
// CE: Normalize and resolve asset bundle paths.
gameConfigResolvePath(GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_DAT_KEY);
gameConfigResolvePath(GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY);
gameConfigResolvePath(GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_DAT_KEY);
gameConfigResolvePath(GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_PATCHES_KEY);
gameConfigResolvePath(GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_PATCHES_KEY);
gameConfigResolvePath(GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH1_KEY);
gameConfigResolvePath(GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH2_KEY);
static const struct {
const char* section;
const char* key;
} pathsToResolve[] = {
{GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_DAT_KEY},
{GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY},
{GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_DAT_KEY},
{GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_PATCHES_KEY},
{GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH1_KEY},
{GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH2_KEY},
};
for (const auto& path : pathsToResolve) {
gameConfigResolvePath(path.section, path.key);
}
gGameConfigInitialized = true;
-2
View File
@@ -11,7 +11,6 @@ namespace fallout {
#define GAME_CONFIG_SYSTEM_KEY "system"
#define GAME_CONFIG_SCREEN_KEY "screen"
#define GAME_CONFIG_UI_KEY "ui"
#define GAME_CONFIG_GAMEPLAY_KEY "gameplay"
#define GAME_CONFIG_PREFERENCES_KEY "preferences"
#define GAME_CONFIG_SOUND_KEY "sound"
#define GAME_CONFIG_MAPPER_KEY "mapper"
@@ -87,7 +86,6 @@ namespace fallout {
#define GAME_CONFIG_SORT_SCRIPT_LIST_KEY "sort_script_list"
#define GAME_CONFIG_PLAYER_SPEEDUP_KEY "player_speedup"
#define GAME_CONFIG_UI_ANIM_SPEED_KEY "ui_anim_speed"
#define GAME_CONFIG_PERK_CARRYOVER_KEY "perk_carryover"
#define GAME_CONFIG_RESOLUTION_X_KEY "resolution_x"
#define GAME_CONFIG_RESOLUTION_Y_KEY "resolution_y"

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