Final 1.10 master upcoming merge (#6350)

This commit is contained in:
Alex 2025-02-26 22:56:18 +01:00 committed by GitHub
commit 0a0189de98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 165 additions and 78 deletions

View File

@ -1034,6 +1034,7 @@ BattleScript_EffectCoaching::
setallytonexttarget EffectCoaching_CheckAllyStats
goto BattleScript_ButItFailed
EffectCoaching_CheckAllyStats:
accuracycheck BattleScript_ButItFailed, NO_ACC_CALC_CHECK_LOCK_ON
jumpifstat BS_TARGET, CMP_NOT_EQUAL, STAT_ATK, MAX_STAT_STAGE, BattleScript_CoachingWorks
jumpifstat BS_TARGET, CMP_NOT_EQUAL, STAT_DEF, MAX_STAT_STAGE, BattleScript_CoachingWorks
goto BattleScript_ButItFailed @ ally at max atk, def
@ -3776,6 +3777,7 @@ BattleScript_TwoTurnMovesSecondTurn::
BattleScript_TwoTurnMovesSecondTurnRet:
setbyte sB_ANIM_TURN, 1
setbyte sB_ANIM_TARGETS_HIT, 0
clearstatusfromeffect BS_ATTACKER, MOVE_EFFECT_CHARGING
clearsemiinvulnerablebit @ only for moves with EFFECT_SEMI_INVULNERABLE/EFFECT_SKY_DROP
return

View File

@ -54,6 +54,7 @@ Pull Requests that fall into this category are not in scope by default and shoul
2. **Fangame Features**: Adds a popular feature from other fangames
3. **Popular Non-SS Features**: Exceptions can be made for uniquely popular or requested features (Drowsy, PLA Legend Plate, etc.)
4. **External Program**: External programs like poryscript, porymoves, etc.
5. **Intergenerational Feature Compatibility**: Addresses limitations and issues resulting from including all generational behaviours in a GBA native title, and extrapolation of features no longer supported by GameFreak
## Workflow for Proposed Feature Scope Discussion
For the contributor:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 B

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 560 B

View File

@ -829,7 +829,10 @@ struct BattleStruct
u8 calculatedSpreadMoveAccuracy:1;
u8 printedStrongWindsWeakenedAttack:1;
u8 numSpreadTargets:2;
u8 padding3:2;
u8 bypassMoldBreakerChecks:1; // for ABILITYEFFECT_IMMUNITY
u8 padding3:1;
u8 usedEjectItem;
u8 usedMicleBerry;
struct MessageStatus slideMessageStatus;
u8 trainerSlideSpriteIds[MAX_BATTLERS_COUNT];
u8 embodyAspectBoost[NUM_BATTLE_SIDES];

View File

@ -218,6 +218,7 @@ bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData
void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, struct AiLogicData *aiData);
u32 IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move);
bool32 IsBattlerItemEnabled(u32 battler);
bool32 IsBattlerPredictedToSwitch(u32 battler);
bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef);

View File

@ -558,7 +558,7 @@ enum MoveEffects
#define MOVE_TARGET_SELECTED 0
#define MOVE_TARGET_DEPENDS (1 << 0)
#define MOVE_TARGET_USER_OR_SELECTED (1 << 1)
#define MOVE_TARGET_OPPONENT (1 << 1)
#define MOVE_TARGET_RANDOM (1 << 2)
#define MOVE_TARGET_BOTH (1 << 3)
#define MOVE_TARGET_USER (1 << 4)

View File

@ -270,10 +270,10 @@
#define OBJ_EVENT_GFX_VAR_E (OBJ_EVENT_GFX_VARS + 0xE)
#define OBJ_EVENT_GFX_VAR_F (OBJ_EVENT_GFX_VARS + 0xF)
#define OBJ_EVENT_MON (1u << 15)
#define OBJ_EVENT_MON_SHINY (1u << 14)
#define OBJ_EVENT_MON_FEMALE (1u << 13)
#define OBJ_EVENT_MON_SPECIES_MASK (~(7u << 13))
#define OBJ_EVENT_MON (1u << 14)
#define OBJ_EVENT_MON_SHINY (1u << 13)
#define OBJ_EVENT_MON_FEMALE (1u << 12)
#define OBJ_EVENT_MON_SPECIES_MASK (~(7u << 12))
// Used to call a specific species' follower graphics. Useful for static encounters.
#define OBJ_EVENT_GFX_SPECIES(name) (SPECIES_##name + OBJ_EVENT_MON)

