src/mess/machine/sms.c
| r9564 | r9565 | |
| 19 | 19 | |
| 20 | 20 | #define MAX_CARTRIDGES 16 |
| 21 | 21 | |
| 22 | #define LGUN_RADIUS 6 |
| 23 | #define LGUN_X_INTERVAL 4 |
| 24 | |
| 22 | 25 | typedef struct _sms_driver_data sms_driver_data; |
| 23 | 26 | struct _sms_driver_data { |
| 24 | 27 | UINT8 bios_page_count; |
| r9564 | r9565 | |
| 74 | 77 | UINT8 sports_pad_2_y; |
| 75 | 78 | |
| 76 | 79 | /* Data needed for Light Phaser */ |
| 77 | | UINT16 lphaser_latch; |
| 80 | emu_timer *lphaser_1_timer; |
| 81 | emu_timer *lphaser_2_timer; |
| 82 | UINT8 lphaser_1_latch; |
| 83 | UINT8 lphaser_2_latch; |
| 78 | 84 | int lphaser_x_offs; /* Needed to 'calibrate' lphaser; set at cart loading */ |
| 79 | 85 | |
| 80 | 86 | /* Data needed for SegaScope (3D glasses) */ |
| r9564 | r9565 | |
| 161 | 167 | } |
| 162 | 168 | } |
| 163 | 169 | |
| 164 | | static TIMER_CALLBACK( lightgun_tick ) |
| 170 | /* FIXME: this function is a hack for Light Phaser emulation. Theoretically |
| 171 | sms_vdp_hcount_latch() should be used instead, but it returns incorrect |
| 172 | position for unknown reason (timing?) */ |
| 173 | static void sms_vdp_hcount_lphaser( running_machine *machine, int hpos ) |
| 165 | 174 | { |
| 166 | | /* is there a Light Phaser in P1 port? */ |
| 167 | | if ((input_port_read_safe(machine, "CTRLSEL", 0x00) & 0x0f) == 0x01) |
| 168 | | crosshair_set_screen(machine, 0, CROSSHAIR_SCREEN_ALL); /* enable crosshair */ |
| 169 | | else |
| 170 | | crosshair_set_screen(machine, 0, CROSSHAIR_SCREEN_NONE); /* disable crosshair */ |
| 171 | | |
| 172 | | /* is there a Light Phaser in P2 port? */ |
| 173 | | if ((input_port_read_safe(machine, "CTRLSEL", 0x00) & 0xf0) == 0x10) |
| 174 | | crosshair_set_screen(machine, 1, CROSSHAIR_SCREEN_ALL); /* enable crosshair */ |
| 175 | | else |
| 176 | | crosshair_set_screen(machine, 1, CROSSHAIR_SCREEN_NONE); /* disable crosshair */ |
| 175 | running_device *smsvdp = machine->device("sms_vdp"); |
| 176 | int hpos_tmp = hpos + sms_state.lphaser_x_offs; |
| 177 | UINT8 tmp = ((hpos_tmp - 46) >> 1) & 0xff; |
| 178 | |
| 179 | //printf ("sms_vdp_hcount_lphaser: hpos %3d hpos_tmp %3d => hcount %2X\n", hpos, hpos_tmp, tmp); |
| 180 | sms_vdp_hcount_latch_w(smsvdp, 0, tmp); |
| 177 | 181 | } |
| 178 | 182 | |
| 179 | 183 | |
| 180 | | /* FIXME: this function is a hack. For Light Phaser X pos sms_vdp_hcount_latch |
| 181 | | function should be used instead, but emulation (timming?) needs to improve. */ |
| 182 | | static void sms_vdp_hcount_lphaser( running_machine *machine, int hpos) |
| 184 | /* |
| 185 | Light Phaser (light gun) emulation notes: |
| 186 | - The sensor is activated based on color brightness of some individual |
| 187 | pixels being drawn by the beam, at circular area where the gun is aiming. |
| 188 | - Currently, brightness is calculated based only on single pixels. |
| 189 | - In general, after the trigger is pressed, games draw the next frame using |
| 190 | a light color pattern, to make sure sensor will be activated. If emulation |
| 191 | skips that frame, sensor may stay deactivated. Frameskip set to 0 (no skip) is |
| 192 | recommended to avoid problems. |
| 193 | - When sensor switches from off to on, a value is latched for HCount register |
| 194 | and a flag is set. The emulation uses the flag to avoid sequential latches and |
| 195 | to signal that TH line is activated when the status of the input port is read. |
| 196 | When the status is read, the flag is cleared, or else it is cleared later when |
| 197 | the Pause status is read (end of a frame). This is necessary because the |
| 198 | "Color & Switch Test" ROM only reads the TH bit after the VINT line. |
| 199 | - The gun test of "Color & Switch Test" is an example that requires checks |
| 200 | of sensor status independent of other events, like trigger press or TH bit |
| 201 | reads. Another example is the title screen of "Hang-On & Safari Hunt", where |
| 202 | the game only reads HCount register in a loop, expecting a latch by the gun. |
| 203 | - The whole procedure is managed by a timer callback, that always reschedule |
| 204 | itself to run in some intervals when the beam is at the circular area. |
| 205 | */ |
| 206 | static int lgun_bright_aim_area( running_machine *machine, emu_timer *timer, int lgun_x, int lgun_y ) |
| 183 | 207 | { |
| 184 | | UINT8 tmp = ((hpos - 46) >> 1) & 0xff; |
| 208 | const int r_x_r = LGUN_RADIUS * LGUN_RADIUS; |
| 185 | 209 | running_device *smsvdp = machine->device("sms_vdp"); |
| 210 | screen_device *screen = screen_first(*machine); |
| 211 | const rectangle &visarea = screen->visible_area(); |
| 212 | int beam_x = screen->hpos(); |
| 213 | int beam_y = screen->vpos(); |
| 214 | int dx, dy; |
| 215 | int result = 0; |
| 216 | int pos_changed = 0; |
| 217 | double dx_circ; |
| 186 | 218 | |
| 187 | | //printf ("sms_vdp_hcount_lphaser: hpos %3d => hcount %2X\n", hpos, tmp); |
| 188 | | sms_vdp_hcount_latch_w(smsvdp, 0, tmp); |
| 219 | while (1) |
| 220 | { |
| 221 | dy = abs(beam_y - lgun_y); |
| 222 | |
| 223 | if (dy > LGUN_RADIUS || beam_y < visarea.min_y || beam_y > visarea.max_y) |
| 224 | { |
| 225 | beam_y = lgun_y - LGUN_RADIUS; |
| 226 | if (beam_y < visarea.min_y) |
| 227 | beam_y = visarea.min_y; |
| 228 | dy = abs(beam_y - lgun_y); |
| 229 | pos_changed = 1; |
| 230 | } |
| 231 | /* step 1: r^2 = dx^2 + dy^2 */ |
| 232 | /* step 2: dx^2 = r^2 - dy^2 */ |
| 233 | /* step 3: dx = sqrt(r^2 - dy^2) */ |
| 234 | dx_circ = ceil(sqrt(r_x_r - (dy * dy))); |
| 235 | dx = abs(beam_x - lgun_x); |
| 236 | |
| 237 | if (dx > dx_circ || beam_x < visarea.min_x || beam_x > visarea.max_x) |
| 238 | { |
| 239 | if (beam_x > lgun_x) |
| 240 | { |
| 241 | beam_x = 0; |
| 242 | beam_y++; |
| 243 | continue; |
| 244 | } |
| 245 | beam_x = lgun_x - dx_circ; |
| 246 | if (beam_x < visarea.min_x) |
| 247 | beam_x = visarea.min_x; |
| 248 | pos_changed = 1; |
| 249 | } |
| 250 | |
| 251 | if (!pos_changed) |
| 252 | { |
| 253 | result = sms_vdp_check_brightness(smsvdp, beam_x, beam_y); |
| 254 | /* next check at same line */ |
| 255 | beam_x += LGUN_X_INTERVAL; |
| 256 | pos_changed = 1; |
| 257 | } |
| 258 | else |
| 259 | break; |
| 260 | } |
| 261 | timer_adjust_oneshot(timer, screen_first(*machine)->time_until_pos(beam_y, beam_x), 0); |
| 262 | |
| 263 | return result; |
| 189 | 264 | } |
| 190 | 265 | |
| 191 | | |
| 192 | 266 | static UINT8 sms_vdp_hcount( running_machine *machine ) |
| 193 | 267 | { |
| 194 | 268 | UINT8 tmp; |
| r9564 | r9565 | |
| 244 | 318 | } |
| 245 | 319 | |
| 246 | 320 | |
| 247 | | static int lphaser_sensor_is_on( running_machine *machine, const char *tag_x, const char *tag_y ) |
| 321 | static void lphaser1_sensor_check( running_machine *machine ) |
| 248 | 322 | { |
| 249 | | int x = screen_hpos_nonscaled(machine->primary_screen, input_port_read(machine, tag_x)); |
| 250 | | int y = screen_vpos_nonscaled(machine->primary_screen, input_port_read(machine, tag_y)); |
| 323 | const int x = screen_hpos_nonscaled(screen_first(*machine), input_port_read(machine, "LPHASER0")); |
| 324 | const int y = screen_vpos_nonscaled(screen_first(*machine), input_port_read(machine, "LPHASER1")); |
| 251 | 325 | |
| 252 | | if (sms_vdp_area_brightness(machine->device("sms_vdp"), x, y, 60, 5) >= 0x7f) |
| 326 | if (lgun_bright_aim_area(machine, sms_state.lphaser_1_timer, x, y)) |
| 253 | 327 | { |
| 254 | | /* avoid latching hcount more than once in a line */ |
| 255 | | if (sms_state.lphaser_latch == 0) |
| 328 | if (sms_state.lphaser_1_latch == 0) |
| 256 | 329 | { |
| 257 | | sms_state.lphaser_latch = 1; |
| 258 | | //sms_vdp_hcount_latch(machine); |
| 259 | | /* FIXME: sms_vdp_hcount_lphaser() is a hack necessary while normal latch gives too inconstant shot positions */ |
| 260 | | x += sms_state.lphaser_x_offs; |
| 330 | sms_state.lphaser_1_latch = 1; |
| 261 | 331 | sms_vdp_hcount_lphaser(machine, x); |
| 262 | 332 | } |
| 263 | | return 1; |
| 264 | 333 | } |
| 334 | } |
| 265 | 335 | |
| 266 | | sms_state.lphaser_latch = 0; |
| 267 | | return 0; |
| 336 | static void lphaser2_sensor_check( running_machine *machine ) |
| 337 | { |
| 338 | const int x = screen_hpos_nonscaled(screen_first(*machine), input_port_read(machine, "LPHASER2")); |
| 339 | const int y = screen_vpos_nonscaled(screen_first(*machine), input_port_read(machine, "LPHASER3")); |
| 340 | |
| 341 | if (lgun_bright_aim_area(machine, sms_state.lphaser_2_timer, x, y)) |
| 342 | { |
| 343 | if (sms_state.lphaser_2_latch == 0) |
| 344 | { |
| 345 | sms_state.lphaser_2_latch = 1; |
| 346 | sms_vdp_hcount_lphaser(machine, x); |
| 347 | } |
| 348 | } |
| 268 | 349 | } |
| 269 | 350 | |
| 270 | 351 | |
| 352 | // at each frame we check if lightguns are enabled in one of the ports: |
| 353 | // if so, we turn on crosshair and the lightgun timer |
| 354 | static TIMER_CALLBACK( lightgun_tick ) |
| 355 | { |
| 356 | if ((input_port_read_safe(machine, "CTRLSEL", 0x00) & 0x0f) == 0x01) |
| 357 | { |
| 358 | /* enable crosshair */ |
| 359 | crosshair_set_screen(machine, 0, CROSSHAIR_SCREEN_ALL); |
| 360 | if (!timer_enabled(sms_state.lphaser_1_timer)) |
| 361 | lphaser1_sensor_check(machine); |
| 362 | } |
| 363 | else |
| 364 | { |
| 365 | /* disable crosshair */ |
| 366 | crosshair_set_screen(machine, 0, CROSSHAIR_SCREEN_NONE); |
| 367 | timer_enable(sms_state.lphaser_1_timer, 0); |
| 368 | } |
| 369 | |
| 370 | if ((input_port_read_safe(machine, "CTRLSEL", 0x00) & 0xf0) == 0x10) |
| 371 | { |
| 372 | /* enable crosshair */ |
| 373 | crosshair_set_screen(machine, 1, CROSSHAIR_SCREEN_ALL); |
| 374 | if (!timer_enabled(sms_state.lphaser_2_timer)) |
| 375 | lphaser2_sensor_check(machine); |
| 376 | } |
| 377 | else |
| 378 | { |
| 379 | /* disable crosshair */ |
| 380 | crosshair_set_screen(machine, 1, CROSSHAIR_SCREEN_NONE); |
| 381 | timer_enable(sms_state.lphaser_2_timer, 0); |
| 382 | } |
| 383 | } |
| 384 | |
| 385 | |
| 386 | static TIMER_CALLBACK( lphaser_1_callback ) |
| 387 | { |
| 388 | lphaser1_sensor_check(machine); |
| 389 | } |
| 390 | |
| 391 | |
| 392 | static TIMER_CALLBACK( lphaser_2_callback ) |
| 393 | { |
| 394 | lphaser2_sensor_check(machine); |
| 395 | } |
| 396 | |
| 397 | |
| 398 | INPUT_CHANGED( lgun1_changed ) |
| 399 | { |
| 400 | if (!sms_state.lphaser_1_timer || |
| 401 | (input_port_read_safe(field->port->machine, "CTRLSEL", 0x00) & 0x0f) != 0x01) |
| 402 | return; |
| 403 | |
| 404 | if (newval != oldval) |
| 405 | lphaser1_sensor_check(field->port->machine); |
| 406 | } |
| 407 | |
| 408 | INPUT_CHANGED( lgun2_changed ) |
| 409 | { |
| 410 | if (!sms_state.lphaser_2_timer || |
| 411 | (input_port_read_safe(field->port->machine, "CTRLSEL", 0x00) & 0xf0) != 0x10) |
| 412 | return; |
| 413 | |
| 414 | if (newval != oldval) |
| 415 | lphaser2_sensor_check(field->port->machine); |
| 416 | } |
| 417 | |
| 418 | |
| 271 | 419 | static void sms_get_inputs( address_space *space ) |
| 272 | 420 | { |
| 273 | 421 | UINT8 data = 0x00; |
| r9564 | r9565 | |
| 494 | 642 | { |
| 495 | 643 | sms_state.paused = 0; |
| 496 | 644 | } |
| 645 | |
| 646 | /* clear Light Phaser latch flags for next frame */ |
| 647 | sms_state.lphaser_1_latch = 0; |
| 648 | sms_state.lphaser_2_latch = 0; |
| 497 | 649 | } |
| 498 | 650 | |
| 499 | 651 | READ8_HANDLER( sms_input_port_0_r ) |
| r9564 | r9565 | |
| 532 | 684 | } |
| 533 | 685 | else |
| 534 | 686 | { |
| 535 | | if (sms_state.ctrl_reg & 0x02 |
| 536 | | && (input_port_read_safe(space->machine, "CTRLSEL", 0x00) & 0x0f) == 0x01 |
| 537 | | && lphaser_sensor_is_on(space->machine, "LPHASER0", "LPHASER1")) |
| 687 | if (sms_state.ctrl_reg & 0x02 && sms_state.lphaser_1_latch) |
| 538 | 688 | { |
| 539 | 689 | sms_state.input_port1 &= ~0x40; |
| 690 | sms_state.lphaser_1_latch = 0; |
| 540 | 691 | } |
| 541 | | if (sms_state.ctrl_reg & 0x08 |
| 542 | | && (input_port_read_safe(space->machine, "CTRLSEL", 0x00) & 0xf0) == 0x10 |
| 543 | | && lphaser_sensor_is_on(space->machine, "LPHASER2", "LPHASER3")) |
| 692 | |
| 693 | if (sms_state.ctrl_reg & 0x08 && sms_state.lphaser_2_latch) |
| 544 | 694 | { |
| 545 | 695 | sms_state.input_port1 &= ~0x80; |
| 696 | sms_state.lphaser_2_latch = 0; |
| 546 | 697 | } |
| 547 | 698 | } |
| 548 | 699 | |
| r9564 | r9565 | |
| 1585 | 1736 | machine->add_notifier(MACHINE_NOTIFY_EXIT, sms_machine_stop); |
| 1586 | 1737 | sms_state.rapid_fire_timer = timer_alloc(machine, rapid_fire_callback , NULL); |
| 1587 | 1738 | timer_adjust_periodic(sms_state.rapid_fire_timer, ATTOTIME_IN_HZ(10), 0, ATTOTIME_IN_HZ(10)); |
| 1739 | |
| 1740 | sms_state.lphaser_1_timer = timer_alloc(machine, lphaser_1_callback , NULL); |
| 1741 | sms_state.lphaser_2_timer = timer_alloc(machine, lphaser_2_callback , NULL); |
| 1742 | |
| 1588 | 1743 | /* Check if lightgun has been chosen as input: if so, enable crosshair */ |
| 1589 | 1744 | timer_set(machine, attotime_zero, NULL, 0, lightgun_tick); |
| 1590 | 1745 | } |
| r9564 | r9565 | |
| 1646 | 1801 | sms_state.sports_pad_2_x = 0; |
| 1647 | 1802 | sms_state.sports_pad_2_y = 0; |
| 1648 | 1803 | |
| 1649 | | sms_state.lphaser_latch = 0; |
| 1804 | sms_state.lphaser_1_latch = 0; |
| 1805 | sms_state.lphaser_2_latch = 0; |
| 1650 | 1806 | |
| 1651 | 1807 | sms_state.sscope_state = 0; |
| 1652 | 1808 | |