diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 41a51efeb..85f090e1f 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1755,6 +1755,14 @@ various \battler, VARIOUS_JUMP_IF_ABSENT .4byte \ptr .endm + + .macro activateitemeffects battler:req + various \battler, VARIOUS_MOVEEND_ITEM_EFFECTS + .endm + + .macro pickpocketsteal + various 0, VARIOUS_PICKPOCKET + .endm @ helpful macros .macro setstatchanger stat:req, stages:req, down:req diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 34ae2704b..94c4485fd 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -7691,7 +7691,11 @@ BattleScript_PrintPlayerForfeitedLinkBattle:: BattleScript_Pickpocket:: call BattleScript_AbilityPopUp + setmoveeffect MOVE_EFFECT_STEAL_ITEM swapattackerwithtarget - seteffectsecondary + pickpocketsteal + call BattleScript_ItemSteal swapattackerwithtarget + activateitemeffects BS_TARGET return + diff --git a/include/battle.h b/include/battle.h index 13fe13171..15c4a466b 100644 --- a/include/battle.h +++ b/include/battle.h @@ -557,6 +557,7 @@ struct BattleStruct #define BATTLER_MAX_HP(battlerId)(gBattleMons[battlerId].hp == gBattleMons[battlerId].maxHP) #define TARGET_TURN_DAMAGED ((gSpecialStatuses[gBattlerTarget].physicalDmg != 0 || gSpecialStatuses[gBattlerTarget].specialDmg != 0)) +#define BATTLER_DAMAGED(battlerId) ((gSpecialStatuses[battlerId].physicalDmg != 0 || gSpecialStatuses[battlerId].specialDmg != 0)) #define IS_BATTLER_OF_TYPE(battlerId, type)((gBattleMons[battlerId].type1 == type || gBattleMons[battlerId].type2 == type || gBattleMons[battlerId].type3 == type)) #define SET_BATTLER_TYPE(battlerId, type) \ diff --git a/include/battle_util.h b/include/battle_util.h index 422aa7300..36364bdb7 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -130,5 +130,8 @@ void ClearIllusionMon(u32 battlerId); bool32 SetIllusionMon(struct Pokemon *mon, u32 battlerId); bool8 ShouldGetStatBadgeBoost(u16 flagId, u8 battlerId); u8 GetBattleMoveSplit(u32 moveId); +void SortBattlersBySpeed(u8 *battlers, bool8 slowToFast); +bool32 TestSheerForceFlag(u8 battler, u16 move); +bool32 ItemCanBeStolen(u16 item, u8 battlerId); #endif // GUARD_BATTLE_UTIL_H diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index b081907ec..816a4b991 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -165,6 +165,8 @@ #define VARIOUS_SET_LAST_USED_ITEM 99 #define VARIOUS_PARALYZE_TYPE_IMMUNITY 100 #define VARIOUS_JUMP_IF_ABSENT 101 +#define VARIOUS_MOVEEND_ITEM_EFFECTS 102 +#define VARIOUS_PICKPOCKET 103 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 @@ -189,7 +191,7 @@ #define STAT_CHANGE_ONLY_MULTIPLE 0x4 #define STAT_CHANGE_CANT_PREVENT 0x8 -// cases for Cmd_moveend +// cases for Cmd_moveend - reference https://bulbapedia.bulbagarden.net/wiki/User:FIQ/Turn_sequence #define MOVEEND_PROTECT_LIKE_EFFECT 0 #define MOVEEND_RAGE 1 #define MOVEEND_DEFROST 2 @@ -212,10 +214,11 @@ #define MOVEEND_MIRROR_MOVE 19 #define MOVEEND_NEXT_TARGET 20 #define MOVEEND_LIFE_ORB 21 -#define MOVEEND_DANCER 22 -#define MOVEEND_EMERGENCY_EXIT 23 -#define MOVEEND_CLEAR_BITS 24 -#define MOVEEND_COUNT 25 +#define MOVEEND_PICKPOCKET 22 +#define MOVEEND_DANCER 23 +#define MOVEEND_EMERGENCY_EXIT 24 +#define MOVEEND_CLEAR_BITS 25 +#define MOVEEND_COUNT 26 // stat flags for Cmd_playstatchangeanimation #define BIT_HP 0x1 diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d721a9102..d5b57af48 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2419,9 +2419,7 @@ void SetMoveEffect(bool32 primary, u32 certain) && !primary && gBattleScripting.moveEffect <= 7) INCREMENT_RESET_RETURN - if (GetBattlerAbility(gBattlerAttacker) == ABILITY_SHEER_FORCE - && gBattleMoves[gCurrentMove].flags & FLAG_SHEER_FORCE_BOOST - && affectsUser != MOVE_EFFECT_AFFECTS_USER) + if (TestSheerForceFlag(gBattlerAttacker, gCurrentMove) && affectsUser != MOVE_EFFECT_AFFECTS_USER) INCREMENT_RESET_RETURN if (gBattleMons[gEffectBattler].hp == 0 @@ -2924,36 +2922,12 @@ void SetMoveEffect(bool32 primary, u32 certain) break; case MOVE_EFFECT_STEAL_ITEM: { - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_HILL) + if (!ItemCanBeStolen(gBattleMons[gBattlerTarget].item, gBattlerAttacker)) { gBattlescriptCurrInstr++; break; } - - side = GetBattlerSide(gBattlerAttacker); - if (gLastUsedAbility != ABILITY_PICKPOCKET //we need to swap attacker and target so this check otherwise fails - && GetBattlerSide(gBattlerAttacker) == B_SIDE_OPPONENT - && !(gBattleTypeFlags & - (BATTLE_TYPE_EREADER_TRAINER - | BATTLE_TYPE_FRONTIER - | BATTLE_TYPE_LINK - | BATTLE_TYPE_x2000000 - | BATTLE_TYPE_SECRET_BASE))) - { - gBattlescriptCurrInstr++; - } - else if (!(gBattleTypeFlags & - (BATTLE_TYPE_EREADER_TRAINER - | BATTLE_TYPE_FRONTIER - | BATTLE_TYPE_LINK - | BATTLE_TYPE_x2000000 - | BATTLE_TYPE_SECRET_BASE)) - && (gWishFutureKnock.knockedOffMons[side] & gBitTable[gBattlerPartyIndexes[gBattlerAttacker]])) - { - gBattlescriptCurrInstr++; - } - else if (gBattleMons[gBattlerTarget].item - && gBattleMons[gBattlerTarget].ability == ABILITY_STICKY_HOLD) + else if (gBattleMons[gBattlerTarget].item && gBattleMons[gBattlerTarget].ability == ABILITY_STICKY_HOLD) { BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_NoItemSteal; @@ -2972,7 +2946,11 @@ void SetMoveEffect(bool32 primary, u32 certain) { gLastUsedItem = gBattleStruct->changedItems[gBattlerAttacker] = gBattleMons[gBattlerTarget].item; gBattleMons[gBattlerTarget].item = 0; - + + RecordItemEffectBattle(gBattlerTarget, 0); + RecordItemEffectBattle(gBattlerAttacker, ItemId_GetHoldEffect(gLastUsedItem)); + //item assignment doesn't happen yet + CheckSetUnburden(gBattlerTarget); gBattleResources->flags->flags[gBattlerAttacker] &= ~(RESOURCE_FLAG_UNBURDEN); @@ -5010,7 +4988,7 @@ static void Cmd_moveend(void) case MOVEEND_LIFE_ORB: if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LIFE_ORB && IsBattlerAlive(gBattlerAttacker) - && !(GetBattlerAbility(gBattlerAttacker) == ABILITY_SHEER_FORCE && gBattleMoves[gCurrentMove].flags & FLAG_SHEER_FORCE_BOOST) + && !(TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD && gSpecialStatuses[gBattlerAttacker].damagedMons) { @@ -5024,6 +5002,38 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; + case MOVEEND_PICKPOCKET: + if (IsBattlerAlive(gBattlerAttacker) + && GetBattlerAbility(gBattlerAttacker) != ABILITY_STICKY_HOLD + && gBattleMons[gBattlerAttacker].item != ITEM_NONE //attacker must be holding an item + && !(TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) //pickpocket doesn't activate for sheer force + && IsMoveMakingContact(gCurrentMove, gBattlerAttacker) //pickpocket requires contact + && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) //obviously attack needs to have worked + { + u8 battlers[4] = {0, 1, 2, 3}; + SortBattlersBySpeed(battlers, FALSE); //pickpocket activates for fastest mon without item + for (i = 0; i < gBattlersCount; i++) + { + u8 battler = battlers[i]; + + if (battler != gBattlerAttacker //cannot pickpocket yourself + && GetBattlerAbility(battler) == ABILITY_PICKPOCKET //'target' must have pickpocket ability + && BATTLER_DAMAGED(battler) //obviously battler needs to have been damaged as well + && !DoesSubstituteBlockMove(gCurrentMove, gBattlerAttacker, battler) //subsitute unaffected + && IsBattlerAlive(battler) //battler must be alive to be pickpocketed + && gBattleMons[battler].item == ITEM_NONE //pickpocketer can't have an item already + && ItemCanBeStolen(gBattleMons[gBattlerAttacker].item, battler)) //cannot steal plates, mega stones, etc + { + gBattlerTarget = gBattlerAbility = battler; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_Pickpocket; + effect = TRUE; + break; // pickpocket activates on fastest mon, so exit loop. + } + } + } + gBattleScripting.moveendState++; + break; case MOVEEND_DANCER: // Special case because it's so annoying if (gBattleMoves[gCurrentMove].flags & FLAG_DANCE) { @@ -8301,6 +8311,35 @@ static void Cmd_various(void) gBattlescriptCurrInstr += 7; } return; + case VARIOUS_MOVEEND_ITEM_EFFECTS: + ItemBattleEffects(1, gActiveBattler, FALSE); + break; + case VARIOUS_PICKPOCKET: + { + // different from MOVE_EFFECT_STEAL_ITEM in that it immediately assigns the stolen item to the 'attacker' + gEffectBattler = gBattlerTarget; + gBattleScripting.battler = gBattlerAttacker; + gLastUsedItem = gBattleMons[gEffectBattler].item; + gBattleMons[gEffectBattler].item = 0; + gBattleMons[gBattlerAttacker].item = gLastUsedItem; + + RecordItemEffectBattle(gBattlerTarget, 0); + RecordItemEffectBattle(gBattlerAttacker, ItemId_GetHoldEffect(gLastUsedItem)); + + CheckSetUnburden(gEffectBattler); //Give target Unburden boost + gBattleResources->flags->flags[gBattlerTarget] &= ~(RESOURCE_FLAG_UNBURDEN); //remove attacker boost + + gActiveBattler = gBattlerAttacker; + BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedItem); + MarkBattlerForControllerExec(gActiveBattler); + + gActiveBattler = gEffectBattler; + BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gBattleMons[gActiveBattler].item); + MarkBattlerForControllerExec(gActiveBattler); + + gBattleStruct->choicedMove[gEffectBattler] = 0; + } + break; } gBattlescriptCurrInstr += 3; diff --git a/src/battle_util.c b/src/battle_util.c index 3f9dd81ec..91d10af23 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4227,7 +4227,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA && gBattleStruct->hpBefore[battler] > gBattleMons[battler].maxHP / 2 && gBattleMons[battler].hp < gBattleMons[battler].maxHP / 2 && (gMultiHitCounter == 0 || gMultiHitCounter == 1) - && !(GetBattlerAbility(gBattlerAttacker) == ABILITY_SHEER_FORCE && gBattleMoves[gCurrentMove].flags & FLAG_SHEER_FORCE_BOOST) + && !(TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) && gBattleMons[battler].statStages[STAT_SPATK] != 12) { SET_STATCHANGER(STAT_SPATK, 1, FALSE); @@ -4245,7 +4245,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA && gBattleStruct->hpBefore[battler] > gBattleMons[battler].maxHP / 2 && gBattleMons[battler].hp < gBattleMons[battler].maxHP / 2 && (gMultiHitCounter == 0 || gMultiHitCounter == 1) - && !(GetBattlerAbility(gBattlerAttacker) == ABILITY_SHEER_FORCE && gBattleMoves[gCurrentMove].flags & FLAG_SHEER_FORCE_BOOST) + && !(TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) && (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) && !(gBattleTypeFlags & BATTLE_TYPE_ARENA)) { @@ -4518,24 +4518,6 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA effect++; } break; - case ABILITY_PICKPOCKET: - if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) - && IsBattlerAlive(gBattlerAttacker) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && (gBattleMoves[move].flags & FLAG_MAKES_CONTACT) - && TARGET_TURN_DAMAGED - && IsBattlerAlive(gBattlerTarget) - && gBattleMons[gBattlerAttacker].item != ITEM_NONE - && gBattleMons[gBattlerTarget].item == ITEM_NONE - && GetBattlerAbility(gBattlerAttacker) != ABILITY_STICKY_HOLD) - { - gBattleScripting.moveEffect = MOVE_EFFECT_STEAL_ITEM; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_Pickpocket; - gHitMarker |= HITMARKER_IGNORE_SAFEGUARD; - effect++; - } - break; } break; case ABILITYEFFECT_MOVE_END_ATTACKER: // Same as above, but for attacker @@ -7762,3 +7744,110 @@ u8 GetBattleMoveSplit(u32 moveId) else return SPLIT_SPECIAL; } + +// sort an array of battlers by speed +// useful for effects like pickpocket, eject button, red card, dancer +void SortBattlersBySpeed(u8 *battlers, bool8 slowToFast) +{ + int i, j, key, keyBank; + u16 speeds[4] = {0}; + + for (i = 0; i < gBattlersCount; i++) + speeds[i] = GetBattlerTotalSpeedStat(battlers[i]); + + for (i = 1; i < gBattlersCount; i++) + { + keyBank = battlers[i]; + key = speeds[i]; + j = i - 1; + + if (slowToFast) + { + while (j >= 0 && speeds[j] > key) + { + battlers[j + 1] = battlers[j]; + speeds[j + 1] = speeds[j]; + j = j - 1; + } + } + else + { + while (j >= 0 && speeds[j] < key) + { + battlers[j + 1] = battlers[j]; + speeds[j + 1] = speeds[j]; + j = j - 1; + } + } + + battlers[j + 1] = keyBank; + speeds[j + 1] = key; + } +} + +bool32 TestSheerForceFlag(u8 battler, u16 move) +{ + if (GetBattlerAbility(battler) == ABILITY_SHEER_FORCE && gBattleMoves[move].flags & FLAG_SHEER_FORCE_BOOST) + return TRUE; + else + return FALSE; +} + +bool32 ItemCanBeStolen(u16 item, u8 battlerId) +{ + u8 effect = ItemId_GetHoldEffect(item); + + if (item == ITEM_ENIGMA_BERRY) + return FALSE; + + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_HILL) + return FALSE; + + if (GetBattlerSide(battlerId) == B_SIDE_OPPONENT + && !(gBattleTypeFlags & + (BATTLE_TYPE_EREADER_TRAINER + | BATTLE_TYPE_FRONTIER + | BATTLE_TYPE_LINK + | BATTLE_TYPE_x2000000 + | BATTLE_TYPE_SECRET_BASE))) + { + return FALSE; + } + else if (!(gBattleTypeFlags & + (BATTLE_TYPE_EREADER_TRAINER + | BATTLE_TYPE_FRONTIER + | BATTLE_TYPE_LINK + | BATTLE_TYPE_x2000000 + | BATTLE_TYPE_SECRET_BASE)) + && (gWishFutureKnock.knockedOffMons[GetBattlerSide(battlerId)] & gBitTable[gBattlerPartyIndexes[battlerId]])) + { + return FALSE; + } + + if (IS_ITEM_MAIL(item)) + return FALSE; + + switch (effect) + { + case HOLD_EFFECT_MEGA_STONE: + #ifdef HOLD_EFFECT_MEMORY + case HOLD_EFFECT_MEMORY: + #endif + #ifdef HOLD_EFFECT_Z_CRYSTAL + case HOLD_EFFECT_Z_CRYSTAL: + #endif + #ifdef HOLD_EFFECT_DRIVE + case HOLD_EFFECT_DRIVE: + #endif + #ifdef HOLD_EFFECT_GEMS + case HOLD_EFFECT_GEMS: + #endif + #ifdef HOLD_EFFECT_GRISEOUS_ORB + case HOLD_EFFECT_GRISEOUS_ORB: + #endif + return FALSE; + } + + return TRUE; +} +