View File

@ -2349,9 +2349,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
break;
case EFFECT_NATURAL_GIFT:
if (aiData->abilities[battlerAtk] == ABILITY_KLUTZ
|| gFieldStatuses & STATUS_FIELD_MAGIC_ROOM
|| GetPocketByItemId(gBattleMons[battlerAtk].item) != POCKET_BERRIES)
if (!IsBattlerItemEnabled(battlerAtk) || GetPocketByItemId(gBattleMons[battlerAtk].item) != POCKET_BERRIES)
ADJUST_SCORE(-10);
break;
case EFFECT_GRASSY_TERRAIN:
@ -2458,8 +2456,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
break;
case EFFECT_EMBARGO:
if (aiData->abilities[battlerDef] == ABILITY_KLUTZ
|| gFieldStatuses & STATUS_FIELD_MAGIC_ROOM
if (!IsBattlerItemEnabled(battlerAtk)
|| gDisableStructs[battlerDef].embargoTimer != 0
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
ADJUST_SCORE(-10);
@ -2706,7 +2703,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
} // move effect checks
// Choice items
if (HOLD_EFFECT_CHOICE(aiData->holdEffects[battlerAtk]) && gBattleMons[battlerAtk].ability != ABILITY_KLUTZ)
if (HOLD_EFFECT_CHOICE(aiData->holdEffects[battlerAtk]) && IsBattlerItemEnabled(battlerAtk))
{
// Don't use user-target moves ie. Swords Dance, with exceptions
if ((moveTarget & MOVE_TARGET_USER)
@ -3369,6 +3366,12 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT && effectiveness >= UQ_4_12(1.0))
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_DREAM_EATER:
case EFFECT_STRENGTH_SAP:
case EFFECT_AQUA_RING:
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT)
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_EXPLOSION:
case EFFECT_MEMENTO:
if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_WILL_SUICIDE && gBattleMons[battlerDef].statStages[STAT_EVASION] < 7)
@ -3590,8 +3593,6 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
case EFFECT_MOONLIGHT:
if (ShouldRecover(battlerAtk, battlerDef, move, 50))
ADJUST_SCORE(GOOD_EFFECT);
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT)
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_TOXIC:
case EFFECT_POISON:
@ -3667,7 +3668,8 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|| aiData->abilities[battlerDef] == ABILITY_MAGIC_GUARD)
break;
ADJUST_SCORE(GOOD_EFFECT);
if (!HasDamagingMove(battlerDef) || IsBattlerTrapped(battlerDef, FALSE))
if (!HasDamagingMove(battlerDef) || IsBattlerTrapped(battlerDef, FALSE)
|| aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT)
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_DO_NOTHING:

View File

@ -966,7 +966,7 @@ static bool32 ShouldSwitchIfBadChoiceLock(u32 battler)
{
u32 holdEffect = GetBattlerHoldEffect(battler, FALSE);
if (HOLD_EFFECT_CHOICE(holdEffect) && gBattleMons[battler].ability != ABILITY_KLUTZ)
if (HOLD_EFFECT_CHOICE(holdEffect) && IsBattlerItemEnabled(battler))
{
if (GetMoveCategory(AI_DATA->lastUsedMove[battler]) == DAMAGE_CATEGORY_STATUS && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED)))
return SetSwitchinAndSwitch(battler, PARTY_SIZE);
@ -1379,10 +1379,10 @@ bool32 IsMonGrounded(u16 heldItemEffect, u32 ability, u8 type1, u8 type2)
{
// List that makes mon not grounded
if (type1 == TYPE_FLYING || type2 == TYPE_FLYING || ability == ABILITY_LEVITATE
|| (heldItemEffect == HOLD_EFFECT_AIR_BALLOON && ability != ABILITY_KLUTZ))
|| (heldItemEffect == HOLD_EFFECT_AIR_BALLOON && !(ability == ABILITY_KLUTZ || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM))))
{
// List that overrides being off the ground
if ((heldItemEffect == HOLD_EFFECT_IRON_BALL && ability != ABILITY_KLUTZ) || (gFieldStatuses & STATUS_FIELD_GRAVITY) || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM))
if ((heldItemEffect == HOLD_EFFECT_IRON_BALL && !(ability == ABILITY_KLUTZ || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM))) || (gFieldStatuses & STATUS_FIELD_GRAVITY) || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM))
return TRUE;
else
return FALSE;

