From 550fe9a7de028a57a391b52ac6c026501b14a8f9 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Wed, 14 Jan 2026 12:04:33 +0100 Subject: [PATCH] Refactor pokerus and add configs (#7731) Co-authored-by: Bassoonian --- data/scripts/debug.inc | 7 + include/config/pokerus.h | 21 + include/constants/generational_changes.h | 15 +- include/generational_changes.h | 4 +- include/pokemon.h | 7 +- include/pokerus.h | 14 + include/random.h | 5 + src/battle_main.c | 6 +- src/clock.c | 3 +- src/debug.c | 118 +++- src/field_specials.c | 8 - src/generational_changes.c | 21 +- src/party_menu.c | 3 +- src/pokemon.c | 161 +----- src/pokemon_summary_screen.c | 3 +- src/pokerus.c | 218 +++++++ test/pokerus.c | 704 +++++++++++++++++++++++ test/test_runner.c | 2 + 18 files changed, 1143 insertions(+), 177 deletions(-) create mode 100644 include/config/pokerus.h create mode 100644 include/pokerus.h create mode 100644 src/pokerus.c create mode 100644 test/pokerus.c diff --git a/data/scripts/debug.inc b/data/scripts/debug.inc index 37e55d4059..6f5a4e3ff6 100644 --- a/data/scripts/debug.inc +++ b/data/scripts/debug.inc @@ -441,6 +441,13 @@ Debug_EventScript_SetFriendship:: releaseall end +Debug_EventScript_GivePokerus:: + special ChoosePartyMon + waitstate + callnative DebugNative_Party_SetPokerus + releaseall + end + Debug_EventScript_InflictStatus1_Single_Poison: setstatus1 STATUS1_POISON, VAR_0x8004 releaseall diff --git a/include/config/pokerus.h b/include/config/pokerus.h new file mode 100644 index 0000000000..689b082856 --- /dev/null +++ b/include/config/pokerus.h @@ -0,0 +1,21 @@ +#ifndef GUARD_CONFIG_POKERUS_H +#define GUARD_CONFIG_POKERUS_H + +//For Pokérus, we refer to infection as a Pokémon catching Pokérus from an enemy Pokémon (trainer or wild) and we refer to spreading as Pokémon catching Pokérus from another infected Pokémon in the party +#define P_POKERUS_ENABLED TRUE // If FALSE, Pokérus will have no effect, won't be shown and won't be aquired in any way but save data won't be affected +#define P_POKERUS_STRAIN_DISTRIBUTION GEN_LATEST // Pokérus has 16 different strains and their probability distribution change depending on generation, GEN_3 will use the Ruby/Sapphire version and GEN_4 will use the version used in Emerald and Gen 4 +#define P_POKERUS_SPREAD_ADJACENCY GEN_LATEST // In Gen 2, Pokérus spread to one adjacent Pokémon but it spreads to both adjacent Pokémon in gen 3+ +#define P_POKERUS_SPREAD_DAYS_LEFT GEN_LATEST // In Gen 2, a freshly spreaded Pokémon will get its full infection duration based on strain. In gen 3+, the Pokérus duration will copy the duration from the Pokémon it was spreaded from +#define P_POKERUS_INFECT_AGAIN GEN_LATEST // If Gen 2 only, your party can get infected even when it is already infected with Pokérus (doesn't affect spreading) +#define P_POKERUS_VISIBLE_ON_EGG GEN_LATEST // Controls if eggs can show Pokérus symbol in summary screen (TRUE from gen 3 to 6, FALSE in gen 2,7 and 8) +#define P_POKERUS_INFECT_EGG FALSE // If TRUE, eggs can receive Pokérus from spread and direction infection (gen 2). If FALSE, eggs can receive Pokérus from spread but not direct infection (gen 3). Behavior is unknown in other gens. +#define P_POKERUS_FLAG_INFECTION 0 // If Pokérus can only get infected if this flag is set or undefined (0). This emulates a gen 2 mechanic where Pokémon can only get infected by Pokérus after visiting Goldernrod. This does not affect spreading + +// Weird Pokérus behaviors that could be considered bugs. They are TRUE in vanilla Emerald but set to FALSE by default in Expansion (behaviors in other gens is unknown) +#define P_POKERUS_HERD_IMMUNITY FALSE // If TRUE, Pokémon that have been previously infected by the Pokérus in your party reduce the chances of your party getting infected by the Pokérus (because they can be rolled at the target of the infection but are now "immune") +#define P_POKERUS_WEAK_VARIANT FALSE // If TRUE, the variant 0 of Pokérus can be erased by stronger variant when Pokérus is spreading + +#define P_POKERUS_INFECTION_ODDS 3 // Actual probability is POKERUS_INFECTION_ODDS/65536 +#define P_POKERUS_SPREAD_ODDS 21846 // Actual probability is POKERUS_SPREAD_ODDS/65536 (21846 should correspond to the vanilla Random() % 3) + +#endif // GUARD_CONFIG_POKERUS_H diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 217ddfcf25..c764712bae 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -2,7 +2,7 @@ #define GUARD_CONSTANTS_GENERATIONAL_CHANGES_H /* Config definitions */ -#define CONFIG_DEFINITIONS(F) \ +#define BATTLE_CONFIG_DEFINITIONS(F) \ /* Calculation settings */ \ F(CRIT_CHANCE, critChance, (u32, GEN_COUNT - 1)) \ F(CRIT_MULTIPLIER, critMultiplier, (u32, GEN_COUNT - 1)) \ @@ -210,6 +210,16 @@ F(COUNTER_TRY_HIT_PARTNER, counterTryHitPartner, (u32, GEN_COUNT - 1)) \ +#define POKEMON_CONFIG_DEFINITIONS(F) \ + F(POKERUS_ENABLED, pokerusEnabled, (u32, TRUE)) \ + F(POKERUS_SPREAD_ADJACENCY, pokerusSpreadAdjacency, (u32, GEN_COUNT - 1)) \ + F(POKERUS_SPREAD_DAYS_LEFT, pokerusSpreadDaysLeft, (u32, GEN_COUNT - 1)) \ + F(POKERUS_INFECT_AGAIN, pokerusInfectAgain, (u32, GEN_COUNT - 1)) \ + F(POKERUS_INFECT_EGG, pokerusInfectEgg, (u32, TRUE)) \ + F(POKERUS_HERD_IMMUNITY, pokerusHerdImmunity, (u32, TRUE)) \ + F(POKERUS_WEAK_VARIANT, pokerusWeakVariant, (u32, TRUE)) \ + + #define GET_CONFIG_MAXIMUM(_typeMaxValue, ...) INVOKE_WITH_B(GET_CONFIG_MAXIMUM_, _typeMaxValue) #define GET_CONFIG_MAXIMUM_(_type, ...) FIRST(__VA_OPT__(FIRST(__VA_ARGS__),) MAX_BITS((sizeof(_type) * 8))) @@ -217,7 +227,8 @@ enum ConfigTag { - CONFIG_DEFINITIONS(UNPACK_CONFIG_ENUMS) + BATTLE_CONFIG_DEFINITIONS(UNPACK_CONFIG_ENUMS) + POKEMON_CONFIG_DEFINITIONS(UNPACK_CONFIG_ENUMS) CONFIG_COUNT }; diff --git a/include/generational_changes.h b/include/generational_changes.h index 2e6767ca84..90668f31b6 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -3,13 +3,15 @@ #include "constants/generational_changes.h" #include "config/battle.h" +#include "config/pokerus.h" #define UNPACK_CONFIG_STRUCT(_name, _field, _typeMaxValue, ...) INVOKE_WITH_(UNPACK_CONFIG_STRUCT_, _field, UNPACK_B(_typeMaxValue)); #define UNPACK_CONFIG_STRUCT_(_field, _type, ...) _type FIRST(__VA_OPT__(_field:BIT_SIZE(FIRST(__VA_ARGS__)),) _field) struct GenChanges { - CONFIG_DEFINITIONS(UNPACK_CONFIG_STRUCT) + BATTLE_CONFIG_DEFINITIONS(UNPACK_CONFIG_STRUCT) + POKEMON_CONFIG_DEFINITIONS(UNPACK_CONFIG_STRUCT) // Expands to: // u32 critChance:4; // u32 critMultiplier:4; diff --git a/include/pokemon.h b/include/pokemon.h index a8f69d779c..10ebbd9d25 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -65,6 +65,8 @@ enum MonData { MON_DATA_FRIENDSHIP, MON_DATA_SMART, MON_DATA_POKERUS, + MON_DATA_POKERUS_STRAIN, + MON_DATA_POKERUS_DAYS_LEFT, MON_DATA_MET_LOCATION, MON_DATA_MET_LEVEL, MON_DATA_MET_GAME, @@ -832,11 +834,6 @@ void AdjustFriendship(struct Pokemon *mon, u8 event); u8 CalculateFriendshipBonuses(struct Pokemon *mon, u32 modifier, enum HoldEffect itemHoldEffect); void MonGainEVs(struct Pokemon *mon, u16 defeatedSpecies); u16 GetMonEVCount(struct Pokemon *mon); -void RandomlyGivePartyPokerus(struct Pokemon *party); -u8 CheckPartyPokerus(struct Pokemon *party, u8 selection); -u8 CheckPartyHasHadPokerus(struct Pokemon *party, u8 selection); -void UpdatePartyPokerusTime(u16 days); -void PartySpreadPokerus(struct Pokemon *party); bool8 TryIncrementMonLevel(struct Pokemon *mon); u8 CanLearnTeachableMove(u16 species, enum Move move); u32 GetRelearnerLevelUpMoves(struct Pokemon *mon, u16 *moves); diff --git a/include/pokerus.h b/include/pokerus.h new file mode 100644 index 0000000000..010071197e --- /dev/null +++ b/include/pokerus.h @@ -0,0 +1,14 @@ +#ifndef GUARD_POKERUS_H +#define GUARD_POKERUS_H + +u32 GetDaysLeftBasedOnStrain(u32 strain); +void RandomlyGivePartyPokerus(void); +bool32 IsPokerusInParty(void); +bool32 CheckMonPokerus(struct Pokemon *mon); +bool32 CheckMonHasHadPokerus(struct Pokemon *mon); +bool32 ShouldPokemonShowActivePokerus(struct Pokemon *mon); +bool32 ShouldPokemonShowCuredPokerus(struct Pokemon *mon); +void UpdatePartyPokerusTime(u32 days); +void PartySpreadPokerus(void); + +#endif // GUARD_POKERUS_H diff --git a/include/random.h b/include/random.h index 1a8f790ca9..7afdac5974 100644 --- a/include/random.h +++ b/include/random.h @@ -218,6 +218,11 @@ enum RandomTag RNG_AI_RANDOM_VALID_SWITCHIN_MID_BATTLE, RNG_HEALER, RNG_DEXNAV_ENCOUNTER_LEVEL, + RNG_POKERUS_PARTY_MEMBER, + RNG_POKERUS_INFECTION, + RNG_POKERUS_STRAIN_DISTRIBUTION, + RNG_POKERUS_SPREAD, + RNG_POKERUS_SPREAD_SIDE, RNG_AI_ASSUME_STATUS_SLEEP, RNG_AI_ASSUME_STATUS_NONVOLATILE, RNG_AI_ASSUME_STATUS_HIGH_ODDS, diff --git a/src/battle_main.c b/src/battle_main.c index 7aa24bbcb0..81dde82873 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -43,6 +43,7 @@ #include "pokeball.h" #include "pokedex.h" #include "pokemon.h" +#include "pokerus.h" #include "random.h" #include "recorded_battle.h" #include "roamer.h" @@ -5665,8 +5666,9 @@ static void ReturnFromBattleToOverworld(void) { if (!(gBattleTypeFlags & BATTLE_TYPE_LINK)) { - RandomlyGivePartyPokerus(gPlayerParty); - PartySpreadPokerus(gPlayerParty); + CalculatePlayerPartyCount(); + RandomlyGivePartyPokerus(); + PartySpreadPokerus(); } if (gBattleTypeFlags & BATTLE_TYPE_LINK && gReceivedRemoteLinkPlayers) diff --git a/src/clock.c b/src/clock.c index 2440f5b5a6..a806bdd1a8 100644 --- a/src/clock.c +++ b/src/clock.c @@ -5,9 +5,10 @@ #include "event_data.h" #include "field_specials.h" #include "field_weather.h" -#include "main.h" #include "lottery_corner.h" +#include "main.h" #include "overworld.h" +#include "pokerus.h" #include "rtc.h" #include "time_events.h" #include "tv.h" diff --git a/src/debug.c b/src/debug.c index 552ea65a4f..13c67c05ee 100644 --- a/src/debug.c +++ b/src/debug.c @@ -285,6 +285,7 @@ static void DebugAction_PCBag_ClearBag(u8 taskId); static void DebugAction_PCBag_ClearBoxes(u8 taskId); static void DebugAction_Party_HealParty(u8 taskId); +static void DebugAction_Party_ClearPokerus(u8 taskId); static void DebugAction_Party_ClearParty(u8 taskId); static void DebugAction_Party_SetParty(u8 taskId); static void DebugAction_Party_BattleSingle(u8 taskId); @@ -366,6 +367,7 @@ extern const u8 Debug_FlagsAndVarNotSetBattleConfigMessage[]; extern const u8 Debug_EventScript_FontTest[]; extern const u8 Debug_EventScript_CheckEVs[]; extern const u8 Debug_EventScript_CheckIVs[]; +extern const u8 Debug_EventScript_GivePokerus[]; extern const u8 Debug_EventScript_InflictStatus1[]; extern const u8 Debug_EventScript_KoPokemon[]; extern const u8 Debug_EventScript_SetHiddenNature[]; @@ -612,6 +614,8 @@ static const struct DebugMenuOption sDebugMenu_Actions_Party[] = { COMPOUND_STRING("Edit Pokemon"), DebugAction_OpenSubMenu, sDebugMenu_Actions_EditPokemon }, { COMPOUND_STRING("Check EVs"), DebugAction_ExecuteScript, Debug_EventScript_CheckEVs }, { COMPOUND_STRING("Check IVs"), DebugAction_ExecuteScript, Debug_EventScript_CheckIVs }, + { COMPOUND_STRING("Give Pokerus"), DebugAction_ExecuteScript, Debug_EventScript_GivePokerus }, + { COMPOUND_STRING("Clear Pokerus"), DebugAction_Party_ClearPokerus}, { COMPOUND_STRING("Clear Party"), DebugAction_Party_ClearParty }, { COMPOUND_STRING("Set Party"), DebugAction_Party_SetParty }, { COMPOUND_STRING("Start Debug Battle"), DebugAction_Party_BattleSingle }, @@ -4708,15 +4712,127 @@ void DebugNative_Party_SetFriendship(void) } } -#undef tPartyId #undef tFriendship +#define tStrain data[6] + +static void Debug_Display_PokerusDaysLeftInfo(s32 daysLeft, s32 strain, u32 digit, u8 windowId) +{ + ConvertIntToDecimalStringN(gStringVar1, daysLeft, STR_CONV_MODE_LEADING_ZEROS, 2); + + if (daysLeft == 0 && strain) + StringCopy(gStringVar2, COMPOUND_STRING("Inactive")); + else if (daysLeft == 0) + StringCopy(gStringVar2, COMPOUND_STRING("No Pokerus")); + else + StringCopy(gStringVar2, COMPOUND_STRING("")); + StringCopy(gStringVar3, gText_DigitIndicator[digit]); + StringExpandPlaceholders(gStringVar4, COMPOUND_STRING("Days Left:\n{STR_VAR_1}\n{STR_VAR_2}{CLEAR_TO 90}\n{STR_VAR_3}")); + AddTextPrinterParameterized(windowId, DEBUG_MENU_FONT, gStringVar4, 0, 0, 0, NULL); +} + +static void DebugNativeStep_Party_SetPokerusDaysLeftSelect(u8 taskId) +{ + if (JOY_NEW(A_BUTTON)) + { + PlaySE(SE_SELECT); + SetMonData(&gPlayerParty[gTasks[taskId].tPartyId], MON_DATA_POKERUS_DAYS_LEFT, &gTasks[taskId].tInput); + DebugNativeStep_CloseDebugWindow(taskId); + return; + } + else if (JOY_NEW(B_BUTTON)) + { + PlaySE(SE_SELECT); + DebugNativeStep_CloseDebugWindow(taskId); + return; + } + + Debug_HandleInput_Numeric(taskId, 0, 15, 2); + + if (JOY_NEW(DPAD_ANY) || JOY_NEW(A_BUTTON)) + Debug_Display_PokerusDaysLeftInfo(gTasks[taskId].tInput, gTasks[taskId].tStrain, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); +} + +static void Debug_Display_PokerusStrainInfo(s32 strain, u32 digit, u8 windowId) +{ + ConvertIntToDecimalStringN(gStringVar1, strain, STR_CONV_MODE_LEADING_ZEROS, 2); + StringCopy(gStringVar3, gText_DigitIndicator[digit]); + StringExpandPlaceholders(gStringVar4, COMPOUND_STRING("Strain:\n{STR_VAR_1}\n\n{STR_VAR_3}")); + AddTextPrinterParameterized(windowId, DEBUG_MENU_FONT, gStringVar4, 0, 0, 0, NULL); +} + +static void DebugNativeStep_Party_SetPokerusStrainSelect(u8 taskId) +{ + if (JOY_NEW(A_BUTTON)) + { + PlaySE(SE_SELECT); + gTasks[taskId].tStrain = gTasks[taskId].tInput; + SetMonData(&gPlayerParty[gTasks[taskId].tPartyId], MON_DATA_POKERUS_STRAIN, &gTasks[taskId].tInput); + gTasks[taskId].tInput = GetMonData(&gPlayerParty[gTasks[taskId].tPartyId], MON_DATA_POKERUS_DAYS_LEFT); + Debug_Display_PokerusDaysLeftInfo(gTasks[taskId].tInput, gTasks[taskId].tStrain, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); + gTasks[taskId].func = DebugNativeStep_Party_SetPokerusDaysLeftSelect; + return; + } + else if (JOY_NEW(B_BUTTON)) + { + PlaySE(SE_SELECT); + DebugNativeStep_CloseDebugWindow(taskId); + return; + } + + Debug_HandleInput_Numeric(taskId, 0, 15, 2); + + if (JOY_NEW(DPAD_ANY) || JOY_NEW(A_BUTTON)) + Debug_Display_PokerusStrainInfo(gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); +} + +static void DebugNativeStep_Party_SetPokerusMain(u8 taskId) +{ + u8 windowId = DebugNativeStep_CreateDebugWindow(); + u32 strain = GetMonData(&gPlayerParty[gTasks[taskId].tPartyId], MON_DATA_POKERUS_STRAIN); + + // Display initial flag + Debug_Display_PokerusStrainInfo(strain, 0, windowId); + + gTasks[taskId].func = DebugNativeStep_Party_SetPokerusStrainSelect; + gTasks[taskId].tSubWindowId = windowId; + gTasks[taskId].tStrain = strain; + gTasks[taskId].tInput = strain; + gTasks[taskId].tDigit = 0; + gTasks[taskId].tPartyId = 0; +} + +void DebugNative_Party_SetPokerus(void) +{ + if (gSpecialVar_0x8004 < PARTY_SIZE) + { + u32 taskId = CreateTask(DebugNativeStep_Party_SetPokerusMain, 1); + gTasks[taskId].tPartyId = gSpecialVar_0x8004; + } +} + +#undef tStrain +#undef tPartyId + #undef tMenuTaskId #undef tWindowId #undef tSubWindowId #undef tInput #undef tDigit +static void DebugAction_Party_ClearPokerus(u8 taskId) +{ + for (u32 i = 0; i < PARTY_SIZE; i++) + { + if (!GetMonData(&gPlayerParty[i], MON_DATA_SPECIES)) + continue; + u32 data = 0; + SetMonData(&gPlayerParty[i], MON_DATA_POKERUS, &data); + } + ScriptContext_Enable(); + Debug_DestroyMenu_Full(taskId); +} + static void DebugAction_Party_ClearParty(u8 taskId) { ZeroPlayerPartyMons(); diff --git a/src/field_specials.c b/src/field_specials.c index 37b0516be1..275cbd53d9 100644 --- a/src/field_specials.c +++ b/src/field_specials.c @@ -1519,14 +1519,6 @@ bool8 ScriptCheckFreePokemonStorageSpace(void) return CheckFreePokemonStorageSpace(); } -bool8 IsPokerusInParty(void) -{ - if (!CheckPartyPokerus(gPlayerParty, (1 << PARTY_SIZE) - 1)) - return FALSE; - - return TRUE; -} - // Task data for Task_ShakeCamera #define tHorizontalPan data[0] #define tDelayCounter data[1] diff --git a/src/generational_changes.c b/src/generational_changes.c index a02b6ce944..a045d14f8f 100644 --- a/src/generational_changes.c +++ b/src/generational_changes.c @@ -2,12 +2,15 @@ #include "generational_changes.h" #include "malloc.h" #include "constants/generational_changes.h" +#include "config/pokerus.h" -#define UNPACK_CONFIG_GEN_CHANGES2(_name, _field, ...) ._field = B_##_name, +#define UNPACK_BATTLE_CONFIG_GEN_CHANGES(_name, _field, ...) ._field = B_##_name, +#define UNPACK_POKEMON_CONFIG_GEN_CHANGES(_name, _field, ...) ._field = P_##_name, const struct GenChanges sConfigChanges = { - CONFIG_DEFINITIONS(UNPACK_CONFIG_GEN_CHANGES2) + BATTLE_CONFIG_DEFINITIONS(UNPACK_BATTLE_CONFIG_GEN_CHANGES) + POKEMON_CONFIG_DEFINITIONS(UNPACK_POKEMON_CONFIG_GEN_CHANGES) /* Expands to: .critChance = B_CRIT_CHANCE, .critMultiplier = B_CRIT_MULTIPLIER, @@ -29,8 +32,6 @@ EWRAM_DATA struct GenChanges *gConfigChangesTestOverride = NULL; #define UNPACK_CONFIG_SETTERS(_name, _field, ...) case CONFIG_##_name: return; #endif -// Gets the value of a volatile status flag for a certain battler -// Primarily used for the debug menu and scripts. Outside of it explicit references are preferred u32 GetConfig(enum ConfigTag _genConfig) { #if TESTING @@ -38,7 +39,8 @@ u32 GetConfig(enum ConfigTag _genConfig) { switch (_genConfig) { - CONFIG_DEFINITIONS(UNPACK_CONFIG_GETTERS) + BATTLE_CONFIG_DEFINITIONS(UNPACK_CONFIG_GETTERS) + POKEMON_CONFIG_DEFINITIONS(UNPACK_CONFIG_GETTERS) /* Expands to: case CONFIG_CRIT_CHANCE: return gConfigChangesTestOverride->critChance; @@ -52,7 +54,8 @@ u32 GetConfig(enum ConfigTag _genConfig) { switch (_genConfig) { - CONFIG_DEFINITIONS(UNPACK_CONFIG_OVERRIDE_GETTERS) + BATTLE_CONFIG_DEFINITIONS(UNPACK_CONFIG_OVERRIDE_GETTERS) + POKEMON_CONFIG_DEFINITIONS(UNPACK_CONFIG_OVERRIDE_GETTERS) /* Expands to: case CONFIG_CRIT_CHANCE: return sConfigChanges.critChance; @@ -69,7 +72,8 @@ u32 GetClampedValue(enum ConfigTag _genConfig, u32 newValue) u32 clampedValue = 0; switch(_genConfig) { - CONFIG_DEFINITIONS(UNPACK_CONFIG_CLAMPER) + BATTLE_CONFIG_DEFINITIONS(UNPACK_CONFIG_CLAMPER) + POKEMON_CONFIG_DEFINITIONS(UNPACK_CONFIG_CLAMPER) default: return 0; } @@ -84,7 +88,8 @@ void SetConfig(enum ConfigTag _genConfig, u32 _value) u32 clampedValue = GetClampedValue(_genConfig, _value); switch (_genConfig) { - CONFIG_DEFINITIONS(UNPACK_CONFIG_SETTERS) + BATTLE_CONFIG_DEFINITIONS(UNPACK_CONFIG_SETTERS) + POKEMON_CONFIG_DEFINITIONS(UNPACK_CONFIG_SETTERS) /* Expands to: #if TESTING case CONFIG_CRIT_CHANCE: diff --git a/src/party_menu.c b/src/party_menu.c index 3f22260d0e..910e4d60b0 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -53,6 +53,7 @@ #include "pokemon_jump.h" #include "pokemon_storage_system.h" #include "pokemon_summary_screen.h" +#include "pokerus.h" #include "region_map.h" #include "reshow_battle_screen.h" #include "scanline_effect.h" @@ -2172,7 +2173,7 @@ u8 GetMonAilment(struct Pokemon *mon) ailment = GetAilmentFromStatus(GetMonData(mon, MON_DATA_STATUS)); if (ailment != AILMENT_NONE) return ailment; - if (CheckPartyPokerus(mon, 0)) + if (ShouldPokemonShowActivePokerus(mon)) return AILMENT_PKRS; return AILMENT_NONE; } diff --git a/src/pokemon.c b/src/pokemon.c index 889b24fcf4..f74669ec6e 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -39,6 +39,7 @@ #include "pokemon_icon.h" #include "pokemon_summary_screen.h" #include "pokemon_storage_system.h" +#include "pokerus.h" #include "random.h" #include "recorded_battle.h" #include "regions.h" @@ -2387,6 +2388,12 @@ u32 GetBoxMonData3(struct BoxPokemon *boxMon, s32 field, u8 *data) case MON_DATA_POKERUS: retVal = GetSubstruct3(boxMon)->pokerus; break; + case MON_DATA_POKERUS_STRAIN: + retVal = ((GetSubstruct3(boxMon)->pokerus & 0xF0) >> 4); + break; + case MON_DATA_POKERUS_DAYS_LEFT: + retVal = (GetSubstruct3(boxMon)->pokerus & 0x0F); + break; case MON_DATA_MET_LOCATION: retVal = GetSubstruct3(boxMon)->metLocation; break; @@ -2896,6 +2903,12 @@ void SetBoxMonData(struct BoxPokemon *boxMon, s32 field, const void *dataArg) case MON_DATA_POKERUS: SET8(GetSubstruct3(boxMon)->pokerus); break; + case MON_DATA_POKERUS_STRAIN: + GetSubstruct3(boxMon)->pokerus = (*data << 4) | (GetSubstruct3(boxMon)->pokerus & 0x0F); + break; + case MON_DATA_POKERUS_DAYS_LEFT: + GetSubstruct3(boxMon)->pokerus = (GetSubstruct3(boxMon)->pokerus & 0xF0) | *data; + break; case MON_DATA_MET_LOCATION: SET8(GetSubstruct3(boxMon)->metLocation); break; @@ -5236,7 +5249,7 @@ void MonGainEVs(struct Pokemon *mon, u16 defeatedSpecies) if (totalEVs >= currentEVCap) break; - if (CheckPartyHasHadPokerus(mon, 0)) + if (CheckMonHasHadPokerus(mon)) multiplier = 2; else multiplier = 1; @@ -5313,152 +5326,6 @@ u16 GetMonEVCount(struct Pokemon *mon) return count; } -void RandomlyGivePartyPokerus(struct Pokemon *party) -{ - u16 rnd = Random(); - if (rnd == 0x4000 || rnd == 0x8000 || rnd == 0xC000) - { - struct Pokemon *mon; - - do - { - rnd = Random() % PARTY_SIZE; - mon = &party[rnd]; - } - while (!GetMonData(mon, MON_DATA_SPECIES, 0) || GetMonData(mon, MON_DATA_IS_EGG, 0)); - - if (!(CheckPartyHasHadPokerus(party, 1u << rnd))) - { - u8 rnd2; - - do - { - rnd2 = Random(); - } - while ((rnd2 & 0x7) == 0); - - if (rnd2 & 0xF0) - rnd2 &= 0x7; - - rnd2 |= (rnd2 << 4); - rnd2 &= 0xF3; - rnd2++; - - SetMonData(&party[rnd], MON_DATA_POKERUS, &rnd2); - } - } -} - -u8 CheckPartyPokerus(struct Pokemon *party, u8 selection) -{ - u8 retVal; - - int partyIndex = 0; - unsigned curBit = 1; - retVal = 0; - - if (selection) - { - do - { - if ((selection & 1) && (GetMonData(&party[partyIndex], MON_DATA_POKERUS, 0) & 0xF)) - retVal |= curBit; - partyIndex++; - curBit <<= 1; - selection >>= 1; - } - while (selection); - } - else if (GetMonData(&party[0], MON_DATA_POKERUS, 0) & 0xF) - { - retVal = 1; - } - - return retVal; -} - -u8 CheckPartyHasHadPokerus(struct Pokemon *party, u8 selection) -{ - u8 retVal; - - int partyIndex = 0; - unsigned curBit = 1; - retVal = 0; - - if (selection) - { - do - { - if ((selection & 1) && GetMonData(&party[partyIndex], MON_DATA_POKERUS, 0)) - retVal |= curBit; - partyIndex++; - curBit <<= 1; - selection >>= 1; - } - while (selection); - } - else if (GetMonData(&party[0], MON_DATA_POKERUS, 0)) - { - retVal = 1; - } - - return retVal; -} - -void UpdatePartyPokerusTime(u16 days) -{ - int i; - for (i = 0; i < PARTY_SIZE; i++) - { - if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES, 0)) - { - u8 pokerus = GetMonData(&gPlayerParty[i], MON_DATA_POKERUS, 0); - if (pokerus & 0xF) - { - if ((pokerus & 0xF) < days || days > 4) - pokerus &= 0xF0; - else - pokerus -= days; - - if (pokerus == 0) - pokerus = 0x10; - - SetMonData(&gPlayerParty[i], MON_DATA_POKERUS, &pokerus); - } - } - } -} - -void PartySpreadPokerus(struct Pokemon *party) -{ - if ((Random() % 3) == 0) - { - int i; - for (i = 0; i < PARTY_SIZE; i++) - { - if (GetMonData(&party[i], MON_DATA_SPECIES, 0)) - { - u8 pokerus = GetMonData(&party[i], MON_DATA_POKERUS, 0); - u8 curPokerus = pokerus; - if (pokerus) - { - if (pokerus & 0xF) - { - // Spread to adjacent party members. - if (i != 0 && !(GetMonData(&party[i - 1], MON_DATA_POKERUS, 0) & 0xF0)) - SetMonData(&party[i - 1], MON_DATA_POKERUS, &curPokerus); - if (i != (PARTY_SIZE - 1) && !(GetMonData(&party[i + 1], MON_DATA_POKERUS, 0) & 0xF0)) - { - SetMonData(&party[i + 1], MON_DATA_POKERUS, &curPokerus); - i++; - } - } - } - } - } - } -} - bool8 TryIncrementMonLevel(struct Pokemon *mon) { u16 species = GetMonData(mon, MON_DATA_SPECIES, 0); diff --git a/src/pokemon_summary_screen.c b/src/pokemon_summary_screen.c index 6c4702da94..f0a4632bb9 100644 --- a/src/pokemon_summary_screen.c +++ b/src/pokemon_summary_screen.c @@ -33,6 +33,7 @@ #include "pokemon_sprite_visualizer.h" #include "pokemon_storage_system.h" #include "pokemon_summary_screen.h" +#include "pokerus.h" #include "region_map.h" #include "scanline_effect.h" #include "sound.h" @@ -3129,7 +3130,7 @@ static void TilemapFiveMovesDisplay(u16 *dst, u16 palette, bool8 remove) static void DrawPokerusCuredSymbol(struct Pokemon *mon) // This checks if the mon has been cured of pokerus { - if (!CheckPartyPokerus(mon, 0) && CheckPartyHasHadPokerus(mon, 0)) // If yes it draws the cured symbol + if (ShouldPokemonShowCuredPokerus(mon)) { sMonSummaryScreen->bgTilemapBuffers[PSS_PAGE_INFO][0][0x223] = 0x2C; sMonSummaryScreen->bgTilemapBuffers[PSS_PAGE_INFO][1][0x223] = 0x2C; diff --git a/src/pokerus.c b/src/pokerus.c new file mode 100644 index 0000000000..9e769d9b8c --- /dev/null +++ b/src/pokerus.c @@ -0,0 +1,218 @@ +#include "global.h" +#include "event_data.h" +#include "generational_changes.h" +#include "pokemon.h" +#include "pokerus.h" +#include "random.h" +#include "config/pokerus.h" + +u32 GetDaysLeftBasedOnStrain(u32 strain) +{ + u32 daysLeft = (strain % 4) + 1; + return daysLeft; +} + +static u32 GetRandomPokerusStrain(void) +{ + if (P_POKERUS_STRAIN_DISTRIBUTION < GEN_3) // Gen 1 - 2 (Gen 1 had no Pokérus but we default it with gen 2) + return RandomWeighted(RNG_POKERUS_STRAIN_DISTRIBUTION, 15, 30, 30, 30, 30, 30, 30, 30, 30, 1, 1, 1, 1, 1, 1, 1); + else if (P_POKERUS_STRAIN_DISTRIBUTION < GEN_4) //Gen 3 (Ruby/Sapphire only) + return RandomWeighted(RNG_POKERUS_STRAIN_DISTRIBUTION, 30, 31, 31, 31, 31, 31, 31, 31, 1, 1, 1, 1, 1, 1, 1, 1); + else // Gen 4+ (Pokérus was disabled in gen 9 but we default it here) + return RandomWeighted(RNG_POKERUS_STRAIN_DISTRIBUTION, 0, 31, 31, 31, 31, 31, 31, 31, 0, 1, 1, 1, 1, 1, 1, 1); +} + +void RandomlyGivePartyPokerus(void) +{ + if (!GetConfig(CONFIG_POKERUS_ENABLED)) + return; + + if ((GetConfig(CONFIG_POKERUS_INFECT_AGAIN) > GEN_2) && IsPokerusInParty()) + return; + + if (P_POKERUS_FLAG_INFECTION && !FlagGet(P_POKERUS_FLAG_INFECTION)) + return; + + if (RandomUniform(RNG_POKERUS_INFECTION, 0, MAX_u16) < P_POKERUS_INFECTION_ODDS) + { + struct Pokemon *mon; + u32 randomIndex; + u32 validTargetsCount = 0; + s32 validTargets[PARTY_SIZE]; + + for (u32 i = 0; i < PARTY_SIZE; i++) + { + mon = &gPlayerParty[i]; + if (!GetMonData(mon, MON_DATA_SPECIES)) + continue; + else if (!GetConfig(CONFIG_POKERUS_INFECT_EGG) && GetMonData(mon, MON_DATA_IS_EGG)) + continue; + else if (!GetConfig(CONFIG_POKERUS_HERD_IMMUNITY) && CheckMonHasHadPokerus(mon)) + continue; + validTargets[validTargetsCount] = i; + validTargetsCount++; + } + + if (validTargetsCount == 0) + return; + + randomIndex = RandomUniform(RNG_POKERUS_PARTY_MEMBER, 0, validTargetsCount - 1); + mon = &gPlayerParty[validTargets[randomIndex]]; + + if (!CheckMonHasHadPokerus(mon)) + { + u32 strain = GetRandomPokerusStrain(); + u32 daysLeft = GetDaysLeftBasedOnStrain(strain); + + SetMonData(mon, MON_DATA_POKERUS_STRAIN, &strain); + SetMonData(mon, MON_DATA_POKERUS_DAYS_LEFT, &daysLeft); + } + } +} + +bool32 IsPokerusInParty(void) +{ + if (!GetConfig(CONFIG_POKERUS_ENABLED)) + return FALSE; + + for (u32 i = 0; i < PARTY_SIZE; i++) + { + if (!GetMonData(&gPlayerParty[i], MON_DATA_SPECIES)) + continue; + + if (GetMonData(&gPlayerParty[i], MON_DATA_POKERUS_DAYS_LEFT)) + return TRUE; + } + + return FALSE; +} + +bool32 CheckMonPokerus(struct Pokemon *mon) +{ + if (!GetConfig(CONFIG_POKERUS_ENABLED)) + return FALSE; + + if (GetMonData(mon, MON_DATA_POKERUS_DAYS_LEFT)) + return TRUE; + + return FALSE; +} + +bool32 CheckMonHasHadPokerus(struct Pokemon *mon) +{ + if (!GetConfig(CONFIG_POKERUS_ENABLED)) + return FALSE; + + if (GetMonData(mon, MON_DATA_POKERUS)) + return TRUE; + + return FALSE; +} + +bool32 IsPokerusVisible(struct Pokemon *mon) +{ + if ((P_POKERUS_VISIBLE_ON_EGG >= GEN_3 && P_POKERUS_VISIBLE_ON_EGG <= GEN_6) || !GetMonData(mon, MON_DATA_IS_EGG)) + return TRUE; + return FALSE; +} + +bool32 ShouldPokemonShowActivePokerus(struct Pokemon *mon) +{ + if (!IsPokerusVisible(mon)) + return FALSE; + return CheckMonPokerus(mon); +} + +bool32 ShouldPokemonShowCuredPokerus(struct Pokemon *mon) +{ + if (!IsPokerusVisible(mon)) + return FALSE; + if (CheckMonPokerus(mon)) + return FALSE; + return CheckMonHasHadPokerus(mon); +} + +void UpdatePartyPokerusTime(u32 days) +{ + if (!GetConfig(CONFIG_POKERUS_ENABLED)) + return; + + for (u32 i = 0; i < PARTY_SIZE; i++) + { + if (!GetMonData(&gPlayerParty[i], MON_DATA_SPECIES)) + continue; + + u32 strain = GetMonData(&gPlayerParty[i], MON_DATA_POKERUS_STRAIN); + u32 daysLeft = GetMonData(&gPlayerParty[i], MON_DATA_POKERUS_DAYS_LEFT); + if (daysLeft) + { + if (daysLeft < days) + daysLeft = 0; + else + daysLeft -= days; + + //If the strain was 0, we changed it to 1 when the Pokérus disappear to remember the Pokémon was infected by Pokérus + // (otherwise its data would look the same as unaffected Pokémon) + if (daysLeft == 0 && strain == 0) + { + strain = 1; + SetMonData(&gPlayerParty[i], MON_DATA_POKERUS_STRAIN, &strain); + } + + SetMonData(&gPlayerParty[i], MON_DATA_POKERUS_DAYS_LEFT, &daysLeft); + } + } +} + +static void SpreadPokerusToSpecificMon(struct Pokemon *mon, u32 strain, u32 daysLeft) +{ + SetMonData(mon, MON_DATA_POKERUS_STRAIN, &strain); + if (GetConfig(CONFIG_POKERUS_SPREAD_DAYS_LEFT) < GEN_3) + daysLeft = GetDaysLeftBasedOnStrain(strain); + SetMonData(mon, MON_DATA_POKERUS_DAYS_LEFT, &daysLeft); +} + +static bool32 CanReceivePokerusFromSpread(struct Pokemon *mon) +{ + if (GetConfig(CONFIG_POKERUS_WEAK_VARIANT)) + return !GetMonData(mon, MON_DATA_POKERUS_STRAIN); + return !GetMonData(mon, MON_DATA_POKERUS); +} + +void PartySpreadPokerus(void) +{ + if (!GetConfig(CONFIG_POKERUS_ENABLED)) + return; + + if (RandomUniform(RNG_POKERUS_SPREAD, 0, MAX_u16) >= P_POKERUS_SPREAD_ODDS) + return; + + for (u32 i = 0; i < PARTY_SIZE; i++) + { + if (!GetMonData(&gPlayerParty[i], MON_DATA_SPECIES)) + continue; + + u32 strain = GetMonData(&gPlayerParty[i], MON_DATA_POKERUS_STRAIN); + u32 daysLeft = GetMonData(&gPlayerParty[i], MON_DATA_POKERUS_DAYS_LEFT); + if (daysLeft) + { + bool32 spreadUp = TRUE, spreadDown = TRUE; + if (GetConfig(CONFIG_POKERUS_SPREAD_ADJACENCY) < GEN_3) + { + if (i == (gPlayerPartyCount - 1)) + spreadUp = FALSE; + else if (RandomUniform(RNG_POKERUS_SPREAD_SIDE, 0, 1)) + spreadDown = FALSE; + else + spreadUp = FALSE; + } + if (spreadDown && i != 0 && CanReceivePokerusFromSpread(&gPlayerParty[i - 1])) + SpreadPokerusToSpecificMon(&gPlayerParty[i - 1], strain, daysLeft); + if (spreadUp && i != (PARTY_SIZE - 1) && CanReceivePokerusFromSpread(&gPlayerParty[i + 1])) + { + SpreadPokerusToSpecificMon(&gPlayerParty[i + 1], strain, daysLeft); + i++; + } + } + } +} diff --git a/test/pokerus.c b/test/pokerus.c new file mode 100644 index 0000000000..74cc0552fd --- /dev/null +++ b/test/pokerus.c @@ -0,0 +1,704 @@ +#include "global.h" +#include "malloc.h" +#include "event_data.h" +#include "pokemon.h" +#include "pokerus.h" +#include "generational_changes.h" +#include "random.h" +#include "test/overworld_script.h" +#include "test/test.h" +#include "config/pokerus.h" + +TEST("(Pokerus) No infection when POKERUS_ENABLED is false") +{ + bool32 enabled; + PARAMETRIZE { enabled = TRUE; } + PARAMETRIZE { enabled = FALSE; } + SetConfig(CONFIG_POKERUS_ENABLED, enabled); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + + SET_RNG(RNG_POKERUS_INFECTION, 0); + + CalculatePlayerPartyCount(); + RandomlyGivePartyPokerus(); + + EXPECT_EQ((GetMonData(&gPlayerParty[0], MON_DATA_POKERUS) > 0), enabled); +} + +TEST("(Pokerus) RandomlyGivePartyPokerus doesn't freeze if the party is empty") +{ + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + ZeroPlayerPartyMons(); + SET_RNG(RNG_POKERUS_INFECTION, 0); + + CalculatePlayerPartyCount(); + RandomlyGivePartyPokerus(); + + EXPECT_EQ(gPlayerPartyCount, 0); +} + +TEST("(Pokerus) Eggs can only be infected if POKERUS_INFECT_EGG is TRUE") +{ + bool32 infectEgg; + PARAMETRIZE { infectEgg = TRUE; } + PARAMETRIZE { infectEgg = FALSE; } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + SetConfig(CONFIG_POKERUS_INFECT_EGG, infectEgg); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + + bool32 isEgg = TRUE; + SetMonData(&gPlayerParty[0], MON_DATA_IS_EGG, &isEgg); + + SET_RNG(RNG_POKERUS_INFECTION, 0); + + CalculatePlayerPartyCount(); + RandomlyGivePartyPokerus(); + + EXPECT_EQ((GetMonData(&gPlayerParty[0], MON_DATA_POKERUS) > 0), infectEgg); +} + +TEST("(Pokerus) No infection when POKERUS_INFECT_AGAIN is false and you already have active pokerus in party") +{ + u32 infectAgain; + PARAMETRIZE { infectAgain = GEN_2; } + PARAMETRIZE { infectAgain = GEN_3; } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + SetConfig(CONFIG_POKERUS_INFECT_AGAIN, infectAgain); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100; + givemon SPECIES_PIKACHU, 100; + ); + + u8 pokerus = 1; + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS, &pokerus); + + SET_RNG(RNG_POKERUS_INFECTION, 0); + + CalculatePlayerPartyCount(); + RandomlyGivePartyPokerus(); + + EXPECT_EQ((GetMonData(&gPlayerParty[1], MON_DATA_POKERUS) > 0), infectAgain == GEN_2); +} + +TEST("(Pokerus) Test POKERUS_HERD_IMMUNITY config in RandomlyGivePartyPokerus") +{ + u32 herdImmunity; + PARAMETRIZE { herdImmunity = TRUE; } + PARAMETRIZE { herdImmunity = FALSE; } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + SetConfig(CONFIG_POKERUS_HERD_IMMUNITY, herdImmunity); + SetConfig(CONFIG_POKERUS_INFECT_AGAIN, GEN_2); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100; + givemon SPECIES_PIKACHU, 100; + ); + + u8 pokerus = 1; + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS, &pokerus); + + SET_RNG(RNG_POKERUS_INFECTION, 0); + SET_RNG(RNG_POKERUS_PARTY_MEMBER, 0); + + CalculatePlayerPartyCount(); + RandomlyGivePartyPokerus(); + + EXPECT_EQ((GetMonData(&gPlayerParty[1], MON_DATA_POKERUS) == 0), herdImmunity); +} + +#if P_POKERUS_FLAG_INFECTION +TEST("(Pokerus) No infection when P_POKERUS_FLAG_INFECTION is clear") +{ + u32 flag; + PARAMETRIZE { flag = TRUE; } + PARAMETRIZE { flag = FALSE; } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100; + givemon SPECIES_PIKACHU, 100; + ); + + u8 pokerus = 1; + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS, &pokerus); + + if (flag) + FlagSet(P_POKERUS_FLAG_INFECTION); + else + FlagClear(P_POKERUS_FLAG_INFECTION); + + SET_RNG(RNG_POKERUS_INFECTION, 0); + + CalculatePlayerPartyCount(); + RandomlyGivePartyPokerus(); + + EXPECT_EQ((GetMonData(&gPlayerParty[1], MON_DATA_POKERUS) > 0), flag); +} +#endif + +TEST("(Pokerus) Test GetMonData for MON_DATA_POKERUS_DAYS_LEFT and MON_DATA_POKERUS_STRAIN") +{ + u32 strain = 0; + u32 daysLeft = 0; + for (u32 i = 0; i < 16; i++) + { + for (u32 j = 0; j < 16; j++) + { + PARAMETRIZE { strain = i; daysLeft = j;} + } + } + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100; + ); + + u8 pokerus = (strain << 4) | daysLeft; + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS, &pokerus); + + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS_STRAIN), strain); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS_DAYS_LEFT), daysLeft); +} + +TEST("(Pokerus) Test SetMonData for MON_DATA_POKERUS_DAYS_LEFT and MON_DATA_POKERUS_STRAIN") +{ + u32 strain = 0; + u32 daysLeft = 0; + for (u32 i = 0; i < 16; i++) + { + for (u32 j = 0; j < 16; j++) + { + PARAMETRIZE { strain = i; daysLeft = j; } + } + } + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100; + ); + + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS_STRAIN, &strain); + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS_DAYS_LEFT, &daysLeft); + + u8 pokerus = (strain << 4) | daysLeft; + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS), pokerus); +} + +TEST("(Pokerus) Test IsPokerusInParty general behavior") +{ + u32 enabled = 0; + u32 partyMember = 0; + u32 pokerus = 0; + for (u32 i = 0; i < PARTY_SIZE; i++) + { + for (u32 j = 0; j <= MAX_u8; j++) + { + if ((j & 0x0F) == 0) + continue; + PARAMETRIZE { enabled = TRUE; partyMember = i; pokerus = j; } + PARAMETRIZE { enabled = FALSE; partyMember = i, pokerus = j; } + } + } + SetConfig(CONFIG_POKERUS_ENABLED, enabled); + + ZeroPlayerPartyMons(); + for (u32 i = 0; i < PARTY_SIZE; i++) + { + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + } + + EXPECT_EQ(IsPokerusInParty(), FALSE); + + u32 tmp = pokerus; + SetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS, &tmp); + + EXPECT_EQ(IsPokerusInParty(), enabled); +} + +TEST("(Pokerus) Test CheckMonPokerus general behavior") +{ + u32 enabled = 0; + u32 pokerus = 0; + for (u32 i = 0; i < PARTY_SIZE; i++) + { + for (u32 j = 0; j <= MAX_u8; j++) + { + if ((j & 0x0F) == 0) + continue; + PARAMETRIZE { enabled = TRUE; pokerus = j; } + PARAMETRIZE { enabled = FALSE; pokerus = j; } + } + } + SetConfig(CONFIG_POKERUS_ENABLED, enabled); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + + EXPECT_EQ(CheckMonPokerus(&gPlayerParty[0]), FALSE); + + u32 tmp = pokerus; + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS, &tmp); + + EXPECT_EQ(CheckMonPokerus(&gPlayerParty[0]), enabled); +} + +TEST("(Pokerus) Test CheckMonHasHadPokerus general behavior") +{ + u32 enabled = 0; + u32 pokerus = 0; + for (u32 i = 0; i < PARTY_SIZE; i++) + { + for (u32 j = 1; j <= MAX_u8; j++) + { + PARAMETRIZE { enabled = TRUE; pokerus = j; } + PARAMETRIZE { enabled = FALSE; pokerus = j; } + } + } + SetConfig(CONFIG_POKERUS_ENABLED, enabled); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + + + EXPECT_EQ(CheckMonHasHadPokerus(&gPlayerParty[0]), FALSE); + + u32 tmp = pokerus; + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS, &tmp); + + EXPECT_EQ(CheckMonHasHadPokerus(&gPlayerParty[0]), enabled); +} + +TEST("(Pokerus) Test UpdatePartyPokerusTime general behavior") +{ + u32 enabled = 0; + u32 strain = 0; + s32 daysLeft = 0; + s32 daysPassed = 0; + for (u32 i = 0; i < 16; i++) + { + for (u32 j = 0; j < 16; j++) + { + for (u32 k = 1; k < 4; k++) + { + PARAMETRIZE { enabled = TRUE; strain = i; daysLeft = j; daysPassed = k; } + PARAMETRIZE { enabled = FALSE; strain = i; daysLeft = j; daysPassed = k; } + } + } + } + SetConfig(CONFIG_POKERUS_ENABLED, enabled); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + + + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS_STRAIN, &strain); + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS_DAYS_LEFT, &daysLeft); + UpdatePartyPokerusTime(daysPassed); + + if (enabled) + { + if ( (strain == 0) //Verify strain 0 is modified to strain 1 when timer is up + && (daysLeft > 0) + && ((daysLeft - daysPassed) <= 0) + ) + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS_STRAIN), 1); + else + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS_STRAIN), strain); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS_DAYS_LEFT), max(0, daysLeft - daysPassed)); + } + else + { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS_STRAIN), strain); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS_DAYS_LEFT), daysLeft); + } +} + + +TEST("(Pokerus) Test PartySpreadPokerus general behavior") +{ + u32 partyMember = 0; + u32 pokerus = 0; + for (u32 i = 0; i <= MAX_u8; i++) + { + if ((i & 0x0F) == 0) + continue; + for (u32 k = 0; k < PARTY_SIZE; k++) + { + PARAMETRIZE {pokerus = i; partyMember = k;} + } + } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + SetConfig(CONFIG_POKERUS_SPREAD_DAYS_LEFT, GEN_3); + SetConfig(CONFIG_POKERUS_SPREAD_ADJACENCY, GEN_3); + + ZeroPlayerPartyMons(); + for (u32 i = 0; i < PARTY_SIZE; i++) + { + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + } + + SetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS, &pokerus); + SET_RNG(RNG_POKERUS_SPREAD, 0); + PartySpreadPokerus(); + + EXPECT_EQ(GetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS), pokerus); + if (partyMember == 0) + { + EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_POKERUS), pokerus); + for (u32 i = 2; i < PARTY_SIZE; i++) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + + } + else if (partyMember == PARTY_SIZE - 1) + { + EXPECT_EQ(GetMonData(&gPlayerParty[PARTY_SIZE - 2], MON_DATA_POKERUS), pokerus); + for (u32 i = 0; i < (PARTY_SIZE - 2); i++) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + } + else + { + for (u32 i = 0; i < PARTY_SIZE; i++) + { + if ((i < (partyMember - 1)) || (i > (partyMember + 1))) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + else + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), pokerus); + } + } +} + +TEST("(Pokerus) Test PartySpreadPokerus: Pokerus can spread to and from eggs") +{ + u32 partyMember = 0; + u32 pokerus = 0; + for (u32 i = 0; i <= MAX_u8; i++) + { + if ((i & 0x0F) == 0) + continue; + for (u32 k = 0; k < PARTY_SIZE; k++) + { + PARAMETRIZE {pokerus = i; partyMember = k;} + } + } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + SetConfig(CONFIG_POKERUS_SPREAD_DAYS_LEFT, GEN_3); + SetConfig(CONFIG_POKERUS_SPREAD_ADJACENCY, GEN_3); + + ZeroPlayerPartyMons(); + for (u32 i = 0; i < PARTY_SIZE; i++) + { + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + bool32 isEgg = TRUE; + SetMonData(&gPlayerParty[i], MON_DATA_IS_EGG, &isEgg); + } + + SetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS, &pokerus); + SET_RNG(RNG_POKERUS_SPREAD, 0); + PartySpreadPokerus(); + + EXPECT_EQ(GetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS), pokerus); + if (partyMember == 0) + { + EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_POKERUS), pokerus); + for (u32 i = 2; i < PARTY_SIZE; i++) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + + } + else if (partyMember == PARTY_SIZE - 1) + { + EXPECT_EQ(GetMonData(&gPlayerParty[PARTY_SIZE - 2], MON_DATA_POKERUS), pokerus); + for (u32 i = 0; i < (PARTY_SIZE - 2); i++) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + } + else + { + for (u32 i = 0; i < PARTY_SIZE; i++) + { + if ((i < (partyMember - 1)) || (i > (partyMember + 1))) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + else + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), pokerus); + } + } +} + +TEST("(Pokerus) Test PartySpreadPokerus: do not spread inactive pokerus") +{ + u32 partyMember = 0; + u32 pokerus = 0; + for (u32 i = 0; i <= MAX_u8; i++) + { + if (i & 0x0F) + continue; + for (u32 k = 0; k < PARTY_SIZE; k++) + { + PARAMETRIZE { pokerus = i; partyMember = k; } + } + } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + + ZeroPlayerPartyMons(); + for (u32 i = 0; i < PARTY_SIZE; i++) + { + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + } + + SetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS, &pokerus); + SET_RNG(RNG_POKERUS_SPREAD, 0); + + EXPECT_EQ(GetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS), pokerus); + for (u32 i = 0; i < PARTY_SIZE; i++) + { + if (i != partyMember) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + } +} + + +TEST("(Pokerus) Test PartySpreadPokerus: do not spread if POKERUS_ENABLED is false") +{ + u32 partyMember = 0; + u32 pokerus = 0; + for (u32 i = 0; i <= MAX_u8; i++) + { + for (u32 k = 0; k < PARTY_SIZE; k++) + { + PARAMETRIZE { pokerus = i; partyMember = k; } + } + } + SetConfig(CONFIG_POKERUS_ENABLED, FALSE); + + ZeroPlayerPartyMons(); + for (u32 i = 0; i < PARTY_SIZE; i++) + { + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + } + + SetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS, &pokerus); + SET_RNG(RNG_POKERUS_SPREAD, 0); + PartySpreadPokerus(); + + EXPECT_EQ(GetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS), pokerus); + for (u32 i = 0; i < PARTY_SIZE; i++) + { + if (i != partyMember) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + } +} + +TEST("(Pokerus) Test PartySpreadPokerus: do not spread to pokemon who got pokerus before") +{ + u32 pokerus1 = 0; + u32 pokerus2 = 0; + for (u32 i = 1; i < 16; i++) + { + for (u32 j = 1; j < 16; j++) + { + PARAMETRIZE { pokerus1 = ((i << 4) | 1); pokerus2 = ((j << 4) | 0); } + PARAMETRIZE { pokerus1 = ((i << 4) | 2); pokerus2 = ((j << 4) | 1); } + } + } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100; + givemon SPECIES_PIKACHU, 100; + ); + + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS, &pokerus1); + SetMonData(&gPlayerParty[1], MON_DATA_POKERUS, &pokerus2); + SET_RNG(RNG_POKERUS_SPREAD, 0); + PartySpreadPokerus(); + + EXPECT_NE(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS), GetMonData(&gPlayerParty[1], MON_DATA_POKERUS)); +} + +TEST("(Pokerus) Test PartySpreadPokerus: strain 0 can be spread to if POKERUS_WEAK_VARIANT is true") +{ + u32 weakVariant = 0; + u32 pokerus2 = 0; + for (u32 i = 2; i <= MAX_u8; i++) + { + if ((i & 0x0F) == 0) + continue; + PARAMETRIZE { weakVariant = TRUE; pokerus2 = i; } + PARAMETRIZE { weakVariant = FALSE; pokerus2 = i; } + } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + SetConfig(CONFIG_POKERUS_WEAK_VARIANT, weakVariant); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100; + givemon SPECIES_PIKACHU, 100; + ); + + u32 pokerus1 = 1; + SetMonData(&gPlayerParty[0], MON_DATA_POKERUS, &pokerus1); + SetMonData(&gPlayerParty[1], MON_DATA_POKERUS, &pokerus2); + SET_RNG(RNG_POKERUS_SPREAD, 0); + PartySpreadPokerus(); + + if (weakVariant) + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS), GetMonData(&gPlayerParty[1], MON_DATA_POKERUS)); + else + EXPECT_NE(GetMonData(&gPlayerParty[0], MON_DATA_POKERUS), GetMonData(&gPlayerParty[1], MON_DATA_POKERUS)); +} + +TEST("(Pokerus) Test PartySpreadPokerus when POKERUS_SPREAD_DAYS_LEFT is set to GEN2") +{ + u32 partyMember = 0; + u32 strain = 0; + u32 daysLeft = 0; + for (u32 i = 0; i < 16; i++) + { + for (u32 j = 1; j < 16; j++) + { + for (u32 k = 0; k < PARTY_SIZE; k++) + { + PARAMETRIZE {strain = i; daysLeft = j; partyMember = k;} + } + } + } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + SetConfig(CONFIG_POKERUS_SPREAD_DAYS_LEFT, GEN_2); + SetConfig(CONFIG_POKERUS_SPREAD_ADJACENCY, GEN_3); + + ZeroPlayerPartyMons(); + for (u32 i = 0; i < PARTY_SIZE; i++) + { + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + } + + SetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS_STRAIN, &strain); + SetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS_DAYS_LEFT, &daysLeft); + SET_RNG(RNG_POKERUS_SPREAD, 0); + PartySpreadPokerus(); + + EXPECT_EQ(GetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS_STRAIN), strain); + EXPECT_EQ(GetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS_DAYS_LEFT), daysLeft); + if (partyMember == 0) + { + EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_POKERUS_STRAIN), strain); + EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_POKERUS_DAYS_LEFT), GetDaysLeftBasedOnStrain(strain)); + for (u32 i = 2; i < PARTY_SIZE; i++) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + + } + else if (partyMember == PARTY_SIZE - 1) + { + EXPECT_EQ(GetMonData(&gPlayerParty[PARTY_SIZE - 2], MON_DATA_POKERUS_STRAIN), strain); + EXPECT_EQ(GetMonData(&gPlayerParty[PARTY_SIZE - 2], MON_DATA_POKERUS_DAYS_LEFT), GetDaysLeftBasedOnStrain(strain)); + for (u32 i = 0; i < (PARTY_SIZE - 2); i++) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + } + else + { + for (u32 i = 0; i < PARTY_SIZE; i++) + { + if ((i < (partyMember - 1)) || (i > (partyMember + 1))) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + else if (i != partyMember) + { + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS_STRAIN), strain); + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS_DAYS_LEFT), GetDaysLeftBasedOnStrain(strain)); + } + } + } +} + +TEST("(Pokerus) Test PartySpreadPokerus using gen2 adjacency") +{ + u32 partyMember = 0; + u32 pokerus = 0; + u32 spreadUp = 0; //spread in ascending order of party index + for (u32 i = 0; i <= MAX_u8; i++) + { + if ((i & 0x0F) == 0) + continue; + for (u32 k = 0; k < PARTY_SIZE; k++) + { + PARAMETRIZE { pokerus = i; partyMember = k; spreadUp = TRUE; } + PARAMETRIZE { pokerus = i; partyMember = k; spreadUp = FALSE; } + } + } + SetConfig(CONFIG_POKERUS_ENABLED, TRUE); + SetConfig(CONFIG_POKERUS_SPREAD_DAYS_LEFT, GEN_3); + SetConfig(CONFIG_POKERUS_SPREAD_ADJACENCY, GEN_2); + + ZeroPlayerPartyMons(); + for (u32 i = 0; i < PARTY_SIZE; i++) + { + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100 + ); + } + + SetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS, &pokerus); + + SET_RNG(RNG_POKERUS_SPREAD, 0); + SET_RNG(RNG_POKERUS_SPREAD_SIDE, spreadUp); + + PartySpreadPokerus(); + + EXPECT_EQ(GetMonData(&gPlayerParty[partyMember], MON_DATA_POKERUS), pokerus); + if (partyMember == 0) + { + if (spreadUp) + EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_POKERUS), pokerus); + else + EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_POKERUS), 0); + for (u32 i = 2; i < PARTY_SIZE; i++) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + + } + else if (partyMember == PARTY_SIZE - 1) + { + EXPECT_EQ(GetMonData(&gPlayerParty[PARTY_SIZE - 2], MON_DATA_POKERUS), pokerus); + for (u32 i = 0; i < (PARTY_SIZE - 2); i++) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + } + else + { + for (u32 i = 0; i < PARTY_SIZE; i++) + { + if (!spreadUp && (i == (partyMember - 1))) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), pokerus); + else if (spreadUp && (i == (partyMember + 1))) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), pokerus); + else if (i != partyMember) + EXPECT_EQ(GetMonData(&gPlayerParty[i], MON_DATA_POKERUS), 0); + } + } +} diff --git a/test/test_runner.c b/test/test_runner.c index b61cef891c..dc5029989e 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -561,6 +561,7 @@ void Test_ExpectFail(u32 failLine) static void FunctionTest_SetUp(void *data) { (void)data; + TestInitConfigData(); ClearRiggedRng(); gFunctionTestRunnerState = AllocZeroed(sizeof(*gFunctionTestRunnerState)); SeedRng(0); @@ -581,6 +582,7 @@ static void FunctionTest_Run(void *data) static void FunctionTest_TearDown(void *data) { (void)data; + TestFreeConfigData(); FREE_AND_SET_NULL(gFunctionTestRunnerState); }