merge rhh/upcoming
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
1163
docs/tutorials/how_to_new_pokemon_1_10_0.md
Normal file
|
Before Width: | Height: | Size: 686 B After Width: | Height: | Size: 813 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.3 KiB |
@ -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
|
||||
|
||||
@ -16,4 +16,4 @@ JASC-PAL
|
||||
64 64 64
|
||||
45 43 43
|
||||
8 8 8
|
||||
0 0 0
|
||||
255 255 255
|
||||
|
||||
|
Before Width: | Height: | Size: 691 B After Width: | Height: | Size: 618 B |
|
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 563 B |
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
@ -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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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))
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
|
||||
@ -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
@ -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;
|
||||
}
|
||||
@ -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")
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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); }
|
||||
}
|
||||
}
|
||||
|
||||
67
test/battle/hold_effect/blunder_policy.c
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
37
test/battle/move_effect/flower_shield.c
Normal 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!");
|
||||
}
|
||||
}
|
||||
@ -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); }
|
||||
|
||||
@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
}
|
||||
|
||||