View File

@ -423,15 +423,6 @@ static inline s32 DmgRoll(s32 dmg)
bool32 IsDamageMoveUnusable(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveType)
{
struct AiLogicData *aiData = AI_DATA;
u32 battlerDefAbility;
if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move))
battlerDefAbility = ABILITY_NONE;
else
battlerDefAbility = aiData->abilities[battlerDef];
if (battlerDef == BATTLE_PARTNER(battlerAtk))
battlerDefAbility = aiData->abilities[battlerDef];
if (gBattleStruct->battlerState[battlerDef].commandingDondozo)
return TRUE;
@ -470,7 +461,7 @@ bool32 IsDamageMoveUnusable(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveTy
return TRUE;
break;
case EFFECT_POLTERGEIST:
if (AI_DATA->items[battlerDef] == ITEM_NONE || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || battlerDefAbility == ABILITY_KLUTZ)
if (AI_DATA->items[battlerDef] == ITEM_NONE || !IsBattlerItemEnabled(battlerDef))
return TRUE;
break;
case EFFECT_FIRST_TURN_ONLY:
@ -4296,3 +4287,16 @@ bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef)
}
return FALSE;
}
bool32 IsBattlerItemEnabled(u32 battler)
{
if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_NEGATE_UNAWARE)
return TRUE;
if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)
return FALSE;
if (gStatuses3[battler] & STATUS3_EMBARGO)
return FALSE;
if (gBattleMons[battler].ability == ABILITY_KLUTZ && !(gStatuses3[battler] & STATUS3_GASTRO_ACID))
return FALSE;
return TRUE;
}

View File

