Ally Switch (#3533)
* ally switch move animation * Ally Switch anim done * ally switch test and improve animation * derp * add ally switch known failing test for ally targeting moves * moves which targetted ally fail after ally switch * ally switch works like protect --------- Co-authored-by: root <root@LAPTOP-3SNV7DEQ> Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
parent
8d238c88b9
commit
6137db102e
18 changed files with 514 additions and 66 deletions
|
@ -1313,6 +1313,15 @@
|
|||
.4byte \jumpInstr
|
||||
.endm
|
||||
|
||||
.macro allyswitchswapbattlers
|
||||
callnative BS_AllySwitchSwapBattler
|
||||
.endm
|
||||
|
||||
.macro allyswitchfailchance jumpInstr:req
|
||||
callnative BS_AllySwitchFailChance
|
||||
.4byte \jumpInstr
|
||||
.endm
|
||||
|
||||
.macro jumpifholdeffect battler:req, holdEffect:req, jumpInstr:req
|
||||
callnative BS_JumpIfHoldEffect
|
||||
.byte \battler
|
||||
|
|
|
@ -5625,6 +5625,11 @@ Move_QUICK_GUARD:
|
|||
end
|
||||
|
||||
Move_ALLY_SWITCH:
|
||||
call SetPsychicBackground
|
||||
createvisualtask AnimTask_AllySwitchAttacker, 2
|
||||
createvisualtask AnimTask_AllySwitchPartner, 2
|
||||
call DoubleTeamAnimRet
|
||||
call UnsetPsychicBg
|
||||
end
|
||||
|
||||
Move_SCALD:
|
||||
|
@ -19349,8 +19354,7 @@ Move_TELEPORT:
|
|||
waitforvisualfinish
|
||||
end
|
||||
|
||||
Move_DOUBLE_TEAM:
|
||||
createvisualtask AnimTask_DoubleTeam, 2
|
||||
DoubleTeamAnimRet:
|
||||
setalpha 12, 8
|
||||
monbg ANIM_ATK_PARTNER
|
||||
playsewithpan SE_M_DOUBLE_TEAM, SOUND_PAN_ATTACKER
|
||||
|
@ -19374,6 +19378,11 @@ Move_DOUBLE_TEAM:
|
|||
clearmonbg ANIM_ATK_PARTNER
|
||||
blendoff
|
||||
delay 1
|
||||
return
|
||||
|
||||
Move_DOUBLE_TEAM:
|
||||
createvisualtask AnimTask_DoubleTeam, 2
|
||||
call DoubleTeamAnimRet
|
||||
end
|
||||
|
||||
Move_MINIMIZE:
|
||||
|
|
|
@ -1521,8 +1521,11 @@ BattleScript_EffectAllySwitch:
|
|||
attackstring
|
||||
ppreduce
|
||||
jumpifnoally BS_ATTACKER, BattleScript_ButItFailed
|
||||
allyswitchfailchance BattleScript_ButItFailed
|
||||
attackanimation
|
||||
waitanimation
|
||||
@ The actual data/gfx swap happens in the move animation. Here it's just the gBattlerAttacker / scripting battler change
|
||||
allyswitchswapbattlers
|
||||
printstring STRINGID_ALLYSWITCHPOSITION
|
||||
waitmessage B_WAIT_TIME_LONG
|
||||
goto BattleScript_MoveEnd
|
||||
|
|
|
@ -67,8 +67,8 @@ struct DisableStruct
|
|||
u32 transformedMonOtId;
|
||||
u16 disabledMove;
|
||||
u16 encoredMove;
|
||||
u8 protectUses;
|
||||
u8 stockpileCounter;
|
||||
u8 protectUses:4;
|
||||
u8 stockpileCounter:4;
|
||||
s8 stockpileDef;
|
||||
s8 stockpileSpDef;
|
||||
s8 stockpileBeforeDef;
|
||||
|
@ -163,6 +163,7 @@ struct ProtectStruct
|
|||
u16 silkTrapped:1;
|
||||
u16 eatMirrorHerb:1;
|
||||
u16 activateOpportunist:2; // 2 - to copy stats. 1 - stats copied (do not repeat). 0 - no stats to copy
|
||||
u16 usedAllySwitch:1;
|
||||
u32 physicalDmg;
|
||||
u32 specialDmg;
|
||||
u8 physicalBattlerId;
|
||||
|
|
|
@ -134,7 +134,7 @@ void SetBattlerSpriteYOffsetFromRotation(u8 spriteId);
|
|||
u32 GetBattlePalettesMask(bool8 battleBackground, bool8 attacker, bool8 target, bool8 attackerPartner, bool8 targetPartner, bool8 anim1, bool8 anim2);
|
||||
u32 GetBattleMonSpritePalettesMask(u8 playerLeft, u8 playerRight, u8 opponentLeft, u8 opponentRight);
|
||||
u8 GetSpritePalIdxByBattler(u8 battler);
|
||||
s16 CloneBattlerSpriteWithBlend(u8);
|
||||
s16 CloneBattlerSpriteWithBlend(u8 animBattler);
|
||||
void DestroySpriteWithActiveSheet(struct Sprite *);
|
||||
u8 CreateInvisibleSpriteCopy(int, u8, int);
|
||||
void AnimLoadCompressedBgTilemapHandleContest(struct BattleAnimBgData *, const void *, bool32);
|
||||
|
|
|
@ -103,6 +103,7 @@ bool32 IsBurstTriggerSpriteActive(void);
|
|||
void HideBurstTriggerSprite(void);
|
||||
void DestroyBurstTriggerSprite(void);
|
||||
void MegaIndicator_LoadSpritesGfx(void);
|
||||
void MegaIndicator_SetVisibilities(u32 healthboxId, bool32 invisible);
|
||||
u8 CreatePartyStatusSummarySprites(u8 battler, struct HpAndStatus *partyInfo, bool8 skipPlayer, bool8 isBattleStart);
|
||||
void Task_HidePartyStatusSummary(u8 taskId);
|
||||
void UpdateHealthboxAttribute(u8 healthboxSpriteId, struct Pokemon *mon, u8 elementId);
|
||||
|
|
|
@ -57,6 +57,7 @@ void SwitchInClearSetData(u32 battler);
|
|||
const u8* FaintClearSetData(u32 battler);
|
||||
void BattleTurnPassed(void);
|
||||
u8 IsRunningFromBattleImpossible(u32 battler);
|
||||
void SwitchTwoBattlersInParty(u32 battler, u32 battler2);
|
||||
void SwitchPartyOrder(u32 battlerId);
|
||||
void SwapTurnOrder(u8 id1, u8 id2);
|
||||
u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect);
|
||||
|
|
|
@ -13,6 +13,7 @@ extern const u8 BattleScript_MoveMissedPause[];
|
|||
extern const u8 BattleScript_MoveMissed[];
|
||||
extern const u8 BattleScript_FlingFailConsumeItem[];
|
||||
extern const u8 BattleScript_FailedFromAtkString[];
|
||||
extern const u8 BattleScript_FailedFromAtkCanceler[];
|
||||
extern const u8 BattleScript_ButItFailed[];
|
||||
extern const u8 BattleScript_StatUp[];
|
||||
extern const u8 BattleScript_StatDown[];
|
||||
|
|
|
@ -111,6 +111,7 @@
|
|||
#define B_WIDE_GUARD GEN_LATEST // In Gen5 only, Quick Guard has a chance to fail if used consecutively.
|
||||
#define B_QUICK_GUARD GEN_LATEST // In Gen5 only, Wide Guard has a chance to fail if used consecutively.
|
||||
#define B_IMPRISON GEN_LATEST // In Gen5+, Imprison doesn't fail if opposing pokemon don't have any moves the user knows.
|
||||
#define B_ALLY_SWITCH_FAIL_CHANCE GEN_LATEST // In Gen9, using Ally Switch consecutively decreases the chance of success for each consecutive use.
|
||||
|
||||
// Ability settings
|
||||
#define B_EXPANDED_ABILITY_NAMES TRUE // If TRUE, ability names are increased from 12 characters to 16 characters.
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
|
||||
void ReshowBattleScreenDummy(void);
|
||||
void ReshowBattleScreenAfterMenu(void);
|
||||
void CreateBattlerSprite(u32 battler);
|
||||
|
||||
#endif // GUARD_RESHOW_BATTLE_SCREEN_H
|
||||
|
|
|
@ -237,7 +237,9 @@ void LaunchBattleAnimation(u32 animType, u32 animId)
|
|||
if (gTestRunnerEnabled)
|
||||
{
|
||||
TestRunner_Battle_RecordAnimation(animType, animId);
|
||||
if (gTestRunnerHeadless)
|
||||
// Play Transform and Ally Switch even in Headless as these move animations also change mon data.
|
||||
if (gTestRunnerHeadless
|
||||
&& !(animType == ANIM_TYPE_MOVE && (animId == MOVE_TRANSFORM || animId == MOVE_ALLY_SWITCH)))
|
||||
{
|
||||
gAnimScriptCallback = Nop;
|
||||
gAnimScriptActive = FALSE;
|
||||
|
|
|
@ -9,21 +9,16 @@
|
|||
#include "math_util.h"
|
||||
#include "palette.h"
|
||||
#include "random.h"
|
||||
#include "reshow_battle_screen.h"
|
||||
#include "scanline_effect.h"
|
||||
#include "sound.h"
|
||||
#include "trig.h"
|
||||
#include "util.h"
|
||||
#include "constants/abilities.h"
|
||||
#include "constants/rgb.h"
|
||||
#include "constants/songs.h"
|
||||
#include "constants/moves.h"
|
||||
|
||||
struct {
|
||||
s16 startX;
|
||||
s16 startY;
|
||||
s16 targetX;
|
||||
s16 targetY;
|
||||
} static EWRAM_DATA sFrenzyPlantRootData = {0}; // Debug? Written to but never read.
|
||||
|
||||
static void AnimMovePowderParticle_Step(struct Sprite *);
|
||||
static void AnimSolarBeamSmallOrb(struct Sprite *);
|
||||
static void AnimSolarBeamSmallOrb_Step(struct Sprite *);
|
||||
|
@ -4256,10 +4251,6 @@ static void AnimFrenzyPlantRoot(struct Sprite *sprite)
|
|||
StartSpriteAnim(sprite, gBattleAnimArgs[4]);
|
||||
sprite->data[2] = gBattleAnimArgs[5];
|
||||
sprite->callback = AnimRootFlickerOut;
|
||||
sFrenzyPlantRootData.startX = sprite->x;
|
||||
sFrenzyPlantRootData.startY = sprite->y;
|
||||
sFrenzyPlantRootData.targetX = targetX;
|
||||
sFrenzyPlantRootData.targetY = targetY;
|
||||
}
|
||||
|
||||
static void AnimRootFlickerOut(struct Sprite *sprite)
|
||||
|
@ -6486,78 +6477,238 @@ static void AnimHornHit_Step(struct Sprite *sprite)
|
|||
DestroyAnimSprite(sprite);
|
||||
}
|
||||
|
||||
void AnimTask_DoubleTeam(u8 taskId)
|
||||
{
|
||||
u16 i;
|
||||
int obj;
|
||||
u16 r3;
|
||||
u16 r4;
|
||||
struct Task *task = &gTasks[taskId];
|
||||
task->data[0] = GetAnimBattlerSpriteId(ANIM_ATTACKER);
|
||||
task->data[1] = AllocSpritePalette(ANIM_TAG_BENT_SPOON);
|
||||
r3 = OBJ_PLTT_ID(task->data[1]);
|
||||
r4 = OBJ_PLTT_ID2(gSprites[task->data[0]].oam.paletteNum);
|
||||
for (i = 1; i < 16; i++)
|
||||
gPlttBufferUnfaded[r3 + i] = gPlttBufferUnfaded[r4 + i];
|
||||
// Double Team and Ally Switch.
|
||||
#define tBattlerSpriteId data[0]
|
||||
#define tSpoonPal data[1]
|
||||
#define tBlendSpritesCount data[3]
|
||||
#define tBattlerId data[4]
|
||||
#define tIsAllySwitch data[5]
|
||||
|
||||
BlendPalette(r3, 16, 11, RGB_BLACK);
|
||||
task->data[3] = 0;
|
||||
i = 0;
|
||||
while (i < 2 && (obj = CloneBattlerSpriteWithBlend(0)) >= 0)
|
||||
#define sCounter data[0]
|
||||
#define sSinIndex data[1]
|
||||
#define sTaskId data[2]
|
||||
#define sCounter2 data[3]
|
||||
#define sSinAmplitude data[4]
|
||||
#define sSinIndexMod data[5]
|
||||
#define sBattlerFlank data[6]
|
||||
|
||||
void PrepareDoubleTeamAnim(u32 taskId, u32 animBattler, bool32 forAllySwitch)
|
||||
{
|
||||
s32 i, spriteId;
|
||||
u16 palOffsetBattler, palOffsetSpoon;
|
||||
struct Task *task = &gTasks[taskId];
|
||||
|
||||
task->tBattlerSpriteId = GetAnimBattlerSpriteId(animBattler);
|
||||
task->tSpoonPal = AllocSpritePalette(ANIM_TAG_BENT_SPOON);
|
||||
task->tBattlerId = GetAnimBattlerId(animBattler);
|
||||
task->tIsAllySwitch = forAllySwitch;
|
||||
palOffsetSpoon = OBJ_PLTT_ID(task->tSpoonPal);
|
||||
palOffsetBattler = OBJ_PLTT_ID2(gSprites[task->tBattlerSpriteId].oam.paletteNum);
|
||||
for (i = 1; i < 16; i++)
|
||||
gPlttBufferUnfaded[palOffsetSpoon + i] = gPlttBufferUnfaded[palOffsetBattler + i];
|
||||
|
||||
BlendPalette(palOffsetSpoon, 16, 11, RGB_BLACK);
|
||||
task->tBlendSpritesCount = 0;
|
||||
for (i = 0; i < ((forAllySwitch == TRUE) ? 1 : 2); i++)
|
||||
{
|
||||
gSprites[obj].oam.paletteNum = task->data[1];
|
||||
gSprites[obj].data[0] = 0;
|
||||
gSprites[obj].data[1] = i << 7;
|
||||
gSprites[obj].data[2] = taskId;
|
||||
gSprites[obj].callback = AnimDoubleTeam;
|
||||
task->data[3]++;
|
||||
i++;
|
||||
spriteId = CloneBattlerSpriteWithBlend(animBattler);
|
||||
if (spriteId < 0)
|
||||
break;
|
||||
gSprites[spriteId].oam.paletteNum = task->tSpoonPal;
|
||||
gSprites[spriteId].sCounter = 0;
|
||||
gSprites[spriteId].sSinIndex = i << 7;
|
||||
gSprites[spriteId].sTaskId = taskId;
|
||||
// Which direction
|
||||
if (gBattleAnimAttacker & BIT_FLANK)
|
||||
gSprites[spriteId].sBattlerFlank = (animBattler != ANIM_ATTACKER);
|
||||
else
|
||||
gSprites[spriteId].sBattlerFlank = (animBattler == ANIM_ATTACKER);
|
||||
gSprites[spriteId].callback = AnimDoubleTeam;
|
||||
task->tBlendSpritesCount++;
|
||||
}
|
||||
|
||||
task->func = AnimTask_DoubleTeam_Step;
|
||||
if (GetBattlerSpriteBGPriorityRank(gBattleAnimAttacker) == 1)
|
||||
if (GetBattlerSpriteBGPriorityRank(task->tBattlerId) == 1)
|
||||
ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG1_ON);
|
||||
else
|
||||
ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG2_ON);
|
||||
}
|
||||
|
||||
void AnimTask_DoubleTeam(u8 taskId)
|
||||
{
|
||||
PrepareDoubleTeamAnim(taskId, ANIM_ATTACKER, FALSE);
|
||||
}
|
||||
|
||||
static inline void SwapStructData(void *s1, void *s2, void *data, u32 size)
|
||||
{
|
||||
memcpy(data, s1, size);
|
||||
memcpy(s1, s2, size);
|
||||
memcpy(s2, data, size);
|
||||
}
|
||||
|
||||
static void ReloadBattlerSprites(u32 battler, struct Pokemon *party)
|
||||
{
|
||||
BattleLoadMonSpriteGfx(&party[gBattlerPartyIndexes[battler]], battler);
|
||||
CreateBattlerSprite(battler);
|
||||
UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], &party[gBattlerPartyIndexes[battler]], HEALTHBOX_ALL);
|
||||
// If battler is mega evolved / primal reversed, hide the sprite until the move animation finishes.
|
||||
MegaIndicator_SetVisibilities(gHealthboxSpriteIds[battler], TRUE);
|
||||
}
|
||||
|
||||
static void AnimTask_AllySwitchDataSwap(u8 taskId)
|
||||
{
|
||||
s32 i, j;
|
||||
struct Pokemon *party;
|
||||
u32 temp;
|
||||
u32 battlerAtk = gBattlerAttacker;
|
||||
u32 battlerPartner = BATTLE_PARTNER(battlerAtk);
|
||||
|
||||
void *data = Alloc(0x200);
|
||||
if (data == NULL)
|
||||
{
|
||||
SoftReset(1);
|
||||
}
|
||||
|
||||
SwapStructData(&gBattleMons[battlerAtk], &gBattleMons[battlerPartner], data, sizeof(struct BattlePokemon));
|
||||
SwapStructData(&gDisableStructs[battlerAtk], &gDisableStructs[battlerPartner], data, sizeof(struct DisableStruct));
|
||||
SwapStructData(&gSpecialStatuses[battlerAtk], &gSpecialStatuses[battlerPartner], data, sizeof(struct SpecialStatus));
|
||||
SwapStructData(&gProtectStructs[battlerAtk], &gProtectStructs[battlerPartner], data, sizeof(struct ProtectStruct));
|
||||
SwapStructData(&gBattleSpritesDataPtr->battlerData[battlerAtk], &gBattleSpritesDataPtr->battlerData[battlerPartner], data, sizeof(struct BattleSpriteInfo));
|
||||
|
||||
SWAP(gTransformedPersonalities[battlerAtk], gTransformedPersonalities[battlerPartner], temp);
|
||||
SWAP(gTransformedOtIds[battlerAtk], gTransformedOtIds[battlerPartner], temp);
|
||||
SWAP(gStatuses3[battlerAtk], gStatuses3[battlerPartner], temp);
|
||||
SWAP(gStatuses4[battlerAtk], gStatuses4[battlerPartner], temp);
|
||||
SWAP(gBattleStruct->chosenMovePositions[battlerAtk], gBattleStruct->chosenMovePositions[battlerPartner], temp);
|
||||
SWAP(gChosenMoveByBattler[battlerAtk], gChosenMoveByBattler[battlerPartner], temp);
|
||||
SWAP(gBattleStruct->moveTarget[battlerAtk], gBattleStruct->moveTarget[battlerPartner], temp);
|
||||
SWAP(gMoveSelectionCursor[battlerAtk], gMoveSelectionCursor[battlerPartner], temp);
|
||||
// Swap turn order, so that all the battlers take action
|
||||
SWAP(gChosenActionByBattler[battlerAtk], gChosenActionByBattler[battlerPartner], temp);
|
||||
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
||||
{
|
||||
if (gBattlerByTurnOrder[i] == battlerAtk || gBattlerByTurnOrder[i] == battlerPartner)
|
||||
{
|
||||
for (j = i + 1; j < MAX_BATTLERS_COUNT; j++)
|
||||
{
|
||||
if (gBattlerByTurnOrder[j] == battlerAtk || gBattlerByTurnOrder[j] == battlerPartner)
|
||||
break;
|
||||
}
|
||||
SWAP(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], temp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
party = GetBattlerParty(battlerAtk);
|
||||
SwitchTwoBattlersInParty(battlerAtk, battlerPartner);
|
||||
SWAP(gBattlerPartyIndexes[battlerAtk], gBattlerPartyIndexes[battlerPartner], temp);
|
||||
|
||||
// For Snipe Shot and abilities Stalwart/Propeller Tail - keep the original target.
|
||||
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
||||
{
|
||||
u16 ability = GetBattlerAbility(i);
|
||||
if (gChosenMoveByBattler[i] == MOVE_SNIPE_SHOT || ability == ABILITY_PROPELLER_TAIL || ability == ABILITY_STALWART)
|
||||
gBattleStruct->moveTarget[i] ^= BIT_FLANK;
|
||||
}
|
||||
|
||||
// For some reason the order in which the sprites are created matters. Looks like an issue with the sprite system, potentially with the Sprite Template.
|
||||
if ((battlerAtk & BIT_FLANK) != 0)
|
||||
{
|
||||
ReloadBattlerSprites(battlerAtk, party);
|
||||
ReloadBattlerSprites(battlerPartner, party);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReloadBattlerSprites(battlerPartner, party);
|
||||
ReloadBattlerSprites(battlerAtk, party);
|
||||
}
|
||||
|
||||
Free(data);
|
||||
|
||||
gBattleScripting.battler = battlerPartner;
|
||||
DestroyAnimVisualTask(taskId);
|
||||
}
|
||||
|
||||
static void AnimTask_DoubleTeam_Step(u8 taskId)
|
||||
{
|
||||
struct Task *task = &gTasks[taskId];
|
||||
if (!task->data[3])
|
||||
if (task->tBlendSpritesCount == 0)
|
||||
{
|
||||
if (GetBattlerSpriteBGPriorityRank(gBattleAnimAttacker) == 1)
|
||||
if (GetBattlerSpriteBGPriorityRank(task->tBattlerId) == 1)
|
||||
SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG1_ON);
|
||||
else
|
||||
SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG2_ON);
|
||||
|
||||
FreeSpritePaletteByTag(ANIM_TAG_BENT_SPOON);
|
||||
DestroyAnimVisualTask(taskId);
|
||||
// Swap attacker and partner data-wise and visually
|
||||
if (task->tIsAllySwitch && task->tBattlerId == BATTLE_PARTNER(gBattlerAttacker))
|
||||
gTasks[taskId].func = AnimTask_AllySwitchDataSwap;
|
||||
else
|
||||
DestroyAnimVisualTask(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
static void AnimDoubleTeam(struct Sprite *sprite)
|
||||
{
|
||||
if (++sprite->data[3] > 1)
|
||||
if (++sprite->sCounter2 > 1)
|
||||
{
|
||||
sprite->data[3] = 0;
|
||||
sprite->data[0]++;
|
||||
sprite->sCounter2 = 0;
|
||||
sprite->sCounter++;
|
||||
}
|
||||
|
||||
if (sprite->data[0] > 64)
|
||||
if (sprite->sCounter > 64)
|
||||
{
|
||||
gTasks[sprite->data[2]].data[3]--;
|
||||
gTasks[sprite->sTaskId].tBlendSpritesCount--;
|
||||
// If Ally Switch - destroy the mon sprites, they'll be created again later.
|
||||
if (gTasks[sprite->sTaskId].tIsAllySwitch && gTasks[sprite->sTaskId].tBattlerId == BATTLE_PARTNER(gBattlerAttacker))
|
||||
{
|
||||
DestroySprite(&gSprites[gBattlerSpriteIds[gBattlerAttacker]]);
|
||||
DestroySprite(&gSprites[gBattlerSpriteIds[BATTLE_PARTNER(gBattlerAttacker)]]);
|
||||
}
|
||||
DestroySpriteWithActiveSheet(sprite);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite->data[4] = gSineTable[sprite->data[0]] / 6;
|
||||
sprite->data[5] = gSineTable[sprite->data[0]] / 13;
|
||||
sprite->data[1] = (sprite->data[1] + sprite->data[5]) & 0xFF;
|
||||
sprite->x2 = Sin(sprite->data[1], sprite->data[4]);
|
||||
sprite->sSinAmplitude = gSineTable[sprite->sCounter] / 6;
|
||||
sprite->sSinIndexMod = gSineTable[sprite->sCounter] / 13;
|
||||
sprite->sSinIndex = (sprite->sSinIndex + sprite->sSinIndexMod) & 0xFF;
|
||||
sprite->x2 = Sin(sprite->sSinIndex, sprite->sSinAmplitude);
|
||||
if (gTasks[sprite->sTaskId].tIsAllySwitch)
|
||||
{
|
||||
if (sprite->sBattlerFlank)
|
||||
sprite->x2 = abs(sprite->x2);
|
||||
else
|
||||
sprite->x2 = -(abs(sprite->x2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimTask_AllySwitchAttacker(u8 taskId)
|
||||
{
|
||||
PrepareDoubleTeamAnim(taskId, ANIM_ATTACKER, TRUE);
|
||||
gSprites[gBattlerSpriteIds[gBattlerAttacker]].invisible = TRUE;
|
||||
gSprites[gBattlerSpriteIds[BATTLE_PARTNER(gBattlerAttacker)]].invisible = TRUE;
|
||||
}
|
||||
|
||||
void AnimTask_AllySwitchPartner(u8 taskId)
|
||||
{
|
||||
PrepareDoubleTeamAnim(taskId, ANIM_ATK_PARTNER, TRUE);
|
||||
}
|
||||
|
||||
#undef tBattlerSpriteId
|
||||
#undef tSpoonPal
|
||||
#undef tBlendSpritesCount
|
||||
#undef tBattlerId
|
||||
#undef tIsAllySwitch
|
||||
|
||||
#undef sCounter
|
||||
#undef sSinIndex
|
||||
#undef sTaskId
|
||||
#undef sCounter2
|
||||
#undef sSinAmplitude
|
||||
#undef sSinIndexMod
|
||||
#undef sBattlerFlank
|
||||
|
||||
static void AnimSuperFang(struct Sprite *sprite)
|
||||
{
|
||||
StoreSpriteCallbackInData6(sprite, DestroyAnimSprite);
|
||||
|
|
|
@ -195,7 +195,6 @@ static void SpriteCB_StatusSummaryBalls_OnSwitchout(struct Sprite *);
|
|||
|
||||
static void SpriteCb_MegaTrigger(struct Sprite *);
|
||||
static void SpriteCb_BurstTrigger(struct Sprite *);
|
||||
static void MegaIndicator_SetVisibilities(u32 healthboxId, bool32 invisible);
|
||||
static void MegaIndicator_UpdateLevel(u32 healthboxId, u32 level);
|
||||
static void MegaIndicator_CreateSprite(u32 battlerId, u32 healthboxSpriteId);
|
||||
static void MegaIndicator_UpdateOamPriority(u32 healthboxId, u32 oamPriority);
|
||||
|
|
|
@ -3974,6 +3974,25 @@ u8 IsRunningFromBattleImpossible(u32 battler)
|
|||
return BATTLE_RUN_SUCCESS;
|
||||
}
|
||||
|
||||
void SwitchTwoBattlersInParty(u32 battler, u32 battler2)
|
||||
{
|
||||
s32 i;
|
||||
u32 partyId1, partyId2;
|
||||
|
||||
for (i = 0; i < (int)ARRAY_COUNT(gBattlePartyCurrentOrder); i++)
|
||||
gBattlePartyCurrentOrder[i] = *(battler * 3 + i + (u8 *)(gBattleStruct->battlerPartyOrders));
|
||||
|
||||
partyId1 = GetPartyIdFromBattlePartyId(gBattlerPartyIndexes[battler]);
|
||||
partyId2 = GetPartyIdFromBattlePartyId(gBattlerPartyIndexes[battler2]);
|
||||
SwitchPartyMonSlots(partyId1, partyId2);
|
||||
|
||||
for (i = 0; i < (int)ARRAY_COUNT(gBattlePartyCurrentOrder); i++)
|
||||
{
|
||||
*(battler * 3 + i + (u8 *)(gBattleStruct->battlerPartyOrders)) = gBattlePartyCurrentOrder[i];
|
||||
*(BATTLE_PARTNER(battler) * 3 + i + (u8 *)(gBattleStruct->battlerPartyOrders)) = gBattlePartyCurrentOrder[i];
|
||||
}
|
||||
}
|
||||
|
||||
void SwitchPartyOrder(u32 battler)
|
||||
{
|
||||
s32 i;
|
||||
|
|
|
@ -2182,6 +2182,7 @@ static void Cmd_attackanimation(void)
|
|||
if ((gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION))
|
||||
&& gCurrentMove != MOVE_TRANSFORM
|
||||
&& gCurrentMove != MOVE_SUBSTITUTE
|
||||
&& gCurrentMove != MOVE_ALLY_SWITCH
|
||||
// In a wild double battle gotta use the teleport animation if two wild pokemon are alive.
|
||||
&& !(gCurrentMove == MOVE_TELEPORT && WILD_DOUBLE_BATTLE && GetBattlerSide(gBattlerAttacker) == B_SIDE_OPPONENT && IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker))))
|
||||
{
|
||||
|
@ -10600,17 +10601,22 @@ static void Cmd_various(void)
|
|||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||||
}
|
||||
|
||||
static void TryResetProtectUseCounter(u32 battler)
|
||||
{
|
||||
u32 lastMove = gLastResultingMoves[battler];
|
||||
if (lastMove == MOVE_UNAVAILABLE
|
||||
|| (!gBattleMoves[lastMove].protectionMove && (B_ALLY_SWITCH_FAIL_CHANCE >= GEN_9 && gBattleMoves[lastMove].effect != EFFECT_ALLY_SWITCH)))
|
||||
gDisableStructs[battler].protectUses = 0;
|
||||
}
|
||||
|
||||
static void Cmd_setprotectlike(void)
|
||||
{
|
||||
CMD_ARGS();
|
||||
|
||||
bool32 fail = TRUE;
|
||||
bool32 notLastTurn = TRUE;
|
||||
u32 lastMove = gLastResultingMoves[gBattlerAttacker];
|
||||
|
||||
if (lastMove == MOVE_UNAVAILABLE || !(gBattleMoves[lastMove].protectionMove))
|
||||
gDisableStructs[gBattlerAttacker].protectUses = 0;
|
||||
|
||||
TryResetProtectUseCounter(gBattlerAttacker);
|
||||
if (gCurrentTurnActionNumber == (gBattlersCount - 1))
|
||||
notLastTurn = FALSE;
|
||||
|
||||
|
@ -16481,3 +16487,34 @@ void BS_TryTriggerStatusForm(void)
|
|||
}
|
||||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||||
}
|
||||
|
||||
void BS_AllySwitchSwapBattler(void)
|
||||
{
|
||||
NATIVE_ARGS();
|
||||
|
||||
gBattleScripting.battler = gBattlerAttacker;
|
||||
gBattlerAttacker ^= BIT_FLANK;
|
||||
gProtectStructs[gBattlerAttacker].usedAllySwitch = TRUE;
|
||||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||||
}
|
||||
|
||||
void BS_AllySwitchFailChance(void)
|
||||
{
|
||||
NATIVE_ARGS(const u8 *failInstr);
|
||||
|
||||
if (B_ALLY_SWITCH_FAIL_CHANCE >= GEN_9)
|
||||
{
|
||||
TryResetProtectUseCounter(gBattlerAttacker);
|
||||
if (sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] < Random())
|
||||
{
|
||||
gDisableStructs[gBattlerAttacker].protectUses = 0;
|
||||
gBattlescriptCurrInstr = cmd->failInstr;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
gDisableStructs[gBattlerAttacker].protectUses++;
|
||||
}
|
||||
}
|
||||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||||
}
|
||||
|
|
|
@ -520,6 +520,11 @@ void HandleAction_UseMove(void)
|
|||
gBattlescriptCurrInstr = BattleScript_MoveUsedLoafingAround;
|
||||
}
|
||||
}
|
||||
// Edge case: moves targeting the ally fail after a successful Ally Switch.
|
||||
else if (moveTarget == MOVE_TARGET_ALLY && gProtectStructs[BATTLE_PARTNER(gBattlerAttacker)].usedAllySwitch)
|
||||
{
|
||||
gBattlescriptCurrInstr = BattleScript_FailedFromAtkCanceler;
|
||||
}
|
||||
else
|
||||
{
|
||||
gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect];
|
||||
|
|
|
@ -18,9 +18,8 @@
|
|||
|
||||
// this file's functions
|
||||
static void CB2_ReshowBattleScreenAfterMenu(void);
|
||||
static bool8 LoadBattlerSpriteGfx(u8 battlerId);
|
||||
static void CreateBattlerSprite(u8 battlerId);
|
||||
static void CreateHealthboxSprite(u8 battlerId);
|
||||
static bool8 LoadBattlerSpriteGfx(u32 battler);
|
||||
static void CreateHealthboxSprite(u32 battler);
|
||||
static void ClearBattleBgCntBaseBlocks(void);
|
||||
|
||||
void ReshowBattleScreenDummy(void)
|
||||
|
@ -180,7 +179,7 @@ static void ClearBattleBgCntBaseBlocks(void)
|
|||
regBgcnt2->charBaseBlock = 0;
|
||||
}
|
||||
|
||||
static bool8 LoadBattlerSpriteGfx(u8 battler)
|
||||
static bool8 LoadBattlerSpriteGfx(u32 battler)
|
||||
{
|
||||
if (battler < gBattlersCount)
|
||||
{
|
||||
|
@ -205,7 +204,7 @@ static bool8 LoadBattlerSpriteGfx(u8 battler)
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static void CreateBattlerSprite(u8 battler)
|
||||
void CreateBattlerSprite(u32 battler)
|
||||
{
|
||||
if (battler < gBattlersCount)
|
||||
{
|
||||
|
@ -271,7 +270,7 @@ static void CreateBattlerSprite(u8 battler)
|
|||
}
|
||||
}
|
||||
|
||||
static void CreateHealthboxSprite(u8 battler)
|
||||
static void CreateHealthboxSprite(u32 battler)
|
||||
{
|
||||
if (battler < gBattlersCount)
|
||||
{
|
||||
|
|
209
test/battle/move_effect/ally_switch.c
Normal file
209
test/battle/move_effect/ally_switch.c
Normal file
|
@ -0,0 +1,209 @@
|
|||
#include "global.h"
|
||||
#include "test/battle.h"
|
||||
|
||||
ASSUMPTIONS
|
||||
{
|
||||
ASSUME(gBattleMoves[MOVE_ALLY_SWITCH].effect == EFFECT_ALLY_SWITCH);
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Ally Switch fails in a single battle")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_ALLY_SWITCH); }
|
||||
} SCENE {
|
||||
MESSAGE("Wobbuffet used Ally Switch!");
|
||||
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, player);
|
||||
MESSAGE("But it failed!");
|
||||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Ally Switch fails if there is no partner")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(opponentLeft, MOVE_TACKLE, target:playerRight); }
|
||||
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); }
|
||||
} SCENE {
|
||||
MESSAGE("Wobbuffet fainted!");
|
||||
MESSAGE("Wobbuffet used Ally Switch!");
|
||||
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft);
|
||||
MESSAGE("But it failed!");
|
||||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Ally Switch changes the position of battlers")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(gBattleMoves[MOVE_SCREECH].effect == EFFECT_DEFENSE_DOWN_2);
|
||||
ASSUME(gBattleMoves[MOVE_SCREECH].target == MOVE_TARGET_SELECTED);
|
||||
PLAYER(SPECIES_WOBBUFFET) { Speed(5); } // Wobb is playerLeft, but it'll be Wynaut after Ally Switch
|
||||
PLAYER(SPECIES_WYNAUT) { Speed(4); }
|
||||
OPPONENT(SPECIES_KADABRA) { Speed(3); }
|
||||
OPPONENT(SPECIES_ABRA) { Speed(2); }
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); MOVE(opponentLeft, MOVE_SCREECH, target:playerLeft); MOVE(opponentRight, MOVE_SCREECH, target:playerLeft); }
|
||||
} SCENE {
|
||||
MESSAGE("Wobbuffet used Ally Switch!");
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft);
|
||||
MESSAGE("Wobbuffet and Wynaut switched places!");
|
||||
|
||||
MESSAGE("Foe Kadabra used Screech!");
|
||||
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft);
|
||||
MESSAGE("Wynaut's Defense harshly fell!");
|
||||
|
||||
MESSAGE("Foe Abra used Screech!");
|
||||
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft);
|
||||
MESSAGE("Wynaut's Defense harshly fell!");
|
||||
} THEN {
|
||||
EXPECT_EQ(playerLeft->speed, 4);
|
||||
EXPECT_EQ(playerLeft->species, SPECIES_WYNAUT);
|
||||
EXPECT_EQ(playerRight->speed, 5);
|
||||
EXPECT_EQ(playerRight->species, SPECIES_WOBBUFFET);
|
||||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Ally Switch does not redirect the target of Snipe Shot")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(gBattleMoves[MOVE_SNIPE_SHOT].effect == EFFECT_SNIPE_SHOT);
|
||||
PLAYER(SPECIES_WOBBUFFET); // Wobb is playerLeft, but it'll be Wynaut after Ally Switch
|
||||
PLAYER(SPECIES_WYNAUT);
|
||||
OPPONENT(SPECIES_KADABRA);
|
||||
OPPONENT(SPECIES_ABRA);
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); MOVE(opponentLeft, MOVE_SNIPE_SHOT, target:playerLeft); } // Kadabra targets Wobb and Snipe Shot ignores Ally Switch position change.
|
||||
} SCENE {
|
||||
MESSAGE("Wobbuffet used Ally Switch!");
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft);
|
||||
MESSAGE("Wobbuffet and Wynaut switched places!");
|
||||
|
||||
MESSAGE("Foe Kadabra used Snipe Shot!");
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_SNIPE_SHOT, opponentLeft);
|
||||
HP_BAR(playerRight);
|
||||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Ally Switch does not redirect moves done by pokemon with Stalwart and Propeller Tail")
|
||||
{
|
||||
u16 ability;
|
||||
PARAMETRIZE { ability = ABILITY_STALWART; }
|
||||
PARAMETRIZE { ability = ABILITY_PROPELLER_TAIL; }
|
||||
PARAMETRIZE { ability = ABILITY_TELEPATHY; }
|
||||
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET); // Wobb is playerLeft, but it'll be Wynaut after Ally Switch
|
||||
PLAYER(SPECIES_WYNAUT);
|
||||
OPPONENT(SPECIES_KADABRA) { Ability(ability); }
|
||||
OPPONENT(SPECIES_ABRA);
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); MOVE(opponentLeft, MOVE_TACKLE, target:playerRight); } // Kadabra targets playerRight Wynaut.
|
||||
} SCENE {
|
||||
MESSAGE("Wobbuffet used Ally Switch!");
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft);
|
||||
MESSAGE("Wobbuffet and Wynaut switched places!");
|
||||
|
||||
MESSAGE("Foe Kadabra used Tackle!");
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft);
|
||||
HP_BAR((ability == ABILITY_STALWART || ability == ABILITY_PROPELLER_TAIL) ? playerLeft : playerRight);
|
||||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Ally Switch has no effect on parnter's chosen move")
|
||||
{
|
||||
u16 chosenMove;
|
||||
struct BattlePokemon *chosenTarget = NULL;
|
||||
|
||||
PARAMETRIZE { chosenMove = MOVE_TACKLE; chosenTarget = opponentLeft; }
|
||||
PARAMETRIZE { chosenMove = MOVE_TACKLE; chosenTarget = opponentRight; }
|
||||
PARAMETRIZE { chosenMove = MOVE_POUND; chosenTarget = opponentLeft; }
|
||||
PARAMETRIZE { chosenMove = MOVE_POUND; chosenTarget = opponentRight; }
|
||||
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WYNAUT) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_CELEBRATE, MOVE_SCRATCH); }
|
||||
OPPONENT(SPECIES_KADABRA);
|
||||
OPPONENT(SPECIES_ABRA);
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); MOVE(playerRight, chosenMove, target:chosenTarget); }
|
||||
} SCENE {
|
||||
MESSAGE("Wobbuffet used Ally Switch!");
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft);
|
||||
MESSAGE("Wobbuffet and Wynaut switched places!");
|
||||
|
||||
ANIMATION(ANIM_TYPE_MOVE, chosenMove, playerLeft);
|
||||
HP_BAR(chosenTarget);
|
||||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Ally Switch - move fails if the target was ally which changed position")
|
||||
{
|
||||
u32 move = MOVE_NONE;
|
||||
|
||||
PARAMETRIZE { move = MOVE_COACHING; }
|
||||
PARAMETRIZE { move = MOVE_AROMATIC_MIST; }
|
||||
PARAMETRIZE { move = MOVE_HOLD_HANDS; }
|
||||
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WYNAUT);
|
||||
OPPONENT(SPECIES_KADABRA);
|
||||
OPPONENT(SPECIES_ABRA);
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); MOVE(playerRight, move, target:playerLeft); }
|
||||
} SCENE {
|
||||
MESSAGE("Wobbuffet used Ally Switch!");
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft);
|
||||
MESSAGE("Wobbuffet and Wynaut switched places!");
|
||||
|
||||
NOT ANIMATION(ANIM_TYPE_MOVE, move, playerLeft);
|
||||
MESSAGE("But it failed!");
|
||||
}
|
||||
}
|
||||
|
||||
// Verified on Showdown, even though Bulbapedia says otherwise.
|
||||
DOUBLE_BATTLE_TEST("Acupressure works after ally used Ally Switch")
|
||||
{
|
||||
struct BattlePokemon *battlerTarget = NULL;
|
||||
|
||||
PARAMETRIZE { battlerTarget = playerLeft; }
|
||||
PARAMETRIZE { battlerTarget = playerRight; }
|
||||
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WYNAUT);
|
||||
OPPONENT(SPECIES_KADABRA);
|
||||
OPPONENT(SPECIES_ABRA);
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); MOVE(playerRight, MOVE_ACUPRESSURE, target:battlerTarget); }
|
||||
} SCENE {
|
||||
MESSAGE("Wobbuffet used Ally Switch!");
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft);
|
||||
MESSAGE("Wobbuffet and Wynaut switched places!");
|
||||
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_ACUPRESSURE);
|
||||
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, battlerTarget);
|
||||
NOT MESSAGE("But it failed!");
|
||||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Ally Switch increases the Protect-like moves counter")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(B_ALLY_SWITCH_FAIL_CHANCE >= GEN_9);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); }
|
||||
} THEN {
|
||||
EXPECT(gDisableStructs[B_POSITION_PLAYER_RIGHT].protectUses == 1);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue