merge rhh/upcoming

This commit is contained in:
AlexOn1ine 2024-12-02 17:45:00 +01:00
commit 09c9d8a800
39 changed files with 2050 additions and 120 deletions

View File

@ -6,12 +6,16 @@
pokeemerald-expansion is a decomp hack base project based off pret's [pokeemerald](https://github.com/pret/pokeemerald) decompilation project. It's recommended that any new projects that plan on using it, to clone this repository instead of pret's vanilla repository, as we regurlarly incorporate pret's documentation changes. This is ***NOT*** a standalone romhack, and as such, most features will be unavailable and/or unbalanced if played as is.
## Using pokeemerald-expansion
If you use pokeemerald-expansion in your hack, please add RHH (Rom Hacking Hideout) to your credits list. Optionally, you can list the version used, so it can help players know what features to expect.
You can phrase it as the following:
```
Based off RHH's pokeemerald-expansion 1.9.3 https://github.com/rh-hideout/pokeemerald-expansion/
```
Please follow the instructions in `INSTALL.md` to get pokeemerald-expansion set up on your machine.
## What features are included?
- ***IMPORTANT*❗❗ Read through these to learn what features you can toggle**:
- [Battle configurations](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/config/battle.h)

View File

@ -3666,7 +3666,7 @@ gBattleAnimMove_DarkVoid::
loopsewithpan SE_M_CONFUSE_RAY, SOUND_PAN_ATTACKER, 5, 2
delay 48
createsprite gSlideMonToOffsetSpriteTemplate, ANIM_ATTACKER, 2, ANIM_TARGET, -768, 21, 0, 112 @Last is duration
createsprite gSlideMonToOffsetSpriteTemplate, ANIM_ATTACKER, 2, ANIM_DEF_PARTNER, -768, 21, 0, 112 @Last is duration
createsprite gSlideMonToOffsetPartnerSpriteTemplate, ANIM_ATTACKER, 2, ANIM_DEF_PARTNER, -768, 21, 0, 112 @Last is duration
delay 64
invisible ANIM_TARGET
invisible ANIM_DEF_PARTNER
@ -3674,7 +3674,7 @@ gBattleAnimMove_DarkVoid::
createsprite gDarkVoidPurpleStarsTemplate, ANIM_ATTACKER, 2, 0, 0, ANIM_DEF_PARTNER, 0, 32, 60
waitforvisualfinish
createsprite gSlideMonToOriginalPosSpriteTemplate, ANIM_ATTACKER, 2, ANIM_TARGET, 0, 16
createsprite gSlideMonToOriginalPosSpriteTemplate, ANIM_ATTACKER, 2, ANIM_DEF_PARTNER, 0, 16
createsprite gSlideMonToOriginalPosPartnerSpriteTemplate, ANIM_ATTACKER, 2, ANIM_DEF_PARTNER, 0, 16
delay 32
call UnsetPsychicBg
visible ANIM_TARGET
@ -10093,7 +10093,6 @@ gBattleAnimMove_FloralHealing::
loadspritegfx ANIM_TAG_ORBS @circles
loadspritegfx ANIM_TAG_PINK_PETAL @pink particles
monbg ANIM_ATTACKER
monbg ANIM_TARGET
playsewithpan SE_M_DETECT, SOUND_PAN_ATTACKER
call CIRCLES_LEAVES
call CIRCLES_LEAVES
@ -10101,6 +10100,7 @@ gBattleAnimMove_FloralHealing::
panse SE_M_COMET_PUNCH, SOUND_PAN_ATTACKER, SOUND_PAN_TARGET, 0x2, 0x0
playsewithpan SE_M_TWISTER, 0x0
createsprite gSweetScentPetalSpriteTemplate, ANIM_ATTACKER, 2, 0x46, 0x1, 0x40
clearmonbg ANIM_ATTACKER
delay 0x2
createsprite gFloralHealingWindLeavesTemplate, ANIM_ATTACKER, 2, 0x3c, 0x0, 0x40
delay 0x2
@ -10123,6 +10123,7 @@ gBattleAnimMove_FloralHealing::
createsprite gSweetScentPetalSpriteTemplate, ANIM_ATTACKER, 2, 0x55, 0x0, 0x78
delay 0x2
loopsewithpan SE_M_POISON_POWDER, SOUND_PAN_TARGET, 0x12, 0xa
monbg ANIM_TARGET
call FloralHealingSpores
call FloralHealingSpores
call FloralHealingSpores
@ -10133,7 +10134,6 @@ gBattleAnimMove_FloralHealing::
createsprite gGrantingStarsSpriteTemplate, ANIM_ATTACKER, 16, 0xc, 0xfffb, 0x1, 0x0, 0x20, 0x3c, 0x1
waitforvisualfinish
clearmonbg ANIM_TARGET
clearmonbg ANIM_ATTACKER
end
FloralHealingSpores:
createsprite gFloralHealingFlowerTemplate, ANIM_ATTACKER, 2, 0x0, 0xffec, 0x55, 0x50, 0x0
@ -33527,7 +33527,7 @@ gBattleAnimMove_ClangorousSoulblaze::
delay 0x2
createvisualtask AnimTask_StartSlidingBg, 0x5, 0x0, 0xFFE0, 0x1, 0xffff
createsprite gSlideMonToOffsetSpriteTemplate, ANIM_ATTACKER, 2, ANIM_TARGET, 0xfd00, 0xa, 0x0, 0x2a
createsprite gSlideMonToOffsetSpriteTemplate, ANIM_ATTACKER, 2, ANIM_DEF_PARTNER, 0xfd00, 0xa, 0x0, 0x2a
createsprite gSlideMonToOffsetPartnerSpriteTemplate, ANIM_ATTACKER, 2, ANIM_DEF_PARTNER, 0xfd00, 0xa, 0x0, 0x2a
delay 0x20
createvisualtask AnimTask_StartSlidingBg, 0x5, 0x0, 0x20, 0x1, 0xffff
delay 0xC
@ -33719,7 +33719,7 @@ FINISH_SOULBLAZE:
call ResetFromWhiteScreen
blendoff
createsprite gSlideMonToOriginalPosSpriteTemplate, ANIM_ATTACKER, 2, ANIM_TARGET, 0x0, 0x10
createsprite gSlideMonToOriginalPosSpriteTemplate, ANIM_ATTACKER, 2, ANIM_DEF_PARTNER, 0x0, 0x10
createsprite gSlideMonToOriginalPosPartnerSpriteTemplate, ANIM_ATTACKER, 2, ANIM_DEF_PARTNER, 0x0, 0x10
waitforvisualfinish
end
ClangorousSoulblazeEnergySwirl:

View File

@ -2401,7 +2401,6 @@ BattleScript_EffectHealingWish::
storehealingwish BS_ATTACKER
.if B_HEALING_WISH_SWITCH <= GEN_4
openpartyscreen BS_ATTACKER, BattleScript_EffectHealingWishEnd
switchoutabilities BS_ATTACKER
waitstate
switchhandleorder BS_ATTACKER, 2
returnatktoball
@ -5754,7 +5753,6 @@ BattleScript_PrintFullBox::
BattleScript_ActionSwitch::
hpthresholds2 BS_ATTACKER
saveattacker
printstring STRINGID_RETURNMON
jumpifbattletype BATTLE_TYPE_DOUBLE, BattleScript_PursuitSwitchDmgSetMultihit
setmultihit 1
@ -5772,7 +5770,6 @@ BattleScript_DoSwitchOut::
switchoutabilities BS_ATTACKER
updatedynamax
waitstate
restoreattacker
returnatktoball
waitstate
drawpartystatussummary BS_ATTACKER
@ -9621,7 +9618,9 @@ BattleScript_EjectButtonActivates::
removeitem BS_SCRIPTING
makeinvisible BS_SCRIPTING
openpartyscreen BS_SCRIPTING, BattleScript_EjectButtonEnd
copybyte sSAVED_BATTLER, sBATTLER
switchoutabilities BS_SCRIPTING
copybyte sBATTLER, sSAVED_BATTLER
waitstate
switchhandleorder BS_SCRIPTING 0x2
returntoball BS_SCRIPTING, FALSE
@ -9718,6 +9717,7 @@ BattleScript_PastelVeilEnd:
end3
BattleScript_NeutralizingGasExits::
saveattacker
savetarget
pause B_WAIT_TIME_SHORT
printstring STRINGID_NEUTRALIZINGGASOVER
@ -9727,6 +9727,7 @@ BattleScript_NeutralizingGasExitsLoop:
switchinabilities BS_TARGET
addbyte gBattlerTarget, 1
jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_NeutralizingGasExitsLoop
restoreattacker
restoretarget
return

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,7 +1,7 @@
JASC-PAL
0100
16
255 255 255
120 255 255
254 235 185
220 220 218
235 192 100
@ -16,4 +16,4 @@ JASC-PAL
64 64 64
45 43 43
8 8 8
0 0 0
255 255 255