@ -560,7 +560,7 @@ static void OpponentHandleChooseMove(u32 battler)
default:
{
u16 chosenMove = moveInfo->moves[chosenMoveId];
if (GetBattlerMoveTargetType(battler, chosenMove) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER))
if (GetBattlerMoveTargetType(battler, chosenMove) & MOVE_TARGET_USER)
gBattlerTarget = battler;
if (GetBattlerMoveTargetType(battler, chosenMove) & MOVE_TARGET_BOTH)
{
@ -595,7 +595,7 @@ static void OpponentHandleChooseMove(u32 battler)
move = moveInfo->moves[chosenMoveId];
} while (move == MOVE_NONE);
if (GetBattlerMoveTargetType(battler, move) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER))
if (GetBattlerMoveTargetType(battler, move) & MOVE_TARGET_USER)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (battler << 8));
else if (IsDoubleBattle())
{

View File

@ -495,8 +495,6 @@ void HandleInputChooseTarget(u32 battler)
case B_POSITION_PLAYER_RIGHT:
if (battler != gMultiUsePlayerCursor)
i++;
else if (moveTarget & MOVE_TARGET_USER_OR_SELECTED)
i++;
break;
case B_POSITION_OPPONENT_LEFT:
case B_POSITION_OPPONENT_RIGHT:
@ -505,7 +503,8 @@ void HandleInputChooseTarget(u32 battler)
}
if (gAbsentBattlerFlags & (1u << gMultiUsePlayerCursor)
|| !CanTargetBattler(battler, gMultiUsePlayerCursor, move))
|| !CanTargetBattler(battler, gMultiUsePlayerCursor, move)
|| (moveTarget & MOVE_TARGET_OPPONENT && GetBattlerSide(gMultiUsePlayerCursor) == B_SIDE_PLAYER))
i = 0;
} while (i == 0);
}
@ -545,8 +544,6 @@ void HandleInputChooseTarget(u32 battler)
case B_POSITION_PLAYER_RIGHT:
if (battler != gMultiUsePlayerCursor)
i++;
else if (moveTarget & MOVE_TARGET_USER_OR_SELECTED)
i++;
break;
case B_POSITION_OPPONENT_LEFT:
case B_POSITION_OPPONENT_RIGHT:
@ -555,7 +552,8 @@ void HandleInputChooseTarget(u32 battler)
}
if (gAbsentBattlerFlags & (1u << gMultiUsePlayerCursor)
|| !CanTargetBattler(battler, gMultiUsePlayerCursor, move))
|| !CanTargetBattler(battler, gMultiUsePlayerCursor, move)
|| (moveTarget & MOVE_TARGET_OPPONENT && GetBattlerSide(gMultiUsePlayerCursor) == B_SIDE_PLAYER))
i = 0;
} while (i == 0);
}
@ -690,12 +688,7 @@ void HandleInputChooseMove(u32 battler)
else
gMultiUsePlayerCursor = GetOpposingSideBattler(battler);
if (!gBattleResources->bufferA[battler][1]) // not a double battle
{
if (moveTarget & MOVE_TARGET_USER_OR_SELECTED && !gBattleResources->bufferA[battler][2])
canSelectTarget = 1;
}
else // double battle
if (gBattleResources->bufferA[battler][1]) // a double battle
{
if (!(moveTarget & (MOVE_TARGET_RANDOM | MOVE_TARGET_BOTH | MOVE_TARGET_DEPENDS | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_USER | MOVE_TARGET_ALLY)))
canSelectTarget = 1; // either selected or user
@ -706,7 +699,7 @@ void HandleInputChooseMove(u32 battler)
{
canSelectTarget = 0;
}
else if (!(moveTarget & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) && CountAliveMonsInBattle(BATTLE_ALIVE_EXCEPT_BATTLER, battler) <= 1)
else if (!(moveTarget & MOVE_TARGET_USER) && CountAliveMonsInBattle(BATTLE_ALIVE_EXCEPT_BATTLER, battler) <= 1)
{
gMultiUsePlayerCursor = GetDefaultMoveTarget(battler);
canSelectTarget = 0;
@ -749,7 +742,7 @@ void HandleInputChooseMove(u32 battler)
case 1:
gBattlerControllerFuncs[battler] = HandleInputChooseTarget;
if (moveTarget & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
if (moveTarget & MOVE_TARGET_USER)
gMultiUsePlayerCursor = battler;
else if (gAbsentBattlerFlags & (1u << GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT)))
gMultiUsePlayerCursor = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);

View File

@ -353,7 +353,7 @@ static void PlayerPartnerHandleChooseMove(u32 battler)
gBattlerTarget = gBattleStruct->aiChosenTarget[battler];
u32 moveTarget = GetBattlerMoveTargetType(battler, moveInfo->moves[chosenMoveId]);
if (moveTarget & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
if (moveTarget & MOVE_TARGET_USER)
gBattlerTarget = battler;
else if (moveTarget & MOVE_TARGET_BOTH)
{

View File

@ -326,7 +326,7 @@ static u8 GetBattlePalaceMoveGroup(u8 battler, u16 move)
switch (GetBattlerMoveTargetType(battler, move))
{
case MOVE_TARGET_SELECTED:
case MOVE_TARGET_USER_OR_SELECTED:
case MOVE_TARGET_OPPONENT:
case MOVE_TARGET_RANDOM:
case MOVE_TARGET_BOTH:
case MOVE_TARGET_FOES_AND_ALLY:

View File

@ -371,10 +371,10 @@ void UpdateIndicatorLevelData(u32 healthboxId, u32 level)
static const s8 sIndicatorPositions[][2] =
{
[B_POSITION_PLAYER_LEFT] = {53, -9},
[B_POSITION_OPPONENT_LEFT] = {44, -9},
[B_POSITION_PLAYER_RIGHT] = {52, -9},
[B_POSITION_OPPONENT_RIGHT] = {44, -9},
[B_POSITION_PLAYER_LEFT] = {49, -9},
[B_POSITION_OPPONENT_LEFT] = {40, -9},
[B_POSITION_PLAYER_RIGHT] = {48, -9},
[B_POSITION_OPPONENT_RIGHT] = {40, -9},
};
void CreateIndicatorSprite(u32 battler)

View File

@ -3205,6 +3205,9 @@ void SwitchInClearSetData(u32 battler)
gCurrentMove = MOVE_NONE;
gBattleStruct->arenaTurnCounter = 0xFF;
// Restore struct member so replacement does not miss timing
gSpecialStatuses[battler].switchInAbilityDone = FALSE;
// Reset damage to prevent things like red card activating if the switched-in mon is holding it
gSpecialStatuses[battler].physicalDmg = 0;
gSpecialStatuses[battler].specialDmg = 0;

View File

@ -1197,6 +1197,9 @@ static void Cmd_attackcanceler(void)
if (AbilityBattleEffects(ABILITYEFFECT_MOVES_BLOCK, gBattlerTarget, 0, 0, 0))
return;
if (gMovesInfo[gCurrentMove].effect == EFFECT_PARALYZE && AbilityBattleEffects(ABILITYEFFECT_ABSORBING, gBattlerTarget, 0, 0, gCurrentMove))
return;
if (!gBattleMons[gBattlerAttacker].pp[gCurrMovePos] && gCurrentMove != MOVE_STRUGGLE
&& !(gHitMarker & (HITMARKER_ALLOW_NO_PP | HITMARKER_NO_ATTACKSTRING | HITMARKER_NO_PPDEDUCT))
&& !(gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS))
@ -3884,7 +3887,7 @@ void SetMoveEffect(bool32 primary, bool32 certain)
gProtectStructs[gBattlerTarget].banefulBunkered = FALSE;
gProtectStructs[gBattlerTarget].obstructed = FALSE;
gProtectStructs[gBattlerTarget].silkTrapped = FALSE;
gProtectStructs[gBattlerAttacker].burningBulwarked = FALSE;
gProtectStructs[gBattlerTarget].burningBulwarked = FALSE;
BattleScriptPush(gBattlescriptCurrInstr + 1);
if (gCurrentMove == MOVE_HYPERSPACE_FURY)
gBattlescriptCurrInstr = BattleScript_HyperspaceFuryRemoveProtect;
@ -6360,6 +6363,7 @@ static void Cmd_moveend(void)
else if (moveRecoil > 0
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)
&& IsBattlerAlive(gBattlerAttacker)
&& IsBattlerTurnDamaged(gBattlerTarget)
&& gBattleScripting.savedDmg != 0) // Some checks may be redundant alongside this one
{
gBattleStruct->moveDamage[gBattlerAttacker] = max(1, gBattleScripting.savedDmg * max(1, moveRecoil) / 100);
@ -11605,7 +11609,8 @@ static void TryResetProtectUseCounter(u32 battler)
u32 lastEffect = GetMoveEffect(lastMove);
if (lastMove == MOVE_UNAVAILABLE
|| (!gBattleMoveEffects[lastEffect].usesProtectCounter
&& (B_ALLY_SWITCH_FAIL_CHANCE >= GEN_9 && lastEffect != EFFECT_ALLY_SWITCH)))
&& ((B_ALLY_SWITCH_FAIL_CHANCE >= GEN_9 && lastEffect != EFFECT_ALLY_SWITCH)
|| B_ALLY_SWITCH_FAIL_CHANCE < GEN_9)))
gDisableStructs[battler].protectUses = 0;
}

View File

@ -3887,6 +3887,21 @@ bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2
playerId = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
party = gEnemyParty;
// Edge case: If both opposing Pokemon were knocked out on the same turn,
// make sure opponent only sents out the final Pokemon once.
if (battler == playerId
&& (gHitMarker & HITMARKER_FAINTED(flankId))
&& (gHitMarker & HITMARKER_FAINTED(playerId)))
{
u8 count = 0;
for (i = 0; i < PARTY_SIZE; i++)
if (IsValidForBattle(&party[i]))
count++;
if (count < 2)
return TRUE;
}
if (partyIdBattlerOn1 == PARTY_SIZE)
partyIdBattlerOn1 = gBattlerPartyIndexes[flankId];
if (partyIdBattlerOn2 == PARTY_SIZE)
@ -6340,6 +6355,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
}
break;
case ABILITYEFFECT_IMMUNITY:
gBattleStruct->bypassMoldBreakerChecks = TRUE;
for (battler = 0; battler < gBattlersCount; battler++)
{
switch (GetBattlerAbility(battler))
@ -6431,6 +6447,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
return effect;
}
}
gBattleStruct->bypassMoldBreakerChecks = FALSE;
break;
case ABILITYEFFECT_SYNCHRONIZE:
if (gLastUsedAbility == ABILITY_SYNCHRONIZE && (gHitMarker & HITMARKER_SYNCHRONISE_EFFECT))
@ -6670,7 +6687,9 @@ u32 GetBattlerAbility(u32 battler)
&& gBattleMons[battler].ability == ABILITY_COMATOSE)
return ABILITY_NONE;
if (noAbilityShield && CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability))
if (!gBattleStruct->bypassMoldBreakerChecks
&& noAbilityShield
&& CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability))
return ABILITY_NONE;
return gBattleMons[battler].ability;
@ -6684,7 +6703,9 @@ u32 GetBattlerAbility(u32 battler)
&& noAbilityShield)
return ABILITY_NONE;
if (noAbilityShield && CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability))
if (!gBattleStruct->bypassMoldBreakerChecks
&& noAbilityShield
&& CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability))
return ABILITY_NONE;
return gBattleMons[battler].ability;
@ -8517,6 +8538,7 @@ u32 GetBattleMoveTarget(u16 move, u8 setTarget)
switch (moveTarget)
{
case MOVE_TARGET_SELECTED:
case MOVE_TARGET_OPPONENT:
side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
if (IsAffectedByFollowMe(gBattlerAttacker, side, move))
{
@ -8576,7 +8598,6 @@ u32 GetBattleMoveTarget(u16 move, u8 setTarget)
else
targetBattler = GetOpposingSideBattler(gBattlerAttacker);
break;
case MOVE_TARGET_USER_OR_SELECTED:
case MOVE_TARGET_USER:
default:
targetBattler = gBattlerAttacker;
@ -8715,7 +8736,7 @@ u32 GetBattlerHoldEffectInternal(u32 battler, bool32 checkNegating, bool32 check
return HOLD_EFFECT_NONE;
if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)
return HOLD_EFFECT_NONE;
if (checkAbility && GetBattlerAbility(battler) == ABILITY_KLUTZ)
if (checkAbility && GetBattlerAbility(battler) == ABILITY_KLUTZ && !(gStatuses3[battler] & STATUS3_GASTRO_ACID))
return HOLD_EFFECT_NONE;
}
@ -8927,7 +8948,7 @@ u32 GetMoveTargetCount(struct DamageCalculationData *damageCalcData)
case MOVE_TARGET_DEPENDS:
case MOVE_TARGET_SELECTED:
case MOVE_TARGET_RANDOM:
case MOVE_TARGET_USER_OR_SELECTED:
case MOVE_TARGET_OPPONENT:
return IsBattlerAlive(battlerDef);
case MOVE_TARGET_USER:
return IsBattlerAlive(battlerAtk);
@ -11533,8 +11554,12 @@ bool32 CanStealItem(u32 battlerStealing, u32 battlerItem, u16 item)
return FALSE;
}
// It's supposed to pop before trying to steal but this also works
if (ItemId_GetHoldEffect(item) == HOLD_EFFECT_AIR_BALLOON)
return FALSE;
if (!CanBattlerGetOrLoseItem(battlerItem, item) // Battler with item cannot have it stolen
||!CanBattlerGetOrLoseItem(battlerStealing, item)) // Stealer cannot take the item
|| !CanBattlerGetOrLoseItem(battlerStealing, item)) // Stealer cannot take the item
return FALSE;
return TRUE;

