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:432F
24 sbyte bump_col_left_of_wall;
25 // data:436E
26 sbyte bump_col_right_of_wall;
27 // data:4C0A
28 sbyte right_checked_col;
29 // data:408A
30 sbyte left_checked_col;
31
32
33 // data:4C0C
34 short coll_tile_left_xpos;
35 // These two arrays are indexed with the return value of wall_type.
36 // data:24BA
37 const sbyte wall_dist_from_left[] = {0, 10, 0, -1, 0, 0};
38 // data:24C0
39 const sbyte wall_dist_from_right[] = {0, 0, 10, 13, 0, 0};
40
41 // seg004:0004
check_collisions()42 void __pascal far check_collisions() {
43 short column;
44 bump_col_left_of_wall = bump_col_right_of_wall = -1;
45 if (Char.action == actions_7_turn) return;
46 collision_row = Char.curr_row;
47 move_coll_to_prev();
48 prev_collision_row = collision_row;
49 right_checked_col = MIN(get_tile_div_mod_m7(char_x_right_coll) + 2, 11);
50 left_checked_col = get_tile_div_mod_m7(char_x_left_coll) - 1;
51 get_row_collision_data(collision_row , curr_row_coll_room, curr_row_coll_flags);
52 get_row_collision_data(collision_row + 1, below_row_coll_room, below_row_coll_flags);
53 get_row_collision_data(collision_row - 1, above_row_coll_room, above_row_coll_flags);
54 for (column = 9; column >= 0; --column) {
55 if (curr_row_coll_room[column] >= 0 &&
56 prev_coll_room[column] == curr_row_coll_room[column]
57 ) {
58 // char bumps into left of wall
59 if (
60 (prev_coll_flags[column] & 0x0F) == 0 &&
61 (curr_row_coll_flags[column] & 0x0F) != 0
62 ) {
63 bump_col_left_of_wall = column;
64 }
65 // char bumps into right of wall
66 if (
67 (prev_coll_flags[column] & 0xF0) == 0 &&
68 (curr_row_coll_flags[column] & 0xF0) != 0
69 ) {
70 bump_col_right_of_wall = column;
71 }
72 }
73 }
74 }
75
76 // seg004:00DF
move_coll_to_prev()77 void __pascal far move_coll_to_prev() {
78 sbyte* row_coll_room_ptr;
79 byte* row_coll_flags_ptr;
80 short column;
81 if (collision_row == prev_collision_row ||
82 collision_row + 3 == prev_collision_row ||
83 collision_row - 3 == prev_collision_row
84 ) {
85 row_coll_room_ptr = curr_row_coll_room;
86 row_coll_flags_ptr = curr_row_coll_flags;
87 } else if (
88 collision_row + 1 == prev_collision_row ||
89 collision_row - 2 == prev_collision_row
90 ) {
91 row_coll_room_ptr = above_row_coll_room;
92 row_coll_flags_ptr = above_row_coll_flags;
93 } else {
94 row_coll_room_ptr = below_row_coll_room;
95 row_coll_flags_ptr = below_row_coll_flags;
96 }
97 for (column = 0; column < 10; ++column) {
98 prev_coll_room[column] = row_coll_room_ptr[column];
99 prev_coll_flags[column] = row_coll_flags_ptr[column];
100 below_row_coll_room[column] = -1;
101 above_row_coll_room[column] = -1;
102 curr_row_coll_room[column] = -1;
103 #ifdef FIX_COLL_FLAGS
104 // bugfix:
105 curr_row_coll_flags[column] = 0;
106 below_row_coll_flags[column] = 0;
107 above_row_coll_flags[column] = 0;
108 #endif
109 }
110 }
111
112 // seg004:0185
get_row_collision_data(short row,sbyte * row_coll_room_ptr,byte * row_coll_flags_ptr)113 void __pascal far get_row_collision_data(short row, sbyte *row_coll_room_ptr, byte *row_coll_flags_ptr) {
114 short right_wall_xpos;
115 byte curr_flags;
116 short room;
117 short column;
118 short left_wall_xpos;
119 room = Char.room;
120 coll_tile_left_xpos = x_bump[left_checked_col + 5] + 7;
121 for (column = left_checked_col; column <= right_checked_col; ++column) {
122 left_wall_xpos = get_left_wall_xpos(room, column, row);
123 right_wall_xpos = get_right_wall_xpos(room, column, row);
124 // char bumps into left of wall
125 curr_flags = (left_wall_xpos < char_x_right_coll) * 0x0F;
126 // char bumps into right of wall
127 curr_flags |= (right_wall_xpos > char_x_left_coll) * 0xF0;
128 row_coll_flags_ptr[tile_col] = curr_flags;
129 row_coll_room_ptr[tile_col] = curr_room;
130 coll_tile_left_xpos += 14;
131 }
132 }
133
134 // seg004:0226
get_left_wall_xpos(int room,int column,int row)135 int __pascal far get_left_wall_xpos(int room,int column,int row) {
136 short type;
137 type = wall_type(get_tile(room, column, row));
138 if (type) {
139 return wall_dist_from_left[type] + coll_tile_left_xpos;
140 } else {
141 return 0xFF;
142 }
143 }
144
145 // seg004:025F
get_right_wall_xpos(int room,int column,int row)146 int __pascal far get_right_wall_xpos(int room,int column,int row) {
147 short type;
148 type = wall_type(get_tile(room, column, row));
149 if (type) {
150 return coll_tile_left_xpos - wall_dist_from_right[type] + 13;
151 } else {
152 return 0;
153 }
154 }
155
156 // seg004:029D
check_bumped()157 void __pascal far check_bumped() {
158 if (
159 Char.action != actions_2_hang_climb &&
160 Char.action != actions_6_hang_straight &&
161 // frames 135..149: climb up
162 (Char.frame < frame_135_climbing_1 || Char.frame >= 149)
163 ) {
164 #ifdef FIX_TWO_COLL_BUG
165 if (bump_col_left_of_wall >= 0) {
166 check_bumped_look_right();
167 if (!fixes->fix_two_coll_bug) return; // check for the left-oriented collision only with the fix enabled
168 }
169 if (bump_col_right_of_wall >= 0) {
170 check_bumped_look_left();
171 }
172 #else
173 if (bump_col_left_of_wall >= 0) {
174 check_bumped_look_right();
175 }
176 else
177 if (bump_col_right_of_wall >= 0) {
178 check_bumped_look_left();
179 }
180 #endif // FIX_TWO_COLL_BUG
181
182 }
183 }
184
185 // seg004:02D2
check_bumped_look_left()186 void __pascal far check_bumped_look_left() {
187 if ((Char.sword == sword_2_drawn || Char.direction < dir_0_right) && // looking left
188 is_obstacle_at_col(bump_col_right_of_wall)
189 ) {
190 bumped(get_right_wall_xpos(curr_room, tile_col, tile_row) - char_x_left_coll, dir_0_right);
191 }
192 }
193
194 // seg004:030A
check_bumped_look_right()195 void __pascal far check_bumped_look_right() {
196 if ((Char.sword == sword_2_drawn || Char.direction == dir_0_right) && // looking right
197 is_obstacle_at_col(bump_col_left_of_wall)
198 ) {
199 bumped(get_left_wall_xpos(curr_room, tile_col, tile_row) - char_x_right_coll, dir_FF_left);
200 }
201 }
202
203 // seg004:0343
is_obstacle_at_col(int tile_col)204 int __pascal far is_obstacle_at_col(int tile_col) {
205 short tile_row;
206 tile_row = Char.curr_row;
207 if (tile_row < 0) {
208 tile_row += 3;
209 }
210 if (tile_row >= 3) {
211 tile_row -= 3;
212 }
213 get_tile(curr_row_coll_room[tile_col], tile_col, tile_row);
214 return is_obstacle();
215 }
216
217 // seg004:037E
is_obstacle()218 int __pascal far is_obstacle() {
219 if (curr_tile2 == tiles_10_potion) {
220 return 0;
221 } else if (curr_tile2 == tiles_4_gate) {
222 if (! can_bump_into_gate()) return 0;
223 } else if (curr_tile2 == tiles_18_chomper) {
224 // is the chomper closed?
225 if (curr_room_modif[curr_tilepos] != 2) return 0;
226 } else if (
227 curr_tile2 == tiles_13_mirror &&
228 Char.charid == charid_0_kid &&
229 Char.frame >= frame_39_start_run_jump_6 && Char.frame < frame_44_running_jump_5 && // run-jump
230 Char.direction < dir_0_right // right-to-left only
231 ) {
232 curr_room_modif[curr_tilepos] = 0x56; // broken mirror or what?
233 jumped_through_mirror = -1;
234 return 0;
235 }
236 coll_tile_left_xpos = xpos_in_drawn_room(x_bump[tile_col + 5]) + 7;
237 return 1;
238 }
239
240 // seg004:0405
xpos_in_drawn_room(int xpos)241 int __pascal far xpos_in_drawn_room(int xpos) {
242 if (curr_room != drawn_room) {
243 if (curr_room == room_L || curr_room == room_BL) {
244 xpos -= 140;
245 } else if (curr_room == room_R || curr_room == room_BR) {
246 xpos += 140;
247 }
248 }
249 return xpos;
250 }
251
252 // seg004:0448
bumped(sbyte delta_x,sbyte push_direction)253 void __pascal far bumped(sbyte delta_x,sbyte push_direction) {
254 // frame 177: spiked
255 if (Char.alive < 0 && Char.frame != frame_177_spiked) {
256 Char.x += delta_x;
257 if (push_direction < dir_0_right) {
258 // pushing left
259 if (curr_tile2 == tiles_20_wall) {
260 get_tile(curr_room, --tile_col, tile_row);
261 }
262 } else {
263 // pushing right
264 if (curr_tile2 == tiles_12_doortop ||
265 curr_tile2 == tiles_7_doortop_with_floor ||
266 curr_tile2 == tiles_20_wall
267 ) {
268 ++tile_col;
269 if (curr_room == 0 && tile_col == 10) {
270 curr_room = Char.room;
271 tile_col = 0;
272 }
273 get_tile(curr_room, tile_col, tile_row);
274 }
275 }
276 if (tile_is_floor(curr_tile2)) {
277 bumped_floor(push_direction);
278 } else {
279 bumped_fall();
280 }
281 }
282 }
283
284 // seg004:04E4
bumped_fall()285 void __pascal far bumped_fall() {
286 short action;
287 action = Char.action;
288 Char.x = char_dx_forward(-4);
289 if (action == actions_4_in_freefall) {
290 Char.fall_x = 0;
291 } else {
292 seqtbl_offset_char(seq_45_bumpfall); // fall after bumped
293 play_seq();
294 }
295 bumped_sound();
296 }
297
298 // seg004:0520
bumped_floor(sbyte push_direction)299 void __pascal far bumped_floor(sbyte push_direction) {
300 short frame;
301 short seq_index;
302 if (Char.sword != sword_2_drawn && (word)(y_land[Char.curr_row + 1] - Char.y) >= (word)15) {
303 bumped_fall();
304 } else {
305 Char.y = y_land[Char.curr_row + 1];
306 if (Char.fall_y >= 22) {
307 Char.x = char_dx_forward(-5);
308 } else {
309 Char.fall_y = 0;
310 if (Char.alive) {
311 if (Char.sword == sword_2_drawn) {
312 if (push_direction == Char.direction) {
313 seqtbl_offset_char(seq_65_bump_forward_with_sword); // pushed forward with sword (Kid)
314 play_seq();
315 Char.x = char_dx_forward(1);
316 return;
317 } else {
318 seq_index = seq_64_pushed_back_with_sword; // pushed back with sword
319 }
320 } else {
321 frame = Char.frame;
322 if (frame == 24 || frame == 25 ||
323 (frame >= 40 && frame < 43) ||
324 (frame >= frame_102_start_fall_1 && frame < 107)
325 ) {
326 seq_index = seq_46_hardbump; // bump into wall after run-jump (crouch)
327 } else {
328 seq_index = seq_47_bump; // bump into wall
329 }
330 }
331 seqtbl_offset_char(seq_index);
332 play_seq();
333 bumped_sound();
334 }
335 }
336 }
337 }
338
339 // seg004:05F1
bumped_sound()340 void __pascal far bumped_sound() {
341 is_guard_notice = 1;
342 play_sound(sound_8_bumped); // touching a wall
343 }
344
345 // seg004:0601
clear_coll_rooms()346 void __pascal far clear_coll_rooms() {
347 memset_near(prev_coll_room, -1, sizeof(prev_coll_room));
348 memset_near(curr_row_coll_room, -1, sizeof(curr_row_coll_room));
349 memset_near(below_row_coll_room, -1, sizeof(below_row_coll_room));
350 memset_near(above_row_coll_room, -1, sizeof(above_row_coll_room));
351 #ifdef FIX_COLL_FLAGS
352 // workaround
353 memset_near(prev_coll_flags, 0, sizeof(prev_coll_flags));
354 memset_near(curr_row_coll_flags, 0, sizeof(curr_row_coll_flags));
355 memset_near(below_row_coll_flags, 0, sizeof(below_row_coll_flags));
356 memset_near(above_row_coll_flags, 0, sizeof(above_row_coll_flags));
357 #endif
358 prev_collision_row = -1;
359 }
360
361 // seg004:0657
can_bump_into_gate()362 int __pascal far can_bump_into_gate() {
363 return (curr_room_modif[curr_tilepos] >> 2) + 6 < char_height;
364 }
365
366 // seg004:067C
get_edge_distance()367 int __pascal far get_edge_distance() {
368 /*
369 Possible results in edge_type:
370 0: closer/sword/potion
371 1: edge
372 2: floor (nothing near char)
373 */
374 short distance;
375 byte tiletype;
376 determine_col();
377 load_frame_to_obj();
378 set_char_collision();
379 tiletype = get_tile_at_char();
380 if (wall_type(tiletype) != 0) {
381 tile_col = Char.curr_col;
382 distance = dist_from_wall_forward(tiletype);
383 if (distance >= 0) {
384 loc_59DD:
385 if (distance < 14) {
386 edge_type = 1;
387 } else {
388 edge_type = 2;
389 distance = 11;
390 }
391 } else {
392 goto loc_59E8;
393 }
394 } else {
395 loc_59E8:
396 tiletype = get_tile_infrontof_char();
397 if (tiletype == tiles_12_doortop && Char.direction >= dir_0_right) {
398 loc_59FB:
399 edge_type = 0;
400 distance = distance_to_edge_weight();
401 } else {
402 if (wall_type(tiletype) != 0) {
403 tile_col = infrontx;
404 distance = dist_from_wall_forward(tiletype);
405 if (distance >= 0) goto loc_59DD;
406 }
407 if (tiletype == tiles_11_loose) goto loc_59FB;
408 if (
409 tiletype == tiles_6_closer ||
410 tiletype == tiles_22_sword ||
411 tiletype == tiles_10_potion
412 ) {
413 distance = distance_to_edge_weight();
414 if (distance != 0) {
415 edge_type = 0;
416 } else {
417 edge_type = 2;
418 distance = 11;
419 }
420 } else {
421 if (tile_is_floor(tiletype)) {
422 edge_type = 2;
423 distance = 11;
424 } else {
425 goto loc_59FB;
426 }
427 }
428 }
429 }
430 curr_tile2 = tiletype;
431 return distance;
432 }
433
434 // seg004:076B
check_chomped_kid()435 void __pascal far check_chomped_kid() {
436 short tile_col;
437 short tile_row;
438 tile_row = Char.curr_row;
439 for (tile_col = 0; tile_col < 10; ++tile_col) {
440 if (curr_row_coll_flags[tile_col] == 0xFF &&
441 get_tile(curr_row_coll_room[tile_col], tile_col, tile_row) == tiles_18_chomper &&
442 (curr_room_modif[curr_tilepos] & 0x7F) == 2 // closed chomper
443 ) {
444 chomped();
445 }
446 }
447 }
448
449 // seg004:07BF
chomped()450 void __pascal far chomped() {
451 #ifdef FIX_SKELETON_CHOMPER_BLOOD
452 if (!(fixes->fix_skeleton_chomper_blood && Char.charid == charid_4_skeleton))
453 #endif
454 curr_room_modif[curr_tilepos] |= 0x80; // put blood
455 if (Char.frame != frame_178_chomped && Char.room == curr_room) {
456 #ifdef FIX_OFFSCREEN_GUARDS_DISAPPEARING
457 // a guard can get teleported to the other side of kid's room
458 // when hitting a chomper in another room
459 if (fixes->fix_offscreen_guards_disappearing) {
460 short chomper_col = tile_col;
461 if (curr_room != Char.room) {
462 if (curr_room == level.roomlinks[Char.room - 1].right) {
463 chomper_col += 10;
464 } else if (curr_room == level.roomlinks[Char.room - 1].left) {
465 chomper_col -= 10;
466 }
467 }
468 Char.x = x_bump[chomper_col + 5] + 7;
469 } else {
470 #endif
471 Char.x = x_bump[tile_col + 5] + 7;
472 #ifdef FIX_OFFSCREEN_GUARDS_DISAPPEARING
473 }
474 #endif
475 Char.x = char_dx_forward(7 - !Char.direction);
476 Char.y = y_land[Char.curr_row + 1];
477 take_hp(100);
478 play_sound(sound_46_chomped); // something chomped
479 seqtbl_offset_char(seq_54_chomped); // chomped
480 play_seq();
481 }
482 }
483
484 // seg004:0833
check_gate_push()485 void __pascal far check_gate_push() {
486 // Closing gate pushes Kid
487 short frame;
488 short orig_col;
489 frame = Char.frame;
490 if (Char.action == actions_7_turn ||
491 frame == frame_15_stand || // stand
492 (frame >= frame_108_fall_land_2 && frame < 111) // crouch
493 ) {
494 get_tile_at_char();
495 orig_col = tile_col;
496 int orig_room = curr_room;
497 if ((curr_tile2 == tiles_4_gate ||
498 get_tile(curr_room, --tile_col, tile_row) == tiles_4_gate) &&
499 (curr_row_coll_flags[tile_col] & prev_coll_flags[tile_col]) == 0xFF &&
500 can_bump_into_gate()
501 ) {
502 bumped_sound();
503 #ifdef FIX_CAPED_PRINCE_SLIDING_THROUGH_GATE
504 if (fixes->fix_caped_prince_sliding_through_gate) {
505 // If get_tile() changed curr_room from orig_room to the left neighbor of orig_room (because tile_col was outside room orig_room),
506 // then change tile_col (and curr_room) so that orig_col and tile_col are meant in the same room.
507 if (curr_room == level.roomlinks[orig_room - 1].left) {
508 tile_col -= 10;
509 curr_room = orig_room;
510 }
511 }
512 #endif
513 //printf("check_gate_push: orig_col = %d, tile_col = %d, curr_room = %d, Char.room = %d, orig_room = %d\n", orig_col, tile_col, curr_room, Char.room, orig_room);
514 // push Kid left if orig_col <= tile_col, gate at char's tile
515 // push Kid right if orig_col > tile_col, gate is left from char's tile
516 Char.x += 5 - (orig_col <= tile_col) * 10;
517 }
518 }
519 }
520
521 // seg004:08C3
check_guard_bumped()522 void __pascal far check_guard_bumped() {
523 if (
524 Char.action == actions_1_run_jump &&
525 Char.alive < 0 &&
526 Char.sword >= sword_2_drawn
527 ) {
528 if (
529
530 #ifdef FIX_PUSH_GUARD_INTO_WALL
531 // Should also check for a wall BEHIND the guard, instead of only the current tile
532 (fixes->fix_push_guard_into_wall && get_tile_behind_char() == tiles_20_wall) ||
533 #endif
534
535 get_tile_at_char() == tiles_20_wall ||
536 curr_tile2 == tiles_7_doortop_with_floor ||
537 (curr_tile2 == tiles_4_gate && can_bump_into_gate()) ||
538 (Char.direction >= dir_0_right && (
539 get_tile(curr_room, --tile_col, tile_row) == tiles_7_doortop_with_floor ||
540 (curr_tile2 == tiles_4_gate && can_bump_into_gate())
541 ))
542 ) {
543 load_frame_to_obj();
544 set_char_collision();
545 if (is_obstacle()) {
546 short delta_x;
547 delta_x = dist_from_wall_behind(curr_tile2);
548 if (delta_x < 0 && delta_x > -13) {
549 Char.x = char_dx_forward(-delta_x);
550 seqtbl_offset_char(seq_65_bump_forward_with_sword); // pushed to wall with sword (Guard)
551 play_seq();
552 load_fram_det_col();
553 }
554 }
555 }
556 }
557 }
558
559 // seg004:0989
check_chomped_guard()560 void __pascal far check_chomped_guard() {
561 get_tile_at_char();
562 if ( ! check_chomped_here()) {
563 get_tile(curr_room, ++tile_col, tile_row);
564 check_chomped_here();
565 }
566 }
567
568 // seg004:09B0
check_chomped_here()569 int __pascal far check_chomped_here() {
570 if (curr_tile2 == tiles_18_chomper &&
571 (curr_room_modif[curr_tilepos] & 0x7F) == 2
572 ) {
573 coll_tile_left_xpos = x_bump[tile_col + 5] + 7;
574 if (get_left_wall_xpos(curr_room, tile_col, tile_row) < char_x_right_coll &&
575 get_right_wall_xpos(curr_room, tile_col, tile_row) > char_x_left_coll
576 ) {
577 chomped();
578 return 1;
579 } else {
580 return 0;
581 }
582 } else {
583 return 0;
584 }
585 }
586
587 // seg004:0A10
dist_from_wall_forward(byte tiletype)588 int __pascal far dist_from_wall_forward(byte tiletype) {
589 short type;
590 if (tiletype == tiles_4_gate && ! can_bump_into_gate()) {
591 return -1;
592 } else {
593 coll_tile_left_xpos = x_bump[tile_col + 5] + 7;
594 type = wall_type(tiletype);
595 if (type == 0) return -1;
596 if (Char.direction < dir_0_right) {
597 // looking left
598 //return wall_dist_from_right[type] + char_x_left_coll - coll_tile_left_xpos - 13;
599 return char_x_left_coll - (coll_tile_left_xpos + 13 - wall_dist_from_right[type]);
600 } else {
601 // looking right
602 return wall_dist_from_left[type] + coll_tile_left_xpos - char_x_right_coll;
603 }
604 }
605 }
606
607 // seg004:0A7B
dist_from_wall_behind(byte tiletype)608 int __pascal far dist_from_wall_behind(byte tiletype) {
609 short type;
610 type = wall_type(tiletype);
611 if (type == 0) {
612 return 99;
613 } else {
614 if (Char.direction >= dir_0_right) {
615 // looking right
616 //return wall_dist_from_right[type] + char_x_left_coll - coll_tile_left_xpos - 13;
617 return char_x_left_coll - (coll_tile_left_xpos + 13 - wall_dist_from_right[type]);
618 } else {
619 // looking left
620 return wall_dist_from_left[type] + coll_tile_left_xpos - char_x_right_coll;
621 }
622 }
623 }
624