/*
SDLPoP, a port/conversion of the DOS game Prince of Persia.
Copyright (C) 2013-2018 Dávid Nagy
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
The authors of this program may be contacted at http://forum.princed.org
*/
#include "common.h"
// data:3D1A
sbyte distance_mirror;
// seg003:0000
void __pascal far init_game(int level) {
if(offscreen_surface) {
free_surface(offscreen_surface); // missing in original
offscreen_surface = NULL;
}
offscreen_surface = make_offscreen_buffer(&rect_top);
load_kid_sprite();
text_time_remaining = 0;
text_time_total = 0;
is_show_time = 0;
checkpoint = 0;
upside_down = 0; // N.B. upside_down is also reset in set_start_pos()
resurrect_time = 0;
if (!dont_reset_time) {
rem_min = start_minutes_left; // 60
rem_tick = start_ticks_left; // 719
hitp_beg_lev = start_hitp; // 3
}
need_level1_music = (level == 1);
play_level(level);
}
// seg003:005C
void __pascal far play_level(int level_number) {
cutscene_ptr_type cutscene_func;
#ifdef USE_COPYPROT
if (enable_copyprot && level_number == copyprot_level) {
level_number = 15;
}
#endif
for (;;) {
if (demo_mode && level_number > 2) {
start_level = -1;
need_quotes = 1;
start_game();
}
if (level_number != current_level) {
if (level_number <0 || level_number >15) {
printf("Tried to load cutscene for level %d, not in 0..15\n", level_number
);
quit(1);
}
cutscene_func = tbl_cutscenes[level_number];
if (cutscene_func != NULL
#ifdef USE_REPLAY
&& !(recording || replaying)
#endif
#ifdef USE_SCREENSHOT
&& !want_auto_screenshot()
#endif
) {
load_intro(level_number > 2, cutscene_func, 1);
}
}
if (level_number != current_level) {
load_lev_spr(level_number);
}
load_level();
pos_guards();
clear_coll_rooms();
clear_saved_ctrl();
drawn_room = 0;
mobs_count = 0;
trobs_count = 0;
next_sound = -1;
holding_sword = 0;
grab_timer = 0;
can_guard_see_kid = 0;
united_with_shadow = 0;
flash_time = 0;
leveldoor_open = 0;
demo_index = 0;
demo_time = 0;
guardhp_curr = 0;
hitp_delta = 0;
Guard.charid = charid_2_guard;
Guard.direction = dir_56_none;
do_startpos();
have_sword = (level_number != 1);
find_start_level_door();
// busy waiting?
while (check_sound_playing() && !do_paused()) idle();
stop_sounds();
#ifdef USE_REPLAY
if (replaying) replay_restore_level();
if (skipping_replay) {
if (replay_seek_target == replay_seek_0_next_room ||
replay_seek_target == replay_seek_1_next_level
)
skipping_replay = 0; // resume replay from here
}
#endif
draw_level_first();
show_copyprot(0);
level_number = play_level_2();
// hacked...
#ifdef USE_COPYPROT
if (enable_copyprot && level_number == copyprot_level && !demo_mode) {
level_number = 15;
} else {
if (level_number == 16) {
level_number = copyprot_level;
copyprot_level = -1;
}
}
#endif
free_peels();
}
}
// seg003:01A3
void __pascal far do_startpos() {
word x;
// Special event: start at checkpoint
if (current_level == 3 && checkpoint) {
level.start_dir = dir_FF_left;
level.start_room = 2;
level.start_pos = 6;
// Special event: remove loose floor
get_tile(7, 4, 0);
curr_room_tiles[curr_tilepos] = tiles_0_empty;
}
next_room = Char.room = level.start_room;
x = level.start_pos;
Char.curr_col = x % 10;
Char.curr_row = x / 10;
Char.x = x_bump[Char.curr_col + 5] + 14;
// Start in the opposite direction (and turn into the correct one).
Char.direction = ~ level.start_dir;
if (seamless == 0) {
if (current_level != 0) {
x = hitp_beg_lev;
} else {
// HP on demo level
x = 4;
}
hitp_max = hitp_curr = x;
}
if (current_level == 1) {
// Special event: press button + falling entry
get_tile(5, 2, 0);
trigger_button(0, 0, -1);
seqtbl_offset_char(seq_7_fall); // fall
} else if (current_level == 13) {
// Special event: running entry
seqtbl_offset_char(seq_84_run); // run
} else {
seqtbl_offset_char(seq_5_turn); // turn
}
set_start_pos();
}
// seg003:028A
void __pascal far set_start_pos() {
Char.y = y_land[Char.curr_row + 1];
Char.alive = -1;
Char.charid = charid_0_kid;
is_screaming = 0;
knock = 0;
upside_down = start_upside_down; // 0
is_feather_fall = 0;
Char.fall_y = 0;
Char.fall_x = 0;
offguard = 0;
Char.sword = sword_0_sheathed;
droppedout = 0;
play_seq();
if (current_level == 7 && Char.room == 17) {
// Special event: level 7 falling entry
// level 7, room 17: show room below
goto_other_room(3);
}
savekid();
}
// seg003:02E6
void __pascal far find_start_level_door() {
short tilepos;
get_room_address(Kid.room);
for (tilepos = 0; tilepos < 30; ++tilepos) {
if ((curr_room_tiles[tilepos] & 0x1F) == tiles_16_level_door_left) {
start_level_door(Kid.room, tilepos);
}
}
}
// seg003:0326
void __pascal far draw_level_first() {
screen_updates_suspended = 1;
next_room = Kid.room;
check_the_end();
if (tbl_level_type[current_level]) {
gen_palace_wall_colors();
}
draw_rect(&screen_rect, 0);
show_level();
redraw_screen(0);
draw_kid_hp(hitp_curr, hitp_max);
screen_updates_suspended = 0;
request_screen_update();
#ifdef USE_SCREENSHOT
auto_screenshot();
#endif
// Busy waiting!
start_timer(timer_1, 5);
do_simple_wait(1);
}
// seg003:037B
void __pascal far redraw_screen(int drawing_different_room) {
//remove_flash();
if (drawing_different_room) {
draw_rect(&rect_top, 0);
}
different_room = 0;
if (is_blind_mode) {
draw_rect(&rect_top, 0);
} else {
if (curr_guard_color) {
// Moved *before* drawings.
set_chtab_palette(chtab_addrs[id_chtab_5_guard], &guard_palettes[0x30 * curr_guard_color - 0x30], 0x10);
}
need_drects = 0;
redraw_room();
#ifdef USE_LIGHTING
redraw_lighting();
#endif
if (is_keyboard_mode) {
clear_kbd_buf();
}
is_blind_mode = 1;
draw_tables();
if (is_keyboard_mode) {
clear_kbd_buf();
}
#ifdef USE_COPYPROT
if (current_level == 15) {
// letters on potions level
current_target_surface = offscreen_surface;
short var_2;
for (var_2 = 0; var_2 < 14; ++var_2) {
if (copyprot_room[var_2] == drawn_room) {
set_curr_pos((copyprot_tile[var_2] % 10 << 5) + 24, copyprot_tile[var_2] / 10 * 63 + 38);
draw_text_character(copyprot_letter[cplevel_entr[var_2]]);
}
}
current_target_surface = onscreen_surface_;
}
#endif
is_blind_mode = 0;
memset_near(table_counts, 0, sizeof(table_counts));
draw_moving();
draw_tables();
if (is_keyboard_mode) {
clear_kbd_buf();
}
need_drects = 1;
if (curr_guard_color) {
//set_pal_arr(0x80, 0x10, &guard_palettes[0x30 * curr_guard_color - 0x30], 1);
}
if (upside_down) {
flip_screen(offscreen_surface);
}
copy_screen_rect(&rect_top);
if (upside_down) {
flip_screen(offscreen_surface);
}
if (is_keyboard_mode) {
clear_kbd_buf();
}
}
exit_room_timer = 2;
}
// seg003:04F8
// Returns a level number:
// - The current level if it was restarted.
// - The next level if the level was completed.
int __pascal far play_level_2() {
while (1) { // main loop
#ifdef USE_QUICKSAVE
check_quick_op();
#endif
#ifdef USE_REPLAY
if (need_replay_cycle) replay_cycle();
#endif
if (Kid.sword == sword_2_drawn) {
// speed when fighting (smaller is faster)
start_timer(timer_1, 6);
} else {
// speed when not fighting (smaller is faster)
start_timer(timer_1, 5);
}
guardhp_delta = 0;
hitp_delta = 0;
timers();
play_frame();
#ifdef USE_REPLAY
// At the exact "end of level" frame, preserve the seed to ensure reproducibility,
// regardless of how long the sound is still playing *after* this frame (torch animation modifies the seed!)
if (keep_last_seed == 1) {
preserved_seed = random_seed;
keep_last_seed = -1; // disable repeat
}
#endif
if (is_restart_level) {
is_restart_level = 0;
return current_level;
} else {
if (next_level == current_level || check_sound_playing()) {
screen_updates_suspended = 1;
draw_game_frame();
if (flash_if_hurt()) {
screen_updates_suspended = 0;
request_screen_update(); // display the flash
delay_ticks(2); // and add a short delay
}
screen_updates_suspended = 0;
remove_flash_if_hurt();
#ifdef USE_DEBUG_CHEATS
if (debug_cheats_enabled && is_timer_displayed) {
char timer_text[16];
if (rem_min < 0) {
snprintf(timer_text
, 16, "%02d:%02d:%02d", -(rem_min
+ 1), (719 - rem_tick
) / 12, (719 - rem_tick
) % 12);
} else {
snprintf(timer_text
, 16, "%02d:%02d:%02d", rem_min
- 1, rem_tick
/ 12, rem_tick
% 12);
}
screen_updates_suspended = 1;
draw_rect(&timer_rect, color_0_black);
show_text(&timer_rect, -1, -1, timer_text);
screen_updates_suspended = 0;
}
#endif
request_screen_update(); // request screen update manually
do_simple_wait(1);
} else {
stop_sounds();
hitp_beg_lev = hitp_max;
checkpoint = 0;
#ifdef USE_REPLAY
if (keep_last_seed == -1) {
random_seed = preserved_seed; // Ensure reproducibility in the next level.
keep_last_seed = 0;
}
#endif
return next_level;
}
}
}
}
// seg003:0576
void __pascal far redraw_at_char() {
short x_top_row;
short tile_col;
short tile_row;
short x_col_left;
short x_col_right;
if (Char.sword >= sword_2_drawn) {
// If char is holding sword, it makes redraw-area bigger.
if (Char.direction >= dir_0_right) {
if (++char_col_right > 9) char_col_right = 9;
// char_col_right = MIN(char_col_right + 1, 9);
} else {
if (--char_col_left < 0) char_col_left = 0;
// char_col_left = MAX(char_col_left - 1, 0);
}
}
if (Char.charid == charid_0_kid) {
x_top_row = MIN(char_top_row, prev_char_top_row);
x_col_right = MAX(char_col_right, prev_char_col_right);
x_col_left = MIN(char_col_left, prev_char_col_left);
} else {
x_top_row = char_top_row;
x_col_right = char_col_right;
x_col_left = char_col_left;
}
for (tile_row = x_top_row; tile_row <= char_bottom_row; ++tile_row) {
for (tile_col = x_col_left; tile_col <= x_col_right; ++tile_col) {
set_redraw_fore(get_tilepos(tile_col, tile_row), 1);
}
}
if (Char.charid == charid_0_kid) {
prev_char_top_row = char_top_row;
prev_char_col_right = char_col_right;
prev_char_col_left = char_col_left;
}
}
// seg003:0645
void __pascal far redraw_at_char2() {
short char_action;
short char_frame;
void __pascal (* redraw_func)(short, byte);
char_action = Char.action;
char_frame = Char.frame;
redraw_func = &set_redraw2;
// frames 78..80: grab
if (char_frame < frame_78_jumphang || char_frame >= frame_80_jumphang) {
// frames 135..149: climb up
if (char_frame >= frame_137_climbing_3 && char_frame < frame_145_climbing_11) {
redraw_func = &set_redraw_floor_overlay;
} else {
// frames 102..106: fall
if (char_action != actions_2_hang_climb && char_action != actions_3_in_midair &&
char_action != actions_4_in_freefall && char_action != actions_6_hang_straight &&
(char_action != actions_5_bumped || char_frame < frame_102_start_fall_1 || char_frame > frame_106_fall)) {
return;
}
}
}
for (tile_col = char_col_right; tile_col >= char_col_left; --tile_col) {
if (char_action != 2) {
redraw_func(get_tilepos(tile_col, char_bottom_row), 1);
}
if (char_top_row != char_bottom_row) {
redraw_func(get_tilepos(tile_col, char_top_row), 1);
}
}
}
// seg003:0706
void __pascal far check_knock() {
if (knock) {
do_knock(Char.room, Char.curr_row - (knock>0));
knock = 0;
}
}
// seg003:0735
void __pascal far timers() {
if (united_with_shadow > 0) {
--united_with_shadow;
if (united_with_shadow == 0) {
--united_with_shadow;
}
}
if (guard_notice_timer > 0) {
--guard_notice_timer;
}
if (resurrect_time > 0) {
--resurrect_time;
}
if (is_feather_fall && !check_sound_playing()) {
#ifdef USE_REPLAY
if (recording) special_move = MOVE_EFFECT_END;
if (!replaying) // during replays, feather effect gets cancelled in do_replay_move()
#endif
is_feather_fall = 0;
}
// Special event: mouse
if (current_level == 8 && Char.room == 16 && leveldoor_open) {
++leveldoor_open;
// time before mouse comes: 150/12=12.5 seconds
if (leveldoor_open == 150) {
do_mouse();
}
}
}
// seg003:0798
void __pascal far check_mirror() {
word clip_top;
if (jumped_through_mirror == -1) {
jump_through_mirror();
} else {
if (get_tile_at_char() == tiles_13_mirror) {
loadkid();
load_frame();
check_mirror_image();
if (distance_mirror >= 0) {
load_frame_to_obj();
reset_obj_clip();
clip_top = y_clip[Char.curr_row + 1];
if (clip_top < obj_y) {
obj_clip_top = clip_top;
obj_clip_left = (Char.curr_col << 5) + 9;
add_objtable(4); // mirror image
}
}
}
}
}
// seg003:080A
void __pascal far jump_through_mirror() {
loadkid();
load_frame();
check_mirror_image();
jumped_through_mirror = 0;
Char.charid = charid_1_shadow;
play_sound(sound_45_jump_through_mirror); // jump through mirror
saveshad();
guardhp_max = guardhp_curr = hitp_max;
hitp_curr = 1;
draw_kid_hp(1, hitp_max);
draw_guard_hp(guardhp_curr, guardhp_max);
}
// seg003:085B
void __pascal far check_mirror_image() {
short distance;
short xpos;
xpos = x_bump[Char.curr_col + 5] + 10;
distance = distance_to_edge_weight();
if (Char.direction >= dir_0_right) {
distance = (~distance) + 14;
}
distance_mirror = distance - 2;
Char.x = (xpos << 1) - Char.x;
Char.direction = ~Char.direction;
}
// seg003:08AA
void __pascal far bump_into_opponent() {
// This is called from play_kid_frame, so char=Kid, Opp=Guard
short distance;
if (can_guard_see_kid >= 2 &&
Char.sword == sword_0_sheathed && // Kid must not be in fighting pose
Opp.sword != sword_0_sheathed && // but Guard must
Opp.action < 2 &&
Char.direction != Opp.direction // must be facing toward each other
) {
distance = char_opp_dist();
if (ABS(distance) <= 15) {
#ifdef FIX_PAINLESS_FALL_ON_GUARD
if (fix_painless_fall_on_guard) {
if (Char.fall_y >= 33) return; // don't bump; dead
else if (Char.fall_y >= 22) { // medium land
take_hp(1);
play_sound(sound_16_medium_land);
}
}
#endif
Char.y = y_land[Char.curr_row + 1];
Char.fall_y = 0;
seqtbl_offset_char(seq_47_bump); // bump into opponent
play_seq();
}
}
}
// seg003:0913
void __pascal far pos_guards() {
short guard_tile;
short room1;
for (room1 = 0; room1 < 24; ++room1) {
guard_tile = level.guards_tile[room1];
if (guard_tile < 30) {
level.guards_x[room1] = x_bump[guard_tile % 10 + 5] + 14;
level.guards_seq_hi[room1] = 0;
}
}
}
// seg003:0959
void __pascal far check_can_guard_see_kid() {
/*
Possible results in can_guard_see_kid:
0: Guard can't see Kid
1: Guard can see Kid, but won't come
2: Guard can see Kid, and will come
*/
short kid_frame;
short left_pos;
short temp;
short right_pos;
kid_frame = Kid.frame;
if (Guard.charid == charid_24_mouse) {
return;
}
if ((Guard.charid != charid_1_shadow || current_level == 12) &&
// frames 217..228: going up on stairs
kid_frame != 0 && (kid_frame < frame_219_exit_stairs_3 || kid_frame >= 229) &&
Guard.direction != dir_56_none && Kid.alive < 0 && Guard.alive < 0 && Kid.room == Guard.room && Kid.curr_row == Guard.curr_row
) {
can_guard_see_kid = 2;
left_pos = x_bump[Kid.curr_col + 5] + 7;
right_pos = x_bump[Guard.curr_col + 5] + 7;
if (left_pos > right_pos) {
temp = left_pos;
left_pos = right_pos;
right_pos = temp;
}
// A chomper is on the left side of a tile, so it doesn't count.
if (get_tile_at_kid(left_pos) == tiles_18_chomper) {
left_pos += 14;
}
// A gate is on the right side of a tile, so it doesn't count.
if (get_tile_at_kid(right_pos) == tiles_4_gate) {
right_pos -= 14;
}
if (right_pos >= left_pos) {
while (left_pos <= right_pos) {
// Can't see through these tiles.
if (get_tile_at_kid(left_pos) == tiles_20_wall ||
curr_tile2 == tiles_7_doortop_with_floor ||
curr_tile2 == tiles_12_doortop
) {
can_guard_see_kid = 0; return;
}
// Can see through these, but won't go through them.
if (curr_tile2 == tiles_11_loose ||
curr_tile2 == tiles_18_chomper ||
(curr_tile2 == tiles_4_gate && curr_room_modif[curr_tilepos] < 112) ||
!tile_is_floor(curr_tile2)
) {
can_guard_see_kid = 1;
}
left_pos += 14;
}
}
} else {
can_guard_see_kid = 0;
}
}
// seg003:0A99
byte __pascal far get_tile_at_kid(int xpos) {
return get_tile(Kid.room, get_tile_div_mod_m7(xpos), Kid.curr_row);
}
// seg003:0ABA
void __pascal far do_mouse() {
loadkid();
Char.charid = charid_24_mouse;
Char.x = 200;
Char.curr_row = 0;
Char.y = y_land[Char.curr_row + 1];
Char.alive = -1;
Char.direction = dir_FF_left;
guardhp_curr = 1;
seqtbl_offset_char(seq_105_mouse_forward); // mouse forward
play_seq();
saveshad();
}
// seg003:0AFC
int __pascal far flash_if_hurt() {
if (flash_time != 0) {
do_flash_no_delay(flash_color); // don't add delay to the flash
return 1;
} else if (hitp_delta < 0) {
if (is_joyst_mode && enable_controller_rumble && sdl_haptic != NULL) {
SDL_HapticRumblePlay(sdl_haptic, 1.0, 100); // rumble at full strength for 100 milliseconds
}
do_flash_no_delay(color_12_brightred); // red
return 1;
}
return 0; // not flashed
}
// seg003:0B1A
void __pascal far remove_flash_if_hurt() {
if (flash_time != 0) {
--flash_time;
} else {
if (hitp_delta >= 0) return;
}
remove_flash();
}