[GUN]: added two players support, partially based on LilaQ PR, but more optimized.

Co-authored-by: LilaQ [email protected]

Resolves #174, #175 and fixes https://github.com/libretro/libretro-cap32/issues/142
This commit is contained in:
D_SkywalkandLilaQ [email protected] committed 2026-10-06 19:17:02 +02:00
1 parent c23855092f
commit b8b22931aa
10 files changed
+158 -107

No files matched your search

+1 -1
View File
@@ -28,4 +28,4 @@ external/cmocka/cmocka-lib
tests/
tests-db/
unit-tests/test-db
unit-tests/autorun
unit-tests/test-dsk
+3 -3
View File
@@ -375,7 +375,7 @@ t_disk_format disk_format[MAX_DISK_FORMAT] = {
{ "169K Vendor Format", 40, 1, 9, 2, 0x52, 0xe5, {{ 0x41, 0x46, 0x42, 0x47, 0x43, 0x48, 0x44, 0x49, 0x45 }} }
};
t_lightgun gun = { 0, 0, GUN_SLEEP, 0 };
t_lightgun gun[2] = {{ 0, 0, GUN_SLEEP, 0 }, { 0, 0, GUN_SLEEP, 0 }};
#define psg_write \
{ \
@@ -516,8 +516,7 @@ uint8_t z80_IN_handler (reg_pair port)
if (PSG.reg_select == 14) { // PSG port A?
if (!(PSG.RegisterAY.Index[7] & 0x40)) { // port A in input mode?
ret_val = keyboard_matrix[CPC.keyboard_line & 0x0f]; // read keyboard matrix node status
if ((CPC.keyboard_line & 0x0f) == 9) { //read line 9 GunStick & state != sleep
if ((1 << (CPC.keyboard_line & 0x0f)) & 0x0240) { // joystick gunstick two inputs using bit lines (9 => 0x200 / 6 => 0x40)
//checking and return gun value
ret_val &= CPC.gun_IN
? CPC.gun_IN()
@@ -1564,6 +1563,7 @@ int video_init (void)
CPC.scr_bpp = retro_getGfxBpp();
CPC.scr_density = 2 >> (CPC.scr_bpp >> 4);
printf("\n>>>>>>> b:%u d:%u\n", CPC.scr_bpp, CPC.scr_density);
CPC.scr_style = retro_getStyle();
CPC.scr_bps = retro_getGfxBps();
CPC.scr_pos = CPC.scr_base = retro_getScreenPtr();
+89 -48
View File
@@ -53,16 +53,13 @@
#include "retro_gun.h"
extern uint32_t * video_buffer;
extern uint8_t keyboard_matrix[16];
extern t_CPC CPC;
typedef struct{
int x, y;
unsigned int timer;
} t_light;
t_light light = {0,0,0};
void ev_lightgun();
static t_light light[2];
#define GUNSTICK_NONE 0xff
#define GUNSTICK_HIT 0xfd
@@ -71,42 +68,81 @@ void ev_lightgun();
#define GUNSTICK_FIRE_MASK 0x10
#define GUNSTICK_TIMER 4
/**
* TODO:
* I'm using the same code I used on Wii (Wiituka) &
* It could be unified with the pointer detection in PHASER code that Colin has done.
**/
static unsigned gunstick_luminance(uint32_t pixel)
{
unsigned r, g, b;
switch (retro_video.depth) {
case DEPTH_16BPP:
r = (pixel >> 11) & 0x1F; r = (r << 3) | (r >> 2);
g = (pixel >> 5) & 0x3F; g = (g << 2) | (g >> 4);
b = pixel & 0x1F; b = (b << 3) | (b >> 2);
break;
case DEPTH_8BPP:
r = ((pixel >> 5) & 0x07) * 36;
g = ((pixel >> 2) & 0x07) * 36;
b = (pixel & 0x03) * 85;
break;
default:
r = (pixel >> 16) & 0xFF;
g = (pixel >> 8) & 0xFF;
b = pixel & 0xFF;
break;
}
return (299 * r) + (587 * g) + (114 * b);
}
void gunstick_reset(void)
{
memset(light, 0, sizeof(light));
/* Guillermo Tell uses a light-yellow flash. Keep grey targets (Solo)
* and allow a black sample followed by a flash (Mike Gunner). */
lightgun_cfg.threshold_color = gunstick_luminance(lightgun_cfg.luminance_color);
}
uint32_t _gunstick_get_screen(int x, int y)
{
return *(
(video_buffer + (x >> retro_video.raw_density_byte)) +
(y * retro_video.bps)
);
if ((unsigned)x >= (unsigned)EMULATION_SCREEN_WIDTH ||
(unsigned)y >= (unsigned)EMULATION_SCREEN_HEIGHT)
return 0;
int index = y * EMULATION_SCREEN_WIDTH + x - 1;
switch (retro_video.depth) {
case DEPTH_8BPP:
return ((uint8_t *)video_buffer)[index];
case DEPTH_16BPP:
return ((uint16_t *)video_buffer)[index];
default:
return video_buffer[index];
}
}
bool _gunstick_check(void)
bool _gunstick_check(unsigned port)
{
uint32_t gcolor;
uint32_t gcolor, luminance;
if (gun.state == GUN_XYGET)
if (gun[port].state == GUN_XYGET)
{
light.x = gun.x;
light.y = gun.y;
gun.state = GUN_SSEND;
light[port].x = gun[port].x;
light[port].y = gun[port].y;
gun[port].state = GUN_SSEND;
}
gcolor = _gunstick_get_screen(light.x, light.y);
gcolor = _gunstick_get_screen(light[port].x, light[port].y);
luminance = gunstick_luminance(gcolor);
#ifdef DEBUG_GUNSTICK
printf("gunstick: 0x%X (%u,%u) [0x%X]\n", gcolor, light.x, light.y, lightgun_cfg.whitecolor);
printf("gunstick: 0x%X[0x%X] (%u,%u) [0x%X]\n", gcolor, luminance, light[port].x, light[port].y, lightgun_cfg.threshold_color);
#endif
if (gcolor == GUNSTICK_PREPARE_COLOR)
gun.state = GUN_PREPARE;
if ((gun.state != GUN_PREPARE) && (gcolor == lightgun_cfg.whitecolor))
if ( luminance > lightgun_cfg.threshold_color)
{
gun.state = GUN_SLEEP;
gun[port].state = GUN_SLEEP;
return true;
}
@@ -115,43 +151,48 @@ bool _gunstick_check(void)
void gunstick_emulator_update(void)
{
if (light.timer)
light.timer --;
ev_lightgun();
// send joy fire on gunstick pressed and update state
if (gun.pressed)
{
keyboard_matrix[GUNSTICK_FIRE_KEYCODE >> 4] &= ~GUNSTICK_FIRE_MASK;
gun.state = GUN_SHOOT;
} else {
keyboard_matrix[GUNSTICK_FIRE_KEYCODE >> 4] |= GUNSTICK_FIRE_MASK;
for (unsigned port = 0; port < 2; port++) {
if (light[port].timer)
light[port].timer--;
ev_lightgun(port);
if (gun[port].pressed && gun[port].x >= 0)
gun[port].state = GUN_SHOOT;
}
}
unsigned char gunstick_emulator_IN()
{
if (gun.state == GUN_SLEEP)
return GUNSTICK_NONE;
unsigned line = CPC.keyboard_line & 0x0f;
unsigned port;
if (line == 9) port = 0;
else if (line == 6) port = 1;
else return GUNSTICK_NONE;
unsigned char result = gun[port].pressed ? (GUNSTICK_NONE & ~GUNSTICK_FIRE_MASK) : GUNSTICK_NONE;
if (gun[port].x < 0 || gun[port].state == GUN_SLEEP)
return result;
// on shoot update current light X/Y
// some games prove that the light is not always on target.
if (gun.state == GUN_SHOOT)
if (gun[port].state == GUN_SHOOT)
{
light.timer = GUNSTICK_TIMER;
gun.state = GUN_XYGET;
light[port].timer = GUNSTICK_TIMER;
gun[port].state = GUN_XYGET;
}
// wait timer finished
// TODO: after shoot maybe we need another timer (better dettection if user press long)
if (!light.timer)
gun.state = GUN_SLEEP;
else if (_gunstick_check())
return GUNSTICK_HIT;
if (!light[port].timer)
gun[port].state = GUN_SLEEP;
else if (_gunstick_check(port))
return result & GUNSTICK_HIT;
// need a fail answer when you have missed or in any other case
return GUNSTICK_NONE;
return result;
}
void gunstick_emulator_OUT(){}
+1
View File
@@ -38,6 +38,7 @@
#ifndef LIGHTGUN_GUNSTICK_H__
#define LIGHTGUN_GUNSTICK_H__
void gunstick_reset(void);
void gunstick_emulator_update(void);
unsigned char gunstick_emulator_IN();
void gunstick_emulator_OUT();
+2 -1
View File
@@ -51,6 +51,7 @@ typedef struct{
gun_state state;
int pressed;
} t_lightgun;
extern t_lightgun gun;
extern t_lightgun gun[2];
void ev_lightgun(unsigned port);
#endif
+11 -14
View File
@@ -17,7 +17,7 @@
*/
/* PHASER Logic
/* Magnum Phaser Logic
David Skywalker - libretro port Phaser code
based on the new cpc caprice core by Colin Pitrat - https://github.com/ColinPitrat/caprice32
*/
@@ -36,29 +36,26 @@ extern t_CPC CPC;
extern t_CRTC CRTC;
extern t_VDU VDU;
// libretro update lightgun position
void ev_lightgun();
#define PHASER_SCREEN_SHIFT 4
void phaser_emulator_update(void)
{
ev_lightgun();
ev_lightgun(0);
if(gun.pressed)
if(gun[0].pressed)
{
gun.state = GUN_SHOOT;
gun[0].state = GUN_SHOOT;
} else {
gun.state = GUN_PREPARE;
gun[0].state = GUN_PREPARE;
}
}
// When the phazer is not pressed, the CRTC is constantly refreshing R16 & R17:
// https://www.cpcwiki.eu/index.php/Amstrad_Magnum_Phaser
void phaser_emulator_OUT()
{
if (gun.state != GUN_PREPARE)
if (gun[0].state != GUN_PREPARE)
return;
CRTC.registers[17] += 1;
@@ -67,15 +64,15 @@ void phaser_emulator_OUT()
void phaser_emulator_CRTC()
{
// If the trigger is pressed, it only updates it when the phazer receives light from the screen.
if (gun.state != GUN_SHOOT)
if (gun[0].state != GUN_SHOOT)
return;
unsigned int x = CPC.scr_pos - CPC.scr_base;
unsigned int x = (CPC.scr_pos - CPC.scr_base) << CPC.scr_density;
unsigned int y = VDU.scrln;
unsigned int address = CRTC.addr + CRTC.char_count + PHASER_SCREEN_SHIFT;
if (gun.x >= x && gun.x < x + 16 && gun.y >= y && gun.y < y + 2)
if (gun[0].x >= x && gun[0].x < x + 16 && gun[0].y >= y && gun[0].y < y + 2)
{
CRTC.registers[16] = address >> 8;
CRTC.registers[17] = address & 0xff;
+7 -16
View File
@@ -75,6 +75,7 @@ extern dc_storage* dc;
// LIGHTGUN
#include "retro_gun.h"
#include "lightgun/lightgun.h"
extern t_lightgun_cfg lightgun_cfg;
// LOG
@@ -1238,6 +1239,8 @@ static void update_variables(void)
retro_ui_update_text();
computer_reset();
}
lightgun_prepare(lightgun_cfg.guntype);
}
void Emu_init()
@@ -1742,22 +1745,10 @@ void retro_set_controller_port_device( unsigned port, unsigned device )
if ( port > 1 )
return;
switch (device)
{
case RETRO_DEVICE_AMSTRAD_LIGHTGUN:
lightgun_prepare(lightgun_cfg.guntype);
amstrad_devices[port] = RETRO_DEVICE_AMSTRAD_LIGHTGUN;
break;
default:
// please do not deinit the lightgun config
if (lightgun_cfg.gunconfigured == LIGHTGUN_TYPE_UNCONFIGURED)
{
lightgun_prepare(LIGHTGUN_TYPE_NONE);
}
amstrad_devices[port] = device;
break;
}
amstrad_devices[port] = device;
gun[port].pressed = 0;
gun[port].state = GUN_SLEEP;
lightgun_prepare(lightgun_cfg.guntype);
LOGI("retro_set_controller_port_device: (%d)=%d\n", port, device);
}
+18 -18
View File
@@ -969,24 +969,24 @@ void ev_process_cursor()
);
}
void ev_lightgun()
void ev_lightgun(unsigned port)
{
if(input_state_cb(0, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN))
{
gun.state = GUN_PREPARE;
gun.x = 0xfff;
gun.y = 0xfff;
t_lightgun *g = &gun[port];
int x, y;
g->pressed = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER) != 0;
x = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X);
y = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y);
if (x == -0x8000 || y == -0x8000 ||
input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN)) {
g->state = GUN_PREPARE;
g->x = g->y = -1;
return;
}
gun.x = ((input_state_cb(0, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X) + 0x7fff) * EMULATION_SCREEN_WIDTH) / 0xfffe;
gun.y = ((input_state_cb(0, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y) + 0x7fff) * EMULATION_SCREEN_HEIGHT) / 0xfffe;
if(input_state_cb(0, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER)
|| (input_state_cb(0, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT)))
{
gun.pressed = true;
} else {
gun.pressed = false;
}
}
g->x = ((x + 0x7fff) * (retro_video.screen_render_width - 1)) / 0xfffe;
g->y = ((y + 0x7fff) * (retro_video.screen_render_height - 1)) / 0xfffe;
if (retro_video.screen_crop)
g->x += EMULATION_CROP;
}
+24 -5
View File
@@ -65,6 +65,12 @@ static uint32_t cursor_color = 0;
void lightgun_prepare(lightgun_type guntype)
{
if (lightgun_cfg.gunconfigured != guntype) {
memset(gun, 0, sizeof(gun));
gunstick_reset();
}
lightgun_cfg.gun_draw = NULL;
cursor_color = retro_video.cursor_color;
lightgun_cfg.gunconfigured = guntype;
@@ -102,10 +108,17 @@ void lightgun_prepare(lightgun_type guntype)
{
case DEPTH_24BPP:
lightgun_cfg.whitecolor = colours[11];
lightgun_cfg.luminance_color = colours[0]; // grey color
break;
case DEPTH_16BPP:
lightgun_cfg.whitecolor = colours[11] + (colours[11] << 16);
lightgun_cfg.whitecolor = colours[11] & 0xffff;
lightgun_cfg.luminance_color = colours[0] & 0xffff;
break;
case DEPTH_8BPP:
lightgun_cfg.whitecolor = colours[11] & 0xff;
lightgun_cfg.luminance_color = colours[0] & 0xff;
break;
default:
@@ -118,9 +131,15 @@ void lightgun_prepare(lightgun_type guntype)
void lightgun_draw(void)
{
// check margins
if (gun.x < 16 || gun.y < 16)
return;
for (unsigned port = 0; port < 2; port++) {
if (
gun[port].x < 16 ||
gun[port].y < 16 ||
gun[port].x >= EMULATION_SCREEN_WIDTH - 16 ||
gun[port].y >= EMULATION_SCREEN_HEIGHT - 16
)
continue;
draw_char(video_buffer, gun.x - (4 * EMULATION_SCALE), gun.y - 4, FNT_CROSS, cursor_color);
draw_char(video_buffer, gun[port].x - (4 * EMULATION_SCALE), gun[port].y - 4, FNT_CROSS, cursor_color);
}
}
+2 -1
View File
@@ -51,7 +51,8 @@ typedef struct
lightgun_type gunconfigured;
bool show;
unsigned int whitecolor;
// TODO: prepare color (gunstick)
unsigned int luminance_color;
unsigned int threshold_color;
void (*gun_update)(void);
void (*gun_draw)(void);