Compare commits

...
Author SHA1 Message Date
Mike Klaas f9a5aeff9a PR feedback 2026-04-10 23:48:45 -07:00
Mike Klaas bf55a6fbb1 cleanup 2026-04-10 23:48:45 -07:00
github-actions[bot] 9764e2911d chore: auto-format with clang-format 2026-04-11 06:30:12 +00:00
Mike Klaas 5d5bb78087 Implement perk carryover, plus get/set_perk_owed
As in Sfall, the 0/1 "perk owned" boolean becomes an unsigned char, allowing perks to accumulate if they are not spent by the time the next perk is granted.

Unlike Sfall, we add a mode where perks must be spent "in order".  For instance, if the player has not spent their level 3 perk when they hit level 6, they still have 2 perks to spend, but the first perk must be their level 3 perk (with only level 3 perks available), and then they can spend their level 6 perk.  This behaviour is configurable to be: vanilla, CE, or Sfall.

This value is the same char saved in vanilla and Sfall, so is backward compatible with old saves.

To implement `get/set_perk_owed`, perk incrementing has to happen at level-up time instead of the first time the character sheet is opened after level up.  This matches Sfall's implementation.

It would be pretty easy to implement set_perk_freq / seq_perk_freq / set_perk_level_mod / get_perk_available.  For now leaving this out since the PR is big and I'm not sure how often these are used in mods.
2026-04-10 23:21:41 -07:00
Mike Klaas 6f0f68d5aa Fix crash when using op_pickup with global scripts (#366)
1 isn't valid return value here. 0 is success, -1 is failure.  Verified against original assembly and dozens of other examples.
2026-04-10 07:53:46 -07:00
Mike Klaas 141c67575f Update PR template to remove formatting instructions (#365)
(Auto-format takes care of this)
2026-04-10 07:53:30 -07:00
Mike Klaas a7a7f0b63c Implement sfall signal_close_game (#364)
* Implement sfall `signal_close_game`

Used by Et tu, but might be useful for LLMs too to quit the game after running a test case.

Also added enum for `_user_wants_to_quit`, and did minor reformatting of SFALL_COMPATIBILITY.md
2026-04-10 05:07:43 +00:00
40 changed files with 361 additions and 139 deletions
-10
View File
@@ -17,15 +17,5 @@
- *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.
--->
+10 -8
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 | 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 | - |
| 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 | - |
| 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 | 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 | - |
| Explosions | set_attack_explosion_pattern<br>set_attack_explosion_art<br>set_attack_explosion_radius<br>set_attack_is_explosion_fire<br>set_explosion_radius<br>set_dynamite_damage<br>set_plastic_damage<br>get_explosion_damage<br>set_explosion_max_targets<br>item_make_explosive | âś… except item_make_explosive | - |
| Animations | reg_anim_combat_check<br>reg_anim_destroy<br>reg_anim_animate_and_hide<br>reg_anim_light<br>reg_anim_change_fid<br>reg_anim_take_out<br>reg_anim_turn_towards<br>reg_anim_callback<br>reg_anim_animate_and_move | âś… except reg_anim_callback | - |
| Art and appearance | art_exists<br>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,19 +38,21 @@ 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 | implemented: all except available_global_script_types | - |
| Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | âś… except available_global_script_types | - |
| Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | implemented: 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 | 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 / 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 / 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 | 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. |
| 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. |
| 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 | - |
| 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. |
| 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. |
## Hooks
+23 -1
View File
@@ -1,5 +1,25 @@
#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
@@ -10,6 +30,8 @@ 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 != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
return;
}
+1 -1
View File
@@ -475,7 +475,7 @@ void automapShow(bool isInGame, bool isUsingScanner)
break;
}
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
+159 -39
View File
@@ -37,6 +37,7 @@
#include "platform_compat.h"
#include "proto.h"
#include "scripts.h"
#include "settings.h"
#include "sfall_config.h"
#include "skill.h"
#include "stat.h"
@@ -144,6 +145,11 @@ 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,
@@ -282,6 +288,8 @@ 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);
@@ -769,7 +777,7 @@ static int gCharacterEditorTaggedSkillCount;
static int gCharacterEditorTempTaggedSkills[NUM_TAGGED_SKILLS];
// 0x570A28
static char gCharacterEditorHasFreePerkBackup;
static unsigned char gCharacterEditorHasFreePerkBackup;
// 0x570A29
static unsigned char gCharacterEditorHasFreePerk;
@@ -919,7 +927,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 != 0) {
} else if (keyCode == 502 || keyCode == KEY_ESCAPE || keyCode == KEY_UPPERCASE_C || keyCode == KEY_LOWERCASE_C || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
windowRefresh(gCharacterEditorWindow);
rc = 1;
} else if (gCharacterEditorIsCreationMode && (keyCode == 517 || keyCode == KEY_UPPERCASE_N || keyCode == KEY_LOWERCASE_N)) {
@@ -1950,7 +1958,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 != 0) {
} else if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
rc = -1;
} else {
if ((keyCode == KEY_DELETE || keyCode == KEY_BACKSPACE) && nameLength >= 1) {
@@ -2474,7 +2482,7 @@ static void characterEditorDrawPrimaryStat(int stat, bool animate, int previousV
characterEditorDrawBigNumber(58, gCharacterEditorPrimaryStatY[stat], flags, value, previousValue, gCharacterEditorWindow);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, 40, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, kPrimaryStatDescriptionWidth, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
messageListItemId = critterGetStat(gDude, stat) + 199;
if (messageListItemId > 210) {
@@ -2486,7 +2494,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, 40, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, kPrimaryStatDescriptionWidth, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
value = critterGetStat(gDude, stat);
if (value > 10) {
@@ -3435,7 +3443,7 @@ static int characterEditorEditAge()
windowDestroy(win);
return 0;
} else if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != 0) {
} else if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
} else if (keyCode == 501) {
age = critterGetStat(gDude, STAT_AGE);
@@ -3535,7 +3543,7 @@ static int characterEditorEditAge()
}
keyCode = inputGetInput();
if (keyCode == 503 || keyCode == 504 || _game_user_wants_to_quit != 0) {
if (keyCode == 503 || keyCode == 504 || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
@@ -3667,7 +3675,7 @@ static void characterEditorEditGender()
break;
}
if (eventCode == KEY_ESCAPE || _game_user_wants_to_quit != 0) {
if (eventCode == KEY_ESCAPE || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
critterSetBaseStat(gDude, STAT_GENDER, savedGender);
characterEditorDrawPrimaryStat(RENDER_ALL_STATS, 0, 0);
characterEditorDrawDerivedStats();
@@ -3891,7 +3899,7 @@ static int characterEditorShowOptions()
int keyCode = inputGetInput();
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
rc = 2;
} else if (keyCode == 504) {
rc = 2;
@@ -5668,11 +5676,117 @@ 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
@@ -5682,6 +5796,13 @@ 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;
@@ -5700,30 +5821,13 @@ static int characterEditorUpdateLevel()
pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, sp);
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;
}
if (shouldReplayPerkGrants) {
characterEditorGrantPerkAtLevel(nextLevel);
}
}
}
if (gCharacterEditorHasFreePerk != 0) {
while (characterEditorGetPerkOwed() != 0) {
characterEditorWindowSelectedFolder = 0;
characterEditorDrawFolders();
windowRefresh(gCharacterEditorWindow);
@@ -5732,11 +5836,15 @@ static int characterEditorUpdateLevel()
if (rc == -1) {
debugPrint("\n *** Error running perks dialog! ***\n");
return -1;
} else if (rc == 0) {
}
if (rc == 0) {
characterEditorDrawFolders();
} else if (rc == 1) {
break;
}
if (rc == 1) {
characterEditorDrawFolders();
gCharacterEditorHasFreePerk = 0;
}
}
@@ -5785,6 +5893,10 @@ 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)) {
@@ -5910,6 +6022,12 @@ 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;
@@ -5931,7 +6049,8 @@ static int perkDialogShow()
int rc = perkDialogHandleInput(count, perkDialogRefreshPerks);
if (rc == 1) {
if (perkAdd(gDude, gPerkDialogOptionList[gPerkDialogTopLine + gPerkDialogCurrentLine].value) == -1) {
int selectionLevel = characterEditorGetPerkSelectionLevel();
if (perkAddAtLevel(gDude, gPerkDialogOptionList[gPerkDialogTopLine + gPerkDialogCurrentLine].value, selectionLevel) == -1) {
debugPrint("\n*** Unable to add perk! ***\n");
rc = 2;
}
@@ -5940,19 +6059,20 @@ static int perkDialogShow()
rc &= 1;
if (rc != 0) {
if (perkGetRank(gDude, PERK_TAG) != 0 && gCharacterEditorPerksBackup[PERK_TAG] == 0) {
characterEditorSetPerkOwed(characterEditorGetPerkOwed() - 1);
if (perkGetRank(gDude, PERK_TAG) != 0 && previousPerkRanks[PERK_TAG] == 0) {
if (!perkDialogHandleTagPerk()) {
perkRemove(gDude, PERK_TAG);
}
} else if (perkGetRank(gDude, PERK_MUTATE) != 0 && gCharacterEditorPerksBackup[PERK_MUTATE] == 0) {
} else if (perkGetRank(gDude, PERK_MUTATE) != 0 && previousPerkRanks[PERK_MUTATE] == 0) {
if (!perkDialogHandleMutatePerk()) {
perkRemove(gDude, PERK_MUTATE);
}
} else if (perkGetRank(gDude, PERK_LIFEGIVER) != gCharacterEditorPerksBackup[PERK_LIFEGIVER]) {
} else if (perkGetRank(gDude, PERK_LIFEGIVER) != previousPerkRanks[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) != gCharacterEditorPerksBackup[PERK_EDUCATED]) {
} else if (perkGetRank(gDude, PERK_EDUCATED) != previousPerkRanks[PERK_EDUCATED]) {
int sp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS);
pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, sp + 2);
}
@@ -6016,7 +6136,7 @@ static int perkDialogHandleInput(int count, void (*refreshProc)())
}
gPerkDialogPreviousCurrentLine = gPerkDialogCurrentLine;
refreshProc();
} else if (keyCode == 502 || keyCode == KEY_ESCAPE || _game_user_wants_to_quit != 0) {
} else if (keyCode == 502 || keyCode == KEY_ESCAPE || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
rc = 2;
} else {
switch (keyCode) {
@@ -6266,7 +6386,7 @@ static int perkDialogDrawPerks()
fontSetCurrent(101);
int perks[PERK_COUNT];
int count = perkGetAvailablePerks(gDude, perks);
int count = perkGetAvailablePerks(gDude, characterEditorGetPerkSelectionLevel(), perks);
if (count == 0) {
return 0;
}
+14
View File
@@ -7,6 +7,16 @@ 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);
@@ -14,6 +24,10 @@ 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 != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
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 == 0) {
if (_game_user_wants_to_quit == GAME_QUIT_REQUEST_NONE) {
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 == 0) {
if (_game_user_wants_to_quit == GAME_QUIT_REQUEST_NONE) {
_combat_give_exps(_combat_exps);
}
@@ -3167,7 +3167,7 @@ static int _combat_input()
break;
}
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
@@ -3205,8 +3205,8 @@ static int _combat_input()
}
int v4 = _game_user_wants_to_quit;
if (_game_user_wants_to_quit == 1) {
_game_user_wants_to_quit = 0;
if (_game_user_wants_to_quit == GAME_QUIT_REQUEST_END_COMBAT) {
_game_user_wants_to_quit = GAME_QUIT_REQUEST_NONE;
}
if ((gCombatState & COMBAT_STATE_0x08) != 0) {
@@ -3214,7 +3214,7 @@ static int _combat_input()
return -1;
}
if (_game_user_wants_to_quit != 0 || v4 != 0 || _combat_end_due_to_load != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE || v4 != GAME_QUIT_REQUEST_NONE || _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 == 1) {
if (_game_user_wants_to_quit == GAME_QUIT_REQUEST_END_COMBAT) {
return -1;
}
}
@@ -3511,8 +3511,8 @@ void _combat(CombatStartData* csd)
_combat_end_due_to_load = 0;
if (_game_user_wants_to_quit == 1) {
_game_user_wants_to_quit = 0;
if (_game_user_wants_to_quit == GAME_QUIT_REQUEST_END_COMBAT) {
_game_user_wants_to_quit = GAME_QUIT_REQUEST_NONE;
}
}
}
@@ -5669,7 +5669,7 @@ static int calledShotSelectHitLocation(Object* critter, int* hitLocation, int hi
break;
}
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
+1 -1
View File
@@ -912,7 +912,7 @@ void critterKill(Object* critter, int anim, bool refreshRect)
if (critter == gDude) {
endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_DEATH);
_game_user_wants_to_quit = 2;
_game_user_wants_to_quit = GAME_QUIT_REQUEST_MAIN_MENU;
}
}
+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 != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
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 != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
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 != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
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 != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
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 = 2;
_game_user_wants_to_quit = GAME_QUIT_REQUEST_MAIN_MENU;
}
} else {
rc = -1;
+6 -6
View File
@@ -114,7 +114,7 @@ int gGameGlobalVarsLength = 0;
const char* asc_5186C8 = _aGame_0;
// 0x5186CC
int _game_user_wants_to_quit = 0;
GameQuitRequest _game_user_wants_to_quit = GAME_QUIT_REQUEST_NONE;
// misc.msg
//
@@ -445,7 +445,7 @@ void gameReset()
_ResetLoadSave();
gameDialogReset();
combatReset();
_game_user_wants_to_quit = 0;
_game_user_wants_to_quit = GAME_QUIT_REQUEST_NONE;
automapReset();
_init_options_menu();
@@ -1274,7 +1274,7 @@ static void showHelp()
windowShow(overlay);
windowShow(win);
while (inputGetInput() == -1 && _game_user_wants_to_quit == 0) {
while (inputGetInput() == -1 && _game_user_wants_to_quit == GAME_QUIT_REQUEST_NONE) {
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 = 2;
_game_user_wants_to_quit = GAME_QUIT_REQUEST_MAIN_MENU;
}
} else {
rc = -1;
@@ -1579,8 +1579,8 @@ int gameShowDeathDialog(const char* message)
int oldCursor = gameMouseGetCursor();
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
int oldUserWantsToQuit = _game_user_wants_to_quit;
_game_user_wants_to_quit = 0;
GameQuitRequest oldUserWantsToQuit = _game_user_wants_to_quit;
_game_user_wants_to_quit = GAME_QUIT_REQUEST_NONE;
int rc = showDialogBox(message, nullptr, 0, 169, 117, _colorTable[32328], nullptr, _colorTable[32328], DIALOG_BOX_LARGE);
+8 -1
View File
@@ -16,10 +16,17 @@ 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 int _game_user_wants_to_quit;
extern GameQuitRequest _game_user_wants_to_quit;
extern MessageList gMiscMessageList;
+2
View File
@@ -11,6 +11,7 @@ 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"
@@ -86,6 +87,7 @@ 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"
+5 -5
View File
@@ -1525,7 +1525,7 @@ int gameDialogShowReview()
showQuitConfirmationDialog();
}
if (_game_user_wants_to_quit != 0 || keyCode == KEY_ESCAPE) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE || keyCode == KEY_ESCAPE) {
break;
}
@@ -1936,7 +1936,7 @@ int gameDialogProcessUI()
showQuitConfirmationDialog();
}
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
@@ -3772,7 +3772,7 @@ void partyMemberControlWindowHandleEvents()
showQuitConfirmationDialog();
}
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
@@ -4022,7 +4022,7 @@ void partyMemberCustomizationWindowHandleEvents()
showQuitConfirmationDialog();
}
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
@@ -4235,7 +4235,7 @@ int _gdCustomSelect(int option)
showQuitConfirmationDialog();
}
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
+1 -1
View File
@@ -1146,7 +1146,7 @@ void _gmouse_handle_event(int mouseX, int mouseY, int mouseState)
inputGetInput();
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
actionMenuItems[actionIndex] = 0;
}
+3 -3
View File
@@ -1569,7 +1569,7 @@ static void opPickup(Program* program)
int sid = scriptGetSid(program);
Script* script;
if (scriptGetScript(sid, &script) == 1) {
if (scriptGetScript(sid, &script) == -1) {
scriptPredefinedError(program, "pickup_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);
return;
}
@@ -3208,7 +3208,7 @@ static void opMetarule(Program* program)
switch (rule) {
case METARULE_SIGNAL_END_GAME:
result = 0;
_game_user_wants_to_quit = 2;
_game_user_wants_to_quit = GAME_QUIT_REQUEST_MAIN_MENU;
break;
case METARULE_FIRST_RUN:
result = (gMapHeader.flags & MAP_SAVED) == 0;
@@ -4775,7 +4775,7 @@ static void opCritterSetFleeState(Program* program)
static void opTerminateCombat(Program* program)
{
if (isInCombat()) {
_game_user_wants_to_quit = 1;
_game_user_wants_to_quit = GAME_QUIT_REQUEST_END_COMBAT;
Object* self = scriptGetSelf(program);
if (self != nullptr) {
if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) {
+6 -6
View File
@@ -669,7 +669,7 @@ void inventoryOpen()
break;
}
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
@@ -2696,7 +2696,7 @@ void inventoryOpenUseItemOn(Object* targetObj)
for (;;) {
sharedFpsLimiter.mark();
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
@@ -4328,7 +4328,7 @@ int inventoryOpenLooting(Object* looter, Object* target)
for (;;) {
sharedFpsLimiter.mark();
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
@@ -4342,7 +4342,7 @@ int inventoryOpenLooting(Object* looter, Object* target)
showQuitConfirmationDialog();
}
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
@@ -5212,7 +5212,7 @@ void barterProcessUI(int win, Object* barterer, Object* playerTable, Object* bar
for (;;) {
sharedFpsLimiter.mark();
if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != 0) {
if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
@@ -5221,7 +5221,7 @@ void barterProcessUI(int win, Object* barterer, Object* playerTable, Object* bar
showQuitConfirmationDialog();
}
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
break;
}
+6 -6
View File
@@ -528,7 +528,7 @@ int lsgSaveGame(int mode)
convertMouseWheelToArrowKey(&keyCode);
if (keyCode == KEY_ESCAPE || keyCode == 501 || _game_user_wants_to_quit != 0) {
if (keyCode == KEY_ESCAPE || keyCode == 501 || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
rc = 0;
} else {
switch (keyCode) {
@@ -659,7 +659,7 @@ int lsgSaveGame(int mode)
case KEY_F10:
showQuitConfirmationDialog();
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
rc = 0;
}
break;
@@ -1057,7 +1057,7 @@ int lsgLoadGame(int mode)
messageListFree(&gLoadSaveMessageList);
mapNewMap();
_game_user_wants_to_quit = 2;
_game_user_wants_to_quit = GAME_QUIT_REQUEST_MAIN_MENU;
return -1;
}
@@ -1141,7 +1141,7 @@ int lsgLoadGame(int mode)
convertMouseWheelToArrowKey(&keyCode);
if (keyCode == KEY_ESCAPE || keyCode == 501 || _game_user_wants_to_quit != 0) {
if (keyCode == KEY_ESCAPE || keyCode == 501 || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
rc = 0;
} else {
switch (keyCode) {
@@ -1281,7 +1281,7 @@ int lsgLoadGame(int mode)
case KEY_CTRL_X:
case KEY_F10:
showQuitConfirmationDialog();
if (_game_user_wants_to_quit != 0) {
if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) {
rc = 0;
}
break;
@@ -1462,7 +1462,7 @@ int lsgLoadGame(int mode)
strcpy(_str1, getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 135));
showDialogBox(_str0, body, 1, 169, 116, _colorTable[32328], nullptr, _colorTable[32328], DIALOG_BOX_LARGE);
mapNewMap();
_game_user_wants_to_quit = 2;
_game_user_wants_to_quit = GAME_QUIT_REQUEST_MAIN_MENU;
rc = -1;
}
break;

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