diff --git a/INSTALL.md b/INSTALL.md index 072d43a858..47f00cf78f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -32,6 +32,7 @@ Distributions with instructions: - [Debian](docs/install/linux/DEBIAN.md) - [Arch Linux](docs/install/linux/ARCH_LINUX.md) - [NixOS](docs/install/linux/NIXOS.md) +- [Fedora](docs/install/linux/FEDORA.md) Other distributions have to infer what to do from [general instructions](docs/install/linux/OTHERS.md). diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index dea3729c70..7eb3b9d064 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1733,10 +1733,6 @@ callnative BS_WaitFanfare .endm - .macro setbeakblast - callnative BS_SetBeakBlast - .endm - .macro cantarshotwork failInstr:req callnative BS_CanTarShotWork .4byte \failInstr diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 0a6f4a624b..8754ed053c 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -139,7 +139,11 @@ BattleScript_EffectShedTail:: waitmessage B_WAIT_TIME_LONG moveendto MOVEEND_ATTACKER_VISIBLE moveendfrom MOVEEND_TARGET_VISIBLE - goto BattleScript_MoveSwitchOpenPartyScreen + call BattleScript_MoveSwitchOpenPartyScreenReturnWithNoAnim + switchinanim BS_ATTACKER, FALSE, TRUE + waitstate + switchineffects BS_ATTACKER + end BattleScript_EffectPsychicNoise:: printstring STRINGID_PKMNPREVENTEDFROMHEALING @@ -282,6 +286,14 @@ BattleScript_MoveSwitch: printstring STRINGID_PKMNWENTBACK waitmessage B_WAIT_TIME_SHORT BattleScript_MoveSwitchOpenPartyScreen:: + call BattleScript_MoveSwitchOpenPartyScreenReturnWithNoAnim + switchinanim BS_ATTACKER, FALSE, FALSE + waitstate + switchineffects BS_ATTACKER +BattleScript_MoveSwitchEnd: + end + +BattleScript_MoveSwitchOpenPartyScreenReturnWithNoAnim: openpartyscreen BS_ATTACKER, BattleScript_MoveSwitchEnd waitstate returntoball BS_ATTACKER, FALSE @@ -294,11 +306,7 @@ BattleScript_MoveSwitchOpenPartyScreen:: printstring STRINGID_EMPTYSTRING3 waitmessage 1 printstring STRINGID_SWITCHINMON - switchinanim BS_ATTACKER, FALSE, TRUE - waitstate - switchineffects BS_ATTACKER -BattleScript_MoveSwitchEnd: - end + return BattleScript_EffectPledge:: attackcanceler @@ -612,7 +620,6 @@ BattleScript_EffectCourtChange:: goto BattleScript_MoveEnd BattleScript_BeakBlastSetUp:: - setbeakblast flushtextbox playanimation BS_ATTACKER, B_ANIM_BEAK_BLAST_SETUP, NULL printstring STRINGID_HEATUPBEAK diff --git a/docs/install/linux/FEDORA.md b/docs/install/linux/FEDORA.md new file mode 100644 index 0000000000..0dba9c1e78 --- /dev/null +++ b/docs/install/linux/FEDORA.md @@ -0,0 +1,6 @@ +# Fedora instructions +## Installing dependencies +Open a terminal and run the following command from the command line: +```console +sudo dnf install gcc g++ arm-none-eabi-binutils-cs arm-none-eabi-gcc-cs arm-none-eabi-newlib git libpng-devel python3 +``` diff --git a/include/battle.h b/include/battle.h index cb56ae930e..3cd40cb16b 100755 --- a/include/battle.h +++ b/include/battle.h @@ -161,18 +161,17 @@ struct ProtectStruct u32 disableEjectPack:1; u32 pranksterElevated:1; u32 quickDraw:1; - u32 beakBlastCharge:1; u32 quash:1; u32 shellTrap:1; u32 eatMirrorHerb:1; u32 activateOpportunist:2; // 2 - to copy stats. 1 - stats copied (do not repeat). 0 - no stats to copy u16 usedAllySwitch:1; + u16 lashOutAffected:1; // End of 32-bit bitfield u16 helpingHand:3; - u16 lashOutAffected:1; u16 assuranceDoubled:1; u16 myceliumMight:1; - u16 padding:10; + u16 padding:11; // End of 16-bit bitfield u16 physicalDmg; u16 specialDmg; @@ -697,7 +696,7 @@ struct BattleStruct u8 fickleBeamBoosted:1; u8 poisonPuppeteerConfusion:1; u8 toxicChainPriority:1; // If Toxic Chain will trigger on target, all other non volatiles will be blocked - u8 padding1:1; + u8 moldBreakerActive:1; u16 startingStatusTimer; struct BattleTvMovePoints tvMovePoints; struct BattleTv tv; diff --git a/include/battle_util.h b/include/battle_util.h index 1406098222..36ca129206 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -112,7 +112,7 @@ enum MoveSuccessOrder CANCELER_TAUNTED, CANCELER_IMPRISONED, CANCELER_CONFUSED, - CANCELER_PARALYSED, + CANCELER_PARALYZED, CANCELER_INFATUATION, CANCELER_BIDE, CANCELER_Z_MOVES, diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index c04b22d417..b1e61e3493 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -2247,7 +2247,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, // Check if current mon can revenge kill in some capacity // If AI mon can one shot - if (damageDealt > playerMonHP) + if (damageDealt >= playerMonHP) { if (canSwitchinWin1v1) { @@ -2259,7 +2259,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, } // If AI mon can two shot - if (damageDealt > playerMonHP / 2) + if (damageDealt >= (playerMonHP / 2 + playerMonHP % 2)) // Modulo to handle odd numbers in non-decimal division { if (canSwitchinWin1v1) { diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 4dde40e29a..aba9d7d837 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -198,46 +198,36 @@ static void CompleteOnBattlerSpritePosX_0(u32 battler) static u16 GetPrevBall(u16 ballId) { - u16 ballPrev; - s32 i, j; - CompactItemsInBagPocket(POCKET_POKE_BALLS); - for (i = 0; i < gBagPockets[POCKET_POKE_BALLS].capacity; i++) + s32 i; + s32 index = ItemIdToBallId(ballId); + u32 newBall = 0; + for (i = 0; i < POKEBALL_COUNT; i++) { - if (ballId == GetBagItemId(POCKET_POKE_BALLS, i)) - { - if (i <= 0) - { - for (j = gBagPockets[POCKET_POKE_BALLS].capacity - 1; j >= 0; j--) - { - ballPrev = GetBagItemId(POCKET_POKE_BALLS, j); - if (ballPrev != ITEM_NONE) - return ballPrev; - } - } - i--; - break; - } + index--; + if (index == -1) + index = POKEBALL_COUNT - 1; + newBall = gBallItemIds[index]; + if (CheckBagHasItem(newBall, 1)) + return newBall; } - return GetBagItemId(POCKET_POKE_BALLS, i); + return ballId; } static u32 GetNextBall(u32 ballId) { - u32 ballNext = ITEM_NONE; s32 i; - CompactItemsInBagPocket(POCKET_POKE_BALLS); - for (i = 1; i < gBagPockets[POCKET_POKE_BALLS].capacity; i++) + s32 index = ItemIdToBallId(ballId); + u32 newBall = 0; + for (i = 0; i < POKEBALL_COUNT; i++) { - if (ballId == GetBagItemId(POCKET_POKE_BALLS, i-1)) - { - ballNext = GetBagItemId(POCKET_POKE_BALLS, i); - break; - } + index++; + if (index == POKEBALL_COUNT) + index = 0; + newBall = gBallItemIds[index]; + if (CheckBagHasItem(newBall, 1)) + return newBall; } - if (ballNext == ITEM_NONE) - return GetBagItemId(POCKET_POKE_BALLS, 0); // Zeroth slot - else - return ballNext; + return ballId; } static void HandleInputChooseAction(u32 battler) diff --git a/src/battle_main.c b/src/battle_main.c index b23a959e33..a0d578bf33 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3263,7 +3263,6 @@ void SwitchInClearSetData(u32 battler, struct Volatiles *volatilesCopy) } gBattleStruct->choicedMove[battler] = MOVE_NONE; - gCurrentMove = MOVE_NONE; gBattleStruct->eventState.arenaTurn = 0xFF; // Restore struct member so replacement does not miss timing diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 07cb240c7e..99847ba3f3 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1106,6 +1106,15 @@ bool32 EmergencyExitCanBeTriggered(u32 battler) return FALSE; } +static inline bool32 IsBattlerUsingBeakBlast(u32 battler) +{ + if (gChosenActionByBattler[battler] != B_ACTION_USE_MOVE) + return FALSE; + if (GetMoveEffect(gChosenMoveByBattler[battler]) != EFFECT_BEAK_BLAST) + return FALSE; + return !HasBattlerActedThisTurn(battler); +} + static void Cmd_attackcanceler(void) { CMD_ARGS(); @@ -1315,7 +1324,8 @@ static void Cmd_attackcanceler(void) gBattleCommunication[MISS_TYPE] = B_MSG_PROTECTED; gBattlescriptCurrInstr = cmd->nextInstr; } - else if (gProtectStructs[gBattlerTarget].beakBlastCharge && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove)) + else if (IsBattlerUsingBeakBlast(gBattlerTarget) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = TRUE; gBattlescriptCurrInstr = cmd->nextInstr; @@ -6116,7 +6126,7 @@ static void Cmd_moveend(void) } // Not strictly a protect effect, but works the same way - if (gProtectStructs[gBattlerTarget].beakBlastCharge + if (IsBattlerUsingBeakBlast(gBattlerTarget) && CanBeBurned(gBattlerAttacker, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) { @@ -6404,8 +6414,9 @@ static void Cmd_moveend(void) gBattleStruct->lastMoveTarget[gBattlerAttacker] = gBattlerTarget; } enum BattleMoveEffects originalEffect = GetMoveEffect(originallyUsedMove); - if (!(gAbsentBattlerFlags & (1u << gBattlerAttacker)) - && originalEffect != EFFECT_BATON_PASS && originalEffect != EFFECT_HEALING_WISH) + if (IsBattlerAlive(gBattlerAttacker) + && originalEffect != EFFECT_BATON_PASS + && originalEffect != EFFECT_HEALING_WISH) { if (gHitMarker & HITMARKER_OBEYS) { @@ -6979,25 +6990,17 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_THIRD_MOVE_BLOCK: - if (gChosenMove == MOVE_UNAVAILABLE) - { - gBattleScripting.moveendState++; - break; - } - - // Special case for Steel Roller since it has to check the chosen move - if (GetMoveEffect(gChosenMove) == EFFECT_STEEL_ROLLER && IsBattlerTurnDamaged(gBattlerTarget)) - { - BattleScriptCall(BattleScript_RemoveTerrain); - effect = TRUE; - gBattleScripting.moveendState++; - break; - } - switch (moveEffect) { + case EFFECT_STEEL_ROLLER: + if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && IsBattlerTurnDamaged(gBattlerTarget)) + { + BattleScriptCall(BattleScript_RemoveTerrain); + effect = TRUE; + } case EFFECT_ICE_SPINNER: if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY + && gLastPrintedMoves[gBattlerAttacker] == gCurrentMove && IsBattlerAlive(gBattlerAttacker) && IsBattlerTurnDamaged(gBattlerTarget)) { @@ -7070,6 +7073,7 @@ static void Cmd_moveend(void) gProtectStructs[gBattlerAttacker].shellTrap = FALSE; gBattleStruct->battlerState[gBattlerAttacker].ateBoost = FALSE; gBattleScripting.moveEffect = MOVE_EFFECT_NONE; + gBattleStruct->moldBreakerActive = FALSE; gBattleStruct->swapDamageCategory = FALSE; gBattleStruct->categoryOverride = FALSE; gBattleStruct->additionalEffectsCounter = 0; @@ -7113,11 +7117,7 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_DANCER: - if (gCurrentMove == MOVE_NONE) - originallyUsedMove = gChosenMove; // Fallback to chosen move in case attacker is switched out in the middle of an attack resolution (eg red card) - else - originallyUsedMove = gCurrentMove; - if (IsDanceMove(originallyUsedMove) && !gBattleStruct->snatchedMoveIsUsed) + if (IsDanceMove(gCurrentMove) && !gBattleStruct->snatchedMoveIsUsed) { u32 battler, nextDancer = 0; bool32 hasDancerTriggered = FALSE; @@ -7151,7 +7151,7 @@ static void Cmd_moveend(void) nextDancer = battler | 0x4; } } - if (nextDancer && AbilityBattleEffects(ABILITYEFFECT_MOVE_END_OTHER, nextDancer & 0x3, 0, 0, originallyUsedMove)) + if (nextDancer && AbilityBattleEffects(ABILITYEFFECT_MOVE_END_OTHER, nextDancer & 0x3, 0, 0, gCurrentMove)) effect = TRUE; } } @@ -15975,13 +15975,6 @@ void BS_WaitFanfare(void) gBattlescriptCurrInstr = cmd->nextInstr; } -void BS_SetBeakBlast(void) -{ - NATIVE_ARGS(); - gProtectStructs[gBattlerAttacker].beakBlastCharge = TRUE; - gBattlescriptCurrInstr = cmd->nextInstr; -} - void BS_RemoveTerrain(void) { NATIVE_ARGS(); diff --git a/src/battle_setup.c b/src/battle_setup.c index e52b31e692..1ad284d05e 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -1305,6 +1305,7 @@ static void CB2_EndTrainerBattle(void) { HandleBattleVariantEndParty(); + gIsDebugBattle = FALSE; if (FollowerNPCIsBattlePartner()) { RestorePartyAfterFollowerNPCBattle(); diff --git a/src/battle_tv.c b/src/battle_tv.c index a56862aa26..cecab5efb3 100644 --- a/src/battle_tv.c +++ b/src/battle_tv.c @@ -1265,6 +1265,7 @@ static void TrySetBattleSeminarShow(void) dmgByMove[gMoveSelectionCursor[gBattlerAttacker]] = gBattleStruct->moveDamage[gBattlerTarget]; // TODO: Not sure currMoveSaved = gCurrentMove; + u16 storedMoveResultFlags = gBattleStruct->moveResultFlags[gBattlerTarget]; for (i = 0; i < MAX_MON_MOVES; i++) { gCurrentMove = gBattleMons[gBattlerAttacker].moves[i]; @@ -1281,8 +1282,7 @@ static void TrySetBattleSeminarShow(void) ctx.updateFlags = FALSE; ctx.isSelfInflicted = FALSE; ctx.fixedBasePower = powerOverride; - gBattleStruct->moveDamage[gBattlerTarget] = CalculateMoveDamage(&ctx); - dmgByMove[i] = gBattleStruct->moveDamage[gBattlerTarget]; + dmgByMove[i] = CalculateMoveDamage(&ctx); if (dmgByMove[i] == 0 && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) dmgByMove[i] = 1; } @@ -1313,8 +1313,8 @@ static void TrySetBattleSeminarShow(void) } } - gBattleStruct->moveDamage[gBattlerTarget] = dmgByMove[gMoveSelectionCursor[gBattlerAttacker]]; gCurrentMove = currMoveSaved; + gBattleStruct->moveResultFlags[gBattlerTarget] = storedMoveResultFlags; } static bool8 ShouldCalculateDamage(u16 move, s32 *dmg, u16 *powerOverride) diff --git a/src/battle_util.c b/src/battle_util.c index 68e89fc93b..b5b10ebcd7 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -533,6 +533,9 @@ void HandleAction_UseMove(void) gCurrentMove = gChosenMove = GetMaxMove(gBattlerAttacker, gCurrentMove); } + if (IsMoldBreakerTypeAbility(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) || MoveIgnoresTargetAbility(gCurrentMove)) + gBattleStruct->moldBreakerActive = TRUE; + moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); if (!HandleMoveTargetRedirection()) @@ -958,12 +961,13 @@ void HandleAction_ActionFinished(void) gHitMarker &= ~(HITMARKER_OBEYS); ClearDamageCalcResults(); - gCurrentMove = 0; + gCurrentMove = MOVE_NONE; gBattleScripting.animTurn = 0; gBattleScripting.animTargetsHit = 0; gBattleStruct->dynamicMoveType = 0; gBattleStruct->bouncedMoveIsUsed = FALSE; gBattleStruct->snatchedMoveIsUsed = FALSE; + gBattleStruct->moldBreakerActive = FALSE; gBattleScripting.moveendState = 0; gBattleCommunication[3] = 0; gBattleCommunication[4] = 0; @@ -2297,7 +2301,7 @@ static enum MoveCanceler CancelerConfused(struct BattleContext *ctx) return MOVE_STEP_SUCCESS; } -static enum MoveCanceler CancelerParalysed(struct BattleContext *ctx) +static enum MoveCanceler CancelerParalyzed(struct BattleContext *ctx) { if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_PARALYSIS && !(B_MAGIC_GUARD == GEN_4 && IsAbilityAndRecord(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk], ABILITY_MAGIC_GUARD)) @@ -2996,7 +3000,7 @@ static enum MoveCanceler (*const sMoveSuccessOrderCancelers[])(struct BattleCont [CANCELER_TAUNTED] = CancelerTaunted, [CANCELER_IMPRISONED] = CancelerImprisoned, [CANCELER_CONFUSED] = CancelerConfused, - [CANCELER_PARALYSED] = CancelerParalysed, + [CANCELER_PARALYZED] = CancelerParalyzed, [CANCELER_INFATUATION] = CancelerInfatuation, [CANCELER_BIDE] = CancelerBide, [CANCELER_Z_MOVES] = CancelerZMoves, @@ -5680,17 +5684,11 @@ bool32 IsMoldBreakerTypeAbility(u32 battler, enum Ability ability) return FALSE; } -static inline bool32 CanBreakThroughAbility(u32 battlerAtk, u32 battlerDef, enum Ability ability, u32 hasAbilityShield, u32 ignoreMoldBreaker) +static inline bool32 CanBreakThroughAbility(u32 battlerAtk, u32 battlerDef, u32 hasAbilityShield, u32 ignoreMoldBreaker) { - if (hasAbilityShield || ignoreMoldBreaker) + if (hasAbilityShield || ignoreMoldBreaker || battlerDef == battlerAtk) return FALSE; - - return ((IsMoldBreakerTypeAbility(battlerAtk, ability) || MoveIgnoresTargetAbility(gCurrentMove)) - && battlerDef != battlerAtk - && gAbilitiesInfo[gBattleMons[battlerDef].ability].breakable - && gBattlerByTurnOrder[gCurrentTurnActionNumber] == battlerAtk - && gActionsByTurnOrder[gCurrentTurnActionNumber] == B_ACTION_USE_MOVE - && gCurrentTurnActionNumber < gBattlersCount); + return gBattleStruct->moldBreakerActive && gAbilitiesInfo[gBattleMons[battlerDef].ability].breakable; } u32 GetBattlerAbilityNoAbilityShield(u32 battler) @@ -5721,7 +5719,7 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS && gBattleMons[battler].ability == ABILITY_COMATOSE) return ABILITY_NONE; - if (CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability, hasAbilityShield, ignoreMoldBreaker)) + if (CanBreakThroughAbility(gBattlerAttacker, battler, hasAbilityShield, ignoreMoldBreaker)) return ABILITY_NONE; return gBattleMons[battler].ability; @@ -5735,7 +5733,7 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS && (gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS || gBattleMons[battler].volatiles.gastroAcid)) return ABILITY_NONE; - if (CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability, hasAbilityShield, ignoreMoldBreaker)) + if (CanBreakThroughAbility(gBattlerAttacker, battler, hasAbilityShield, ignoreMoldBreaker)) return ABILITY_NONE; return gBattleMons[battler].ability; diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index c8d6a3b177..e206246b27 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -20,6 +20,7 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = { .battleScript = BattleScript_EffectNonVolatileStatus, .battleTvScore = 0, // Handled within the battle TV functions + .encourageEncore = TRUE, }, [EFFECT_ABSORB] = diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 808734afd5..a4f0dc06bc 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -18680,8 +18680,8 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = { .name = COMPOUND_STRING("Lash Out"), .description = COMPOUND_STRING( - "If stats lowered during this\n" - "turn, power is doubled."), + "If user's stats were lowered\n" + "this turn, power is doubled."), .effect = EFFECT_LASH_OUT, .power = 75, .type = TYPE_DARK, diff --git a/src/field_effect_helpers.c b/src/field_effect_helpers.c index 3623437a4d..f5f62a2367 100755 --- a/src/field_effect_helpers.c +++ b/src/field_effect_helpers.c @@ -35,8 +35,8 @@ static void UpdateFeetInFlowingWaterFieldEffect(struct Sprite *); static void UpdateAshFieldEffect_Wait(struct Sprite *); static void UpdateAshFieldEffect_Show(struct Sprite *); static void UpdateAshFieldEffect_End(struct Sprite *); -static void SynchroniseSurfAnim(struct ObjectEvent *, struct Sprite *); -static void SynchroniseSurfPosition(struct ObjectEvent *, struct Sprite *); +static void SynchronizeSurfAnim(struct ObjectEvent *, struct Sprite *); +static void SynchronizeSurfPosition(struct ObjectEvent *, struct Sprite *); static void UpdateBobbingEffect(struct ObjectEvent *, struct Sprite *, struct Sprite *); static void SpriteCB_UnderwaterSurfBlob(struct Sprite *); static u32 ShowDisguiseFieldEffect(u8, u8, u8); @@ -1248,13 +1248,13 @@ void UpdateSurfBlobFieldEffect(struct Sprite *sprite) { struct ObjectEvent *playerObj = &gObjectEvents[sprite->sPlayerObjId]; struct Sprite *playerSprite = &gSprites[playerObj->spriteId]; - SynchroniseSurfAnim(playerObj, sprite); - SynchroniseSurfPosition(playerObj, sprite); + SynchronizeSurfAnim(playerObj, sprite); + SynchronizeSurfPosition(playerObj, sprite); UpdateBobbingEffect(playerObj, playerSprite, sprite); sprite->oam.priority = playerSprite->oam.priority; } -static void SynchroniseSurfAnim(struct ObjectEvent *playerObj, struct Sprite *sprite) +static void SynchronizeSurfAnim(struct ObjectEvent *playerObj, struct Sprite *sprite) { // Indexes into sAnimTable_SurfBlob u8 surfBlobDirectionAnims[] = { @@ -1273,7 +1273,7 @@ static void SynchroniseSurfAnim(struct ObjectEvent *playerObj, struct Sprite *sp StartSpriteAnimIfDifferent(sprite, surfBlobDirectionAnims[playerObj->movementDirection]); } -void SynchroniseSurfPosition(struct ObjectEvent *playerObj, struct Sprite *sprite) +void SynchronizeSurfPosition(struct ObjectEvent *playerObj, struct Sprite *sprite) { u8 i; s16 x = playerObj->currentCoords.x; diff --git a/src/list_menu.c b/src/list_menu.c index 6b8db8dafc..1a129e9f20 100644 --- a/src/list_menu.c +++ b/src/list_menu.c @@ -13,6 +13,10 @@ #include "sound.h" #include "constants/songs.h" +// GF cast Task data to ListMenu in many places, which effectively puts +// an upper bound on sizeof(struct ListMenu). +STATIC_ASSERT(sizeof(struct ListMenu) <= sizeof(((struct Task *)NULL)->data), ListMenuTooLargeForTaskData); + // Cursors after this point are created using a sprite with their own task. // This allows them to have idle animations. Cursors prior to this are simply printed text. #define CURSOR_OBJECT_START CURSOR_RED_OUTLINE diff --git a/src/overworld.c b/src/overworld.c index 8eb83d076e..5f16de1f2e 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -860,8 +860,8 @@ void LoadMapFromCameraTransition(u8 mapGroup, u8 mapNum) TryUpdateRandomTrainerRematches(mapGroup, mapNum); #endif //FREE_MATCH_CALL -if (I_VS_SEEKER_CHARGING != 0) - MapResetTrainerRematches(mapGroup, mapNum); + if (I_VS_SEEKER_CHARGING != 0) + MapResetTrainerRematches(mapGroup, mapNum); DoTimeBasedEvents(); SetSavedWeatherFromCurrMapHeader(); @@ -926,8 +926,8 @@ static void LoadMapFromWarp(bool32 a1) TryUpdateRandomTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); #endif //FREE_MATCH_CALL -if (I_VS_SEEKER_CHARGING != 0) - MapResetTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); + if (I_VS_SEEKER_CHARGING != 0) + MapResetTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); if (a1 != TRUE) DoTimeBasedEvents(); diff --git a/test/battle/ability/neutralizing_gas.c b/test/battle/ability/neutralizing_gas.c index 83336d3113..02f7323af2 100644 --- a/test/battle/ability/neutralizing_gas.c +++ b/test/battle/ability/neutralizing_gas.c @@ -353,3 +353,45 @@ SINGLE_BATTLE_TEST("Neutralizing Gas only displays exiting message for the last NOT MESSAGE("The effects of the neutralizing gas wore off!"); } } + +DOUBLE_BATTLE_TEST("Neutralizing Gas is active for the duration of a Spread Move even if Neutralizing Gas is no longer on the field") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_ORIGIN_PULSE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WEEZING) { HP(1); Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_GOLEM) { Ability(ABILITY_STURDY); } + OPPONENT(SPECIES_BASCULEGION) { Ability(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_ORIGIN_PULSE); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_NEUTRALIZING_GAS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ORIGIN_PULSE, opponentLeft); + HP_BAR(playerLeft); + HP_BAR(playerRight); + MESSAGE("Weezing fainted!"); + MESSAGE("Golem fainted!"); + NOT ABILITY_POPUP(playerRight, ABILITY_STURDY); + } +} + +DOUBLE_BATTLE_TEST("Neutralizing Gas is active until the last Dragon Darts hit even if Neutralizing Gas is no longer on the field") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_DARTS) == EFFECT_DRAGON_DARTS); + PLAYER(SPECIES_WEEZING) { HP(1); Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_GOLEM) { HP(2); MaxHP(2); Ability(ABILITY_STURDY); } + OPPONENT(SPECIES_BASCULEGION) { Ability(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_DRAGON_DARTS, target: playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_NEUTRALIZING_GAS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, opponentLeft); + HP_BAR(playerLeft); + MESSAGE("Weezing fainted!"); + HP_BAR(playerRight); + NOT MESSAGE("Golem fainted!"); + ABILITY_POPUP(playerRight, ABILITY_STURDY); + } +} diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index cc3fb135e5..08ed4d7807 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -487,7 +487,6 @@ AI_DOUBLE_BATTLE_TEST("AI treats an ally's redirection ability appropriately (ge AI_DOUBLE_BATTLE_TEST("AI recognizes Volt Absorb received from Trace") { - KNOWN_FAILING; // MGriffin's PR that switched two turn charging moves in AI tests broke this test, waiting on a fix GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_MAGNETON); @@ -495,7 +494,7 @@ AI_DOUBLE_BATTLE_TEST("AI recognizes Volt Absorb received from Trace") OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); } OPPONENT(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_THUNDER_WAVE, MOVE_THUNDERSHOCK, MOVE_WATER_GUN); } } WHEN { - TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDERSHOCK); NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDER_WAVE); NOT_EXPECT_MOVE(opponentRight, MOVE_THUNDER_WAVE); } + TURN { NOT_EXPECT_MOVES(opponentLeft, MOVE_THUNDERSHOCK, MOVE_THUNDER_WAVE); NOT_EXPECT_MOVE(opponentRight, MOVE_THUNDER_WAVE); } } THEN { EXPECT(gAiLogicData->abilities[B_POSITION_PLAYER_RIGHT] == ABILITY_VOLT_ABSORB); } diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 1203413c04..40a1d16778 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1704,3 +1704,23 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider choice-locked TURN { MOVE(player, MOVE_MIGHTY_CLEAVE); EXPECT_MOVE(opponent, MOVE_TACKLE); item == ITEM_NONE ? EXPECT_SEND_OUT(opponent, 1) : EXPECT_SEND_OUT(opponent, 2); } } } + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI considers both meeting and exceeding KO thresholds correctly") +{ + u32 hp; + PARAMETRIZE { hp = 40; } + PARAMETRIZE { hp = 80; } + PARAMETRIZE { hp = 79; } + PARAMETRIZE { hp = 81; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_RAGE) == EFFECT_FIXED_HP_DAMAGE); + ASSUME(GetMoveFixedHPDamage(MOVE_DRAGON_RAGE) == 40); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_ZIGZAGOON) { Speed(5); HP(hp); Moves(MOVE_PROTECT, MOVE_TACKLE); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(6); Moves(MOVE_EXPLOSION); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(6); Moves(MOVE_DRAGON_RAGE); } + OPPONENT(SPECIES_BELDUM) { Speed(4); Moves(MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_EXPLOSION); hp > 80 ? EXPECT_SEND_OUT(opponent, 2) : EXPECT_SEND_OUT(opponent, 1); } + } +} diff --git a/test/battle/move_effect/beak_blast.c b/test/battle/move_effect/beak_blast.c index b879a198c8..6e94a908e5 100644 --- a/test/battle/move_effect/beak_blast.c +++ b/test/battle/move_effect/beak_blast.c @@ -86,7 +86,7 @@ SINGLE_BATTLE_TEST("Beak Blast burns only when contact moves are used") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_BEAK_BLAST); MOVE(opponent, move); } + TURN { MOVE(opponent, move); MOVE(player, MOVE_BEAK_BLAST); } TURN {} } SCENE { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_BEAK_BLAST_SETUP, player); @@ -115,11 +115,11 @@ SINGLE_BATTLE_TEST("Beak Blast burns only when contact moves are used") SINGLE_BATTLE_TEST("Beak Blast doesn't burn fire types") { GIVEN { - ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE || gSpeciesInfo[SPECIES_ARCANINE].types[1]); + ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE || gSpeciesInfo[SPECIES_ARCANINE].types[1] == TYPE_FIRE); PLAYER(SPECIES_ARCANINE); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_BEAK_BLAST); MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_BEAK_BLAST); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); NOT STATUS_ICON(player, burn: TRUE); @@ -127,6 +127,20 @@ SINGLE_BATTLE_TEST("Beak Blast doesn't burn fire types") } } +SINGLE_BATTLE_TEST("Beak Blast doesn't burn after being used") +{ + GIVEN { + ASSUME(GetMovePriority(MOVE_COUNTER) < GetMovePriority(MOVE_BEAK_BLAST)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BEAK_BLAST); MOVE(player, MOVE_COUNTER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAK_BLAST, opponent); + NOT STATUS_ICON(player, burn: TRUE); + } +} + TO_DO_BATTLE_TEST("Beak Blast's charging message is shown regardless if it would've missed"); TO_DO_BATTLE_TEST("Beak Blast fails if it's forced by Encore after choosing a different move"); TO_DO_BATTLE_TEST("Bulletproof is immune to Beak Blast but not to the burn it causes"); diff --git a/test/battle/move_effect/fling.c b/test/battle/move_effect/fling.c index 05ccec70a0..2046ebe638 100644 --- a/test/battle/move_effect/fling.c +++ b/test/battle/move_effect/fling.c @@ -80,8 +80,35 @@ SINGLE_BATTLE_TEST("Fling fails for Pokémon with Klutz ability") } } -TO_DO_BATTLE_TEST("Fling fails if the item changes the Pokémon's form") -TO_DO_BATTLE_TEST("Fling works if the item changes a Pokémon's form but not the one holding it") //Eg. non-matching Mega Stones +SINGLE_BATTLE_TEST("Fling fails if the item changes the Pokémon's form") +{ + GIVEN { + PLAYER(SPECIES_GIRATINA_ORIGIN) { Item(ITEM_GRISEOUS_CORE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_GRISEOUS_CORE); + } +} + +SINGLE_BATTLE_TEST("Fling works if the item changes a Pokémon's form but not the one holding it") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Item(ITEM_BLASTOISINITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + NOT MESSAGE("But it failed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} SINGLE_BATTLE_TEST("Fling's thrown item can be regained with Recycle") { @@ -478,25 +505,46 @@ SINGLE_BATTLE_TEST("Fling deals damage based on items fling power") } } -SINGLE_BATTLE_TEST("Flinging a Mental Herb does not trigger the item if the target doesn't have anything that's cured by Mental Herb") +SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power") { + s16 damage[2]; + GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MENTAL_HERB); } - OPPONENT(SPECIES_WOBBUFFET); + ASSUME(GetMovePower(MOVE_EARTHQUAKE) == GetMovePower(MOVE_EGG_BOMB)); + ASSUME(!IsSpeciesOfType(SPECIES_WOBBUFFET, TYPE_DARK)); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_TM_EARTHQUAKE); } + OPPONENT(SPECIES_HIPPOWDON); } WHEN { TURN { MOVE(player, MOVE_FLING); } + TURN { MOVE(player, MOVE_EGG_BOMB); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); - NONE_OF { - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); - MESSAGE("The opposing Wobbuffet got over its infatuation!"); - MESSAGE("The opposing Wobbuffet's Taunt wore off!"); - MESSAGE("The opposing Wobbuffet ended its encore!"); - MESSAGE("The opposing Wobbuffet is no longer tormented!"); - MESSAGE("The opposing Wobbuffet's move is no longer disabled!"); - MESSAGE("The opposing Wobbuffet is cured of its heal block!"); - } + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EGG_BOMB, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); } } -TO_DO_BATTLE_TEST("Fling deals damage based on a TM's move power") +SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power") +{ + s16 damage[2]; + + GIVEN { + ASSUME(GetMovePower(MOVE_EARTHQUAKE) == GetMovePower(MOVE_EGG_BOMB)); + ASSUME(!IsSpeciesOfType(SPECIES_WOBBUFFET, TYPE_DARK)); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_TM_EARTHQUAKE); } + OPPONENT(SPECIES_HIPPOWDON); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + TURN { MOVE(player, MOVE_EGG_BOMB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EGG_BOMB, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} diff --git a/test/battle/move_effect/roar.c b/test/battle/move_effect/roar.c index 5b1230f253..5c46e0cdf4 100644 --- a/test/battle/move_effect/roar.c +++ b/test/battle/move_effect/roar.c @@ -20,6 +20,8 @@ SINGLE_BATTLE_TEST("Roar switches the target with a random non-fainted replaceme } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_ROAR, player); MESSAGE("The opposing Bulbasaur was dragged out!"); + } THEN { + EXPECT_EQ(gLastUsedMove, MOVE_ROAR); } }