View File

@ -16,4 +16,4 @@ JASC-PAL
64 64 64
45 43 43
8 8 8
0 0 0
255 255 255

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 B

After

Width:  |  Height:  |  Size: 563 B

View File

@ -10,6 +10,7 @@
max(POKEMON_NAME_LENGTH + 1, \
ABILITY_NAME_LENGTH + 1)))
#define BATTLE_MSG_MAX_WIDTH 208
#define BATTLE_MSG_MAX_LINES 2
// for 0xFD
#define B_TXT_BUFF1 0x0

View File

@ -507,7 +507,7 @@
#define MOVE_TARGET_FOES_AND_ALLY (1 << 5)
#define MOVE_TARGET_OPPONENTS_FIELD (1 << 6)
#define MOVE_TARGET_ALLY (1 << 7)
#define MOVE_TARGET_ALL_BATTLERS ((1 << 8) | MOVE_TARGET_USER)
#define MOVE_TARGET_ALL_BATTLERS ((1 << 8) | MOVE_TARGET_USER) // No functionality for status moves
// For the second argument of GetMoveTarget, when no target override is needed
#define NO_TARGET_OVERRIDE 0

View File

@ -289,9 +289,9 @@ enum MoveEndEffects
MOVEEND_RECOIL,
MOVEEND_ITEM_EFFECTS_ATTACKER,
MOVEEND_MAGICIAN, // Occurs after final multi-hit strike, and after other items/abilities would activate
MOVEEND_RED_CARD, // Red Card triggers before Eject Pack
MOVEEND_EJECT_ITEMS,
MOVEEND_WHITE_HERB,
MOVEEND_RED_CARD,
MOVEEND_LIFEORB_SHELLBELL, // Includes shell bell, throat spray, etc
MOVEEND_CHANGED_ITEMS,
MOVEEND_PICKPOCKET,

View File

@ -134,4 +134,8 @@
// param1: amount of days
#define FORM_CHANGE_DAYS_PASSED 23
// Form change for Aegislash
#define FORM_CHANGE_BATTLE_ATTACK 24
#define FORM_CHANGE_BATTLE_KINGS_SHIELD 25
#endif // GUARD_CONSTANTS_FORM_CHANGE_TYPES_H

33
include/line_break.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef GUARD_LINE_BREAK_H
#define GUARD_LINE_BREAK_H
#define BADNESS_UNFILLED 1 // Badness added per pixel diff from max width
#define BADNESS_JAGGED 1 // Badness added per pixel diff from longest, squared per line
#define BADNESS_RUNT 100 // Badness added if there's a runt
#define BADNESS_OVERFLOW 100 // Badness added per pixel overflow, squared per line (not used)
#define BADNESS_WIDE_SPACE 1 // Badness added per extra pixel width (not used)
#define MAX_SPACE_WIDTH 5
struct StringWord {
u32 startIndex:16;
u32 length:8;
u32 width:8;
};
struct StringLine {
struct StringWord *words;
u16 numWords;
u8 spaceWidth;
u8 extraSpaceWidth;
};
void StripLineBreaks(u8 *src);
void BreakStringAutomatic(u8 *src, u32 maxWidth, u32 screenLines, u8 fontId);
void BreakSubStringAutomatic(u8 *src, u32 maxWidth, u32 screenLines, u8 fontId);
bool32 IsWordSplittingChar(const u8 *src, u32 index);
u32 GetStringBadness(struct StringLine *stringLines, u32 numLines, u32 maxWidth);
void BuildNewString(struct StringLine *stringLines, u32 numLines, u32 maxLines, u8 *str);
bool32 StringHasManualBreaks(u8 *src);
#endif // GUARD_LINE_BREAK_H

View File

@ -3506,6 +3506,7 @@ u32 AI_WhoStrikesFirstPartyMon(u32 battlerAtk, u32 battlerDef, struct BattlePoke
SetBattlerAiData(battlerAtk, AI_DATA);
u32 aiMonFaster = AI_IsFaster(battlerAtk, battlerDef, moveConsidered);
FreeRestoreBattleMons(savedBattleMons);
SetBattlerAiData(battlerAtk, AI_DATA);
return aiMonFaster;
}

View File

@ -363,7 +363,6 @@ static void AnimEllipticalGustCentered(struct Sprite *sprite)
InitSpritePosToAnimTargetsCentre(sprite, FALSE);
else
InitSpritePosToAnimTarget(sprite, FALSE);
sprite->y += 20;
sprite->data[1] = 191;
sprite->callback = AnimEllipticalGust_Step;

View File