View File

@ -5485,10 +5485,10 @@ static void SetMoveTargetPosition(u16 move)
{
switch (GetBattlerMoveTargetType(gBattlerAttacker, move))
{
case MOVE_TARGET_USER_OR_SELECTED:
case MOVE_TARGET_USER:
gBattlerTarget = B_POSITION_PLAYER_RIGHT;
break;
case MOVE_TARGET_OPPONENT:
case MOVE_TARGET_SELECTED:
case MOVE_TARGET_RANDOM:
case MOVE_TARGET_BOTH:

View File

@ -135,8 +135,8 @@ static const struct SpritePalette sSpritePalette_TeraIndicator = {sTeraIndicator
static const struct OamData sOamData_GimmickIndicator =
{
.shape = SPRITE_SHAPE(16x16),
.size = SPRITE_SIZE(16x16),
.shape = SPRITE_SHAPE(8x16),
.size = SPRITE_SIZE(8x16),
.priority = 1,
};

View File

@ -9892,7 +9892,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.type = TYPE_NORMAL,
.accuracy = 0,
.pp = 20,
.target = MOVE_TARGET_SELECTED,
.target = MOVE_TARGET_OPPONENT,
.priority = 0,
.category = DAMAGE_CATEGORY_STATUS,
.zMove = { .effect = Z_EFFECT_SPD_UP_2 },

View File

@ -1186,6 +1186,15 @@ void ItemUseInBattle_PartyMenuChooseMove(u8 taskId)
ItemUseInBattle_ShowPartyMenu(taskId);
}
static bool32 SelectedMonHasStatus2(u16 itemId)
{
if (gPartyMenu.slotId == 0)
return gBattleMons[0].status2 & GetItemStatus2Mask(itemId);
else if (gBattleTypeFlags & (BATTLE_TYPE_DOUBLE | BATTLE_TYPE_MULTI) && gPartyMenu.slotId == 1)
return gBattleMons[2].status2 & GetItemStatus2Mask(itemId);
return FALSE;
}
// Returns whether an item can be used in battle and sets the fail text.
bool32 CannotUseItemsInBattle(u16 itemId, struct Pokemon *mon)
{
@ -1258,13 +1267,13 @@ bool32 CannotUseItemsInBattle(u16 itemId, struct Pokemon *mon)
break;
case EFFECT_ITEM_CURE_STATUS:
if (!((GetMonData(mon, MON_DATA_STATUS) & GetItemStatus1Mask(itemId))
|| (gPartyMenu.slotId == 0 && gBattleMons[gBattlerInMenuId].status2 & GetItemStatus2Mask(itemId))))
|| SelectedMonHasStatus2(itemId)))
cannotUse = TRUE;
break;
case EFFECT_ITEM_HEAL_AND_CURE_STATUS:
if ((hp == 0 || hp == GetMonData(mon, MON_DATA_MAX_HP))
&& !((GetMonData(mon, MON_DATA_STATUS) & GetItemStatus1Mask(itemId))
|| (gPartyMenu.slotId == 0 && gBattleMons[gBattlerInMenuId].status2 & GetItemStatus2Mask(itemId))))
|| SelectedMonHasStatus2(itemId)))
cannotUse = TRUE;
break;
case EFFECT_ITEM_REVIVE:

