/* Multimedia timer callback exported at 1000:eab1. */ #include "tdkpin_timer_tick.h" #include "tdkpin_arithmetic.h" #include "tdkpin_collision_records.h" #include "tdkpin_flippers.h" #include "tdkpin_gameplay_helpers.h" #include "tdkpin_mechanism_render.h" #include "tdkpin_message_pump.h" #include "tdkpin_panel_animation.h" #include "tdkpin_physics.h" #include "tdkpin_player_state.h" #include "tdkpin_real48.h" #include "tdkpin_render.h" #include "tdkpin_sound.h" #include "tdkpin_turn_state.h" enum { RECORDS_BASE = 0x095b, RECORD_BYTES = 0x53, RECORD_ACTIVE = 0x34, }; typedef struct { uint16_t claw_position; int32_t ball_x; int32_t ball_y; BorlandReal48 velocity_x_factor; BorlandReal48 velocity_y_factor; bool direct_velocity; } ClawRelease; static const ClawRelease g_claw_releases[] = { {1, 258000, 78000, {{0x80,0x9a,0x99,0x99,0x99,0x99}}, {{0,0,0,0,0,0}}, false}, {2, 259000, 81000, {{0x7f,0x66,0x66,0x66,0x66,0xe6}}, {{0x7c,0xcd,0xcc,0xcc,0xcc,0x4c}}, false}, {3, 258000, 78000, {{0x80,0x33,0x33,0x33,0x33,0xb3}}, {{0x7f,0x9a,0x99,0x99,0x99,0x19}}, false}, {4, 261000, 86000, {{0x7f,0x9a,0x99,0x99,0x99,0x99}}, {{0x7e,0xcd,0xcc,0xcc,0xcc,0x4c}}, false}, {5, 266000, 90000, {{0x7f,0x71,0x3d,0x0a,0xd7,0xa3}}, {{0x7e,0x7b,0x14,0xae,0x47,0x61}}, false}, {6, 270000, 94000, {{0x7f,0xcd,0xcc,0xcc,0xcc,0xcc}}, {{0x80,0x9a,0x99,0x99,0x99,0x19}}, false}, {7, 275000, 97000, {{0x7f,0x85,0xeb,0x51,0xb8,0x9e}}, {{0x80,0x33,0x33,0x33,0x33,0x33}}, false}, {8, 278000, 98000, {{0x7e,0xcd,0xcc,0xcc,0xcc,0xcc}}, {{0x80,0xcd,0xcc,0xcc,0xcc,0x4c}}, false}, {9, 282000, 101000, {{0x7d,0xcd,0xcc,0xcc,0xcc,0xcc}}, {{0x80,0x66,0x66,0x66,0x66,0x66}}, false}, {18, 325000, 92000, {{0,0,0,0,0,0}}, {{0,0,0,0,0,0}}, true}, }; static int32_t dgroup_i32(uint16_t offset) { return (int32_t)win16_read_u32(win16_dgroup_pointer(offset)); } static void write_dgroup_i32(uint16_t offset, int32_t value) { win16_write_u32(win16_dgroup_pointer(offset), 0, (uint32_t)value); } static uint32_t tick_count(void) { return win16_read_u32(win16_dgroup_pointer(0x0876)); } static int32_t quotient_i32(int32_t dividend, int32_t divisor) { return borland_divide_i32(dividend, divisor).quotient; } static int32_t remainder_i32(int32_t dividend, int32_t divisor) { return borland_divide_i32(dividend, divisor).remainder; } static Win16FarPtr collision_record(uint16_t id) { return win16_dgroup_pointer((uint16_t)(RECORDS_BASE + id * RECORD_BYTES)); } static void write_receiver_i32( Win16FarPtr window, uint16_t offset, int32_t value) { win16_write_u32(window, offset, (uint32_t)value); } static void refresh_region( Win16FarPtr window, bool background_b, INT16 height, INT16 width, INT16 y, INT16 x) { HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4)); HDC16 dc = GetDC16(handle); if (background_b) { tdkpin_restore_background_region_b(height, width, y, x, dc); } else { tdkpin_restore_background_region_a(height, width, y, x, dc); } tdkpin_present_background_region(height, width, y, x, dc); (void)ReleaseDC16(handle, dc); } static void draw_claw( Win16FarPtr window, uint8_t alternate, uint16_t frame) { HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4)); HDC16 dc = GetDC16(handle); tdkpin_draw_claw_frame(alternate, frame, dc); (void)ReleaseDC16(handle, dc); } static const ClawRelease *find_claw_release(uint16_t claw_position) { for (size_t index = 0; index < sizeof(g_claw_releases) / sizeof(g_claw_releases[0]); index++) { if (g_claw_releases[index].claw_position == claw_position) { return &g_claw_releases[index]; } } return NULL; } static void release_ball_from_claw( uint16_t frame_bp, Win16FarPtr window, uint16_t claw_position) { draw_claw(window, 0, claw_position); win16_write_u16(win16_dgroup_pointer(0x086f), 0, 10); win16_write_u8(win16_dgroup_pointer(0x0873), 0); win16_write_u16(win16_dgroup_pointer(0x2679), 0, 0); write_receiver_i32(window, 0x0bca, 0); for (uint16_t index = 0; index < 6; index++) { win16_write_u8( win16_far_add_offset(window, (uint16_t)(0x0bce + index)), 0); } const ClawRelease *release = find_claw_release(claw_position); if (release != NULL) { write_dgroup_i32(0x07d3, release->ball_x); write_dgroup_i32(0x07d7, release->ball_y); if (release->direct_velocity) { write_receiver_i32(window, 0x0baa, 0); write_receiver_i32(window, 0x0bae, 1000); } else { BorlandReal48 scalar = borland_i32_to_real48( (int32_t)win16_read_u32(win16_far_add_offset(window, 0x56))); write_receiver_i32( window, 0x0baa, borland_real48_round_to_i32(borland_real48_multiply( scalar, release->velocity_x_factor))); write_receiver_i32( window, 0x0bae, borland_real48_round_to_i32(borland_real48_multiply( scalar, release->velocity_y_factor))); } } tdkpin_save_ball_slot(frame_bp, 1); tdkpin_render_ball_from_caller_frame(frame_bp); tdkpin_play_sound_if_not_tilted(16); win16_write_u8(win16_far_add_offset(window, 0x0bdf), 0); } static void update_claw_state(uint16_t frame_bp, Win16FarPtr window) { if (win16_read_u8(win16_dgroup_pointer(0x07ca)) == 0 || win16_read_u8(win16_dgroup_pointer(0x0874)) != 0) { return; } uint16_t current = win16_read_u16(win16_dgroup_pointer(0x086d)); uint16_t target = win16_read_u16(win16_dgroup_pointer(0x086f)); if (current != target) { current = target > current ? (uint16_t)(current + 1u) : (uint16_t)(current - 1u); win16_write_u16(win16_dgroup_pointer(0x086d), 0, current); draw_claw( window, win16_read_u8(win16_dgroup_pointer(0x0873)), current); return; } if (current != 10) { release_ball_from_claw(frame_bp, window, current); return; } draw_claw(window, 0, 0); if (win16_read_u8(win16_dgroup_pointer(0x0873)) == 0) { win16_write_u8(win16_dgroup_pointer(0x07ca), 0); for (uint16_t id = 12; id <= 20; id++) { win16_write_u8( win16_far_add_offset(collision_record(id), RECORD_ACTIVE), 1); } } } static void update_record_countdowns( uint16_t frame_bp, Win16FarPtr window) { for (uint16_t id = 1; id <= 175; id++) { Win16FarPtr counter = win16_far_add_offset( window, (uint16_t)(0x61 + id)); uint8_t value = win16_read_u8(counter); if (value != 0) { value--; win16_write_u8(counter, value); if (value == 0) { tdkpin_restore_record_b(frame_bp, id); } } } } static void update_target_rotation(uint16_t frame_bp) { if (win16_read_u8(win16_dgroup_pointer(0x07c7)) == 0) { return; } int32_t state = dgroup_i32(0x0855); if (state < 6) { write_dgroup_i32(0x0855, (int32_t)((uint32_t)state + 1u)); tdkpin_rotate_and_draw_targets(frame_bp); } else { win16_write_u8(win16_dgroup_pointer(0x07c7), 0); } } static void publish_previous_position_from_slot( Win16FarPtr window, uint16_t slot) { uint16_t stride = (uint16_t)(slot * 4u); write_receiver_i32(window, 0x0bc2, dgroup_i32((uint16_t)(0x07e3 + stride))); write_receiver_i32(window, 0x0bc6, dgroup_i32((uint16_t)(0x07eb + stride))); } static void restore_slot_region_or_clear_suppression( uint16_t frame_bp, Win16FarPtr window, uint16_t slot) { if (win16_read_u8(win16_far_add_offset(window, 0x0be1)) == 0) { publish_previous_position_from_slot(window, slot); tdkpin_restore_previous_ball_region(frame_bp, 0); } else { win16_write_u8(win16_far_add_offset(window, 0x0be1), 0); } } static void copy_ball_slot_one_to_zero(void) { write_dgroup_i32(0x07e3, dgroup_i32(0x07e7)); write_dgroup_i32(0x07eb, dgroup_i32(0x07ef)); write_dgroup_i32(0x07f3, dgroup_i32(0x07f7)); write_dgroup_i32(0x07fb, dgroup_i32(0x07ff)); write_dgroup_i32(0x0803, dgroup_i32(0x0807)); for (uint16_t index = 0; index < 6; index++) { win16_write_u8( win16_dgroup_pointer((uint16_t)(0x080b + index)), win16_read_u8(win16_dgroup_pointer( (uint16_t)(0x0811 + index)))); } } static void collapse_finished_multiball( uint16_t frame_bp, Win16FarPtr window) { if (win16_read_u8(win16_far_add_offset(window, 0x61)) <= 1) { return; } if (win16_read_u8(win16_dgroup_pointer(0x41f9)) == 0) { restore_slot_region_or_clear_suppression(frame_bp, window, 0); win16_write_u8(win16_far_add_offset(window, 0x61), 1); win16_write_u8(win16_dgroup_pointer(0x0874), 0); win16_write_u8(win16_dgroup_pointer(0x41f9), 1); if (win16_read_u8(win16_dgroup_pointer(0x424c)) == 0) { publish_previous_position_from_slot(window, 1); tdkpin_finish_ball_or_advance_player(frame_bp); } else { copy_ball_slot_one_to_zero(); tdkpin_load_ball_slot(frame_bp, 0); win16_write_u8(win16_dgroup_pointer(0x424c), 0); } } else if (win16_read_u8(win16_dgroup_pointer(0x424c)) == 0) { restore_slot_region_or_clear_suppression(frame_bp, window, 1); win16_write_u8(win16_far_add_offset(window, 0x61), 1); win16_write_u8(win16_dgroup_pointer(0x0874), 0); tdkpin_load_ball_slot(frame_bp, 0); } } static void handle_ball_reset_request( uint16_t frame_bp, Win16FarPtr window) { if (win16_read_u8(win16_far_add_offset(window, 0x0be2)) != 1) { return; } win16_write_u8(win16_far_add_offset(window, 0x0be2), 0); tdkpin_save_ball_slot(frame_bp, 1); tdkpin_update_dynamic_ball_record(frame_bp, 2); tdkpin_reset_ball_state(frame_bp); refresh_region(window, true, 20, 148, 243, 84); tdkpin_save_ball_slot(frame_bp, 0); tdkpin_update_dynamic_ball_record(frame_bp, 1); } static void handle_multiball_spawn_request( uint16_t frame_bp, Win16FarPtr window) { if (win16_read_u8(win16_far_add_offset(window, 0x0be0)) != 1) { return; } tdkpin_save_ball_slot(frame_bp, 0); tdkpin_update_dynamic_ball_record(frame_bp, 1); write_dgroup_i32(0x07d3, dgroup_i32(0x3969)); write_dgroup_i32(0x07d7, dgroup_i32(0x396d)); write_receiver_i32(window, 0x0bc2, dgroup_i32(0x07d3)); write_receiver_i32(window, 0x0bc6, dgroup_i32(0x07d7)); tdkpin_restore_previous_ball_region(frame_bp, 0); uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1)); win16_write_u8( win16_far_add_offset(window, (uint16_t)(player * 0x14u + 0x047b)), 0); tdkpin_restore_record_b(frame_bp, 1020); write_receiver_i32(window, 0x0baa, 0); static const BorlandReal48 four_fifths = {{0x80,0xcd,0xcc,0xcc,0xcc,0x4c}}; write_receiver_i32( window, 0x0bae, borland_real48_round_to_i32(borland_real48_multiply( borland_i32_to_real48((int32_t)win16_read_u32( win16_far_add_offset(window, 0x56))), four_fifths))); write_receiver_i32(window, 0x0bca, 0); for (uint16_t index = 0; index < 6; index++) { win16_write_u8( win16_far_add_offset(window, (uint16_t)(0x0bce + index)), 0); } win16_write_u16(win16_dgroup_pointer(0x399a), 0, 2); tdkpin_save_ball_slot(frame_bp, 1); tdkpin_update_dynamic_ball_record(frame_bp, 2); win16_write_u8(win16_dgroup_pointer(0x41f9), 1); win16_write_u8(win16_dgroup_pointer(0x424c), 1); win16_write_u8(win16_far_add_offset(window, 0x61), 2); win16_write_u8(win16_dgroup_pointer(0x0874), 1); win16_write_u8(win16_far_add_offset(window, 0x0be0), 0); } static void active_ball_tick(uint16_t frame_bp, Win16FarPtr window) { update_claw_state(frame_bp, window); update_record_countdowns(frame_bp, window); update_target_rotation(frame_bp); collapse_finished_multiball(frame_bp, window); if (win16_read_u8(win16_far_add_offset(window, 0x0bd8)) == 0 && win16_read_u8(win16_far_add_offset(window, 0x0bdf)) == 0) { tdkpin_simulate_ball_and_dispatch_collision(frame_bp); } handle_ball_reset_request(frame_bp, window); handle_multiball_spawn_request(frame_bp, window); } static void update_intro_collision_visibility(uint16_t frame_bp, int32_t tick) { if (remainder_i32(tick, 2) == 0) { int32_t phase = quotient_i32(remainder_i32(tick, 32), 2); if (phase >= 0 && phase <= 7) { tdkpin_restore_record_or_item_a( frame_bp, (uint16_t)(phase + 140)); } else if (phase >= 8 && phase <= 15) { tdkpin_restore_record_b( frame_bp, (uint16_t)(phase + 132)); } } if (remainder_i32(tick, 4) == 0) { int32_t phase = quotient_i32(remainder_i32(tick, 32), 4); if (phase == 0) { tdkpin_restore_record_or_item_a(frame_bp, 1001); tdkpin_restore_record_b(frame_bp, 1002); } else if (phase >= 1 && phase <= 4) { tdkpin_restore_record_or_item_a( frame_bp, (uint16_t)(phase + 1001)); tdkpin_restore_record_b( frame_bp, (uint16_t)(phase + 1000)); } else if (phase >= 5 && phase <= 8) { tdkpin_restore_record_or_item_a( frame_bp, (uint16_t)(1009 - phase)); tdkpin_restore_record_b( frame_bp, (uint16_t)(1010 - phase)); } } int32_t phase32 = remainder_i32(tick, 32); if (phase32 == 0 || phase32 == 16) { bool background_b = phase32 == 16; uint16_t ids[] = {153, 154, 6}; for (size_t index = 0; index < sizeof(ids) / sizeof(ids[0]); index++) { if (background_b) { tdkpin_restore_record_b(frame_bp, ids[index]); } else { tdkpin_restore_record_or_item_a(frame_bp, ids[index]); } } } } static void update_intro_record_strip( Win16FarPtr window, int32_t tick) { if (remainder_i32(tick, 4) != 0) { return; } int32_t phase = quotient_i32(remainder_i32(tick, 40), 4); bool background_b = phase >= 5; int32_t normalized = background_b ? phase - 5 : phase; uint16_t id = (uint16_t)(normalized * 3 + 90); Win16FarPtr record = collision_record(id); INT16 left = (INT16)win16_read_u16( win16_far_add_offset(record, 0x4b)); INT16 top = (INT16)win16_read_u16( win16_far_add_offset(record, 0x4d)); INT16 width = (INT16)( win16_read_u16(win16_far_add_offset(record, 0x4f)) - (uint16_t)left - 10u); INT16 height = (INT16)( win16_read_u16(win16_far_add_offset(record, 0x51)) - (uint16_t)top + 1u); refresh_region(window, background_b, height, width, top, left); } static void update_intro_four_point_chase( Win16FarPtr window, int32_t tick) { if (remainder_i32(tick, 4) != 0) { return; } static const INT16 y[4] = {209, 214, 219, 224}; static const INT16 x[4] = {150, 164, 178, 192}; int32_t phase = quotient_i32(remainder_i32(tick, 16), 4); if (phase < 0 || phase > 3) { return; } HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4)); HDC16 dc = GetDC16(handle); tdkpin_restore_background_region_a(9, 9, y[phase], x[phase], dc); tdkpin_present_background_region(9, 9, y[phase], x[phase], dc); uint16_t previous = phase == 0 ? 3u : (uint16_t)(phase - 1); tdkpin_restore_background_region_b(9, 9, y[previous], x[previous], dc); tdkpin_present_background_region(9, 9, y[previous], x[previous], dc); (void)ReleaseDC16(handle, dc); } static void update_intro_item_status(Win16FarPtr window, int32_t tick) { if (remainder_i32(tick, 8) != 0) { return; } int32_t phase = quotient_i32(remainder_i32(tick, 144), 8); int16_t item = (int16_t)(phase < 10 ? phase : 18 - phase); HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4)); HDC16 dc = GetDC16(handle); tdkpin_draw_item_status(item, dc); (void)ReleaseDC16(handle, dc); } static void draw_intro_score_marquee(Win16FarPtr window) { uint32_t counter = win16_read_u32(win16_dgroup_pointer(0x087a)) + 1u; win16_write_u32(win16_dgroup_pointer(0x087a), 0, counter); HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4)); HDC16 dc = GetDC16(handle); HDC16 source = CreateCompatibleDC16(dc); HDC16 temporary = CreateCompatibleDC16(dc); HBITMAP16 bitmap = CreateCompatibleBitmap16(dc, 30, 284); HGDIOBJ16 old_source = SelectObject16( source, win16_read_u16(win16_dgroup_pointer(0x085f))); HGDIOBJ16 old_temporary = SelectObject16(temporary, bitmap); (void)BitBlt16( temporary, 0, 0, 30, 100, source, 473, 277, 0x00cc0020u); (void)BitBlt16( temporary, 0, 100, 30, 137, source, 472, 233, 0x00cc0020u); (void)SelectObject16( source, win16_read_u16(win16_dgroup_pointer(0x0861))); int32_t phase = remainder_i32((int32_t)counter, 49); if (phase > 0 && phase < 22) { (void)BitBlt16( temporary, 0, 0, 30, 237, source, (INT16)(400 + (phase - 1) * 16), 0, 0x008800c6u); } else { INT16 split = (INT16)((phase - 22) * 16); INT16 upper_height = (INT16)(237 - split); (void)BitBlt16( temporary, 0, 0, 30, upper_height, source, (INT16)(400 + (phase - 1) * 16), 0, 0x008800c6u); (void)BitBlt16( temporary, 0, upper_height, 30, split, source, 160, 0, 0x008800c6u); } (void)BitBlt16( dc, 233, 372, 30, 237, temporary, 0, 0, 0x00cc0020u); (void)SelectObject16(source, old_source); (void)SelectObject16(temporary, old_temporary); (void)DeleteObject16(bitmap); (void)DeleteDC16(source); (void)DeleteDC16(temporary); (void)ReleaseDC16(handle, dc); } static void idle_transition_tick(uint16_t frame_bp, Win16FarPtr window) { if (win16_read_u8(win16_dgroup_pointer(0x07c9)) == 0) { return; } int32_t tick = (int32_t)tick_count(); update_intro_collision_visibility(frame_bp, tick); update_intro_record_strip(window, tick); update_intro_four_point_chase(window, tick); update_intro_item_status(window, tick); if (remainder_i32(tick, 6) == 0) { draw_intro_score_marquee(window); } } static void update_panel_completion( uint16_t frame_bp, Win16FarPtr window) { if (win16_read_u8(win16_dgroup_pointer(0x07c8)) == 0 || win16_read_u8(win16_far_add_offset(window, 0x0bdd)) != 0) { return; } win16_write_u8(win16_far_add_offset(window, 0x0bdf), 1); int32_t frame = dgroup_i32(0x0851); if (frame < 281) { if (frame == 1) { for (uint16_t id = 129; id <= 133; id++) { win16_write_u16( win16_far_add_offset(collision_record(id), 0x43), 0, 0); } } write_dgroup_i32(0x0851, (int32_t)((uint32_t)frame + 1u)); tdkpin_draw_panel_animation(frame_bp); return; } win16_write_u8(win16_dgroup_pointer(0x07c8), 0); win16_write_u8(win16_far_add_offset(window, 0x0bdf), 0); write_dgroup_i32(0x0851, 0); refresh_region(window, true, 70, 68, 41, 87); uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1)); for (uint16_t item = 15; item <= 19; item++) { win16_write_u8( win16_far_add_offset( window, (uint16_t)(player * 0x14u + item + 0x0467u)), 0); } } static void initialize_game_if_requested( uint16_t frame_bp, Win16FarPtr window) { uint8_t player_count = win16_read_u8(win16_dgroup_pointer(0x07d0)); int16_t marker = (int16_t)win16_read_u16(win16_dgroup_pointer( (uint16_t)(0x081f + player_count * 2u))); if (marker != -1) { return; } tdkpin_initialize_player_state(frame_bp); tdkpin_redraw_all_players(frame_bp); tdkpin_refresh_player_progress_markers(frame_bp); tdkpin_reset_ball_state(frame_bp); refresh_region(window, true, 20, 148, 243, 84); win16_write_u32(win16_dgroup_pointer(0x0876), 0, 0); } void tdkpin_timer_tick_with_frame( uint16_t frame_bp, Win16FarPtr window) { if (win16_read_u16(win16_far_add_offset(window, 0x41)) != 0 || win16_read_u16(win16_far_add_offset(window, 0x43)) != 0 || win16_read_u16(win16_far_add_offset(window, 0x45)) != 0 || win16_read_u16(win16_far_add_offset(window, 0x47)) != 0 || win16_read_u8(win16_far_add_offset(window, 0x0bd8)) != 0 || win16_read_u8(win16_far_add_offset(window, 0x0bd9)) != 0 || win16_read_u8(win16_dgroup_pointer(0x07cc)) != 0 || win16_read_u8(win16_far_add_offset(window, 0x51)) != 0 || win16_read_u8(win16_dgroup_pointer(0x07c4)) == 0) { return; } win16_write_u8(win16_far_add_offset(window, 0x51), 1); if (win16_read_u8(win16_dgroup_pointer(0x07c5)) != 0) { for (uint16_t id = 1; id <= 175; id++) { tdkpin_refresh_collision_record(frame_bp, id); } win16_write_u8(win16_dgroup_pointer(0x07c5), 0); } win16_write_u32( win16_dgroup_pointer(0x0876), 0, tick_count() + 1u); if (win16_read_u8(win16_far_add_offset(window, 0x0bdd)) != 0) { active_ball_tick(frame_bp, window); } else { idle_transition_tick(frame_bp, window); update_panel_completion(frame_bp, window); initialize_game_if_requested(frame_bp, window); } tdkpin_update_flippers(frame_bp); tdkpin_drain_messages_except_input(); win16_write_u8(win16_far_add_offset(window, 0x51), 0); uint16_t tilt_counter = win16_read_u16( win16_far_add_offset(window, 0x5e)); if (tilt_counter != 0) { /* 1000:fd73 reads game_tilted but both branches converge here. */ (void)win16_read_u8(win16_dgroup_pointer(0x07c6)); win16_write_u16(window, 0x5e, (uint16_t)(tilt_counter - 1u)); } } void tdkpin_timer_tick( Win16FarPtr window, uint32_t ignored_callback_argument) { (void)ignored_callback_argument; tdkpin_timer_tick_with_frame(win16_current_stack_pointer(), window); }