forked from NagyD/SDLPoP
/
menu.c
2861 lines (2606 loc) · 109 KB
/
menu.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
SDLPoP, a port/conversion of the DOS game Prince of Persia.
Copyright (C) 2013-2019 Dávid Nagy
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
The authors of this program may be contacted at https://forum.princed.org
*/
#include "common.h"
#ifdef USE_MENU
byte arrowhead_up_image_data[];
byte arrowhead_down_image_data[];
byte arrowhead_left_image_data[];
byte arrowhead_right_image_data[];
image_type* arrowhead_up_image;
image_type* arrowhead_down_image;
image_type* arrowhead_left_image;
image_type* arrowhead_right_image;
void load_arrowhead_images() {
// Make a dummy palette for decode_image().
dat_pal_type dat_pal;
memset(&dat_pal, 0, sizeof(dat_pal));
dat_pal.vga[1].r = dat_pal.vga[1].g = dat_pal.vga[1].b = 0x3F; // white
if (arrowhead_up_image == NULL) {
arrowhead_up_image = decode_image((image_data_type*) arrowhead_up_image_data, &dat_pal);
}
if (arrowhead_down_image == NULL) {
arrowhead_down_image = decode_image((image_data_type*) arrowhead_down_image_data, &dat_pal);
}
// dat_pal.vga[1] = vga_palette[color_7_lightgray];
if (arrowhead_left_image == NULL) {
arrowhead_left_image = decode_image((image_data_type*) arrowhead_left_image_data, &dat_pal);
}
if (arrowhead_right_image == NULL) {
arrowhead_right_image = decode_image((image_data_type*) arrowhead_right_image_data, &dat_pal);
}
}
#define MAX_MENU_ITEM_LENGTH 32
typedef struct pause_menu_item_type pause_menu_item_type;
struct pause_menu_item_type {
int id;
pause_menu_item_type* previous;
pause_menu_item_type* next;
void* required;
char text[MAX_MENU_ITEM_LENGTH];
};
enum pause_menu_item_ids {
PAUSE_MENU_RESUME,
PAUSE_MENU_CHEATS,
PAUSE_MENU_SAVE_GAME,
PAUSE_MENU_LOAD_GAME,
PAUSE_MENU_RESTART_LEVEL,
PAUSE_MENU_SETTINGS,
PAUSE_MENU_QUIT_GAME,
SETTINGS_MENU_GENERAL,
SETTINGS_MENU_GAMEPLAY,
SETTINGS_MENU_VISUALS,
SETTINGS_MENU_MODS,
SETTINGS_MENU_LEVEL_CUSTOMIZATION,
SETTINGS_MENU_BACK,
};
pause_menu_item_type pause_menu_items[] = {
{.id = PAUSE_MENU_RESUME, .text = "RESUME"},
// TODO: Add a cheats menu, where you can choose a cheat from a list?
/*{.id = PAUSE_MENU_CHEATS, .text = "CHEATS", .required = &cheats_enabled},*/
{.id = PAUSE_MENU_SAVE_GAME, .text = "SAVE GAME"},
{.id = PAUSE_MENU_LOAD_GAME, .text = "LOAD GAME"},
{.id = PAUSE_MENU_RESTART_LEVEL, .text = "RESTART LEVEL"},
{.id = PAUSE_MENU_SETTINGS, .text = "SETTINGS"},
{.id = PAUSE_MENU_QUIT_GAME, .text = "QUIT GAME"},
};
int hovering_pause_menu_item = PAUSE_MENU_RESUME;
pause_menu_item_type* next_pause_menu_item;
pause_menu_item_type* previous_pause_menu_item;
int drawn_menu;
byte pause_menu_alpha;
int current_dialog_box;
const char* current_dialog_text;
word menu_current_level = 1;
bool need_close_menu;
enum menu_dialog_ids {
DIALOG_NONE,
DIALOG_RESTORE_DEFAULT_SETTINGS,
DIALOG_CONFIRM_QUIT,
DIALOG_SELECT_LEVEL,
};
pause_menu_item_type settings_menu_items[] = {
{.id = SETTINGS_MENU_GENERAL, .text = "GENERAL"},
{.id = SETTINGS_MENU_GAMEPLAY, .text = "GAMEPLAY"},
{.id = SETTINGS_MENU_VISUALS, .text = "VISUALS"},
{.id = SETTINGS_MENU_MODS, .text = "MODS"},
{.id = SETTINGS_MENU_BACK, .text = "BACK"},
};
int active_settings_subsection = 0;
int highlighted_settings_subsection = 0;
int scroll_position = 0;
int menu_control_y;
int menu_control_scroll_y;
int menu_control_x;
int menu_control_back;
enum menu_setting_style_ids {
SETTING_STYLE_TOGGLE,
SETTING_STYLE_NUMBER,
SETTING_STYLE_TEXT_ONLY,
};
enum menu_setting_number_type_ids {
SETTING_BYTE = 0,
SETTING_SBYTE = 1,
SETTING_WORD = 2,
SETTING_SHORT = 3,
//SETTING_DWORD = 4,
SETTING_INT = 5,
};
enum setting_ids {
SETTING_RESET_ALL_SETTINGS,
SETTING_SHOW_MENU_ON_PAUSE,
SETTING_ENABLE_INFO_SCREEN,
SETTING_ENABLE_SOUND,
SETTING_ENABLE_MUSIC,
SETTING_ENABLE_DIRECTIONAL_SOUND,
SETTING_ENABLE_CONTROLLER_RUMBLE,
SETTING_JOYSTICK_THRESHOLD,
SETTING_JOYSTICK_ONLY_HORIZONTAL,
SETTING_FULLSCREEN,
SETTING_USE_CORRECT_ASPECT_RATIO,
SETTING_USE_INTEGER_SCALING,
SETTING_SCALING_TYPE,
SETTING_ENABLE_FADE,
SETTING_ENABLE_FLASH,
SETTING_ENABLE_LIGHTING,
SETTING_ENABLE_CHEATS,
SETTING_ENABLE_COPYPROT,
SETTING_ENABLE_QUICKSAVE,
SETTING_ENABLE_QUICKSAVE_PENALTY,
SETTING_ENABLE_REPLAY,
SETTING_USE_FIXES_AND_ENHANCEMENTS,
SETTING_ENABLE_CROUCH_AFTER_CLIMBING,
SETTING_ENABLE_FREEZE_TIME_DURING_END_MUSIC,
SETTING_ENABLE_REMEMBER_GUARD_HP,
SETTING_FIX_GATE_SOUNDS,
SETTING_TWO_COLL_BUG,
SETTING_FIX_INFINITE_DOWN_BUG,
SETTING_FIX_GATE_DRAWING_BUG,
SETTING_FIX_BIGPILLAR_CLIMB,
SETTING_FIX_JUMP_DISTANCE_AT_EDGE,
SETTING_FIX_EDGE_DISTANCE_CHECK_WHEN_CLIMBING,
SETTING_FIX_PAINLESS_FALL_ON_GUARD,
SETTING_FIX_WALL_BUMP_TRIGGERS_TILE_BELOW,
SETTING_FIX_STAND_ON_THIN_AIR,
SETTING_FIX_PRESS_THROUGH_CLOSED_GATES,
SETTING_FIX_GRAB_FALLING_SPEED,
SETTING_FIX_SKELETON_CHOMPER_BLOOD,
SETTING_FIX_MOVE_AFTER_DRINK,
SETTING_FIX_LOOSE_LEFT_OF_POTION,
SETTING_FIX_GUARD_FOLLOWING_THROUGH_CLOSED_GATES,
SETTING_FIX_SAFE_LANDING_ON_SPIKES,
SETTING_FIX_GLIDE_THROUGH_WALL,
SETTING_FIX_DROP_THROUGH_TAPESTRY,
SETTING_FIX_LAND_AGAINST_GATE_OR_TAPESTRY,
SETTING_FIX_UNINTENDED_SWORD_STRIKE,
SETTING_FIX_RETREAT_WITHOUT_LEAVING_ROOM,
SETTING_FIX_RUNNING_JUMP_THROUGH_TAPESTRY,
SETTING_FIX_PUSH_GUARD_INTO_WALL,
SETTING_FIX_JUMP_THROUGH_WALL_ABOVE_GATE,
SETTING_FIX_CHOMPERS_NOT_STARTING,
SETTING_FIX_FEATHER_INTERRUPTED_BY_LEVELDOOR,
SETTING_FIX_OFFSCREEN_GUARDS_DISAPPEARING,
SETTING_FIX_MOVE_AFTER_SHEATHE,
SETTING_FIX_HIDDEN_FLOORS_DURING_FLASHING,
SETTING_FIX_HANG_ON_TELEPORT,
SETTING_FIX_EXIT_DOOR,
SETTING_USE_CUSTOM_OPTIONS,
SETTING_START_MINUTES_LEFT,
SETTING_START_TICKS_LEFT,
SETTING_START_HITP,
SETTING_MAX_HITP_ALLOWED,
SETTING_SAVING_ALLOWED_FIRST_LEVEL,
SETTING_SAVING_ALLOWED_LAST_LEVEL,
SETTING_START_UPSIDE_DOWN,
SETTING_START_IN_BLIND_MODE,
SETTING_COPYPROT_LEVEL,
SETTING_DRAWN_TILE_TOP_LEVEL_EDGE,
SETTING_DRAWN_TILE_LEFT_LEVEL_EDGE,
SETTING_LEVEL_EDGE_HIT_TILE,
SETTING_ALLOW_TRIGGERING_ANY_TILE,
SETTING_ENABLE_WDA_IN_PALACE,
SETTING_FIRST_LEVEL,
SETTING_SKIP_TITLE,
SETTING_SHIFT_L_ALLOWED_UNTIL_LEVEL,
SETTING_SHIFT_L_REDUCED_MINUTES,
SETTING_SHIFT_L_REDUCED_TICKS,
SETTING_DEMO_HITP,
SETTING_DEMO_END_ROOM,
SETTING_INTRO_MUSIC_LEVEL,
SETTING_HAVE_SWORD_FROM_LEVEL,
SETTING_CHECKPOINT_LEVEL,
SETTING_CHECKPOINT_RESPAWN_DIR,
SETTING_CHECKPOINT_RESPAWN_ROOM,
SETTING_CHECKPOINT_RESPAWN_TILEPOS,
SETTING_CHECKPOINT_CLEAR_TILE_ROOM,
SETTING_CHECKPOINT_CLEAR_TILE_COL,
SETTING_CHECKPOINT_CLEAR_TILE_ROW,
SETTING_SKELETON_LEVEL,
SETTING_SKELETON_ROOM,
SETTING_SKELETON_TRIGGER_COLUMN_1,
SETTING_SKELETON_TRIGGER_COLUMN_2,
SETTING_SKELETON_COLUMN,
SETTING_SKELETON_ROW,
SETTING_SKELETON_REQUIRE_OPEN_LEVEL_DOOR,
SETTING_SKELETON_SKILL,
SETTING_SKELETON_REAPPEAR_ROOM,
SETTING_SKELETON_REAPPEAR_X,
SETTING_SKELETON_REAPPEAR_ROW,
SETTING_SKELETON_REAPPEAR_DIR,
SETTING_MIRROR_LEVEL,
SETTING_MIRROR_ROOM,
SETTING_MIRROR_COLUMN,
SETTING_MIRROR_ROW,
SETTING_MIRROR_TILE,
SETTING_SHOW_MIRROR_IMAGE,
SETTING_FALLING_EXIT_LEVEL,
SETTING_FALLING_EXIT_ROOM,
SETTING_FALLING_ENTRY_LEVEL,
SETTING_FALLING_ENTRY_ROOM,
SETTING_MOUSE_LEVEL,
SETTING_MOUSE_ROOM,
SETTING_MOUSE_DELAY,
SETTING_MOUSE_OBJECT,
SETTING_MOUSE_START_X,
SETTING_LOOSE_TILES_LEVEL,
SETTING_LOOSE_TILES_ROOM_1,
SETTING_LOOSE_TILES_ROOM_2,
SETTING_LOOSE_TILES_FIRST_TILE,
SETTING_LOOSE_TILES_LAST_TILE,
SETTING_JAFFAR_VICTORY_LEVEL,
SETTING_JAFFAR_VICTORY_FLASH_TIME,
SETTING_HIDE_LEVEL_NUMBER_FIRST_LEVEL,
SETTING_LEVEL_13_LEVEL_NUMBER,
SETTING_VICTORY_STOPS_TIME_LEVEL,
SETTING_WIN_LEVEL,
SETTING_WIN_ROOM,
SETTING_LOOSE_FLOOR_DELAY,
SETTING_LEVEL_SETTINGS,
SETTING_LEVEL_TYPE,
SETTING_LEVEL_COLOR,
SETTING_GUARD_TYPE,
SETTING_GUARD_HP,
SETTING_CUTSCENE,
SETTING_ENTRY_POSE,
SETTING_SEAMLESS_EXIT,
};
typedef struct setting_type {
int index;
int id;
int previous, next;
byte style;
byte number_type;
void* linked;
void* required;
int min, max; // for 'number'-style settings
char text[64];
char explanation[256];
names_list_type* names_list;
} setting_type;
setting_type general_settings[] = {
{.id = SETTING_SHOW_MENU_ON_PAUSE, .style = SETTING_STYLE_TOGGLE, .linked = &enable_pause_menu,
.text = "Enable pause menu",
.explanation = "Show the in-game menu when you pause the game.\n"
"If disabled, you can still bring up the menu by pressing Backspace."},
{.id = SETTING_ENABLE_INFO_SCREEN, .style = SETTING_STYLE_TOGGLE, .linked = &enable_info_screen,
.text = "Display info screen on launch",
.explanation = "Display the SDLPoP information screen when the game starts."},
{.id = SETTING_ENABLE_SOUND, .style = SETTING_STYLE_TOGGLE, .linked = &is_sound_on,
.text = "Enable sound",
.explanation = "Turn sound on or off."},
{.id = SETTING_ENABLE_MUSIC, .style = SETTING_STYLE_TOGGLE, .linked = &enable_music,
.text = "Enable music",
.explanation = "Turn music on or off."},
{.id = SETTING_ENABLE_DIRECTIONAL_SOUND, .style = SETTING_STYLE_TOGGLE, .linked = &enable_directional_sound,
.text = "Enable directional sound",
.explanation = "Turn directional sound on or off.\n"
"Works best with headphones.\n"
"Distant sounds will also be softer.\n"},
{.id = SETTING_ENABLE_CONTROLLER_RUMBLE, .style = SETTING_STYLE_TOGGLE, .linked = &enable_controller_rumble,
.text = "Enable controller rumble",
.explanation = "If using a controller with a rumble motor, provide haptic feedback when the kid is hurt."},
{.id = SETTING_JOYSTICK_THRESHOLD, .style = SETTING_STYLE_NUMBER, .number_type = SETTING_INT,
.linked = &joystick_threshold, .min = 0, .max = INT16_MAX,
.text = "Joystick threshold",
.explanation = "Joystick 'dead zone' sensitivity threshold."},
{.id = SETTING_JOYSTICK_ONLY_HORIZONTAL, .style = SETTING_STYLE_TOGGLE, .linked = &joystick_only_horizontal,
.text = "Horizontal joystick movement only",
.explanation = "Use joysticks for horizontal movement only, not all-directional. "
"This may make the game easier to control for some controllers."},
{.id = SETTING_RESET_ALL_SETTINGS, .style = SETTING_STYLE_TEXT_ONLY,
.text = "Restore defaults...", .explanation = "Revert all settings to the default state."},
};
NAMES_LIST(scaling_type_setting_names, {"Sharp", "Fuzzy", "Blurry",});
int integer_scaling_possible =
#if SDL_VERSION_ATLEAST(2,0,5) // SDL_RenderSetIntegerScale
1
#else
0
#endif
;
setting_type visuals_settings[] = {
{.id = SETTING_FULLSCREEN, .style = SETTING_STYLE_TOGGLE, .linked = &start_fullscreen,
.text = "Start fullscreen",
.explanation = "Start the game in fullscreen mode.\nYou can also toggle fullscreen by pressing Alt+Enter."},
{.id = SETTING_USE_CORRECT_ASPECT_RATIO, .style = SETTING_STYLE_TOGGLE, .linked = &use_correct_aspect_ratio,
.text = "Use 4:3 aspect ratio",
.explanation = "Render the game in the originally intended 4:3 aspect ratio."
"\nNB. Works best using a high resolution."},
{.id = SETTING_USE_INTEGER_SCALING, .style = SETTING_STYLE_TOGGLE, .linked = &use_integer_scaling,
.required = &integer_scaling_possible,
.text = "Use integer scaling",
.explanation = "Enable pixel perfect scaling. That is, make all pixels the same size by forcing integer scale factors.\n"
"Combining with 4:3 aspect ratio requires at least 1600x1200."
"\nYou need to compile with SDL 2.0.5 or newer to enable this."},
{.id = SETTING_SCALING_TYPE, .style = SETTING_STYLE_NUMBER, .number_type = SETTING_BYTE, .max = 2,
.linked = &scaling_type, .names_list = &scaling_type_setting_names_list,
.text = "Scaling method",
.explanation = "Sharp - Use nearest neighbour resampling.\n"
"Fuzzy - First upscale to double size, then use smooth scaling.\n"
"Blurry - Use smooth scaling."},
{.id = SETTING_ENABLE_FADE, .style = SETTING_STYLE_TOGGLE, .linked = &enable_fade,
.text = "Fading enabled",
.explanation = "Turn fading on or off."},
{.id = SETTING_ENABLE_FLASH, .style = SETTING_STYLE_TOGGLE, .linked = &enable_flash,
.text = "Flashing enabled",
.explanation = "Turn flashing on or off."},
{.id = SETTING_ENABLE_LIGHTING, .style = SETTING_STYLE_TOGGLE, .linked = &enable_lighting,
.text = "Torch shadows enabled",
.explanation = "Darken those parts of the screen that are not near a torch."},
};
setting_type gameplay_settings[] = {
{.id = SETTING_ENABLE_CHEATS, .style = SETTING_STYLE_TOGGLE, .linked = &cheats_enabled,
.text = "Enable cheats",
.explanation = "Turn cheats on or off."/*"\nAlso, display the CHEATS option on the pause menu."*/},
{.id = SETTING_ENABLE_COPYPROT, .style = SETTING_STYLE_TOGGLE, .linked = &enable_copyprot,
.text = "Enable copy protection level",
.explanation = "Enable or disable the potions (copy protection) level."},
{.id = SETTING_ENABLE_QUICKSAVE, .style = SETTING_STYLE_TOGGLE, .linked = &enable_quicksave,
.text = "Enable quicksave",
.explanation = "Enable quicksave/load feature.\nPress F6 to quicksave, F9 to quickload."},
{.id = SETTING_ENABLE_QUICKSAVE_PENALTY, .style = SETTING_STYLE_TOGGLE, .linked = &enable_quicksave_penalty,
.text = "Quicksave time penalty",
.explanation = "Try to let time run out when quickloading (similar to dying).\n"
"Actually, the 'remaining time' will still be restored, "
"but a penalty (up to one minute) will be applied."},
{.id = SETTING_ENABLE_REPLAY, .style = SETTING_STYLE_TOGGLE, .linked = &enable_replay,
.text = "Enable replays",
.explanation = "Enable recording/replay feature.\n"
"Press Ctrl+Tab in-game to start recording.\n"
"To stop, press Ctrl+Tab again."},
{.id = SETTING_USE_FIXES_AND_ENHANCEMENTS, .style = SETTING_STYLE_TOGGLE, .linked = &use_fixes_and_enhancements,
.text = "Enhanced mode (allow bug fixes)",
.explanation = "Turn on game fixes and enhancements.\n"
"Below, you can turn individual fixes/enhancements on or off.\n"
"NOTE: Some fixes disable 'tricks' that depend on game quirks."},
{.id = SETTING_ENABLE_CROUCH_AFTER_CLIMBING, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.enable_crouch_after_climbing, .required = &use_fixes_and_enhancements,
.text = "Enable crouching after climbing",
.explanation = "Adds a way to crouch immediately after climbing up: press down and forward simultaneously. "
"In the original game, this could not be done (pressing down always causes the kid to climb down)."},
{.id = SETTING_ENABLE_FREEZE_TIME_DURING_END_MUSIC, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.enable_freeze_time_during_end_music, .required = &use_fixes_and_enhancements,
.text = "Freeze time during level end music",
.explanation = "Time runs out while the level ending music plays; however, the music can be skipped by disabling sound. "
"This option stops time while the ending music is playing (so there is no need to disable sound)."},
{.id = SETTING_ENABLE_REMEMBER_GUARD_HP, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.enable_remember_guard_hp, .required = &use_fixes_and_enhancements,
.text = "Remember guard hitpoints",
.explanation = "Enable guard hitpoints not resetting to their default (maximum) value when re-entering the room."},
{.id = SETTING_FIX_GATE_SOUNDS, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_gate_sounds, .required = &use_fixes_and_enhancements,
.text = "Fix gate sounds bug",
.explanation = "If a room is linked to itself on the left, the closing sounds of the gates in that room can't be heard."},
{.id = SETTING_TWO_COLL_BUG, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_two_coll_bug, .required = &use_fixes_and_enhancements,
.text = "Fix two collisions bug",
.explanation = "An open gate or chomper may enable the Kid to go through walls. (Trick 7, 37, 62)"},
{.id = SETTING_FIX_INFINITE_DOWN_BUG, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_infinite_down_bug, .required = &use_fixes_and_enhancements,
.text = "Fix infinite down bug",
.explanation = "If a room is linked to itself at the bottom, and the Kid's column has no floors, the game hangs."},
{.id = SETTING_FIX_GATE_DRAWING_BUG, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_gate_drawing_bug, .required = &use_fixes_and_enhancements,
.text = "Fix gate drawing bug",
.explanation = "When a gate is under another gate, the top of the bottom gate is not visible."},
{.id = SETTING_FIX_BIGPILLAR_CLIMB, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_bigpillar_climb, .required = &use_fixes_and_enhancements,
.text = "Fix big pillar climbing bug",
.explanation = "When climbing up to a floor with a big pillar top behind, turned right, Kid sees through floor."},
{.id = SETTING_FIX_JUMP_DISTANCE_AT_EDGE, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_jump_distance_at_edge, .required = &use_fixes_and_enhancements,
.text = "Fix jump distance at edge",
.explanation = "When climbing up two floors, turning around and jumping upward, the kid falls down. "
"This fix makes the workaround of Trick 25 unnecessary."},
{.id = SETTING_FIX_EDGE_DISTANCE_CHECK_WHEN_CLIMBING, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_edge_distance_check_when_climbing, .required = &use_fixes_and_enhancements,
.text = "Fix edge distance check when climbing",
.explanation = "When climbing to a higher floor, the game unnecessarily checks how far away the edge below is. "
"Sometimes you will \"teleport\" some distance when climbing from firm ground."},
{.id = SETTING_FIX_PAINLESS_FALL_ON_GUARD, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_painless_fall_on_guard, .required = &use_fixes_and_enhancements,
.text = "Fix painless fall on guard",
.explanation = "Falling from a great height directly on top of guards does not hurt."},
{.id = SETTING_FIX_WALL_BUMP_TRIGGERS_TILE_BELOW, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_wall_bump_triggers_tile_below, .required = &use_fixes_and_enhancements,
.text = "Fix wall bump triggering tile below",
.explanation = "Bumping against a wall may cause a loose floor below to drop, even though it has not been touched. (Trick 18, 34)"},
{.id = SETTING_FIX_STAND_ON_THIN_AIR, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_stand_on_thin_air, .required = &use_fixes_and_enhancements,
.text = "Fix standing on thin air",
.explanation = "When pressing a loose tile, you can temporarily stand on thin air by standing up from crouching."},
{.id = SETTING_FIX_PRESS_THROUGH_CLOSED_GATES, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_press_through_closed_gates, .required = &use_fixes_and_enhancements,
.text = "Fix pressing through closed gates",
.explanation = "Buttons directly to the right of gates can be pressed even though the gate is closed (Trick 1)"},
{.id = SETTING_FIX_GRAB_FALLING_SPEED, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_grab_falling_speed, .required = &use_fixes_and_enhancements,
.text = "Fix grab falling speed",
.explanation = "By jumping and bumping into a wall, you can sometimes grab a ledge two stories down (which should not be possible)."},
{.id = SETTING_FIX_SKELETON_CHOMPER_BLOOD, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_skeleton_chomper_blood, .required = &use_fixes_and_enhancements,
.text = "Fix skeleton chomper blood",
.explanation = "When chomped, skeletons cause the chomper to become bloody even though skeletons do not have blood."},
{.id = SETTING_FIX_MOVE_AFTER_DRINK, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_move_after_drink, .required = &use_fixes_and_enhancements,
.text = "Fix movement after drinking",
.explanation = "Controls do not get released properly when drinking a potion, sometimes causing unintended movements."},
{.id = SETTING_FIX_LOOSE_LEFT_OF_POTION, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_loose_left_of_potion, .required = &use_fixes_and_enhancements,
.text = "Fix loose floor left of potion",
.explanation = "A drawing bug occurs when a loose tile is placed to the left of a potion (or sword)."},
{.id = SETTING_FIX_GUARD_FOLLOWING_THROUGH_CLOSED_GATES, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_guard_following_through_closed_gates, .required = &use_fixes_and_enhancements,
.text = "Fix guards passing closed gates",
.explanation = "Guards may \"follow\" the kid to the room on the left or right, even though there is a closed gate in between."},
{.id = SETTING_FIX_SAFE_LANDING_ON_SPIKES, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_safe_landing_on_spikes, .required = &use_fixes_and_enhancements,
.text = "Fix safe landing on spikes",
.explanation = "When landing on the edge of a spikes tile, it is considered safe. (Trick 65)"},
{.id = SETTING_FIX_GLIDE_THROUGH_WALL, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_glide_through_wall, .required = &use_fixes_and_enhancements,
.text = "Fix gliding through walls",
.explanation = "The kid may glide through walls after turning around while running (especially when weightless)."},
{.id = SETTING_FIX_DROP_THROUGH_TAPESTRY, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_drop_through_tapestry, .required = &use_fixes_and_enhancements,
.text = "Fix dropping through tapestries",
.explanation = "The kid can drop down through a closed gate, when there is a tapestry (doortop) above the gate."},
{.id = SETTING_FIX_LAND_AGAINST_GATE_OR_TAPESTRY, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_land_against_gate_or_tapestry, .required = &use_fixes_and_enhancements,
.text = "Fix land against gate or tapestry",
.explanation = "When dropping down and landing right in front of a wall, the entire landing animation should normally play. "
"However, when falling against a closed gate or a tapestry(+floor) tile, the animation aborts."},
{.id = SETTING_FIX_UNINTENDED_SWORD_STRIKE, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_unintended_sword_strike, .required = &use_fixes_and_enhancements,
.text = "Fix unintended sword strike",
.explanation = "Sometimes, the kid may automatically strike immediately after drawing the sword. "
"This especially happens when dropping down from a higher floor and then turning towards the opponent."},
{.id = SETTING_FIX_RETREAT_WITHOUT_LEAVING_ROOM, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_retreat_without_leaving_room, .required = &use_fixes_and_enhancements,
.text = "Fix retreat without leaving room",
.explanation = "By repeatedly pressing 'back' in a swordfight, you can retreat out of a room without the room changing. (Trick 35)"},
{.id = SETTING_FIX_RUNNING_JUMP_THROUGH_TAPESTRY, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_running_jump_through_tapestry, .required = &use_fixes_and_enhancements,
.text = "Fix running jumps through tapestries",
.explanation = "The kid can jump through a tapestry with a running jump to the left, if there is a floor above it."},
{.id = SETTING_FIX_PUSH_GUARD_INTO_WALL, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_push_guard_into_wall, .required = &use_fixes_and_enhancements,
.text = "Fix pushing guards into walls",
.explanation = "Guards can be pushed into walls, because the game does not correctly check for walls located behind a guard."},
{.id = SETTING_FIX_JUMP_THROUGH_WALL_ABOVE_GATE, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_jump_through_wall_above_gate, .required = &use_fixes_and_enhancements,
.text = "Fix jump through wall above gate",
.explanation = "By doing a running jump into a wall, you can fall behind a closed gate two floors down. (e.g. skip in Level 7)"},
{.id = SETTING_FIX_CHOMPERS_NOT_STARTING, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_chompers_not_starting, .required = &use_fixes_and_enhancements,
.text = "Fix chompers not starting",
.explanation = "If you grab a ledge that is one or more floors down, the chompers on that row will not start."},
{.id = SETTING_FIX_FEATHER_INTERRUPTED_BY_LEVELDOOR, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_feather_interrupted_by_leveldoor, .required = &use_fixes_and_enhancements,
.text = "Fix leveldoor interrupting feather fall",
.explanation = "As soon as a level door has completely opened, the feather fall effect is interrupted because the sound stops."},
{.id = SETTING_FIX_OFFSCREEN_GUARDS_DISAPPEARING, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_offscreen_guards_disappearing, .required = &use_fixes_and_enhancements,
.text = "Fix offscreen guards disappearing",
.explanation = "Guards will often not reappear in another room if they have been pushed (partly or entirely) offscreen."},
{.id = SETTING_FIX_MOVE_AFTER_SHEATHE, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_move_after_sheathe, .required = &use_fixes_and_enhancements,
.text = "Fix movement after sheathing",
.explanation = "While putting the sword away, if you press forward and down, and then release down, the kid will still duck."},
{.id = SETTING_FIX_HIDDEN_FLOORS_DURING_FLASHING, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_hidden_floors_during_flashing, .required = &use_fixes_and_enhancements,
.text = "Fix hidden floors during flashing",
.explanation = "After uniting with the shadow in level 12, the hidden floors will not appear until after the flashing stops."},
{.id = SETTING_FIX_HANG_ON_TELEPORT, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_hang_on_teleport, .required = &use_fixes_and_enhancements,
.text = "Fix hang on teleport bug",
.explanation = "By jumping towards one of the bottom corners of the room and grabbing a ledge, you can teleport to the room above."},
{.id = SETTING_FIX_EXIT_DOOR, .style = SETTING_STYLE_TOGGLE,
.linked = &fixes_saved.fix_exit_door, .required = &use_fixes_and_enhancements,
.text = "Fix exit doors",
.explanation = "You can enter closed exit doors after you met the shadow or Jaffar died, or after you opened one of multiple exits."},
};
NAMES_LIST(tile_type_setting_names, {
"Empty", "Floor", "Spikes", "Pillar", "Gate", // 0..4
"Stuck button", "Closer button", "Tapestry/floor", "Big pillar: bottom", "Big pillar: top", // 5..9
"Potion", "Loose floor", "Tapestry", "Mirror", "Floor/debris", // 10..14
"Raise button", "Level door: left", "Level door: right", "Chomper", "Torch", // 15..19
"Wall", "Skeleton", "Sword", "Balcony: left", "Balcony: right", // 20..24
"Lattice: pillar", "Lattice: down", "Lattice: small", "Lattice: left", "Lattice: right", // 25..29
"Torch/debris", "Tile 31 (unused)" // 30
});
NAMES_LIST(row_setting_names, {"Top", "Middle", "Bottom"});
KEY_VALUE_LIST(direction_setting_names, {{"Left", dir_FF_left}, {"Right", dir_0_right}});
extern names_list_type never_is_16_list;
setting_type mods_settings[] = {
{.id = SETTING_USE_CUSTOM_OPTIONS, .style = SETTING_STYLE_TOGGLE, .linked = &use_custom_options,
.text = "Use customization options",
.explanation = "Turn customization options on or off.\n(default = OFF)"},
{.id = SETTING_LEVEL_SETTINGS, .style = SETTING_STYLE_TEXT_ONLY, .required = &use_custom_options,
.text = "Customize level...",
.explanation = "Change level-specific options (such as level type, guard type, number of guard hitpoints)."},
{.id = SETTING_START_MINUTES_LEFT, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.start_minutes_left, .number_type = SETTING_SHORT, .min = -1, .max = INT16_MAX,
.text = "Starting minutes left",
.explanation = "Starting minutes left. (default = 60)\n"
"To disable the time limit completely, set this to -1."},
{.id = SETTING_START_TICKS_LEFT, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.start_ticks_left, .number_type = SETTING_WORD, .max = UINT16_MAX,
.text = "Starting seconds left",
.explanation = "Starting number of seconds left in the first minute.\n(default = 59.92)"},
{.id = SETTING_START_HITP, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.start_hitp, .number_type = SETTING_WORD, .max = UINT16_MAX,
.text = "Starting hitpoints",
.explanation = "Starting hitpoints. (default = 3)"},
{.id = SETTING_MAX_HITP_ALLOWED, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.max_hitp_allowed, .number_type = SETTING_WORD, .max = UINT16_MAX,
.text = "Max hitpoints allowed",
.explanation = "Maximum number of hitpoints you can get. (default = 10)"},
{.id = SETTING_SAVING_ALLOWED_FIRST_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.saving_allowed_first_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Saving allowed: first level",
.explanation = "First level where you can save the game. (default = 3)"},
{.id = SETTING_SAVING_ALLOWED_LAST_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.saving_allowed_last_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Saving allowed: last level",
.explanation = "Last level where you can save the game. (default = 13)"},
{.id = SETTING_START_UPSIDE_DOWN, .style = SETTING_STYLE_TOGGLE, .required = &use_custom_options,
.linked = &custom_saved.start_upside_down,
.text = "Start with the screen flipped",
.explanation = "Start the game with the screen flipped upside down, similar to Shift+I (default = OFF)"},
{.id = SETTING_START_IN_BLIND_MODE, .style = SETTING_STYLE_TOGGLE, .required = &use_custom_options,
.linked = &custom_saved.start_in_blind_mode,
.text = "Start in blind mode",
.explanation = "Start in blind mode, similar to Shift+B (default = OFF)"},
{.id = SETTING_COPYPROT_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.copyprot_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Copy protection before level",
.explanation = "The potions level will appear before this level. (default = 2)"},
{.id = SETTING_DRAWN_TILE_TOP_LEVEL_EDGE, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.names_list = &tile_type_setting_names_list,
.linked = &custom_saved.drawn_tile_top_level_edge, .number_type = SETTING_BYTE, .max = 31,
.text = "Drawn tile: top level edge",
.explanation = "Tile drawn at the top of the room if there is no room that way. (default = floor)"},
{.id = SETTING_DRAWN_TILE_LEFT_LEVEL_EDGE, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.names_list = &tile_type_setting_names_list,
.linked = &custom_saved.drawn_tile_left_level_edge, .number_type = SETTING_BYTE, .max = 31,
.text = "Drawn tile: left level edge",
.explanation = "Tile drawn at the left of the room if there is no room that way. (default = wall)"},
{.id = SETTING_LEVEL_EDGE_HIT_TILE, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.names_list = &tile_type_setting_names_list,
.linked = &custom_saved.level_edge_hit_tile, .number_type = SETTING_BYTE, .max = 31,
.text = "Level edge hit tile",
.explanation = "Tile behavior at the top or left of the room if there is no room that way (default = wall)"},
{.id = SETTING_ALLOW_TRIGGERING_ANY_TILE, .style = SETTING_STYLE_TOGGLE, .required = &use_custom_options,
.linked = &custom_saved.allow_triggering_any_tile,
.text = "Allow triggering any tile",
.explanation = "Enable triggering any tile. For example a button could make loose floors fall, or start a stuck chomper. (default = OFF)"},
{.id = SETTING_ENABLE_WDA_IN_PALACE, .style = SETTING_STYLE_TOGGLE, .required = &use_custom_options,
.linked = &custom_saved.enable_wda_in_palace,
.text = "Enable WDA in palace",
.explanation = "Enable the dungeon wall drawing algorithm in the palace."
"\nN.B. Use with a modified VPALACE.DAT that provides dungeon-like wall graphics! (default = OFF)"},
{.id = SETTING_FIRST_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.first_level, .number_type = SETTING_WORD, .max = 15,
.text = "First level",
.explanation = "Level that will be loaded when starting a new game."
"\n(default = 1)"},
{.id = SETTING_SKIP_TITLE, .style = SETTING_STYLE_TOGGLE, .required = &use_custom_options,
.linked = &custom_saved.skip_title,
.text = "Skip title sequence",
.explanation = "Always skip the title sequence: the first level will be loaded immediately."
"\n(default = OFF)"},
{.id = SETTING_SHIFT_L_ALLOWED_UNTIL_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.shift_L_allowed_until_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Shift+L allowed until level",
.explanation = "First level where level skipping with Shift+L is denied in non-cheat mode.\n"
"(default = 4)"},
{.id = SETTING_SHIFT_L_REDUCED_MINUTES, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.shift_L_reduced_minutes, .number_type = SETTING_WORD, .max = UINT16_MAX,
.text = "Minutes left after Shift+L used",
.explanation = "Number of minutes left after Shift+L is used in non-cheat mode.\n"
"(default = 15)"},
{.id = SETTING_SHIFT_L_REDUCED_TICKS, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.shift_L_reduced_ticks, .number_type = SETTING_WORD, .max = UINT16_MAX,
.text = "Seconds left after Shift+L used",
.explanation = "Number of seconds left after Shift+L is used in non-cheat mode.\n(default = 59.92)"},
{.id = SETTING_DEMO_HITP, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.demo_hitp, .number_type = SETTING_WORD, .max = UINT16_MAX,
.text = "Demo level hitpoints",
.explanation = "Hitpoints the kid has on the demo level.\n(default = 4)"},
{.id = SETTING_DEMO_END_ROOM, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.demo_end_room, .number_type = SETTING_WORD, .min = 1, .max = 24,
.text = "Demo level ending room",
.explanation = "Demo level ending room.\n(default = 24)"},
{.id = SETTING_INTRO_MUSIC_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.intro_music_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Level with intro music",
.explanation = "Level where the presentation music is played when the kid crouches down. (default = 1)\n"
"Note: only works if this level is the starting level."},
{.id = SETTING_HAVE_SWORD_FROM_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.have_sword_from_level, .number_type = SETTING_WORD, .min = 1, .max = 16, .names_list = &never_is_16_list,
.text = "Have sword from level",
.explanation = "First level (except the demo level) where kid has the sword.\n(default = 2)\n"},
{.id = SETTING_CHECKPOINT_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.checkpoint_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Checkpoint level",
.explanation = "Level where there is a checkpoint. (default = 3)\n"
"The checkpoint is triggered when leaving room 7 to the left."},
{.id = SETTING_CHECKPOINT_RESPAWN_DIR, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.checkpoint_respawn_dir, .number_type = SETTING_SBYTE, .min = -1, .max = 0, .names_list = &direction_setting_names_list,
.text = "Checkpoint respawn direction",
.explanation = "Respawn direction after triggering the checkpoint.\n(default = left)"},
{.id = SETTING_CHECKPOINT_RESPAWN_ROOM, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.checkpoint_respawn_room, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Checkpoint respawn room",
.explanation = "Room where you respawn after triggering the checkpoint.\n(default = 2)"},
{.id = SETTING_CHECKPOINT_RESPAWN_TILEPOS, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.checkpoint_respawn_tilepos, .number_type = SETTING_BYTE, .max = 29,
.text = "Checkpoint respawn tile position",
.explanation = "Tile position (0 to 29) where you respawn after triggering the checkpoint.\n(default = 6)"},
{.id = SETTING_CHECKPOINT_CLEAR_TILE_ROOM, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.checkpoint_clear_tile_room, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Checkpoint clear tile room",
.explanation = "Room where a tile is cleared after respawning at the checkpoint location.\n(default = 7)"},
{.id = SETTING_CHECKPOINT_CLEAR_TILE_COL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.checkpoint_clear_tile_col, .number_type = SETTING_BYTE, .max = 9,
.text = "Checkpoint clear tile column",
.explanation = "Location (column/row) of the cleared tile after respawning at the checkpoint location.\n(default: column = 4, row = top)"},
{.id = SETTING_CHECKPOINT_CLEAR_TILE_ROW, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.checkpoint_clear_tile_row, .number_type = SETTING_BYTE, .max = 2, .names_list = &row_setting_names_list,
.text = "Checkpoint clear tile row",
.explanation = "Location (column/row) of the cleared tile after respawning at the checkpoint location.\n(default: column = 4, row = top)"},
{.id = SETTING_SKELETON_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Skeleton awakes level",
.explanation = "Level and room where a skeleton can come alive.\n(default: level = 3, room = 1)"},
{.id = SETTING_SKELETON_ROOM, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_room, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Skeleton awakes room",
.explanation = "Level and room where a skeleton can come alive.\n(default: level = 3, room = 1)"},
{.id = SETTING_SKELETON_TRIGGER_COLUMN_1, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_trigger_column_1, .number_type = SETTING_BYTE, .max = 9,
.text = "Skeleton trigger column (1)",
.explanation = "The skeleton will wake up if the kid is on one of these two columns.\n(defaults = 2,3)"},
{.id = SETTING_SKELETON_TRIGGER_COLUMN_2, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_trigger_column_2, .number_type = SETTING_BYTE, .max = 9,
.text = "Skeleton trigger column (2)",
.explanation = "The skeleton will wake up if the kid is on one of these two columns.\n(defaults = 2,3)"},
{.id = SETTING_SKELETON_COLUMN, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_column, .number_type = SETTING_BYTE, .max = 9,
.text = "Skeleton tile column",
.explanation = "Location (column/row) of the skeleton tile that will awaken.\n(default: column = 5, row = middle)"},
{.id = SETTING_SKELETON_ROW, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_row, .number_type = SETTING_BYTE, .max = 2, .names_list = &row_setting_names_list,
.text = "Skeleton tile row",
.explanation = "Location (column/row) of the skeleton tile that will awaken.\n(default: column = 5, row = middle)"},
{.id = SETTING_SKELETON_REQUIRE_OPEN_LEVEL_DOOR, .style = SETTING_STYLE_TOGGLE, .required = &use_custom_options,
.linked = &custom_saved.skeleton_require_open_level_door,
.text = "Skeleton requires level door",
.explanation = "Whether the level door must first be opened before the skeleton awakes.\n(default = true)"},
{.id = SETTING_SKELETON_SKILL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_skill, .number_type = SETTING_BYTE, .max = 15,
.text = "Skeleton skill",
.explanation = "Skill of the awoken skeleton.\n(default = 2)"},
{.id = SETTING_SKELETON_REAPPEAR_ROOM, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_reappear_room, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Skeleton reappear room",
.explanation = "If the skeleton falls into this room, it will reappear there.\n(default = 3)"},
{.id = SETTING_SKELETON_REAPPEAR_X, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_reappear_x, .number_type = SETTING_BYTE, .max = 255,
.text = "Skeleton reappear X coordinate",
.explanation = "Horizontal coordinate where the skeleton reappears.\n(default = 133)\n"
"(58 = left edge of the room, 198 = right edge)"},
{.id = SETTING_SKELETON_REAPPEAR_ROW, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_reappear_row, .number_type = SETTING_BYTE, .max = 2, .names_list = &row_setting_names_list,
.text = "Skeleton reappear row",
.explanation = "Row on which the skeleton reappears.\n(default = middle)"},
{.id = SETTING_SKELETON_REAPPEAR_DIR, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.skeleton_reappear_dir, .number_type = SETTING_SBYTE, .min = -1, .max = 0, .names_list = &direction_setting_names_list,
.text = "Skeleton reappear direction",
.explanation = "Direction the skeleton is facing when it reappears.\n(default = right)"},
{.id = SETTING_MIRROR_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.mirror_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Mirror level",
.explanation = "Level and room where the mirror appears.\n(default: level = 4, room = 4)"},
{.id = SETTING_MIRROR_ROOM, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.mirror_room, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Mirror room",
.explanation = "Level and room where the mirror appears.\n(default: level = 4, room = 4)"},
{.id = SETTING_MIRROR_COLUMN, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.mirror_column, .number_type = SETTING_BYTE, .max = 9,
.text = "Mirror column",
.explanation = "Location (column/row) of the tile where the mirror appears.\n(default: column = 4, row = top)"},
{.id = SETTING_MIRROR_ROW, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.mirror_row, .number_type = SETTING_BYTE, .max = 2, .names_list = &row_setting_names_list,
.text = "Mirror row",
.explanation = "Location (column/row) of the tile where the mirror appears.\n(default: column = 4, row = top)"},
{.id = SETTING_MIRROR_TILE, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.mirror_tile, .number_type = SETTING_BYTE, .max = 31, .names_list = &tile_type_setting_names_list,
.text = "Mirror tile",
.explanation = "Tile type that appears when the mirror should appear.\n(default = mirror)"},
{.id = SETTING_SHOW_MIRROR_IMAGE, .style = SETTING_STYLE_TOGGLE, .required = &use_custom_options,
.linked = &custom_saved.show_mirror_image, .number_type = SETTING_BYTE,
.text = "Show mirror image",
.explanation = "Show the kid's mirror image in the mirror.\n(default = true)"},
{.id = SETTING_FALLING_EXIT_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.falling_exit_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Falling exit level",
.explanation = "Level where the kid can progress to the next level by falling off a specific room.\n(default = 6)"},
{.id = SETTING_FALLING_EXIT_ROOM, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.falling_exit_room, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Falling exit room",
.explanation = "Room where the kid can progress to the next level by falling down.\n(default = 1)"},
{.id = SETTING_FALLING_ENTRY_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.falling_entry_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Falling entry level",
.explanation = "If the kid starts in this level in this room, the starting room will not be shown,\n"
"but the room below instead, to allow for a falling entry. (default: level = 7, room = 17)"},
{.id = SETTING_FALLING_ENTRY_ROOM, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.falling_entry_room, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Falling entry room",
.explanation = "If the kid starts in this level in this room, the starting room will not be shown,\n"
"but the room below instead, to allow for a falling entry. (default: level = 7, room = 17)"},
{.id = SETTING_MOUSE_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.mouse_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Mouse level",
.explanation = "Level where the mouse appears.\n(default = 8)"},
{.id = SETTING_MOUSE_ROOM, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.mouse_room, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Mouse room",
.explanation = "Room where the mouse appears.\n(default = 16)"},
{.id = SETTING_MOUSE_DELAY, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.mouse_delay, .number_type = SETTING_WORD, .max = UINT16_MAX,
.text = "Mouse delay",
.explanation = "Number of seconds to wait before the mouse appears.\n(default = 12.5)"},
{.id = SETTING_MOUSE_OBJECT, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.mouse_object, .number_type = SETTING_BYTE, .max = 255,
.text = "Mouse object",
.explanation = "Mouse object type. (default = 24)\n"
"Be careful: a value not 24 will change the mouse for the kid."},
{.id = SETTING_MOUSE_START_X, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.mouse_start_x, .number_type = SETTING_BYTE, .max = 255,
.text = "Mouse start X coordinate",
.explanation = "Horizontal starting coordinate of the mouse.\n(default = 200)"},
{.id = SETTING_LOOSE_TILES_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.loose_tiles_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Loose tiles level",
.explanation = "Level where loose floor tiles will fall down.\n(default = 13)"},
{.id = SETTING_LOOSE_TILES_ROOM_1, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.loose_tiles_room_1, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Loose tiles room (1)",
.explanation = "Rooms where visible loose floor tiles will fall down.\n(default = 23, 16)"},
{.id = SETTING_LOOSE_TILES_ROOM_2, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.loose_tiles_room_2, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Loose tiles room (2)",
.explanation = "Rooms where visible loose floor tiles will fall down.\n(default = 23, 16)"},
{.id = SETTING_LOOSE_TILES_FIRST_TILE, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.loose_tiles_first_tile, .number_type = SETTING_BYTE, .max = 29,
.text = "Loose tiles first tile",
.explanation = "Range of loose floor tile positions that will be pressed.\n(default = 22 to 27)"},
{.id = SETTING_LOOSE_TILES_LAST_TILE, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.loose_tiles_last_tile, .number_type = SETTING_BYTE, .max = 29,
.text = "Loose tiles last tile",
.explanation = "Range of loose floor tile positions that will be pressed.\n(default = 22 to 27)"},
{.id = SETTING_JAFFAR_VICTORY_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.jaffar_victory_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Jaffar victory level",
.explanation = "Killing the guard in this level causes the screen to flash, and event 0 to be triggered upon leaving the room.\n(default = 13)"},
{.id = SETTING_JAFFAR_VICTORY_FLASH_TIME, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.jaffar_victory_flash_time, .number_type = SETTING_BYTE, .max = UINT16_MAX,
.text = "Jaffar victory flash time",
.explanation = "How long the screen will flash after killing Jaffar.\n(default = 18)"},
{.id = SETTING_HIDE_LEVEL_NUMBER_FIRST_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.hide_level_number_from_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Hide level number from level",
.explanation = "First level where the level number will not be displayed.\n(default = 14)"},
{.id = SETTING_LEVEL_13_LEVEL_NUMBER, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.level_13_level_number, .number_type = SETTING_BYTE, .max = UINT16_MAX,
.text = "Level 13 displayed level number",
.explanation = "Level number displayed on level 13.\n(default = 12)"},
{.id = SETTING_VICTORY_STOPS_TIME_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.victory_stops_time_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Victory stops time level",
.explanation = "Level where Jaffar's death stops time.\n(default = 13)"},
{.id = SETTING_WIN_LEVEL, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.win_level, .number_type = SETTING_WORD, .max = 16, .names_list = &never_is_16_list,
.text = "Level where you can win",
.explanation = "Level and room where you can win the game.\n(default: level = 14, room = 5)"},
{.id = SETTING_WIN_ROOM, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.win_room, .number_type = SETTING_BYTE, .min = 1, .max = 24,
.text = "Room where you can win",
.explanation = "Level and room where you can win the game.\n(default: level = 14, room = 5)"},
{.id = SETTING_LOOSE_FLOOR_DELAY, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = &custom_saved.loose_floor_delay, .number_type = SETTING_BYTE, .min = 0, .max = 127,
.text = "Loose floor delay",
.explanation = "Number of seconds to wait before a loose floor falls.\n(default = 0.92)"},
};
NAMES_LIST(level_type_setting_names, { "Dungeon", "Palace", });
KEY_VALUE_LIST(guard_type_setting_names, {{"None", -1}, {"Normal", 0}, {"Fat", 1}, {"Skeleton", 2}, {"Vizier", 3}, {"Shadow", 4}});
NAMES_LIST(entry_pose_setting_names, {"Turning", "Falling", "Running"});
KEY_VALUE_LIST(off_setting_name, {{"Off", -1}}); // used for the seamless exit setting
setting_type level_settings[] = {
{.id = SETTING_LEVEL_SETTINGS, .style = SETTING_STYLE_TEXT_ONLY, .required = &use_custom_options,
.text = "Customize another level...",
.explanation = "Select another level to customize."},
{.id = SETTING_LEVEL_TYPE, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.names_list = &level_type_setting_names_list,
.linked = NULL /* depends on which level */, .number_type = SETTING_BYTE, .max = 1,
.text = "Level type",
.explanation = "Which environment is used in this level.\n"
"(either dungeon or palace)"},
{.id = SETTING_LEVEL_COLOR, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = NULL, .number_type = SETTING_WORD, .max = 4,
.text = "Level color palette",
.explanation = "0: colors from VDUNGEON.DAT/VPALACE.DAT\n>0: colors from PRINCE.DAT.\n"
"You need a PRINCE.DAT from PoP 1.3 or 1.4 for this."},
{.id = SETTING_GUARD_TYPE, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.names_list = &guard_type_setting_names_list,
.linked = NULL, .number_type = SETTING_SHORT, .min = -1, .max = 4,
.text = "Guard type",
.explanation = "Guard type used in this level (normal, fat, skeleton, vizier, or shadow)."},
{.id = SETTING_GUARD_HP, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = NULL, .number_type = SETTING_BYTE, .max = UINT8_MAX,
.text = "Guard hitpoints",
.explanation = "Number of hitpoints guards have in this level."},
{.id = SETTING_CUTSCENE, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = NULL, .number_type = SETTING_BYTE, .max = 15,
.text = "Cutscene before level",
.explanation = "Cutscene that plays between the previous level and this level.\n"
"0: none, 2 or 6: standing, 4: lying down, 8: mouse leaves,\n"
"9: mouse returns, 12: standing or turn around"},
{.id = SETTING_ENTRY_POSE, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = NULL, .number_type = SETTING_BYTE, .max = 2, .names_list = &entry_pose_setting_names_list,
.text = "Entry pose",
.explanation = "The pose the kid has when the level starts.\n"},
{.id = SETTING_SEAMLESS_EXIT, .style = SETTING_STYLE_NUMBER, .required = &use_custom_options,
.linked = NULL, .number_type = SETTING_SBYTE, .min = -1, .max = 24, .names_list = &off_setting_name_list,
.text = "Seamless exit",
.explanation = "Entering this room moves the kid to the next level.\n"
"Set to -1 to disable."},
};
typedef struct settings_area_type {
setting_type* settings;
int setting_count;
} settings_area_type;
settings_area_type general_settings_area = { .settings = general_settings, .setting_count = COUNT(general_settings)};
settings_area_type gameplay_settings_area = { .settings = gameplay_settings, .setting_count = COUNT(gameplay_settings)};
settings_area_type visuals_settings_area = { .settings = visuals_settings, .setting_count = COUNT(visuals_settings)};
settings_area_type mods_settings_area = { .settings = mods_settings, .setting_count = COUNT(mods_settings)};
settings_area_type level_settings_area = { .settings = level_settings, .setting_count = COUNT(level_settings)};
settings_area_type* get_settings_area(int menu_item_id) {
switch(menu_item_id) {
default:
return NULL;
case SETTINGS_MENU_GENERAL:
return &general_settings_area;
case SETTINGS_MENU_GAMEPLAY:
return &gameplay_settings_area;
case SETTINGS_MENU_VISUALS:
return &visuals_settings_area;
case SETTINGS_MENU_MODS:
return &mods_settings_area;
case SETTINGS_MENU_LEVEL_CUSTOMIZATION:
return &level_settings_area;
}
}
void init_pause_menu_items(pause_menu_item_type* first_item, int item_count) {
if (item_count > 0) {
for (int i = 0; i < item_count; ++i) {
pause_menu_item_type* item = first_item + i;
item->previous = (first_item + MAX(0, i-1));
item->next = (first_item + MIN(item_count-1, i+1));
}
pause_menu_item_type* last_item = first_item + (item_count-1);
first_item->previous = last_item;
last_item->next = first_item;
}
}
void init_settings_list(setting_type* first_setting, int setting_count) {
if (setting_count > 0) {
for (int i = 0; i < setting_count; ++i) {
setting_type* item = first_setting + i;
item->index = i;
item->previous = (first_setting + MAX(0, i-1))->id;
item->next = (first_setting + MIN(setting_count-1, i+1))->id;
}
// setting_type* last_item = first_setting + (setting_count-1);
// first_setting->previous = last_item->id;
// last_item->next = first_setting->id;
}
}
void init_menu() {
load_arrowhead_images();
init_pause_menu_items(pause_menu_items, COUNT(pause_menu_items));
init_pause_menu_items(settings_menu_items, COUNT(settings_menu_items));
init_settings_list(general_settings, COUNT(general_settings));
init_settings_list(visuals_settings, COUNT(visuals_settings));
init_settings_list(gameplay_settings, COUNT(gameplay_settings));
init_settings_list(mods_settings, COUNT(mods_settings));
init_settings_list(level_settings, COUNT(level_settings));
}
bool is_mouse_over_rect(rect_type* rect) {
return (mouse_x >= rect->left && mouse_x < rect->right && mouse_y >= rect->top && mouse_y < rect->bottom);
}
// Maps the cursor position into a coordinate between (0,0) and (320,200) and sets mouse_x, mouse_y and mouse_moved.
void read_mouse_state() {
float scale_x, scale_y;
SDL_RenderGetScale(renderer_, &scale_x, &scale_y);
int logical_width, logical_height;
SDL_RenderGetLogicalSize(renderer_, &logical_width, &logical_height);
int logical_scale_x = logical_width / 320; // These may be higher than 1, if 4:3 aspect ratio scaling is enabled.
int logical_scale_y = logical_height / 200;
scale_x *= logical_scale_x;
scale_y *= logical_scale_y;
if (!(scale_x > 0 && scale_y > 0 && logical_scale_x > 0 && logical_scale_y > 0)) return;
SDL_Rect viewport;
SDL_RenderGetViewport(renderer_, &viewport); // Get the width/height of the 'black bars' around the rendering area.
viewport.x /= logical_scale_x;
viewport.y /= logical_scale_y;
int last_mouse_x = mouse_x;
int last_mouse_y = mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
mouse_x = (int) ((float)mouse_x/scale_x - viewport.x + 0.5f);
mouse_y = (int) ((float)mouse_y/scale_y - viewport.y + 0.5f);
mouse_moved = (last_mouse_x != mouse_x || last_mouse_y != mouse_y);
}
rect_type explanation_rect = {170, 20, 200, 300};
int highlighted_setting_id = SETTING_ENABLE_INFO_SCREEN;