diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 91b6f34be..5c56a7255 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1772,6 +1772,14 @@ .macro setzeffect various BS_ATTACKER, VARIOUS_SET_Z_EFFECT .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 1ac9a4142..a8554ba69 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8045,3 +8045,28 @@ BattleScript_EffectTerrainHit: BattleScript_TryFaint: tryfaintmon BS_TARGET, FALSE, NULL goto BattleScript_MoveEnd + +BattleScript_Pickpocket:: + call BattleScript_AbilityPopUp + jumpifability BS_ATTACKER, ABILITY_STICKY_HOLD, BattleScript_PickpocketPrevented + swapattackerwithtarget + call BattleScript_ItemSteal + swapattackerwithtarget + activateitemeffects BS_TARGET + return + +BattleScript_PickpocketPrevented: + pause B_WAIT_TIME_SHORT + copybyte gBattlerAbility, gBattlerAttacker + call BattleScript_AbilityPopUp + printstring STRINGID_ITEMCANNOTBEREMOVED + waitmessage B_WAIT_TIME_LONG + return + +BattleScript_StickyBarbTransfer:: + playanimation BS_TARGET, B_ANIM_ITEM_STEAL, NULL + printstring STRINGID_STICKYBARBTRANSFER + waitmessage B_WAIT_TIME_LONG + removeitem BS_TARGET + return + diff --git a/include/battle.h b/include/battle.h index dfde52cfc..13832bcf5 100644 --- a/include/battle.h +++ b/include/battle.h @@ -490,6 +490,12 @@ struct ZMoveData u8 splits[MAX_BATTLERS_COUNT]; }; +struct StolenItem +{ + u16 originalItem:15; + u16 stolen:1; +}; + struct BattleStruct { u8 turnEffectsTracker; @@ -607,6 +613,7 @@ struct BattleStruct u16 moveEffect2; // For Knock Off u16 changedSpecies[PARTY_SIZE]; // For Zygarde or future forms when multiple mons can change into the same pokemon. u8 quickClawBattlerId; + struct StolenItem itemStolen[PARTY_SIZE]; // Player's team that had items stolen (two bytes per party member) }; #define GET_MOVE_TYPE(move, typeArg) \ @@ -623,6 +630,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_script_commands.h b/include/battle_script_commands.h index a46bf6494..2e6a0aa4e 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -35,6 +35,7 @@ u32 IsAbilityStatusProtected(u32 battler); bool32 TryResetBattlerStatChanges(u8 battler); bool32 CanCamouflage(u8 battlerId); u16 GetNaturePowerMove(void); +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 0edddebe7..36a4694fc 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -369,6 +369,9 @@ extern const u8 BattleScript_JabocaRowapBerryActivates[]; extern const u8 BattleScript_NotAffectedAbilityPopUp[]; extern const u8 BattleScript_BattlerShookOffTaunt[]; extern const u8 BattleScript_BattlerGotOverItsInfatuation[]; +extern const u8 BattleScript_Pickpocket[]; +extern const u8 BattleScript_StickyBarbTransfer[]; + // zmoves extern const u8 BattleScript_ZMoveActivateDamaging[]; diff --git a/include/battle_util.h b/include/battle_util.h index f89ff6027..0027146fb 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -140,6 +140,13 @@ bool32 IsHealBlockPreventingMove(u32 battler, u32 move); bool32 HasEnoughHpToEatBerry(u32 battlerId, u32 hpFraction, u32 itemId); bool32 IsPartnerMonFromSameTrainer(u8 battlerId); u8 GetSplitBasedOnStats(u8 battlerId); +void SortBattlersBySpeed(u8 *battlers, bool8 slowToFast); +bool32 TestSheerForceFlag(u8 battler, u16 move); +void TryRestoreStolenItems(void); +bool32 CanStealItem(u8 battlerStealing, u8 battlerItem, u16 item); +void TrySaveExchangedItem(u8 battlerId, u16 stolenItem); +bool32 IsPartnerMonFromSameTrainer(u8 battlerId); + // ability checks bool32 IsRolePlayBannedAbilityAtk(u16 ability); diff --git a/include/constants/battle_config.h b/include/constants/battle_config.h index b14428fc7..6da4c73b0 100644 --- a/include/constants/battle_config.h +++ b/include/constants/battle_config.h @@ -188,6 +188,9 @@ #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. + // Other #define B_DOUBLE_WILD_CHANCE 0 // % chance of encountering two Pokémon in a Wild Encounter. #define B_SLEEP_TURNS GEN_7 // In Gen5+, sleep lasts for 1-3 turns instead of 2-5 turns. diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 1b68827d5..c6fe2df00 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -174,6 +174,7 @@ #define VARIOUS_TOTEM_BOOST 103 #define VARIOUS_TRY_ACTIVATE_GRIM_NEIGH 104 #define VARIOUS_SET_Z_EFFECT 105 +#define VARIOUS_MOVEEND_ITEM_EFFECTS 106 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 @@ -234,9 +235,10 @@ #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 #endif // GUARD_CONSTANTS_BATTLE_SCRIPT_COMMANDS_H diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index b6d50da62..a4e0851d8 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -579,8 +579,10 @@ #define STRINGID_ZMOVESTATUP 575 #define STRINGID_ZMOVEHPTRAP 576 #define STRINGID_TERRAINREMOVED 577 +#define STRINGID_ITEMCANNOTBEREMOVED 578 +#define STRINGID_STICKYBARBTRANSFER 579 -#define BATTLESTRINGS_COUNT 578 +#define BATTLESTRINGS_COUNT 580 //// multichoice message IDs // switch in ability message diff --git a/include/constants/hold_effects.h b/include/constants/hold_effects.h index 3afad4f55..13e886944 100644 --- a/include/constants/hold_effects.h +++ b/include/constants/hold_effects.h @@ -134,6 +134,7 @@ #define HOLD_EFFECT_LUMINOUS_MOSS 142 #define HOLD_EFFECT_SNOWBALL 143 #define HOLD_EFFECT_WEAKNESS_POLICY 144 +#define HOLD_EFFECT_PRIMAL_ORB 145 // Gen7 hold effects #define HOLD_EFFECT_PROTECTIVE_PADS 154 diff --git a/src/battle_main.c b/src/battle_main.c index d080b8a1b..1a12a71c6 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -2929,6 +2929,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) @@ -4877,6 +4880,10 @@ static void HandleEndTurn_FinishBattle(void) sub_8186444(); BeginFastPaletteFade(3); FadeOutMapMusic(5); + #if B_TRAINERS_KNOCK_OFF_ITEMS + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) + TryRestoreStolenItems(); + #endif for (i = 0; i < PARTY_SIZE; i++) { UndoMegaEvolution(i); diff --git a/src/battle_message.c b/src/battle_message.c index 7713082e8..b5e825313 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -705,6 +705,9 @@ static const u8 sText_ZMoveRestoreHp[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} rest static const u8 sText_ZMoveStatUp[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} boosted\nits stats using its Z-Power!"); static const u8 sText_ZMoveHpSwitchInTrap[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX}'s HP was restored by the Z-Power!"); static const u8 sText_TerrainReturnedToNormal[] = _("The terrain returned to\nnormal!"); +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] = { @@ -716,6 +719,8 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = [STRINGID_ZMOVERESTOREHP - 12] = sText_ZMoveRestoreHp, [STRINGID_ZMOVESTATUP - 12] = sText_ZMoveStatUp, [STRINGID_ZMOVEHPTRAP - 12] = sText_ZMoveHpSwitchInTrap, + [STRINGID_STICKYBARBTRANSFER - 12] = sText_StickyBarbTransfer, + [STRINGID_ITEMCANNOTBEREMOVED - 12] = sText_ItemCannotBeRemoved, [STRINGID_PKMNGOTOVERITSINFATUATION - 12] = sText_PkmnGotOverItsInfatuation, [STRINGID_PKMNSHOOKOFFTHETAUNT - 12] = sText_PkmnShookOffTheTaunt, [STRINGID_MICLEBERRYACTIVATES - 12] = sText_MicleBerryActivates, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 11fb71274..3bd320f33 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2459,6 +2459,32 @@ static void CheckSetUnburden(u8 battlerId) } } +// battlerStealer steals the item of battlerItem +void StealTargetItem(u8 battlerStealer, u8 battlerItem) +{ + gLastUsedItem = gBattleMons[battlerItem].item; + gBattleMons[battlerItem].item = 0; + + RecordItemEffectBattle(battlerItem, 0); + RecordItemEffectBattle(battlerStealer, ItemId_GetHoldEffect(gLastUsedItem)); + gBattleMons[battlerStealer].item = gLastUsedItem; + + CheckSetUnburden(battlerItem); + gBattleResources->flags->flags[battlerStealer] &= ~(RESOURCE_FLAG_UNBURDEN); + + gActiveBattler = battlerStealer; + BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedItem); // set attacker item + MarkBattlerForControllerExec(battlerStealer); + + gActiveBattler = battlerItem; + BtlController_EmitSetMonData(0, REQUEST_HELDITEM_BATTLE, 0, 2, &gBattleMons[battlerItem].item); // remove target item + MarkBattlerForControllerExec(battlerItem); + + gBattleStruct->choicedMove[battlerItem] = 0; + + TrySaveExchangedItem(battlerItem, gLastUsedItem); +} + #define INCREMENT_RESET_RETURN \ { \ gBattlescriptCurrInstr++; \ @@ -2509,9 +2535,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 @@ -3005,7 +3029,7 @@ void SetMoveEffect(bool32 primary, u32 certain) break; case MOVE_EFFECT_STEAL_ITEM: { - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_HILL) + if (!CanStealItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerTarget].item)) { gBattlescriptCurrInstr++; break; @@ -3043,31 +3067,17 @@ 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 { - gLastUsedItem = gBattleStruct->changedItems[gBattlerAttacker] = gBattleMons[gBattlerTarget].item; - gBattleMons[gBattlerTarget].item = 0; - - 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(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; - - gBattleStruct->choicedMove[gBattlerTarget] = 0; } } @@ -4879,9 +4889,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; @@ -5105,7 +5114,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) { @@ -5119,6 +5128,43 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; + 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 + { + 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]; + // 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; // Includes sticky hold check to print separate string + 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) { @@ -8471,6 +8517,10 @@ static void Cmd_various(void) case VARIOUS_SET_Z_EFFECT: SetZEffect(); //handles battle script jumping internally return; + case VARIOUS_MOVEEND_ITEM_EFFECTS: + if (ItemBattleEffects(1, gActiveBattler, FALSE)) + return; + break; } gBattlescriptCurrInstr += 3; @@ -11249,7 +11299,11 @@ static void Cmd_tryswapitems(void) // trick | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_SECRET_BASE - | BATTLE_TYPE_RECORDED_LINK)))) + | BATTLE_TYPE_RECORDED_LINK + #if B_TRAINERS_KNOCK_OFF_ITEMS + | BATTLE_TYPE_TRAINER + #endif + )))) { gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 1); } @@ -11297,6 +11351,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); @@ -11313,6 +11370,15 @@ static void Cmd_tryswapitems(void) // trick PREPARE_ITEM_BUFFER(gBattleTextBuff1, *newItemAtk) PREPARE_ITEM_BUFFER(gBattleTextBuff2, oldItemAtk) + + 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) + TrySaveExchangedItem(gBattlerAttacker, oldItemAtk); + if (GetBattlerSide(gBattlerTarget) == B_SIDE_PLAYER) + TrySaveExchangedItem(gBattlerTarget, *newItemAtk); + } if (oldItemAtk != 0 && *newItemAtk != 0) gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ITEM_SWAP_BOTH; // attacker's item -> <- target's item diff --git a/src/battle_util.c b/src/battle_util.c index 9f191aa7b..02f518c8b 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4644,7 +4644,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && 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] != MAX_STAT_STAGE) { SET_STATCHANGER(STAT_SPATK, 1, FALSE); @@ -4662,7 +4662,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && 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) && CountUsablePartyMons(battler) > 0) @@ -5925,18 +5925,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) @@ -6497,6 +6485,23 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) case HOLD_EFFECT_MARANGA_BERRY: // consume and boost sp. defense if used special move effect = DamagedStatBoostBerryEffect(battlerId, STAT_SPDEF, SPLIT_SPECIAL); break; + case HOLD_EFFECT_STICKY_BARB: + if (TARGET_TURN_DAMAGED + && (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) + && IsMoveMakingContact(gCurrentMove, gBattlerAttacker) + && !DoesSubstituteBlockMove(gCurrentMove, gBattlerAttacker, battlerId) + && IsBattlerAlive(gBattlerAttacker) + && CanStealItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerTarget].item) + && gBattleMons[gBattlerAttacker].item == ITEM_NONE) + { + // No sticky hold checks. + gEffectBattler = battlerId; // gEffectBattler = target + StealTargetItem(gBattlerAttacker, gBattlerTarget); // Attacker takes target's barb + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_StickyBarbTransfer; + effect = ITEM_EFFECT_OTHER; + } + break; } } break; @@ -6530,6 +6535,18 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) RecordItemEffectBattle(battlerId, battlerHoldEffect); } break; + 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; + 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) @@ -8435,25 +8452,30 @@ bool32 DoBattlersShareType(u32 battler1, u32 battler2) bool32 CanBattlerGetOrLoseItem(u8 battlerId, u16 itemId) { u16 species = gBattleMons[battlerId].species; - - if (IS_ITEM_MAIL(itemId)) + u16 holdEffect = ItemId_GetHoldEffect(itemId); + + // Mail can be stolen now + if (itemId == ITEM_ENIGMA_BERRY) return FALSE; - else if (itemId == ITEM_ENIGMA_BERRY) + else if (GET_BASE_SPECIES_ID(species) == SPECIES_KYOGRE && itemId == ITEM_BLUE_ORB) // includes primal return FALSE; - else if (species == SPECIES_KYOGRE && itemId == ITEM_BLUE_ORB) + else if (GET_BASE_SPECIES_ID(species) == SPECIES_GROUDON && itemId == ITEM_RED_ORB) // includes primal return FALSE; - else if (species == SPECIES_GROUDON && itemId == ITEM_RED_ORB) + // 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; - // 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 (GET_BASE_SPECIES_ID(species) == SPECIES_GIRATINA && itemId == ITEM_GRISEOUS_ORB) return FALSE; - else if (species == SPECIES_GIRATINA && itemId == ITEM_GRISEOUS_ORB) + else if (GET_BASE_SPECIES_ID(species) == SPECIES_GENESECT && holdEffect == HOLD_EFFECT_DRIVE) return FALSE; - else if (species == SPECIES_GENESECT && GetBattlerHoldEffect(battlerId, FALSE) == HOLD_EFFECT_DRIVE) + else if (GET_BASE_SPECIES_ID(species) == SPECIES_SILVALLY && holdEffect == HOLD_EFFECT_MEMORY) return FALSE; - else if (species == SPECIES_SILVALLY && GetBattlerHoldEffect(battlerId, FALSE) == HOLD_EFFECT_MEMORY) + else if (GET_BASE_SPECIES_ID(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; } @@ -8721,3 +8743,122 @@ bool32 IsEntrainmentTargetOrSimpleBeamBannedAbility(u16 ability) } return FALSE; } + +// 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; + u16 speeds[4] = {0}; + + for (i = 0; i < gBattlersCount; i++) + speeds[i] = GetBattlerTotalSpeedStat(battlers[i]); + + for (i = 1; i < gBattlersCount; i++) + { + currBattler = battlers[i]; + currSpeed = speeds[i]; + j = i - 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] = currBattler; + speeds[j + 1] = currSpeed; + } +} + +bool32 TestSheerForceFlag(u8 battler, u16 move) +{ + if (GetBattlerAbility(battler) == ABILITY_SHEER_FORCE && gBattleMoves[move].flags & FLAG_SHEER_FORCE_BOOST) + return TRUE; + else + return FALSE; +} + +void TryRestoreStolenItems(void) +{ + u32 i; + u16 stolenItem = ITEM_NONE; + + for (i = 0; i < PARTY_SIZE; i++) + { + 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 + } + } +} + +bool32 CanStealItem(u8 battlerStealing, u8 battlerItem, u16 item) +{ + u8 stealerSide = GetBattlerSide(battlerStealing); + + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_HILL) + return FALSE; + + // Check if the battler trying to steal should be able to + if (stealerSide == B_SIDE_OPPONENT + && !(gBattleTypeFlags & + (BATTLE_TYPE_EREADER_TRAINER + | BATTLE_TYPE_FRONTIER + | BATTLE_TYPE_LINK + | BATTLE_TYPE_RECORDED_LINK + | BATTLE_TYPE_SECRET_BASE + #if B_TRAINERS_KNOCK_OFF_ITEMS + | BATTLE_TYPE_TRAINER + #endif + ))) + { + return FALSE; + } + else if (!(gBattleTypeFlags & + (BATTLE_TYPE_EREADER_TRAINER + | BATTLE_TYPE_FRONTIER + | BATTLE_TYPE_LINK + | BATTLE_TYPE_RECORDED_LINK + | BATTLE_TYPE_SECRET_BASE)) + && (gWishFutureKnock.knockedOffMons[stealerSide] & gBitTable[gBattlerPartyIndexes[battlerStealing]])) + { + return FALSE; + } + + 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) + #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 +} +