@ -15,8 +15,10 @@ static void ReverseHorizontalLungeDirection(struct Sprite *sprite);
static void DoVerticalDip(struct Sprite *sprite);
static void ReverseVerticalDipDirection(struct Sprite *sprite);
static void SlideMonToOriginalPos(struct Sprite *sprite);
static void SlideMonToOriginalPosPartner(struct Sprite *sprite);
static void SlideMonToOriginalPos_Step(struct Sprite *sprite);
static void SlideMonToOffset(struct Sprite *sprite);
static void SlideMonToOffsetPartner(struct Sprite *sprite);
static void SlideMonToOffsetAndBack(struct Sprite *sprite);
static void SlideMonToOffsetAndBack_End(struct Sprite *sprite);
static void AnimTask_WindUpLunge_Step1(u8 taskId);
@ -63,6 +65,17 @@ const struct SpriteTemplate gSlideMonToOriginalPosSpriteTemplate =
.callback = SlideMonToOriginalPos,
};
const struct SpriteTemplate gSlideMonToOriginalPosPartnerSpriteTemplate =
{
.tileTag = 0,
.paletteTag = 0,
.oam = &gDummyOamData,
.anims = gDummySpriteAnimTable,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SlideMonToOriginalPosPartner,
};
const struct SpriteTemplate gSlideMonToOffsetSpriteTemplate =
{
.tileTag = 0,
@ -74,6 +87,17 @@ const struct SpriteTemplate gSlideMonToOffsetSpriteTemplate =
.callback = SlideMonToOffset,
};
const struct SpriteTemplate gSlideMonToOffsetPartnerSpriteTemplate =
{
.tileTag = 0,
.paletteTag = 0,
.oam = &gDummyOamData,
.anims = gDummySpriteAnimTable,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SlideMonToOffsetPartner,
};
const struct SpriteTemplate gSlideMonToOffsetAndBackSpriteTemplate =
{
.tileTag = 0,
@ -487,7 +511,41 @@ static void ReverseVerticalDipDirection(struct Sprite *sprite)
// arg 2: duration
static void SlideMonToOriginalPos(struct Sprite *sprite)
{
u32 monSpriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
u32 monSpriteId;
if (!gBattleAnimArgs[0])
monSpriteId = gBattlerSpriteIds[gBattleAnimAttacker];
else
monSpriteId = gBattlerSpriteIds[gBattleAnimTarget];
sprite->data[0] = gBattleAnimArgs[2];
sprite->data[1] = gSprites[monSpriteId].x + gSprites[monSpriteId].x2;
sprite->data[2] = gSprites[monSpriteId].x;
sprite->data[3] = gSprites[monSpriteId].y + gSprites[monSpriteId].y2;
sprite->data[4] = gSprites[monSpriteId].y;
InitSpriteDataForLinearTranslation(sprite);
sprite->data[3] = 0;
sprite->data[4] = 0;
sprite->data[5] = gSprites[monSpriteId].x2;
sprite->data[6] = gSprites[monSpriteId].y2;
sprite->invisible = TRUE;
if (gBattleAnimArgs[1] == 1)
sprite->data[2] = 0;
else if (gBattleAnimArgs[1] == 2)
sprite->data[1] = 0;
sprite->data[7] = gBattleAnimArgs[1];
sprite->data[7] |= monSpriteId << 8;
sprite->callback = SlideMonToOriginalPos_Step;
}
static void SlideMonToOriginalPosPartner(struct Sprite *sprite)
{
u32 monSpriteId;
if (!gBattleAnimArgs[0])
monSpriteId = gBattlerSpriteIds[BATTLE_PARTNER(gBattleAnimAttacker)];
else
monSpriteId = gBattlerSpriteIds[BATTLE_PARTNER(gBattleAnimTarget)];
sprite->data[0] = gBattleAnimArgs[2];
sprite->data[1] = gSprites[monSpriteId].x + gSprites[monSpriteId].x2;
@ -550,9 +608,48 @@ static void SlideMonToOriginalPos_Step(struct Sprite *sprite)
// arg 4: duration
static void SlideMonToOffset(struct Sprite *sprite)
{
u8 monSpriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
u8 battler;
u8 monSpriteId;
if (!gBattleAnimArgs[0])
battler = gBattleAnimAttacker;
else
battler = gBattleAnimTarget;
if (GetBattlerSide(gBattleAnimArgs[0]) != B_SIDE_PLAYER)
monSpriteId = gBattlerSpriteIds[battler];
if (GetBattlerSide(battler) != B_SIDE_PLAYER)
{
gBattleAnimArgs[1] = -gBattleAnimArgs[1];
if (gBattleAnimArgs[3] == 1)
{
gBattleAnimArgs[2] = -gBattleAnimArgs[2];
}
}
sprite->data[0] = gBattleAnimArgs[4];
sprite->data[1] = gSprites[monSpriteId].x;
sprite->data[2] = gSprites[monSpriteId].x + gBattleAnimArgs[1];
sprite->data[3] = gSprites[monSpriteId].y;
sprite->data[4] = gSprites[monSpriteId].y + gBattleAnimArgs[2];
InitSpriteDataForLinearTranslation(sprite);
sprite->data[3] = 0;
sprite->data[4] = 0;
sprite->data[5] = monSpriteId;
sprite->invisible = TRUE;
StoreSpriteCallbackInData6(sprite, DestroyAnimSprite);
sprite->callback = TranslateSpriteLinearByIdFixedPoint;
}
static void SlideMonToOffsetPartner(struct Sprite *sprite)
{
u8 battler;
u8 monSpriteId;
if (!gBattleAnimArgs[0])
battler = BATTLE_PARTNER(gBattleAnimAttacker);
else
battler = BATTLE_PARTNER(gBattleAnimTarget);
monSpriteId = gBattlerSpriteIds[battler];
if (GetBattlerSide(battler) != B_SIDE_PLAYER)
{
gBattleAnimArgs[1] = -gBattleAnimArgs[1];
if (gBattleAnimArgs[3] == 1)

View File

@ -478,14 +478,15 @@ void AnimRockFragment(struct Sprite *sprite)
// Swirls particle in vortex. Used for moves like Fire Spin or Sand Tomb
void AnimParticleInVortex(struct Sprite *sprite)
{
if (IsDoubleBattle() //got a little lazy here will fix later
&& (gAnimMoveIndex == MOVE_BLEAKWIND_STORM
if (IsDoubleBattle()
&& (gAnimMoveIndex == MOVE_BLEAKWIND_STORM
|| gAnimMoveIndex == MOVE_SANDSEAR_STORM
|| gAnimMoveIndex == MOVE_SPRINGTIDE_STORM
|| gAnimMoveIndex == MOVE_WILDBOLT_STORM))
InitSpritePosToAnimTargetsCentre(sprite, FALSE);
else
InitSpritePosToAnimTarget(sprite, FALSE);
InitSpritePosToAnimBattler(gBattleAnimArgs[6], sprite, FALSE);
sprite->data[0] = gBattleAnimArgs[3];
sprite->data[1] = gBattleAnimArgs[2];
sprite->data[2] = gBattleAnimArgs[4];

View File

@ -31,6 +31,7 @@
#include "text.h"
#include "util.h"
#include "window.h"
#include "line_break.h"
#include "constants/battle_anim.h"
#include "constants/battle_move_effects.h"
#include "constants/battle_partner.h"
@ -2044,6 +2045,7 @@ static void PlayerHandleChooseAction(u32 battler)
ActionSelectionCreateCursorAt(gActionSelectionCursor[battler], 0);
PREPARE_MON_NICK_BUFFER(gBattleTextBuff1, battler, gBattlerPartyIndexes[battler]);
BattleStringExpandPlaceholdersToDisplayedString(gText_WhatWillPkmnDo);
BreakStringAutomatic(gDisplayedStringBattle, WindowWidthPx(B_WIN_ACTION_PROMPT), 2, FONT_NORMAL);
if (B_SHOW_PARTNER_TARGET && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && IsBattlerAlive(B_POSITION_PLAYER_RIGHT))
{

View File

@ -20,6 +20,7 @@
#include "text.h"
#include "util.h"
#include "window.h"
#include "line_break.h"
#include "constants/battle_anim.h"
#include "constants/songs.h"
#include "constants/trainers.h"
@ -298,6 +299,7 @@ static void SafariHandleChooseAction(u32 battler)
ActionSelectionCreateCursorAt(gActionSelectionCursor[battler], 0);
BattleStringExpandPlaceholdersToDisplayedString(gText_WhatWillPkmnDo2);
BreakStringAutomatic(gDisplayedStringBattle, WindowWidthPx(B_WIN_ACTION_PROMPT), 2, FONT_NORMAL);
BattlePutTextOnWindow(gDisplayedStringBattle, B_WIN_ACTION_PROMPT);
}

View File

@ -22,6 +22,7 @@
#include "text.h"
#include "trainer_hill.h"
#include "window.h"
#include "line_break.h"
#include "constants/abilities.h"
#include "constants/battle_dome.h"
#include "constants/battle_string_ids.h"
@ -162,6 +163,11 @@ const u8 gText_drastically[] = _("drastically ");
const u8 gText_severely[] = _("severely ");
static const u8 sText_TerrainReturnedToNormal[] = _("The terrain returned to normal!"); // Unused
// Remove these when done testing
static const u8 sTest_TempTestText1[] = _("This is a text for testing stuff.");
static const u8 sTest_TempTestText2[] = _("This is a text for testing stuff that should be two lines.");
static const u8 sTest_TempTestText3[] = _("This is a text for testing stuff that should be three lines so it has to have some extra text.");
const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
{
[STRINGID_TRAINER1LOSETEXT] = COMPOUND_STRING("{B_TRAINER1_LOSE_TEXT}"),
@ -1402,8 +1408,8 @@ const u8 gText_PkmnIsEvolving[] = _("What?\n{STR_VAR_1} is evolving!");
const u8 gText_CongratsPkmnEvolved[] = _("Congratulations! Your {STR_VAR_1}\nevolved into {STR_VAR_2}!{WAIT_SE}\p");
const u8 gText_PkmnStoppedEvolving[] = _("Huh? {STR_VAR_1}\nstopped evolving!\p");
const u8 gText_EllipsisQuestionMark[] = _("……?\p");
const u8 gText_WhatWillPkmnDo[] = _("What will\n{B_BUFF1} do?");
const u8 gText_WhatWillPkmnDo2[] = _("What will\n{B_PLAYER_NAME} do?");
const u8 gText_WhatWillPkmnDo[] = _("What will {B_BUFF1} do?");
const u8 gText_WhatWillPkmnDo2[] = _("What will {B_PLAYER_NAME} do?");
const u8 gText_WhatWillWallyDo[] = _("What will\nWALLY do?");
const u8 gText_LinkStandby[] = _("{PAUSE 16}Link standby…");
const u8 gText_BattleMenu[] = _("Battle{CLEAR_TO 56}Bag\nPokémon{CLEAR_TO 56}Run");
@ -2421,8 +2427,7 @@ static void GetBattlerNick(u32 battler, u8 *dst)
} \
} \
GetBattlerNick(battler, text); \
toCpy = text; \
dstWidth = GetStringLineWidth(fontId, dst, letterSpacing, lineNum, dstSize);
toCpy = text;
#define HANDLE_NICKNAME_STRING_LOWERCASE(battler) \
if (GetBattlerSide(battler) != B_SIDE_PLAYER) \
@ -2439,8 +2444,7 @@ static void GetBattlerNick(u32 battler, u8 *dst)
} \
} \
GetBattlerNick(battler, text); \
toCpy = text; \
dstWidth = GetStringLineWidth(fontId, dst, letterSpacing, lineNum, dstSize);
toCpy = text;
static const u8 *BattleStringGetOpponentNameByTrainerId(u16 trainerId, u8 *text, u8 multiplayerId, u8 battler)
{
@ -2589,17 +2593,10 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize)
{
u32 dstID = 0; // if they used dstID, why not use srcID as well?
const u8 *toCpy = NULL;
u32 lastValidSkip = 0;
u32 toCpyWidth = 0;
u32 dstWidth = 0;
// This buffer may hold either the name of a trainer, Pokémon, or item.
u8 text[max(max(max(32, TRAINER_NAME_LENGTH + 1), POKEMON_NAME_LENGTH + 1), ITEM_NAME_LENGTH)];
u8 *textStart = &text[0];
u8 multiplayerId;
u8 fontId = FONT_NORMAL;
s16 letterSpacing = 0;
u32 lineNum = 1;
u32 displayedLineNums = 1;
if (gBattleTypeFlags & BATTLE_TYPE_RECORDED_LINK)
multiplayerId = gRecordedBattleMultiplayerId;
@ -2617,11 +2614,14 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize)
while (*src != EOS)
{
toCpy = NULL;
dstWidth = GetStringLineWidth(fontId, dst, letterSpacing, lineNum, dstSize);
if (*src == PLACEHOLDER_BEGIN)
{
src++;
u32 classLength = 0;
u32 nameLength = 0;
const u8 *classString;
const u8 *nameString;
switch (*src)
{
case B_TXT_BUFF1:
@ -2804,9 +2804,24 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize)
break;
case B_TXT_TRAINER1_NAME_WITH_CLASS: // trainer1 name with trainer class
toCpy = textStart;
textStart = StringCopy(textStart, BattleStringGetOpponentClassByTrainerId(gTrainerBattleOpponent_A));
textStart = StringAppend(textStart, gText_Space2);
textStart = StringAppend(textStart, BattleStringGetOpponentNameByTrainerId(gTrainerBattleOpponent_A, textStart, multiplayerId, GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT)));
classString = BattleStringGetOpponentClassByTrainerId(gTrainerBattleOpponent_A);
while (classString[classLength] != EOS)
{
textStart[classLength] = classString[classLength];
classLength++;
}
textStart[classLength] = CHAR_SPACE;
textStart += classLength + 1;
nameString = BattleStringGetOpponentNameByTrainerId(gTrainerBattleOpponent_A, textStart, multiplayerId, GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT));
if (nameString != textStart)
{
while (nameString[nameLength] != EOS)
{
textStart[nameLength] = nameString[nameLength];
nameLength++;
}
textStart[nameLength] = EOS;
}
break;
case B_TXT_LINK_PLAYER_NAME: // link player name
toCpy = gLinkPlayers[multiplayerId].name;
@ -2926,9 +2941,24 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize)
break;
case B_TXT_TRAINER2_NAME_WITH_CLASS:
toCpy = textStart;
textStart = StringCopy(textStart, BattleStringGetOpponentClassByTrainerId(gTrainerBattleOpponent_B));
textStart = StringAppend(textStart, gText_Space2);
textStart = StringAppend(textStart, BattleStringGetOpponentNameByTrainerId(gTrainerBattleOpponent_B, textStart, multiplayerId, GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT)));
classString = BattleStringGetOpponentClassByTrainerId(gTrainerBattleOpponent_B);
while (classString[classLength] != EOS)
{
textStart[classLength] = classString[classLength];
classLength++;
}
textStart[classLength] = CHAR_SPACE;
textStart += classLength + 1;
nameString = BattleStringGetOpponentNameByTrainerId(gTrainerBattleOpponent_B, textStart, multiplayerId, GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT));
if (nameString != textStart)
{
while (nameString[nameLength] != EOS)
{
textStart[nameLength] = nameString[nameLength];
nameLength++;
}
textStart[nameLength] = EOS;
}
break;
case B_TXT_TRAINER2_LOSE_TEXT:
if (gBattleTypeFlags & BATTLE_TYPE_FRONTIER)
@ -2966,9 +2996,24 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize)
break;
case B_TXT_PARTNER_NAME_WITH_CLASS:
toCpy = textStart;
textStart = StringCopy(textStart, gTrainerClasses[GetFrontierOpponentClass(gPartnerTrainerId)].name);
textStart = StringAppend(textStart, gText_Space2);
textStart = StringAppend(textStart, BattleStringGetPlayerName(textStart, GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT)));
classString = gTrainerClasses[GetFrontierOpponentClass(gPartnerTrainerId)].name;
while (classString[classLength] != EOS)
{
textStart[classLength] = classString[classLength];
classLength++;
}
textStart[classLength] = CHAR_SPACE;
textStart += classLength + 1;
nameString = BattleStringGetPlayerName(textStart, GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT));
if (nameString != textStart)
{
while (nameString[nameLength] != EOS)
{
textStart[nameLength] = nameString[nameLength];
nameLength++;
}
textStart[nameLength] = EOS;
}
break;
case B_TXT_ATK_TRAINER_NAME:
toCpy = BattleStringGetTrainerName(text, multiplayerId, gBattlerAttacker);
@ -2999,24 +3044,42 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize)
}
else
{
classString = NULL;
switch (GetBattlerPosition(gBattlerAttacker))
{
case B_POSITION_PLAYER_RIGHT:
if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
textStart = StringCopy(textStart, gTrainerClasses[GetFrontierOpponentClass(gPartnerTrainerId)].name);
classString = gTrainerClasses[GetFrontierOpponentClass(gPartnerTrainerId)].name;
break;
case B_POSITION_OPPONENT_LEFT:
textStart = StringCopy(textStart, BattleStringGetOpponentClassByTrainerId(gTrainerBattleOpponent_A));
classString = BattleStringGetOpponentClassByTrainerId(gTrainerBattleOpponent_A);
break;
case B_POSITION_OPPONENT_RIGHT:
if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS && !BATTLE_TWO_VS_ONE_OPPONENT)
textStart = StringCopy(textStart, BattleStringGetOpponentClassByTrainerId(gTrainerBattleOpponent_B));
classString = BattleStringGetOpponentClassByTrainerId(gTrainerBattleOpponent_B);
else
textStart = StringCopy(textStart, BattleStringGetOpponentClassByTrainerId(gTrainerBattleOpponent_A));
classString = BattleStringGetOpponentClassByTrainerId(gTrainerBattleOpponent_A);
break;
}
textStart = StringAppend(textStart, gText_Space2);
textStart = StringAppend(textStart, BattleStringGetTrainerName(textStart, multiplayerId, gBattlerAttacker));
classLength = 0;
nameLength = 0;
while (classString[classLength] != EOS)
{
textStart[classLength] = classString[classLength];
classLength++;
}
textStart[classLength] = CHAR_SPACE;
textStart += 1 + classLength;
nameString = BattleStringGetTrainerName(textStart, multiplayerId, gBattlerAttacker);
if (nameString != textStart)
{
while (nameString[nameLength] != EOS)
{
textStart[nameLength] = nameString[nameLength];
nameLength++;
}
textStart[nameLength] = EOS;
}
}
break;
case B_TXT_ATK_TEAM1:
@ -3059,18 +3122,6 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize)
if (toCpy != NULL)
{
toCpyWidth = GetStringLineWidth(fontId, toCpy, letterSpacing, 1, dstSize);
if (dstWidth + toCpyWidth > BATTLE_MSG_MAX_WIDTH)
{
dst[lastValidSkip] = displayedLineNums == 1 ? CHAR_NEWLINE : CHAR_PROMPT_SCROLL;
dstWidth = GetStringLineWidth(fontId, dst, letterSpacing, lineNum, dstSize);
if (displayedLineNums == 1)
displayedLineNums++;
else
displayedLineNums = 1;
lineNum++;
}
while (*toCpy != EOS)
{
dst[dstID] = *toCpy;
@ -3090,31 +3141,7 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize)
}
else
{
toCpyWidth = GetGlyphWidth(*src, FALSE, fontId);
dst[dstID] = *src;
if (dstWidth + toCpyWidth > BATTLE_MSG_MAX_WIDTH)
{
dst[lastValidSkip] = displayedLineNums == 1 ? CHAR_NEWLINE : CHAR_PROMPT_SCROLL;
if (displayedLineNums == 1)
displayedLineNums++;
else
displayedLineNums = 1;
lineNum++;
dstWidth = 0;
}
switch (*src)
{
case CHAR_PROMPT_CLEAR:
case CHAR_PROMPT_SCROLL:
displayedLineNums = 1;
case CHAR_NEWLINE:
lineNum++;
dstWidth = 0;
//fallthrough
case CHAR_SPACE:
lastValidSkip = dstID;
break;
}
dstID++;
}
src++;
@ -3123,6 +3150,8 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize)
dst[dstID] = *src;
dstID++;
BreakStringAutomatic(dst, BATTLE_MSG_MAX_WIDTH, BATTLE_MSG_MAX_WIDTH, fontId);
return dstID;
}

