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 
23 // data:3D1A
24 sbyte distance_mirror;
25 
26 // seg003:0000
init_game(int level)27 void __pascal far init_game(int level) {
28 	if(offscreen_surface) {
29 		free_surface(offscreen_surface); // missing in original
30 		offscreen_surface = NULL;
31 	}
32 	offscreen_surface = make_offscreen_buffer(&rect_top);
33 	load_kid_sprite();
34 	text_time_remaining = 0;
35 	text_time_total = 0;
36 	is_show_time = 0;
37 	checkpoint = 0;
38 	upside_down = 0; // N.B. upside_down is also reset in set_start_pos()
39 	resurrect_time = 0;
40 	if (!dont_reset_time) {
41 		rem_min = custom->start_minutes_left;   // 60
42 		rem_tick = custom->start_ticks_left;    // 719
43 		hitp_beg_lev = custom->start_hitp;      // 3
44 	}
45 	need_level1_music = (level == /*1*/ custom->intro_music_level);
46 	play_level(level);
47 }
48 
49 // seg003:005C
play_level(int level_number)50 void __pascal far play_level(int level_number) {
51 	cutscene_ptr_type cutscene_func;
52 #ifdef USE_COPYPROT
53 	if (enable_copyprot && level_number == custom->copyprot_level) {
54 		level_number = 15;
55 	}
56 #endif
57 	for (;;) {
58 		if (demo_mode && level_number > 2) {
59 			start_level = -1;
60 			need_quotes = 1;
61 			start_game();
62 		}
63 		if (level_number != current_level) {
64 			if (level_number <0 || level_number >15) {
65 				printf("Tried to load cutscene for level %d, not in 0..15\n", level_number);
66 				quit(1);
67 			}
68 			cutscene_func = tbl_cutscenes[custom->tbl_cutscenes_by_index[level_number]];
69 			if (cutscene_func != NULL
70 
71 				#ifdef USE_REPLAY
72 				&& !(recording || replaying)
73 				#endif
74 #ifdef USE_SCREENSHOT
75 				&& !want_auto_screenshot()
76 #endif
77 					) {
78 				load_intro(level_number > 2, cutscene_func, 1);
79 			}
80 		}
81 		if (level_number != current_level) {
82 			load_lev_spr(level_number);
83 		}
84 		load_level();
85 		pos_guards();
86 		clear_coll_rooms();
87 		clear_saved_ctrl();
88 		drawn_room = 0;
89 		mobs_count = 0;
90 		trobs_count = 0;
91 		next_sound = -1;
92 		holding_sword = 0;
93 		grab_timer = 0;
94 		can_guard_see_kid = 0;
95 		united_with_shadow = 0;
96 		flash_time = 0;
97 		leveldoor_open = 0;
98 		demo_index = 0;
99 		demo_time = 0;
100 		guardhp_curr = 0;
101 		hitp_delta = 0;
102 		Guard.charid = charid_2_guard;
103 		Guard.direction = dir_56_none;
104 		do_startpos();
105 		have_sword = /*(level_number != 1)*/ (level_number == 0 || level_number >= custom->have_sword_from_level);
106 		find_start_level_door();
107 		// busy waiting?
108 		while (check_sound_playing() && !do_paused()) idle();
109 		stop_sounds();
110 		#ifdef USE_REPLAY
111 		if (replaying) replay_restore_level();
112 		if (skipping_replay) {
113 			if (replay_seek_target == replay_seek_0_next_room ||
114 				replay_seek_target == replay_seek_1_next_level
115 			)
116 				skipping_replay = 0; // resume replay from here
117 		}
118 		#endif
119 		draw_level_first();
120 		show_copyprot(0);
121 		level_number = play_level_2();
122 		// hacked...
123 #ifdef USE_COPYPROT
124 		if (enable_copyprot && level_number == custom->copyprot_level && !demo_mode) {
125 			level_number = 15;
126 		} else {
127 			if (level_number == 16) {
128 				level_number = custom->copyprot_level;
129 				custom->copyprot_level = -1;
130 			}
131 		}
132 #endif
133 		free_peels();
134 	}
135 }
136 
137 // seg003:01A3
do_startpos()138 void __pascal far do_startpos() {
139 	word x;
140 	// Special event: start at checkpoint
141 	if (current_level == /*3*/ custom->checkpoint_level && checkpoint) {
142 		level.start_dir = /*dir_FF_left*/ custom->checkpoint_respawn_dir;
143 		level.start_room = /*2*/ custom->checkpoint_respawn_room;
144 		level.start_pos = /*6*/ custom->checkpoint_respawn_tilepos;
145 		// Special event: remove loose floor
146 		get_tile(/*7*/ custom->checkpoint_clear_tile_room,
147 				/*4*/ custom->checkpoint_clear_tile_col,
148 				/*0*/ custom->checkpoint_clear_tile_row);
149 		curr_room_tiles[curr_tilepos] = tiles_0_empty;
150 	}
151 	next_room = Char.room = level.start_room;
152 	x = level.start_pos;
153 	Char.curr_col = x % 10;
154 	Char.curr_row = x / 10;
155 	Char.x = x_bump[Char.curr_col + 5] + 14;
156 	// Start in the opposite direction (and turn into the correct one).
157 	Char.direction = ~ level.start_dir;
158 	if (seamless == 0) {
159 		if (current_level != 0) {
160 			x = hitp_beg_lev;
161 		} else {
162 			// HP on demo level
163 			x = /*4*/ custom->demo_hitp;
164 		}
165 		hitp_max = hitp_curr = x;
166 	}
167 	if (/*current_level == 1*/ custom->tbl_entry_pose[current_level] == 1) {
168 		// Special event: press button + falling entry
169 		get_tile(5, 2, 0);
170 		trigger_button(0, 0, -1);
171 		seqtbl_offset_char(seq_7_fall); // fall
172 	} else if (/*current_level == 13*/ custom->tbl_entry_pose[current_level] == 2) {
173 		// Special event: running entry
174 		seqtbl_offset_char(seq_84_run); // run
175 	} else {
176 		seqtbl_offset_char(seq_5_turn); // turn
177 	}
178 	set_start_pos();
179 }
180 
181 // seg003:028A
set_start_pos()182 void __pascal far set_start_pos() {
183 	Char.y = y_land[Char.curr_row + 1];
184 	Char.alive = -1;
185 	Char.charid = charid_0_kid;
186 	is_screaming = 0;
187 	knock = 0;
188 	upside_down = custom->start_upside_down; // 0
189 	is_feather_fall = 0;
190 	Char.fall_y = 0;
191 	Char.fall_x = 0;
192 	offguard = 0;
193 	Char.sword = sword_0_sheathed;
194 	droppedout = 0;
195 	play_seq();
196 	if (current_level == /*7*/ custom->falling_entry_level && Char.room == /*17*/ custom->falling_entry_room) {
197 		// Special event: level 7 falling entry
198 		// level 7, room 17: show room below
199 		goto_other_room(3);
200 	}
201 	savekid();
202 }
203 
204 // seg003:02E6
find_start_level_door()205 void __pascal far find_start_level_door() {
206 	short tilepos;
207 	get_room_address(Kid.room);
208 	for (tilepos = 0; tilepos < 30; ++tilepos) {
209 		if ((curr_room_tiles[tilepos] & 0x1F) == tiles_16_level_door_left) {
210 			start_level_door(Kid.room, tilepos);
211 		}
212 	}
213 }
214 
215 // seg003:0326
draw_level_first()216 void __pascal far draw_level_first() {
217 	next_room = Kid.room;
218 	check_the_end();
219 	if (custom->tbl_level_type[current_level]) {
220 		gen_palace_wall_colors();
221 	}
222 	draw_rect(&screen_rect, 0);
223 	show_level();
224 	redraw_screen(0);
225 	draw_kid_hp(hitp_curr, hitp_max);
226 
227 #ifdef USE_QUICKSAVE
228 	check_quick_op();
229 #endif
230 
231 #ifdef USE_SCREENSHOT
232 	auto_screenshot();
233 #endif
234 
235 	// Busy waiting!
236 	start_timer(timer_1, 5);
237 	do_simple_wait(1);
238 }
239 
240 // seg003:037B
redraw_screen(int drawing_different_room)241 void __pascal far redraw_screen(int drawing_different_room) {
242 	//remove_flash();
243 	if (drawing_different_room) {
244 		draw_rect(&rect_top, 0);
245 	}
246 
247 	different_room = 0;
248 	if (is_blind_mode) {
249 		draw_rect(&rect_top, 0);
250 	} else {
251 		if (curr_guard_color) {
252 			// Moved *before* drawings.
253 			set_chtab_palette(chtab_addrs[id_chtab_5_guard], &guard_palettes[0x30 * curr_guard_color - 0x30], 0x10);
254 		}
255 		need_drects = 0;
256 		redraw_room();
257 #ifdef USE_LIGHTING
258 	redraw_lighting();
259 #endif
260 		if (is_keyboard_mode) {
261 			clear_kbd_buf();
262 		}
263 		is_blind_mode = 1;
264 		draw_tables();
265 		if (is_keyboard_mode) {
266 			clear_kbd_buf();
267 		}
268 #ifdef USE_COPYPROT
269 		if (current_level == 15) {
270 			// letters on potions level
271 			current_target_surface = offscreen_surface;
272 			short var_2;
273 			for (var_2 = 0; var_2 < 14; ++var_2) {
274 				if (copyprot_room[var_2] == drawn_room) {
275 					set_curr_pos((copyprot_tile[var_2] % 10 << 5) + 24, copyprot_tile[var_2] / 10 * 63 + 38);
276 					draw_text_character(copyprot_letter[cplevel_entr[var_2]]);
277 				}
278 			}
279 			current_target_surface = onscreen_surface_;
280 		}
281 #endif
282 		is_blind_mode = 0;
283 		memset_near(table_counts, 0, sizeof(table_counts));
284 		draw_moving();
285 		draw_tables();
286 		if (is_keyboard_mode) {
287 			clear_kbd_buf();
288 		}
289 		need_drects = 1;
290 		if (curr_guard_color) {
291 			//set_pal_arr(0x80, 0x10, &guard_palettes[0x30 * curr_guard_color - 0x30], 1);
292 		}
293 		if (upside_down) {
294 			flip_screen(offscreen_surface);
295 		}
296 		copy_screen_rect(&rect_top);
297 		if (upside_down) {
298 			flip_screen(offscreen_surface);
299 		}
300 		if (is_keyboard_mode) {
301 			clear_kbd_buf();
302 		}
303 	}
304 	exit_room_timer = 2;
305 
306 }
307 
308 #ifdef CHECK_TIMING
309 typedef struct test_timing_state_type {
310 	bool already_had_first_frame;
311 	Uint64 level_start_counter;
312 	int ticks_left_at_level_start;
313 	float seconds_left_at_level_start;
314 } test_timing_state_type;
315 
test_timings(test_timing_state_type * state)316 void test_timings(test_timing_state_type* state) {
317 
318 	if (!state->already_had_first_frame) {
319 		state->level_start_counter = SDL_GetPerformanceCounter();
320 		state->ticks_left_at_level_start = (rem_min-1)*720 + rem_tick;
321 		state->seconds_left_at_level_start = (1.0f / 60.0f) * (5 * state->ticks_left_at_level_start);
322 		printf("Seconds left = %f\n", state->seconds_left_at_level_start);
323 		state->already_had_first_frame = true;
324 	} else if (rem_tick % 12 == 11) {
325 		Uint64 current_counter = SDL_GetPerformanceCounter();
326 		float actual_seconds_elapsed = (float)(current_counter - state->level_start_counter) / (float)SDL_GetPerformanceFrequency();
327 
328 		int ticks_left = (rem_min-1)*720 + rem_tick;
329 		float game_seconds_left = (1.0f / 60.0f) * (5 * ticks_left);
330 		float game_seconds_elapsed = state->seconds_left_at_level_start - game_seconds_left;
331 
332 		printf("rem_min: %d   game elapsed (s): %.2f    actual elapsed (s): %.2f     delta: %.2f\n",
333 		       rem_min, game_seconds_elapsed, actual_seconds_elapsed, actual_seconds_elapsed - game_seconds_elapsed);
334 	}
335 
336 }
337 #endif
338 
339 // seg003:04F8
340 // Returns a level number:
341 // - The current level if it was restarted.
342 // - The next level if the level was completed.
play_level_2()343 int __pascal far play_level_2() {
344 	reset_timer(timer_1);
345 #ifdef CHECK_TIMING
346 	test_timing_state_type test_timing_state = {0};
347 #endif
348 	while (1) { // main loop
349 #ifdef USE_QUICKSAVE
350 		check_quick_op();
351 #endif
352 #ifdef CHECK_TIMING
353 		test_timings(&test_timing_state);
354 #endif
355 
356 #ifdef USE_REPLAY
357 		if (need_replay_cycle) replay_cycle();
358 #endif
359 		if (Kid.sword == sword_2_drawn) {
360 			// speed when fighting (smaller is faster)
361 			set_timer_length(timer_1, /*6*/ custom->fight_speed);
362 		} else {
363 			// speed when not fighting (smaller is faster)
364 			set_timer_length(timer_1, /*5*/ custom->base_speed);
365 		}
366 		guardhp_delta = 0;
367 		hitp_delta = 0;
368 		timers();
369 		play_frame();
370 
371 #ifdef USE_REPLAY
372 		// At the exact "end of level" frame, preserve the seed to ensure reproducibility,
373 		// regardless of how long the sound is still playing *after* this frame (torch animation modifies the seed!)
374 		if (keep_last_seed == 1) {
375 			preserved_seed = random_seed;
376 			keep_last_seed = -1; // disable repeat
377 		}
378 #endif
379 
380 		if (is_restart_level) {
381 			is_restart_level = 0;
382 			return current_level;
383 		} else {
384 			if (next_level == current_level || check_sound_playing()) {
385 				draw_game_frame();
386 				flash_if_hurt();
387 				remove_flash_if_hurt();
388 				do_simple_wait(timer_1);
389 			} else {
390 				stop_sounds();
391 				hitp_beg_lev = hitp_max;
392 				checkpoint = 0;
393 
394 				#ifdef USE_REPLAY
395 				if (keep_last_seed == -1) {
396 					random_seed = preserved_seed; // Ensure reproducibility in the next level.
397 					keep_last_seed = 0;
398 				}
399 				#endif
400 
401 				return next_level;
402 			}
403 		}
404 	}
405 }
406 
407 // seg003:0576
redraw_at_char()408 void __pascal far redraw_at_char() {
409 	short x_top_row;
410 	short tile_col;
411 	short tile_row;
412 	short x_col_left;
413 	short x_col_right;
414 	if (Char.sword >= sword_2_drawn) {
415 		// If char is holding sword, it makes redraw-area bigger.
416 		if (Char.direction >= dir_0_right) {
417 			if (++char_col_right > 9) char_col_right = 9;
418 			// char_col_right = MIN(char_col_right + 1, 9);
419 		} else {
420 			if (--char_col_left < 0) char_col_left = 0;
421 			// char_col_left = MAX(char_col_left - 1, 0);
422 		}
423 	}
424 	if (Char.charid == charid_0_kid) {
425 		x_top_row = MIN(char_top_row, prev_char_top_row);
426 		x_col_right = MAX(char_col_right, prev_char_col_right);
427 		x_col_left = MIN(char_col_left, prev_char_col_left);
428 	} else {
429 		x_top_row = char_top_row;
430 		x_col_right = char_col_right;
431 		x_col_left = char_col_left;
432 	}
433 	for (tile_row = x_top_row; tile_row <= char_bottom_row; ++tile_row) {
434 		for (tile_col = x_col_left; tile_col <= x_col_right; ++tile_col) {
435 			set_redraw_fore(get_tilepos(tile_col, tile_row), 1);
436 		}
437 	}
438 	if (Char.charid == charid_0_kid) {
439 		prev_char_top_row = char_top_row;
440 		prev_char_col_right = char_col_right;
441 		prev_char_col_left = char_col_left;
442 	}
443 }
444 
445 // seg003:0645
redraw_at_char2()446 void __pascal far redraw_at_char2() {
447 	short char_action;
448 	short char_frame;
449 	void __pascal (* redraw_func)(short, byte);
450 	char_action = Char.action;
451 	char_frame = Char.frame;
452 	redraw_func = &set_redraw2;
453 	// frames 78..80: grab
454 	if (char_frame < frame_78_jumphang || char_frame >= frame_80_jumphang) {
455 		// frames 135..149: climb up
456 		if (char_frame >= frame_137_climbing_3 && char_frame < frame_145_climbing_11) {
457 			redraw_func = &set_redraw_floor_overlay;
458 		} else {
459 			// frames 102..106: fall
460 			if (char_action != actions_2_hang_climb && char_action != actions_3_in_midair &&
461 					char_action != actions_4_in_freefall && char_action != actions_6_hang_straight &&
462 					(char_action != actions_5_bumped || char_frame < frame_102_start_fall_1 || char_frame > frame_106_fall)) {
463 				return;
464 			}
465 		}
466 	}
467 	for (tile_col = char_col_right; tile_col >= char_col_left; --tile_col) {
468 		if (char_action != 2) {
469 			redraw_func(get_tilepos(tile_col, char_bottom_row), 1);
470 		}
471 		if (char_top_row != char_bottom_row) {
472 			redraw_func(get_tilepos(tile_col, char_top_row), 1);
473 		}
474 	}
475 }
476 
477 // seg003:0706
check_knock()478 void __pascal far check_knock() {
479 	if (knock) {
480 		do_knock(Char.room, Char.curr_row - (knock>0));
481 		knock = 0;
482 	}
483 }
484 
485 // seg003:0735
timers()486 void __pascal far timers() {
487 	if (united_with_shadow > 0) {
488 		--united_with_shadow;
489 		if (united_with_shadow == 0) {
490 			--united_with_shadow;
491 		}
492 	}
493 	if (guard_notice_timer > 0) {
494 		--guard_notice_timer;
495 	}
496 	if (resurrect_time > 0) {
497 		--resurrect_time;
498 	}
499 
500 	if (fixes->fix_quicksave_during_feather) {
501 		if (is_feather_fall > 0) {
502 			--is_feather_fall;
503 			if (is_feather_fall == 0) {
504 				if (check_sound_playing()) {
505 					stop_sounds();
506 				}
507 
508 				//printf("slow fall ended at: rem_min = %d, rem_tick = %d\n", rem_min, rem_tick);
509 				//printf("length = %d ticks\n", is_feather_fall);
510 	#ifdef USE_REPLAY
511 				if (recording) special_move = MOVE_EFFECT_END;
512 	#endif
513 			}
514 		}
515 	} else {
516 		if (is_feather_fall) is_feather_fall++;
517 
518 		if (is_feather_fall && (!check_sound_playing() || is_feather_fall > 225)) {
519 			//printf("slow fall ended at: rem_min = %d, rem_tick = %d\n", rem_min, rem_tick);
520 			//printf("length = %d ticks\n", is_feather_fall);
521 	#ifdef USE_REPLAY
522 			if (recording) special_move = MOVE_EFFECT_END;
523 			if (!replaying) // during replays, feather effect gets cancelled in do_replay_move()
524 	#endif
525 			is_feather_fall = 0;
526 		}
527 	}
528 
529 	// Special event: mouse
530 	if (current_level == /*8*/ custom->mouse_level && Char.room == /*16*/ custom->mouse_room && leveldoor_open) {
531 		++leveldoor_open;
532 		// time before mouse comes: 150/12=12.5 seconds
533 		if (leveldoor_open == /*150*/ custom->mouse_delay) {
534 			do_mouse();
535 		}
536 	}
537 }
538 
539 // seg003:0798
check_mirror()540 void __pascal far check_mirror() {
541 	word clip_top;
542 	if (jumped_through_mirror == -1) {
543 		jump_through_mirror();
544 	} else {
545 		if (get_tile_at_char() == tiles_13_mirror) {
546 			loadkid();
547 			load_frame();
548 			check_mirror_image();
549 			if (distance_mirror >= 0 && custom->show_mirror_image && Char.room == drawn_room) {
550 				load_frame_to_obj();
551 				reset_obj_clip();
552 				clip_top = y_clip[Char.curr_row + 1];
553 				if (clip_top < obj_y) {
554 					obj_clip_top = clip_top;
555 					obj_clip_left = (Char.curr_col << 5) + 9;
556 					add_objtable(4); // mirror image
557 				}
558 			}
559 		}
560 	}
561 }
562 
563 // seg003:080A
jump_through_mirror()564 void __pascal far jump_through_mirror() {
565 	loadkid();
566 	load_frame();
567 	check_mirror_image();
568 	jumped_through_mirror = 0;
569 	Char.charid = charid_1_shadow;
570 	play_sound(sound_45_jump_through_mirror); // jump through mirror
571 	saveshad();
572 	guardhp_max = guardhp_curr = hitp_max;
573 	hitp_curr = 1;
574 	draw_kid_hp(1, hitp_max);
575 	draw_guard_hp(guardhp_curr, guardhp_max);
576 }
577 
578 // seg003:085B
check_mirror_image()579 void __pascal far check_mirror_image() {
580 	short distance;
581 	short xpos;
582 	xpos = x_bump[Char.curr_col + 5] + 10;
583 	distance = distance_to_edge_weight();
584 	if (Char.direction >= dir_0_right) {
585 		distance = (~distance) + 14;
586 	}
587 	distance_mirror = distance - 2;
588 	Char.x = (xpos << 1) - Char.x;
589 	Char.direction = ~Char.direction;
590 }
591 
592 // seg003:08AA
bump_into_opponent()593 void __pascal far bump_into_opponent() {
594 	// This is called from play_kid_frame, so char=Kid, Opp=Guard
595 	short distance;
596 	if (can_guard_see_kid >= 2 &&
597 		Char.sword == sword_0_sheathed && // Kid must not be in fighting pose
598 		Opp.sword != sword_0_sheathed && // but Guard must
599 		Opp.action < 2 &&
600 		Char.direction != Opp.direction // must be facing toward each other
601 	) {
602 		distance = char_opp_dist();
603 		if (ABS(distance) <= 15) {
604 
605 			#ifdef FIX_PAINLESS_FALL_ON_GUARD
606 			if (fixes->fix_painless_fall_on_guard) {
607 				if (Char.fall_y >= 33) return; // don't bump; dead
608 				else if (Char.fall_y >= 22) { // medium land
609 					take_hp(1);
610 					play_sound(sound_16_medium_land);
611 				}
612 			}
613 			#endif
614 
615 			Char.y = y_land[Char.curr_row + 1];
616 			Char.fall_y = 0;
617 			seqtbl_offset_char(seq_47_bump); // bump into opponent
618 			play_seq();
619 		}
620 	}
621 }
622 
623 // seg003:0913
pos_guards()624 void __pascal far pos_guards() {
625 	short guard_tile;
626 	short room1;
627 	for (room1 = 0; room1 < 24; ++room1) {
628 		guard_tile = level.guards_tile[room1];
629 		if (guard_tile < 30) {
630 			level.guards_x[room1] = x_bump[guard_tile % 10 + 5] + 14;
631 			level.guards_seq_hi[room1] = 0;
632 		}
633 	}
634 }
635 
636 // seg003:0959
check_can_guard_see_kid()637 void __pascal far check_can_guard_see_kid() {
638 /*
639 Possible results in can_guard_see_kid:
640 0: Guard can't see Kid
641 1: Guard can see Kid, but won't come
642 2: Guard can see Kid, and will come
643 */
644 	short kid_frame;
645 	short left_pos;
646 	short temp;
647 	short right_pos;
648 	kid_frame = Kid.frame;
649 	if (Guard.charid == charid_24_mouse) {
650 		// If the prince is fighting a guard, and the player does a quickload to a state where the prince is near the mouse, the prince would draw the sword.
651 		// The following line prevents this.
652 		can_guard_see_kid = 0;
653 		return;
654 	}
655 	if ((Guard.charid != charid_1_shadow || current_level == 12) &&
656 		// frames 217..228: going up on stairs
657 		kid_frame != 0 && (kid_frame < frame_219_exit_stairs_3 || kid_frame >= 229) &&
658 		Guard.direction != dir_56_none && Kid.alive < 0 && Guard.alive < 0 && Kid.room == Guard.room && Kid.curr_row == Guard.curr_row
659 	) {
660 		can_guard_see_kid = 2;
661 		left_pos = x_bump[Kid.curr_col + 5] + 7;
662 #ifdef FIX_DOORTOP_DISABLING_GUARD
663 		if (fixes->fix_doortop_disabling_guard) {
664 			// When the kid is hanging on the right side of a doortop, Kid.curr_col points at the doortop tile and a guard on the left side will see the prince.
665 			// This fixes that.
666 			if (Kid.action == actions_2_hang_climb || Kid.action == actions_6_hang_straight) left_pos += 14;
667 		}
668 #endif
669 		//printf("Kid.curr_col = %d, Kid.action = %d\n", Kid.curr_col, Kid.action);
670 		right_pos = x_bump[Guard.curr_col + 5] + 7;
671 		if (left_pos > right_pos) {
672 			temp = left_pos;
673 			left_pos = right_pos;
674 			right_pos = temp;
675 		}
676 		// A chomper is on the left side of a tile, so it doesn't count.
677 		if (get_tile_at_kid(left_pos) == tiles_18_chomper) {
678 			left_pos += 14;
679 		}
680 		// A gate is on the right side of a tile, so it doesn't count.
681 		if (get_tile_at_kid(right_pos) == tiles_4_gate
682 #ifdef FIX_DOORTOP_DISABLING_GUARD
683 			|| (fixes->fix_doortop_disabling_guard && (get_tile_at_kid(right_pos) == tiles_7_doortop_with_floor || get_tile_at_kid(right_pos) == tiles_12_doortop))
684 #endif
685 		) {
686 			right_pos -= 14;
687 		}
688 		if (right_pos >= left_pos) {
689 			while (left_pos <= right_pos) {
690 				// Can't see through these tiles.
691 				if (get_tile_at_kid(left_pos) == tiles_20_wall ||
692 					curr_tile2 == tiles_7_doortop_with_floor ||
693 					curr_tile2 == tiles_12_doortop
694 				) {
695 					can_guard_see_kid = 0; return;
696 				}
697 				// Can see through these, but won't go through them.
698 				if (curr_tile2 == tiles_11_loose ||
699 					curr_tile2 == tiles_18_chomper ||
700 					(curr_tile2 == tiles_4_gate && curr_room_modif[curr_tilepos] < 112) ||
701 					!tile_is_floor(curr_tile2)
702 				) {
703 					can_guard_see_kid = 1;
704 				}
705 				left_pos += 14;
706 			}
707 		}
708 	} else {
709 		can_guard_see_kid = 0;
710 	}
711 }
712 
713 // seg003:0A99
get_tile_at_kid(int xpos)714 byte __pascal far get_tile_at_kid(int xpos) {
715 	return get_tile(Kid.room, get_tile_div_mod_m7(xpos), Kid.curr_row);
716 }
717 
718 // seg003:0ABA
do_mouse()719 void __pascal far do_mouse() {
720 	loadkid();
721 	Char.charid = /*charid_24_mouse*/ custom->mouse_object;
722 	Char.x = /*200*/ custom->mouse_start_x;
723 	Char.curr_row = 0;
724 	Char.y = y_land[Char.curr_row + 1];
725 	Char.alive = -1;
726 	Char.direction = dir_FF_left;
727 	guardhp_curr = 1;
728 	seqtbl_offset_char(seq_105_mouse_forward); // mouse forward
729 	play_seq();
730 	saveshad();
731 }
732 
733 // seg003:0AFC
flash_if_hurt()734 int __pascal far flash_if_hurt() {
735 	if (flash_time != 0) {
736 		do_flash(flash_color);
737 		return 1;
738 	} else if (hitp_delta < 0) {
739 		if (is_joyst_mode && enable_controller_rumble) {
740 			if (sdl_haptic != NULL) {
741 				SDL_HapticRumblePlay(sdl_haptic, 1.0, 100); // rumble at full strength for 100 milliseconds
742 #if SDL_VERSION_ATLEAST(2,0,9)
743 			} else if (sdl_controller_ != NULL) {
744 				SDL_GameControllerRumble(sdl_controller_, 0xFFFF, 0xFFFF, 100);
745 			} else {
746 				SDL_JoystickRumble(sdl_joystick_, 0xFFFF, 0xFFFF, 100);
747 #endif
748 			}
749 		}
750 		do_flash(color_12_brightred); // red
751 		return 1;
752 	}
753 	return 0; // not flashed
754 }
755 
756 // seg003:0B1A
remove_flash_if_hurt()757 void __pascal far remove_flash_if_hurt() {
758 	if (flash_time != 0) {
759 		--flash_time;
760 	} else {
761 		if (hitp_delta >= 0) return;
762 	}
763 	remove_flash();
764 }
765