Subversion Repositories Games.Prince of Persia

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. /*
  2. SDLPoP, a port/conversion of the DOS game Prince of Persia.
  3. Copyright (C) 2013-2018  Dávid Nagy
  4.  
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9.  
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. GNU General Public License for more details.
  14.  
  15. You should have received a copy of the GNU General Public License
  16. along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17.  
  18. The authors of this program may be contacted at http://forum.princed.org
  19. */
  20.  
  21. #include "common.h"
  22. #include <ctype.h>
  23. #include <inttypes.h>
  24.  
  25.  
  26. extern int filter_level;
  27.  
  28.  
  29. void disable_fixes_and_enhancements() {
  30.         enable_crouch_after_climbing = 0;
  31.         enable_freeze_time_during_end_music = 0;
  32.         enable_remember_guard_hp = 0;
  33.         fix_gate_sounds = 0;
  34.         fix_two_coll_bug = 0;
  35.         fix_infinite_down_bug = 0;
  36.         fix_gate_drawing_bug = 0;
  37.         fix_bigpillar_climb = 0;
  38.         fix_jump_distance_at_edge = 0;
  39.         fix_edge_distance_check_when_climbing = 0;
  40.         fix_painless_fall_on_guard = 0;
  41.         fix_wall_bump_triggers_tile_below = 0;
  42.         fix_stand_on_thin_air = 0;
  43.         fix_press_through_closed_gates = 0;
  44.         fix_grab_falling_speed = 0;
  45.         fix_skeleton_chomper_blood = 0;
  46.         fix_move_after_drink = 0;
  47.         fix_loose_left_of_potion = 0;
  48.         fix_guard_following_through_closed_gates = 0;
  49.         fix_safe_landing_on_spikes = 0;
  50.         fix_glide_through_wall = 0;
  51.         fix_drop_through_tapestry = 0;
  52.         fix_land_against_gate_or_tapestry = 0;
  53.         fix_unintended_sword_strike = 0;
  54.         fix_retreat_without_leaving_room = 0;
  55.         fix_running_jump_through_tapestry= 0;
  56.         fix_push_guard_into_wall = 0;
  57.         fix_jump_through_wall_above_gate = 0;
  58.         fix_chompers_not_starting = 0;
  59.         fix_feather_interrupted_by_leveldoor = 0;
  60.         fix_offscreen_guards_disappearing = 0;
  61.         fix_move_after_sheathe = 0;
  62. }
  63.  
  64. // .ini file parser adapted from https://gist.github.com/OrangeTide/947070
  65. /* Load an .ini format file
  66.  * filename - path to a file
  67.  * report - callback can return non-zero to stop, the callback error code is
  68.  *     returned from this function.
  69.  * return - return 0 on success
  70.  */
  71. int ini_load(const char *filename,
  72.              int (*report)(const char *section, const char *name, const char *value))
  73. {
  74.         char name[64];
  75.         char value[256];
  76.         char section[128] = "";
  77.         char *s;
  78.         FILE *f;
  79.         int cnt;
  80.  
  81.         f = fopen(filename, "r");
  82.         if (!f) {
  83.                 return -1;
  84.         }
  85.  
  86.         while (!feof(f)) {
  87.                 if (fscanf(f, "[%127[^];\n]]\n", section) == 1) {
  88.                 } else if ((cnt = fscanf(f, " %63[^=;\n] = %255[^;\n]", name, value))) {
  89.                         if (cnt == 1)
  90.                                 *value = 0;
  91.                         for (s = name + strlen(name) - 1; s > name && isspace(*s); s--)
  92.                                 *s = 0;
  93.                         for (s = value + strlen(value) - 1; s > value && isspace(*s); s--)
  94.                                 *s = 0;
  95.                         report(section, name, value);
  96.                 }
  97.                 fscanf(f, " ;%*[^\n]");
  98.                 fscanf(f, " \n");
  99.         }
  100.  
  101.         fclose(f);
  102.         return 0;
  103. }
  104.  
  105. #define MAX_NAME_LENGTH 20
  106. typedef struct ini_value_list_type {
  107.         const char (* names)[][MAX_NAME_LENGTH];
  108.         word num_names;
  109. } ini_value_list_type;
  110.  
  111. const char level_type_names[][MAX_NAME_LENGTH] = {"dungeon", "palace"};
  112. const char guard_type_names[][MAX_NAME_LENGTH] = {"guard", "fat", "skel", "vizier", "shadow"};
  113. const char tile_type_names[][MAX_NAME_LENGTH] = {
  114.                                 "empty", "floor", "spike", "pillar", "gate",                                        // 0..4
  115.                                 "stuck", "closer", "doortop_with_floor", "bigpillar_bottom", "bigpillar_top",       // 5..9
  116.                                 "potion", "loose", "doortop", "mirror", "debris",                                   // 10..14
  117.                                 "opener", "level_door_left", "level_door_right", "chomper", "torch",                // 15..19
  118.                                 "wall", "skeleton", "sword", "balcony_left", "balcony_right",                       // 20..24
  119.                                 "lattice_pillar", "lattice_down", "lattice_small", "lattice_left", "lattice_right", // 25..29
  120.                                 "torch_with_debris", // 30
  121. };
  122.  
  123. ini_value_list_type level_type_names_list = {&level_type_names, COUNT(level_type_names)};
  124. ini_value_list_type guard_type_names_list = {&guard_type_names, COUNT(guard_type_names)};
  125. ini_value_list_type tile_type_names_list = {&tile_type_names, COUNT(tile_type_names)};
  126.  
  127. #define INI_NO_VALID_NAME -9999
  128.  
  129. static inline int ini_get_named_value(const char* value, ini_value_list_type* value_names) {
  130.         if (value_names != NULL) {
  131.                 int i;
  132.                 char *base_ptr = (char *) value_names->names;
  133.                 for (i = 0; i < value_names->num_names; ++i) {
  134.                         char *name = (base_ptr + i * MAX_NAME_LENGTH);
  135.                         if (strcasecmp(value, name) == 0) return i;
  136.                 }
  137.         }
  138.         return INI_NO_VALID_NAME; // failure
  139. }
  140.  
  141. static inline int ini_process_boolean(const char* curr_name, const char* value, const char* option_name, byte* target) {
  142.         if(strcasecmp(curr_name, option_name) == 0) {
  143.                 if (strcasecmp(value, "true") == 0) *target = 1;
  144.                 else if (strcasecmp(value, "false") == 0) *target = 0;
  145.                 return 1; // finished; don't look for more possible options that curr_name can be
  146.         }
  147.         return 0; // not the right option; should check another option_name
  148. }
  149.  
  150. #define ini_process_numeric_func(data_type) \
  151. static inline int ini_process_##data_type(const char* curr_name, const char* value, const char* option_name, data_type* target, ini_value_list_type* value_names) { \
  152.         if(strcasecmp(curr_name, option_name) == 0) { \
  153.                 if (strcasecmp(value, "default") != 0) { \
  154.                         int named_value = ini_get_named_value(value, value_names); \
  155.                         *target = (named_value == INI_NO_VALID_NAME) ? ((data_type) strtoimax(value, NULL, 0)) : ((data_type) named_value); \
  156.                 } \
  157.                 return 1; /* finished; don't look for more possible options that curr_name can be */ \
  158.         } \
  159.         return 0; /* not the right option; should check another option_name */ \
  160. }
  161. ini_process_numeric_func(word)
  162. ini_process_numeric_func(short)
  163. ini_process_numeric_func(byte)
  164. ini_process_numeric_func(int)
  165.  
  166. static int global_ini_callback(const char *section, const char *name, const char *value)
  167. {
  168.         //fprintf(stdout, "[%s] '%s'='%s'\n", section, name, value);
  169.  
  170.         #define check_ini_section(section_name)    (strcasecmp(section, section_name) == 0)
  171.  
  172.         // Make sure that we return successfully as soon as name matches the correct option_name
  173.         #define process_word(option_name, target, value_names)                           \
  174.         if (ini_process_word(name, value, option_name, target, value_names)) return 1;
  175.  
  176.         #define process_short(option_name, target, value_names)                           \
  177.         if (ini_process_short(name, value, option_name, target, value_names)) return 1;
  178.  
  179.         #define process_byte(option_name, target, value_names)                           \
  180.         if (ini_process_byte(name, value, option_name, target, value_names)) return 1;
  181.  
  182.         #define process_int(option_name, target, value_names)                           \
  183.         if (ini_process_int(name, value, option_name, target, value_names)) return 1;
  184.  
  185.         #define process_boolean(option_name, target)                        \
  186.         if (ini_process_boolean(name, value, option_name, target)) return 1;
  187.  
  188.         if (check_ini_section("General")) {
  189.                 process_boolean("enable_copyprot", &enable_copyprot);
  190.                 process_boolean("enable_mixer", &enable_mixer);
  191.                 process_int("mixer_volume", &mixer_volume, NULL);
  192.                 process_boolean("enable_fade", &enable_fade);
  193.                 process_boolean("enable_flash", &enable_flash);
  194.                 process_boolean("enable_text", &enable_text);
  195.                 process_boolean("enable_info_screen", &enable_info_screen);
  196.                 process_boolean("start_fullscreen", &start_fullscreen);
  197.                 process_word("pop_window_width", &pop_window_width, NULL);
  198.                 process_word("pop_window_height", &pop_window_height, NULL);
  199.                 process_boolean("use_correct_aspect_ratio", &use_correct_aspect_ratio);
  200.                 process_boolean("use_integer_scaling", &use_integer_scaling);
  201.                 process_boolean("enable_controller_rumble", &enable_controller_rumble);
  202.                 process_boolean("joystick_only_horizontal", &joystick_only_horizontal);
  203.                 process_int("joystick_threshold", &joystick_threshold, NULL);
  204.  
  205.                 if (strcasecmp(name, "levelset") == 0) {
  206.                         if (value[0] == '\0' || strcasecmp(value, "original") == 0 || strcasecmp(value, "default") == 0) {
  207.                                 use_custom_levelset = 0;
  208.                         } else {
  209.                                 use_custom_levelset = 1;
  210.                                 strcpy(levelset_name, value);
  211.                         }
  212.                         return 1;
  213.                 }
  214.         }
  215.  
  216.         if (check_ini_section("AdditionalFeatures")) {
  217.                 process_boolean("enable_quicksave", &enable_quicksave);
  218.                 process_boolean("enable_quicksave_penalty", &enable_quicksave_penalty);
  219.  
  220. #ifdef USE_REPLAY
  221.                 process_boolean("enable_replay", &enable_replay);
  222.  
  223.                 if (strcasecmp(name, "replays_folder") == 0) {
  224.                         if (value[0] != '\0' && strcasecmp(value, "default") != 0) {
  225.                                 strcpy(replays_folder, value);
  226.                         }
  227.                         return 1;
  228.                 }
  229. #endif
  230. #ifdef USE_LIGHTING
  231.                 process_boolean("enable_lighting", &enable_lighting);
  232. #endif
  233.                 process_int("filter_level", &filter_level, NULL);
  234.         }
  235.  
  236.         if (check_ini_section("Enhancements")) {
  237.                 if (strcasecmp(name, "use_fixes_and_enhancements") == 0) {
  238.                         if (strcasecmp(value, "true") == 0) use_fixes_and_enhancements = 1;
  239.                         else if (strcasecmp(value, "false") == 0) use_fixes_and_enhancements = 0;
  240.                         else if (strcasecmp(value, "prompt") == 0) use_fixes_and_enhancements = 2;
  241.                         return 1;
  242.                 }
  243.                 process_boolean("enable_crouch_after_climbing", &enable_crouch_after_climbing);
  244.                 process_boolean("enable_freeze_time_during_end_music", &enable_freeze_time_during_end_music);
  245.                 process_boolean("enable_remember_guard_hp", &enable_remember_guard_hp);
  246.                 process_boolean("fix_gate_sounds", &fix_gate_sounds);
  247.                 process_boolean("fix_two_coll_bug", &fix_two_coll_bug);
  248.                 process_boolean("fix_infinite_down_bug", &fix_infinite_down_bug);
  249.                 process_boolean("fix_gate_drawing_bug", &fix_gate_drawing_bug);
  250.                 process_boolean("fix_bigpillar_climb", &fix_bigpillar_climb);
  251.                 process_boolean("fix_jump_distance_at_edge", &fix_jump_distance_at_edge);
  252.                 process_boolean("fix_edge_distance_check_when_climbing", &fix_edge_distance_check_when_climbing);
  253.                 process_boolean("fix_painless_fall_on_guard", &fix_painless_fall_on_guard);
  254.                 process_boolean("fix_wall_bump_triggers_tile_below", &fix_wall_bump_triggers_tile_below);
  255.                 process_boolean("fix_stand_on_thin_air", &fix_stand_on_thin_air);
  256.                 process_boolean("fix_press_through_closed_gates", &fix_press_through_closed_gates);
  257.                 process_boolean("fix_grab_falling_speed", &fix_grab_falling_speed);
  258.                 process_boolean("fix_skeleton_chomper_blood", &fix_skeleton_chomper_blood);
  259.                 process_boolean("fix_move_after_drink", &fix_move_after_drink);
  260.                 process_boolean("fix_loose_left_of_potion", &fix_loose_left_of_potion);
  261.                 process_boolean("fix_guard_following_through_closed_gates", &fix_guard_following_through_closed_gates);
  262.                 process_boolean("fix_safe_landing_on_spikes", &fix_safe_landing_on_spikes);
  263.                 process_boolean("fix_glide_through_wall", &fix_glide_through_wall);
  264.                 process_boolean("fix_drop_through_tapestry", &fix_drop_through_tapestry);
  265.                 process_boolean("fix_land_against_gate_or_tapestry", &fix_land_against_gate_or_tapestry);
  266.                 process_boolean("fix_unintended_sword_strike", &fix_unintended_sword_strike);
  267.                 process_boolean("fix_retreat_without_leaving_room", &fix_retreat_without_leaving_room);
  268.                 process_boolean("fix_running_jump_through_tapestry", &fix_running_jump_through_tapestry);
  269.                 process_boolean("fix_push_guard_into_wall", &fix_push_guard_into_wall);
  270.                 process_boolean("fix_jump_through_wall_above_gate", &fix_jump_through_wall_above_gate);
  271.                 process_boolean("fix_chompers_not_starting", &fix_chompers_not_starting);
  272.                 process_boolean("fix_feather_interrupted_by_leveldoor", &fix_feather_interrupted_by_leveldoor);
  273.                 process_boolean("fix_offscreen_guards_disappearing", &fix_offscreen_guards_disappearing);
  274.                 process_boolean("fix_move_after_sheathe", &fix_move_after_sheathe);
  275.         }
  276.  
  277.         if (check_ini_section("CustomGameplay")) {
  278.                 process_word("start_minutes_left", &start_minutes_left, NULL);
  279.                 process_word("start_ticks_left", &start_ticks_left, NULL);
  280.                 process_word("start_hitp", &start_hitp, NULL);
  281.                 process_word("max_hitp_allowed", &max_hitp_allowed, NULL);
  282.                 process_word("saving_allowed_first_level", &saving_allowed_first_level, NULL);
  283.                 process_word("saving_allowed_last_level", &saving_allowed_last_level, NULL);
  284.                 process_boolean("start_upside_down", &start_upside_down);
  285.                 process_boolean("start_in_blind_mode", &start_in_blind_mode);
  286.                 process_word("copyprot_level", &copyprot_level, NULL);
  287.                 process_byte("drawn_tile_top_level_edge", &drawn_tile_top_level_edge, &tile_type_names_list);
  288.                 process_byte("drawn_tile_left_level_edge", &drawn_tile_left_level_edge, &tile_type_names_list);
  289.                 process_byte("level_edge_hit_tile", &level_edge_hit_tile, &tile_type_names_list);
  290.                 process_boolean("allow_triggering_any_tile", &allow_triggering_any_tile);
  291.                 // TODO: Maybe allow automatically choosing the correct WDA, depending on the loaded VDUNGEON.DAT?
  292.                 process_boolean("enable_wda_in_palace", &enable_wda_in_palace);
  293.  
  294.                 // Options that change the hard-coded color palette (options 'vga_color_0', 'vga_color_1', ...)
  295.                 static const char prefix[] = "vga_color_";
  296.                 static const size_t prefix_len = sizeof(prefix)-1;
  297.                 int ini_palette_color = -1;
  298.                 if (strncasecmp(name, prefix, prefix_len) == 0 && sscanf(name+prefix_len, "%d", &ini_palette_color) == 1) {
  299.                         if (!(ini_palette_color >= 0 && ini_palette_color <= 15)) return 0;
  300.  
  301.                         byte rgb[3] = {0};
  302.                         if (strcasecmp(value, "default") != 0) {
  303.                                 // We want to parse an rgb string with three entries like this: "255, 255, 255"
  304.                                 char* start = (char*) value;
  305.                                 char* end   = (char*) value;
  306.                                 int i;
  307.                                 for (i = 0; i < 3 && *end != '\0'; ++i) {
  308.                                         rgb[i] = (byte) strtol(start, &end, 0); // convert this entry into a number 0..255
  309.  
  310.                                         while (*end == ',' || *end == ' ') {
  311.                                                 ++end; // skip delimiter characters or whitespace
  312.                                         }
  313.                                         start = end; // start parsing the next entry here
  314.                                 }
  315.                         }
  316.                         rgb_type* palette_color = &vga_palette[ini_palette_color];
  317.                         palette_color->r = rgb[0] / 4; // the palette uses values 0..63, not 0..255
  318.                         palette_color->g = rgb[1] / 4;
  319.                         palette_color->b = rgb[2] / 4;
  320.                         return 1;
  321.                 }
  322.                 process_word("first_level", &first_level, NULL);
  323.                 process_boolean("skip_title", &skip_title);
  324.                 process_word("shift_L_allowed_until_level", &shift_L_allowed_until_level, NULL);
  325.                 process_word("shift_L_reduced_minutes", &shift_L_reduced_minutes, NULL);
  326.                 process_word("shift_L_reduced_ticks", &shift_L_reduced_ticks, NULL);
  327.         } // end of section [CustomGameplay]
  328.  
  329.         // [Level 1], etc.
  330.         int ini_level = -1;
  331.         if (strncasecmp(section, "Level ", 6) == 0 && sscanf(section+6, "%d", &ini_level) == 1) {
  332.                 if (ini_level >= 0 && ini_level <= 15) {
  333.                         // TODO: And maybe allow new types in addition to the existing ones.
  334.                         process_byte("level_type", &tbl_level_type[ini_level], &level_type_names_list);
  335.                         process_word("level_color", &tbl_level_color[ini_level], NULL);
  336.                         process_short("guard_type", &tbl_guard_type[ini_level], &guard_type_names_list);
  337.                         process_byte("guard_hp", &tbl_guard_hp[ini_level], NULL);
  338.  
  339.                         byte cutscene_index = 0xFF;
  340.                         if (ini_process_byte(name, value, "cutscene", &cutscene_index, NULL) == 1) {
  341.                                 if (cutscene_index < COUNT(tbl_cutscenes_lookup)) {
  342.                                         tbl_cutscenes_by_index[ini_level] = cutscene_index;
  343.                                         tbl_cutscenes[ini_level] = tbl_cutscenes_lookup[cutscene_index];
  344.                                 }
  345.                                 return 1;
  346.                         }
  347.                 } else {
  348.                         // TODO: warning?
  349.                 }
  350.         }
  351.         return 0;
  352. }
  353.  
  354. // Callback for a mod-specific INI configuration (that may overrule SDLPoP.ini for SOME but not all options):
  355. static int mod_ini_callback(const char *section, const char *name, const char *value) {
  356.         if (check_ini_section("Enhancements") || check_ini_section("CustomGameplay") ||
  357.                 strncasecmp(section, "Level ", 6) == 0 ||
  358.                 strcasecmp(name, "enable_copyprot") == 0 ||
  359.                 strcasecmp(name, "enable_quicksave") == 0 ||
  360.                 strcasecmp(name, "enable_quicksave_penalty") == 0
  361.         ) {
  362.                 global_ini_callback(section, name, value);
  363.         }
  364.         return 0;
  365. }
  366.  
  367. void load_global_options() {
  368.         ini_load(locate_file("game.ini"), global_ini_callback); // global configuration
  369. }
  370.  
  371. void check_mod_param() {
  372.         // The 'mod' command line argument can override the levelset choice in SDLPoP.ini
  373.         // usage: prince mod "Mod Name"
  374.         const char* mod_param = check_param("mod");
  375.         if (mod_param != NULL) {
  376.                 use_custom_levelset = true;
  377.                 memset(levelset_name, 0, sizeof(levelset_name));
  378.                 strncpy(levelset_name, mod_param, sizeof(levelset_name));
  379.         }
  380. }
  381.  
  382. void load_mod_options() {
  383.         // load mod-specific INI configuration
  384.         if (use_custom_levelset) {
  385.                 char filename[POP_MAX_PATH];
  386.                 snprintf(filename, sizeof(filename), "mods/%s/%s", levelset_name, "mod.ini");
  387.                 ini_load(filename, mod_ini_callback);
  388.         }
  389.  
  390.         if (!use_fixes_and_enhancements) disable_fixes_and_enhancements();
  391. }
  392.  
  393. void show_use_fixes_and_enhancements_prompt() {
  394.         if (use_fixes_and_enhancements != 2) return;
  395.         draw_rect(&screen_rect, 0);
  396.         show_text(&screen_rect, 0, 0,
  397.                 "\n"
  398.                 "Enable bug fixes and\n"
  399.                 "gameplay enhancements?\n"
  400.                 "\n"
  401.                 "NOTE:\n"
  402.                 "This option disables some game quirks.\n"
  403.                 "Certain tricks will no longer work by default.\n"
  404.                 "\n"
  405.                 "\n"
  406.                 "Y:  enhanced behavior \n"
  407.                 "N:  original behavior    \n"
  408.                 "\n"
  409.                 "Y / N ?\n"
  410.                 "\n"
  411.                 "\n"
  412.                 "\n"
  413.                 "You can fine-tune your preferences\n"
  414.                 "and/or bypass this screen by editing the file\n"
  415.                 "'SDLPoP.ini'"
  416.         );
  417.         while (use_fixes_and_enhancements == 2 ) {
  418.                 idle();
  419.                 switch (key_test_quit()) {
  420.                         case SDL_SCANCODE_Y:
  421.                                 use_fixes_and_enhancements = 1;
  422.                                 printf("Enabling game fixes and enhancements.\n");
  423.                                 break;
  424.                         case SDL_SCANCODE_N:
  425.                                 use_fixes_and_enhancements = 0;
  426.                                 printf("Disabling game fixes and enhancements.\n");
  427.                                 break;
  428.                 }
  429.         }
  430.         if (!use_fixes_and_enhancements) disable_fixes_and_enhancements();
  431. }
  432.  
  433.  
  434.