View File

@ -1125,7 +1125,6 @@ static bool32 NoTargetPresent(u8 battler, u32 move)
return FALSE;
}
// TODO: Convert this to a proper FORM_CHANGE type.
static bool32 TryAegiFormChange(void)
{
// Only Aegislash with Stance Change can transform, transformed mons cannot.
@ -1140,12 +1139,12 @@ static bool32 TryAegiFormChange(void)
case SPECIES_AEGISLASH_SHIELD: // Shield -> Blade
if (IS_MOVE_STATUS(gCurrentMove))
return FALSE;
gBattleMons[gBattlerAttacker].species = SPECIES_AEGISLASH_BLADE;
TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_ATTACK);
break;
case SPECIES_AEGISLASH_BLADE: // Blade -> Shield
if (gCurrentMove != MOVE_KINGS_SHIELD)
return FALSE;
gBattleMons[gBattlerAttacker].species = SPECIES_AEGISLASH_SHIELD;
TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_KINGS_SHIELD);
break;
}
@ -2323,9 +2322,9 @@ static void Cmd_adjustdamage(void)
gBattleStruct->calculatedDamageDone = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
// TODO: might be a bug
if (gSpecialStatuses[gBattlerAttacker].gemBoost
&& MoveResultHasEffect(gBattlerTarget)
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
&& gBattleMons[gBattlerAttacker].item
&& gMovesInfo[gCurrentMove].effect != EFFECT_PLEDGE
&& gCurrentMove != MOVE_STRUGGLE)
@ -2919,6 +2918,7 @@ static void Cmd_resultmessage(void)
CMD_ARGS();
u32 stringId = 0;
u16 *moveResultFlags = &gBattleStruct->moveResultFlags[gBattlerTarget];
if (gBattleControllerExecFlags)
return;
@ -2935,8 +2935,8 @@ static void Cmd_resultmessage(void)
return;
}
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_MISSED
&& (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_DOESNT_AFFECT_FOE) || gBattleStruct->missStringId[gBattlerTarget] > 2))
if (*moveResultFlags & MOVE_RESULT_MISSED
&& (!(*moveResultFlags & MOVE_RESULT_DOESNT_AFFECT_FOE) || gBattleStruct->missStringId[gBattlerTarget] > 2))
{
if (gBattleStruct->missStringId[gBattlerTarget] > B_MSG_AVOIDED_ATK) // Wonder Guard or Levitate - show the ability pop-up
CreateAbilityPopUp(gBattlerTarget, gBattleMons[gBattlerTarget].ability, (IsDoubleBattle()) != 0);
@ -2946,7 +2946,7 @@ static void Cmd_resultmessage(void)
else
{
gBattleCommunication[MSG_DISPLAY] = 1;
switch (gBattleStruct->moveResultFlags[gBattlerTarget] & ~MOVE_RESULT_MISSED)
switch (*moveResultFlags & ~MOVE_RESULT_MISSED)
{
case MOVE_RESULT_SUPER_EFFECTIVE:
if (IsDoubleSpreadMove())
@ -3014,52 +3014,52 @@ static void Cmd_resultmessage(void)
case MOVE_RESULT_FOE_HUNG_ON:
gLastUsedItem = gBattleMons[gBattlerTarget].item;
gPotentialItemEffectBattler = gBattlerTarget;
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~(MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
*moveResultFlags &= ~(MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_HangedOnMsg;
return;
default:
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_ONE_HIT_KO)
if (*moveResultFlags & MOVE_RESULT_ONE_HIT_KO)
{
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~MOVE_RESULT_ONE_HIT_KO;
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~MOVE_RESULT_SUPER_EFFECTIVE;
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~MOVE_RESULT_NOT_VERY_EFFECTIVE;
*moveResultFlags &= ~MOVE_RESULT_ONE_HIT_KO;
*moveResultFlags &= ~MOVE_RESULT_SUPER_EFFECTIVE;
*moveResultFlags &= ~MOVE_RESULT_NOT_VERY_EFFECTIVE;
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_OneHitKOMsg;
return;
}
else if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_STURDIED)
else if (*moveResultFlags & MOVE_RESULT_STURDIED)
{
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~(MOVE_RESULT_STURDIED | MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
*moveResultFlags &= ~(MOVE_RESULT_STURDIED | MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
gSpecialStatuses[gBattlerTarget].sturdied = FALSE;
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_SturdiedMsg;
return;
}
else if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FOE_ENDURED)
else if (*moveResultFlags & MOVE_RESULT_FOE_ENDURED)
{
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~(MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
*moveResultFlags &= ~(MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_EnduredMsg;
return;
}
else if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FOE_HUNG_ON)
else if (*moveResultFlags & MOVE_RESULT_FOE_HUNG_ON)
{
gLastUsedItem = gBattleMons[gBattlerTarget].item;
gPotentialItemEffectBattler = gBattlerTarget;
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~(MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
*moveResultFlags &= ~(MOVE_RESULT_FOE_ENDURED | MOVE_RESULT_FOE_HUNG_ON);
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_HangedOnMsg;
return;
}
else if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FAILED)
else if (*moveResultFlags & MOVE_RESULT_FAILED)
{
stringId = STRINGID_BUTITFAILED;
}
else if (B_AFFECTION_MECHANICS == TRUE && (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FOE_ENDURED_AFFECTION))
else if (B_AFFECTION_MECHANICS == TRUE && (*moveResultFlags & MOVE_RESULT_FOE_ENDURED_AFFECTION))
{
gSpecialStatuses[gBattlerTarget].affectionEndured = FALSE;
gBattleStruct->moveResultFlags[gBattlerTarget] &= ~MOVE_RESULT_FOE_ENDURED_AFFECTION;
*moveResultFlags &= ~MOVE_RESULT_FOE_ENDURED_AFFECTION;
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_AffectionBasedEndurance;
return;
@ -4436,6 +4436,15 @@ static void Cmd_tryfaintmon(void)
}
else
{
if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS
&& !(gAbsentBattlerFlags & (1u << battler))
&& !IsBattlerAlive(battler))
{
gBattleMons[battler].ability = ABILITY_NONE;
BattleScriptPush(gBattlescriptCurrInstr);
gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits;
return;
}
if (cmd->battler == BS_ATTACKER)
{
destinyBondBattler = gBattlerTarget;

View File

@ -6933,7 +6933,7 @@ static u8 TrySetEnigmaBerry(u32 battler)
{
if (IsBattlerAlive(battler)
&& !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove)
&& ((TARGET_TURN_DAMAGED && gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_SUPER_EFFECTIVE) || gBattleScripting.overrideBerryRequirements)
&& ((BATTLER_TURN_DAMAGED(battler) && gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_SUPER_EFFECTIVE) || gBattleScripting.overrideBerryRequirements)
&& !(gBattleScripting.overrideBerryRequirements && gBattleMons[battler].hp == gBattleMons[battler].maxHP)
&& (B_HEAL_BLOCKING < GEN_5 || !(gStatuses3[battler] & STATUS3_HEAL_BLOCK)))
{
@ -6957,7 +6957,7 @@ static u8 DamagedStatBoostBerryEffect(u32 battler, u8 statId, u8 category)
|| (!DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove)
&& GetBattleMoveCategory(gCurrentMove) == category
&& battler != gBattlerAttacker
&& TARGET_TURN_DAMAGED))
&& BATTLER_TURN_DAMAGED(battler)))
)
{
BufferStatChange(battler, statId, STRINGID_STATROSE);
@ -10505,6 +10505,7 @@ static void UpdateMoveResultFlags(uq4_12_t modifier, u32 battler)
{
gBattleStruct->moveResultFlags[battler] |= MOVE_RESULT_DOESNT_AFFECT_FOE;
gBattleStruct->moveResultFlags[battler] &= ~(MOVE_RESULT_NOT_VERY_EFFECTIVE | MOVE_RESULT_SUPER_EFFECTIVE);
gBattleStruct->blunderPolicy = FALSE; // Don't activate if missed
}
else if (modifier == UQ_4_12(1.0))
{
@ -10981,6 +10982,10 @@ u16 GetBattleFormChangeTargetSpecies(u32 battler, u16 method)
if (GetBattlerTeraType(battler) == formChanges[i].param1)
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_ATTACK:
case FORM_CHANGE_BATTLE_KINGS_SHIELD:
targetSpecies = formChanges[i].targetSpecies;
break;
}
}
}

View File

@ -8492,7 +8492,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] =
.type = TYPE_NORMAL,
.accuracy = 0,
.pp = 40,
.target = MOVE_TARGET_USER,
.target = MOVE_TARGET_USER, // Targeting is handled through the script
.priority = 0,
.category = DAMAGE_CATEGORY_STATUS,
.zMove = { .effect = Z_EFFECT_ATK_UP_1 },
@ -12217,7 +12217,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] =
{
.name = COMPOUND_STRING("Coil"),
.description = COMPOUND_STRING(
"Coils up to raise Attack\n"
"Coils up to raise Attack,\n"
"Defense and Accuracy."),
.effect = EFFECT_COIL,
.power = 0,
@ -14421,7 +14421,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] =
.type = TYPE_FAIRY,
.accuracy = 0,
.pp = 10,
.target = MOVE_TARGET_USER,
.target = MOVE_TARGET_USER, // The targeting of Flower Shield is handled through a script
.priority = 0,
.category = DAMAGE_CATEGORY_STATUS,
.zMove = { .effect = Z_EFFECT_DEF_UP_1 },
@ -17335,7 +17335,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] =
.name = COMPOUND_STRING("Octolock"),
.description = COMPOUND_STRING(
"Traps the foe to lower Def\n"
"and Sp. Def fall each turn."),
"and Sp. Def each turn."),
.effect = EFFECT_OCTOLOCK,
.power = 0,
.type = TYPE_FIGHTING,
@ -20610,6 +20610,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] =
.priority = 0,
.category = DAMAGE_CATEGORY_PHYSICAL,
.makesContact = TRUE,
.minimizeDoubleDamage = TRUE,
.battleAnimScript = gBattleAnimMove_SupercellSlam,
},

