From 520f00f4aed894981bb9a0b8ebbcabe7635072b5 Mon Sep 17 00:00:00 2001 From: Evan Date: Wed, 11 Nov 2020 18:08:46 -0700 Subject: [PATCH 01/12] add pickpocket --- data/battle_scripts_1.s | 7 +++++++ include/battle_scripts.h | 1 + src/battle_script_commands.c | 3 ++- src/battle_util.c | 18 ++++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 504896d63..d470fc1b6 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -7688,3 +7688,10 @@ BattleScript_PrintPlayerForfeitedLinkBattle:: atk57 waitmessage 0x40 end2 + +BattleScript_Pickpocket:: + call BattleScript_AbilityPopUp + swapattackerwithtarget + seteffectsecondary + swapattackerwithtarget + return diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 84a7ba2ef..1ff245d29 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -349,5 +349,6 @@ extern const u8 BattleScript_EmergencyExitNoPopUp[]; extern const u8 BattleScript_EmergencyExitWild[]; extern const u8 BattleScript_EmergencyExitWildNoPopUp[]; extern const u8 BattleScript_CheekPouchActivates[]; +extern const u8 BattleScript_Pickpocket[]; #endif // GUARD_BATTLE_SCRIPTS_H diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d0811e77d..d721a9102 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2931,7 +2931,8 @@ void SetMoveEffect(bool32 primary, u32 certain) } side = GetBattlerSide(gBattlerAttacker); - if (GetBattlerSide(gBattlerAttacker) == B_SIDE_OPPONENT + 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 diff --git a/src/battle_util.c b/src/battle_util.c index d23e85542..3f9dd81ec 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4518,6 +4518,24 @@ 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 From e585be6f2e5ffe7d8df50917060e2905fcae15d0 Mon Sep 17 00:00:00 2001 From: Evan Date: Wed, 11 Nov 2020 18:37:11 -0700 Subject: [PATCH 02/12] format fix --- data/battle_scripts_1.s | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index d470fc1b6..34ae2704b 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -7691,7 +7691,7 @@ BattleScript_PrintPlayerForfeitedLinkBattle:: BattleScript_Pickpocket:: call BattleScript_AbilityPopUp - swapattackerwithtarget - seteffectsecondary - swapattackerwithtarget - return + swapattackerwithtarget + seteffectsecondary + swapattackerwithtarget + return From 64bcac45891d66c59a94e73b961dedbdf4b3c902 Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 12 Nov 2020 23:30:00 -0700 Subject: [PATCH 03/12] move pickpocket to back of moveend sequence --- asm/macros/battle_script.inc | 8 ++ data/battle_scripts_1.s | 6 +- include/battle.h | 1 + include/battle_util.h | 3 + include/constants/battle_script_commands.h | 13 ++- src/battle_script_commands.c | 101 +++++++++++----- src/battle_util.c | 129 +++++++++++++++++---- 7 files changed, 204 insertions(+), 57 deletions(-) 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; +} + From 435f928aa45cfaf12aeda096b6bb5268e6798f7a Mon Sep 17 00:00:00 2001 From: Evan Date: Fri, 13 Nov 2020 11:00:44 -0700 Subject: [PATCH 04/12] opponent trainer theft config option. Clean up code a bit --- asm/macros/battle_script.inc | 16 +++--- include/battle.h | 1 + include/battle_util.h | 2 +- include/constants/battle_config.h | 1 + src/battle_main.c | 6 +- src/battle_script_commands.c | 91 ++++++++++++++++--------------- src/battle_util.c | 59 +++----------------- 7 files changed, 71 insertions(+), 105 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 85f090e1f..6c98d530d 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1755,14 +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 + + .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/include/battle.h b/include/battle.h index 15c4a466b..ffa500ace 100644 --- a/include/battle.h +++ b/include/battle.h @@ -541,6 +541,7 @@ struct BattleStruct u8 sameMoveTurns[MAX_BATTLERS_COUNT]; // For Metronome, number of times the same moves has been SUCCESFULLY used. u16 moveEffect2; // For Knock Off u16 changedSpecies[PARTY_SIZE]; // For Zygarde or future forms when multiple mons can change into the same pokemon. + u16 itemStolen[PARTY_SIZE]; //player's team that had items stolen (bit per party member) }; #define GET_MOVE_TYPE(move, typeArg) \ diff --git a/include/battle_util.h b/include/battle_util.h index 36364bdb7..4623cff26 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -132,6 +132,6 @@ 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); +void TryRestoreStolenItems(void); #endif // GUARD_BATTLE_UTIL_H diff --git a/include/constants/battle_config.h b/include/constants/battle_config.h index 4659e1595..6412129a0 100644 --- a/include/constants/battle_config.h +++ b/include/constants/battle_config.h @@ -132,6 +132,7 @@ #define B_SLEEP_TURNS GEN_6 // In Gen5+, sleep lasts for 1-3 turns instead of 2-5 turns. #define B_PARALYZE_ELECTRIC GEN_6 // In Gen6+, Electric type Pokémon can't be paralyzed. #define B_POWDER_GRASS GEN_6 // In Gen6+, Grass type Pokémon are immune to powder and spore moves. +#define B_TRAINERS_STEAL_ITEMS TRUE // If TRUE, trainer with thief/pickpocket will temporarily steal your items // Animation Settings #define B_NEW_SWORD_PARTICLE TRUE // If set to TRUE, it updates Swords Dance's particle. diff --git a/src/battle_main.c b/src/battle_main.c index 781f9e20b..34280b32c 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4819,6 +4819,10 @@ static void HandleEndTurn_FinishBattle(void) sub_8186444(); BeginFastPaletteFade(3); FadeOutMapMusic(5); + #if B_TRAINERS_STEAL_ITEMS + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) + TryRestoreStolenItems(); + #endif for (i = 0; i < PARTY_SIZE; i++) { UndoMegaEvolution(i); @@ -4837,7 +4841,7 @@ static void HandleEndTurn_FinishBattle(void) static void FreeResetData_ReturnToOvOrDoEvolutions(void) { if (!gPaletteFade.active) - { + { ResetSpriteData(); if (gLeveledUpInBattle && (gBattleOutcome == B_OUTCOME_WON || gBattleOutcome == B_OUTCOME_CAUGHT)) { diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d5b57af48..676fe31e7 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2369,6 +2369,35 @@ static void CheckSetUnburden(u8 battlerId) } } +//slight difference in thief/pickpocket requires this +static void StealTargetItem(void) +{ + 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 for thief + + CheckSetUnburden(gBattlerTarget); + gBattleResources->flags->flags[gBattlerAttacker] &= ~(RESOURCE_FLAG_UNBURDEN); + + gActiveBattler = gBattlerAttacker; + BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedItem); + MarkBattlerForControllerExec(gBattlerAttacker); + + gActiveBattler = gBattlerTarget; + BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gBattleMons[gBattlerTarget].item); + MarkBattlerForControllerExec(gBattlerTarget); + + gBattleStruct->choicedMove[gBattlerTarget] = 0; + + #if B_TRAINERS_STEAL_ITEMS + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER && GetBattlerSide(gBattlerTarget) == B_SIDE_PLAYER) + gBattleStruct->itemStolen[gBattlerPartyIndexes[gBattlerTarget]] = gLastUsedItem; + #endif +} + #define INCREMENT_RESET_RETURN \ { \ gBattlescriptCurrInstr++; \ @@ -2922,7 +2951,7 @@ void SetMoveEffect(bool32 primary, u32 certain) break; case MOVE_EFFECT_STEAL_ITEM: { - if (!ItemCanBeStolen(gBattleMons[gBattlerTarget].item, gBattlerAttacker)) + if (!CanBattlerGetOrLoseItem(gBattlerAttacker, gBattleMons[gBattlerTarget].item)) { gBattlescriptCurrInstr++; break; @@ -2944,28 +2973,9 @@ void SetMoveEffect(bool32 primary, u32 certain) } else { - 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); - - gActiveBattler = gBattlerAttacker; - BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedItem); - MarkBattlerForControllerExec(gBattlerAttacker); - - gActiveBattler = gBattlerTarget; - BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gBattleMons[gBattlerTarget].item); - MarkBattlerForControllerExec(gBattlerTarget); - + StealTargetItem(); BattleScriptPush(gBattlescriptCurrInstr + 1); gBattlescriptCurrInstr = BattleScript_ItemSteal; - - gBattleStruct->choicedMove[gBattlerTarget] = 0; } } @@ -5022,7 +5032,7 @@ static void Cmd_moveend(void) && !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 + && CanBattlerGetOrLoseItem(battler, gBattleMons[gBattlerAttacker].item)) //cannot steal plates, mega stones, etc { gBattlerTarget = gBattlerAbility = battler; BattleScriptPushCursor(); @@ -8316,28 +8326,9 @@ static void Cmd_various(void) 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; + StealTargetItem(); 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; } @@ -11066,7 +11057,11 @@ static void Cmd_tryswapitems(void) // trick | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_SECRET_BASE - | BATTLE_TYPE_x2000000)))) + | BATTLE_TYPE_x2000000 + #if B_TRAINERS_STEAL_ITEMS + | BATTLE_TYPE_TRAINER + #endif + )))) { gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 1); } @@ -11130,6 +11125,16 @@ static void Cmd_tryswapitems(void) // trick PREPARE_ITEM_BUFFER(gBattleTextBuff1, *newItemAtk) PREPARE_ITEM_BUFFER(gBattleTextBuff2, oldItemAtk) + + #if B_TRAINERS_STEAL_ITEMS + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) + { + if (GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER) + gBattleStruct->itemStolen[gBattlerPartyIndexes[gBattlerAttacker]] = oldItemAtk; + if (GetBattlerSide(gBattlerTarget) == B_SIDE_PLAYER) + gBattleStruct->itemStolen[gBattlerPartyIndexes[gBattlerTarget]] = *newItemAtk; + } + #endif if (oldItemAtk != 0 && *newItemAtk != 0) gBattleCommunication[MULTISTRING_CHOOSER] = 2; // attacker's item -> <- target's item diff --git a/src/battle_util.c b/src/battle_util.c index 91d10af23..480e1d57d 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -7793,61 +7793,16 @@ bool32 TestSheerForceFlag(u8 battler, u16 move) return FALSE; } -bool32 ItemCanBeStolen(u16 item, u8 battlerId) +void TryRestoreStolenItems(void) { - u8 effect = ItemId_GetHoldEffect(item); + u32 i; + u16 stolenItem = ITEM_NONE; - 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))) + for (i = 0; i < PARTY_SIZE; i++) { - return FALSE; + stolenItem = gBattleStruct->itemStolen[i]; + if (stolenItem != ITEM_NONE && ItemId_GetPocket(stolenItem) != POCKET_BERRIES) + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &stolenItem); //restore stolen non-berry items } - 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; } From 66152538ec15770713bc9b4ba97f94cfd33f2323 Mon Sep 17 00:00:00 2001 From: Evan Date: Fri, 13 Nov 2020 13:42:49 -0700 Subject: [PATCH 05/12] add back accidentally deleted item theft battle type checks --- include/battle_util.h | 1 + src/battle_script_commands.c | 4 ++-- src/battle_util.c | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/include/battle_util.h b/include/battle_util.h index 4623cff26..3b049be56 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -133,5 +133,6 @@ u8 GetBattleMoveSplit(u32 moveId); void SortBattlersBySpeed(u8 *battlers, bool8 slowToFast); bool32 TestSheerForceFlag(u8 battler, u16 move); void TryRestoreStolenItems(void); +bool8 CanStealItem(u8 battlerId, u16 item); #endif // GUARD_BATTLE_UTIL_H diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 676fe31e7..faee7ec67 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2951,7 +2951,7 @@ void SetMoveEffect(bool32 primary, u32 certain) break; case MOVE_EFFECT_STEAL_ITEM: { - if (!CanBattlerGetOrLoseItem(gBattlerAttacker, gBattleMons[gBattlerTarget].item)) + if (!CanStealItem(gBattlerAttacker, gBattleMons[gBattlerTarget].item)) { gBattlescriptCurrInstr++; break; @@ -5032,7 +5032,7 @@ static void Cmd_moveend(void) && !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 - && CanBattlerGetOrLoseItem(battler, gBattleMons[gBattlerAttacker].item)) //cannot steal plates, mega stones, etc + && CanStealItem(battler, gBattleMons[gBattlerAttacker].item)) //cannot steal plates, mega stones, etc { gBattlerTarget = gBattlerAbility = battler; BattleScriptPushCursor(); diff --git a/src/battle_util.c b/src/battle_util.c index 00aa19f8c..4d38adc04 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -7826,3 +7826,36 @@ void TryRestoreStolenItems(void) } } +bool8 CanStealItem(u8 battlerId, u16 item) +{ + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_HILL) + return FALSE; + + if (GetBattlerSide(gBattlerAttacker) == B_SIDE_OPPONENT + && !(gBattleTypeFlags & + (BATTLE_TYPE_EREADER_TRAINER + | BATTLE_TYPE_FRONTIER + | BATTLE_TYPE_LINK + | BATTLE_TYPE_x2000000 + | BATTLE_TYPE_SECRET_BASE + #if B_TRAINERS_STEAL_ITEMS + | BATTLE_TYPE_TRAINER + #endif + ))) + { + 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(gBattlerAttacker)] & gBitTable[gBattlerPartyIndexes[gBattlerAttacker]])) + { + return FALSE; + } + + return CanBattlerGetOrLoseItem(battlerId, item); +} + From 34dd11448b3edf9bb8f4e7141a4a20854a527a57 Mon Sep 17 00:00:00 2001 From: Evan Date: Wed, 9 Dec 2020 10:28:18 -0700 Subject: [PATCH 06/12] sticky barb transfer + pickpocket combo, fix CanBattlerGetOrLoseItem --- data/battle_scripts_1.s | 19 ++- include/battle.h | 8 +- include/battle_script_commands.h | 1 + include/battle_scripts.h | 1 + include/battle_util.h | 4 +- include/constants/battle_script_commands.h | 1 - include/constants/battle_string_ids.h | 4 +- src/battle_main.c | 3 + src/battle_message.c | 4 + src/battle_script_commands.c | 99 ++++++------- src/battle_util.c | 162 +++++++++++++-------- 11 files changed, 191 insertions(+), 115 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index f51871d1f..0f512dc88 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -7700,10 +7700,25 @@ BattleScript_AnnounceAirLockCloudNine:: BattleScript_Pickpocket:: call BattleScript_AbilityPopUp - setmoveeffect MOVE_EFFECT_STEAL_ITEM + jumpifability BS_ATTACKER, ABILITY_STICKY_HOLD, BattleScript_PickpocketPrevented swapattackerwithtarget - pickpocketsteal call BattleScript_ItemSteal swapattackerwithtarget activateitemeffects BS_TARGET return + +BattleScript_PickpocketPrevented: + pause 0x20 + copybyte gBattlerAbility, gBattlerAttacker + call BattleScript_AbilityPopUp + printstring STRINGID_ITEMCANNOTBEREMOVED + waitmessage 0x40 + return + +BattleScript_StickyBarbTransfer:: + playanimation BS_TARGET, B_ANIM_ITEM_STEAL, NULL + printstring STRINGID_STICKYBARBTRANSFER + waitmessage 0x40 + removeitem BS_TARGET + return + \ No newline at end of file diff --git a/include/battle.h b/include/battle.h index 5c62d0711..fe71c52e6 100644 --- a/include/battle.h +++ b/include/battle.h @@ -427,6 +427,12 @@ struct Illusion struct Pokemon *mon; }; +struct StolenItem +{ + u16 originalItem:15; + u16 stolen:1; +}; + struct BattleStruct { u8 turnEffectsTracker; @@ -541,7 +547,7 @@ struct BattleStruct u8 sameMoveTurns[MAX_BATTLERS_COUNT]; // For Metronome, number of times the same moves has been SUCCESFULLY used. u16 moveEffect2; // For Knock Off u16 changedSpecies[PARTY_SIZE]; // For Zygarde or future forms when multiple mons can change into the same pokemon. - u16 itemStolen[PARTY_SIZE]; //player's team that had items stolen (bit per party member) + struct StolenItem itemStolen[PARTY_SIZE]; //player's team that had items stolen (bit per party member) }; #define GET_MOVE_TYPE(move, typeArg) \ diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index 454b6ab34..60f108520 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -26,6 +26,7 @@ u32 IsFlowerVeilProtected(u32 battler); u32 IsLeafGuardProtected(u32 battler); bool32 IsShieldsDownProtected(u32 battler); u32 IsAbilityStatusProtected(u32 battler); +void StealTargetItem(u8 battlerStealer, u8 battlerItem); extern void (* const gBattleScriptingCommandsTable[])(void); extern const u8 gBattlePalaceNatureToMoveGroupLikelihood[NUM_NATURES][4]; diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 37447ded9..0084a3eab 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -351,5 +351,6 @@ extern const u8 BattleScript_EmergencyExitWildNoPopUp[]; extern const u8 BattleScript_CheekPouchActivates[]; extern const u8 BattleScript_AnnounceAirLockCloudNine[]; extern const u8 BattleScript_Pickpocket[]; +extern const u8 BattleScript_StickyBarbTransfer[]; #endif // GUARD_BATTLE_SCRIPTS_H diff --git a/include/battle_util.h b/include/battle_util.h index 39d8de6a8..3a6746450 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -133,6 +133,8 @@ u8 GetBattleMoveSplit(u32 moveId); void SortBattlersBySpeed(u8 *battlers, bool8 slowToFast); bool32 TestSheerForceFlag(u8 battler, u16 move); void TryRestoreStolenItems(void); -bool8 CanStealItem(u8 battlerId, u16 item); +bool32 CanStealItem(u8 battlerStealing, u8 battlerItem, u16 item); +void TrySaveExchangedItem(u8 battlerId, u16 stolenItem); +bool32 IsPartnerMonFromSameTrainer(u8 battlerId); #endif // GUARD_BATTLE_UTIL_H diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 816a4b991..95c7a57be 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -166,7 +166,6 @@ #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 diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 9348d67d5..8ddea0f21 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -558,8 +558,10 @@ #define STRINGID_AURABREAKENTERS 554 #define STRINGID_COMATOSEENTERS 555 #define STRINGID_SCREENCLEANERENTERS 556 +#define STRINGID_ITEMCANNOTBEREMOVED 557 +#define STRINGID_STICKYBARBTRANSFER 558 -#define BATTLESTRINGS_COUNT 557 +#define BATTLESTRINGS_COUNT 559 //// multichoice message IDs // switch in ability message diff --git a/src/battle_main.c b/src/battle_main.c index a916e3e27..3d589084f 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -2941,6 +2941,9 @@ static void BattleStartClearSetData(void) gBattleStruct->arenaLostOpponentMons = 0; gBattleStruct->mega.triggerSpriteId = 0xFF; + + for (i = 0; i < PARTY_SIZE; i++) + gBattleStruct->itemStolen[i].originalItem = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM); } void SwitchInClearSetData(void) diff --git a/src/battle_message.c b/src/battle_message.c index 35ccc6e26..6dba30a31 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -685,9 +685,13 @@ static const u8 sText_FairyAuraActivates[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} static const u8 sText_AuraBreakActivates[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} reversed all\nother POKéMON's auras!"); static const u8 sText_ComatoseActivates[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} is drowsing!"); static const u8 sText_ScreenCleanerActivates[] = _("All screens on the field were\ncleansed!"); +static const u8 sText_ItemCannotBeRemoved[] = _("{B_ATK_NAME_WITH_PREFIX}'s item cannot be removed!"); +static const u8 sText_StickyBarbTransfer[] = _("The {B_LAST_ITEM} attached itself to\n{B_ATK_NAME_WITH_PREFIX}!"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { + [STRINGID_STICKYBARBTRANSFER - 12] = sText_StickyBarbTransfer, + [STRINGID_ITEMCANNOTBEREMOVED - 12] = sText_ItemCannotBeRemoved, [STRINGID_STATWASNOTLOWERED - 12] = sText_StatWasNotLowered, [STRINGID_CLOAKEDINAFREEZINGLIGHT - 12] = sText_CloakedInAFreezingLight, [STRINGID_DESTINYKNOTACTIVATES - 12] = sText_DestinyKnotActivates, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 34d2271c9..1d236a3a9 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2369,33 +2369,30 @@ static void CheckSetUnburden(u8 battlerId) } } -//slight difference in thief/pickpocket requires this -static void StealTargetItem(void) -{ - gLastUsedItem = gBattleStruct->changedItems[gBattlerAttacker] = gBattleMons[gBattlerTarget].item; - gBattleMons[gBattlerTarget].item = 0; +// battlerStealer steals the item of battlerItem +void StealTargetItem(u8 battlerStealer, u8 battlerItem) +{ + gLastUsedItem = gBattleMons[battlerItem].item; + gBattleMons[battlerItem].item = 0; - RecordItemEffectBattle(gBattlerTarget, 0); - RecordItemEffectBattle(gBattlerAttacker, ItemId_GetHoldEffect(gLastUsedItem)); - //item assignment doesn't happen yet for thief + RecordItemEffectBattle(battlerItem, 0); + RecordItemEffectBattle(battlerStealer, ItemId_GetHoldEffect(gLastUsedItem)); + gBattleMons[battlerStealer].item = gLastUsedItem; - CheckSetUnburden(gBattlerTarget); - gBattleResources->flags->flags[gBattlerAttacker] &= ~(RESOURCE_FLAG_UNBURDEN); + CheckSetUnburden(battlerItem); + gBattleResources->flags->flags[battlerStealer] &= ~(RESOURCE_FLAG_UNBURDEN); - gActiveBattler = gBattlerAttacker; - BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedItem); - MarkBattlerForControllerExec(gBattlerAttacker); + gActiveBattler = battlerStealer; + BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedItem); // set attacker item + MarkBattlerForControllerExec(battlerStealer); - gActiveBattler = gBattlerTarget; - BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gBattleMons[gBattlerTarget].item); - MarkBattlerForControllerExec(gBattlerTarget); + gActiveBattler = battlerItem; + BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gBattleMons[battlerItem].item); // remove target item + MarkBattlerForControllerExec(battlerItem); - gBattleStruct->choicedMove[gBattlerTarget] = 0; + gBattleStruct->choicedMove[battlerItem] = 0; - #if B_TRAINERS_KNOCK_OFF_ITEMS == TRUE - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER && GetBattlerSide(gBattlerTarget) == B_SIDE_PLAYER) - gBattleStruct->itemStolen[gBattlerPartyIndexes[gBattlerTarget]] = gLastUsedItem; - #endif + TrySaveExchangedItem(battlerItem, gLastUsedItem); } #define INCREMENT_RESET_RETURN \ @@ -2951,7 +2948,7 @@ void SetMoveEffect(bool32 primary, u32 certain) break; case MOVE_EFFECT_STEAL_ITEM: { - if (!CanStealItem(gBattlerAttacker, gBattleMons[gBattlerTarget].item)) + if (!CanStealItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerTarget].item)) { gBattlescriptCurrInstr++; break; @@ -2966,14 +2963,15 @@ void SetMoveEffect(bool32 primary, u32 certain) } else if (gBattleMons[gBattlerAttacker].item != 0 || gBattleMons[gBattlerTarget].item == ITEM_ENIGMA_BERRY - || IS_ITEM_MAIL(gBattleMons[gBattlerTarget].item) || gBattleMons[gBattlerTarget].item == 0) { gBattlescriptCurrInstr++; } else { - StealTargetItem(); + StealTargetItem(gBattlerAttacker, gBattlerTarget); //attacker steals target item + gBattleMons[gBattlerAttacker].item = 0; //item assigned later on with thief (see MOVEEND_CHANGED_ITEMS) + gBattleStruct->changedItems[gBattlerAttacker] = gLastUsedItem; // stolen item to be assigned later BattleScriptPush(gBattlescriptCurrInstr + 1); gBattlescriptCurrInstr = BattleScript_ItemSteal; } @@ -5014,29 +5012,33 @@ static void Cmd_moveend(void) 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 + && 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 + SortBattlersBySpeed(battlers, FALSE); // pickpocket activates for fastest mon without item for (i = 0; i < gBattlersCount; i++) { u8 battler = battlers[i]; - //attacker is mon who made contact, battler is mon with pickpocket - 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 pickpocket - && gBattleMons[battler].item == ITEM_NONE //pickpocketer can't have an item already - && CanStealItem(battler, gBattleMons[gBattlerAttacker].item)) //cannot steal plates, mega stones, etc + // attacker is mon who made contact, battler is mon with pickpocket + if (battler != gBattlerAttacker // cannot pickpocket yourself + && GetBattlerAbility(battler) == ABILITY_PICKPOCKET // 'target' must have pickpocket ability + && BATTLER_DAMAGED(battler) // target needs to have been damaged + && !DoesSubstituteBlockMove(gCurrentMove, gBattlerAttacker, battler) // subsitute unaffected + && IsBattlerAlive(battler) // battler must be alive to pickpocket + && gBattleMons[battler].item == ITEM_NONE // pickpocketer can't have an item already + && CanStealItem(battler, gBattlerAttacker, gBattleMons[gBattlerAttacker].item)) // cannot steal plates, mega stones, etc { gBattlerTarget = gBattlerAbility = battler; + // battle scripting is super brittle so we shall do the item exchange now (if possible) + if (GetBattlerAbility(gBattlerAttacker) != ABILITY_STICKY_HOLD) + StealTargetItem(gBattlerTarget, gBattlerAttacker); // target takes attacker's item + + gEffectBattler = gBattlerAttacker; BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_Pickpocket; + gBattlescriptCurrInstr = BattleScript_Pickpocket; // includes sticky hold check to print separate string effect = TRUE; break; // pickpocket activates on fastest mon, so exit loop. } @@ -8322,14 +8324,8 @@ static void Cmd_various(void) } return; case VARIOUS_MOVEEND_ITEM_EFFECTS: - ItemBattleEffects(1, gActiveBattler, FALSE); - break; - case VARIOUS_PICKPOCKET: - { - gBattleScripting.battler = gBattlerAttacker; - StealTargetItem(); - gBattleMons[gBattlerAttacker].item = gLastUsedItem; - } + if (ItemBattleEffects(1, gActiveBattler, FALSE)) + return; break; } @@ -11126,15 +11122,14 @@ static void Cmd_tryswapitems(void) // trick PREPARE_ITEM_BUFFER(gBattleTextBuff1, *newItemAtk) PREPARE_ITEM_BUFFER(gBattleTextBuff2, oldItemAtk) - #if B_TRAINERS_KNOCK_OFF_ITEMS - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) + if (!(sideAttacker == sideTarget && IsPartnerMonFromSameTrainer(gBattlerAttacker))) { + // if targeting your own side and you aren't in a multi battle, don't save items as stolen if (GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER) - gBattleStruct->itemStolen[gBattlerPartyIndexes[gBattlerAttacker]] = oldItemAtk; + TrySaveExchangedItem(gBattlerAttacker, oldItemAtk); if (GetBattlerSide(gBattlerTarget) == B_SIDE_PLAYER) - gBattleStruct->itemStolen[gBattlerPartyIndexes[gBattlerTarget]] = *newItemAtk; + TrySaveExchangedItem(gBattlerTarget, *newItemAtk); } - #endif if (oldItemAtk != 0 && *newItemAtk != 0) gBattleCommunication[MULTISTRING_CHOOSER] = 2; // attacker's item -> <- target's item diff --git a/src/battle_util.c b/src/battle_util.c index 83af12f9d..a503dfc48 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5388,18 +5388,6 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) case HOLD_EFFECT_BLACK_SLUDGE: if (IS_BATTLER_OF_TYPE(battlerId, TYPE_POISON)) goto LEFTOVERS; - case HOLD_EFFECT_STICKY_BARB: - if (!moveTurn) - { - gBattleMoveDamage = gBattleMons[battlerId].maxHP / 8; - if (gBattleMoveDamage == 0) - gBattleMoveDamage = 1; - BattleScriptExecute(BattleScript_ItemHurtEnd2); - effect = ITEM_HP_CHANGE; - RecordItemEffectBattle(battlerId, battlerHoldEffect); - PREPARE_ITEM_BUFFER(gBattleTextBuff1, gLastUsedItem); - } - break; case HOLD_EFFECT_LEFTOVERS: LEFTOVERS: if (gBattleMons[battlerId].hp < gBattleMons[battlerId].maxHP && !moveTurn) @@ -5862,6 +5850,22 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) gBattleScripting.statChanger = SET_STATCHANGER(STAT_SPATK, 1, FALSE); } break; + case HOLD_EFFECT_STICKY_BARB: + if (TARGET_TURN_DAMAGED + && (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) + && IsMoveMakingContact(gCurrentMove, gBattlerAttacker) + && !DoesSubstituteBlockMove(gCurrentMove, gBattlerAttacker, battlerId) + && IsBattlerAlive(gBattlerAttacker) + && gBattleMons[gBattlerAttacker].item == ITEM_NONE) + { + //no sticky hold checks. item is already known so no CanStealItem checks + gEffectBattler = battlerId; //effect battler = target + StealTargetItem(gBattlerAttacker, gBattlerTarget); //attacker takes target's barb + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_StickyBarbTransfer; + effect = ITEM_EFFECT_OTHER; + } + break; } } break; @@ -5890,6 +5894,18 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) RecordItemEffectBattle(battlerId, battlerHoldEffect); } break; + case HOLD_EFFECT_STICKY_BARB: //not an orb per-say, but similar effect, and needs to NOT activate with pickpocket + if (GetBattlerAbility(battlerId) != ABILITY_MAGIC_GUARD) + { + gBattleMoveDamage = gBattleMons[battlerId].maxHP / 8; + if (gBattleMoveDamage == 0) + gBattleMoveDamage = 1; + BattleScriptExecute(BattleScript_ItemHurtEnd2); + effect = ITEM_HP_CHANGE; + RecordItemEffectBattle(battlerId, battlerHoldEffect); + PREPARE_ITEM_BUFFER(gBattleTextBuff1, gLastUsedItem); + } + break; } if (effect == ITEM_STATUS_CHANGE) @@ -7553,7 +7569,7 @@ s32 GetStealthHazardDamage(u8 hazardType, u8 battlerId) return dmg; } -static bool32 IsPartnerMonFromSameTrainer(u8 battlerId) +bool32 IsPartnerMonFromSameTrainer(u8 battlerId) { if (GetBattlerSide(battlerId) == B_SIDE_OPPONENT && gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) return FALSE; @@ -7721,25 +7737,37 @@ bool32 DoBattlersShareType(u32 battler1, u32 battler2) bool32 CanBattlerGetOrLoseItem(u8 battlerId, u16 itemId) { u16 species = gBattleMons[battlerId].species; - - if (IS_ITEM_MAIL(itemId)) - return FALSE; - else if (itemId == ITEM_ENIGMA_BERRY) + u16 holdEffect = ItemId_GetHoldEffect(itemId); + + // Mail can be stolen now + if (itemId == ITEM_ENIGMA_BERRY) return FALSE; else if (species == SPECIES_KYOGRE && itemId == ITEM_BLUE_ORB) return FALSE; else if (species == SPECIES_GROUDON && itemId == ITEM_RED_ORB) return FALSE; // Mega stone cannot be lost if pokemon can mega evolve with it or is already mega evolved. - else if (ItemId_GetHoldEffect(itemId) == HOLD_EFFECT_MEGA_STONE - && ((GetMegaEvolutionSpecies(species, itemId) != SPECIES_NONE) || gBattleStruct->mega.evolvedPartyIds[GetBattlerSide(battlerId)] & gBitTable[gBattlerPartyIndexes[battlerId]])) + else if (holdEffect == HOLD_EFFECT_MEGA_STONE + && ((GetMegaEvolutionSpecies(species, itemId) != SPECIES_NONE) + || gBattleStruct->mega.evolvedPartyIds[GetBattlerSide(battlerId)] & gBitTable[gBattlerPartyIndexes[battlerId]])) return FALSE; +#ifdef HOLD_EFFECT_PRIMAL_ORB + // Primal orbs cannot be lost if the species can undergo primal reversion (no need to check if it has since it always does) + else if (holdEffect == HOLD_EFFECT_PRIMAL_ORB + && ((GetPrimalMegaEvolutionSpecies(species, itemId) != SPECIES_NONE)) +#endif else if (species == SPECIES_GIRATINA && itemId == ITEM_GRISEOUS_ORB) return FALSE; else if (species == SPECIES_GENESECT && GetBattlerHoldEffect(battlerId, FALSE) == HOLD_EFFECT_DRIVE) return FALSE; else if (species == SPECIES_SILVALLY && GetBattlerHoldEffect(battlerId, FALSE) == HOLD_EFFECT_MEMORY) return FALSE; + else if (species == SPECIES_ARCEUS && holdEffect == HOLD_EFFECT_PLATE) + return FALSE; +#ifdef HOLD_EFFECT_Z_CRYSTAL + else if (holdEffect == HOLD_EFFECT_Z_CRYSTAL) + return FALSE; +#endif else return TRUE; } @@ -7835,40 +7863,40 @@ u8 GetBattleMoveSplit(u32 moveId) // 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}; + int i, j, currSpeed, currBattler; + u16 speeds[4] = {0}; - for (i = 0; i < gBattlersCount; i++) - speeds[i] = GetBattlerTotalSpeedStat(battlers[i]); + 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; + for (i = 1; i < gBattlersCount; i++) + { + currBattler = battlers[i]; + currSpeed = 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; - } - } + if (slowToFast) + { + while (j >= 0 && speeds[j] > currSpeed) + { + battlers[j + 1] = battlers[j]; + speeds[j + 1] = speeds[j]; + j = j - 1; + } + } + else + { + while (j >= 0 && speeds[j] < currSpeed) + { + battlers[j + 1] = battlers[j]; + speeds[j + 1] = speeds[j]; + j = j - 1; + } + } - battlers[j + 1] = keyBank; - speeds[j + 1] = key; - } + battlers[j + 1] = currBattler; + speeds[j + 1] = currSpeed; + } } bool32 TestSheerForceFlag(u8 battler, u16 move) @@ -7886,18 +7914,24 @@ void TryRestoreStolenItems(void) for (i = 0; i < PARTY_SIZE; i++) { - stolenItem = gBattleStruct->itemStolen[i]; - if (stolenItem != ITEM_NONE && ItemId_GetPocket(stolenItem) != POCKET_BERRIES) - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &stolenItem); //restore stolen non-berry items + if (gBattleStruct->itemStolen[i].stolen) + { + stolenItem = gBattleStruct->itemStolen[i].originalItem; + if (stolenItem != ITEM_NONE && ItemId_GetPocket(stolenItem) != POCKET_BERRIES) + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &stolenItem); //restore stolen non-berry items + } } } -bool8 CanStealItem(u8 battlerId, u16 item) +bool32 CanStealItem(u8 battlerStealing, u8 battlerItem, u16 item) { + u8 stealerSide = GetBattlerSide(battlerStealing); + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_HILL) return FALSE; - - if (GetBattlerSide(battlerId) == B_SIDE_OPPONENT + + // check if the battler trying to steal should be able to + if (stealerSide == B_SIDE_OPPONENT && !(gBattleTypeFlags & (BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_FRONTIER @@ -7917,11 +7951,25 @@ bool8 CanStealItem(u8 battlerId, u16 item) | BATTLE_TYPE_LINK | BATTLE_TYPE_x2000000 | BATTLE_TYPE_SECRET_BASE)) - && (gWishFutureKnock.knockedOffMons[GetBattlerSide(battlerId)] & gBitTable[gBattlerPartyIndexes[battlerId]])) + && (gWishFutureKnock.knockedOffMons[stealerSide] & gBitTable[gBattlerPartyIndexes[battlerStealing]])) { return FALSE; } - return CanBattlerGetOrLoseItem(battlerId, item); + // check if battler with the item can lose it + return CanBattlerGetOrLoseItem(battlerItem, item); } +void TrySaveExchangedItem(u8 battlerId, u16 stolenItem) +{ + // because BtlController_EmitSetMonData does SetMonData, we need to save the stolen item only if it matches the battler's original + // so, if the player steals an item during battle and has it stolen from it, it will not end the battle with it (naturally) + #if B_TRAINERS_KNOCK_OFF_ITEMS == TRUE + // If regular trainer battle and mon's original item matches what is being stolen, save it to be restored at end of battle + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER + && !(gBattleTypeFlags & BATTLE_TYPE_FRONTIER) + && GetBattlerSide(battlerId) == B_SIDE_PLAYER + && stolenItem == gBattleStruct->itemStolen[gBattlerPartyIndexes[battlerId]].originalItem) + gBattleStruct->itemStolen[gBattlerPartyIndexes[battlerId]].stolen = TRUE; + #endif +} From 5bdf2660002110a722eb5ea504aedf305db2bd8f Mon Sep 17 00:00:00 2001 From: Evan Date: Sun, 20 Dec 2020 19:06:50 -0700 Subject: [PATCH 07/12] add additional item theft check --- include/battle.h | 2 +- include/constants/battle_config.h | 5 +++- src/battle_script_commands.c | 40 +++++++++++++++---------------- src/battle_util.c | 27 +++++++++++---------- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/include/battle.h b/include/battle.h index fcca03cd2..79d2f2016 100644 --- a/include/battle.h +++ b/include/battle.h @@ -548,7 +548,7 @@ struct BattleStruct u8 sameMoveTurns[MAX_BATTLERS_COUNT]; // For Metronome, number of times the same moves has been SUCCESFULLY used. u16 moveEffect2; // For Knock Off u16 changedSpecies[PARTY_SIZE]; // For Zygarde or future forms when multiple mons can change into the same pokemon. - struct StolenItem itemStolen[PARTY_SIZE]; //player's team that had items stolen (bit per party member) + struct StolenItem itemStolen[PARTY_SIZE]; // Player's team that had items stolen (byte per party member) }; #define GET_MOVE_TYPE(move, typeArg) \ diff --git a/include/constants/battle_config.h b/include/constants/battle_config.h index 631138def..e057b85ce 100644 --- a/include/constants/battle_config.h +++ b/include/constants/battle_config.h @@ -141,13 +141,16 @@ #define B_CRITICAL_CAPTURE TRUE // If set to TRUE, Critical Capture will be enabled. #define B_CATCHING_CHARM_BOOST 20 // % boost in Critical Capture odds if player has the Catching Charm. +// Item Theft Settings +#define B_TRAINERS_KNOCK_OFF_ITEMS TRUE // If TRUE, trainers can steal/swap your items (non-berries are restored after battle). In vanilla games trainers cannot steal items. +#define B_KEEP_STOLEN_TRAINER_ITEMS GEN_5 // In Gen5+, you do not keep items stolen from trainers. Wild Pokemon still always have their items permanently stolen. + // Other #define B_DOUBLE_WILD_CHANCE 0 // % chance of encountering two Pokémon in a Wild Encounter. #define B_SLEEP_TURNS GEN_6 // In Gen5+, sleep lasts for 1-3 turns instead of 2-5 turns. #define B_PARALYZE_ELECTRIC GEN_6 // In Gen6+, Electric-type Pokémon can't be paralyzed. #define B_POWDER_GRASS GEN_6 // In Gen6+, Grass-type Pokémon are immune to powder and spore moves. #define B_STEEL_RESISTANCES GEN_6 // In Gen6+, Steel-type Pokémon are no longer resistant to Dark and Ghost moves. -#define B_TRAINERS_KNOCK_OFF_ITEMS TRUE // If TRUE, trainers can steal/swap your items (non-berries are restored after battle). In vanilla games trainers cannot steal items. // Animation Settings #define B_NEW_SWORD_PARTICLE TRUE // If set to TRUE, it updates Swords Dance's particle. diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 26325f1a8..5259e5c32 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2969,9 +2969,9 @@ void SetMoveEffect(bool32 primary, u32 certain) } else { - StealTargetItem(gBattlerAttacker, gBattlerTarget); //attacker steals target item - gBattleMons[gBattlerAttacker].item = 0; //item assigned later on with thief (see MOVEEND_CHANGED_ITEMS) - gBattleStruct->changedItems[gBattlerAttacker] = gLastUsedItem; // stolen item to be assigned later + StealTargetItem(gBattlerAttacker, gBattlerTarget); // Attacker steals target item + gBattleMons[gBattlerAttacker].item = 0; // Item assigned later on with thief (see MOVEEND_CHANGED_ITEMS) + gBattleStruct->changedItems[gBattlerAttacker] = gLastUsedItem; // Stolen item to be assigned later BattleScriptPush(gBattlescriptCurrInstr + 1); gBattlescriptCurrInstr = BattleScript_ItemSteal; } @@ -5012,35 +5012,35 @@ static void Cmd_moveend(void) break; case MOVEEND_PICKPOCKET: if (IsBattlerAlive(gBattlerAttacker) - && 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 + && 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 + SortBattlersBySpeed(battlers, FALSE); // Pickpocket activates for fastest mon without item for (i = 0; i < gBattlersCount; i++) { u8 battler = battlers[i]; - // attacker is mon who made contact, battler is mon with pickpocket - if (battler != gBattlerAttacker // cannot pickpocket yourself - && GetBattlerAbility(battler) == ABILITY_PICKPOCKET // 'target' must have pickpocket ability - && BATTLER_DAMAGED(battler) // target needs to have been damaged - && !DoesSubstituteBlockMove(gCurrentMove, gBattlerAttacker, battler) // subsitute unaffected - && IsBattlerAlive(battler) // battler must be alive to pickpocket - && gBattleMons[battler].item == ITEM_NONE // pickpocketer can't have an item already - && CanStealItem(battler, gBattlerAttacker, gBattleMons[gBattlerAttacker].item)) // cannot steal plates, mega stones, etc + // Attacker is mon who made contact, battler is mon with pickpocket + if (battler != gBattlerAttacker // Cannot pickpocket yourself + && GetBattlerAbility(battler) == ABILITY_PICKPOCKET // Target must have pickpocket ability + && BATTLER_DAMAGED(battler) // Target needs to have been damaged + && !DoesSubstituteBlockMove(gCurrentMove, gBattlerAttacker, battler) // Subsitute unaffected + && IsBattlerAlive(battler) // Battler must be alive to pickpocket + && gBattleMons[battler].item == ITEM_NONE // Pickpocketer can't have an item already + && CanStealItem(battler, gBattlerAttacker, gBattleMons[gBattlerAttacker].item)) // Cannot steal plates, mega stones, etc { gBattlerTarget = gBattlerAbility = battler; - // battle scripting is super brittle so we shall do the item exchange now (if possible) + // Battle scripting is super brittle so we shall do the item exchange now (if possible) if (GetBattlerAbility(gBattlerAttacker) != ABILITY_STICKY_HOLD) - StealTargetItem(gBattlerTarget, gBattlerAttacker); // target takes attacker's item + StealTargetItem(gBattlerTarget, gBattlerAttacker); // Target takes attacker's item gEffectBattler = gBattlerAttacker; BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_Pickpocket; // includes sticky hold check to print separate string + gBattlescriptCurrInstr = BattleScript_Pickpocket; // Includes sticky hold check to print separate string effect = TRUE; - break; // pickpocket activates on fastest mon, so exit loop. + break; // Pickpocket activates on fastest mon, so exit loop. } } } diff --git a/src/battle_util.c b/src/battle_util.c index a503dfc48..34e26e679 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5858,9 +5858,9 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) && IsBattlerAlive(gBattlerAttacker) && gBattleMons[gBattlerAttacker].item == ITEM_NONE) { - //no sticky hold checks. item is already known so no CanStealItem checks - gEffectBattler = battlerId; //effect battler = target - StealTargetItem(gBattlerAttacker, gBattlerTarget); //attacker takes target's barb + // No sticky hold checks. item is already known so no CanStealItem checks + gEffectBattler = battlerId; // gEffectBattler = target + StealTargetItem(gBattlerAttacker, gBattlerTarget); // Attacker takes target's barb BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_StickyBarbTransfer; effect = ITEM_EFFECT_OTHER; @@ -5894,7 +5894,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) RecordItemEffectBattle(battlerId, battlerHoldEffect); } break; - case HOLD_EFFECT_STICKY_BARB: //not an orb per-say, but similar effect, and needs to NOT activate with pickpocket + case HOLD_EFFECT_STICKY_BARB: // Not an orb per se, but similar effect, and needs to NOT activate with pickpocket if (GetBattlerAbility(battlerId) != ABILITY_MAGIC_GUARD) { gBattleMoveDamage = gBattleMons[battlerId].maxHP / 8; @@ -7859,8 +7859,8 @@ u8 GetBattleMoveSplit(u32 moveId) return SPLIT_SPECIAL; } -// sort an array of battlers by speed -// useful for effects like pickpocket, eject button, red card, dancer +// 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, currSpeed, currBattler; @@ -7918,7 +7918,7 @@ void TryRestoreStolenItems(void) { stolenItem = gBattleStruct->itemStolen[i].originalItem; if (stolenItem != ITEM_NONE && ItemId_GetPocket(stolenItem) != POCKET_BERRIES) - SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &stolenItem); //restore stolen non-berry items + SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &stolenItem); // Restore stolen non-berry items } } } @@ -7930,7 +7930,7 @@ bool32 CanStealItem(u8 battlerStealing, u8 battlerItem, u16 item) if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_HILL) return FALSE; - // check if the battler trying to steal should be able to + // Check if the battler trying to steal should be able to if (stealerSide == B_SIDE_OPPONENT && !(gBattleTypeFlags & (BATTLE_TYPE_EREADER_TRAINER @@ -7956,14 +7956,17 @@ bool32 CanStealItem(u8 battlerStealing, u8 battlerItem, u16 item) return FALSE; } - // check if battler with the item can lose it - return CanBattlerGetOrLoseItem(battlerItem, item); + if (!CanBattlerGetOrLoseItem(battlerItem, item) // Battler with item cannot have it stolen + ||!CanBattlerGetOrLoseItem(battlerStealing, item)) // Stealer cannot take the item + return FALSE; + + return TRUE; } void TrySaveExchangedItem(u8 battlerId, u16 stolenItem) { - // because BtlController_EmitSetMonData does SetMonData, we need to save the stolen item only if it matches the battler's original - // so, if the player steals an item during battle and has it stolen from it, it will not end the battle with it (naturally) + // Because BtlController_EmitSetMonData does SetMonData, we need to save the stolen item only if it matches the battler's original + // So, if the player steals an item during battle and has it stolen from it, it will not end the battle with it (naturally) #if B_TRAINERS_KNOCK_OFF_ITEMS == TRUE // If regular trainer battle and mon's original item matches what is being stolen, save it to be restored at end of battle if (gBattleTypeFlags & BATTLE_TYPE_TRAINER From a4260920060e497bd6708000b44cf9758af58c83 Mon Sep 17 00:00:00 2001 From: Evan Date: Tue, 22 Dec 2020 08:35:11 -0700 Subject: [PATCH 08/12] fix sticky barb theft in trainer battles --- include/constants/battle_config.h | 3 +-- src/battle_util.c | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/constants/battle_config.h b/include/constants/battle_config.h index e057b85ce..e091d8ca2 100644 --- a/include/constants/battle_config.h +++ b/include/constants/battle_config.h @@ -142,8 +142,7 @@ #define B_CATCHING_CHARM_BOOST 20 // % boost in Critical Capture odds if player has the Catching Charm. // Item Theft Settings -#define B_TRAINERS_KNOCK_OFF_ITEMS TRUE // If TRUE, trainers can steal/swap your items (non-berries are restored after battle). In vanilla games trainers cannot steal items. -#define B_KEEP_STOLEN_TRAINER_ITEMS GEN_5 // In Gen5+, you do not keep items stolen from trainers. Wild Pokemon still always have their items permanently stolen. +#define B_TRAINERS_KNOCK_OFF_ITEMS TRUE // If TRUE, trainers can steal/swap your items (non-berries are restored after battle). In vanilla games trainers cannot steal items. // Other #define B_DOUBLE_WILD_CHANCE 0 // % chance of encountering two Pokémon in a Wild Encounter. diff --git a/src/battle_util.c b/src/battle_util.c index 34e26e679..72e79e6fa 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5856,9 +5856,10 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) && IsMoveMakingContact(gCurrentMove, gBattlerAttacker) && !DoesSubstituteBlockMove(gCurrentMove, gBattlerAttacker, battlerId) && IsBattlerAlive(gBattlerAttacker) + && CanStealItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerTarget].item) && gBattleMons[gBattlerAttacker].item == ITEM_NONE) { - // No sticky hold checks. item is already known so no CanStealItem checks + // No sticky hold checks. gEffectBattler = battlerId; // gEffectBattler = target StealTargetItem(gBattlerAttacker, gBattlerTarget); // Attacker takes target's barb BattleScriptPushCursor(); From d8d00ba96746c0860066f482d0c910579de60ee9 Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 21 Jan 2021 21:28:19 -0700 Subject: [PATCH 09/12] fix orb check --- src/battle_util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index 8ab69f4b5..8f1fe3c24 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -7857,9 +7857,9 @@ bool32 CanBattlerGetOrLoseItem(u8 battlerId, u16 itemId) // Mail can be stolen now if (itemId == ITEM_ENIGMA_BERRY) return FALSE; - else if (GET_BASE_SPECIES_ID(species) == SPECIES_KYOGRE && (itemId == ITEM_BLUE_ORB || holdEffect == HOLD_EFFECT_PRIMAL_ORB)) + else if (GET_BASE_SPECIES_ID(species) == SPECIES_KYOGRE && itemId == ITEM_BLUE_ORB) // includes primal return FALSE; - else if (GET_BASE_SPECIES_ID(species) == SPECIES_GROUDON && (itemId == ITEM_BLUE_ORB || holdEffect == HOLD_EFFECT_PRIMAL_ORB)) + else if (GET_BASE_SPECIES_ID(species) == SPECIES_GROUDON && itemId == ITEM_RED_ORB) // includes primal return FALSE; // Mega stone cannot be lost if pokemon can mega evolve with it or is already mega evolved. else if (holdEffect == HOLD_EFFECT_MEGA_STONE From e3d30602d9ee7dc9cabde93e1f1a505788f69803 Mon Sep 17 00:00:00 2001 From: ghoulslash Date: Thu, 12 Aug 2021 01:29:52 -0400 Subject: [PATCH 10/12] record item effect with trick --- src/battle_script_commands.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 99d83f8f4..eaa0e4f63 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -11302,6 +11302,9 @@ static void Cmd_tryswapitems(void) // trick gBattleMons[gBattlerAttacker].item = 0; gBattleMons[gBattlerTarget].item = oldItemAtk; + + RecordItemEffectBattle(gBattlerAttacker, 0); + RecordItemEffectBattle(gBattlerTarget, ItemId_GetHoldEffect(oldItemAtk)); gActiveBattler = gBattlerAttacker; BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, newItemAtk); From 7b0e60c705ca713366e641f4fd6bc2a9e222ef3c Mon Sep 17 00:00:00 2001 From: ghoulslash Date: Thu, 12 Aug 2021 12:44:53 -0600 Subject: [PATCH 11/12] some tweaks --- src/battle_script_commands.c | 1 + src/battle_util.c | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index eaa0e4f63..f8804aa8c 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5125,6 +5125,7 @@ static void Cmd_moveend(void) case MOVEEND_PICKPOCKET: if (IsBattlerAlive(gBattlerAttacker) && gBattleMons[gBattlerAttacker].item != ITEM_NONE // Attacker must be holding an item + && !(gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerAttacker)] & gBitTable[gBattlerPartyIndexes[gBattlerAttacker]]) // But not knocked off && !(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 diff --git a/src/battle_util.c b/src/battle_util.c index f57561dae..5f60fed86 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8419,16 +8419,14 @@ bool32 CanBattlerGetOrLoseItem(u8 battlerId, u16 itemId) return FALSE; else if (GET_BASE_SPECIES_ID(species) == SPECIES_GROUDON && itemId == ITEM_RED_ORB) // includes primal return FALSE; - // Mega stone cannot be lost if pokemon can mega evolve with it or is already mega evolved. - else if (holdEffect == HOLD_EFFECT_MEGA_STONE - && ((GetMegaEvolutionSpecies(species, itemId) != SPECIES_NONE) - || gBattleStruct->mega.evolvedPartyIds[GetBattlerSide(battlerId)] & gBitTable[gBattlerPartyIndexes[battlerId]])) + // Mega stone cannot be lost if pokemon's base species can mega evolve with it. + else if (holdEffect == HOLD_EFFECT_MEGA_STONE && (GetMegaEvolutionSpecies(GET_BASE_SPECIES_ID(species), itemId) != SPECIES_NONE)) return FALSE; else if (GET_BASE_SPECIES_ID(species) == SPECIES_GIRATINA && itemId == ITEM_GRISEOUS_ORB) return FALSE; - else if (GET_BASE_SPECIES_ID(species) == SPECIES_GENESECT && GetBattlerHoldEffect(battlerId, FALSE) == HOLD_EFFECT_DRIVE) + else if (GET_BASE_SPECIES_ID(species) == SPECIES_GENESECT && holdEffect == HOLD_EFFECT_DRIVE) return FALSE; - else if (GET_BASE_SPECIES_ID(species) == SPECIES_SILVALLY && GetBattlerHoldEffect(battlerId, FALSE) == HOLD_EFFECT_MEMORY) + else if (GET_BASE_SPECIES_ID(species) == SPECIES_SILVALLY && holdEffect == HOLD_EFFECT_MEMORY) return FALSE; else if (GET_BASE_SPECIES_ID(species) == SPECIES_ARCEUS && holdEffect == HOLD_EFFECT_PLATE) return FALSE; From d60545af7a7cf76626890620c42a134319264ad5 Mon Sep 17 00:00:00 2001 From: LOuroboros Date: Tue, 31 Aug 2021 23:49:23 -0300 Subject: [PATCH 12/12] Fixed U-turn and Choice Items interaction --- src/battle_script_commands.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 21bc7945b..5d2249e03 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -4872,9 +4872,8 @@ static void Cmd_moveend(void) && (*choicedMoveAtk == 0 || *choicedMoveAtk == 0xFFFF)) { if ((gBattleMoves[gChosenMove].effect == EFFECT_BATON_PASS - || gBattleMoves[gChosenMove].effect == EFFECT_HEALING_WISH - || gBattleMoves[gChosenMove].effect == EFFECT_HIT_ESCAPE) - && !(gMoveResultFlags & MOVE_RESULT_FAILED)) + || gBattleMoves[gChosenMove].effect == EFFECT_HEALING_WISH) + && !(gMoveResultFlags & MOVE_RESULT_FAILED)) { ++gBattleScripting.moveendState; break;