From aa5c6a3f0126bf693b84cb2ce6ae4a271dd45148 Mon Sep 17 00:00:00 2001 From: LOuroboros Date: Sun, 19 Jun 2022 16:43:22 -0300 Subject: [PATCH] Implemented affection-now-friendship mechanics --- data/battle_anim_scripts.s | 12 ++++++ data/battle_scripts_1.s | 55 +++++++++++++++++++++++++++ include/battle.h | 1 + include/battle_scripts.h | 2 + include/battle_util.h | 1 + include/constants/battle.h | 21 +++++----- include/constants/battle_anim.h | 1 + include/constants/battle_config.h | 1 + include/constants/battle_string_ids.h | 8 +++- src/battle_message.c | 12 ++++++ src/battle_script_commands.c | 54 +++++++++++++++++++++++++- src/battle_util.c | 37 ++++++++++++++++++ 12 files changed, 193 insertions(+), 12 deletions(-) diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index d76aff05a..1ea6fc2b4 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -831,6 +831,7 @@ gBattleAnims_General:: .4byte General_AquaRingHeal @ B_ANIM_AQUA_RING_HEAL .4byte General_BeakBlastSetUp @ B_ANIM_BEAK_BLAST_SETUP .4byte General_ShellTrapSetUp @ B_ANIM_SHELL_TRAP_SETUP + .4byte General_AffectionHangedOn @ B_ANIM_AFFECTION_HANGED_ON .align 2 gBattleAnims_Special:: @@ -24774,6 +24775,17 @@ PrimalReversionParticles: delay 3 return +General_AffectionHangedOn:: @ Shameless copy of General_HangedOn + createsprite gSimplePaletteBlendSpriteTemplate, ANIM_ATTACKER, 0, 2, 7, 0, 9, RGB_RED + playsewithpan SE_M_DRAGON_RAGE, SOUND_PAN_ATTACKER + createvisualtask AnimTask_SlideMonForFocusBand, 5, 30, 128, 0, 1, 2, 0, 1 + waitforvisualfinish + createsprite gSimplePaletteBlendSpriteTemplate, ANIM_ATTACKER, 0, 2, 4, 9, 0, RGB_RED + waitforvisualfinish + delay 6 + createsprite gSlideMonToOriginalPosSpriteTemplate, ANIM_ATTACKER, 0, 0, 0, 15 + end + SnatchMoveTrySwapFromSubstitute: createvisualtask AnimTask_IsAttackerBehindSubstitute, 2 jumprettrue SnatchMoveSwapSubstituteForMon diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a9c15040d..80df7a994 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -412,6 +412,61 @@ gBattleScriptsForMoveEffects:: .4byte BattleScript_EffectCourtChange @ EFFECT_COURT_CHANGE .4byte BattleScript_EffectSteelBeam @ EFFECT_STEEL_BEAM +BattleScript_AffectionBasedEndurance:: + playanimation BS_TARGET, B_ANIM_AFFECTION_HANGED_ON + printstring STRINGID_TARGETTOUGHEDITOUT + waitmessage B_WAIT_TIME_LONG + return + +BattleScript_AffectionBasedStatusHeal:: + jumpifstatus BS_ATTACKER, STATUS1_POISON, BattleScript_AffectionBasedStatusHeal_Poison + jumpifstatus BS_ATTACKER, STATUS1_TOXIC_POISON, BattleScript_AffectionBasedStatusHeal_Poison + jumpifstatus BS_ATTACKER, STATUS1_SLEEP, BattleScript_AffectionBasedStatusHeal_Sleep + jumpifstatus BS_ATTACKER, STATUS1_PARALYSIS, BattleScript_AffectionBasedStatusHeal_Paralysis + jumpifstatus BS_ATTACKER, STATUS1_BURN, BattleScript_AffectionBasedStatusHeal_Burn + jumpifstatus BS_ATTACKER, STATUS1_FREEZE, BattleScript_AffectionBasedStatusHeal_Freeze + end2 +BattleScript_AffectionBasedStatusHeal_Poison: + printstring STRINGID_ATTACKEREXPELLEDTHEPOISON + waitmessage B_WAIT_TIME_LONG + clearstatus BS_ATTACKER + waitstate + updatestatusicon BS_ATTACKER + waitstate + end2 +BattleScript_AffectionBasedStatusHeal_Sleep: + printstring STRINGID_ATTACKERSHOOKITSELFAWAKE + waitmessage B_WAIT_TIME_LONG + clearstatus BS_ATTACKER + waitstate + updatestatusicon BS_ATTACKER + waitstate + end2 +BattleScript_AffectionBasedStatusHeal_Paralysis: + printstring STRINGID_ATTACKERBROKETHROUGHPARALYSIS + waitmessage B_WAIT_TIME_LONG + clearstatus BS_ATTACKER + waitstate + updatestatusicon BS_ATTACKER + waitstate + end2 +BattleScript_AffectionBasedStatusHeal_Burn: + printstring STRINGID_ATTACKERHEALEDITSBURN + waitmessage B_WAIT_TIME_LONG + clearstatus BS_ATTACKER + waitstate + updatestatusicon BS_ATTACKER + waitstate + end2 +BattleScript_AffectionBasedStatusHeal_Freeze: + printstring STRINGID_ATTACKERMELTEDTHEICE + waitmessage B_WAIT_TIME_LONG + clearstatus BS_ATTACKER + waitstate + updatestatusicon BS_ATTACKER + waitstate + end2 + BattleScript_EffectSteelBeam:: attackcanceler attackstring diff --git a/include/battle.h b/include/battle.h index ea556c012..6d6dc4695 100644 --- a/include/battle.h +++ b/include/battle.h @@ -184,6 +184,7 @@ struct SpecialStatus u8 physicalBattlerId; u8 specialBattlerId; u8 changedStatsBattlerId; // Battler that was responsible for the latest stat change. Can be self. + bool8 affectionEndured:1; }; struct SideTimer diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 4134a87dc..071a0846a 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -420,5 +420,7 @@ extern const u8 BattleScript_MagicianActivates[]; extern const u8 BattleScript_BeakBlastSetUp[]; extern const u8 BattleScript_BeakBlastBurn[]; extern const u8 BattleScript_DefDownSpeedUp[]; +extern const u8 BattleScript_AffectionBasedStatusHeal[]; +extern const u8 BattleScript_AffectionBasedEndurance[]; #endif // GUARD_BATTLE_SCRIPTS_H diff --git a/include/battle_util.h b/include/battle_util.h index f1864f096..0f982423e 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -189,5 +189,6 @@ bool32 CanBeParalyzed(u8 battlerId); bool32 CanBeFrozen(u8 battlerId); bool32 CanBeConfused(u8 battlerId); bool32 IsBattlerTerrainAffected(u8 battlerId, u32 terrainFlag); +u32 GetMonFriendshipScore(struct Pokemon *pokemon); #endif // GUARD_BATTLE_UTIL_H diff --git a/include/constants/battle.h b/include/constants/battle.h index d651dab18..6a078f7a1 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -248,16 +248,17 @@ #define STATUS_FIELD_TERRAIN_ANY (STATUS_FIELD_GRASSY_TERRAIN | STATUS_FIELD_MISTY_TERRAIN | STATUS_FIELD_ELECTRIC_TERRAIN | STATUS_FIELD_PSYCHIC_TERRAIN) // Flags describing move's result -#define MOVE_RESULT_MISSED (1 << 0) -#define MOVE_RESULT_SUPER_EFFECTIVE (1 << 1) -#define MOVE_RESULT_NOT_VERY_EFFECTIVE (1 << 2) -#define MOVE_RESULT_DOESNT_AFFECT_FOE (1 << 3) -#define MOVE_RESULT_ONE_HIT_KO (1 << 4) -#define MOVE_RESULT_FAILED (1 << 5) -#define MOVE_RESULT_FOE_ENDURED (1 << 6) -#define MOVE_RESULT_FOE_HUNG_ON (1 << 7) -#define MOVE_RESULT_STURDIED (1 << 8) -#define MOVE_RESULT_NO_EFFECT (MOVE_RESULT_MISSED | MOVE_RESULT_DOESNT_AFFECT_FOE | MOVE_RESULT_FAILED) +#define MOVE_RESULT_MISSED (1 << 0) +#define MOVE_RESULT_SUPER_EFFECTIVE (1 << 1) +#define MOVE_RESULT_NOT_VERY_EFFECTIVE (1 << 2) +#define MOVE_RESULT_DOESNT_AFFECT_FOE (1 << 3) +#define MOVE_RESULT_ONE_HIT_KO (1 << 4) +#define MOVE_RESULT_FAILED (1 << 5) +#define MOVE_RESULT_FOE_ENDURED (1 << 6) +#define MOVE_RESULT_FOE_HUNG_ON (1 << 7) +#define MOVE_RESULT_STURDIED (1 << 8) +#define MOVE_RESULT_NO_EFFECT (MOVE_RESULT_MISSED | MOVE_RESULT_DOESNT_AFFECT_FOE | MOVE_RESULT_FAILED) +#define MOVE_RESULT_FOE_ENDURED_AFFECTION (1 << 9) // Battle Weather flags #define B_WEATHER_RAIN_TEMPORARY (1 << 0) diff --git a/include/constants/battle_anim.h b/include/constants/battle_anim.h index bddb727fd..8e34fbcc0 100644 --- a/include/constants/battle_anim.h +++ b/include/constants/battle_anim.h @@ -534,6 +534,7 @@ #define B_ANIM_AQUA_RING_HEAL 32 #define B_ANIM_BEAK_BLAST_SETUP 33 #define B_ANIM_SHELL_TRAP_SETUP 34 +#define B_ANIM_AFFECTION_HANGED_ON 35 // special animations table (gBattleAnims_Special) #define B_ANIM_LVL_UP 0 diff --git a/include/constants/battle_config.h b/include/constants/battle_config.h index 74ac9e129..0a58ba94d 100644 --- a/include/constants/battle_config.h +++ b/include/constants/battle_config.h @@ -254,6 +254,7 @@ #define B_MULTI_BATTLE_WHITEOUT GEN_8 // In Gen4+, multi battles end when the Player and also their Partner don't have any more Pokémon to fight. #define B_EVOLUTION_AFTER_WHITEOUT GEN_6 // In Gen6+, Pokemon that qualify for evolution after battle will evolve even if the player loses. #define B_WILD_NATURAL_ENEMIES TRUE // If set to TRUE, certain wild mon species will attack other species when partnered in double wild battles (eg. Zangoose vs Seviper) +#define B_AFFECTION_MECHANICS FALSE // In Gen6+, there's a stat called affection that can trigger different effects in battle. From LGPE onwards, those effects use friendship instead. // Animation Settings #define B_NEW_SWORD_PARTICLE FALSE // If set to TRUE, it updates Swords Dance's particle. diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 4eff91c85..bfc047299 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -613,8 +613,14 @@ #define STRINGID_METEORBEAMCHARGING 611 #define STRINGID_HEATUPBEAK 612 #define STRINGID_COURTCHANGE 613 +#define STRINGID_ATTACKEREXPELLEDTHEPOISON 614 +#define STRINGID_ATTACKERSHOOKITSELFAWAKE 615 +#define STRINGID_ATTACKERBROKETHROUGHPARALYSIS 616 +#define STRINGID_ATTACKERHEALEDITSBURN 617 +#define STRINGID_ATTACKERMELTEDTHEICE 618 +#define STRINGID_TARGETTOUGHEDITOUT 619 -#define BATTLESTRINGS_COUNT 614 +#define BATTLESTRINGS_COUNT 620 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/src/battle_message.c b/src/battle_message.c index 8cf9a508b..dae30f176 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -738,9 +738,21 @@ static const u8 sText_TargetTooHeavy[] = _("But the target\nwas too heavy!"); static const u8 sText_MeteorBeamCharging[] = _("{B_ATK_NAME_WITH_PREFIX} is overflowing\nwith space energy!"); static const u8 sText_HeatingUpBeak[] = _("{B_ATK_NAME_WITH_PREFIX} started\nheating up its beak!"); static const u8 sText_CourtChange[] = _("{B_ATK_NAME_WITH_PREFIX} swapped the battle\neffects affecting each side!"); +static const u8 sText_AttackerExpelledThePoison[] = _("{B_ATK_NAME_WITH_PREFIX} managed to\nexpel the poison!"); +static const u8 sText_AttackerShookItselfAwake[] = _("{B_ATK_NAME_WITH_PREFIX} shook itself awake!"); +static const u8 sText_AttackerBrokeThroughParalysis[] = _("{B_ATK_NAME_WITH_PREFIX} gathered all its energy\nto overcome its paralysis!"); +static const u8 sText_AttackerHealedItsBurn[] = _("{B_ATK_NAME_WITH_PREFIX} healed its burn with\nits sheer determination!"); +static const u8 sText_AttackerMeltedTheIce[] = _("{B_ATK_NAME_WITH_PREFIX} melted the ice with\nits fiery determination!"); +static const u8 sText_TargetToughedItOut[] = _("{B_DEF_NAME_WITH_PREFIX} toughed it out\nto show you its best side!"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { + [STRINGID_TARGETTOUGHEDITOUT - BATTLESTRINGS_TABLE_START] = sText_TargetToughedItOut, + [STRINGID_ATTACKERMELTEDTHEICE - BATTLESTRINGS_TABLE_START] = sText_AttackerMeltedTheIce, + [STRINGID_ATTACKERHEALEDITSBURN - BATTLESTRINGS_TABLE_START] = sText_AttackerHealedItsBurn, + [STRINGID_ATTACKERBROKETHROUGHPARALYSIS - BATTLESTRINGS_TABLE_START] = sText_AttackerBrokeThroughParalysis, + [STRINGID_ATTACKERSHOOKITSELFAWAKE - BATTLESTRINGS_TABLE_START] = sText_AttackerShookItselfAwake, + [STRINGID_ATTACKEREXPELLEDTHEPOISON - BATTLESTRINGS_TABLE_START] = sText_AttackerExpelledThePoison, [STRINGID_COURTCHANGE - BATTLESTRINGS_TABLE_START] = sText_CourtChange, [STRINGID_HEATUPBEAK - BATTLESTRINGS_TABLE_START] = sText_HeatingUpBeak, [STRINGID_METEORBEAMCHARGING - BATTLESTRINGS_TABLE_START] = sText_MeteorBeamCharging, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 15915c71a..1d42e5511 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1719,6 +1719,13 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move) if (gFieldStatuses & STATUS_FIELD_GRAVITY) calc = (calc * 5) / 3; // 1.66 Gravity acc boost +#if B_AFFECTION_MECHANICS == TRUE + // With high affection/friendship there's a chance to evade a move by substracting 10% of its accuracy. + // I can't find exact information about that chance, so I'm just gonna write it as a 20% chance for now. + if (GetMonFriendshipScore(&gPlayerParty[gBattlerPartyIndexes[battlerDef]]) >= 4 && (Random() % 100) <= 20) + calc = (calc * 90) / 100; +#endif + return calc; } @@ -1885,7 +1892,10 @@ s32 CalcCritChanceStage(u8 battlerAtk, u8 battlerDef, u32 move, bool32 recordAbi + (holdEffectAtk == HOLD_EFFECT_SCOPE_LENS) + 2 * (holdEffectAtk == HOLD_EFFECT_LUCKY_PUNCH && gBattleMons[gBattlerAttacker].species == SPECIES_CHANSEY) + 2 * BENEFITS_FROM_LEEK(battlerAtk, holdEffectAtk) - + (abilityAtk == ABILITY_SUPER_LUCK); + + (abilityAtk == ABILITY_SUPER_LUCK) + #if B_AFFECTION_MECHANICS == TRUE + *= 2 (GetMonFriendshipScore(&gPlayerParty[gBattlerPartyIndexes[gBattlerAttacker]]) >= 5); + #endif if (critChance >= ARRAY_COUNT(sCriticalHitChance)) critChance = ARRAY_COUNT(sCriticalHitChance) - 1; @@ -1983,12 +1993,27 @@ static void Cmd_adjustdamage(void) RecordAbilityBattle(gBattlerTarget, ABILITY_STURDY); gSpecialStatuses[gBattlerTarget].sturdied = TRUE; } +#if B_AFFECTION_MECHANICS == TRUE + else if (GetBattlerSide(gBattlerTarget) == B_SIDE_PLAYER && GetMonFriendshipScore(&gPlayerParty[gBattlerPartyIndexes[gBattlerTarget]]) >= 3) + { + if ((GetMonFriendshipScore(&gPlayerParty[gBattlerPartyIndexes[gBattlerTarget]]) == 6 && (Random() % 100) < 25) + || (GetMonFriendshipScore(&gPlayerParty[gBattlerPartyIndexes[gBattlerTarget]]) == 5 && (Random() % 100) < 20) + || (GetMonFriendshipScore(&gPlayerParty[gBattlerPartyIndexes[gBattlerTarget]]) == 4 && (Random() % 100) < 15) + || (GetMonFriendshipScore(&gPlayerParty[gBattlerPartyIndexes[gBattlerTarget]]) == 3 && (Random() % 100) < 10)) + gSpecialStatuses[gBattlerTarget].affectionEndured = TRUE; + } +#endif if (gBattleMoves[gCurrentMove].effect != EFFECT_FALSE_SWIPE && !gProtectStructs[gBattlerTarget].endured && !gSpecialStatuses[gBattlerTarget].focusBanded && !gSpecialStatuses[gBattlerTarget].focusSashed +#if B_AFFECTION_MECHANICS == TRUE + && !gSpecialStatuses[gBattlerTarget].sturdied + && !gSpecialStatuses[gBattlerTarget].affectionEndured) +#else && !gSpecialStatuses[gBattlerTarget].sturdied) +#endif goto END; // Handle reducing the dmg to 1 hp. @@ -2008,6 +2033,12 @@ static void Cmd_adjustdamage(void) gMoveResultFlags |= MOVE_RESULT_STURDIED; gLastUsedAbility = ABILITY_STURDY; } +#if B_AFFECTION_MECHANICS == TRUE + else if (gSpecialStatuses[gBattlerTarget].affectionEndured) + { + gMoveResultFlags |= MOVE_RESULT_FOE_ENDURED_AFFECTION; + } +#endif END: gBattlescriptCurrInstr++; @@ -2463,6 +2494,16 @@ static void Cmd_resultmessage(void) { stringId = STRINGID_BUTITFAILED; } + #if B_AFFECTION_MECHANICS == TRUE + else if (gMoveResultFlags & MOVE_RESULT_FOE_ENDURED_AFFECTION) + { + gSpecialStatuses[gBattlerTarget].affectionEndured = FALSE; + gMoveResultFlags &= ~MOVE_RESULT_FOE_ENDURED_AFFECTION; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_AffectionBasedEndurance; + return; + } + #endif else { gBattleCommunication[MSG_DISPLAY] = 0; @@ -4008,6 +4049,10 @@ static void Cmd_getexp(void) gBattleMoveDamage = value + 1; } #endif + #if B_AFFECTION_MECHANICS == TRUE + if (GetMonFriendshipScore(&gPlayerParty[gBattleStruct->expGetterMonId]) >= 2) + gBattleMoveDamage = (gBattleMoveDamage * 120) / 100; + #endif if (IsTradedMon(&gPlayerParty[gBattleStruct->expGetterMonId])) { @@ -10932,6 +10977,13 @@ static void Cmd_tryKO(void) gMoveResultFlags |= MOVE_RESULT_FOE_HUNG_ON; gLastUsedItem = gBattleMons[gBattlerTarget].item; } + #if B_AFFECTION_MECHANICS == TRUE + else if (gSpecialStatuses[gBattlerTarget].affectionEndured) + { + gBattleMoveDamage = gBattleMons[gBattlerTarget].hp - 1; + gMoveResultFlags |= MOVE_RESULT_FOE_ENDURED_AFFECTION; + } + #endif else { gBattleMoveDamage = gBattleMons[gBattlerTarget].hp; diff --git a/src/battle_util.c b/src/battle_util.c index ebd7917d4..89f2b86fa 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2077,6 +2077,24 @@ void TryToRevertMimicry(void) } } +u32 GetMonFriendshipScore(struct Pokemon *pokemon) // Based on GetLeadMonFriendshipScore +{ + if (GetMonData(pokemon, MON_DATA_FRIENDSHIP) == 255) + return 6; + if (GetMonData(pokemon, MON_DATA_FRIENDSHIP) >= 200) + return 5; + if (GetMonData(pokemon, MON_DATA_FRIENDSHIP) >= 150) + return 4; + if (GetMonData(pokemon, MON_DATA_FRIENDSHIP) >= 100) + return 3; + if (GetMonData(pokemon, MON_DATA_FRIENDSHIP) >= 50) + return 2; + if (GetMonData(pokemon, MON_DATA_FRIENDSHIP) >= 1) + return 1; + + return 0; +} + enum { ENDTURN_ORDER, @@ -2105,6 +2123,9 @@ enum ENDTURN_ION_DELUGE, ENDTURN_FAIRY_LOCK, ENDTURN_RETALIATE, +#if B_AFFECTION_MECHANICS == TRUE + ENDTURN_STATUS_HEAL, +#endif ENDTURN_FIELD_COUNT, }; @@ -2552,6 +2573,22 @@ u8 DoFieldEndTurnEffects(void) gSideTimers[B_SIDE_OPPONENT].retaliateTimer--; gBattleStruct->turnCountersTracker++; break; + #if B_AFFECTION_MECHANICS == TRUE + case ENDTURN_STATUS_HEAL: + for (gBattlerAttacker = 0; gBattlerAttacker < gBattlersCount; gBattlerAttacker++) + { + if (GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER + && GetMonFriendshipScore(&gPlayerParty[gBattlerPartyIndexes[gBattlerAttacker]]) >= 4 + && (Random() % 100 < 20)) + { + gBattleCommunication[MULTISTRING_CHOOSER] = 1; + BattleScriptExecute(BattleScript_AffectionBasedStatusHeal); + break; + } + } + gBattleStruct->turnCountersTracker++; + break; + #endif case ENDTURN_FIELD_COUNT: effect++; break;