View File

@ -789,9 +789,11 @@ static const struct FormChange sFurfrouFormChangeTable[] = {
#if P_FAMILY_HONEDGE
static const struct FormChange sAegislashFormChangeTable[] = {
{FORM_CHANGE_BATTLE_SWITCH, SPECIES_AEGISLASH_SHIELD},
{FORM_CHANGE_FAINT, SPECIES_AEGISLASH_SHIELD},
{FORM_CHANGE_END_BATTLE, SPECIES_AEGISLASH_SHIELD},
{FORM_CHANGE_BATTLE_ATTACK, SPECIES_AEGISLASH_BLADE},
{FORM_CHANGE_BATTLE_KINGS_SHIELD, SPECIES_AEGISLASH_SHIELD},
{FORM_CHANGE_BATTLE_SWITCH, SPECIES_AEGISLASH_SHIELD},
{FORM_CHANGE_FAINT, SPECIES_AEGISLASH_SHIELD},
{FORM_CHANGE_END_BATTLE, SPECIES_AEGISLASH_SHIELD},
{FORM_CHANGE_TERMINATOR},
};
#endif //P_FAMILY_HONEDGE

281
src/line_break.c Normal file
View File

@ -0,0 +1,281 @@
#include "global.h"
#include "line_break.h"
#include "text.h"
#include "malloc.h"
void StripLineBreaks(u8 *src)
{
u32 currIndex = 0;
while (src[currIndex] != EOS)
{
if (src[currIndex] == CHAR_PROMPT_SCROLL || src[currIndex] == CHAR_NEWLINE)
src[currIndex] = CHAR_SPACE;
currIndex++;
}
}
void BreakStringAutomatic(u8 *src, u32 maxWidth, u32 screenLines, u8 fontId)
{
u32 currIndex = 0;
u8 *currSrc = src;
while (src[currIndex] != EOS)
{
if (src[currIndex] == CHAR_PROMPT_CLEAR)
{
u8 replacedChar = src[currIndex + 1];
src[currIndex + 1] = EOS;
BreakSubStringAutomatic(currSrc, maxWidth, screenLines, fontId);
src[currIndex + 1] = replacedChar;
currSrc = &src[currIndex + 1];
}
currIndex++;
}
BreakSubStringAutomatic(currSrc, maxWidth, screenLines, fontId);
}
void BreakSubStringAutomatic(u8 *src, u32 maxWidth, u32 screenLines, u8 fontId)
{
// If the string already has line breaks, don't interfere with them
if (StringHasManualBreaks(src))
return;
// Sanity check
if (src[0] == EOS)
return;
u32 numChars = 1;
u32 numWords = 1;
u32 currWordIndex = 0;
u32 currWordLength = 1;
bool32 isPrevCharSplitting = FALSE;
bool32 isCurrCharSplitting;
// Get numbers of chars in string and count words
while (src[numChars] != EOS)
{
isCurrCharSplitting = IsWordSplittingChar(src, numChars);
if (isCurrCharSplitting && !isPrevCharSplitting)
numWords++;
isPrevCharSplitting = isCurrCharSplitting;
numChars++;
}
// Allocate enough space for word data
struct StringWord *allWords = Alloc(numWords*sizeof(struct StringWord));
allWords[currWordIndex].startIndex = 0;
allWords[currWordIndex].width = 0;
isPrevCharSplitting = FALSE;
// Fill in word begin index and lengths
for (u32 i = 1; i < numChars; i++)
{
isCurrCharSplitting = IsWordSplittingChar(src, i);
if (isCurrCharSplitting && !isPrevCharSplitting)
{
allWords[currWordIndex].length = currWordLength;
currWordIndex++;
currWordLength = 0;
}
else if (!isCurrCharSplitting && isPrevCharSplitting)
{
allWords[currWordIndex].startIndex = i;
allWords[currWordIndex].width = 0;
currWordLength++;
}
else
{
currWordLength++;
}
isPrevCharSplitting = isCurrCharSplitting;
}
allWords[currWordIndex].length = currWordLength;
// Fill in individual word widths
for (u32 i = 0; i < numWords; i++)
{
for (u32 j = 0; j < allWords[i].length; j++)
allWords[i].width += GetGlyphWidth(src[allWords[i].startIndex + j], FALSE, fontId);
}
// Step 1: Does it all fit one one line? Then no break
// Step 2: Try to split across minimum number of lines
u32 spaceWidth = GetGlyphWidth(0, FALSE, fontId);
u32 totalWidth = allWords[0].width;
// Calculate total widths without any line breaks
for (u32 i = 1; i < numWords; i++)
totalWidth += allWords[i].width + spaceWidth;
// If it doesn't fit on 1 line, do fancy line break calculation
// NOTE: Currently the line break calculation isn't fancy
if (totalWidth > maxWidth)
{
// Figure out how many lines are needed with naive method
u32 currLineWidth = 0;
u32 totalLines = 1;
bool32 shouldTryAgain;
for (currWordIndex = 0; currWordIndex < numWords; currWordIndex++)
{
if (currLineWidth + allWords[currWordIndex].length > maxWidth)
{
totalLines++;
currLineWidth = allWords[currWordIndex].width;
}
else
{
currLineWidth += allWords[currWordIndex].width + spaceWidth;
}
}
// LINE LAYOUT STARTS HERE
struct StringLine *stringLines;
do
{
shouldTryAgain = FALSE;
u16 targetLineWidth = totalWidth/totalLines;
stringLines = Alloc(totalLines*sizeof(struct StringLine));
for (u32 lineIndex = 0; lineIndex < totalLines; lineIndex++)
{
stringLines[lineIndex].numWords = 0;
stringLines[lineIndex].spaceWidth = spaceWidth;
stringLines[lineIndex].extraSpaceWidth = 0;
}
currWordIndex = 0;
u16 currLineIndex = 0;
stringLines[currLineIndex].words = &allWords[currWordIndex];
stringLines[currLineIndex].numWords = 1;
currLineWidth = allWords[currWordIndex].width;
currWordIndex++;
while (currWordIndex < numWords)
{
if (currLineWidth + spaceWidth + allWords[currWordIndex].width > maxWidth)
{
// go to next line
currLineIndex++;
if (currLineIndex == totalLines)
{
totalLines++;
Free(stringLines);
shouldTryAgain = TRUE;
break;
}
stringLines[currLineIndex].words = &allWords[currWordIndex];
stringLines[currLineIndex].numWords = 1;
currLineWidth = allWords[currWordIndex].width;
currWordIndex++;
}
else if (currLineWidth > targetLineWidth)
{
// go to next line
currLineIndex++;
if (currLineIndex == totalLines)
{
totalLines++;
Free(stringLines);
shouldTryAgain = TRUE;
break;
}
stringLines[currLineIndex].words = &allWords[currWordIndex];
stringLines[currLineIndex].numWords = 1;
currLineWidth = allWords[currWordIndex].width;
currWordIndex++;
}
else
{
// continue on current line
// add word and space width
currLineWidth += spaceWidth + allWords[currWordIndex].width;
stringLines[currLineIndex].numWords++;
currWordIndex++;
}
}
} while (shouldTryAgain);
//u32 currBadness = GetStringBadness(stringLines, totalLines, maxWidth);
BuildNewString(stringLines, totalLines, screenLines, src);
Free(stringLines);
}
Free(allWords);
}
// Only allow word splitting on allowed chars
bool32 IsWordSplittingChar(const u8 *src, u32 index)
{
switch (src[index])
{
case CHAR_SPACE:
return TRUE;
default:
return FALSE;
}
}
// Badness calculation
// unfilled lines scale linerarly
// jagged lines scales by the square
// runts scale linearly
// numbers not final
// ISN'T ACTUALLY USED RIGHT NOW
u32 GetStringBadness(struct StringLine *stringLines, u32 numLines, u32 maxWidth)
{
u32 badness = 0;
u32 *lineWidths = Alloc(numLines*4);
u32 widestWidth = 0;
for (u32 i = 0; i < numLines; i++)
{
lineWidths[i] = 0;
for (u32 j = 0; j < stringLines[i].numWords; j++)
lineWidths[i] += stringLines[i].words[j].width;
lineWidths[i] += (stringLines[i].numWords-1)*stringLines[i].spaceWidth;
if (lineWidths[i] > widestWidth)
widestWidth = lineWidths[i];
if (stringLines[i].numWords == 1)
badness += BADNESS_RUNT;
}
for (u32 i = 0; i < numLines; i++)
{
u32 extraSpaceWidth = 0;
if (lineWidths[i] != widestWidth)
{
// Not the best way to do this, ideally a line should be allowed to get longer than current widest
// line. But then the widest line has to be recalculated.
while (lineWidths[i] + (extraSpaceWidth + 1) * (stringLines[i].numWords - 1) < widestWidth && extraSpaceWidth < MAX_SPACE_WIDTH)
extraSpaceWidth++;
lineWidths[i] += extraSpaceWidth*(stringLines[i].numWords-1);
}
badness += (maxWidth - lineWidths[i]) * BADNESS_UNFILLED;
u32 baseBadness = (widestWidth - lineWidths[i]) * BADNESS_JAGGED;
badness += baseBadness*baseBadness;
stringLines[i].extraSpaceWidth = extraSpaceWidth;
}
Free(lineWidths);
return badness;
}
// Build the new string from the data stored in the StringLine structs
void BuildNewString(struct StringLine *stringLines, u32 numLines, u32 maxLines, u8 *str)
{
u32 srcCharIndex = 0;
for (u32 lineIndex = 0; lineIndex < numLines; lineIndex++)
{
srcCharIndex += stringLines[lineIndex].words[0].length;
for (u32 wordIndex = 1; wordIndex < stringLines[lineIndex].numWords; wordIndex++)
// Add length of word and a space
srcCharIndex += stringLines[lineIndex].words[wordIndex].length + 1;
if (lineIndex + 1 < numLines)
{
// Add the appropriate line break depending on line number
if (lineIndex >= maxLines - 1 && numLines > maxLines)
str[srcCharIndex] = CHAR_PROMPT_SCROLL;
else
str[srcCharIndex] = CHAR_NEWLINE;
srcCharIndex++;
}
}
}
bool32 StringHasManualBreaks(u8 *src)
{
u32 charIndex = 0;
while (src[charIndex] != EOS)
{
if (src[charIndex] == CHAR_PROMPT_SCROLL || src[charIndex] == CHAR_NEWLINE)
return TRUE;
charIndex++;
}
return FALSE;
}

View File

@ -3,7 +3,7 @@
ASSUMPTIONS
{
ASSUME(B_PROTEAN_LIBERO == GEN_9);
ASSUME(B_DAUNTLESS_SHIELD == GEN_9);
}
SINGLE_BATTLE_TEST("Dauntless Shield raises Defense by one stage")

View File

@ -246,7 +246,7 @@ DOUBLE_BATTLE_TEST("Intimidate is not going to trigger if a mon switches out thr
}
}
SINGLE_BATTLE_TEST("Intimidate activates when it's no longer effected by Neutralizing Gas")
SINGLE_BATTLE_TEST("Intimidate activates when it's no longer effected by Neutralizing Gas - switching out")
{
GIVEN {
PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); }
@ -263,3 +263,91 @@ SINGLE_BATTLE_TEST("Intimidate activates when it's no longer effected by Neutral
SEND_IN_MESSAGE("Wobbuffet");
}
}
SINGLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutralizing Gas - switching moves")
{
u32 move;
PARAMETRIZE { move = MOVE_U_TURN; }
PARAMETRIZE { move = MOVE_HEALING_WISH; }
PARAMETRIZE { move = MOVE_BATON_PASS; }
GIVEN {
ASSUME(gMovesInfo[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE);
ASSUME(gMovesInfo[MOVE_HEALING_WISH].effect == EFFECT_HEALING_WISH);
ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS);
PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); }
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); }
} WHEN {
TURN { MOVE(player, move); SEND_OUT(player, 1); }
} SCENE {
ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS);
MESSAGE("Neutralizing gas filled the area!");
ANIMATION(ANIM_TYPE_MOVE, move, player);
MESSAGE("The effects of the neutralizing gas wore off!");
ABILITY_POPUP(opponent, ABILITY_INTIMIDATE);
SEND_IN_MESSAGE("Wobbuffet");
} THEN {
if (move == MOVE_HEALING_WISH)
EXPECT_EQ(player->hp, player->maxHP);
}
}
SINGLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutralizing Gas - opponent caused switches")
{
u32 move, item;
PARAMETRIZE { move = MOVE_TACKLE; item = ITEM_EJECT_BUTTON; }
PARAMETRIZE { move = MOVE_GROWL; item = ITEM_EJECT_PACK; }
PARAMETRIZE { move = MOVE_ROAR; item = ITEM_NONE; }
PARAMETRIZE { move = MOVE_DRAGON_TAIL; item = ITEM_NONE; }
GIVEN {
ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON);
ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK);
ASSUME(gMovesInfo[MOVE_GROWL].effect == EFFECT_ATTACK_DOWN);
ASSUME(gMovesInfo[MOVE_ROAR].effect == EFFECT_ROAR);
ASSUME(gMovesInfo[MOVE_DRAGON_TAIL].effect == EFFECT_HIT_SWITCH_TARGET);
PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); Item(item); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); }
} WHEN {
if (item != ITEM_NONE) {
TURN { MOVE(opponent, move); SEND_OUT(player, 1); }
} else {
TURN { MOVE(opponent, move); }
}
} SCENE {
ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS);
MESSAGE("Neutralizing gas filled the area!");
ANIMATION(ANIM_TYPE_MOVE, move, opponent);
if (item != ITEM_NONE)
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
MESSAGE("The effects of the neutralizing gas wore off!");
ABILITY_POPUP(opponent, ABILITY_INTIMIDATE);
if (item != ITEM_NONE) {
SEND_IN_MESSAGE("Wobbuffet");
} else {
MESSAGE("Wobbuffet was dragged out!");
}
}
}
SINGLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutralizing Gas - fainted")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_FELL_STINGER].effect == EFFECT_FELL_STINGER);
PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); HP(1); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); }
} WHEN {
TURN { MOVE(opponent, MOVE_FELL_STINGER); SEND_OUT(player, 1); }
} SCENE {
ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS);
MESSAGE("Neutralizing gas filled the area!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_FELL_STINGER, opponent);
MESSAGE("The effects of the neutralizing gas wore off!");
ABILITY_POPUP(opponent, ABILITY_INTIMIDATE);
MESSAGE("Weezing fainted!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
SEND_IN_MESSAGE("Wobbuffet");
}
}

