[GUN]: added Loriciel West Phaser and Trojan Phazer lightgun modes, thx to @LilaQ

Co-authored-by: LilaQ <[email protected]>

Resolves #180, complete and fixes #125
This commit is contained in:
D_SkywalkandLilaQ committed 2026-10-06 21:31:45 +02:00
1 parent 99072f4337
commit d12ce6b9dd
10 files changed
+218 -57

No files matched your search

+1
View File
@@ -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 \
-1
View File
@@ -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();
+3 -48
View File
@@ -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
+101
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <libretro.h>
#include <libretro-core.h>
#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];
}
}
+3
View File
@@ -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
+76 -4
View File
@@ -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;
}
+5
View File
@@ -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
+7 -3
View File
@@ -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;
+19
View File
@@ -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;
+3 -1
View File
@@ -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