1 /*
2 SDLPoP, a port/conversion of the DOS game Prince of Persia.
3 Copyright (C) 2013-2021 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 <https://www.gnu.org/licenses/>.
17
18 The authors of this program may be contacted at https://forum.princed.org
19 */
20
21 #include "common.h"
22 #include <setjmp.h>
23 #include <math.h>
24
25 // data:461E
26 dat_type * dathandle;
27
28 // data:4C08
29 word need_redraw_because_flipped;
30
31 void fix_sound_priorities();
32
33 // seg000:0000
pop_main()34 void far pop_main() {
35 if (check_param("--version") || check_param("-v")) {
36 printf ("SDLPoP v%s\n", SDLPOP_VERSION);
37 exit(0);
38 }
39
40 if (check_param("--help") || check_param("-h") || check_param("-?")) {
41 printf ("See doc/Readme.txt\n");
42 exit(0);
43 }
44
45 const char* temp = check_param("seed=");
46 if (temp != NULL) {
47 random_seed = atoi(temp+5);
48 seed_was_init = 1;
49 }
50
51 // debug only: check that the sequence table deobfuscation did not mess things up
52 #ifdef CHECK_SEQTABLE_MATCHES_ORIGINAL
53 check_seqtable_matches_original();
54 #endif
55
56 #ifdef FIX_SOUND_PRIORITIES
57 fix_sound_priorities();
58 #endif
59
60 load_global_options();
61 check_mod_param();
62 #ifdef USE_MENU
63 load_ingame_settings();
64 #endif
65 if (check_param("mute")) is_sound_on = 0;
66 turn_sound_on_off((is_sound_on != 0) * 15); // Turn off sound/music if those options were set.
67
68 #ifdef USE_REPLAY
69 if (g_argc > 1) {
70 char *filename = g_argv[1]; // file dragged on top of executable or double clicked
71 char *e = strrchr(filename, '.');
72 if (e != NULL && strcasecmp(e, ".P1R") == 0) { // valid replay filename passed as first arg
73 start_with_replay_file(filename);
74 }
75 }
76
77 temp = check_param("validate");
78 if (temp != NULL) {
79 is_validate_mode = 1;
80 start_with_replay_file(temp);
81 }
82 #endif
83
84 load_mod_options();
85
86 // CusPop option
87 is_blind_mode = custom->start_in_blind_mode;
88 // Fix bug: with start_in_blind_mode enabled, moving objects are not displayed until blind mode is toggled off+on??
89 need_drects = 1;
90
91 apply_seqtbl_patches();
92
93 char sprintf_temp[100];
94 int i;
95
96 dathandle = open_dat("PRINCE.DAT", 0);
97
98 /*video_mode =*/ parse_grmode();
99
100 init_timer(BASE_FPS);
101 parse_cmdline_sound();
102
103 set_hc_pal();
104
105 current_target_surface = rect_sthg(onscreen_surface_, &screen_rect);
106 show_loading();
107 set_joy_mode();
108 cheats_enabled = check_param("megahit") != NULL;
109 #ifdef USE_DEBUG_CHEATS
110 debug_cheats_enabled = check_param("debug") != NULL;
111 if (debug_cheats_enabled) cheats_enabled = 1; // param 'megahit' not necessary if 'debug' is used
112 #endif
113 draw_mode = check_param("draw") != NULL && cheats_enabled;
114 demo_mode = check_param("demo") != NULL;
115
116 init_copyprot_dialog();
117 #ifdef USE_REPLAY
118 init_record_replay();
119 #endif
120
121 if (cheats_enabled
122 #ifdef USE_REPLAY
123 || recording
124 #endif
125 ) {
126 for (i = 15; i >= 0; --i) {
127 snprintf(sprintf_temp, sizeof(sprintf_temp), "%d", i);
128 if (check_param(sprintf_temp)) {
129 start_level = i;
130 break;
131 }
132 }
133 }
134
135 play_demo_level = (check_param("playdemo") != NULL);
136
137 #ifdef USE_SCREENSHOT
138 init_screenshot();
139 #endif
140
141 #ifdef USE_MENU
142 init_menu();
143 #endif
144
145 init_game_main();
146 }
147
148 byte* level_var_palettes;
149
150 // seg000:024F
init_game_main()151 void __pascal far init_game_main() {
152 doorlink1_ad = /*&*/level.doorlinks1;
153 doorlink2_ad = /*&*/level.doorlinks2;
154 prandom(1);
155 if (graphics_mode == gmMcgaVga) {
156 // Guard palettes
157 guard_palettes = (byte*) load_from_opendats_alloc(10, "bin", NULL, NULL);
158 // (blood, hurt flash) #E00030 = red
159 set_pal(12, 0x38, 0x00, 0x0C, 1);
160 // (palace wall pattern) #C09850 = light brown
161 set_pal( 6, 0x30, 0x26, 0x14, 0);
162
163 // Level color variations (1.3)
164 level_var_palettes = load_from_opendats_alloc(20, "bin", NULL, NULL);
165 }
166 // PRINCE.DAT: sword
167 chtab_addrs[id_chtab_0_sword] = load_sprites_from_file(700, 1<<2, 1);
168 // PRINCE.DAT: flame, sword on floor, potion
169 chtab_addrs[id_chtab_1_flameswordpotion] = load_sprites_from_file(150, 1<<3, 1);
170 close_dat(dathandle);
171 #ifdef USE_LIGHTING
172 init_lighting();
173 #endif
174 load_all_sounds();
175
176 hof_read();
177 show_splash(); // added
178 start_game();
179 }
180
181
182 // data:02C2
183 word first_start = 1;
184 // data:4C38
185 jmp_buf setjmp_buf;
186 // seg000:0358
start_game()187 void __pascal far start_game() {
188 #ifdef USE_COPYPROT
189 word which_entry;
190 word pos;
191 word entry_used[40];
192 byte letts_used[26];
193 #endif
194 // Prevent filling of stack.
195 // start_game is called from many places to restart the game, for example:
196 // process_key, play_frame, draw_game_frame, play_level, control_kid, end_sequence, expired
197 if (first_start) {
198 first_start = 0;
199 setjmp(/*&*/setjmp_buf);
200 } else {
201 draw_rect(&screen_rect, 0);
202 show_quotes();
203 clear_screen_and_sounds();
204 longjmp(/*&*/setjmp_buf,-1);
205 }
206 release_title_images(); // added
207 free_optsnd_chtab(); // added
208 #ifdef USE_COPYPROT
209 copyprot_plac = prandom(13);
210 memset(&entry_used, 0, sizeof(entry_used));
211 memset(&letts_used, 0, sizeof(letts_used));
212 for (pos = 0; pos < 14; ++pos) {
213 do {
214 if (pos == copyprot_plac) {
215 which_entry = copyprot_idx = prandom(39);
216 } else {
217 which_entry = prandom(39);
218 }
219 } while (entry_used[which_entry] || letts_used[copyprot_letter[which_entry]-'A']);
220 cplevel_entr[pos] = which_entry;
221 entry_used[which_entry] = 1;
222 letts_used[copyprot_letter[which_entry]-'A'] = 1;
223 }
224 #endif
225 if (custom->skip_title) { // CusPop option: skip the title sequence (level loads instantly)
226 int level_number = (start_level >= 0) ? start_level : custom->first_level;
227 init_game(level_number);
228 return;
229 }
230
231 if (start_level < 0) {
232 show_title();
233 } else {
234 init_game(start_level);
235 }
236 }
237
238 #ifdef USE_QUICKSAVE
239 // All these functions return true on success, false otherwise.
240
241 FILE* quick_fp;
242
process_save(void * data,size_t data_size)243 int process_save(void* data, size_t data_size) {
244 return fwrite(data, data_size, 1, quick_fp) == 1;
245 }
246
process_load(void * data,size_t data_size)247 int process_load(void* data, size_t data_size) {
248 return fread(data, data_size, 1, quick_fp) == 1;
249 }
250
251 typedef int process_func_type(void* data, size_t data_size);
252
quick_process(process_func_type process_func)253 int quick_process(process_func_type process_func) {
254 int ok = 1;
255 #define process(x) ok = ok && process_func(&(x), sizeof(x))
256 // level
257 #ifdef USE_DEBUG_CHEATS
258 // Don't load the level if the user holds either Shift key while pressing F9.
259 if (debug_cheats_enabled && (key_states[SDL_SCANCODE_LSHIFT] || key_states[SDL_SCANCODE_RSHIFT])) {
260 fseek(quick_fp, sizeof(level), SEEK_CUR);
261 } else
262 #endif
263 {
264 process(level);
265 }
266 process(checkpoint);
267 process(upside_down);
268 process(drawn_room);
269 process(current_level);
270 process(next_level);
271 process(mobs_count);
272 process(mobs);
273 process(trobs_count);
274 process(trobs);
275 process(leveldoor_open);
276 //process(exit_room_timer);
277 // kid
278 process(Kid);
279 process(hitp_curr);
280 process(hitp_max);
281 process(hitp_beg_lev);
282 process(grab_timer);
283 process(holding_sword);
284 process(united_with_shadow);
285 process(have_sword);
286 /*process(ctrl1_forward);
287 process(ctrl1_backward);
288 process(ctrl1_up);
289 process(ctrl1_down);
290 process(ctrl1_shift2);*/
291 process(kid_sword_strike);
292 process(pickup_obj_type);
293 process(offguard);
294 // guard
295 process(Guard);
296 process(Char);
297 process(Opp);
298 process(guardhp_curr);
299 process(guardhp_max);
300 process(demo_index);
301 process(demo_time);
302 process(curr_guard_color);
303 process(guard_notice_timer);
304 process(guard_skill);
305 process(shadow_initialized);
306 process(guard_refrac);
307 process(justblocked);
308 process(droppedout);
309 // collision
310 process(curr_row_coll_room);
311 process(curr_row_coll_flags);
312 process(below_row_coll_room);
313 process(below_row_coll_flags);
314 process(above_row_coll_room);
315 process(above_row_coll_flags);
316 process(prev_collision_row);
317 // flash
318 process(flash_color);
319 process(flash_time);
320 // sounds
321 process(need_level1_music);
322 process(is_screaming);
323 process(is_feather_fall);
324 process(last_loose_sound);
325 //process(next_sound);
326 //process(current_sound);
327 // random
328 process(random_seed);
329 // remaining time
330 process(rem_min);
331 process(rem_tick);
332 // saved controls
333 process(control_x);
334 process(control_y);
335 process(control_shift);
336 process(control_forward);
337 process(control_backward);
338 process(control_up);
339 process(control_down);
340 process(control_shift2);
341 process(ctrl1_forward);
342 process(ctrl1_backward);
343 process(ctrl1_up);
344 process(ctrl1_down);
345 process(ctrl1_shift2);
346 // replay recording state
347 #ifdef USE_REPLAY
348 process(curr_tick);
349 #endif
350 #ifdef USE_COLORED_TORCHES
351 process(torch_colors);
352 #endif
353 #undef process
354 return ok;
355 }
356
357 const char* quick_file = "QUICKSAVE.SAV";
358 const char quick_version[] = "V1.16b4 ";
359 char quick_control[] = "........";
360
get_quick_path(char * custom_path_buffer,size_t max_len)361 const char* get_quick_path(char* custom_path_buffer, size_t max_len) {
362 if (!use_custom_levelset) {
363 return quick_file;
364 }
365 // if playing a custom levelset, try to use the mod folder
366 snprintf_check(custom_path_buffer, max_len, "%s/%s", mod_data_path, quick_file /*QUICKSAVE.SAV*/ );
367 return custom_path_buffer;
368 }
369
quick_save()370 int quick_save() {
371 int ok = 0;
372 char custom_quick_path[POP_MAX_PATH];
373 const char* path = get_quick_path(custom_quick_path, sizeof(custom_quick_path));
374 quick_fp = fopen(path, "wb");
375 if (quick_fp != NULL) {
376 process_save((void*) quick_version, COUNT(quick_version));
377 ok = quick_process(process_save);
378 fclose(quick_fp);
379 quick_fp = NULL;
380 }
381 return ok;
382 }
383
restore_room_after_quick_load()384 void restore_room_after_quick_load() {
385 int temp1 = curr_guard_color;
386 int temp2 = next_level;
387 reset_level_unused_fields(false);
388 load_lev_spr(current_level);
389 curr_guard_color = temp1;
390 next_level = temp2;
391
392 // feather fall can only get restored if the fix enabled
393 if (!fixes->fix_quicksave_during_feather && is_feather_fall > 0) {
394 is_feather_fall = 0;
395 stop_sounds();
396 }
397
398 //need_full_redraw = 1;
399 different_room = 1;
400 // Show the room where the prince is, even if the player moved the view away from it (with the H,J,U,N keys).
401 next_room = drawn_room = Kid.room;
402 load_room_links();
403 //draw_level_first();
404 //gen_palace_wall_colors();
405 is_guard_notice = 0; // prevent guard turning around immediately
406 draw_game_frame(); // for falling
407 //redraw_screen(1); // for room_L
408
409 hitp_delta = guardhp_delta = 1; // force HP redraw
410 // Don't draw guard HP if a previously viewed room (with the H,J,U,N keys) had a guard but the current room doesn't have one.
411 if (Guard.room != drawn_room) {
412 // Like in clear_char().
413 Guard.direction = dir_56_none;
414 guardhp_curr = 0;
415 }
416
417 draw_hp();
418 loadkid_and_opp();
419 // Get rid of "press button" message if kid was dead before quickload.
420 text_time_total = text_time_remaining = 0;
421 //next_sound = current_sound = -1;
422 exit_room_timer = 0;
423 }
424
quick_load()425 int quick_load() {
426 int ok = 0;
427 char custom_quick_path[POP_MAX_PATH];
428 const char* path = get_quick_path(custom_quick_path, sizeof(custom_quick_path));
429 quick_fp = fopen(path, "rb");
430 if (quick_fp != NULL) {
431 // check quicksave version is compatible
432 process_load(quick_control, COUNT(quick_control));
433 if (strcmp(quick_control, quick_version) != 0) {
434 fclose(quick_fp);
435 quick_fp = NULL;
436 return 0;
437 }
438
439 stop_sounds();
440 draw_rect(&screen_rect, 0);
441 update_screen();
442 delay_ticks(5); // briefly display a black screen as a visual cue
443
444 short old_rem_min = rem_min;
445 word old_rem_tick = rem_tick;
446
447 ok = quick_process(process_load);
448 fclose(quick_fp);
449 quick_fp = NULL;
450
451 restore_room_after_quick_load();
452 update_screen();
453
454 #ifdef USE_QUICKLOAD_PENALTY
455 // Subtract one minute from the remaining time (if it is above 5 minutes)
456 if (enable_quicksave_penalty &&
457 // don't apply the penalty after time has already stopped!
458 (current_level < /*13*/ custom->victory_stops_time_level || (current_level == /*13*/ custom->victory_stops_time_level && leveldoor_open < 2))
459 ) {
460 int ticks_elapsed = 720 * (rem_min - old_rem_min) + (rem_tick - old_rem_tick);
461 // don't restore time at all if the elapsed time is between 0 and 1 minutes
462 if (ticks_elapsed > 0 && ticks_elapsed < 720) {
463 rem_min = old_rem_min;
464 rem_tick = old_rem_tick;
465 }
466 else {
467 if (rem_min == 6) rem_tick = 719; // crop to "5 minutes" exactly, if hitting the threshold in <1 minute
468 if (rem_min > 5 /*be lenient, not much time left*/ || rem_min < 0 /*time runs 'forward' if < 0*/) {
469 --rem_min;
470 }
471 }
472
473 }
474 #endif
475 }
476 return ok;
477 }
478
check_quick_op()479 void check_quick_op() {
480 if (!enable_quicksave) return;
481 if (need_quick_save) {
482 if ((!is_feather_fall || fixes->fix_quicksave_during_feather) && quick_save()) {
483 display_text_bottom("QUICKSAVE");
484 } else {
485 display_text_bottom("NO QUICKSAVE");
486 }
487 need_quick_save = 0;
488 text_time_total = 24;
489 text_time_remaining = 24;
490 }
491 if (need_quick_load) {
492 /*
493 #ifdef USE_REPLAY
494 if (recording) {
495 stop_recording(); // quickloading would mess up the replay!
496 }
497 #endif
498 */
499 if (quick_load()) {
500 display_text_bottom("QUICKLOAD");
501 } else {
502 display_text_bottom("NO QUICKLOAD");
503 }
504 need_quick_load = 0;
505 text_time_total = 24;
506 text_time_remaining = 24;
507 }
508 }
509
510
511 #endif // USE_QUICKSAVE
512
temp_shift_release_callback(Uint32 interval,void * param)513 Uint32 temp_shift_release_callback(Uint32 interval, void *param) {
514 const Uint8* state = SDL_GetKeyboardState(NULL);
515 if (state[SDL_SCANCODE_LSHIFT]) key_states[SDL_SCANCODE_LSHIFT] = 1;
516 if (state[SDL_SCANCODE_RSHIFT]) key_states[SDL_SCANCODE_RSHIFT] = 1;
517 return 0; // causes the timer to be removed
518 }
519
520 // seg000:04CD
process_key()521 int __pascal far process_key() {
522 char sprintf_temp[80];
523 int key;
524 const char* answer_text = NULL;
525 word need_show_text;
526 need_show_text = 0;
527 key = key_test_quit();
528
529 #ifdef USE_MENU
530 if (is_paused && is_menu_shown) {
531 key = key_test_paused_menu(key);
532 if (key == 0) return 0;
533 }
534 #endif
535
536 if (start_level < 0) {
537 if (key || control_shift) {
538 #ifdef USE_QUICKSAVE
539 if (key == SDL_SCANCODE_F9) need_quick_load = 1;
540 #endif
541 #ifdef USE_REPLAY
542 if (key == SDL_SCANCODE_TAB || need_start_replay) {
543 start_replay();
544 }
545 else if (key == (SDL_SCANCODE_TAB | WITH_CTRL)) {
546 start_level = custom->first_level;
547 start_recording();
548 } else
549 #endif
550 if (key == (SDL_SCANCODE_L | WITH_CTRL)) { // Ctrl+L
551 if (!load_game()) return 0;
552 } else {
553 start_level = custom->first_level; // 1
554 }
555 draw_rect(&screen_rect, 0);
556 #ifdef USE_FADE
557 if (is_global_fading) {
558 fade_palette_buffer->proc_restore_free(fade_palette_buffer);
559 is_global_fading = 0;
560 }
561 #endif
562 start_game();
563 }
564 }
565 // If the Kid died, Enter or Shift will restart the level.
566 if (rem_min != 0 && Kid.alive > 6 && (control_shift || key == SDL_SCANCODE_RETURN)) {
567 key = SDL_SCANCODE_A | WITH_CTRL; // Ctrl+A
568 }
569 #ifdef USE_REPLAY
570 if (recording) key_press_while_recording(&key);
571 else if (replaying) key_press_while_replaying(&key);
572 #endif
573 if (key == 0) return 0;
574 if (is_keyboard_mode) clear_kbd_buf();
575
576 switch(key) {
577 case SDL_SCANCODE_ESCAPE: // Esc
578 case SDL_SCANCODE_ESCAPE | WITH_SHIFT: // allow pause while grabbing
579 is_paused = 1;
580 #ifdef USE_MENU
581 if (enable_pause_menu && !is_cutscene && !is_ending_sequence) {
582 is_menu_shown = 1;
583 }
584 break;
585 case SDL_SCANCODE_BACKSPACE:
586 if (!is_cutscene && !is_ending_sequence) {
587 is_paused = 1;
588 is_menu_shown = 1;
589 }
590 #endif
591 break;
592 case SDL_SCANCODE_SPACE: // Space
593 is_show_time = 1;
594 break;
595 case SDL_SCANCODE_A | WITH_CTRL: // Ctrl+A
596 if (current_level != 15) {
597 stop_sounds();
598 is_restart_level = 1;
599 }
600 break;
601 case SDL_SCANCODE_G | WITH_CTRL: // Ctrl+G
602 // CusPoP: first and last level where saving is allowed
603 // if (current_level > 2 && current_level < 14) { // original
604 if (current_level >= custom->saving_allowed_first_level && current_level <= custom->saving_allowed_last_level) {
605 save_game();
606 }
607 break;
608 case SDL_SCANCODE_J | WITH_CTRL: // Ctrl+J
609 if ((sound_flags & sfDigi) && sound_mode == smTandy) {
610 answer_text = "JOYSTICK UNAVAILABLE";
611 } else {
612 if (set_joy_mode()) {
613 answer_text = "JOYSTICK MODE";
614 } else {
615 answer_text = "JOYSTICK NOT FOUND";
616 }
617 }
618 need_show_text = 1;
619 break;
620 case SDL_SCANCODE_K | WITH_CTRL: // Ctrl+K
621 answer_text = "KEYBOARD MODE";
622 is_joyst_mode = 0;
623 is_keyboard_mode = 1;
624 need_show_text = 1;
625 break;
626 case SDL_SCANCODE_R | WITH_CTRL: // Ctrl+R
627 start_level = -1;
628 #ifdef USE_MENU
629 if (is_menu_shown) menu_was_closed(); // Do necessary cleanup.
630 #endif
631 start_game();
632 break;
633 case SDL_SCANCODE_S | WITH_CTRL: // Ctrl+S
634 turn_sound_on_off((!is_sound_on) * 15);
635 answer_text = "SOUND OFF";
636 if (is_sound_on) {
637 answer_text = "SOUND ON";
638 }
639 //
640 need_show_text = 1;
641 break;
642 case SDL_SCANCODE_V | WITH_CTRL: // Ctrl+V
643 //answer_text = "PRINCE OF PERSIA V1.0";
644 snprintf(sprintf_temp, sizeof(sprintf_temp), "SDLPoP v%s\n", SDLPOP_VERSION);
645 answer_text = sprintf_temp;
646 need_show_text = 1;
647 break;
648 case SDL_SCANCODE_C | WITH_CTRL: // Ctrl+C
649 {
650 SDL_version verc, verl;
651 SDL_VERSION (&verc);
652 SDL_GetVersion (&verl);
653 snprintf (sprintf_temp, sizeof (sprintf_temp),
654 "SDL COMP v%u.%u.%u LINK v%u.%u.%u",
655 verc.major, verc.minor, verc.patch,
656 verl.major, verl.minor, verl.patch);
657 answer_text = sprintf_temp;
658 need_show_text = 1;
659 }
660 break;
661 case SDL_SCANCODE_L | WITH_SHIFT: // Shift+L
662 if (current_level < custom->shift_L_allowed_until_level /* 4 */ || cheats_enabled) {
663 // if Shift is not released within the delay, the cutscene is skipped
664 Uint32 delay = 250;
665 key_states[SDL_SCANCODE_LSHIFT] = 0;
666 key_states[SDL_SCANCODE_RSHIFT] = 0;
667 SDL_TimerID timer;
668 timer = SDL_AddTimer(delay, temp_shift_release_callback, NULL);
669 if (timer == 0) {
670 sdlperror("process_key: SDL_AddTimer");
671 quit(1);
672 }
673 if (current_level == 14) {
674 next_level = 1;
675 } else {
676 if (current_level == 15 && cheats_enabled) {
677 #ifdef USE_COPYPROT
678 if (enable_copyprot) {
679 next_level = custom->copyprot_level;
680 custom->copyprot_level = -1;
681 }
682 #endif
683 } else {
684 next_level = current_level + 1;
685 if (!cheats_enabled && rem_min > custom->shift_L_reduced_minutes /* 15 */) {
686 rem_min = custom->shift_L_reduced_minutes; // 15
687 rem_tick = custom->shift_L_reduced_ticks; // 719
688 }
689 }
690 }
691 stop_sounds();
692 }
693 break;
694 #ifdef USE_QUICKSAVE
695 case SDL_SCANCODE_F6:
696 case SDL_SCANCODE_F6 | WITH_SHIFT:
697 if (Kid.alive < 0) need_quick_save = 1;
698 break;
699 case SDL_SCANCODE_F9:
700 case SDL_SCANCODE_F9 | WITH_SHIFT:
701 need_quick_load = 1;
702 break;
703 #ifdef USE_REPLAY
704 case SDL_SCANCODE_TAB | WITH_CTRL:
705 case SDL_SCANCODE_TAB | WITH_CTRL | WITH_SHIFT:
706 if (recording) { // finished recording
707 stop_recording();
708 }
709 else { // should start recording
710 start_recording();
711 }
712 break;
713 #endif // USE_REPLAY
714 #endif // USE_QUICKSAVE
715 }
716 if (cheats_enabled) {
717 switch (key) {
718 case SDL_SCANCODE_C: // c
719 snprintf(sprintf_temp, sizeof(sprintf_temp), "S%d L%d R%d A%d B%d", drawn_room, room_L, room_R, room_A, room_B);
720 answer_text = /*&*/sprintf_temp;
721 need_show_text = 1;
722 break;
723 case SDL_SCANCODE_C | WITH_SHIFT: // Shift+C
724 snprintf(sprintf_temp, sizeof(sprintf_temp), "AL%d AR%d BL%d BR%d", room_AL, room_AR, room_BL, room_BR);
725 answer_text = /*&*/sprintf_temp;
726 need_show_text = 1;
727 break;
728 case SDL_SCANCODE_MINUS:
729 case SDL_SCANCODE_KP_MINUS: // '-' --> subtract time cheat
730 if (rem_min > 1) --rem_min;
731
732 #ifdef ALLOW_INFINITE_TIME
733 else if (rem_min < -1) ++rem_min; // if negative/infinite, time runs 'forward'
734 else if (rem_min == -1) rem_tick = 720; // resets the timer to 00:00:00
735 #endif
736
737 text_time_total = 0;
738 text_time_remaining = 0;
739 is_show_time = 1;
740 break;
741 case SDL_SCANCODE_EQUALS | WITH_SHIFT: // '+'
742 case SDL_SCANCODE_KP_PLUS: // '+' --> add time cheat
743
744 #ifdef ALLOW_INFINITE_TIME
745 if (rem_min < 0) { // if negative/infinite, time runs 'forward'
746 if (rem_min > INT16_MIN) --rem_min;
747 }
748 else ++rem_min;
749 #else
750 ++rem_min;
751 #endif
752
753 text_time_total = 0;
754 text_time_remaining = 0;
755 is_show_time = 1;
756 break;
757 case SDL_SCANCODE_R: // R --> revive kid cheat
758 if (Kid.alive > 0) {
759 resurrect_time = 20;
760 Kid.alive = -1;
761 erase_bottom_text(1);
762 }
763 break;
764 case SDL_SCANCODE_K: // K --> kill guard cheat
765 if (Guard.charid != charid_4_skeleton) {
766 guardhp_delta = -guardhp_curr;
767 Guard.alive = 0;
768 }
769 break;
770 case SDL_SCANCODE_I | WITH_SHIFT: // Shift+I --> invert cheat
771 toggle_upside();
772 break;
773 case SDL_SCANCODE_W | WITH_SHIFT: // Shift+W --> feather fall cheat
774 feather_fall();
775 break;
776 case SDL_SCANCODE_H: // H --> view room to the left
777 draw_guard_hp(0, 10);
778 next_room = room_L;
779 break;
780 case SDL_SCANCODE_J: // J --> view room to the right
781 draw_guard_hp(0, 10);
782 next_room = room_R;
783 break;
784 case SDL_SCANCODE_U: // U --> view room above
785 draw_guard_hp(0, 10);
786 next_room = room_A;
787 break;
788 case SDL_SCANCODE_N: // N --> view room below
789 draw_guard_hp(0, 10);
790 next_room = room_B;
791 break;
792 case SDL_SCANCODE_B | WITH_CTRL: // Ctrl+B --> Go back to the room where the prince is. (Undo H,J,U,N.)
793 draw_guard_hp(0, 10);
794 next_room = Kid.room;
795 break;
796 case SDL_SCANCODE_B | WITH_SHIFT: // Shift+B
797 is_blind_mode = !is_blind_mode;
798 if (is_blind_mode) {
799 draw_rect(&rect_top, 0);
800 } else {
801 need_full_redraw = 1;
802 }
803 break;
804 case SDL_SCANCODE_S | WITH_SHIFT: // Shift+S
805 if (hitp_curr != hitp_max) {
806 play_sound(sound_33_small_potion); // small potion (cheat)
807 hitp_delta = 1;
808 flash_color = 4; // red
809 flash_time = 2;
810 }
811 break;
812 case SDL_SCANCODE_T | WITH_SHIFT: // Shift+T
813 play_sound(sound_30_big_potion); // big potion (cheat)
814 flash_color = 4; // red
815 flash_time = 4;
816 add_life();
817 break;
818 #ifdef USE_DEBUG_CHEATS
819 case SDL_SCANCODE_T:
820 is_timer_displayed = 1 - is_timer_displayed; // toggle
821 break;
822 case SDL_SCANCODE_F:
823 if (fixes->fix_quicksave_during_feather) {
824 is_feather_timer_displayed = 1 - is_feather_timer_displayed; // toggle
825 } else {
826 is_feather_timer_displayed = 0;
827 }
828 break;
829 #endif
830 }
831 }
832
833 if (need_show_text) {
834 display_text_bottom(answer_text);
835 text_time_total = 24;
836 text_time_remaining = 24;
837 }
838 return 1;
839 }
840
841 // seg000:08EB
play_frame()842 void __pascal far play_frame() {
843 // play feather fall music if there is more than 1 second of feather fall left
844 if (fixes->fix_quicksave_during_feather && is_feather_fall >= 10 && !check_sound_playing()) {
845 play_sound(sound_39_low_weight);
846 }
847 do_mobs();
848 process_trobs();
849 check_skel();
850 check_can_guard_see_kid();
851 // if level is restarted, return immediately
852 if (play_kid_frame()) return;
853 play_guard_frame();
854 if (0 == resurrect_time) {
855 check_sword_hurting();
856 check_sword_hurt();
857 }
858 check_sword_vs_sword();
859 do_delta_hp();
860 exit_room();
861 check_the_end();
862 check_guard_fallout();
863 if (current_level == 0) {
864 // Special event: level 0 running exit
865 if (Kid.room == /*24*/ custom->demo_end_room) {
866 draw_rect(&screen_rect, 0);
867 start_level = -1;
868 need_quotes = 1;
869 start_game();
870 }
871 } else if(current_level == /*6*/ custom->falling_exit_level) {
872 // Special event: level 6 falling exit
873 if (roomleave_result == -2) {
874 Kid.y = -1;
875 stop_sounds();
876 ++next_level;
877 }
878 } else if(/*current_level == 12*/ custom->tbl_seamless_exit[current_level] >= 0) {
879 // Special event: level 12 running exit
880 if (Kid.room == /*23*/ custom->tbl_seamless_exit[current_level]) {
881 ++next_level;
882 // Sounds must be stopped, because play_level_2() checks next_level only if there are no sounds playing.
883 stop_sounds();
884 seamless = 1;
885 }
886 }
887 show_time();
888 // expiring doesn't count on Jaffar/princess level
889 if (current_level < 13 && rem_min == 0) {
890 expired();
891 }
892 }
893
894 // seg000:09B6
draw_game_frame()895 void __pascal far draw_game_frame() {
896 short var_2;
897 if (need_full_redraw) {
898 redraw_screen(0);
899 need_full_redraw = 0;
900 } else {
901 if (different_room) {
902 drawn_room = next_room;
903 if (custom->tbl_level_type[current_level]) {
904 gen_palace_wall_colors();
905 }
906 redraw_screen(1);
907 } else {
908 if (need_redraw_because_flipped) {
909 need_redraw_because_flipped = 0;
910 redraw_screen(0);
911 } else {
912 memset_near(&table_counts, 0, sizeof(table_counts));
913 draw_moving();
914 draw_tables();
915 if (is_blind_mode) {
916 draw_rect(&rect_top, 0);
917 }
918 if (upside_down) {
919 flip_screen(offscreen_surface);
920 }
921 while (drects_count--) {
922 copy_screen_rect(&drects[drects_count]);
923 }
924 if (upside_down) {
925 flip_screen(offscreen_surface);
926 }
927 drects_count = 0;
928 }
929 }
930 }
931
932 play_next_sound();
933 // Note: texts are identified by their total time!
934 if (text_time_remaining == 1) {
935 // If the text's is about to expire:
936 if (text_time_total == 36 || text_time_total == 288) {
937 // 36: died on demo/potions level
938 // 288: press button to continue
939 // In this case, restart the game.
940 start_level = -1;
941 need_quotes = 1;
942
943 #ifdef USE_REPLAY
944 if (recording) stop_recording();
945 if (replaying) end_replay();
946 #endif
947
948 start_game();
949 } else {
950 // Otherwise, just clear it.
951 erase_bottom_text(1);
952 }
953 } else {
954 if (text_time_remaining != 0 && text_time_total != 1188) {
955 // 1188: potions level (page/line/word) -- this one does not disappear
956 --text_time_remaining;
957 if (text_time_total == 288 && text_time_remaining < 72) {
958 // 288: press button to continue
959 // Blink the message:
960 var_2 = text_time_remaining % 12;
961 if (var_2 > 3) {
962 erase_bottom_text(0);
963 } else {
964 if (var_2 == 3) {
965 display_text_bottom("Press Button to Continue");
966 play_sound_from_buffer(sound_pointers[sound_38_blink]); // press button blink
967 }
968 }
969 }
970 }
971 }
972 }
973
974 // seg000:0B12
anim_tile_modif()975 void __pascal far anim_tile_modif() {
976 word tilepos;
977 for (tilepos = 0; tilepos < 30; ++tilepos) {
978 switch (get_curr_tile(tilepos)) {
979 case tiles_10_potion:
980 start_anim_potion(drawn_room, tilepos);
981 break;
982 case tiles_19_torch:
983 case tiles_30_torch_with_debris:
984 start_anim_torch(drawn_room, tilepos);
985 break;
986 case tiles_22_sword:
987 start_anim_sword(drawn_room, tilepos);
988 break;
989 }
990 }
991
992 // Animate torches in the rightmost column of the left-side room as well, because they are visible in the current room.
993 for (int row = 0; row <= 2; row++) {
994 switch (get_tile(room_L, 9, row)) {
995 case tiles_19_torch:
996 case tiles_30_torch_with_debris:
997 start_anim_torch(room_L, row * 10 + 9);
998 break;
999 }
1000 }
1001 }
1002
1003 // seg000:0B72
load_sounds(int first,int last)1004 void __pascal far load_sounds(int first,int last) {
1005 dat_type* ibm_dat = NULL;
1006 dat_type* digi1_dat = NULL;
1007 // dat_type* digi2_dat = NULL;
1008 dat_type* digi3_dat = NULL;
1009 dat_type* midi_dat = NULL;
1010 short current;
1011 ibm_dat = open_dat("IBM_SND1.DAT", 0);
1012 if (sound_flags & sfDigi) {
1013 digi1_dat = open_dat("DIGISND1.DAT", 0);
1014 // digi2_dat = open_dat("DIGISND2.DAT", 0);
1015 digi3_dat = open_dat("DIGISND3.DAT", 0);
1016 }
1017 if (sound_flags & sfMidi) {
1018 midi_dat = open_dat("MIDISND1.DAT", 0);
1019 }
1020
1021 load_sound_names();
1022
1023 for (current = first; current <= last; ++current) {
1024 if (sound_pointers[current] != NULL) continue;
1025 /*if (demo_mode) {
1026 sound_pointers[current] = decompress_sound((sound_buffer_type*) load_from_opendats_alloc(current + 10000));
1027 } else*/ {
1028 //sound_pointers[current] = (sound_buffer_type*) load_from_opendats_alloc(current + 10000, "bin", NULL, NULL);
1029 //printf("overwriting sound_pointers[%d] = %p\n", current, sound_pointers[current]);
1030
1031
1032 sound_pointers[current] = load_sound(current);
1033 }
1034 }
1035 if (midi_dat) close_dat(midi_dat);
1036 if (digi1_dat) close_dat(digi1_dat);
1037 // if (digi2_dat) close_dat(digi2_dat);
1038 if (digi3_dat) close_dat(digi3_dat);
1039 close_dat(ibm_dat);
1040 }
1041
1042 // seg000:0C5E
load_opt_sounds(int first,int last)1043 void __pascal far load_opt_sounds(int first,int last) {
1044 // stub
1045 dat_type* ibm_dat = NULL;
1046 dat_type* digi_dat = NULL;
1047 dat_type* midi_dat = NULL;
1048 short current;
1049 ibm_dat = open_dat("IBM_SND2.DAT", 0);
1050 if (sound_flags & sfDigi) {
1051 digi_dat = open_dat("DIGISND2.DAT", 0);
1052 }
1053 if (sound_flags & sfMidi) {
1054 midi_dat = open_dat("MIDISND2.DAT", 0);
1055 }
1056 for (current = first; current <= last; ++current) {
1057 //We don't free sounds, so load only once.
1058 if (sound_pointers[current] != NULL) continue;
1059 /*if (demo_mode) {
1060 sound_pointers[current] = decompress_sound((sound_buffer_type*) load_from_opendats_alloc(current + 10000));
1061 } else*/ {
1062 //sound_pointers[current] = (sound_buffer_type*) load_from_opendats_alloc(current + 10000, "bin", NULL, NULL);
1063 //printf("overwriting sound_pointers[%d] = %p\n", current, sound_pointers[current]);
1064 sound_pointers[current] = load_sound(current);
1065 }
1066 }
1067 if (midi_dat) close_dat(midi_dat);
1068 if (digi_dat) close_dat(digi_dat);
1069 close_dat(ibm_dat);
1070 }
1071
1072 // data:03BA
1073 const char*const tbl_guard_dat[] = {"GUARD.DAT", "FAT.DAT", "SKEL.DAT", "VIZIER.DAT", "SHADOW.DAT"};
1074 // data:03C4
1075 const char*const tbl_envir_gr[] = {"", "C", "C", "E", "E", "V"};
1076 // data:03D0
1077 const char*const tbl_envir_ki[] = {"DUNGEON", "PALACE"};
1078 // seg000:0D20
load_lev_spr(int level)1079 void __pascal far load_lev_spr(int level) {
1080 dat_type* dathandle;
1081 short guardtype;
1082 char filename[20];
1083 dathandle = NULL;
1084 current_level = next_level = level;
1085 draw_rect(&screen_rect, 0);
1086 free_optsnd_chtab();
1087 snprintf(filename, sizeof(filename), "%s%s.DAT",
1088 tbl_envir_gr[graphics_mode],
1089 tbl_envir_ki[custom->tbl_level_type[current_level]]
1090 );
1091 load_chtab_from_file(id_chtab_6_environment, 200, filename, 1<<5);
1092 load_more_opt_graf(filename);
1093 guardtype = custom->tbl_guard_type[current_level];
1094 if (guardtype != -1) {
1095 if (guardtype == 0) {
1096 dathandle = open_dat(custom->tbl_level_type[current_level] ? "GUARD1.DAT" : "GUARD2.DAT", 0);
1097 }
1098 load_chtab_from_file(id_chtab_5_guard, 750, tbl_guard_dat[guardtype], 1<<8);
1099 if (dathandle) {
1100 close_dat(dathandle);
1101 }
1102 }
1103 curr_guard_color = 0;
1104 load_chtab_from_file(id_chtab_7_environmentwall, 360, filename, 1<<6);
1105
1106 // Level colors (1.3)
1107 if (graphics_mode == gmMcgaVga && level_var_palettes != NULL) {
1108 int level_color = custom->tbl_level_color[current_level];
1109 if (level_color != 0) {
1110 byte* env_pal = level_var_palettes + 0x30*(level_color-1);
1111 byte* wall_pal = env_pal + 0x30 * custom->tbl_level_type[current_level];
1112 set_pal_arr(0x50, 0x10, (rgb_type*)env_pal, 1);
1113 set_pal_arr(0x60, 0x10, (rgb_type*)wall_pal, 1);
1114 set_chtab_palette(chtab_addrs[id_chtab_6_environment], env_pal, 0x10);
1115 set_chtab_palette(chtab_addrs[id_chtab_7_environmentwall], wall_pal, 0x10);
1116 }
1117 }
1118
1119 /*if (comp_skeleton[current_level])*/ {
1120 load_opt_sounds(44, 44); // skel alive
1121 }
1122 /*if (comp_mirror[current_level])*/ {
1123 load_opt_sounds(45, 45); // mirror
1124 }
1125 /*if (comp_chomper[current_level])*/ {
1126 load_opt_sounds(46, 47); // something chopped, chomper
1127 }
1128 /*if (comp_spike[current_level])*/ {
1129 load_opt_sounds(48, 49); // something spiked, spikes
1130 }
1131 }
1132
1133 // seg000:0E6C
load_level()1134 void __pascal far load_level() {
1135 dat_type* dathandle;
1136 dathandle = open_dat("LEVELS.DAT", 0);
1137 load_from_opendats_to_area(current_level + 2000, &level, sizeof(level), "bin");
1138 close_dat(dathandle);
1139
1140 alter_mods_allrm();
1141 reset_level_unused_fields(true); // added
1142 }
1143
reset_level_unused_fields(bool loading_clean_level)1144 void reset_level_unused_fields(bool loading_clean_level) {
1145 // Entirely unused fields in the level format: reset to zero for now
1146 // They can be repurposed to add new stuff to the level format in the future
1147 memset(level.roomxs, 0, sizeof(level.roomxs));
1148 memset(level.roomys, 0, sizeof(level.roomys));
1149 memset(level.fill_1, 0, sizeof(level.fill_1));
1150 memset(level.fill_2, 0, sizeof(level.fill_2));
1151 memset(level.fill_3, 0, sizeof(level.fill_3));
1152
1153 // level.used_rooms is 25 on some levels. Limit it to the actual number of rooms.
1154 if (level.used_rooms > 24) level.used_rooms = 24;
1155
1156 // For these fields, only use the bits that are actually used, and set the rest to zero.
1157 // Good for repurposing the unused bits in the future.
1158 int i;
1159 for (i = 0; i < level.used_rooms; ++i) {
1160 //level.guards_dir[i] &= 0x01; // 1 bit in use
1161 level.guards_skill[i] &= 0x0F; // 4 bits in use
1162 }
1163
1164 // In savestates, additional information may be stored (e.g. remembered guard hp) - should not reset this then!
1165 if (loading_clean_level) {
1166 for (i = 0; i < level.used_rooms; ++i) {
1167 level.guards_color[i] &= 0x0F; // 4 bits in use (other 4 bits repurposed as remembered guard hp)
1168 }
1169 }
1170
1171 }
1172
1173 // seg000:0EA8
1174 // returns 1 if level is restarted, 0 otherwise
play_kid_frame()1175 int __pascal far play_kid_frame() {
1176 loadkid_and_opp();
1177 load_fram_det_col();
1178 check_killed_shadow();
1179 play_kid();
1180 if (upside_down && Char.alive >= 0) {
1181 upside_down = 0;
1182 need_redraw_because_flipped = 1;
1183 }
1184 if (is_restart_level) {
1185 return 1;
1186 }
1187 if (Char.room != 0) {
1188 play_seq();
1189 fall_accel();
1190 fall_speed();
1191 load_frame_to_obj();
1192 load_fram_det_col();
1193 set_char_collision();
1194 bump_into_opponent();
1195 check_collisions();
1196 check_bumped();
1197 check_gate_push();
1198 check_action();
1199 check_press();
1200 check_spike_below();
1201 if (resurrect_time == 0) {
1202 check_spiked();
1203 check_chomped_kid();
1204 }
1205 check_knock();
1206 }
1207 savekid();
1208 return 0;
1209 }
1210
1211 // seg000:0F48
play_guard_frame()1212 void __pascal far play_guard_frame() {
1213 if (Guard.direction != dir_56_none) {
1214 loadshad_and_opp();
1215 load_fram_det_col();
1216 check_killed_shadow();
1217 play_guard();
1218 if (Char.room == drawn_room) {
1219 play_seq();
1220 if (Char.x >= 44 && Char.x < 211) {
1221 fall_accel();
1222 fall_speed();
1223 load_frame_to_obj();
1224 load_fram_det_col();
1225 set_char_collision();
1226 check_guard_bumped();
1227 check_action();
1228 check_press();
1229 check_spike_below();
1230 check_spiked();
1231 check_chomped_guard();
1232 }
1233 }
1234 saveshad();
1235 }
1236 }
1237
1238 // seg000:0FBD
check_the_end()1239 void __pascal far check_the_end() {
1240 if (next_room != 0 && next_room != drawn_room) {
1241 drawn_room = next_room;
1242 load_room_links();
1243 if (current_level == /*14*/ custom->win_level && drawn_room == /*5*/ custom->win_room) {
1244 #ifdef USE_REPLAY
1245 if (recording) stop_recording();
1246 if (replaying) end_replay();
1247 #endif
1248 // Special event: end of game
1249 end_sequence();
1250 }
1251 different_room = 1;
1252 loadkid();
1253 anim_tile_modif();
1254 start_chompers();
1255 check_fall_flo();
1256 check_shadow();
1257 }
1258 }
1259
1260 // seg000:1009
check_fall_flo()1261 void __pascal far check_fall_flo() {
1262 // Special event: falling floors
1263 if (current_level == /*13*/ custom->loose_tiles_level &&
1264 (drawn_room == /*23*/ custom->loose_tiles_room_1 || drawn_room == /*16*/ custom->loose_tiles_room_2)
1265 ) {
1266 get_room_address(curr_room = room_A);
1267 for (curr_tilepos = /*22*/ custom->loose_tiles_first_tile;
1268 curr_tilepos <= /*27*/ custom->loose_tiles_last_tile; ++curr_tilepos) {
1269 make_loose_fall(-(prandom(0xFF) & 0x0F));
1270 }
1271 }
1272 }
1273
get_joystick_state(int raw_x,int raw_y,int axis_state[2])1274 void get_joystick_state(int raw_x, int raw_y, int axis_state[2]) {
1275
1276 #define DEGREES_TO_RADIANS (M_PI/180.0)
1277
1278 // check if the X/Y position is within the 'dead zone' of the joystick
1279 int dist_squared = raw_x*raw_x + raw_y*raw_y;
1280 // FIXED: Left jump (top-left) didn't work on some gamepads.
1281 // On some gamepads, raw_x = raw_y = -32768 in the top-left corner.
1282 // In this case, dist_squared is calculated as -32768 * -32768 + -32768 * -32768 = 2147483648.
1283 // But dist_squared is a 32-bit signed integer, which cannot store that number, so it overflows to -2147483648.
1284 // Therefore, dist_squared < joystick_threshold*joystick_threshold becomes true, and the game thinks the stick is centered.
1285 // To fix this, we cast both sides of the comparison to an unsigned 32-bit type.
1286 if ((dword)dist_squared < (dword)(joystick_threshold*joystick_threshold)) {
1287 axis_state[0] = 0;
1288 axis_state[1] = 0;
1289 } else {
1290 double angle = atan2(raw_y, raw_x); // angle of the joystick: 0 = right, >0 = downward, <0 = upward
1291 //printf("Joystick angle is %f degrees\n", angle/DEGREES_TO_RADIANS);
1292
1293 if (fabs(angle) < (60*DEGREES_TO_RADIANS)) // 120 degree range facing right
1294 axis_state[0] = 1;
1295
1296 else if (fabs(angle) > (120*DEGREES_TO_RADIANS)) // 120 degree range facing left
1297 axis_state[0] = -1;
1298
1299 else {
1300 // joystick is neutral horizontally, so the control should be released
1301 // however: prevent stop running if the Kid was already running / trying to do a running-jump
1302 // (this tweak makes it a bit easier to do (multiple) running jumps)
1303 if (!(angle < 0 /*facing upward*/ && Kid.action == actions_1_run_jump)) {
1304 axis_state[0] = 0;
1305 }
1306 }
1307
1308 if (angle < (-30*DEGREES_TO_RADIANS) && angle > (-150*DEGREES_TO_RADIANS)) // 120 degree range facing up
1309 axis_state[1] = -1;
1310
1311 // down slightly less sensitive than up (prevent annoyance when your thumb slips down a bit by accident)
1312 // (not sure if this adjustment is really necessary)
1313 else if (angle > (35*DEGREES_TO_RADIANS) && angle < (145*DEGREES_TO_RADIANS)) // 110 degree range facing down
1314 axis_state[1] = 1;
1315
1316 else {
1317 // joystick is neutral vertically, so the control should be released
1318 // however: should prevent unintended standing up when attempting to crouch-hop
1319 if (!((Kid.frame >= frame_108_fall_land_2 && Kid.frame <= frame_112_stand_up_from_crouch_3)
1320 && angle > 0 /*facing downward*/))
1321 {
1322 axis_state[1] = 0;
1323 }
1324 }
1325 }
1326 }
1327
get_joystick_state_hor_only(int raw_x,int axis_state[2])1328 void get_joystick_state_hor_only(int raw_x, int axis_state[2]) {
1329 if (raw_x > joystick_threshold) {
1330 axis_state[0] = 1;
1331 } else if (raw_x < -joystick_threshold) {
1332 axis_state[0] = -1;
1333 } else axis_state[0] = 0;
1334
1335 // disregard all vertical input from the joystick controls (only use Y and A buttons or D-pad for up/down)
1336 axis_state[1] = 0;
1337 }
1338
1339 // seg000:1051
read_joyst_control()1340 void __pascal far read_joyst_control() {
1341
1342 if (joystick_only_horizontal) {
1343 get_joystick_state_hor_only(joy_axis[SDL_CONTROLLER_AXIS_LEFTX], joy_left_stick_states);
1344 get_joystick_state_hor_only(joy_axis[SDL_CONTROLLER_AXIS_RIGHTX], joy_right_stick_states);
1345 } else {
1346 get_joystick_state(joy_axis[SDL_CONTROLLER_AXIS_LEFTX], joy_axis[SDL_CONTROLLER_AXIS_LEFTY], joy_left_stick_states);
1347 get_joystick_state(joy_axis[SDL_CONTROLLER_AXIS_RIGHTX], joy_axis[SDL_CONTROLLER_AXIS_RIGHTY], joy_right_stick_states);
1348 }
1349
1350 if (joy_left_stick_states[0] == -1 || joy_right_stick_states[0] == -1 || joy_hat_states[0] == -1)
1351 control_x = -1;
1352
1353 if (joy_left_stick_states[0] == 1 || joy_right_stick_states[0] == 1 || joy_hat_states[0] == 1)
1354 control_x = 1;
1355
1356 if (joy_left_stick_states[1] == -1 || joy_right_stick_states[1] == -1 || joy_hat_states[1] == -1 || joy_AY_buttons_state == -1)
1357 control_y = -1;
1358
1359 if (joy_left_stick_states[1] == 1 || joy_right_stick_states[1] == 1 || joy_hat_states[1] == 1 || joy_AY_buttons_state == 1)
1360 control_y = 1;
1361
1362 if (joy_X_button_state == 1 ||
1363 joy_axis[SDL_CONTROLLER_AXIS_TRIGGERLEFT] > 8000 ||
1364 joy_axis[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] > 8000)
1365 {
1366 control_shift = -1;
1367 }
1368
1369 }
1370
1371 // seg000:10EA
draw_kid_hp(short curr_hp,short max_hp)1372 void __pascal far draw_kid_hp(short curr_hp,short max_hp) {
1373 short drawn_hp_index;
1374 for (drawn_hp_index = curr_hp; drawn_hp_index < max_hp; ++drawn_hp_index) {
1375 // empty HP
1376 method_6_blit_img_to_scr(get_image(id_chtab_2_kid, 217), drawn_hp_index * 7, 194, blitters_0_no_transp);
1377 }
1378 for (drawn_hp_index = 0; drawn_hp_index < curr_hp; ++drawn_hp_index) {
1379 // full HP
1380 method_6_blit_img_to_scr(get_image(id_chtab_2_kid, 216), drawn_hp_index * 7, 194, blitters_0_no_transp);
1381 }
1382 }
1383
1384 // seg000:1159
draw_guard_hp(short curr_hp,short max_hp)1385 void __pascal far draw_guard_hp(short curr_hp,short max_hp) {
1386 short drawn_hp_index;
1387 short guard_charid;
1388 if (chtab_addrs[id_chtab_5_guard] == NULL) return;
1389 guard_charid = Guard.charid;
1390 if (guard_charid != charid_4_skeleton &&
1391 guard_charid != charid_24_mouse &&
1392 // shadow has HP only on level 12
1393 (guard_charid != charid_1_shadow || current_level == 12)
1394 ) {
1395 for (drawn_hp_index = curr_hp; drawn_hp_index < max_hp; ++drawn_hp_index) {
1396 method_6_blit_img_to_scr(chtab_addrs[id_chtab_5_guard]->images[0], 314 - drawn_hp_index * 7, 194, blitters_9_black);
1397 }
1398 for (drawn_hp_index = 0; drawn_hp_index < curr_hp; ++drawn_hp_index) {
1399 method_6_blit_img_to_scr(chtab_addrs[id_chtab_5_guard]->images[0], 314 - drawn_hp_index * 7, 194, blitters_0_no_transp);
1400 }
1401 }
1402 }
1403
1404 // seg000:11EC
add_life()1405 void __pascal far add_life() {
1406 short hpmax = hitp_max;
1407 ++hpmax;
1408 // CusPop: set maximum number of hitpoints (max_hitp_allowed, default = 10)
1409 // if (hpmax > 10) hpmax = 10; // original
1410 if (hpmax > custom->max_hitp_allowed) hpmax = custom->max_hitp_allowed;
1411 hitp_max = hpmax;
1412 set_health_life();
1413 }
1414
1415 // seg000:1200
set_health_life()1416 void __pascal far set_health_life() {
1417 hitp_delta = hitp_max - hitp_curr;
1418 }
1419
1420 // seg000:120B
draw_hp()1421 void __pascal far draw_hp() {
1422 if (hitp_delta) {
1423 draw_kid_hp(hitp_curr, hitp_max);
1424 }
1425 if (hitp_curr == 1 && current_level != 15) {
1426 // blinking hitpoint
1427 if (rem_tick & 1) {
1428 draw_kid_hp(1, 0);
1429 } else {
1430 draw_kid_hp(0, 1);
1431 }
1432 }
1433 if (guardhp_delta) {
1434 draw_guard_hp(guardhp_curr, guardhp_max);
1435 }
1436 if (guardhp_curr == 1) {
1437 if (rem_tick & 1) {
1438 draw_guard_hp(1, 0);
1439 } else {
1440 draw_guard_hp(0, 1);
1441 }
1442 }
1443 }
1444
1445 // seg000:127B
do_delta_hp()1446 void __pascal far do_delta_hp() {
1447 // level 12: if the shadow is hurt, Kid is also hurt
1448 if (Opp.charid == charid_1_shadow &&
1449 current_level == 12 &&
1450 guardhp_delta != 0
1451 ) {
1452 hitp_delta = guardhp_delta;
1453 }
1454 hitp_curr = MIN(MAX(hitp_curr + hitp_delta, 0), hitp_max);
1455 guardhp_curr = MIN(MAX(guardhp_curr + guardhp_delta, 0), guardhp_max);
1456 }
1457
1458 byte sound_prio_table[] = {
1459 0x14, // sound_0_fell_to_death
1460 0x1E, // sound_1_falling
1461 0x23, // sound_2_tile_crashing
1462 0x66, // sound_3_button_pressed
1463 0x32, // sound_4_gate_closing
1464 0x37, // sound_5_gate_opening
1465 0x30, // sound_6_gate_closing_fast
1466 0x30, // sound_7_gate_stop
1467 0x4B, // sound_8_bumped
1468 0x50, // sound_9_grab
1469 0x0A, // sound_10_sword_vs_sword
1470 0x12, // sound_11_sword_moving
1471 0x0C, // sound_12_guard_hurt
1472 0x0B, // sound_13_kid_hurt
1473 0x69, // sound_14_leveldoor_closing
1474 0x6E, // sound_15_leveldoor_sliding
1475 0x73, // sound_16_medium_land
1476 0x78, // sound_17_soft_land
1477 0x7D, // sound_18_drink
1478 0x82, // sound_19_draw_sword
1479 0x91, // sound_20_loose_shake_1
1480 0x96, // sound_21_loose_shake_2
1481 0x9B, // sound_22_loose_shake_3
1482 0xA0, // sound_23_footstep
1483 0x01, // sound_24_death_regular
1484 0x01, // sound_25_presentation
1485 0x01, // sound_26_embrace
1486 0x01, // sound_27_cutscene_2_4_6_12
1487 0x01, // sound_28_death_in_fight
1488 0x13, // sound_29_meet_Jaffar
1489 0x01, // sound_30_big_potion
1490 0x01, // sound_31
1491 0x01, // sound_32_shadow_music
1492 0x01, // sound_33_small_potion
1493 0x01, // sound_34
1494 0x01, // sound_35_cutscene_8_9
1495 0x01, // sound_36_out_of_time
1496 0x01, // sound_37_victory
1497 0x01, // sound_38_blink
1498 0x00, // sound_39_low_weight
1499 0x01, // sound_40_cutscene_12_short_time
1500 0x01, // sound_41_end_level_music
1501 0x01, // sound_42
1502 0x01, // sound_43_victory_Jaffar
1503 0x87, // sound_44_skel_alive
1504 0x8C, // sound_45_jump_through_mirror
1505 0x0F, // sound_46_chomped
1506 0x10, // sound_47_chomper
1507 0x19, // sound_48_spiked
1508 0x16, // sound_49_spikes
1509 0x01, // sound_50_story_2_princess
1510 0x00, // sound_51_princess_door_opening
1511 0x01, // sound_52_story_4_Jaffar_leaves
1512 0x01, // sound_53_story_3_Jaffar_comes
1513 0x01, // sound_54_intro_music
1514 0x01, // sound_55_story_1_absence
1515 0x01, // sound_56_ending_music
1516 0x00
1517 };
1518 byte sound_pcspeaker_exists[] = {
1519 1, // sound_0_fell_to_death
1520 0, // sound_1_falling
1521 1, // sound_2_tile_crashing
1522 1, // sound_3_button_pressed
1523 1, // sound_4_gate_closing
1524 1, // sound_5_gate_opening
1525 1, // sound_6_gate_closing_fast
1526 1, // sound_7_gate_stop
1527 1, // sound_8_bumped
1528 1, // sound_9_grab
1529 1, // sound_10_sword_vs_sword
1530 0, // sound_11_sword_moving
1531 1, // sound_12_guard_hurt
1532 1, // sound_13_kid_hurt
1533 1, // sound_14_leveldoor_closing
1534 1, // sound_15_leveldoor_sliding
1535 1, // sound_16_medium_land
1536 1, // sound_17_soft_land
1537 1, // sound_18_drink
1538 0, // sound_19_draw_sword
1539 0, // sound_20_loose_shake_1
1540 0, // sound_21_loose_shake_2
1541 0, // sound_22_loose_shake_3
1542 1, // sound_23_footstep
1543 1, // sound_24_death_regular
1544 1, // sound_25_presentation
1545 1, // sound_26_embrace
1546 1, // sound_27_cutscene_2_4_6_12
1547 1, // sound_28_death_in_fight
1548 1, // sound_29_meet_Jaffar
1549 1, // sound_30_big_potion
1550 1, // sound_31
1551 1, // sound_32_shadow_music
1552 1, // sound_33_small_potion
1553 1, // sound_34
1554 1, // sound_35_cutscene_8_9
1555 1, // sound_36_out_of_time
1556 1, // sound_37_victory
1557 1, // sound_38_blink
1558 1, // sound_39_low_weight
1559 1, // sound_40_cutscene_12_short_time
1560 1, // sound_41_end_level_music
1561 1, // sound_42
1562 1, // sound_43_victory_Jaffar
1563 1, // sound_44_skel_alive
1564 1, // sound_45_jump_through_mirror
1565 1, // sound_46_chomped
1566 1, // sound_47_chomper
1567 1, // sound_48_spiked
1568 1, // sound_49_spikes
1569 1, // sound_50_story_2_princess
1570 1, // sound_51_princess_door_opening
1571 1, // sound_52_story_4_Jaffar_leaves
1572 1, // sound_53_story_3_Jaffar_comes
1573 1, // sound_54_intro_music
1574 1, // sound_55_story_1_absence
1575 1, // sound_56_ending_music
1576 0
1577 };
1578
fix_sound_priorities()1579 void fix_sound_priorities() {
1580 // Change values to match those in PoP 1.3.
1581
1582 // The "spiked" sound didn't interrupt the normal spikes sound when the prince ran into spikes.
1583 sound_interruptible[sound_49_spikes] = 1;
1584 sound_prio_table[sound_48_spiked] = 0x15; // moved above spikes
1585
1586 // With PoP 1.3 sounds, the "guard hurt" sound didn't play when you hit a guard directly after parrying.
1587 sound_prio_table[sound_10_sword_vs_sword] = 0x0D; // moved below hit_user/hit_guard
1588 }
1589
1590 // seg000:12C5
play_sound(int sound_id)1591 void __pascal far play_sound(int sound_id) {
1592 //printf("Would play sound %d\n", sound_id);
1593 if (next_sound < 0 || sound_prio_table[sound_id] <= sound_prio_table[next_sound]) {
1594 if (NULL == sound_pointers[sound_id]) return;
1595 if (sound_pcspeaker_exists[sound_id] != 0 || sound_pointers[sound_id]->type != sound_speaker) {
1596 next_sound = sound_id;
1597 }
1598 }
1599 }
1600
1601 // seg000:1304
play_next_sound()1602 void __pascal far play_next_sound() {
1603 if (next_sound >= 0) {
1604 if (!check_sound_playing() ||
1605 (sound_interruptible[current_sound] != 0 && sound_prio_table[next_sound] <= sound_prio_table[current_sound])
1606 ) {
1607 current_sound = next_sound;
1608 play_sound_from_buffer(sound_pointers[current_sound]);
1609 }
1610 }
1611 next_sound = -1;
1612 }
1613
1614 // seg000:1353
check_sword_vs_sword()1615 void __pascal far check_sword_vs_sword() {
1616 if (Kid.frame == 167 || Guard.frame == 167) {
1617 play_sound(sound_10_sword_vs_sword); // sword vs. sword
1618 }
1619 }
1620
1621 // seg000:136A
load_chtab_from_file(int chtab_id,int resource,const char near * filename,int palette_bits)1622 void __pascal far load_chtab_from_file(int chtab_id,int resource,const char near *filename,int palette_bits) {
1623 //printf("Loading chtab %d, id %d from %s\n",chtab_id,resource,filename);
1624 dat_type* dathandle;
1625 if (chtab_addrs[chtab_id] != NULL) return;
1626 dathandle = open_dat(filename, 0);
1627 chtab_addrs[chtab_id] = load_sprites_from_file(resource, palette_bits, 1);
1628 close_dat(dathandle);
1629 }
1630
1631 // seg000:13BA
free_all_chtabs_from(int first)1632 void __pascal far free_all_chtabs_from(int first) {
1633 word chtab_id;
1634 free_peels();
1635 for (chtab_id = first; chtab_id < 10; ++chtab_id) {
1636 if (chtab_addrs[chtab_id]) {
1637 free_chtab(chtab_addrs[chtab_id]);
1638 chtab_addrs[chtab_id] = NULL;
1639 }
1640 }
1641 }
1642
1643 // seg009:12EF
load_one_optgraf(chtab_type * chtab_ptr,dat_pal_type far * pal_ptr,int base_id,int min_index,int max_index)1644 void __pascal far load_one_optgraf(chtab_type* chtab_ptr,dat_pal_type far *pal_ptr,int base_id,int min_index,int max_index) {
1645 short index;
1646 for (index = min_index; index <= max_index; ++index) {
1647 image_type* image = load_image(base_id + index + 1, pal_ptr);
1648 if (image != NULL) chtab_ptr->images[index] = image;
1649 }
1650 }
1651
1652 byte optgraf_min[] = {0x01, 0x1E, 0x4B, 0x4E, 0x56, 0x65, 0x7F, 0x0A};
1653 byte optgraf_max[] = {0x09, 0x1F, 0x4D, 0x53, 0x5B, 0x7B, 0x8F, 0x0D};
1654 // seg000:13FC
load_more_opt_graf(const char * filename)1655 void __pascal far load_more_opt_graf(const char *filename) {
1656 // stub
1657 dat_type* dathandle;
1658 dat_shpl_type area;
1659 short graf_index;
1660 dathandle = NULL;
1661 for (graf_index = 0; graf_index < 8; ++graf_index) {
1662 /*if (...) */ {
1663 if (dathandle == NULL) {
1664 dathandle = open_dat(filename, 0);
1665 load_from_opendats_to_area(200, &area, sizeof(area), "pal");
1666 area.palette.row_bits = 0x20;
1667 }
1668 load_one_optgraf(chtab_addrs[id_chtab_6_environment], &area.palette, 1200, optgraf_min[graf_index] - 1, optgraf_max[graf_index] - 1);
1669 }
1670 }
1671 if (dathandle != NULL) {
1672 close_dat(dathandle);
1673 }
1674 }
1675
1676 // seg000:148D
do_paused()1677 int __pascal far do_paused() {
1678 #ifdef USE_REPLAY
1679 if (replaying && skipping_replay) return 0;
1680 #endif
1681
1682 word key;
1683 key = 0;
1684 next_room = 0;
1685 control_shift = 0;
1686 control_y = 0;
1687 control_x = 0;
1688 if (is_joyst_mode) {
1689 read_joyst_control();
1690 } else {
1691 read_keyb_control();
1692 }
1693 key = process_key();
1694 if (is_ending_sequence && is_paused) {
1695 is_paused = 0; // fix being able to pause the game during the ending sequence
1696 }
1697 if (is_paused) {
1698 // feather fall gets interrupted by pause
1699 if (fixes->fix_quicksave_during_feather &&
1700 is_feather_fall > 0 &&
1701 check_sound_playing()) {
1702 stop_sounds();
1703 }
1704 display_text_bottom("GAME PAUSED");
1705 #ifdef USE_MENU
1706 if (enable_pause_menu || is_menu_shown) {
1707 draw_menu();
1708 menu_was_closed();
1709 } else
1710 #endif
1711 {
1712 is_paused = 0;
1713 // busy waiting?
1714 do {
1715 idle();
1716 delay_ticks(1);
1717 } while (! process_key());
1718 }
1719 erase_bottom_text(1);
1720 }
1721 return key || control_shift;
1722 }
1723
1724 // seg000:1500
read_keyb_control()1725 void __pascal far read_keyb_control() {
1726
1727 if (key_states[SDL_SCANCODE_UP] || key_states[SDL_SCANCODE_HOME] || key_states[SDL_SCANCODE_PAGEUP]
1728 || key_states[SDL_SCANCODE_KP_8] || key_states[SDL_SCANCODE_KP_7] || key_states[SDL_SCANCODE_KP_9]
1729 ) {
1730 control_y = -1;
1731 } else if (key_states[SDL_SCANCODE_CLEAR] || key_states[SDL_SCANCODE_DOWN]
1732 || key_states[SDL_SCANCODE_KP_5] || key_states[SDL_SCANCODE_KP_2]
1733 ) {
1734 control_y = 1;
1735 }
1736 if (key_states[SDL_SCANCODE_LEFT] || key_states[SDL_SCANCODE_HOME]
1737 || key_states[SDL_SCANCODE_KP_4] || key_states[SDL_SCANCODE_KP_7]
1738 ) {
1739 control_x = -1;
1740 } else if (key_states[SDL_SCANCODE_RIGHT] || key_states[SDL_SCANCODE_PAGEUP]
1741 || key_states[SDL_SCANCODE_KP_6] || key_states[SDL_SCANCODE_KP_9]
1742 ) {
1743 control_x = 1;
1744 }
1745 control_shift = -(key_states[SDL_SCANCODE_LSHIFT] || key_states[SDL_SCANCODE_RSHIFT]);
1746
1747 #ifdef USE_DEBUG_CHEATS
1748 if (cheats_enabled && debug_cheats_enabled) {
1749 if (key_states[SDL_SCANCODE_RIGHTBRACKET]) ++Char.x;
1750 else if (key_states[SDL_SCANCODE_LEFTBRACKET]) --Char.x;
1751 }
1752 #endif
1753 }
1754
1755 // seg000:156D
copy_screen_rect(const rect_type far * source_rect_ptr)1756 void __pascal far copy_screen_rect(const rect_type far *source_rect_ptr) {
1757 const rect_type* far target_rect_ptr;
1758 rect_type target_rect;
1759 if (upside_down) {
1760 target_rect_ptr = &target_rect;
1761 /**target_rect_ptr*/target_rect = *source_rect_ptr;
1762 /*target_rect_ptr->*/target_rect.top = 192 - source_rect_ptr->bottom;
1763 /*target_rect_ptr->*/target_rect.bottom = 192 - source_rect_ptr->top;
1764 } else {
1765 target_rect_ptr = source_rect_ptr;
1766 }
1767 method_1_blit_rect(onscreen_surface_, offscreen_surface, target_rect_ptr, target_rect_ptr, 0);
1768 #ifdef USE_LIGHTING
1769 update_lighting(target_rect_ptr);
1770 #endif
1771 }
1772
1773 // seg000:15E9
toggle_upside()1774 void __pascal far toggle_upside() {
1775 upside_down = ~ upside_down;
1776 need_redraw_because_flipped = 1;
1777 }
1778
1779 // seg000:15F8
feather_fall()1780 void __pascal far feather_fall() {
1781 //printf("slow fall started at: rem_min = %d, rem_tick = %d\n", rem_min, rem_tick);
1782 if (fixes->fix_quicksave_during_feather) {
1783 // feather fall is treated as a timer
1784 is_feather_fall = FEATHER_FALL_LENGTH * get_ticks_per_sec(timer_1);
1785 } else {
1786 is_feather_fall = 1;
1787 }
1788 flash_color = 2; // green
1789 flash_time = 3;
1790 stop_sounds();
1791 play_sound(sound_39_low_weight); // low weight
1792 }
1793
1794 // seg000:1618
parse_grmode()1795 int __pascal far parse_grmode() {
1796 // stub
1797 set_gr_mode(gmMcgaVga);
1798 return gmMcgaVga;
1799 }
1800
1801 // seg000:172C
gen_palace_wall_colors()1802 void __pascal far gen_palace_wall_colors() {
1803 dword old_randseed;
1804 word prev_color;
1805 short row;
1806 short subrow;
1807 word color_base;
1808 short column;
1809 word color;
1810
1811 old_randseed = random_seed;
1812 random_seed = drawn_room;
1813 prandom(1); // discard
1814 for (row = 0; row < 3; row++) {
1815 for (subrow = 0; subrow < 4; subrow++) {
1816 if (subrow % 2) {
1817 color_base = 0x61; // 0x61..0x64 in subrow 1 and 3
1818 } else {
1819 color_base = 0x66; // 0x66..0x69 in subrow 0 and 2
1820 }
1821 prev_color = -1;
1822 for (column = 0; column <= 10; ++column) {
1823 do {
1824 color = color_base + prandom(3);
1825 } while (color == prev_color);
1826 palace_wall_colors[44 * row + 11 * subrow + column] = color;
1827 //palace_wall_colors[row][subrow][column] = color;
1828 prev_color = color;
1829 }
1830 }
1831 }
1832 random_seed = old_randseed;
1833 }
1834
1835 // data:042E
1836 const rect_type rect_titles = {106,24,195,296};
1837
1838 // seg000:17E6
show_title()1839 void __pascal far show_title() {
1840 load_opt_sounds(sound_50_story_2_princess, sound_55_story_1_absence); // main theme, story, princess door
1841 dont_reset_time = 0;
1842 if(offscreen_surface) free_surface(offscreen_surface); // missing in original
1843 offscreen_surface = make_offscreen_buffer(&screen_rect);
1844 load_title_images(1);
1845 current_target_surface = offscreen_surface;
1846 idle(); // modified
1847 do_paused();
1848
1849 draw_full_image(TITLE_MAIN);
1850 fade_in_2(offscreen_surface, 0x1000); //STUB
1851 method_1_blit_rect(onscreen_surface_, offscreen_surface, &screen_rect, &screen_rect, blitters_0_no_transp);
1852 current_sound = sound_54_intro_music; // added
1853 play_sound_from_buffer(sound_pointers[sound_54_intro_music]); // main theme
1854 start_timer(timer_0, 0x82);
1855 draw_full_image(TITLE_PRESENTS);
1856 do_wait(timer_0);
1857
1858 start_timer(timer_0, 0xCD);
1859 method_1_blit_rect(onscreen_surface_, offscreen_surface, &rect_titles, &rect_titles, blitters_0_no_transp);
1860 draw_full_image(TITLE_MAIN);
1861 do_wait(timer_0);
1862
1863 start_timer(timer_0, 0x41);
1864 method_1_blit_rect(onscreen_surface_, offscreen_surface, &rect_titles, &rect_titles, blitters_0_no_transp);
1865 draw_full_image(TITLE_MAIN);
1866 draw_full_image(TITLE_GAME);
1867 do_wait(timer_0);
1868
1869 start_timer(timer_0, 0x10E);
1870 method_1_blit_rect(onscreen_surface_, offscreen_surface, &rect_titles, &rect_titles, blitters_0_no_transp);
1871 draw_full_image(TITLE_MAIN);
1872 do_wait(timer_0);
1873
1874 start_timer(timer_0, 0xEB);
1875 method_1_blit_rect(onscreen_surface_, offscreen_surface, &rect_titles, &rect_titles, blitters_0_no_transp);
1876 draw_full_image(TITLE_MAIN);
1877 draw_full_image(TITLE_POP);
1878 draw_full_image(TITLE_MECHNER);
1879 do_wait(timer_0);
1880
1881 method_1_blit_rect(onscreen_surface_, offscreen_surface, &rect_titles, &rect_titles, blitters_0_no_transp);
1882 draw_full_image(STORY_FRAME);
1883 draw_full_image(STORY_ABSENCE);
1884 current_target_surface = onscreen_surface_;
1885 while (check_sound_playing()) {
1886 idle();
1887 do_paused();
1888 delay_ticks(1);
1889 }
1890 // method_1_blit_rect(onscreen_surface_, offscreen_surface, &screen_rect, &screen_rect, blitters_0_no_transp);
1891 play_sound_from_buffer(sound_pointers[sound_55_story_1_absence]); // story 1: In the absence
1892 transition_ltr();
1893 pop_wait(timer_0, 0x258);
1894 fade_out_2(0x800);
1895 release_title_images();
1896
1897 load_intro(0, &pv_scene, 0);
1898
1899 load_title_images(1);
1900 current_target_surface = offscreen_surface;
1901 draw_full_image(STORY_FRAME);
1902 draw_full_image(STORY_MARRY);
1903 fade_in_2(offscreen_surface, 0x800);
1904 draw_full_image(TITLE_MAIN);
1905 draw_full_image(TITLE_POP);
1906 draw_full_image(TITLE_MECHNER);
1907 while (check_sound_playing()) {
1908 idle();
1909 do_paused();
1910 delay_ticks(1);
1911 }
1912 transition_ltr();
1913 pop_wait(timer_0, 0x78);
1914 draw_full_image(STORY_FRAME);
1915 draw_full_image(STORY_CREDITS);
1916 transition_ltr();
1917 pop_wait(timer_0, 0x168);
1918 if (hof_count) {
1919 draw_full_image(STORY_FRAME);
1920 draw_full_image(HOF_POP);
1921 show_hof();
1922 transition_ltr();
1923 pop_wait(timer_0, 0xF0);
1924 }
1925 current_target_surface = onscreen_surface_;
1926 while (check_sound_playing()) {
1927 idle();
1928 do_paused();
1929 delay_ticks(1);
1930 }
1931 fade_out_2(0x1800);
1932 free_surface(offscreen_surface);
1933 offscreen_surface = NULL; // added
1934 release_title_images();
1935 init_game(0);
1936 }
1937
1938 Uint64 last_transition_counter;
1939
1940 // seg000:1BB3
transition_ltr()1941 void __pascal far transition_ltr() {
1942 short position;
1943 rect_type rect;
1944 rect.top = 0;
1945 rect.bottom = 200;
1946 rect.left = 0;
1947 rect.right = 2;
1948 // Estimated transition fps based on the speed of the transition on an Apple IIe.
1949 // See: https://www.youtube.com/watch?v=7m7j2VuWhQ0
1950 int transition_fps = 120;
1951 #ifdef USE_FAST_FORWARD
1952 extern int audio_speed;
1953 transition_fps *= audio_speed;
1954 #endif
1955 Uint64 counters_per_frame = perf_frequency / transition_fps;
1956 last_transition_counter = SDL_GetPerformanceCounter();
1957 int overshoot = 0;
1958 for (position = 0; position < 320; position += 2) {
1959 method_1_blit_rect(onscreen_surface_, offscreen_surface, &rect, &rect, 0);
1960 rect.left += 2;
1961 rect.right += 2;
1962 if (overshoot > 0 && overshoot < 10) {
1963 --overshoot;
1964 continue; // On slow systems (e.g. Raspberry Pi), allow the animation to catch up, before refreshing the screen.
1965 }
1966 idle(); // modified
1967 do_paused();
1968 // Add an appropriate delay until the next frame, so that the animation isn't instantaneous on fast CPUs.
1969 for (;;) {
1970 Uint64 current_counter = SDL_GetPerformanceCounter();
1971 int frametimes_elapsed = (int)((current_counter / counters_per_frame) - (last_transition_counter / counters_per_frame));
1972 if (frametimes_elapsed > 0) {
1973 overshoot = frametimes_elapsed - 1;
1974 last_transition_counter = current_counter;
1975 break; // Proceed to the next frame.
1976 } else {
1977 SDL_Delay(1);
1978 }
1979 }
1980
1981 }
1982 }
1983
1984 // seg000:1C0F
release_title_images()1985 void __pascal far release_title_images() {
1986 if (chtab_title50) {
1987 free_chtab(chtab_title50);
1988 chtab_title50 = NULL;
1989 }
1990 if (chtab_title40) {
1991 free_chtab(chtab_title40);
1992 chtab_title40 = NULL;
1993 }
1994 }
1995
1996 // seg000:1C3A
draw_full_image(enum full_image_id id)1997 void __pascal far draw_full_image(enum full_image_id id) {
1998 image_type* decoded_image;
1999 image_type* mask = NULL;
2000 int xpos, ypos, blit;
2001
2002 if (id >= MAX_FULL_IMAGES) return;
2003 if (NULL == *full_image[id].chtab) return;
2004 decoded_image = (*full_image[id].chtab)->images[full_image[id].id];
2005 blit = full_image[id].blitter;
2006 xpos = full_image[id].xpos;
2007 ypos = full_image[id].ypos;
2008
2009 switch (blit) {
2010 case blitters_white:
2011 blit = get_text_color(15, color_15_brightwhite, 0x800);
2012 /* fall through */
2013 default:
2014 method_3_blit_mono(decoded_image, xpos, ypos, blitters_0_no_transp, blit);
2015 break;
2016 case blitters_10h_transp:
2017 if (graphics_mode == gmCga || graphics_mode == gmHgaHerc) {
2018 //...
2019 } else {
2020 mask = decoded_image;
2021 }
2022 draw_image_transp(decoded_image, mask, xpos, ypos);
2023 if (graphics_mode == gmCga || graphics_mode == gmHgaHerc) {
2024 free_far(mask);
2025 }
2026 break;
2027 case blitters_0_no_transp:
2028 method_6_blit_img_to_scr(decoded_image, xpos, ypos, blit);
2029 break;
2030 }
2031 }
2032
2033 // seg000:1D2C
load_kid_sprite()2034 void __pascal far load_kid_sprite() {
2035 load_chtab_from_file(id_chtab_2_kid, 400, "KID.DAT", 1<<7);
2036 }
2037
2038 const char* save_file = "PRINCE.SAV";
2039
get_save_path(char * custom_path_buffer,size_t max_len)2040 const char* get_save_path(char* custom_path_buffer, size_t max_len) {
2041 if (!use_custom_levelset) {
2042 return save_file;
2043 }
2044 // if playing a custom levelset, try to use the mod folder
2045 snprintf_check(custom_path_buffer, max_len, "%s/%s", mod_data_path, save_file /*PRINCE.SAV*/ );
2046 return custom_path_buffer;
2047 }
2048
2049 // seg000:1D45
save_game()2050 void __pascal far save_game() {
2051 word success;
2052 FILE* handle;
2053 success = 0;
2054 char custom_save_path[POP_MAX_PATH];
2055 const char* save_path = get_save_path(custom_save_path, sizeof(custom_save_path));
2056 handle = fopen(save_path, "wb");
2057 if (handle == NULL) goto loc_1DB8;
2058 if (fwrite(&rem_min, 1, 2, handle) == 2) goto loc_1DC9;
2059 loc_1D9B:
2060 fclose(handle);
2061 if (!success) {
2062 remove(save_path);
2063 }
2064 loc_1DB8:
2065 if (!success) goto loc_1E18;
2066 display_text_bottom("GAME SAVED");
2067 goto loc_1E2E;
2068 loc_1DC9:
2069 if (fwrite(&rem_tick, 1, 2, handle) != 2) goto loc_1D9B;
2070 if (fwrite(¤t_level, 1, 2, handle) != 2) goto loc_1D9B;
2071 if (fwrite(&hitp_beg_lev, 1, 2, handle) != 2) goto loc_1D9B;
2072 success = 1;
2073 goto loc_1D9B;
2074 loc_1E18:
2075 display_text_bottom("UNABLE TO SAVE GAME");
2076 //play_sound_from_buffer(&sound_cant_save);
2077 loc_1E2E:
2078 text_time_remaining = 24;
2079 }
2080
2081 // seg000:1E38
load_game()2082 short __pascal far load_game() {
2083 word success;
2084 FILE* handle;
2085 success = 0;
2086 char custom_save_path[POP_MAX_PATH];
2087 const char* save_path = get_save_path(custom_save_path, sizeof(custom_save_path));
2088 handle = fopen(save_path, "rb");
2089 if (handle == NULL) goto loc_1E99;
2090 if (fread(&rem_min, 1, 2, handle) == 2) goto loc_1E9E;
2091 loc_1E8E:
2092 fclose(handle);
2093 loc_1E99:
2094 return success;
2095 loc_1E9E:
2096 if (fread(&rem_tick, 1, 2, handle) != 2) goto loc_1E8E;
2097 if (fread(&start_level, 1, 2, handle) != 2) goto loc_1E8E;
2098 if (fread(&hitp_beg_lev, 1, 2, handle) != 2) goto loc_1E8E;
2099 #ifdef USE_COPYPROT
2100 if (enable_copyprot && custom->copyprot_level > 0) {
2101 custom->copyprot_level = start_level;
2102 }
2103 #endif
2104 success = 1;
2105 dont_reset_time = 1;
2106 goto loc_1E8E;
2107 }
2108
2109 // seg000:1F02
clear_screen_and_sounds()2110 void __pascal far clear_screen_and_sounds() {
2111 short index;
2112 stop_sounds();
2113 current_target_surface = rect_sthg(onscreen_surface_, &screen_rect);
2114
2115 is_cutscene = 0;
2116 is_ending_sequence = false; // added
2117 peels_count = 0;
2118 // should these be freed?
2119 for (index = 2; index < 10; ++index) {
2120 if (chtab_addrs[index]) {
2121 // Original code does not free these?
2122 free_chtab(chtab_addrs[index]);
2123 chtab_addrs[index] = NULL;
2124 }
2125 }
2126 /* //Don't free sounds.
2127 for (index = 44; index < 57; ++index) {
2128 //continue; // don't release sounds? modern machines have enough memory
2129 free_sound(sound_pointers[index]); // added
2130 sound_pointers[index] = NULL;
2131 }
2132 */
2133 current_level = -1;
2134 }
2135
2136 // seg000:1F7B
parse_cmdline_sound()2137 void __pascal far parse_cmdline_sound() {
2138 // stub
2139 if (check_param("stdsnd")) {
2140 // Use PC Speaker sounds and music.
2141 } else {
2142 // Use digi (wave) sounds and MIDI music.
2143 sound_flags |= sfDigi;
2144 sound_flags |= sfMidi;
2145 sound_mode = smSblast;
2146 }
2147 }
2148
2149 // seg000:226D
free_optional_sounds()2150 void __pascal far free_optional_sounds() {
2151 /* //Don't free sounds.
2152 int sound_id;
2153 for (sound_id = 44; sound_id < 57; ++sound_id) {
2154 free_sound(sound_pointers[sound_id]);
2155 sound_pointers[sound_id] = NULL;
2156 }
2157 */
2158 // stub
2159 }
2160
free_all_sounds()2161 void free_all_sounds() {
2162 for (int i = 0; i < 58; ++i) {
2163 free_sound(sound_pointers[i]);
2164 sound_pointers[i] = NULL;
2165 }
2166 }
2167
load_all_sounds()2168 void load_all_sounds() {
2169 if (!use_custom_levelset) {
2170 load_sounds(0, 43);
2171 load_opt_sounds(43, 56); //added
2172 } else {
2173 // First load any sounds included in the mod folder...
2174 skip_normal_data_files = true;
2175 load_sounds(0, 43);
2176 load_opt_sounds(43, 56);
2177 skip_normal_data_files = false;
2178 // ... then load any missing sounds from SDLPoP's own resources.
2179 skip_mod_data_files = true;
2180 load_sounds(0, 43);
2181 load_opt_sounds(43, 56);
2182 skip_mod_data_files = false;
2183 }
2184 }
2185
2186 // seg000:22BB
free_optsnd_chtab()2187 void __pascal far free_optsnd_chtab() {
2188 free_optional_sounds();
2189 free_all_chtabs_from(id_chtab_3_princessinstory);
2190 }
2191
2192 // seg000:22C8
load_title_images(int bgcolor)2193 void __pascal far load_title_images(int bgcolor) {
2194 dat_type* dathandle;
2195 dathandle = open_dat("TITLE.DAT", 0);
2196 chtab_title40 = load_sprites_from_file(40, 1<<11, 1);
2197 chtab_title50 = load_sprites_from_file(50, 1<<12, 1);
2198 close_dat(dathandle);
2199 if (graphics_mode == gmMcgaVga) {
2200 // background of text frame
2201 SDL_Color color;
2202 if (bgcolor) {
2203 // RGB(4,0,18h) = #100060 = dark blue
2204 set_pal((find_first_pal_row(1<<11) << 4) + 14, 0x04, 0x00, 0x18, 1);
2205 color.r = 0x10;
2206 color.g = 0x00;
2207 color.b = 0x60;
2208 color.a = 0xFF;
2209 } else {
2210 // RGB(20h,0,0) = #800000 = dark red
2211 set_pal((find_first_pal_row(1<<11) << 4) + 14, 0x20, 0x00, 0x00, 1);
2212 color.r = 0x80;
2213 color.g = 0x00;
2214 color.b = 0x00;
2215 color.a = 0xFF;
2216 }
2217 if (NULL != chtab_title40) {
2218 SDL_SetPaletteColors(chtab_title40->images[0]->format->palette, &color, 14, 1);
2219 }
2220 } else if (graphics_mode == gmEga || graphics_mode == gmTga) {
2221 // ...
2222 }
2223 }
2224
2225 #ifdef USE_COPYPROT
2226 // data:017A
2227 const word copyprot_word[] = {9, 1, 6, 4, 5, 3, 6, 3, 4, 4, 3, 2,12, 5,13, 1, 9, 2, 2, 4, 9, 4,11, 8, 5, 4, 1, 6, 2, 4, 6, 8, 4, 2, 7,11, 5, 4, 1, 2};
2228 // data:012A
2229 const word copyprot_line[] = {2, 1, 5, 4, 3, 5, 1, 3, 7, 2, 2, 4, 6, 6, 2, 6, 3, 1, 2, 3, 2, 2, 3,10, 5, 6, 5, 6, 3, 5, 7, 2, 2, 4, 5, 7, 2, 6, 5, 5};
2230 // data:00DA
2231 const word copyprot_page[] = {5, 3, 7, 3, 3, 4, 1, 5,12, 5,11,10, 1, 2, 8, 8, 2, 4, 6, 1, 4, 7, 3, 2, 1, 7,10, 1, 4, 3, 4, 1, 4, 1, 8, 1, 1,10, 3, 3};
2232 #endif
2233
2234 // seg000:23F4
show_copyprot(int where)2235 void __pascal far show_copyprot(int where) {
2236 #ifdef USE_COPYPROT
2237 char sprintf_temp[140];
2238 if (current_level != 15) return;
2239 if (where) {
2240 if (text_time_remaining || is_cutscene) return;
2241 text_time_total = 1188;
2242 text_time_remaining = 1188;
2243 is_show_time = 0;
2244 snprintf(sprintf_temp, sizeof(sprintf_temp),
2245 "WORD %d LINE %d PAGE %d",
2246 copyprot_word[copyprot_idx], copyprot_line[copyprot_idx], copyprot_page[copyprot_idx]);
2247 display_text_bottom(sprintf_temp);
2248 } else {
2249 snprintf(sprintf_temp, sizeof(sprintf_temp),
2250 "Drink potion matching the first letter of Word %d on Line %d\n"
2251 "of Page %d of the manual.",
2252 copyprot_word[copyprot_idx], copyprot_line[copyprot_idx], copyprot_page[copyprot_idx]);
2253 show_dialog(sprintf_temp);
2254 }
2255 #endif
2256 }
2257
2258 // seg000:2489
show_loading()2259 void __pascal far show_loading() {
2260 show_text(&screen_rect, 0, 0, "Loading. . . .");
2261 update_screen();
2262 }
2263
2264 // data:42C4
2265 word which_quote;
2266
2267 char const * const tbl_quotes[2] = {
2268 "\"(****/****) Incredibly realistic. . . The "
2269 "adventurer character actually looks human as he "
2270 "runs, jumps, climbs, and hangs from ledges.\"\n"
2271 "\n"
2272 " Computer Entertainer\n"
2273 "\n"
2274 "\n"
2275 "\n"
2276 "\n"
2277 "\"A tremendous achievement. . . Mechner has crafted "
2278 "the smoothest animation ever seen in a game of this "
2279 "type.\n"
2280 "\n"
2281 "\"PRINCE OF PERSIA is the STAR WARS of its field.\"\n"
2282 "\n"
2283 " Computer Gaming World",
2284 "\"An unmitigated delight. . . comes as close to "
2285 "(perfection) as any arcade game has come in a long, "
2286 "long time. . . what makes this game so wonderful (am "
2287 "I gushing?) is that the little onscreen character "
2288 "does not move like a little onscreen character -- he "
2289 "moves like a person.\"\n"
2290 "\n"
2291 " Nibble"
2292 };
2293
2294 // seg000:249D
show_quotes()2295 void __pascal far show_quotes() {
2296 //start_timer(timer_0,0);
2297 //remove_timer(timer_0);
2298 if (demo_mode && need_quotes) {
2299 draw_rect(&screen_rect, 0);
2300 show_text(&screen_rect, -1, 0, tbl_quotes[which_quote]);
2301 which_quote = !which_quote;
2302 start_timer(timer_0, 0x384);
2303 }
2304 need_quotes = 0;
2305 }
2306
2307 const rect_type splash_text_1_rect = {0, 0, 50, 320};
2308 const rect_type splash_text_2_rect = {50, 0, 200, 320};
2309
2310 const char* splash_text_1 = "SDLPoP " SDLPOP_VERSION;
2311 const char* splash_text_2 =
2312 #ifdef USE_QUICKSAVE
2313 "To quick save/load, press F6/F9 in-game.\n"
2314 "\n"
2315 #endif
2316 #ifdef USE_REPLAY
2317 "To record replays, press Ctrl+Tab in-game.\n"
2318 "To view replays, press Tab on the title screen.\n"
2319 "\n"
2320 #endif
2321 "Edit SDLPoP.ini to customize SDLPoP.\n"
2322 "Mods also work with SDLPoP.\n"
2323 "\n"
2324 "For more information, read doc/Readme.txt.\n"
2325 "Questions? Visit https://forum.princed.org\n"
2326 "\n"
2327 "Press any key to continue...";
2328
show_splash()2329 void show_splash() {
2330 if (!enable_info_screen || start_level >= 0) return;
2331 current_target_surface = onscreen_surface_;
2332 draw_rect(&screen_rect, 0);
2333 show_text_with_color(&splash_text_1_rect, 0, 0, splash_text_1, color_15_brightwhite);
2334 show_text_with_color(&splash_text_2_rect, 0, -1, splash_text_2, color_7_lightgray);
2335
2336 #ifdef USE_TEXT // Don't wait for a keypress if there is no text for the user to read.
2337 int key = 0;
2338 do {
2339 idle();
2340 key = key_test_quit();
2341
2342 if (joy_hat_states[0] != 0 || joy_X_button_state != 0 || joy_AY_buttons_state != 0 || joy_B_button_state != 0) {
2343 joy_hat_states[0] = 0;
2344 joy_AY_buttons_state = 0;
2345 joy_X_button_state = 0;
2346 joy_B_button_state = 0;
2347 key_states[SDL_SCANCODE_LSHIFT] = 1; // close the splash screen using the gamepad
2348 }
2349 delay_ticks(1);
2350
2351 } while(key == 0 && !(key_states[SDL_SCANCODE_LSHIFT] || key_states[SDL_SCANCODE_RSHIFT]));
2352
2353 if ((key & WITH_CTRL) || (enable_quicksave && key == SDL_SCANCODE_F9) || (enable_replay && key == SDL_SCANCODE_TAB)) {
2354 extern int last_key_scancode; // defined in seg009.c
2355 last_key_scancode = key; // can immediately do Ctrl+L, etc from the splash screen
2356 }
2357 key_states[SDL_SCANCODE_LSHIFT] = 0; // don't immediately start the game if Shift was pressed!
2358 key_states[SDL_SCANCODE_RSHIFT] = 0;
2359 #endif
2360 }
2361
2362