View File

@ -896,3 +896,17 @@ AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch into mon with good type matchup
TURN { MOVE(player, MOVE_TACKLE); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI correctly handles abilities when scoring moves")
{
GIVEN {
ASSUME(B_PRANKSTER_DARK_TYPES >= GEN_7);
ASSUME(gSpeciesInfo[SPECIES_GRENINJA].types[1] == TYPE_DARK);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES);
PLAYER(SPECIES_GRENINJA) { Moves(MOVE_WATER_GUN); }
OPPONENT(SPECIES_WHIMSICOTT) { Ability(ABILITY_PRANKSTER); Moves(MOVE_LEECH_SEED, MOVE_STUN_SPORE, MOVE_ABSORB); }
OPPONENT(SPECIES_WHIMSICOTT) { Ability(ABILITY_INFILTRATOR); }
} WHEN {
TURN { MOVE(player, MOVE_WATER_GUN); EXPECT_MOVE(opponent, MOVE_ABSORB); }
}
}

View File

@ -0,0 +1,67 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(gItemsInfo[ITEM_BLUNDER_POLICY].holdEffect == HOLD_EFFECT_BLUNDER_POLICY);
}
SINGLE_BATTLE_TEST("Blunder Policy raises the users speed by 2 stages if the user misses")
{
PASSES_RANDOMLY(3, 10, RNG_ACCURACY);
GIVEN {
ASSUME(gMovesInfo[MOVE_FOCUS_BLAST].accuracy == 70);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_BLUNDER_POLICY); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_FOCUS_BLAST); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_BLAST, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
} THEN {
EXPECT(player->item == ITEM_NONE);
EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 2);
}
}
SINGLE_BATTLE_TEST("Blunder Policy will never trigger if the move fails due to an immunity")
{
PASSES_RANDOMLY(10, 10, RNG_ACCURACY);
GIVEN {
ASSUME(gMovesInfo[MOVE_FOCUS_BLAST].accuracy == 70);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_BLUNDER_POLICY); }
OPPONENT(SPECIES_GASTLY);
} WHEN {
TURN { MOVE(player, MOVE_FOCUS_BLAST); }
} SCENE {
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_BLAST, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
}
} THEN {
EXPECT(player->item == ITEM_BLUNDER_POLICY);
EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
}
}
SINGLE_BATTLE_TEST("Blunder Policy will never trigger if the move fails due to Protect")
{
PASSES_RANDOMLY(10, 10, RNG_ACCURACY);
GIVEN {
ASSUME(gMovesInfo[MOVE_FOCUS_BLAST].accuracy == 70);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_BLUNDER_POLICY); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_FOCUS_BLAST); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_BLAST, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
}
} THEN {
EXPECT(player->item == ITEM_BLUNDER_POLICY);
EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
}
}

