diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 4b05a422f6b..de4801d3522 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -55,6 +55,7 @@ static unsigned targetSampleRate = GBA_RESAMPLED_RATE; #define VIDEO_WIDTH_MAX 256 #define VIDEO_HEIGHT_MAX 224 #define VIDEO_BUFF_SIZE (VIDEO_WIDTH_MAX * VIDEO_HEIGHT_MAX * sizeof(mColor)) +#define LIBRETRO_MAX_PORTS 8 static retro_environment_t environCallback; static retro_video_refresh_t videoCallback; @@ -75,6 +76,9 @@ static void _audioRateChanged(struct mAVStream*, unsigned rate); static void _setRumble(struct mRumbleIntegrator*, float level); static uint8_t _readLux(struct GBALuminanceSource* lux); static void _updateLux(struct GBALuminanceSource* lux); +static int _boktai1StepToBar(int step); +static int _boktai1BarToStep(int bar); +static int _analogStickSolarLevel(unsigned port); static void _updateCamera(const uint32_t* buffer, unsigned width, unsigned height, size_t pitch); static void _startImage(struct mImageSource*, unsigned w, unsigned h, int colorFormats); static void _stopImage(struct mImageSource*); @@ -106,6 +110,8 @@ static int luxLevelIndex; static uint8_t luxLevel; static bool luxSensorEnabled; static bool luxSensorUsed; +static bool luxAnalogMode; +static bool boktai1Game; static struct mLogger logger; static struct retro_camera_callback cam; static struct mImageSource imageSource; @@ -1436,6 +1442,7 @@ void retro_init(void) { envVarsUpdated = true; luxSensorUsed = false; luxSensorEnabled = false; + boktai1Game = false; luxLevelIndex = 0; luxLevel = 0; lux.readLuminance = _readLux; @@ -1607,23 +1614,60 @@ void retro_run(void) { core->setKeys(core, keys); if (!luxSensorUsed) { - static bool wasAdjustingLux = false; - if (wasAdjustingLux) { - wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) || - inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3); - } else { - if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) { - ++luxLevelIndex; - if (luxLevelIndex > 10) { - luxLevelIndex = 10; + if (luxAnalogMode) { + int level = -1; + unsigned port; + for (port = 0; port < LIBRETRO_MAX_PORTS; ++port) { + level = _analogStickSolarLevel(port); + if (level >= 0) { + break; + } + } + if (level >= 0) { + if (boktai1Game) { + if (level > 8) { + level = 8; + } + luxLevelIndex = _boktai1BarToStep(level); + } else { + luxLevelIndex = level; } - wasAdjustingLux = true; - } else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) { - --luxLevelIndex; - if (luxLevelIndex < 0) { - luxLevelIndex = 0; + } + } else { + static bool wasAdjustingLux = false; + if (wasAdjustingLux) { + wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) || + inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3); + } else { + if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) { + if (boktai1Game) { + int bar = _boktai1StepToBar(luxLevelIndex); + if (bar < 8) { + ++bar; + } + luxLevelIndex = _boktai1BarToStep(bar); + } else { + ++luxLevelIndex; + if (luxLevelIndex > 10) { + luxLevelIndex = 10; + } + } + wasAdjustingLux = true; + } else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) { + if (boktai1Game) { + int bar = _boktai1StepToBar(luxLevelIndex); + if (bar > 0) { + --bar; + } + luxLevelIndex = _boktai1BarToStep(bar); + } else { + --luxLevelIndex; + if (luxLevelIndex < 0) { + luxLevelIndex = 0; + } + } + wasAdjustingLux = true; } - wasAdjustingLux = true; } } } @@ -2088,6 +2132,22 @@ bool retro_load_game(const struct retro_game_info* game) { core->loadROM(core, rom); deferredSetup = true; + /* Boktai 1 has an 8-bar solar gauge. Detect it once per loaded game so + * manual adjustments (core option level + L3/R3) operate in 0-8 bars. */ + boktai1Game = false; +#ifdef M_CORE_GBA + if (core->platform(core) == mPLATFORM_GBA) { + struct mGameInfo info; + core->getGameInfo(core, &info); + boktai1Game = strcmp(info.code, "U3IJ") == 0 || + strcmp(info.code, "U3IE") == 0 || + strcmp(info.code, "U3IP") == 0; + } +#endif + /* Re-apply solar settings with the correct mapping for this title. */ + envVarsUpdated = true; + _updateLux(&lux); + const char* sysDir = 0; const char* biosName = 0; char biosPath[PATH_MAX]; @@ -2154,6 +2214,7 @@ void retro_unload_game(void) { if (!core) { return; } + boktai1Game = false; mCoreConfigDeinit(&core->config); core->deinit(core); mappedMemoryFree(data, dataSize); @@ -2466,6 +2527,81 @@ static void _setRumble(struct mRumbleIntegrator* rumble, float level) { rumbleCallback(0, RETRO_RUMBLE_WEAK, level * 0xFFFF); } +static int _boktai1StepToBar(int step) { + static const uint8_t map[11] = { 0, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8 }; + if (step < 0) { + step = 0; + } else if (step > 10) { + step = 10; + } + return map[step]; +} + +static int _boktai1BarToStep(int bar) { + static const uint8_t map[9] = { 0, 1, 2, 3, 5, 6, 7, 8, 10 }; + if (bar < 0) { + bar = 0; + } else if (bar > 8) { + bar = 8; + } + return map[bar]; +} + +static int _analogStickSolarLevel(unsigned port) { + const int16_t dead = 0x4000; + int16_t lx = inputCallback(port, RETRO_DEVICE_ANALOG, + RETRO_DEVICE_INDEX_ANALOG_LEFT, + RETRO_DEVICE_ID_ANALOG_X); + int16_t ly = inputCallback(port, RETRO_DEVICE_ANALOG, + RETRO_DEVICE_INDEX_ANALOG_LEFT, + RETRO_DEVICE_ID_ANALOG_Y); + int16_t rx = inputCallback(port, RETRO_DEVICE_ANALOG, + RETRO_DEVICE_INDEX_ANALOG_RIGHT, + RETRO_DEVICE_ID_ANALOG_X); + int16_t ry = inputCallback(port, RETRO_DEVICE_ANALOG, + RETRO_DEVICE_INDEX_ANALOG_RIGHT, + RETRO_DEVICE_ID_ANALOG_Y); + + int group = -1; + if (ly < -dead) { + group = 0; /* 0-3 */ + } else if (ly > dead) { + group = 1; /* 4-7 */ + } else if (lx < -dead) { + group = 2; /* 8-10 & 0 */ + } + + int select = -1; + if (ry < -dead) { + select = 0; /* 0/4/8 */ + } else if (ry > dead) { + select = 1; /* 1/5/9 */ + } else if (rx < -dead) { + select = 2; /* 2/6/10 */ + } else if (rx > dead) { + select = 3; /* 3/7/0 */ + } + + if (group < 0 || select < 0) { + return -1; + } + + if (group == 0) { + return select; + } else if (group == 1) { + return 4 + select; + } + + if (select == 0) { + return 8; + } else if (select == 1) { + return 9; + } else if (select == 2) { + return 10; + } + return 0; +} + static void _updateLux(struct GBALuminanceSource* lux) { UNUSED(lux); struct retro_variable var = { @@ -2480,6 +2616,7 @@ static void _updateLux(struct GBALuminanceSource* lux) { if (luxVarUpdated) { luxSensorUsed = strcmp(var.value, "sensor") == 0; + luxAnalogMode = strcmp(var.value, "analog") == 0; } if (luxSensorUsed) { @@ -2487,17 +2624,29 @@ static void _updateLux(struct GBALuminanceSource* lux) { float fLux = luxSensorEnabled ? sensorGetCallback(0, RETRO_SENSOR_ILLUMINANCE) : 0.0f; luxLevel = cbrtf(fLux) * 8; } else { - if (luxVarUpdated) { + if (luxVarUpdated && !luxAnalogMode) { char* end; int newLuxLevelIndex = strtol(var.value, &end, 10); if (!*end) { - if (newLuxLevelIndex > 10) { - luxLevelIndex = 10; - } else if (newLuxLevelIndex < 0) { - luxLevelIndex = 0; + if (boktai1Game) { + /* Boktai 1 shows an 8-bar gauge (0-8). Clamp any user-provided + * values outside that range, then map into the internal 10-step + * lux level indices. */ + if (newLuxLevelIndex > 8) { + newLuxLevelIndex = 8; + } else if (newLuxLevelIndex < 0) { + newLuxLevelIndex = 0; + } + luxLevelIndex = _boktai1BarToStep(newLuxLevelIndex); } else { - luxLevelIndex = newLuxLevelIndex; + if (newLuxLevelIndex > 10) { + luxLevelIndex = 10; + } else if (newLuxLevelIndex < 0) { + luxLevelIndex = 0; + } else { + luxLevelIndex = newLuxLevelIndex; + } } } } diff --git a/src/platform/libretro/libretro_core_options.h b/src/platform/libretro/libretro_core_options.h index 9bd553bdf23..01a814dbd70 100644 --- a/src/platform/libretro/libretro_core_options.h +++ b/src/platform/libretro/libretro_core_options.h @@ -271,11 +271,12 @@ struct retro_core_option_v2_definition option_defs_us[] = { "mgba_solar_sensor_level", "Solar Sensor Level", NULL, - "Sets ambient sunlight intensity. Can be used by games that included a solar sensor in their cartridges, e.g: the Boktai series.", + "Sets sunlight intensity for games that include a solar sensor in their cartridge (e.g. Boktai). 'Analog Stick Control': left stick selects group (Up=0-3, Down=4-7, Left=8-10/0); right stick selects value (Up=0/4/8, Down=1/5/9, Left=2/6/10, Right=3/7/0). ASC disables Analog to Digital until restart.", NULL, "input", { - { "sensor", "Use device sensor if available" }, + { "sensor", "Use Device Sensor If Available" }, + { "analog", "Analog Stick Control" }, { "0", NULL }, { "1", NULL }, { "2", NULL },