Subversion Repositories Games.Prince of Persia

Rev

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

Rev Author Line No. Line
1 pmbaty 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
 
2 pmbaty 26
extern int filter_level;
1 pmbaty 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);
7 pmbaty 191
                process_int("mixer_volume", &mixer_volume, NULL);
1 pmbaty 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
2 pmbaty 233
                process_int("filter_level", &filter_level, NULL);
1 pmbaty 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