View File

@ -58,3 +58,19 @@ SINGLE_BATTLE_TEST("Enigma Berry does nothing if Heal Block applies")
}
}
}
DOUBLE_BATTLE_TEST("Enigma Berry doesn't trigger if partner was hit")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT) { Item(ITEM_ENIGMA_BERRY); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight);
} THEN {
EXPECT(opponentRight->item == ITEM_ENIGMA_BERRY);
}
}

View File

@ -73,3 +73,19 @@ SINGLE_BATTLE_TEST("Kee Berry doesn't trigger if the item hold user used a physi
EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE);
}
}
DOUBLE_BATTLE_TEST("Kee Berry doesn't trigger if partner was hit")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT) { Item(ITEM_KEE_BERRY); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight);
} THEN {
EXPECT(opponentRight->item == ITEM_KEE_BERRY);
}
}

View File

@ -73,3 +73,19 @@ SINGLE_BATTLE_TEST("Maranga Berry doesn't trigger if the item hold user used a s
EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE);
}
}
DOUBLE_BATTLE_TEST("Maranga Berry doesn't trigger if partner was hit")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT) { Item(ITEM_MARANGA_BERRY); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight);
} THEN {
EXPECT(opponentRight->item == ITEM_MARANGA_BERRY);
}
}

