Implemented affection-now-friendship mechanics

This commit is contained in:
LOuroboros 2022-06-19 16:43:22 -03:00
parent 15fe85902f
commit aa5c6a3f01
12 changed files with 193 additions and 12 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -258,6 +258,7 @@
#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)

View File

@ -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

View File

@ -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.

View File

@ -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,

View File

@ -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,

View File

@ -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;

View File

@ -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;