From d12ce6b9dd20b11bb1bdebecc31496d57a0f46f4 Mon Sep 17 00:00:00 2001 From: D_Skywalk <560310+DSkywalk@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:31:45 +0200 Subject: [PATCH] [GUN]: added Loriciel West Phaser and Trojan Phazer lightgun modes, thx to @LilaQ Co-authored-by: LilaQ <2311758+LilaQ@users.noreply.github.com> Resolves #180, complete and fixes #125 --- Makefile.common | 1 + cap32/cap32.c | 1 - cap32/lightgun/gunstick.c | 51 ++----------------- cap32/lightgun/lightgun.c | 101 ++++++++++++++++++++++++++++++++++++++ cap32/lightgun/lightgun.h | 3 ++ cap32/lightgun/phaser.c | 80 ++++++++++++++++++++++++++++-- cap32/lightgun/phaser.h | 5 ++ libretro/libretro-core.c | 10 ++-- libretro/retro_gun.c | 19 +++++++ libretro/retro_gun.h | 4 +- 10 files changed, 218 insertions(+), 57 deletions(-) create mode 100644 cap32/lightgun/lightgun.c diff --git a/Makefile.common b/Makefile.common index 5efb6b6..b871ae6 100644 --- a/Makefile.common +++ b/Makefile.common @@ -22,6 +22,7 @@ SOURCES_C := \ $(CORE_DIR)/cap32/asic.c \ $(CORE_DIR)/cap32/z80.c \ $(CORE_DIR)/cap32/kbdauto.c \ + $(CORE_DIR)/cap32/lightgun/lightgun.c \ $(CORE_DIR)/cap32/lightgun/gunstick.c \ $(CORE_DIR)/cap32/lightgun/phaser.c \ $(CORE_DIR)/libretro/microui/microui.c \ diff --git a/cap32/cap32.c b/cap32/cap32.c index d6a6729..e1112eb 100644 --- a/cap32/cap32.c +++ b/cap32/cap32.c @@ -1563,7 +1563,6 @@ 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(); diff --git a/cap32/lightgun/gunstick.c b/cap32/lightgun/gunstick.c index d65741e..5a5c82d 100644 --- a/cap32/lightgun/gunstick.c +++ b/cap32/lightgun/gunstick.c @@ -69,58 +69,13 @@ static t_light light[2]; #define GUNSTICK_TIMER 4 -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) -{ - 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]; - } + lightgun_cfg.threshold_color = lightgun_luminance(lightgun_cfg.luminance_color); } bool _gunstick_check(unsigned port) @@ -134,8 +89,8 @@ bool _gunstick_check(unsigned port) gun[port].state = GUN_SSEND; } - gcolor = _gunstick_get_screen(light[port].x, light[port].y); - luminance = gunstick_luminance(gcolor); + gcolor = lightgun_get_screen(light[port].x, light[port].y); + luminance = lightgun_luminance(gcolor); #ifdef DEBUG_GUNSTICK printf("gunstick: 0x%X[0x%X] (%u,%u) [0x%X]\n", gcolor, luminance, light[port].x, light[port].y, lightgun_cfg.threshold_color); #endif diff --git a/cap32/lightgun/lightgun.c b/cap32/lightgun/lightgun.c new file mode 100644 index 0000000..0fc4094 --- /dev/null +++ b/cap32/lightgun/lightgun.c @@ -0,0 +1,101 @@ +/**************************************************************************** + * Caprice32 libretro port + * + * Copyright David Colmenero - D_Skywalk (2019-2023) + * - original header - + * Pituka - Nintendo Wii/Gamecube Port + * (c) Copyright 2008-2009 David Colmenero (aka D_Skywalk) + * + * Redistribution and use of this code or any derivative works are permitted + * provided that the following conditions are met: + * + * - Redistributions may not be sold, nor may they be used in a commercial + * product or activity. + * + * - Redistributions that are modified from the original source must include the + * complete source code, including the source code for all components used by a + * binary built from the modified sources. However, as a special exception, the + * source code distributed need not include anything that is normally distributed + * (in either source or binary form) with the major components (compiler, kernel, + * and so on) of the operating system on which the executable runs, unless that + * component itself accompanies the executable. + * + * - Redistributions must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************************/ +#include +#include +#include + +#include +#include + +#include "gfx/software.h" +#include "gfx/video.h" +#include "assets/assets.h" + +#include "cap32.h" +#include "lightgun.h" +#include "retro_gun.h" + +extern uint32_t * video_buffer; + + +unsigned lightgun_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); +} + +unsigned int lightgun_get_screen(int x, int y) +{ + 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]; + } +} diff --git a/cap32/lightgun/lightgun.h b/cap32/lightgun/lightgun.h index 7fcfe7e..e4ca89b 100644 --- a/cap32/lightgun/lightgun.h +++ b/cap32/lightgun/lightgun.h @@ -54,4 +54,7 @@ typedef struct{ extern t_lightgun gun[2]; void ev_lightgun(unsigned port); +unsigned lightgun_luminance(unsigned int pixel); +unsigned int lightgun_get_screen(int x, int y); + #endif \ No newline at end of file diff --git a/cap32/lightgun/phaser.c b/cap32/lightgun/phaser.c index 5889bc6..ca89446 100644 --- a/cap32/lightgun/phaser.c +++ b/cap32/lightgun/phaser.c @@ -31,13 +31,18 @@ #include "cap32.h" #include "lightgun.h" +#include "retro_gun.h" extern t_CPC CPC; extern t_CRTC CRTC; extern t_VDU VDU; -#define PHASER_SCREEN_SHIFT 4 +#define PHASER_NONE 0xff +#define PHASER_SCREEN_SHIFT 4 +#define WESTPHASER_FIRE_MASK 0x20 /* Bit 5 down (Joystick Fire 1) */ +#define WESTPHASER_HIT 0x01 +#define TROJAN_FIRE_MASK 0x10 /* Bit 4 down (Joystick Fire 2) */ void phaser_emulator_update(void) { @@ -61,7 +66,7 @@ void phaser_emulator_OUT() CRTC.registers[17] += 1; } -void phaser_emulator_CRTC() +static void phaser_latch(unsigned shift) { // If the trigger is pressed, it only updates it when the phazer receives light from the screen. if (gun[0].state != GUN_SHOOT) @@ -70,7 +75,7 @@ void phaser_emulator_CRTC() 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; + unsigned int address = CRTC.addr + CRTC.char_count + shift; if (gun[0].x >= x && gun[0].x < x + 16 && gun[0].y >= y && gun[0].y < y + 2) { @@ -79,4 +84,71 @@ void phaser_emulator_CRTC() } } -unsigned char phaser_emulator_IN(){ return 0xff; } +void phaser_emulator_CRTC() +{ + phaser_latch(PHASER_SCREEN_SHIFT); +} + +unsigned char phaser_emulator_IN(){ return PHASER_NONE; } + +/* The Plus AUX gun has a separate active-low trigger on joystick 1 fire 2. + * Unlike the expansion-port Magnum, it does not use writes to port FBFE. */ +unsigned char trojan_emulator_IN(void) +{ + if ((CPC.keyboard_line & 15) != 9) + return PHASER_NONE; + + return gun[0].pressed ? (PHASER_NONE & ~TROJAN_FIRE_MASK) : PHASER_NONE; +} + +void trojan_emulator_CRTC(void) +{ + phaser_latch(0); +} + + +/* Loriciel West Phaser - Implementación basada en rastreo del haz de electrones */ +unsigned char westphaser_emulator_IN(void) +{ + unsigned char value = PHASER_NONE; + int x, y; + + /* The West Phaser connects to joystick port 1 (matrix line 9) */ + if ((CPC.keyboard_line & 15) != 9) + return value; + + /* On Fire we inject Bit 5 low (Joystick Fire 1) */ + if (gun[0].pressed) + value &= ~WESTPHASER_FIRE_MASK; + + /* If the gun is pointing off-screen, abort */ + if (gun[0].x < 0 || gun[0].y < 0) + return value; + + /* Exact electron beam tracking (Raster tracking) + * Get the X coordinate by subtracting the line base from the current memory position, + * adjusting by screen density. Y is provided directly by VDU.scrln. */ + x = (CPC.scr_pos - CPC.scr_base) << CPC.scr_density; + y = VDU.scrln; + + /* The Light sensor: Inject Bit 0 low (Joystick Up) + * The game times the start of the sensor response and then samples the + * subsequent scanlines. To simulate persistence, keep the signal active + * for the rest of each visible line AFTER the aim point (x > gun.x and y > gun.y), + * provided that the screen coordinate has a bright color. */ + if ( + VDU.flag_drawing && + y > gun[0].y && + x > gun[0].x && + x < EMULATION_SCREEN_WIDTH + ) + { + uint32_t color = lightgun_get_screen(gun[0].x, y); + if (lightgun_luminance(color) > lightgun_cfg.threshold_color) + { + value &= ~WESTPHASER_HIT; + } + } + + return value; +} diff --git a/cap32/lightgun/phaser.h b/cap32/lightgun/phaser.h index 7f44491..fc03e41 100644 --- a/cap32/lightgun/phaser.h +++ b/cap32/lightgun/phaser.h @@ -43,4 +43,9 @@ unsigned char phaser_emulator_IN(); void phaser_emulator_OUT(); void phaser_emulator_CRTC(); +unsigned char trojan_emulator_IN(void); +void trojan_emulator_CRTC(void); + +unsigned char westphaser_emulator_IN(void); + #endif \ No newline at end of file diff --git a/libretro/libretro-core.c b/libretro/libretro-core.c index 08e17a6..319ea43 100644 --- a/libretro/libretro-core.c +++ b/libretro/libretro-core.c @@ -470,8 +470,10 @@ static struct retro_core_option_v2_definition option_definitions[] = { "light_gun", { { "disabled", NULL }, - { "phaser", NULL }, - { "gunstick", NULL }, + { "phaser", "Amstrad Magnum Phaser" }, + { "gunstick", "Gun-Stick" }, + { "west_phaser", "Loriciel West Phaser" }, + { "trojan_phazer", "Trojan Phazer (CPC Plus / GX4000)" }, { NULL, NULL }, }, "disabled" @@ -730,7 +732,7 @@ static struct retro_variable variables[] = { }, { "cap32_lightgun_input", - "Light Gun > Input; disabled|phaser|gunstick", + "Light Gun > Input; disabled|phaser|gunstick|west_phaser|trojan_phazer", }, { "cap32_lightgun_show", @@ -1009,6 +1011,8 @@ static void update_variables(void) lightgun_type val = LIGHTGUN_TYPE_NONE; if (strcmp(var.value, "phaser") == 0) val = LIGHTGUN_TYPE_PHASER; else if (strcmp(var.value, "gunstick") == 0) val = LIGHTGUN_TYPE_GUNSTICK; + else if (strcmp(var.value, "west_phaser") == 0) val = LIGHTGUN_TYPE_WEST_PHASER; + else if (strcmp(var.value, "trojan_phazer") == 0) val = LIGHTGUN_TYPE_TROJAN_PHAZER; if (lightgun_cfg.guntype != val) { lightgun_cfg.guntype = val; diff --git a/libretro/retro_gun.c b/libretro/retro_gun.c index 518605d..3c48708 100644 --- a/libretro/retro_gun.c +++ b/libretro/retro_gun.c @@ -90,6 +90,25 @@ void lightgun_prepare(lightgun_type guntype) CPC.gun_OUT = phaser_emulator_OUT; break; + case LIGHTGUN_TYPE_WEST_PHASER: + lightgun_cfg.gun_update = phaser_emulator_update; + CPC.gun_CRTC = NULL; + CPC.gun_IN = westphaser_emulator_IN; + CPC.gun_OUT = NULL; + break; + + case LIGHTGUN_TYPE_TROJAN_PHAZER: + if (retro_computer_cfg.model != CPC_MODEL_PLUS) { + retro_message("Trojan lightgun is for CPC6128+"); + break; + } + + lightgun_cfg.gun_update = phaser_emulator_update; + CPC.gun_CRTC = trojan_emulator_CRTC; + CPC.gun_IN = trojan_emulator_IN; + CPC.gun_OUT = NULL; + break; + default: lightgun_cfg.gun_draw = NULL; lightgun_cfg.gun_update = NULL; diff --git a/libretro/retro_gun.h b/libretro/retro_gun.h index 30798c3..9b46490 100644 --- a/libretro/retro_gun.h +++ b/libretro/retro_gun.h @@ -41,8 +41,10 @@ typedef enum { LIGHTGUN_TYPE_UNCONFIGURED, LIGHTGUN_TYPE_NONE, - LIGHTGUN_TYPE_PHASER, LIGHTGUN_TYPE_GUNSTICK, + LIGHTGUN_TYPE_PHASER, + LIGHTGUN_TYPE_TROJAN_PHAZER, + LIGHTGUN_TYPE_WEST_PHASER, } lightgun_type; typedef struct