View File

@ -468,3 +468,25 @@ SINGLE_BATTLE_TEST("Red Card prevents Emergency Exit activation when triggered")
}
TO_DO_BATTLE_TEST("Red Card activates but fails if the attacker has Dynamaxed");
SINGLE_BATTLE_TEST("Red Card activates before Eject Pack")
{
GIVEN {
ASSUME(MoveHasAdditionalEffectSelf(MOVE_OVERHEAT, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE);
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_OVERHEAT); MOVE(opponent, MOVE_TACKLE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_OVERHEAT, player);
NONE_OF {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
MESSAGE("Wobbuffet is switched out with the Eject Button!");
}
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
}
}

View File

@ -0,0 +1,37 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_FLOWER_SHIELD].effect == EFFECT_FLOWER_SHIELD);
}
DOUBLE_BATTLE_TEST("Flower Shield raises the defense of all grass type pokemon")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS);
ASSUME(gSpeciesInfo[SPECIES_TANGROWTH].types[0] == TYPE_GRASS);
ASSUME(gSpeciesInfo[SPECIES_SUNKERN].types[0] == TYPE_GRASS);
ASSUME(gSpeciesInfo[SPECIES_SUNFLORA].types[0] == TYPE_GRASS);
PLAYER(SPECIES_TANGELA);
PLAYER(SPECIES_TANGROWTH);
OPPONENT(SPECIES_SUNKERN);
OPPONENT(SPECIES_SUNFLORA);
} WHEN {
TURN { MOVE(playerLeft, MOVE_FLOWER_SHIELD); MOVE(playerRight, MOVE_CELEBRATE); }
} SCENE {
MESSAGE("Tangela used Flower Shield!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft);
MESSAGE("Tangela's Defense rose!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
MESSAGE("The opposing Sunkern's Defense rose!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
MESSAGE("Tangrowth's Defense rose!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
MESSAGE("The opposing Sunflora's Defense rose!");
}
}

View File

@ -3,7 +3,7 @@
DOUBLE_BATTLE_TEST("Spread Moves: Ability and Item effects activate correctly after a multi target move")
{
// TODO: Might be a bug, verify on showdown
// TODO: Might be a bug, verify on cardridge
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LUM_BERRY); }
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_COVERT_CLOAK); }

View File

@ -26,3 +26,21 @@ SINGLE_BATTLE_TEST("Confusion adds a 50/33% chance to hit self with 40 power")
EXPECT_EQ(damage[0], damage[1]);
}
}
SINGLE_BATTLE_TEST("Confusion self hit does not consume Gems")
{
PASSES_RANDOMLY(B_CONFUSION_SELF_DMG_CHANCE >= GEN_7 ? 33 : 50, 100, RNG_CONFUSION);
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMAL_GEM); };
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_CONFUSE_RAY); MOVE(player, MOVE_TACKLE); }
} SCENE {
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
MESSAGE("Normal Gem strengthened Wobbuffet's power!");
}
MESSAGE("It hurt itself in its confusion!");
}
}

View File

@ -1545,6 +1545,7 @@ static void fprint_species(FILE *f, const char *prefix, struct String s)
static const unsigned char *male = (unsigned char *)u8"";
static const unsigned char *female = (unsigned char *)u8"";
static const unsigned char *e_diacritic = (unsigned char *)u8"é";
static const unsigned char *right_single_quotation_mark = (unsigned char *)u8"";
for (int i = 0; i < s.string_n; i++)
{
unsigned char c = s.string[i];
@ -1562,7 +1563,7 @@ static void fprint_species(FILE *f, const char *prefix, struct String s)
underscore = false;
fputc(c - 'a' + 'A', f);
}
else if (c == '\'' || c == '%')
else if (c == '\'' || c == '%' || is_utf8_character(s, &i, right_single_quotation_mark))
{
// Do nothing.
}