mirror of
https://github.com/rh-hideout/pokeemerald-expansion.git
synced 2026-03-21 18:04:50 -05:00
12/07/25 master to upcoming merge
This commit is contained in:
commit
68db4c5a77
|
|
@ -2725,13 +2725,7 @@ BattleScript_EffectNaturalGift::
|
|||
jumpifability BS_ATTACKER, ABILITY_KLUTZ, BattleScript_ButItFailed
|
||||
jumpifstatus3 BS_ATTACKER, STATUS3_EMBARGO, BattleScript_ButItFailed
|
||||
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
|
||||
call BattleScript_EffectHit_RetFromCritCalc
|
||||
jumpifmovehadnoeffect BattleScript_EffectNaturalGiftEnd
|
||||
checkparentalbondcounter 2, BattleScript_EffectNaturalGiftEnd
|
||||
removeitem BS_ATTACKER
|
||||
BattleScript_EffectNaturalGiftEnd:
|
||||
tryfaintmon BS_TARGET
|
||||
goto BattleScript_MoveEnd
|
||||
call BattleScript_HitFromCritCalc
|
||||
|
||||
BattleScript_MakeMoveMissed::
|
||||
setmoveresultflags MOVE_RESULT_MISSED
|
||||
|
|
@ -6684,7 +6678,7 @@ BattleScript_CottonDownActivates::
|
|||
swapattackerwithtarget
|
||||
setbyte gBattlerTarget, 0
|
||||
BattleScript_CottonDownLoop:
|
||||
jumpiffainted BS_TARGET, TRUE, BattleScript_CottonDownLoopIncrement
|
||||
jumpifabsent BS_TARGET, BattleScript_CottonDownLoopIncrement
|
||||
setstatchanger STAT_SPEED, 1, TRUE
|
||||
jumpifbyteequal gBattlerTarget, gEffectBattler, BattleScript_CottonDownLoopIncrement
|
||||
statbuffchange BS_TARGET, STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_ALLOW_PTR, BattleScript_CottonDownLoopIncrement
|
||||
|
|
|
|||
|
|
@ -851,4 +851,3 @@ MtChimney_Text_SawyerRematchDefeat:
|
|||
MtChimney_Text_SawyerPostRematch:
|
||||
.string "Actually, it really is hot here.\n"
|
||||
.string "I'm overdressed for these parts.$"
|
||||
|
||||
|
|
|
|||
|
|
@ -433,4 +433,3 @@ SlateportCity_OceanicMuseum_2F_Text_SSAnneReplica:
|
|||
.string "“S.S. ANNE\p"
|
||||
.string "“A replica of the luxury liner that\n"
|
||||
.string "circles the globe.”$"
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ BattleFrontier_BattlePikeThreePathRoom_EventScript_RightRoomWarp::
|
|||
end
|
||||
|
||||
BattleFrontier_BattlePikeThreePathRoom_EventScript_RoomWarp::
|
||||
clearflag FLAG_SAFE_FOLLOWER_MOVEMENT
|
||||
pike_get PIKE_DATA_WIN_STREAK
|
||||
addvar VAR_RESULT, 1
|
||||
pike_set PIKE_DATA_WIN_STREAK, VAR_RESULT
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ If you intend to use vanilla maps and have not already edited them, revert commi
|
|||
|
||||
If you _have_ edited vanilla maps, the merge conflicts from reverting that commit will cause problems. If you are using vanilla maps, manually copy some of the tileset changes, `.pal`, and `.pla` files in your branch, and begin rebuilding your metatiles to have windows use the palettes that have a `.pla` for light blending the correct color slots. [Triple-layer metatiles](https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles) are highly recommended.
|
||||
|
||||
WARNING: [As per issue #7034](https://github.com/rh-hideout/pokeemerald-expansion/issues/7034) if you follow this tutorial reverting the previously mentioned commit to use the updated palettes in the Hoenn maps and *after* that you try to follow the [Triple-layer metatiles tutorial](https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles), you'll encounter an issue when running the script to update old tilesets to support triple-layer metatiles.
|
||||
Follow the band-aid fix proposed in that issue after this tutorial but before following the triple-layer metatiles tutorial if you want everythign to work properly with light-blended palettes and triple-layer metatiles together.
|
||||
|
||||
You will also want to add the lighting object events from that commit.
|
||||
|
||||
If you are not using Hoenn maps, the primary concern is that you do not use the exact same palette indices for colors you want to be darkened during night time and colors you want to light up. Err towards not light blending a color if you aren't sure how to avoid conflicts.
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 786 B After Width: | Height: | Size: 907 B |
|
|
@ -23,6 +23,15 @@ enum DamageCalcContext
|
|||
AI_ATTACKING,
|
||||
};
|
||||
|
||||
// Higher priority at the bottom; note that these are used in the formula MAX_MON_MOVES ^ AiCompareMovesPriority, which must fit within a u32.
|
||||
// In expansion where MAX_MON_MOVES is 4, this means that AiCompareMovesPriority can range from 0 - 15 inclusive.
|
||||
enum AiCompareMovesPriority
|
||||
{
|
||||
PRIORITY_EFFECT,
|
||||
PRIORITY_ACCURACY,
|
||||
PRIORITY_NOT_CHARGING
|
||||
};
|
||||
|
||||
enum AIPivot
|
||||
{
|
||||
DONT_PIVOT,
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ bool32 TryClearIllusion(u32 battler, u32 caseID);
|
|||
u32 GetIllusionMonSpecies(u32 battler);
|
||||
struct Pokemon *GetIllusionMonPtr(u32 battler);
|
||||
void ClearIllusionMon(u32 battler);
|
||||
u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pokemon *partnerMon);
|
||||
u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pokemon *partnerMon, u32 battler);
|
||||
bool32 SetIllusionMon(struct Pokemon *mon, u32 battler);
|
||||
bool32 ShouldGetStatBadgeBoost(u16 flagId, u32 battler);
|
||||
enum DamageCategory GetBattleMoveCategory(u32 move);
|
||||
|
|
|
|||
|
|
@ -279,8 +279,8 @@ enum MoveEndEffects
|
|||
MOVEEND_HIT_ESCAPE,
|
||||
MOVEEND_OPPORTUNIST, // Occurs after other stat change items/abilities to try and copy the boosts
|
||||
MOVEEND_PICKPOCKET,
|
||||
MOVEEND_REMOVE_TERRAIN,
|
||||
MOVEEND_WHITE_HERB,
|
||||
MOVEEND_THIRD_MOVE_BLOCK,
|
||||
MOVEEND_CHANGED_ITEMS,
|
||||
MOVEEND_SAME_MOVE_TURNS,
|
||||
MOVEEND_CLEAR_BITS,
|
||||
|
|
|
|||
|
|
@ -331,11 +331,14 @@
|
|||
#define LOCALID_NONE 0
|
||||
#define LOCALID_CAMERA 127
|
||||
#define LOCALID_BERRY_BLENDER_PLAYER_END 240 // This will use 5 (MAX_RFU_PLAYERS) IDs ending at 240, i.e. 236-240
|
||||
#define LOCALID_FOLLOWING_POKEMON 254
|
||||
#define LOCALID_PLAYER 255
|
||||
#define OBJ_EVENT_ID_FOLLOWER 0xFE
|
||||
#define OBJ_EVENT_ID_NPC_FOLLOWER 0xFD
|
||||
#define OBJ_EVENT_ID_FOLLOWER 0xFE
|
||||
#define OBJ_EVENT_ID_NPC_FOLLOWER 0xFD
|
||||
|
||||
// Aliases for old names. "object event id" normally refers to an index into gObjectEvents, which these are not.
|
||||
// Used for link player OWs in CreateLinkPlayerSprite
|
||||
#define OBJ_EVENT_ID_DYNAMIC_BASE 0xF0
|
||||
#define OBJ_EVENT_ID_CAMERA LOCALID_CAMERA
|
||||
#define OBJ_EVENT_ID_PLAYER LOCALID_PLAYER
|
||||
|
||||
|
|
|
|||
|
|
@ -1397,6 +1397,7 @@
|
|||
|
||||
#define FLAG_SYS_USE_FLASH (SYSTEM_FLAGS + 0x28)
|
||||
#define FLAG_SYS_USE_STRENGTH (SYSTEM_FLAGS + 0x29)
|
||||
// Sets abnormal weather on maps that check for it
|
||||
#define FLAG_SYS_WEATHER_CTRL (SYSTEM_FLAGS + 0x2A)
|
||||
#define FLAG_SYS_CYCLING_ROAD (SYSTEM_FLAGS + 0x2B)
|
||||
#define FLAG_SYS_SAFARI_MODE (SYSTEM_FLAGS + 0x2C)
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ u8 GetWalkInPlaceFasterMovementAction(u32);
|
|||
u8 GetWalkInPlaceFastMovementAction(u32);
|
||||
u8 GetWalkInPlaceNormalMovementAction(u32);
|
||||
u8 GetWalkInPlaceSlowMovementAction(u32);
|
||||
u8 GetCollisionAtCoords(struct ObjectEvent *, s16 x, s16 y, u32 dir);
|
||||
u8 GetCollisionAtCoords(struct ObjectEvent *objectEvent, s16 x, s16 y, u32 dir);
|
||||
u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords);
|
||||
void MoveCoords(u8 direction, s16 *x, s16 *y);
|
||||
bool8 ObjectEventIsHeldMovementActive(struct ObjectEvent *objectEvent);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ void StartRevealDisguise(struct ObjectEvent *objectEvent);
|
|||
void StartAshFieldEffect(s16 x, s16 y, u16 metatileId, s16 delay);
|
||||
void SetUpReflection(struct ObjectEvent *objectEvent, struct Sprite *sprite, bool8 stillReflection);
|
||||
void SetUpShadow(struct ObjectEvent *objectEvent);
|
||||
u32 StartFieldEffectForObjectEvent(u8, struct ObjectEvent *objectEvent);
|
||||
u32 StartFieldEffectForObjectEvent(u8 fieldEffectId, struct ObjectEvent *objectEvent);
|
||||
u8 FindTallGrassFieldEffectSpriteId(u8 localId, u8 mapNum, u8 mapGroup, s16 x, s16 y);
|
||||
void UpdateRayquazaSpotlightEffect(struct Sprite *sprite);
|
||||
void UpdateShadowFieldEffect(struct Sprite *sprite);
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ enum MessageCondition
|
|||
#define MATCH_U16(type, value1, value2) {type, {.split = {.hw = value1, .b = value2}}}
|
||||
#define MATCH_U8(type, v1, v2, v3) {type, {.bytes = {v1, v2, v3}}}
|
||||
|
||||
#define MATCH_SPECIES(species) MATCH_U24(MSG_COND_SPECIES, species)
|
||||
#define MATCH_SPECIES(species) MATCH_U16(MSG_COND_SPECIES, species, 0)
|
||||
#define MATCH_NOT_SPECIES(species) MATCH_U16(MSG_COND_SPECIES, species, 1)
|
||||
#define MATCH_TYPES(type1, type2) MATCH_U8(MSG_COND_TYPE, type1, type2, 0)
|
||||
// Checks that follower has *neither* of the two types
|
||||
#define MATCH_NOT_TYPES(type1, type2) MATCH_U8(MSG_COND_TYPE, type1, type2, TYPE_NONE | 1)
|
||||
|
|
@ -118,6 +119,7 @@ enum ConditionalMessage
|
|||
COND_MSG_BURN,
|
||||
COND_MSG_DAY,
|
||||
COND_MSG_NIGHT,
|
||||
COND_MSG_ABNORMAL_WEATHER,
|
||||
COND_MSG_COUNT,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,5 +10,6 @@ s32 MathUtil_Div32(s32 x, s32 y);
|
|||
s16 MathUtil_Inv16(s16 y);
|
||||
s16 MathUtil_Inv16Shift(u8 s, s16 y);
|
||||
s32 MathUtil_Inv32(s32 y);
|
||||
u32 MathUtil_Exponent(u32 x, u32 y);
|
||||
|
||||
#endif // GUARD_MATH_UTIL_H
|
||||
|
|
|
|||
|
|
@ -219,6 +219,17 @@
|
|||
* {
|
||||
* KNOWN_FAILING; // #2596.
|
||||
*
|
||||
* KNOWN_CRASHING
|
||||
* Marks a test as crashing due to a bug. If there is an issue number
|
||||
* associated with the bug it should be included in a comment. If the
|
||||
* test passes the developer will be notified to remove KNOWN_CRASHING.
|
||||
* For example:
|
||||
* TEST("Crashes")
|
||||
* {
|
||||
* KNOWN_CRASHING; // #7255
|
||||
* void (*f)(void) = NULL;
|
||||
* f(); // Crashes!
|
||||
*
|
||||
* PARAMETRIZE
|
||||
* Runs a test multiple times. i will be set to which parameter is
|
||||
* running, and results will contain an entry for each parameter, e.g.:
|
||||
|
|
|
|||
|
|
@ -54,6 +54,14 @@ struct TestRunnerState
|
|||
u32 timeoutSeconds;
|
||||
};
|
||||
|
||||
struct PersistentTestRunnerState
|
||||
{
|
||||
u32 address:28;
|
||||
u32 state:1;
|
||||
u32 expectCrash:1;
|
||||
u32 unused_30:2;
|
||||
};
|
||||
|
||||
extern const u8 gTestRunnerN;
|
||||
extern const u8 gTestRunnerI;
|
||||
extern const char gTestRunnerArgv[256];
|
||||
|
|
@ -71,11 +79,13 @@ extern const struct TestRunner gFunctionTestRunner;
|
|||
extern struct FunctionTestRunnerState *gFunctionTestRunnerState;
|
||||
|
||||
extern struct TestRunnerState gTestRunnerState;
|
||||
extern struct PersistentTestRunnerState gPersistentTestRunnerState;
|
||||
|
||||
void CB2_TestRunner(void);
|
||||
|
||||
void Test_ExpectedResult(enum TestResult);
|
||||
void Test_ExpectLeaks(bool32);
|
||||
void Test_ExpectCrash(bool32);
|
||||
void Test_ExitWithResult(enum TestResult, u32 stopLine, const char *fmt, ...);
|
||||
u32 SourceLine(u32 sourceLineOffset);
|
||||
u32 SourceLineOffset(u32 sourceLine);
|
||||
|
|
@ -220,6 +230,9 @@ static inline struct Benchmark BenchmarkStop(void)
|
|||
#define KNOWN_LEAKING \
|
||||
Test_ExpectLeaks(TRUE)
|
||||
|
||||
#define KNOWN_CRASHING \
|
||||
Test_ExpectCrash(TRUE)
|
||||
|
||||
#define PARAMETRIZE if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter)
|
||||
|
||||
#define PARAMETRIZE_LABEL(f, label) if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter && (Test_MgbaPrintf(":N%s: " f " (%d/%d)", gTestRunnerState.test->name, label, gFunctionTestRunnerState->runParameter + 1, gFunctionTestRunnerState->parameters), 1))
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ gInitialMainCB2 = CB2_TestRunner;
|
|||
|
||||
MEMORY
|
||||
{
|
||||
EWRAM (rwx) : ORIGIN = 0x2000000, LENGTH = 256K
|
||||
IWRAM (rwx) : ORIGIN = 0x3000000, LENGTH = 32K
|
||||
ROM (rx) : ORIGIN = 0x8000000, LENGTH = 32M
|
||||
EWRAM (rwx) : ORIGIN = 0x2000000, LENGTH = 256K
|
||||
IWRAM (rwx) : ORIGIN = 0x3000000, LENGTH = 0x7F00
|
||||
IWRAM_PERSISTENT (rwx) : ORIGIN = 0x3007F00, LENGTH = 0x100
|
||||
ROM (rx) : ORIGIN = 0x8000000, LENGTH = 32M
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
|
|
@ -60,13 +61,11 @@ SECTIONS {
|
|||
/* .persistent starts at 0x3007F00 */
|
||||
/* WARNING: This is the end of the IRQ stack, if there's too
|
||||
* much data it WILL be overwritten. */
|
||||
|
||||
. = 0x03007F00;
|
||||
.iwram.persistent (NOLOAD) :
|
||||
ALIGN(4)
|
||||
{
|
||||
test/*.o(.persistent);
|
||||
} > IWRAM
|
||||
} > IWRAM_PERSISTENT
|
||||
|
||||
/* BEGIN ROM DATA */
|
||||
. = 0x8000000;
|
||||
|
|
@ -136,6 +135,7 @@ SECTIONS {
|
|||
ALIGN(4)
|
||||
{
|
||||
__start_tests = .;
|
||||
test/test_test_runner.o(.tests); /* Sanity checks first. */
|
||||
test/*.o(.tests);
|
||||
__stop_tests = .;
|
||||
test/*.o(.text);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "debug.h"
|
||||
#include "event_data.h"
|
||||
#include "item.h"
|
||||
#include "math_util.h"
|
||||
#include "pokemon.h"
|
||||
#include "random.h"
|
||||
#include "recorded_battle.h"
|
||||
|
|
@ -37,6 +38,7 @@ static u32 ChooseMoveOrAction_Doubles(u32 battler);
|
|||
static inline void BattleAI_DoAIProcessing(struct AiThinkingStruct *aiThink, u32 battlerAtk, u32 battlerDef);
|
||||
static inline void BattleAI_DoAIProcessing_PredictedSwitchin(struct AiThinkingStruct *aiThink, struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef);
|
||||
static bool32 IsPinchBerryItemEffect(enum ItemHoldEffect holdEffect);
|
||||
static void AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef);
|
||||
|
||||
// ewram
|
||||
EWRAM_DATA const u8 *gAIScriptPtr = NULL; // Still used in contests
|
||||
|
|
@ -737,6 +739,9 @@ static u32 ChooseMoveOrAction_Singles(u32 battler)
|
|||
gAiThinkingStruct->aiLogicId++;
|
||||
}
|
||||
|
||||
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_CHECK_VIABILITY)
|
||||
AI_CompareDamagingMoves(battler, opposingBattler);
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
gAiBattleData->finalScore[battler][opposingBattler][i] = gAiThinkingStruct->score[i];
|
||||
|
|
@ -813,6 +818,8 @@ static u32 ChooseMoveOrAction_Doubles(u32 battler)
|
|||
flags >>= (u64)1;
|
||||
gAiThinkingStruct->aiLogicId++;
|
||||
}
|
||||
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_CHECK_VIABILITY)
|
||||
AI_CompareDamagingMoves(battler, gBattlerTarget);
|
||||
|
||||
mostViableMovesScores[0] = gAiThinkingStruct->score[0];
|
||||
mostViableMovesIndices[0] = 0;
|
||||
|
|
@ -2097,7 +2104,11 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
break;
|
||||
case EFFECT_COPYCAT:
|
||||
case EFFECT_MIRROR_MOVE:
|
||||
return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score);
|
||||
if (predictedMove && GetMoveEffect(predictedMove) != GetMoveEffect(move))
|
||||
return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score);
|
||||
else
|
||||
ADJUST_SCORE(-10);
|
||||
break;
|
||||
case EFFECT_FLOWER_SHIELD:
|
||||
if (!IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GRASS)
|
||||
&& !(isDoubleBattle && IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerAtk), TYPE_GRASS)))
|
||||
|
|
@ -2375,7 +2386,10 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
ADJUST_SCORE(-10);
|
||||
break;
|
||||
case EFFECT_NATURE_POWER:
|
||||
return AI_CheckBadMove(battlerAtk, battlerDef, GetNaturePowerMove(battlerAtk), score);
|
||||
predictedMove = GetNaturePowerMove(battlerAtk);
|
||||
if (GetMoveEffect(predictedMove) != GetMoveEffect(move))
|
||||
return AI_CheckBadMove(battlerAtk, battlerDef, GetNaturePowerMove(battlerAtk), score);
|
||||
break;
|
||||
case EFFECT_TAUNT:
|
||||
if (gDisableStructs[battlerDef].tauntTimer > 0
|
||||
|| DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
|
||||
|
|
@ -2502,7 +2516,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
{
|
||||
if (AI_IsSlower(battlerAtk, battlerDef, move))
|
||||
ADJUST_SCORE(-10); // Target is predicted to go first, Me First will fail
|
||||
else
|
||||
else if (GetMoveEffect(predictedMove) != GetMoveEffect(move))
|
||||
return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score);
|
||||
}
|
||||
else
|
||||
|
|
@ -3041,7 +3055,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
}
|
||||
break;
|
||||
case EFFECT_DRAGON_CHEER:
|
||||
if (gBattleMons[battlerAtkPartner].volatiles.dragonCheer
|
||||
if (gBattleMons[battlerAtkPartner].volatiles.dragonCheer
|
||||
|| gBattleMons[battlerAtkPartner].volatiles.focusEnergy
|
||||
|| !HasDamagingMove(battlerAtkPartner))
|
||||
ADJUST_SCORE(-5);
|
||||
|
|
@ -3679,104 +3693,124 @@ static inline bool32 ShouldUseSpreadDamageMove(u32 battlerAtk, u32 move, u32 mov
|
|||
&& noOfHitsToFaintPartner < (friendlyFireThreshold * 2));
|
||||
}
|
||||
|
||||
static s32 AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef, u32 currId)
|
||||
static bool32 ShouldCompareMove(u32 battlerAtk, u32 battlerDef, u32 moveIndex, u16 move)
|
||||
{
|
||||
u32 i;
|
||||
if (IsTargetingPartner(battlerAtk, battlerDef))
|
||||
return FALSE;
|
||||
if (GetMovePower(move) == 0)
|
||||
return FALSE;
|
||||
if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, moveIndex, AI_ATTACKING) == 0)
|
||||
return FALSE;
|
||||
if (gAiThinkingStruct->aiFlags[battlerAtk] & (AI_FLAG_RISKY | AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE) && GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move)
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void AI_CompareDamagingMoves(u32 battlerAtk, u32 battlerDef)
|
||||
{
|
||||
u32 i, currId;
|
||||
u32 tempMoveScores[MAX_MON_MOVES];
|
||||
u32 moveComparisonScores[MAX_MON_MOVES];
|
||||
u32 bestScore = AI_SCORE_DEFAULT;
|
||||
bool32 multipleBestMoves = FALSE;
|
||||
s32 viableMoveScores[MAX_MON_MOVES];
|
||||
s32 bestViableMoveScore;
|
||||
s32 noOfHits[MAX_MON_MOVES];
|
||||
s32 score = 0;
|
||||
s32 leastHits = 1000;
|
||||
u16 *moves = GetMovesArray(battlerAtk);
|
||||
bool8 isTwoTurnNotSemiInvulnerableMove[MAX_MON_MOVES];
|
||||
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (moves[i] != MOVE_NONE && GetMovePower(moves[i]) != 0)
|
||||
{
|
||||
noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i, AI_ATTACKING);
|
||||
if (ShouldUseSpreadDamageMove(battlerAtk,moves[i], i, noOfHits[i]))
|
||||
{
|
||||
noOfHits[i] = -1;
|
||||
viableMoveScores[i] = 0;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = FALSE;
|
||||
}
|
||||
else if (noOfHits[i] < leastHits && noOfHits[i] != 0)
|
||||
{
|
||||
leastHits = noOfHits[i];
|
||||
}
|
||||
viableMoveScores[i] = AI_SCORE_DEFAULT;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = IsTwoTurnNotSemiInvulnerableMove(battlerAtk, moves[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
noOfHits[i] = -1;
|
||||
viableMoveScores[i] = 0;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Priority list:
|
||||
// 1. Less no of hits to ko
|
||||
// 2. Not charging
|
||||
// 3. More accuracy
|
||||
// 4. Better effect
|
||||
|
||||
// Current move requires the least hits to KO. Compare with other moves.
|
||||
if (leastHits == noOfHits[currId])
|
||||
for (currId = 0; currId < MAX_MON_MOVES; currId++)
|
||||
{
|
||||
moveComparisonScores[currId] = 0;
|
||||
if (!ShouldCompareMove(battlerAtk, battlerDef, currId, moves[currId]))
|
||||
continue;
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (i == currId)
|
||||
continue;
|
||||
if (noOfHits[currId] == noOfHits[i])
|
||||
if (moves[i] != MOVE_NONE && GetMovePower(moves[i]) != 0)
|
||||
{
|
||||
multipleBestMoves = TRUE;
|
||||
// We need to make sure it's the current move which is objectively better.
|
||||
if (isTwoTurnNotSemiInvulnerableMove[i] && !isTwoTurnNotSemiInvulnerableMove[currId])
|
||||
viableMoveScores[i] -= 3;
|
||||
else if (!isTwoTurnNotSemiInvulnerableMove[i] && isTwoTurnNotSemiInvulnerableMove[currId])
|
||||
viableMoveScores[currId] -= 3;
|
||||
|
||||
switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i))
|
||||
noOfHits[i] = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, i, AI_ATTACKING);
|
||||
if (ShouldUseSpreadDamageMove(battlerAtk,moves[i], i, noOfHits[i]))
|
||||
{
|
||||
case 1:
|
||||
viableMoveScores[i] -= 2;
|
||||
break;
|
||||
case -1:
|
||||
viableMoveScores[currId] -= 2;
|
||||
break;
|
||||
noOfHits[i] = -1;
|
||||
tempMoveScores[i] = 0;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = FALSE;
|
||||
}
|
||||
switch (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId]))
|
||||
else if (noOfHits[i] < leastHits && noOfHits[i] != 0)
|
||||
{
|
||||
case 1:
|
||||
viableMoveScores[i] -= 1;
|
||||
break;
|
||||
case -1:
|
||||
viableMoveScores[currId] -= 1;
|
||||
break;
|
||||
leastHits = noOfHits[i];
|
||||
}
|
||||
tempMoveScores[i] = AI_SCORE_DEFAULT;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = IsTwoTurnNotSemiInvulnerableMove(battlerAtk, moves[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
noOfHits[i] = -1;
|
||||
tempMoveScores[i] = 0;
|
||||
isTwoTurnNotSemiInvulnerableMove[i] = FALSE;
|
||||
}
|
||||
}
|
||||
// Turns out the current move deals the most dmg compared to the other 3.
|
||||
if (!multipleBestMoves)
|
||||
ADJUST_SCORE(BEST_DAMAGE_MOVE);
|
||||
else
|
||||
|
||||
// Priority list:
|
||||
// 1. Less no of hits to ko
|
||||
// 2. Not charging
|
||||
// 3. More accuracy
|
||||
// 4. Better effect
|
||||
|
||||
// Current move requires the least hits to KO. Compare with other moves.
|
||||
if (leastHits == noOfHits[currId])
|
||||
{
|
||||
bestViableMoveScore = 0;
|
||||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (viableMoveScores[i] > bestViableMoveScore)
|
||||
bestViableMoveScore = viableMoveScores[i];
|
||||
if (i == currId)
|
||||
continue;
|
||||
if (noOfHits[currId] == noOfHits[i])
|
||||
{
|
||||
multipleBestMoves = TRUE;
|
||||
// We need to make sure it's the current move which is objectively better.
|
||||
if (isTwoTurnNotSemiInvulnerableMove[i] && !isTwoTurnNotSemiInvulnerableMove[currId])
|
||||
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_NOT_CHARGING);
|
||||
else if (!isTwoTurnNotSemiInvulnerableMove[i] && isTwoTurnNotSemiInvulnerableMove[currId])
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_NOT_CHARGING);
|
||||
|
||||
switch (CompareMoveAccuracies(battlerAtk, battlerDef, currId, i))
|
||||
{
|
||||
case 1:
|
||||
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_ACCURACY);
|
||||
break;
|
||||
case -1:
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_ACCURACY);
|
||||
break;
|
||||
}
|
||||
switch (AI_WhichMoveBetter(moves[currId], moves[i], battlerAtk, battlerDef, noOfHits[currId]))
|
||||
{
|
||||
case 1:
|
||||
tempMoveScores[currId] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_EFFECT);
|
||||
break;
|
||||
case -1:
|
||||
tempMoveScores[i] += MathUtil_Exponent(MAX_MON_MOVES, PRIORITY_EFFECT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Unless a better move was found increase score of current move
|
||||
if (viableMoveScores[currId] == bestViableMoveScore)
|
||||
ADJUST_SCORE(BEST_DAMAGE_MOVE);
|
||||
// Turns out the current move deals the most dmg compared to the other 3.
|
||||
if (!multipleBestMoves)
|
||||
moveComparisonScores[currId] = UINT32_MAX;
|
||||
else
|
||||
moveComparisonScores[currId] = tempMoveScores[currId];
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
// Find highest comparison score
|
||||
for (int i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (moveComparisonScores[i] > bestScore)
|
||||
bestScore = moveComparisonScores[i];
|
||||
}
|
||||
// Increase score for corresponding move(s), accomodating ties
|
||||
for (int i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
if (moveComparisonScores[i] == bestScore)
|
||||
gAiThinkingStruct->score[i] += BEST_DAMAGE_MOVE;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 AI_CalcHoldEffectMoveScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
||||
|
|
@ -3891,7 +3925,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
|||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
break;
|
||||
case EFFECT_MIRROR_MOVE:
|
||||
if (predictedMove != MOVE_NONE)
|
||||
if (predictedMove && GetMoveEffect(predictedMove) != GetMoveEffect(move))
|
||||
return AI_CheckViability(battlerAtk, battlerDef, predictedMove, score);
|
||||
break;
|
||||
case EFFECT_ATTACK_UP:
|
||||
|
|
@ -4098,7 +4132,8 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
|||
case EFFECT_MIMIC:
|
||||
if (AI_IsFaster(battlerAtk, battlerDef, move))
|
||||
{
|
||||
if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF)
|
||||
if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF
|
||||
&& (GetMoveEffect(gLastMoves[battlerDef]) != GetMoveEffect(move)))
|
||||
return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score);
|
||||
}
|
||||
break;
|
||||
|
|
@ -4435,7 +4470,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
|||
ADJUST_SCORE(IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF));
|
||||
break;
|
||||
case EFFECT_SWAGGER:
|
||||
if (HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_FOUL_PLAY)
|
||||
if (HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_FOUL_PLAY)
|
||||
|| HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_PSYCH_UP)
|
||||
|| HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_SPECTRAL_THIEF))
|
||||
ADJUST_SCORE(DECENT_EFFECT);
|
||||
|
|
@ -4804,7 +4839,7 @@ case EFFECT_GUARD_SPLIT:
|
|||
u32 atkSpDefense = gBattleMons[battlerAtk].spDefense;
|
||||
u32 defSpDefense = gBattleMons[battlerDef].spDefense;
|
||||
|
||||
// It's actually * 100 and / 2;
|
||||
// It's actually * 100 and / 2;
|
||||
u32 newDefense = (atkDefense + defDefense) * 50;
|
||||
u32 newSpDefense = (atkSpDefense + defSpDefense) * 50;
|
||||
|
||||
|
|
@ -4941,7 +4976,7 @@ case EFFECT_GUARD_SPLIT:
|
|||
break;
|
||||
case EFFECT_SOAK:
|
||||
if (HasMoveWithType(battlerAtk, TYPE_ELECTRIC) || HasMoveWithType(battlerAtk, TYPE_GRASS)
|
||||
|| HasMoveWithType(BATTLE_PARTNER(battlerAtk), TYPE_ELECTRIC) || HasMoveWithType(BATTLE_PARTNER(battlerAtk), TYPE_GRASS)
|
||||
|| HasMoveWithType(BATTLE_PARTNER(battlerAtk), TYPE_ELECTRIC) || HasMoveWithType(BATTLE_PARTNER(battlerAtk), TYPE_GRASS)
|
||||
|| (HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_SUPER_EFFECTIVE_ON_ARG) && GetMoveArgType(move) == TYPE_WATER) )
|
||||
ADJUST_SCORE(DECENT_EFFECT); // Get some super effective moves
|
||||
break;
|
||||
|
|
@ -5327,8 +5362,6 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
|
|||
if (gAiThinkingStruct->aiFlags[battlerAtk] & (AI_FLAG_RISKY | AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE)
|
||||
&& GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move)
|
||||
ADJUST_SCORE(BEST_DAMAGE_MOVE);
|
||||
else
|
||||
ADJUST_SCORE(AI_CompareDamagingMoves(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -160,10 +160,12 @@ static inline bool32 SetSwitchinAndSwitch(u32 battler, u32 switchinId)
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static bool32 AI_DoesChoiceItemBlockMove(u32 battler, u32 move)
|
||||
static bool32 AI_DoesChoiceEffectBlockMove(u32 battler, u32 move)
|
||||
{
|
||||
// Choice locked into something else
|
||||
if (gAiLogicData->lastUsedMove[battler] != MOVE_NONE && gAiLogicData->lastUsedMove[battler] != move && HOLD_EFFECT_CHOICE(GetBattlerHoldEffect(battler, FALSE)) && IsBattlerItemEnabled(battler))
|
||||
if (gAiLogicData->lastUsedMove[battler] != MOVE_NONE && gAiLogicData->lastUsedMove[battler] != move
|
||||
&& ((HOLD_EFFECT_CHOICE(GetBattlerHoldEffect(battler, FALSE)) && IsBattlerItemEnabled(battler))
|
||||
|| gBattleMons[battler].ability == ABILITY_GORILLA_TACTICS))
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -226,12 +228,12 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler)
|
|||
if (!IsBattleMoveStatus(aiMove))
|
||||
{
|
||||
// Check if mon has a super effective move
|
||||
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0) && !AI_DoesChoiceItemBlockMove(battler, aiMove))
|
||||
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) >= UQ_4_12(2.0) && !AI_DoesChoiceEffectBlockMove(battler, aiMove))
|
||||
hasSuperEffectiveMove = TRUE;
|
||||
|
||||
// Get maximum damage mon can deal
|
||||
damageDealt = AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, gAiLogicData);
|
||||
if (damageDealt > maxDamageDealt && !AI_DoesChoiceItemBlockMove(battler, aiMove))
|
||||
if (damageDealt > maxDamageDealt && !AI_DoesChoiceEffectBlockMove(battler, aiMove))
|
||||
{
|
||||
maxDamageDealt = damageDealt;
|
||||
aiBestMove = aiMove;
|
||||
|
|
@ -398,6 +400,8 @@ static bool32 ShouldSwitchIfAllMovesBad(u32 battler)
|
|||
{
|
||||
aiMove = gBattleMons[battler].moves[moveIndex];
|
||||
if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) > UQ_4_12(0.0) && aiMove != MOVE_NONE
|
||||
&& !CanAbilityAbsorbMove(battler, opposingBattler, gBattleMons[opposingBattler].ability, aiMove, GetBattleMoveType(aiMove), AI_CHECK)
|
||||
&& !CanAbilityBlockMove(battler, opposingBattler, gBattleMons[battler].ability, gBattleMons[opposingBattler].ability, aiMove, AI_CHECK)
|
||||
&& (!ALL_MOVES_BAD_STATUS_MOVES_BAD || gMovesInfo[aiMove].power != 0)) // If using ALL_MOVES_BAD_STATUS_MOVES_BAD, then need power to be non-zero
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -482,7 +486,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler)
|
|||
// Only check damage if it's a damaging move
|
||||
if (!IsBattleMoveStatus(aiMove))
|
||||
{
|
||||
if (!AI_DoesChoiceItemBlockMove(battler, aiMove) && AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, gAiLogicData) > gBattleMons[opposingBattler].hp)
|
||||
if (!AI_DoesChoiceEffectBlockMove(battler, aiMove) && AI_GetDamage(battler, opposingBattler, i, AI_ATTACKING, gAiLogicData) > gBattleMons[opposingBattler].hp)
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
|
@ -801,7 +805,7 @@ static bool32 CanUseSuperEffectiveMoveAgainstOpponents(u32 battler)
|
|||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
move = gBattleMons[battler].moves[i];
|
||||
if (move == MOVE_NONE || AI_DoesChoiceItemBlockMove(battler, move))
|
||||
if (move == MOVE_NONE || AI_DoesChoiceEffectBlockMove(battler, move))
|
||||
continue;
|
||||
|
||||
if (AI_GetMoveEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0))
|
||||
|
|
@ -820,7 +824,7 @@ static bool32 CanUseSuperEffectiveMoveAgainstOpponents(u32 battler)
|
|||
for (i = 0; i < MAX_MON_MOVES; i++)
|
||||
{
|
||||
move = gBattleMons[battler].moves[i];
|
||||
if (move == MOVE_NONE || AI_DoesChoiceItemBlockMove(battler, move))
|
||||
if (move == MOVE_NONE || AI_DoesChoiceEffectBlockMove(battler, move))
|
||||
continue;
|
||||
|
||||
if (AI_GetMoveEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0))
|
||||
|
|
|
|||
|
|
@ -6863,6 +6863,25 @@ static void TrySwapWishBattlerIds(u32 battlerAtk, u32 battlerPartner)
|
|||
SWAP(gWishFutureKnock.wishPartyId[battlerAtk], gWishFutureKnock.wishPartyId[battlerPartner], temp);
|
||||
}
|
||||
|
||||
static void SwapBattlerMoveData(u32 battler1, u32 battler2)
|
||||
{
|
||||
u32 temp;
|
||||
SWAP(gBattleStruct->chosenMovePositions[battler1], gBattleStruct->chosenMovePositions[battler2], temp);
|
||||
SWAP(gChosenMoveByBattler[battler1], gChosenMoveByBattler[battler2], temp);
|
||||
SWAP(gBattleStruct->moveTarget[battler1], gBattleStruct->moveTarget[battler2], temp);
|
||||
SWAP(gMoveSelectionCursor[battler1], gMoveSelectionCursor[battler2], temp);
|
||||
SWAP(gLockedMoves[battler1], gLockedMoves[battler2], temp);
|
||||
|
||||
// update last moves
|
||||
SWAP(gLastPrintedMoves[battler1], gLastPrintedMoves[battler2], temp);
|
||||
SWAP(gLastMoves[battler1], gLastMoves[battler2], temp);
|
||||
SWAP(gLastLandedMoves[battler1], gLastLandedMoves[battler2], temp);
|
||||
SWAP(gLastHitByType[battler1], gLastHitByType[battler2], temp);
|
||||
SWAP(gLastUsedMoveType[battler1], gLastUsedMoveType[battler2], temp);
|
||||
SWAP(gLastResultingMoves[battler1], gLastResultingMoves[battler2], temp);
|
||||
SWAP(gLastHitBy[battler1], gLastHitBy[battler2], temp);
|
||||
}
|
||||
|
||||
static void AnimTask_AllySwitchDataSwap(u8 taskId)
|
||||
{
|
||||
s32 i, j;
|
||||
|
|
@ -6889,11 +6908,9 @@ static void AnimTask_AllySwitchDataSwap(u8 taskId)
|
|||
SWAP(gTransformedShininess[battlerAtk], gTransformedShininess[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(gLockedMoves[battlerAtk], gLockedMoves[battlerPartner], temp);
|
||||
SWAP(gBattleStruct->moveTarget[battlerAtk], gBattleStruct->moveTarget[battlerPartner], temp);
|
||||
SWAP(gMoveSelectionCursor[battlerAtk], gMoveSelectionCursor[battlerPartner], temp);
|
||||
|
||||
SwapBattlerMoveData(battlerAtk, battlerPartner);
|
||||
|
||||
// Swap turn order, so that all the battlers take action
|
||||
SWAP(gChosenActionByBattler[battlerAtk], gChosenActionByBattler[battlerPartner], temp);
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
|
|
|
|||
|
|
@ -3198,7 +3198,7 @@ static void IllusionNickHack(u32 battler, u32 partyId, u8 *dst)
|
|||
else
|
||||
partnerMon = mon;
|
||||
|
||||
id = GetIllusionMonPartyId(gEnemyParty, mon, partnerMon);
|
||||
id = GetIllusionMonPartyId(gEnemyParty, mon, partnerMon, battler);
|
||||
}
|
||||
|
||||
if (id != PARTY_SIZE)
|
||||
|
|
|
|||
|
|
@ -347,6 +347,7 @@ static void TryUpdateEvolutionTracker(u32 evolutionCondition, u32 upAmount, u16
|
|||
static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr, u16 move);
|
||||
static void ResetValuesForCalledMove(void);
|
||||
static void TryRestoreDamageAfterCheekPouch(u32 battler);
|
||||
static bool32 TrySymbiosis(u32 battler, u32 itemId, bool32 moveEnd);
|
||||
|
||||
static void Cmd_attackcanceler(void);
|
||||
static void Cmd_accuracycheck(void);
|
||||
|
|
@ -6198,6 +6199,24 @@ static void Cmd_moveend(void)
|
|||
effect = TRUE;
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_SYMBIOSIS:
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
if ((gSpecialStatuses[i].berryReduced
|
||||
|| (B_SYMBIOSIS_GEMS >= GEN_7 && gSpecialStatuses[i].gemBoost))
|
||||
&& TryTriggerSymbiosis(i, BATTLE_PARTNER(i)))
|
||||
{
|
||||
BestowItem(BATTLE_PARTNER(i), i);
|
||||
gLastUsedAbility = gBattleMons[BATTLE_PARTNER(i)].ability;
|
||||
gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(i);
|
||||
gBattlerAttacker = i;
|
||||
BattleScriptPushCursor();
|
||||
gBattlescriptCurrInstr = BattleScript_SymbiosisActivates;
|
||||
effect = TRUE;
|
||||
}
|
||||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_FIRST_MOVE_BLOCK:
|
||||
if ((gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_1ST_HIT && IsBattlerAlive(gBattlerTarget))
|
||||
|| gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT
|
||||
|
|
@ -6269,12 +6288,6 @@ static void Cmd_moveend(void)
|
|||
else
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_KINGSROCK: // King's rock
|
||||
// These effects will occur at each hit in a multi-strike move
|
||||
if (ItemBattleEffects(ITEMEFFECT_KINGSROCK, 0))
|
||||
effect = TRUE;
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_ATTACKER_INVISIBLE: // make attacker sprite invisible
|
||||
if (gStatuses3[gBattlerAttacker] & (STATUS3_SEMI_INVULNERABLE)
|
||||
&& gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION))
|
||||
|
|
@ -6312,6 +6325,12 @@ static void Cmd_moveend(void)
|
|||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_KINGSROCK: // King's rock
|
||||
// These effects will occur at each hit in a multi-strike move
|
||||
if (ItemBattleEffects(ITEMEFFECT_KINGSROCK, 0))
|
||||
effect = TRUE;
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_SUBSTITUTE:
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
|
|
@ -6446,6 +6465,35 @@ static void Cmd_moveend(void)
|
|||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_DEFROST:
|
||||
if (gBattleMons[gBattlerTarget].status1 & STATUS1_FREEZE
|
||||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||||
&& IsBattlerAlive(gBattlerTarget)
|
||||
&& gBattlerAttacker != gBattlerTarget
|
||||
&& (moveType == TYPE_FIRE || CanBurnHitThaw(gCurrentMove))
|
||||
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT))
|
||||
{
|
||||
gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FREEZE;
|
||||
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1);
|
||||
MarkBattlerForControllerExec(gBattlerTarget);
|
||||
BattleScriptCall(BattleScript_DefrostedViaFireMove);
|
||||
effect = TRUE;
|
||||
}
|
||||
if (gBattleMons[gBattlerTarget].status1 & STATUS1_FROSTBITE
|
||||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||||
&& IsBattlerAlive(gBattlerTarget)
|
||||
&& gBattlerAttacker != gBattlerTarget
|
||||
&& MoveThawsUser(originallyUsedMove)
|
||||
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT))
|
||||
{
|
||||
gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FROSTBITE;
|
||||
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1);
|
||||
MarkBattlerForControllerExec(gBattlerTarget);
|
||||
BattleScriptCall(BattleScript_FrostbiteHealedViaFireMove);
|
||||
effect = TRUE;
|
||||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_NEXT_TARGET: // For moves hitting two opposing Pokemon.
|
||||
{
|
||||
u16 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
|
||||
|
|
@ -6568,33 +6616,6 @@ static void Cmd_moveend(void)
|
|||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
}
|
||||
case MOVEEND_DEFROST:
|
||||
if (gBattleMons[gBattlerTarget].status1 & STATUS1_FREEZE
|
||||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||||
&& IsBattlerAlive(gBattlerTarget)
|
||||
&& gBattlerAttacker != gBattlerTarget
|
||||
&& (moveType == TYPE_FIRE || CanBurnHitThaw(gCurrentMove)))
|
||||
{
|
||||
gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FREEZE;
|
||||
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1);
|
||||
MarkBattlerForControllerExec(gBattlerTarget);
|
||||
BattleScriptCall(BattleScript_DefrostedViaFireMove);
|
||||
effect = TRUE;
|
||||
}
|
||||
if (gBattleMons[gBattlerTarget].status1 & STATUS1_FROSTBITE
|
||||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||||
&& IsBattlerAlive(gBattlerTarget)
|
||||
&& gBattlerAttacker != gBattlerTarget
|
||||
&& MoveThawsUser(originallyUsedMove))
|
||||
{
|
||||
gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FROSTBITE;
|
||||
BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1);
|
||||
MarkBattlerForControllerExec(gBattlerTarget);
|
||||
BattleScriptCall(BattleScript_FrostbiteHealedViaFireMove);
|
||||
effect = TRUE;
|
||||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_SECOND_MOVE_BLOCK:
|
||||
if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||||
{
|
||||
|
|
@ -6696,6 +6717,57 @@ static void Cmd_moveend(void)
|
|||
else
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_RED_CARD:
|
||||
{
|
||||
u32 redCardBattlers = 0, i;
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
if (i == gBattlerAttacker)
|
||||
continue;
|
||||
if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_RED_CARD)
|
||||
redCardBattlers |= (1u << i);
|
||||
}
|
||||
if (redCardBattlers && IsBattlerAlive(gBattlerAttacker))
|
||||
{
|
||||
// Since we check if battler was damaged, we don't need to check move result.
|
||||
// In fact, doing so actually prevents multi-target moves from activating red card properly
|
||||
u8 battlers[4] = {0, 1, 2, 3};
|
||||
SortBattlersBySpeed(battlers, FALSE);
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
u32 battler = battlers[i];
|
||||
// Search for fastest hit pokemon with a red card
|
||||
// Attacker is the one to be switched out, battler is one with red card
|
||||
if (redCardBattlers & (1u << battler)
|
||||
&& IsBattlerAlive(battler)
|
||||
&& !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove)
|
||||
&& IsBattlerTurnDamaged(battler)
|
||||
&& CanBattlerSwitch(gBattlerAttacker)
|
||||
&& !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battler)))
|
||||
{
|
||||
effect = TRUE;
|
||||
gLastUsedItem = gBattleMons[battler].item;
|
||||
SaveBattlerTarget(battler); // save battler with red card
|
||||
SaveBattlerAttacker(gBattlerAttacker);
|
||||
gBattleScripting.battler = battler;
|
||||
gEffectBattler = gBattlerAttacker;
|
||||
BattleScriptPushCursor();
|
||||
if (gBattleStruct->commanderActive[gBattlerAttacker] != SPECIES_NONE
|
||||
|| GetBattlerAbility(gBattlerAttacker) == ABILITY_GUARD_DOG
|
||||
|| GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)
|
||||
gBattlescriptCurrInstr = BattleScript_RedCardActivationNoSwitch;
|
||||
else
|
||||
gBattlescriptCurrInstr = BattleScript_RedCardActivates;
|
||||
break; // Only fastest red card activates
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (effect)
|
||||
gBattleScripting.moveendState = MOVEEND_OPPORTUNIST;
|
||||
else
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_EJECT_BUTTON:
|
||||
{
|
||||
// Because sorting the battlers by speed takes lots of cycles, it's better to just check if any of the battlers has the Eject items.
|
||||
|
|
@ -6745,6 +6817,71 @@ static void Cmd_moveend(void)
|
|||
else
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_LIFEORB_SHELLBELL:
|
||||
if (ItemBattleEffects(ITEMEFFECT_LIFEORB_SHELLBELL, 0))
|
||||
effect = TRUE;
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_FORM_CHANGE:
|
||||
if (TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_AFTER_MOVE))
|
||||
{
|
||||
effect = TRUE;
|
||||
BattleScriptCall(BattleScript_AttackerFormChangeMoveEffect);
|
||||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_EMERGENCY_EXIT: // Special case, because moves hitting multiple opponents stop after switching out
|
||||
{
|
||||
// Because sorting the battlers by speed takes lots of cycles,
|
||||
// we check if EE can be activated and cound how many.
|
||||
u32 numEmergencyExitBattlers = 0;
|
||||
u32 emergencyExitBattlers = 0;
|
||||
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
if (EmergencyExitCanBeTriggered(i))
|
||||
{
|
||||
emergencyExitBattlers |= 1u << i;
|
||||
numEmergencyExitBattlers++;
|
||||
}
|
||||
}
|
||||
|
||||
if (numEmergencyExitBattlers == 0)
|
||||
{
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < gBattlersCount; i++)
|
||||
gProtectStructs[i].tryEjectPack = FALSE;
|
||||
|
||||
u8 battlers[4] = {0, 1, 2, 3};
|
||||
if (numEmergencyExitBattlers > 1)
|
||||
SortBattlersBySpeed(battlers, FALSE);
|
||||
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
u32 battler = battlers[i];
|
||||
|
||||
if (!(emergencyExitBattlers & 1u << battler))
|
||||
continue;
|
||||
|
||||
effect = TRUE;
|
||||
gBattleScripting.battler = battler;
|
||||
|
||||
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER || IsOnPlayerSide(battler))
|
||||
BattleScriptCall(BattleScript_EmergencyExit);
|
||||
else
|
||||
BattleScriptCall(BattleScript_EmergencyExitWild);
|
||||
|
||||
break; // Only the fastest Emergency Exit / Wimp Out activates
|
||||
}
|
||||
}
|
||||
if (effect)
|
||||
gBattleScripting.moveendState = MOVEEND_OPPORTUNIST;
|
||||
else
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_EJECT_PACK:
|
||||
{
|
||||
// Because sorting the battlers by speed takes lots of cycles, it's better to just check if any of the battlers has the Eject items.
|
||||
|
|
@ -6798,80 +6935,24 @@ static void Cmd_moveend(void)
|
|||
else
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_WHITE_HERB:
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
case MOVEEND_HIT_ESCAPE:
|
||||
if (moveEffect == EFFECT_HIT_ESCAPE
|
||||
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||||
&& IsBattlerAlive(gBattlerAttacker)
|
||||
&& !NoAliveMonsForBattlerSide(gBattlerTarget))
|
||||
{
|
||||
if (!IsBattlerAlive(i))
|
||||
continue;
|
||||
|
||||
if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_WHITE_HERB
|
||||
&& RestoreWhiteHerbStats(i))
|
||||
{
|
||||
BattleScriptCall(BattleScript_WhiteHerbRet);
|
||||
effect = TRUE;
|
||||
break;
|
||||
}
|
||||
effect = TRUE;
|
||||
BattleScriptCall(BattleScript_EffectHitEscape);
|
||||
}
|
||||
if (!effect)
|
||||
gBattleScripting.moveendState++;
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_RED_CARD:
|
||||
{
|
||||
u32 redCardBattlers = 0, i;
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
if (i == gBattlerAttacker)
|
||||
continue;
|
||||
if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_RED_CARD)
|
||||
redCardBattlers |= (1u << i);
|
||||
}
|
||||
if (redCardBattlers && IsBattlerAlive(gBattlerAttacker))
|
||||
{
|
||||
// Since we check if battler was damaged, we don't need to check move result.
|
||||
// In fact, doing so actually prevents multi-target moves from activating red card properly
|
||||
u8 battlers[4] = {0, 1, 2, 3};
|
||||
SortBattlersBySpeed(battlers, FALSE);
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
u32 battler = battlers[i];
|
||||
// Search for fastest hit pokemon with a red card
|
||||
// Attacker is the one to be switched out, battler is one with red card
|
||||
if (redCardBattlers & (1u << battler)
|
||||
&& IsBattlerAlive(battler)
|
||||
&& !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove)
|
||||
&& IsBattlerTurnDamaged(battler)
|
||||
&& CanBattlerSwitch(gBattlerAttacker)
|
||||
&& !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battler)))
|
||||
{
|
||||
for (u32 i = 0; i < gBattlersCount; i++)
|
||||
gProtectStructs[i].tryEjectPack = FALSE;
|
||||
effect = TRUE;
|
||||
gLastUsedItem = gBattleMons[battler].item;
|
||||
SaveBattlerTarget(battler); // save battler with red card
|
||||
SaveBattlerAttacker(gBattlerAttacker);
|
||||
gBattleScripting.battler = battler;
|
||||
gEffectBattler = gBattlerAttacker;
|
||||
if (gBattleStruct->commanderActive[gBattlerAttacker] != SPECIES_NONE
|
||||
|| GetBattlerAbility(gBattlerAttacker) == ABILITY_GUARD_DOG
|
||||
|| GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)
|
||||
BattleScriptCall(BattleScript_RedCardActivationNoSwitch);
|
||||
else
|
||||
BattleScriptCall(BattleScript_RedCardActivates);
|
||||
break; // Only fastest red card activates
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (effect)
|
||||
gBattleScripting.moveendState = MOVEEND_OPPORTUNIST;
|
||||
case MOVEEND_OPPORTUNIST:
|
||||
if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0))
|
||||
effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers
|
||||
else
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_LIFEORB_SHELLBELL:
|
||||
if (ItemBattleEffects(ITEMEFFECT_LIFEORB_SHELLBELL, 0))
|
||||
effect = TRUE;
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_PICKPOCKET:
|
||||
if (IsBattlerAlive(gBattlerAttacker)
|
||||
&& gBattleMons[gBattlerAttacker].item != ITEM_NONE // Attacker must be holding an item
|
||||
|
|
@ -6907,126 +6988,66 @@ static void Cmd_moveend(void)
|
|||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_FORM_CHANGE:
|
||||
if (TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_AFTER_MOVE))
|
||||
{
|
||||
effect = TRUE;
|
||||
BattleScriptCall(BattleScript_AttackerFormChangeMoveEffect);
|
||||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_EMERGENCY_EXIT: // Special case, because moves hitting multiple opponents stop after switching out
|
||||
{
|
||||
// Because sorting the battlers by speed takes lots of cycles,
|
||||
// we check if EE can be activated and cound how many.
|
||||
u32 numEmergencyExitBattlers = 0;
|
||||
u32 emergencyExitBattlers = 0;
|
||||
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
if (EmergencyExitCanBeTriggered(i))
|
||||
{
|
||||
emergencyExitBattlers |= 1u << i;
|
||||
numEmergencyExitBattlers++;
|
||||
}
|
||||
}
|
||||
|
||||
if (numEmergencyExitBattlers == 0)
|
||||
{
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < gBattlersCount; i++)
|
||||
gProtectStructs[i].tryEjectPack = FALSE;
|
||||
|
||||
u8 battlers[4] = {0, 1, 2, 3};
|
||||
if (numEmergencyExitBattlers > 1)
|
||||
SortBattlersBySpeed(battlers, FALSE);
|
||||
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
u32 battler = battlers[i];
|
||||
|
||||
if (!(emergencyExitBattlers & 1u << battler))
|
||||
continue;
|
||||
|
||||
effect = TRUE;
|
||||
gBattleScripting.battler = battler;
|
||||
|
||||
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER || IsOnPlayerSide(battler))
|
||||
BattleScriptCall(BattleScript_EmergencyExit);
|
||||
else
|
||||
BattleScriptCall(BattleScript_EmergencyExitWild);
|
||||
|
||||
break; // Only the fastest Emergency Exit / Wimp Out activates
|
||||
}
|
||||
}
|
||||
if (effect)
|
||||
gBattleScripting.moveendState = MOVEEND_OPPORTUNIST;
|
||||
else
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_HIT_ESCAPE:
|
||||
if (moveEffect == EFFECT_HIT_ESCAPE
|
||||
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||||
&& IsBattlerTurnDamaged(gBattlerTarget)
|
||||
&& IsBattlerAlive(gBattlerAttacker)
|
||||
&& !NoAliveMonsForBattlerSide(gBattlerTarget))
|
||||
{
|
||||
effect = TRUE;
|
||||
BattleScriptCall(BattleScript_EffectHitEscape);
|
||||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_OPPORTUNIST:
|
||||
if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0))
|
||||
effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers
|
||||
else
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_REMOVE_TERRAIN:
|
||||
if (GetMoveEffect(gChosenMove) == EFFECT_STEEL_ROLLER // Steel Roller has to check the chosen move, Otherwise it would fail in certain cases
|
||||
&& IsBattlerTurnDamaged(gBattlerTarget))
|
||||
{
|
||||
BattleScriptPushCursor();
|
||||
gBattlescriptCurrInstr = BattleScript_RemoveTerrain;
|
||||
effect = TRUE;
|
||||
}
|
||||
else if (moveEffect == EFFECT_ICE_SPINNER
|
||||
&& IsBattlerAlive(gBattlerAttacker)
|
||||
&& IsBattlerTurnDamaged(gBattlerTarget))
|
||||
{
|
||||
BattleScriptPushCursor();
|
||||
gBattlescriptCurrInstr = BattleScript_RemoveTerrain;
|
||||
effect = TRUE;
|
||||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_SYMBIOSIS:
|
||||
case MOVEEND_WHITE_HERB:
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
if ((gSpecialStatuses[i].berryReduced
|
||||
|| (B_SYMBIOSIS_GEMS >= GEN_7 && gSpecialStatuses[i].gemBoost))
|
||||
&& TryTriggerSymbiosis(i, BATTLE_PARTNER(i)))
|
||||
if (!IsBattlerAlive(i))
|
||||
continue;
|
||||
|
||||
if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_WHITE_HERB
|
||||
&& RestoreWhiteHerbStats(i))
|
||||
{
|
||||
BestowItem(BATTLE_PARTNER(i), i);
|
||||
gLastUsedAbility = gBattleMons[BATTLE_PARTNER(i)].ability;
|
||||
gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(i);
|
||||
gBattlerAttacker = i;
|
||||
BattleScriptCall(BattleScript_SymbiosisActivates);
|
||||
BattleScriptCall(BattleScript_WhiteHerbRet);
|
||||
effect = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
gBattleScripting.moveendState++;
|
||||
if (!effect)
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_SAME_MOVE_TURNS:
|
||||
if (gCurrentMove != gLastResultingMoves[gBattlerAttacker]
|
||||
|| gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT
|
||||
|| gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||||
gBattleStruct->sameMoveTurns[gBattlerAttacker] = 0;
|
||||
else if (gCurrentMove == gLastResultingMoves[gBattlerAttacker] && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT)
|
||||
gBattleStruct->sameMoveTurns[gBattlerAttacker]++;
|
||||
case MOVEEND_THIRD_MOVE_BLOCK:
|
||||
// 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_ICE_SPINNER:
|
||||
if (IsBattlerAlive(gBattlerAttacker) && IsBattlerTurnDamaged(gBattlerTarget))
|
||||
{
|
||||
BattleScriptCall(BattleScript_RemoveTerrain);
|
||||
effect = TRUE;
|
||||
}
|
||||
break;
|
||||
case EFFECT_NATURAL_GIFT:
|
||||
if (!(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) && GetItemPocket(gBattleMons[gBattlerAttacker].item) == POCKET_BERRIES)
|
||||
{
|
||||
u32 item = gBattleMons[gBattlerAttacker].item;
|
||||
gBattleMons[gBattlerAttacker].item = ITEM_NONE;
|
||||
gBattleStruct->battlerState[gBattlerAttacker].canPickupItem = TRUE;
|
||||
gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerAttacker]][GetBattlerSide(gBattlerAttacker)] = item;
|
||||
CheckSetUnburden(gBattlerAttacker);
|
||||
BtlController_EmitSetMonData(
|
||||
gBattlerAttacker,
|
||||
B_COMM_TO_CONTROLLER,
|
||||
REQUEST_HELDITEM_BATTLE,
|
||||
0,
|
||||
sizeof(gBattleMons[gBattlerAttacker].item),
|
||||
&gBattleMons[gBattlerAttacker].item);
|
||||
MarkBattlerForControllerExec(gBattlerAttacker);
|
||||
ClearBattlerItemEffectHistory(gBattlerAttacker);
|
||||
|
||||
if (!TrySymbiosis(gBattlerAttacker, item, TRUE))
|
||||
effect = TRUE;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_CHANGED_ITEMS:
|
||||
|
|
@ -7040,6 +7061,15 @@ static void Cmd_moveend(void)
|
|||
}
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_SAME_MOVE_TURNS:
|
||||
if (gCurrentMove != gLastResultingMoves[gBattlerAttacker]
|
||||
|| gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT
|
||||
|| gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
|
||||
gBattleStruct->sameMoveTurns[gBattlerAttacker] = 0;
|
||||
else if (gCurrentMove == gLastResultingMoves[gBattlerAttacker] && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT)
|
||||
gBattleStruct->sameMoveTurns[gBattlerAttacker]++;
|
||||
gBattleScripting.moveendState++;
|
||||
break;
|
||||
case MOVEEND_CLEAR_BITS: // Clear/Set bits for things like using a move for all targets and all hits.
|
||||
if (gSpecialStatuses[gBattlerAttacker].instructedChosenTarget)
|
||||
gBattleStruct->moveTarget[gBattlerAttacker] = gSpecialStatuses[gBattlerAttacker].instructedChosenTarget & 0x3;
|
||||
|
|
@ -8792,7 +8822,7 @@ static void BestowItem(u32 battlerAtk, u32 battlerDef)
|
|||
}
|
||||
|
||||
// Called by Cmd_removeitem. itemId represents the item that was removed, not being given.
|
||||
static bool32 TrySymbiosis(u32 battler, u32 itemId)
|
||||
static bool32 TrySymbiosis(u32 battler, u32 itemId, bool32 moveEnd)
|
||||
{
|
||||
if (!gBattleStruct->itemLost[B_SIDE_PLAYER][gBattlerPartyIndexes[battler]].stolen
|
||||
&& gBattleStruct->changedItems[battler] == ITEM_NONE
|
||||
|
|
@ -8806,7 +8836,10 @@ static bool32 TrySymbiosis(u32 battler, u32 itemId)
|
|||
BestowItem(BATTLE_PARTNER(battler), battler);
|
||||
gLastUsedAbility = gBattleMons[BATTLE_PARTNER(battler)].ability;
|
||||
gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(battler);
|
||||
BattleScriptPush(gBattlescriptCurrInstr + 2);
|
||||
if (moveEnd)
|
||||
BattleScriptPushCursor();
|
||||
else
|
||||
BattleScriptPush(gBattlescriptCurrInstr + 2);
|
||||
gBattlescriptCurrInstr = BattleScript_SymbiosisActivates;
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -8844,7 +8877,7 @@ static void Cmd_removeitem(void)
|
|||
MarkBattlerForControllerExec(battler);
|
||||
|
||||
ClearBattlerItemEffectHistory(battler);
|
||||
if (!TryCheekPouch(battler, itemId) && !TrySymbiosis(battler, itemId))
|
||||
if (!TryCheekPouch(battler, itemId) && !TrySymbiosis(battler, itemId, FALSE))
|
||||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||||
}
|
||||
|
||||
|
|
@ -14233,6 +14266,8 @@ static void Cmd_selectfirstvalidtarget(void)
|
|||
if (IsBattlerAlive(gBattlerTarget))
|
||||
break;
|
||||
}
|
||||
if (gBattlerTarget >= gBattlersCount)
|
||||
gBattlerTarget = 0;
|
||||
gBattlescriptCurrInstr = cmd->nextInstr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1048,6 +1048,9 @@ static void Task_BattleTransition(u8 taskId)
|
|||
static bool8 Transition_StartIntro(struct Task *task)
|
||||
{
|
||||
SetWeatherScreenFadeOut();
|
||||
// cause all shadow sprites to destroy themselves,
|
||||
// freeing up sprite slots for the transition
|
||||
gWeatherPtr->noShadows = TRUE;
|
||||
CpuCopy32(gPlttBufferFaded, gPlttBufferUnfaded, PLTT_SIZE);
|
||||
if (sTasks_Intro[task->tTransitionId] != NULL)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7338,7 +7338,7 @@ u32 GetBattleMoveTarget(u16 move, u8 setTarget)
|
|||
else
|
||||
battlerAbilityOnField = IsAbilityOnOpposingSide(targetBattler, ABILITY_LIGHTNING_ROD);
|
||||
|
||||
if (battlerAbilityOnField > 0)
|
||||
if (battlerAbilityOnField > 0 && (battlerAbilityOnField - 1) != gBattlerAttacker)
|
||||
{
|
||||
targetBattler = battlerAbilityOnField - 1;
|
||||
RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability);
|
||||
|
|
@ -7352,7 +7352,7 @@ u32 GetBattleMoveTarget(u16 move, u8 setTarget)
|
|||
else
|
||||
battlerAbilityOnField = IsAbilityOnOpposingSide(targetBattler, ABILITY_STORM_DRAIN);
|
||||
|
||||
if (battlerAbilityOnField > 0)
|
||||
if (battlerAbilityOnField > 0 && (battlerAbilityOnField - 1) != gBattlerAttacker)
|
||||
{
|
||||
targetBattler = battlerAbilityOnField - 1;
|
||||
RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability);
|
||||
|
|
@ -8086,10 +8086,20 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx)
|
|||
basePower = sSpeedDiffPowerTable[speed];
|
||||
break;
|
||||
case EFFECT_GYRO_BALL:
|
||||
basePower = ((25 * GetBattlerTotalSpeedStat(battlerDef)) / GetBattlerTotalSpeedStat(battlerAtk)) + 1;
|
||||
if (basePower > 150)
|
||||
basePower = 150;
|
||||
break;
|
||||
{
|
||||
u32 attackerSpeed = GetBattlerTotalSpeedStat(battlerAtk);
|
||||
if (attackerSpeed == 0)
|
||||
{
|
||||
basePower = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
basePower = ((25 * GetBattlerTotalSpeedStat(battlerDef)) / attackerSpeed) + 1;
|
||||
if (basePower > 150)
|
||||
basePower = 150;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EFFECT_ECHOED_VOICE:
|
||||
// gBattleStruct->sameMoveTurns incremented in ppreduce
|
||||
if (gBattleStruct->sameMoveTurns[battlerAtk] != 0)
|
||||
|
|
@ -10268,11 +10278,29 @@ u32 GetIllusionMonSpecies(u32 battler)
|
|||
return SPECIES_NONE;
|
||||
}
|
||||
|
||||
u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pokemon *partnerMon)
|
||||
u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pokemon *partnerMon, u32 battler)
|
||||
{
|
||||
s32 id;
|
||||
s32 partyEnd=6;
|
||||
s32 partyStart=0;
|
||||
|
||||
// Adjust party search range for Multibattles and Player vs two-trainers
|
||||
if((GetBattlerSide(battler) == B_SIDE_PLAYER && (gBattleTypeFlags & BATTLE_TYPE_MULTI))
|
||||
|| (GetBattlerSide(battler) == B_SIDE_OPPONENT && (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)))
|
||||
{
|
||||
if((GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT) || (GetBattlerPosition(battler) == B_POSITION_OPPONENT_LEFT))
|
||||
{
|
||||
partyEnd = 3;
|
||||
partyStart = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
partyEnd = 6;
|
||||
partyStart = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Find last alive non-egg pokemon.
|
||||
for (id = PARTY_SIZE - 1; id >= 0; id--)
|
||||
for (s32 id = partyEnd - 1; id >= partyStart; id--)
|
||||
{
|
||||
if (GetMonData(&party[id], MON_DATA_SANITY_HAS_SPECIES)
|
||||
&& GetMonData(&party[id], MON_DATA_HP)
|
||||
|
|
@ -10306,7 +10334,7 @@ bool32 SetIllusionMon(struct Pokemon *mon, u32 battler)
|
|||
else
|
||||
partnerMon = mon;
|
||||
|
||||
id = GetIllusionMonPartyId(party, mon, partnerMon);
|
||||
id = GetIllusionMonPartyId(party, mon, partnerMon, battler);
|
||||
if (id != PARTY_SIZE)
|
||||
{
|
||||
gBattleStruct->illusion[battler].state = ILLUSION_ON;
|
||||
|
|
|
|||
|
|
@ -2334,7 +2334,12 @@ bool32 CheckMsgCondition(const struct MsgCondition *cond, struct Pokemon *mon, u
|
|||
switch (cond->type)
|
||||
{
|
||||
case MSG_COND_SPECIES:
|
||||
return (cond->data.raw == species);
|
||||
multi = cond->data.split.hw;
|
||||
// if byte nonzero, invert; check != species!
|
||||
if (cond->data.split.b)
|
||||
return (cond->data.split.hw != species);
|
||||
else
|
||||
return (cond->data.split.hw == species);
|
||||
case MSG_COND_TYPE:
|
||||
multi = (SpeciesHasType(species, cond->data.bytes[0]) ||
|
||||
SpeciesHasType(species, cond->data.bytes[1]));
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ static const u8 sCondMsg16[] = _("{STR_VAR_1} doesn't want to get off\nthe boat
|
|||
static const u8* const sBoatTexts[] = {sCondMsg14, sCondMsg15, sCondMsg16, NULL};
|
||||
static const u8 sCondMsg17[] = _("{STR_VAR_1} is listening to the\nsound of the machines.");
|
||||
static const u8* const sMachineTexts[] = {sCondMsg13, sCondMsg17, NULL};
|
||||
static const u8 sCondMsg18[] = _("Waah! your POKéMON suddenly splashed\nwater!");
|
||||
static const u8 sCondMsg18[] = _("Waah! Your POKéMON suddenly splashed\nwater!");
|
||||
static const u8 sCondMsg19[] = _("Your POKéMON is blowing sand in the\nair!");
|
||||
static const u8 sCondMsg20[] = _("{STR_VAR_1} is playing around,\nplucking bits of grass.");
|
||||
static const u8 sCondMsg21[] = _("Your POKéMON is happily looking at\nyour footprints!");
|
||||
|
|
@ -74,6 +74,7 @@ static const u8* const sDayTexts[] = {sCondMsg43, sCondMsg44, NULL};
|
|||
static const u8 sCondMsg45[] = _("Your POKéMON is staring spellbound\nat the night sky!");
|
||||
static const u8 sCondMsg46[] = _("Your POKéMON is happily gazing at\nthe beautiful, starry sky!");
|
||||
static const u8* const sNightTexts[] = {sCondMsg45, sCondMsg46, NULL};
|
||||
static const u8 sCondMsg50[] = _("{STR_VAR_1} is disturbed by the\nabnormal weather!");
|
||||
|
||||
// See the struct definition in follower_helper.h for more info
|
||||
const struct FollowerMsgInfoExtended gFollowerConditionalMessages[COND_MSG_COUNT] =
|
||||
|
|
@ -378,6 +379,18 @@ const struct FollowerMsgInfoExtended gFollowerConditionalMessages[COND_MSG_COUNT
|
|||
MATCH_TIME_OF_DAY(TIME_NIGHT),
|
||||
},
|
||||
},
|
||||
[COND_MSG_ABNORMAL_WEATHER] =
|
||||
{
|
||||
.text = sCondMsg50,
|
||||
.emotion = FOLLOWER_EMOTION_SURPRISE,
|
||||
.conditions =
|
||||
{
|
||||
MATCH_MUSIC(MUS_ABNORMAL_WEATHER),
|
||||
MATCH_NOT_SPECIES(SPECIES_KYOGRE),
|
||||
MATCH_NOT_SPECIES(SPECIES_GROUDON),
|
||||
MATCH_NOT_SPECIES(SPECIES_RAYQUAZA),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Pool of "unconditional" follower messages
|
||||
|
|
|
|||
|
|
@ -84,3 +84,12 @@ s32 MathUtil_Inv32(s32 y)
|
|||
x = 0x10000;
|
||||
return x / y;
|
||||
}
|
||||
|
||||
u32 MathUtil_Exponent(u32 x, u32 y)
|
||||
{
|
||||
u32 result = 1;
|
||||
for (u32 index = 0; index < y; index++)
|
||||
result *= x;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
#include "palette.h"
|
||||
#include "util.h"
|
||||
#include "decompress.h"
|
||||
#include "field_weather.h"
|
||||
#include "malloc.h"
|
||||
#include "menu.h"
|
||||
#include "gpu_regs.h"
|
||||
#include "task.h"
|
||||
#include "constants/field_weather.h"
|
||||
#include "constants/rgb.h"
|
||||
|
||||
enum
|
||||
|
|
@ -718,26 +720,46 @@ static void UpdateBlendRegisters(void)
|
|||
{
|
||||
SetGpuReg(REG_OFFSET_BLDCNT, (u16)gPaletteFadeBlendCnt);
|
||||
SetGpuReg(REG_OFFSET_BLDY, gPaletteFade.y);
|
||||
// If fade-out, also adjust BLDALPHA and DISPCNT
|
||||
if (!gPaletteFade.yDec)
|
||||
// if TGT2 enabled, also adjust BLDALPHA and DISPCNT
|
||||
if (((u16)gPaletteFadeBlendCnt) & BLDCNT_TGT2_ALL)
|
||||
{
|
||||
u16 bldAlpha = GetGpuReg(REG_OFFSET_BLDALPHA);
|
||||
u8 tgt1 = BLDALPHA_TGT1(bldAlpha);
|
||||
u8 tgt2 = BLDALPHA_TGT2(bldAlpha);
|
||||
u8 bldFade;
|
||||
u8 mode = (gPaletteFadeBlendCnt & BLDCNT_EFFECT_EFF_MASK) == BLDCNT_EFFECT_LIGHTEN ? FADE_FROM_WHITE : FADE_FROM_BLACK;
|
||||
if (!gPaletteFade.yDec)
|
||||
mode++;
|
||||
|
||||
switch (gPaletteFadeBlendCnt & BLDCNT_EFFECT_EFF_MASK)
|
||||
ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_FORCED_BLANK);
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
// FADE_TO_BLACK
|
||||
case BLDCNT_EFFECT_DARKEN:
|
||||
bldFade = BLDALPHA_TGT1(max(0, 16 - gPaletteFade.y));
|
||||
SetGpuReg(REG_OFFSET_BLDALPHA,
|
||||
BLDALPHA_BLEND(min(tgt1, bldFade), min(tgt2, bldFade)));
|
||||
case FADE_FROM_BLACK:
|
||||
// increment each target until reaching weather's values
|
||||
SetGpuReg(
|
||||
REG_OFFSET_BLDALPHA,
|
||||
BLDALPHA_BLEND(
|
||||
min(++tgt1, gWeatherPtr->currBlendEVA),
|
||||
min(++tgt2, gWeatherPtr->currBlendEVB)
|
||||
)
|
||||
);
|
||||
break;
|
||||
// FADE_TO_WHITE
|
||||
case BLDCNT_EFFECT_LIGHTEN:
|
||||
SetGpuReg(REG_OFFSET_BLDALPHA,
|
||||
BLDALPHA_BLEND(min(++tgt1, 31), min(++tgt2, 31)));
|
||||
case FADE_TO_BLACK:
|
||||
bldAlpha = BLDALPHA_TGT1(max(0, 16 - gPaletteFade.y));
|
||||
SetGpuReg(
|
||||
REG_OFFSET_BLDALPHA,
|
||||
BLDALPHA_BLEND(min(tgt1, bldAlpha), min(tgt2, bldAlpha))
|
||||
);
|
||||
break;
|
||||
// Not handled; blend sprites will pop in,
|
||||
// but the effect coming from white looks okay
|
||||
// case FADE_FROM_WHITE:
|
||||
// break;
|
||||
case FADE_TO_WHITE:
|
||||
SetGpuReg(
|
||||
REG_OFFSET_BLDALPHA,
|
||||
BLDALPHA_BLEND(min(++tgt1, 31), min(++tgt2, 31))
|
||||
);
|
||||
// cause display to show white when finished
|
||||
// (otherwise blend-mode sprites will still be visible)
|
||||
if (gPaletteFade.hardwareFadeFinishing && gPaletteFade.y >= 16)
|
||||
|
|
@ -745,10 +767,6 @@ static void UpdateBlendRegisters(void)
|
|||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_FORCED_BLANK);
|
||||
}
|
||||
|
||||
if (gPaletteFade.hardwareFadeFinishing)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -840,6 +840,8 @@ bool8 ScrCmd_fadescreenswapbuffers(struct ScriptContext *ctx)
|
|||
switch (mode)
|
||||
{
|
||||
case FADE_FROM_BLACK:
|
||||
SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(0, 0));
|
||||
break;
|
||||
case FADE_FROM_WHITE:
|
||||
// Restore last weather blend before fading in,
|
||||
// since BLDALPHA was modified by fade-out
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ SINGLE_BATTLE_TEST("Liquid Ooze causes leech seed victim to faint before seeder"
|
|||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Liquid Ooze causes Dream Eater users to lose HP instead of heal (Gen 5+")
|
||||
SINGLE_BATTLE_TEST("Liquid Ooze causes Dream Eater users to lose HP instead of heal (Gen 5+)")
|
||||
{
|
||||
s16 damage;
|
||||
GIVEN {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,60 @@ SINGLE_BATTLE_TEST("Overcoat blocks powder and spore moves")
|
|||
}
|
||||
}
|
||||
|
||||
TO_DO_BATTLE_TEST("Overcoat blocks damage from hail");
|
||||
TO_DO_BATTLE_TEST("Overcoat blocks damage from sandstorm");
|
||||
TO_DO_BATTLE_TEST("Overcoat blocks Effect Spore's effect");
|
||||
DOUBLE_BATTLE_TEST("Overcoat blocks damage from sandstorm")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WYNAUT) { Speed(50); }
|
||||
PLAYER(SPECIES_HELIOLISK) { Speed(40); Ability(ABILITY_SAND_VEIL); }
|
||||
OPPONENT(SPECIES_PINECO) { Speed(30); Ability(ABILITY_OVERCOAT); }
|
||||
OPPONENT(SPECIES_STARLY) { Speed(20); }
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_SANDSTORM); }
|
||||
} SCENE {
|
||||
MESSAGE("Wynaut used Sandstorm!");
|
||||
MESSAGE("The sandstorm is raging.");
|
||||
HP_BAR(playerLeft);
|
||||
NONE_OF {
|
||||
HP_BAR(playerRight);
|
||||
HP_BAR(opponentLeft);
|
||||
}
|
||||
HP_BAR(opponentRight);
|
||||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Overcoat blocks damage from hail")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WYNAUT) { Speed(50); Ability(ABILITY_SNOW_CLOAK); }
|
||||
PLAYER(SPECIES_SOLOSIS) { Speed(40); Ability(ABILITY_RUN_AWAY); }
|
||||
OPPONENT(SPECIES_PINECO) { Speed(30); Ability(ABILITY_OVERCOAT); }
|
||||
OPPONENT(SPECIES_SNORUNT) { Speed(20); }
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_HAIL); MOVE(playerRight, MOVE_SKILL_SWAP, target: playerLeft); }
|
||||
} SCENE {
|
||||
MESSAGE("Wynaut used Hail!");
|
||||
MESSAGE("Solosis used Skill Swap!");
|
||||
HP_BAR(playerLeft);
|
||||
NONE_OF {
|
||||
HP_BAR(playerRight);
|
||||
HP_BAR(opponentLeft);
|
||||
HP_BAR(opponentRight); // ice type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Overcoat blocks Effect Spore's effect")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_PINECO) {Ability(ABILITY_OVERCOAT);}
|
||||
OPPONENT(SPECIES_SHROOMISH) {Ability(ABILITY_EFFECT_SPORE);}
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_TACKLE, WITH_RNG(RNG_EFFECT_SPORE, 1)); }
|
||||
} SCENE {
|
||||
MESSAGE("Pineco used Tackle!");
|
||||
NOT ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE);
|
||||
} THEN {
|
||||
EXPECT_EQ(player->status1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -866,17 +866,34 @@ AI_SINGLE_BATTLE_TEST("AI will not set up Weather if it wont have any affect")
|
|||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI won't use stat boosting moves if the player has used Haze")
|
||||
AI_SINGLE_BATTLE_TEST("Move scoring comparison properly awards bonus point to best OHKO move")
|
||||
{
|
||||
PASSES_RANDOMLY(BOOST_INTO_HAZE_CHANCE, 100, RNG_AI_BOOST_INTO_HAZE);
|
||||
GIVEN {
|
||||
ASSUME(GetMoveEffect(MOVE_HAZE) == EFFECT_HAZE);
|
||||
ASSUME(GetMoveEffect(MOVE_DRAGON_DANCE) == EFFECT_DRAGON_DANCE);
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
|
||||
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SCRATCH, MOVE_HAZE); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SCRATCH, MOVE_DRAGON_DANCE); }
|
||||
ASSUME(MoveHasAdditionalEffect(MOVE_THUNDER, MOVE_EFFECT_PARALYSIS));
|
||||
ASSUME(GetMoveAdditionalEffectCount(MOVE_WATER_SPOUT) == 0);
|
||||
ASSUME(GetMoveAdditionalEffectCount(MOVE_WATER_GUN) == 0);
|
||||
ASSUME(GetMoveAdditionalEffectCount(MOVE_ORIGIN_PULSE) == 0);
|
||||
ASSUME(GetMoveAccuracy(MOVE_WATER_SPOUT) > GetMoveAccuracy(MOVE_THUNDER));
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY);
|
||||
PLAYER(SPECIES_WAILORD) { Level(50); }
|
||||
OPPONENT(SPECIES_WAILORD) { Moves(MOVE_THUNDER, MOVE_WATER_SPOUT, MOVE_WATER_GUN, MOVE_SURF); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_HAZE); EXPECT_MOVE(opponent, MOVE_DRAGON_DANCE); }
|
||||
TURN { MOVE(player, MOVE_HAZE); EXPECT_MOVE(opponent, MOVE_DRAGON_DANCE); }
|
||||
TURN { EXPECT_MOVE(opponent, MOVE_WATER_SPOUT); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("Move scoring comparison properly awards bonus point to best OHKO move")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(MoveHasAdditionalEffect(MOVE_THUNDER, MOVE_EFFECT_PARALYSIS));
|
||||
ASSUME(GetMoveAdditionalEffectCount(MOVE_WATER_SPOUT) == 0);
|
||||
ASSUME(GetMoveAdditionalEffectCount(MOVE_WATER_GUN) == 0);
|
||||
ASSUME(GetMoveAdditionalEffectCount(MOVE_ORIGIN_PULSE) == 0);
|
||||
ASSUME(GetMoveAccuracy(MOVE_WATER_SPOUT) > GetMoveAccuracy(MOVE_THUNDER));
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY);
|
||||
PLAYER(SPECIES_WAILORD) { Level(50); }
|
||||
OPPONENT(SPECIES_WAILORD) { Moves(MOVE_THUNDER, MOVE_WATER_SPOUT, MOVE_WATER_GUN, MOVE_SURF); }
|
||||
} WHEN {
|
||||
TURN { EXPECT_MOVE(opponent, MOVE_WATER_SPOUT); }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1155,6 +1155,23 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if all moves
|
|||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if all moves deal zero damage (absorbing ability)")
|
||||
{
|
||||
PASSES_RANDOMLY(SHOULD_SWITCH_ALL_SCORES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_SCORES_BAD);
|
||||
GIVEN {
|
||||
ASSUME(GetMoveType(MOVE_THUNDER_PUNCH) == TYPE_ELECTRIC);
|
||||
ASSUME(GetMoveType(MOVE_FAKE_OUT) == TYPE_NORMAL);
|
||||
ASSUME(GetMoveType(MOVE_RETURN) == TYPE_NORMAL);
|
||||
ASSUME(GetMoveType(MOVE_DRAIN_PUNCH) == TYPE_FIGHTING);
|
||||
ASSUME(gSpeciesInfo[SPECIES_MAROWAK_ALOLA].types[1] == TYPE_GHOST);
|
||||
PLAYER(SPECIES_MAROWAK_ALOLA) { Ability(ABILITY_LIGHTNING_ROD); Moves(MOVE_SHADOW_BONE); }
|
||||
OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_FAKE_OUT, MOVE_RETURN, MOVE_DRAIN_PUNCH, MOVE_THUNDER_PUNCH); Ability(ABILITY_LIMBER); }
|
||||
OPPONENT(SPECIES_CHANDELURE) { Moves(MOVE_SHADOW_BALL); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_SHADOW_BONE); EXPECT_SWITCH(opponent, 1); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if Palafin-Zero isn't transformed yet")
|
||||
{
|
||||
GIVEN {
|
||||
|
|
|
|||
|
|
@ -311,5 +311,27 @@ DOUBLE_BATTLE_TEST("Ally Switch swaps Illusion data")
|
|||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Ally switch updates last used moves for Mimic")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_XATU) { Speed(100); }
|
||||
PLAYER(SPECIES_RIOLU) { Speed(150); }
|
||||
OPPONENT(SPECIES_FEAROW) { Speed(20); }
|
||||
OPPONENT(SPECIES_ARON) { Speed(30); }
|
||||
} WHEN {
|
||||
TURN { MOVE(playerRight, MOVE_FAKE_OUT, target: opponentRight); MOVE(playerLeft, MOVE_ALLY_SWITCH);
|
||||
MOVE(opponentLeft, MOVE_MIMIC, target: playerLeft);
|
||||
}
|
||||
} SCENE {
|
||||
MESSAGE("Riolu used Fake Out!");
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, playerRight);
|
||||
MESSAGE("Xatu used Ally Switch!");
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft);
|
||||
MESSAGE("Xatu and Riolu switched places!");
|
||||
MESSAGE("The opposing Fearow used Mimic!");
|
||||
MESSAGE("The opposing Fearow learned Fake Out!");
|
||||
}
|
||||
}
|
||||
|
||||
// Triple Battles required to test
|
||||
//TO_DO_BATTLE_TEST("Ally Switch fails if the user is in the middle of the field in a Triple Battle");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,65 @@
|
|||
#include "global.h"
|
||||
#include "test/battle.h"
|
||||
|
||||
SINGLE_BATTLE_TEST("Natural Gift removes berry if move fails due to an immunity")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); }
|
||||
OPPONENT(SPECIES_PHANPY);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_NATURAL_GIFT); }
|
||||
} SCENE {
|
||||
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player);
|
||||
} THEN {
|
||||
EXPECT(player->item == ITEM_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Natural Gift does not remove berry if user is ejected out")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); }
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_NATURAL_GIFT); }
|
||||
TURN { SWITCH(player, 0); }
|
||||
} SCENE {
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player);
|
||||
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
|
||||
} THEN {
|
||||
EXPECT(player->item == ITEM_PECHA_BERRY);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Natural Gift does not remove berry if user is unable to use a move")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); }
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(opponent, MOVE_THUNDER_WAVE); MOVE(player, MOVE_NATURAL_GIFT, WITH_RNG(RNG_PARALYSIS, FALSE)); }
|
||||
} SCENE {
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, opponent);
|
||||
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player);
|
||||
} THEN {
|
||||
EXPECT(player->item == ITEM_PECHA_BERRY);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Natural Gift removes the berry if user missed")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); }
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(opponent, MOVE_SAND_ATTACK); MOVE(player, MOVE_NATURAL_GIFT, hit: FALSE); }
|
||||
} SCENE {
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, opponent);
|
||||
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player);
|
||||
} THEN {
|
||||
EXPECT(player->item == ITEM_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
TO_DO_BATTLE_TEST("TODO: Write Natural Gift (Move Effect) test titles")
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (right) and
|
|||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (left) and Lightning Rod (reft)")
|
||||
DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (left) and Lightning Rod (right)")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ SINGLE_BATTLE_TEST("Poison can't bad poison a poison or steel type")
|
|||
GIVEN {
|
||||
ASSUME(GetMoveEffect(MOVE_POISON_GAS) == EFFECT_NON_VOLATILE_STATUS);
|
||||
ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_GAS) == MOVE_EFFECT_POISON);
|
||||
ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_GAS) == MOVE_EFFECT_POISON);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(species);
|
||||
} WHEN {
|
||||
|
|
|
|||
|
|
@ -22,10 +22,7 @@ enum {
|
|||
CURRENT_TEST_STATE_RUN,
|
||||
};
|
||||
|
||||
__attribute__((section(".persistent"))) static struct {
|
||||
u32 address:28;
|
||||
u32 state:1;
|
||||
} sCurrentTest = {0};
|
||||
__attribute__((section(".persistent"))) struct PersistentTestRunnerState gPersistentTestRunnerState = {0};
|
||||
|
||||
void TestRunner_Battle(const struct Test *);
|
||||
|
||||
|
|
@ -184,15 +181,16 @@ top:
|
|||
gSaveBlock2Ptr->optionsBattleStyle = OPTIONS_BATTLE_STYLE_SET;
|
||||
|
||||
// The current test restarted the ROM (e.g. by jumping to NULL).
|
||||
if (sCurrentTest.address != 0)
|
||||
if (gPersistentTestRunnerState.address != 0)
|
||||
{
|
||||
gTestRunnerState.test = __start_tests;
|
||||
while ((uintptr_t)gTestRunnerState.test != sCurrentTest.address)
|
||||
while ((uintptr_t)gTestRunnerState.test != gPersistentTestRunnerState.address)
|
||||
{
|
||||
AssignCostToRunner();
|
||||
gTestRunnerState.test++;
|
||||
}
|
||||
if (sCurrentTest.state == CURRENT_TEST_STATE_ESTIMATE)
|
||||
|
||||
if (gPersistentTestRunnerState.state == CURRENT_TEST_STATE_ESTIMATE)
|
||||
{
|
||||
u32 runner = MinCostProcess();
|
||||
gTestRunnerState.processCosts[runner] += 1;
|
||||
|
|
@ -211,6 +209,9 @@ top:
|
|||
gTestRunnerState.state = STATE_REPORT_RESULT;
|
||||
gTestRunnerState.result = TEST_RESULT_CRASH;
|
||||
}
|
||||
|
||||
if (gPersistentTestRunnerState.expectCrash)
|
||||
gTestRunnerState.expectedResult = TEST_RESULT_CRASH;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -252,8 +253,8 @@ top:
|
|||
REG_TM2CNT_L = UINT16_MAX - (274 * 60); // Approx. 1 second.
|
||||
REG_TM2CNT_H = TIMER_ENABLE | TIMER_INTR_ENABLE | TIMER_1024CLK;
|
||||
|
||||
sCurrentTest.address = (uintptr_t)gTestRunnerState.test;
|
||||
sCurrentTest.state = CURRENT_TEST_STATE_ESTIMATE;
|
||||
gPersistentTestRunnerState.address = (uintptr_t)gTestRunnerState.test;
|
||||
gPersistentTestRunnerState.state = CURRENT_TEST_STATE_ESTIMATE;
|
||||
|
||||
// If AssignCostToRunner fails, we want to report the failure.
|
||||
gTestRunnerState.state = STATE_REPORT_RESULT;
|
||||
|
|
@ -266,7 +267,8 @@ top:
|
|||
|
||||
case STATE_RUN_TEST:
|
||||
gTestRunnerState.state = STATE_REPORT_RESULT;
|
||||
sCurrentTest.state = CURRENT_TEST_STATE_RUN;
|
||||
gPersistentTestRunnerState.state = CURRENT_TEST_STATE_RUN;
|
||||
gPersistentTestRunnerState.expectCrash = FALSE;
|
||||
SeedRng(0);
|
||||
SeedRng2(0);
|
||||
if (gTestRunnerState.test->runner->setUp)
|
||||
|
|
@ -423,6 +425,13 @@ void Test_ExpectLeaks(bool32 expectLeaks)
|
|||
gTestRunnerState.expectLeaks = expectLeaks;
|
||||
}
|
||||
|
||||
void Test_ExpectCrash(bool32 expectCrash)
|
||||
{
|
||||
gPersistentTestRunnerState.expectCrash = expectCrash;
|
||||
if (expectCrash)
|
||||
Test_ExpectedResult(TEST_RESULT_CRASH);
|
||||
}
|
||||
|
||||
static void FunctionTest_SetUp(void *data)
|
||||
{
|
||||
(void)data;
|
||||
|
|
|
|||
9
test/test_test_runner.c
Normal file
9
test/test_test_runner.c
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#include "global.h"
|
||||
#include "test/test.h"
|
||||
|
||||
TEST("Tests resume after CRASH")
|
||||
{
|
||||
KNOWN_CRASHING;
|
||||
void (*f)(void) = NULL;
|
||||
f();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user