30/11/25 master to upcoming merge

This commit is contained in:
AlexOn1ine 2025-11-30 14:57:50 +01:00
commit 15d9342fe5
24 changed files with 251 additions and 131 deletions

View File

@ -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).

View File

@ -1733,10 +1733,6 @@
callnative BS_WaitFanfare
.endm
.macro setbeakblast
callnative BS_SetBeakBlast
.endm
.macro cantarshotwork failInstr:req
callnative BS_CanTarShotWork
.4byte \failInstr

View File

@ -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

View File

@ -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
```

View File

@ -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;

View File

@ -112,7 +112,7 @@ enum MoveSuccessOrder
CANCELER_TAUNTED,
CANCELER_IMPRISONED,
CANCELER_CONFUSED,
CANCELER_PARALYSED,
CANCELER_PARALYZED,
CANCELER_INFATUATION,
CANCELER_BIDE,
CANCELER_Z_MOVES,

View File

@ -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)
{

View File

@ -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)

View File

@ -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

View File

@ -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();

View File

@ -1305,6 +1305,7 @@ static void CB2_EndTrainerBattle(void)
{
HandleBattleVariantEndParty();
gIsDebugBattle = FALSE;
if (FollowerNPCIsBattlePartner())
{
RestorePartyAfterFollowerNPCBattle();

View File

@ -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)

View File

@ -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;

View File

@ -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] =

View File

@ -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,

View File

@ -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;

View File

@ -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

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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); }
}
}

View File

@ -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");

View File

@ -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]);
}
}

View File

@ -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);
}
}