View File

@ -375,3 +375,18 @@ DOUBLE_BATTLE_TEST("Intimidate will correctly decrease the attack of the second
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
}
}
SINGLE_BATTLE_TEST("Intimdate does not lose timing after mega evolution and switch out by a hit escape move")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_MANECTRIC) { Item(ITEM_MANECTITE); }
OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); }
} WHEN {
TURN { MOVE(opponent, MOVE_U_TURN, gimmick: GIMMICK_MEGA); SEND_OUT(opponent, 1); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_INTIMIDATE);
ABILITY_POPUP(opponent, ABILITY_INTIMIDATE);
}
}

View File

@ -0,0 +1,14 @@
#include "global.h"
#include "test/battle.h"
SINGLE_BATTLE_TEST("Motor Drive absorbs status moves")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_EMOLGA) { Ability(ABILITY_MOTOR_DRIVE); }
} WHEN {
TURN { MOVE(player, MOVE_THUNDER_WAVE); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_MOTOR_DRIVE);
}
}

View File

@ -58,7 +58,6 @@ SINGLE_BATTLE_TEST("Own Tempo prevents confusion from moves by the user")
SINGLE_BATTLE_TEST("Mold Breaker ignores Own Tempo")
{
KNOWN_FAILING; // Ideally the func CanBeConfused should be split into AttackerCanBeConfused and TargetCanBeConfused or we do it in the same func but have a check for when battlerAtk == battlerDef
GIVEN {
ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE);
PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); }
@ -66,16 +65,13 @@ SINGLE_BATTLE_TEST("Mold Breaker ignores Own Tempo")
} WHEN {
TURN { MOVE(player, MOVE_CONFUSE_RAY); }
} SCENE {
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO);
MESSAGE("The opposing Slowpoke's Own Tempo prevents confusion!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player);
NOT MESSAGE("The opposing Slowpoke's Own Tempo prevents confusion!");
}
}
SINGLE_BATTLE_TEST("Mold Breaker does not prevent Own Tempo from curing confusion right after")
{
KNOWN_FAILING;
GIVEN {
ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE);
PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); };

