diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index daa66b652..4f0f7475b 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -302,7 +302,7 @@ jobs: publish-latest-build: runs-on: ubuntu-latest if: github.ref == 'refs/heads/port' - needs: [build-i686-windows, build-i686-linux, build-x86_64-windows, build-x86_64-linux, build-arm64-nswitch, build-x86_64-osx, build-arm64-osx, build-flatpak-bundle] + needs: [build-i686-windows, build-i686-linux, build-x86_64-windows, build-x86_64-linux, build-arm64-nswitch, build-x86_64-osx, build-arm64-osx] permissions: contents: write steps: @@ -323,7 +323,6 @@ jobs: zip -r ../ci-release/pd-i686-windows.zip pd-i686-windows zip -r ../ci-release/pd-x86_64-windows.zip pd-x86_64-windows zip -r ../ci-release/pd-arm64-nswitch.zip pd-arm64-nswitch - mv io.github.fgsfdsfgs.perfect_dark.flatpak/io.github.fgsfdsfgs.perfect_dark.flatpak ../ci-release/ popd git tag --force ci-dev-build port git push --force origin ci-dev-build diff --git a/port/include/input.h b/port/include/input.h index 916cd72ba..6d5d88bfd 100644 --- a/port/include/input.h +++ b/port/include/input.h @@ -142,6 +142,10 @@ void inputControllerSetSticksSwapped(s32 cidx, s32 swapped); s32 inputControllerGetDualAnalog(s32 cidx); void inputControllerSetDualAnalog(s32 cidx, s32 enable); +// get/set Input.Player%d.CancelCButtons +s32 inputControllerGetCancelCButtons(s32 cidx); +void inputControllerSetCancelCButtons(s32 cidx, s32 cancel); + // get/set sensitivity for a given player f32 inputControllerGetAxisScale(s32 cidx, s32 stick, s32 axis); void inputControllerSetAxisScale(s32 cidx, s32 stick, s32 axis, f32 value); diff --git a/port/src/input.c b/port/src/input.c index ede18d9e3..cfee620a5 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -44,7 +44,8 @@ static SDL_GameController *pads[INPUT_MAX_CONTROLLERS]; .deadzone = { DEFAULT_DEADZONE, DEFAULT_DEADZONE, DEFAULT_DEADZONE, DEFAULT_DEADZONE_RY }, \ .stickCButtons = 0, \ .swapSticks = 1, \ - .deviceIndex = -1 \ + .deviceIndex = -1, \ + .cancelCButtons = 0, \ } static struct controllercfg { @@ -56,6 +57,7 @@ static struct controllercfg { s32 stickCButtons; s32 swapSticks; s32 deviceIndex; + s32 cancelCButtons; } padsCfg[INPUT_MAX_CONTROLLERS] = { CONTROLLERCFG_DEFAULT, CONTROLLERCFG_DEFAULT, @@ -768,12 +770,22 @@ s32 inputReadController(s32 idx, OSContPad *npad) npad->stick_x = xdiff < 0 ? -0x80 : (xdiff > 0 ? 0x7F : 0); npad->stick_y = ydiff < 0 ? -0x80 : (ydiff > 0 ? 0x7F : 0); + const struct controllercfg *cfg = &padsCfg[idx]; + + if (cfg->cancelCButtons) { + // opposite C buttons cancel each other out + if ((npad->button & (L_CBUTTONS | R_CBUTTONS)) == (L_CBUTTONS | R_CBUTTONS)) { + npad->button &= ~(L_CBUTTONS | R_CBUTTONS); + } + if ((npad->button & (U_CBUTTONS | D_CBUTTONS)) == (U_CBUTTONS | D_CBUTTONS)) { + npad->button &= ~(U_CBUTTONS | D_CBUTTONS); + } + } + if (!pads[idx]) { return 0; } - const struct controllercfg *cfg = &padsCfg[idx]; - s32 leftX = SDL_GameControllerGetAxis(pads[idx], cfg->axisMap[0][0]); s32 leftY = SDL_GameControllerGetAxis(pads[idx], cfg->axisMap[0][1]); s32 rightX = SDL_GameControllerGetAxis(pads[idx], cfg->axisMap[1][0]); @@ -951,6 +963,16 @@ void inputControllerSetDualAnalog(s32 cidx, s32 enable) padsCfg[cidx].stickCButtons = !enable; } +s32 inputControllerGetCancelCButtons(s32 cidx) +{ + return padsCfg[cidx].cancelCButtons; +} + +void inputControllerSetCancelCButtons(s32 cidx, s32 cancel) +{ + padsCfg[cidx].cancelCButtons = cancel; +} + f32 inputControllerGetAxisScale(s32 cidx, s32 stick, s32 axis) { return padsCfg[cidx].sens[stick * 2 + axis]; @@ -1476,6 +1498,7 @@ PD_CONSTRUCTOR static void inputConfigInit(void) configRegisterFloat(strFmt("%s.RStickScaleX", secname), &padsCfg[c].sens[2], -10.f, 10.f); configRegisterFloat(strFmt("%s.RStickScaleY", secname), &padsCfg[c].sens[3], -10.f, 10.f); configRegisterInt(strFmt("%s.StickCButtons", secname), &padsCfg[c].stickCButtons, 0, 1); + configRegisterInt(strFmt("%s.CancelCButtons", secname), &padsCfg[c].cancelCButtons, 0, 1); configRegisterInt(strFmt("%s.SwapSticks", secname), &padsCfg[c].swapSticks, 0, 1); configRegisterInt(strFmt("%s.ControllerIndex", secname), &padsCfg[c].deviceIndex, -1, 0x7FFFFFFF); secname[13] = '.'; diff --git a/src/game/challenge.c b/src/game/challenge.c index cfba07b4c..2dfcfb813 100644 --- a/src/game/challenge.c +++ b/src/game/challenge.c @@ -693,6 +693,12 @@ void challengeApply(void) for (i = 0; i < MAX_PLAYERS; i++) { g_PlayerConfigsArray[i].base.team = 0; } + +#ifndef PLATFORM_N64 + // Enable Friendly Fire option + g_MpSetup.options |= MPOPTION_FRIENDLYFIRE; +#endif + } s32 challengeRemovePlayerLock(void) diff --git a/src/game/mplayer/mplayer.c b/src/game/mplayer/mplayer.c index 76326af4a..01a5264a2 100644 --- a/src/game/mplayer/mplayer.c +++ b/src/game/mplayer/mplayer.c @@ -51,6 +51,11 @@ struct modeldef *var800acc28[18]; struct mpweaponset g_MpWeaponSets[12]; s32 g_MpWeaponSetNum; +#ifndef PLATFORM_N64 +u8 g_MpWeaponSetRandomFilters[NUM_MPWEAPONS]; +s32 g_MpWeaponRandomFilterNum; +#endif + #if VERSION >= VERSION_NTSC_1_0 const char var7f1b8a00[] = "||||||||||||| Starting game... players %d\n"; #endif @@ -542,6 +547,12 @@ void mpPlayerSetDefaults(s32 playernum, bool autonames) for (i = 0; i < ARRAYCOUNT(g_PlayerConfigsArray); i++) { g_PlayerConfigsArray[playernum].gunfuncs[i] = 0; } + +#ifndef PLATFORM_N64 + for (i = 0; i < ARRAYCOUNT(g_MpWeapons); i++) { + g_MpWeaponSetRandomFilters[i] = 1; + } +#endif } void func0f1881d4(s32 index) @@ -1192,10 +1203,40 @@ void func0f18913c(void) } } +#ifndef PLATFORM_N64 +void mpSetRandomWeapons(u8 weapons[]) +{ + s32 lockcount = 0; + s32 index = 0; + s32 i; + + for (i = 0; i < NUM_MPWEAPONS; i++) { + if (challengeIsFeatureUnlocked(g_MpWeapons[i].unlockfeature)) { + if (g_MpWeaponSetRandomFilters[i] == 1) { + weapons[index] = i - lockcount; + index++; + } + } else { + lockcount++; + } + } + + if (index == 0) { + weapons[0] = 0; // optionindex (shifted by unlocked weapons, but usually 0 is "Nothing") + g_MpWeaponRandomFilterNum = 1; + } else { + g_MpWeaponRandomFilterNum = index; + } +} +#endif + void mpApplyWeaponSet(void) { s32 i; u8 *ptr; +#ifndef PLATFORM_N64 + u8 randomweapons[NUM_MPWEAPONS]; +#endif if (g_MpWeaponSetNum >= 0 && g_MpWeaponSetNum < ARRAYCOUNT(g_MpWeaponSets)) { if (challengeIsFeatureUnlocked(g_MpWeaponSets[g_MpWeaponSetNum].requirefeatures[0]) @@ -1233,17 +1274,31 @@ void mpApplyWeaponSet(void) } } } else if (g_MpWeaponSetNum == WEAPONSET_RANDOM) { +#ifdef PLATFORM_N64 s32 numoptions = mpGetNumWeaponOptions(); for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) { mpSetWeaponSlot(i, rngRandom() % numoptions); } +#else + mpSetRandomWeapons(randomweapons); + for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) { + mpSetWeaponSlot(i, randomweapons[rngRandom() % g_MpWeaponRandomFilterNum]); + } +#endif } else if (g_MpWeaponSetNum == WEAPONSET_RANDOMFIVE) { +#ifdef PLATFORM_N64 s32 numoptions = mpGetNumWeaponOptions() - 2; for (i = 0; i < 5; i++) { mpSetWeaponSlot(i, rngRandom() % numoptions + 1); } +#else + mpSetRandomWeapons(randomweapons); + for (i = 0; i < 5; i++) { + mpSetWeaponSlot(i, randomweapons[rngRandom() % g_MpWeaponRandomFilterNum]); + } +#endif mpSetWeaponSlot(i, mpGetNumWeaponOptions() - 1); } diff --git a/src/game/mplayer/setup.c b/src/game/mplayer/setup.c index 3a002bd26..8d3033eca 100644 --- a/src/game/mplayer/setup.c +++ b/src/game/mplayer/setup.c @@ -1168,6 +1168,155 @@ struct menudialogdef g_MpSaveSetupExistsMenuDialog = { }; #ifndef PLATFORM_N64 +MenuItemHandlerResult mpSelectRandomWeaponListHandler(s32 operation, struct menuitem *item, union handlerdata *data) +{ + static const char *labels[] = { + "Select Dark", + "Select Classic", + "Select All", + "Select None", + }; + + switch (operation) { + case MENUOP_GETOPTIONCOUNT: + data->list.value = mpGetNumWeaponOptions() + 4; + break; + case MENUOP_GETOPTIONTEXT: + { + s32 numweapons = mpGetNumWeaponOptions(); + + if (data->list.value < numweapons) { + return (uintptr_t) mpGetWeaponLabel(data->list.value); + } else { + return (intptr_t)labels[data->list.value - numweapons]; + } + } + case MENUOP_SET: + { + s32 numweapons = mpGetNumWeaponOptions(); + s32 mpweaponnum = data->list.value; + s32 optionindex = mpweaponnum; + s32 i; + + if (data->list.value < numweapons) { + if (data->list.unk04 == 0) { + for (i = 0; i <= mpweaponnum; i++) { + if (challengeIsFeatureUnlocked(g_MpWeapons[i].unlockfeature) == 0) { + mpweaponnum++; + } + + optionindex = mpweaponnum; + } + + g_MpWeaponSetRandomFilters[optionindex] = 1 - g_MpWeaponSetRandomFilters[optionindex]; + } + } else { + s32 index = data->list.value - numweapons; + + switch (index) { + case 0: + // Select Dark + for (i = 0; i < ARRAYCOUNT(g_MpWeapons); i++) { + if ((i >= MPWEAPON_NONE && i <= MPWEAPON_COMBATBOOST) || i >= MPWEAPON_SHIELD) { + g_MpWeaponSetRandomFilters[i] = 1; + } else { + g_MpWeaponSetRandomFilters[i] = 0; + } + } + break; + case 1: + // Select Classic + for (i = 0; i < ARRAYCOUNT(g_MpWeapons); i++) { + if (i >= MPWEAPON_PP9I && i <= MPWEAPON_RCP45) { + g_MpWeaponSetRandomFilters[i] = 1; + } else { + g_MpWeaponSetRandomFilters[i] = 0; + } + } + break; + case 2: + // Select All + for (i = 0; i < ARRAYCOUNT(g_MpWeapons); i++) { + g_MpWeaponSetRandomFilters[i] = 1; + } + break; + case 3: + // Select None + for (i = 0; i < ARRAYCOUNT(g_MpWeapons); i++) { + g_MpWeaponSetRandomFilters[i] = 0; + } + break; + } + } + } + break; + case MENUOP_GETSELECTEDINDEX: + data->list.value = 0x000fffff; + break; + case MENUOP_GETLISTITEMCHECKBOX: + { + s32 numweapons = mpGetNumWeaponOptions(); + s32 mpweaponnum = data->list.value; + s32 optionindex = mpweaponnum; + s32 i; + + if (data->list.value < numweapons) { + + for (i = 0; i <= mpweaponnum; i++) { + if (challengeIsFeatureUnlocked(g_MpWeapons[i].unlockfeature) == 0) { + mpweaponnum++; + } + + optionindex = mpweaponnum; + } + + data->list.unk04 = g_MpWeaponSetRandomFilters[optionindex]; + } + } + break; + } + + return 0; +} + +struct menuitem g_MpSelectRandomWeaponsMenuItems[] = { + { + MENUITEMTYPE_LIST, + 0, + MENUITEMFLAG_LOCKABLEMINOR, + 0x00000078, + 0x0000004d, + mpSelectRandomWeaponListHandler, + }, + { MENUITEMTYPE_END }, +}; + +struct menudialogdef g_MpSelectRandomWeaponsMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + (uintptr_t)"Select Weapons", + g_MpSelectRandomWeaponsMenuItems, + NULL, + MENUDIALOGFLAG_LITERAL_TEXT, + NULL, +}; + +MenuItemHandlerResult menuhandlerMpSelectRandomWeapons(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_CHECKDISABLED: + case MENUOP_CHECKHIDDEN: + if (g_MpWeaponSetNum == WEAPONSET_RANDOM + || g_MpWeaponSetNum == WEAPONSET_RANDOMFIVE) { + return false; + } + return true; + case MENUOP_SET: + menuPushDialog(&g_MpSelectRandomWeaponsMenuDialog); + } + + return 0; +} + MenuItemHandlerResult menuhandlerMpAutoRandomWeapon(s32 operation, struct menuitem *item, union handlerdata *data) { static const char *labels[] = { @@ -1223,6 +1372,14 @@ struct menuitem g_MpWeaponsMenuItems[] = { menuhandlerMpWeaponSetDropdown, }, #ifndef PLATFORM_N64 + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Select Weapons\n", + 0, + menuhandlerMpSelectRandomWeapons, + }, { MENUITEMTYPE_DROPDOWN, 0, diff --git a/src/include/data.h b/src/include/data.h index 0840a98a8..bd2383b3d 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -559,6 +559,9 @@ extern s32 g_MusicDisableMpDeath; extern s32 g_BgunGeMuzzleFlashes; extern s32 g_FileAutoSelect; +extern u8 g_MpWeaponSetRandomFilters[NUM_MPWEAPONS]; +extern s32 g_MpWeaponRandomFilterNum; + extern s32 g_NetMode; #define PLAYER_EXTCFG() g_PlayerExtCfg[g_Vars.currentplayerstats->mpindex & 3] diff --git a/src/include/game/mplayer/mplayer.h b/src/include/game/mplayer/mplayer.h index b2ab42f95..3d2591ca7 100644 --- a/src/include/game/mplayer/mplayer.h +++ b/src/include/game/mplayer/mplayer.h @@ -34,6 +34,9 @@ s32 func0f189058(bool full); s32 func0f189088(void); char *mpGetWeaponSetName(s32 arg0); void func0f18913c(void); +#ifndef PLATFORM_N64 +void mpSetRandomWeapons(u8 weapons[]); +#endif void mpApplyWeaponSet(void); void mpSetWeaponSet(s32 weaponsetnum); void func0f1895e8(void);