View File

@ -33,7 +33,6 @@ DOUBLE_BATTLE_TEST("Pastel Veil prevents Poison Sting poison on partner")
SINGLE_BATTLE_TEST("Pastel Veil immediately cures Mold Breaker poison")
{
KNOWN_FAILING;
GIVEN {
ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_TOXIC);
PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); }

View File

@ -105,7 +105,6 @@ SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen with Magician")
SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen with Thief or Covet")
{
u32 move;
KNOWN_FAILING;
PARAMETRIZE { move = MOVE_THIEF; }
PARAMETRIZE { move = MOVE_COVET; }
GIVEN {

View File

@ -54,7 +54,7 @@ SINGLE_BATTLE_TEST("Jaboca Berry tirggers before Bug Bite can steal it")
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
HP_BAR(player);
MESSAGE("Wyanut was hurt by the opposing Wobbuffet's Jaboca Berry!");
NOT MESSAGE("Wynaut stole and ate the opposing its target's Jaboca Berry!");
MESSAGE("Wynaut was hurt by the opposing Wobbuffet's Jaboca Berry!");
NOT MESSAGE("Wynaut stole and ate the opposing Wobbuffet's Jaboca Berry!");
}
}

View File

@ -58,7 +58,6 @@ DOUBLE_BATTLE_TEST("Coaching bypasses Crafty Shield")
DOUBLE_BATTLE_TEST("Coaching fails if all allies are is semi-invulnerable")
{
KNOWN_FAILING; // Coaching succeeds
GIVEN {
ASSUME(GetMoveEffect(MOVE_FLY) == EFFECT_SEMI_INVULNERABLE);
PLAYER(SPECIES_WOBBUFFET);

View File

@ -43,7 +43,6 @@ SINGLE_BATTLE_TEST("Razor Wind needs a charging turn")
SINGLE_BATTLE_TEST("Razor Wind doesn't need to charge with Power Herb")
{
KNOWN_FAILING;
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_POWER_HERB); }
OPPONENT(SPECIES_WOBBUFFET);
@ -63,7 +62,6 @@ SINGLE_BATTLE_TEST("Razor Wind doesn't need to charge with Power Herb")
MESSAGE("Wobbuffet became fully charged due to its Power Herb!");
if (B_UPDATED_MOVE_DATA < GEN_5)
MESSAGE("Wobbuffet used Razor Wind!");
// For some reason, this breaks with and only with Razor Wind...
ANIMATION(ANIM_TYPE_MOVE, MOVE_RAZOR_WIND, player);
HP_BAR(opponent);
}

View File

@ -83,3 +83,22 @@ SINGLE_BATTLE_TEST("Flare Blitz deals 33% of recoil damage to the user and can b
EXPECT_MUL_EQ(directDamage, UQ_4_12(0.33), recoilDamage);
}
}
SINGLE_BATTLE_TEST("Flare Blitz is absorbed by Flash Fire and no recoil damage is dealt")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_FLARE_BLITZ].recoil > 0);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_VULPIX) { Ability(ABILITY_FLASH_FIRE); };
} WHEN {
TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_FLARE_BLITZ); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLARE_BLITZ, player);
HP_BAR(opponent);
HP_BAR(player);
}
}
}

View File

@ -2043,7 +2043,7 @@ s32 MoveGetTarget(s32 battlerId, u32 moveId, struct MoveContext *ctx, u32 source
{
target = BATTLE_OPPOSITE(battlerId);
}
else if (move->target == MOVE_TARGET_SELECTED)
else if (move->target == MOVE_TARGET_SELECTED || move->target == MOVE_TARGET_OPPONENT)
{
// In AI Doubles not specified target allows any target for EXPECT_MOVE.
if (GetBattleTest()->type != BATTLE_TEST_AI_DOUBLES)