Merge branch 'upcoming' into RHH/content/gen9SnowWeather

This commit is contained in:
Salem 2023-05-07 17:27:58 +02:00 committed by GitHub
commit fe5e59c910
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 4712 additions and 219 deletions

View File

@ -480,7 +480,7 @@ endif
check: $(TESTELF)
@cp $< $(HEADLESSELF)
$(PATCHELF) $(HEADLESSELF) gTestRunnerHeadless '\x01' gTestRunnerSkipIsFail "$(TEST_SKIP_IS_FAIL)"
$(ROMTESTHYDRA) $(ROMTEST) $(HEADLESSELF)
$(ROMTESTHYDRA) $(ROMTEST) $(OBJCOPY) $(HEADLESSELF)
libagbsyscall:
@$(MAKE) -C libagbsyscall TOOLCHAIN=$(TOOLCHAIN) MODERN=$(MODERN)

View File

@ -453,7 +453,7 @@ BattleScript_StealthRockActivates::
setstealthrock BattleScript_MoveEnd
printfromtable gDmgHazardsStringIds
waitmessage B_WAIT_TIME_LONG
goto BattleScript_MoveEnd
return
BattleScript_EffectDireClaw::
setmoveeffect MOVE_EFFECT_DIRE_CLAW
@ -467,7 +467,7 @@ BattleScript_SpikesActivates::
trysetspikes BattleScript_MoveEnd
printfromtable gDmgHazardsStringIds
waitmessage B_WAIT_TIME_LONG
goto BattleScript_MoveEnd
return
BattleScript_EffectAttackUpUserAlly:
jumpifnoally BS_ATTACKER, BattleScript_EffectAttackUp
@ -10034,23 +10034,42 @@ BattleScript_PrintPlayerForfeitedLinkBattle::
end2
BattleScript_TotemFlaredToLife::
playanimation BS_ATTACKER, B_ANIM_TOTEM_FLARE
playanimation BS_ATTACKER, B_ANIM_TOTEM_FLARE, NULL
printstring STRINGID_AURAFLAREDTOLIFE
waitmessage B_WAIT_TIME_LONG
goto BattleScript_ApplyTotemVarBoost
call BattleScript_ApplyTotemVarBoost
end2
@ remove the mirror herb, do totem loop
BattleScript_MirrorHerbCopyStatChangeEnd2::
call BattleScript_MirrorHerbCopyStatChange
end2
BattleScript_MirrorHerbCopyStatChange::
playanimation BS_SCRIPTING, B_ANIM_HELD_ITEM_EFFECT, NULL
printstring STRINGID_MIRRORHERBCOPIED
waitmessage B_WAIT_TIME_LONG
removeitem BS_SCRIPTING
call BattleScript_TotemVar_Ret
copybyte gBattlerAttacker, sSAVED_BATTLER @ restore the original attacker just to be safe
return
BattleScript_TotemVar::
call BattleScript_TotemVar_Ret
end2
BattleScript_TotemVar_Ret::
gettotemboost BattleScript_ApplyTotemVarBoost
BattleScript_TotemVarEnd:
end2
return
BattleScript_ApplyTotemVarBoost:
statbuffchange STAT_CHANGE_ALLOW_PTR, BattleScript_TotemVarEnd
setgraphicalstatchangevalues
playanimation BS_SCRIPTING, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1
BattleScript_TotemVarPrintStatMsg:
printfromtable gStatUpStringIds
waitmessage B_WAIT_TIME_LONG
goto BattleScript_TotemVar @loop until stats bitfield is empty
goto BattleScript_TotemVar_Ret @loop until stats bitfield is empty
BattleScript_AnnounceAirLockCloudNine::
call BattleScript_AbilityPopUp

View File

@ -75,7 +75,9 @@ BattleScript_ItemCureStatus::
BattleScript_ItemHealAndCureStatus::
call BattleScript_UseItemMessage
itemrestorehp
curestatus BS_ATTACKER
itemcurestatus
printstring STRINGID_ITEMRESTOREDSPECIESHEALTH
waitmessage B_WAIT_TIME_LONG
bichalfword gMoveResultFlags, MOVE_RESULT_NO_EFFECT
orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE
healthbarupdate BS_ATTACKER

View File

@ -1,37 +1,18 @@
#include "global.h"
#include "malloc.h"
static void *sHeapStart;
static u32 sHeapSize;
#define MALLOC_SYSTEM_ID 0xA3A3
struct MemBlock {
// Whether this block is currently allocated.
bool16 flag;
// Magic number used for error checking. Should equal MALLOC_SYSTEM_ID.
u16 magic;
// Size of the block (not including this header struct).
u32 size;
// Previous block pointer. Equals sHeapStart if this is the first block.
struct MemBlock *prev;
// Next block pointer. Equals sHeapStart if this is the last block.
struct MemBlock *next;
// Data in the memory block. (Arrays of length 0 are a GNU extension.)
u8 data[0];
};
void PutMemBlockHeader(void *block, struct MemBlock *prev, struct MemBlock *next, u32 size)
{
struct MemBlock *header = (struct MemBlock *)block;
header->flag = FALSE;
header->allocated = FALSE;
header->locationHi = 0;
header->magic = MALLOC_SYSTEM_ID;
header->size = size;
header->locationLo = 0;
header->prev = prev;
header->next = next;
}
@ -41,7 +22,7 @@ void PutFirstMemBlockHeader(void *block, u32 size)
PutMemBlockHeader(block, (struct MemBlock *)block, (struct MemBlock *)block, size - sizeof(struct MemBlock));
}
void *AllocInternal(void *heapStart, u32 size)
void *AllocInternal(void *heapStart, u32 size, const char *location)
{
struct MemBlock *pos = (struct MemBlock *)heapStart;
struct MemBlock *head = pos;
@ -55,14 +36,14 @@ void *AllocInternal(void *heapStart, u32 size)
for (;;) {
// Loop through the blocks looking for unused block that's big enough.
if (!pos->flag) {
if (!pos->allocated) {
foundBlockSize = pos->size;
if (foundBlockSize >= size) {
if (foundBlockSize - size < 2 * sizeof(struct MemBlock)) {
// The block isn't much bigger than the requested size,
// so just use it.
pos->flag = TRUE;
pos->allocated = TRUE;
} else {
// The block is significantly bigger than the requested
// size, so split the rest into a separate block.
@ -71,7 +52,7 @@ void *AllocInternal(void *heapStart, u32 size)
splitBlock = (struct MemBlock *)(pos->data + size);
pos->flag = TRUE;
pos->allocated = TRUE;
pos->size = size;
PutMemBlockHeader(splitBlock, pos, pos->next, foundBlockSize);
@ -82,6 +63,9 @@ void *AllocInternal(void *heapStart, u32 size)
splitBlock->next->prev = splitBlock;
}
pos->locationHi = ((uintptr_t)location) >> 14;
pos->locationLo = (uintptr_t)location;
return pos->data;
}
}
@ -98,12 +82,12 @@ void FreeInternal(void *heapStart, void *pointer)
if (pointer) {
struct MemBlock *head = (struct MemBlock *)heapStart;
struct MemBlock *block = (struct MemBlock *)((u8 *)pointer - sizeof(struct MemBlock));
block->flag = FALSE;
block->allocated = FALSE;
// If the freed block isn't the last one, merge with the next block
// if it's not in use.
if (block->next != head) {
if (!block->next->flag) {
if (!block->next->allocated) {
block->size += sizeof(struct MemBlock) + block->next->size;
block->next->magic = 0;
block->next = block->next->next;
@ -115,7 +99,7 @@ void FreeInternal(void *heapStart, void *pointer)
// If the freed block isn't the first one, merge with the previous block
// if it's not in use.
if (block != head) {
if (!block->prev->flag) {
if (!block->prev->allocated) {
block->prev->next = block->next;
if (block->next != head)
@ -128,9 +112,9 @@ void FreeInternal(void *heapStart, void *pointer)
}
}
void *AllocZeroedInternal(void *heapStart, u32 size)
void *AllocZeroedInternal(void *heapStart, u32 size, const char *location)
{
void *mem = AllocInternal(heapStart, size);
void *mem = AllocInternal(heapStart, size, location);
if (mem != NULL) {
if (size & 3)
@ -175,14 +159,14 @@ void InitHeap(void *heapStart, u32 heapSize)
PutFirstMemBlockHeader(heapStart, heapSize);
}
void *Alloc(u32 size)
void *Alloc_(u32 size, const char *location)
{
return AllocInternal(sHeapStart, size);
return AllocInternal(sHeapStart, size, location);
}
void *AllocZeroed(u32 size)
void *AllocZeroed_(u32 size, const char *location)
{
return AllocZeroedInternal(sHeapStart, size);
return AllocZeroedInternal(sHeapStart, size, location);
}
void Free(void *pointer)
@ -207,3 +191,16 @@ bool32 CheckHeap()
return TRUE;
}
const struct MemBlock *HeapHead(void)
{
return (const struct MemBlock *)sHeapStart;
}
const char *MemBlockLocation(const struct MemBlock *block)
{
if (!block->allocated)
return NULL;
return (const char *)(ROM_START | (block->locationHi << 14) | block->locationLo);
}

View File

@ -11,11 +11,48 @@
#define TRY_FREE_AND_SET_NULL(ptr) if (ptr != NULL) FREE_AND_SET_NULL(ptr)
#define MALLOC_SYSTEM_ID 0xA3A3
struct MemBlock
{
// Whether this block is currently allocated.
u16 allocated:1;
u16 unused_00:4;
// High 11 bits of location pointer.
u16 locationHi:11;
// Magic number used for error checking. Should equal MALLOC_SYSTEM_ID.
u16 magic;
// Size of the block (not including this header struct).
u32 size:18;
// Low 14 bits of location pointer.
u32 locationLo:14;
// Previous block pointer. Equals sHeapStart if this is the first block.
struct MemBlock *prev;
// Next block pointer. Equals sHeapStart if this is the last block.
struct MemBlock *next;
// Data in the memory block. (Arrays of length 0 are a GNU extension.)
u8 data[0];
};
extern u8 gHeap[];
void *Alloc(u32 size);
void *AllocZeroed(u32 size);
#define Alloc(size) Alloc_(size, __FILE__ ":" STR(__LINE__))
#define AllocZeroed(size) AllocZeroed_(size, __FILE__ ":" STR(__LINE__))
void *Alloc_(u32 size, const char *location);
void *AllocZeroed_(u32 size, const char *location);
void Free(void *pointer);
void InitHeap(void *pointer, u32 size);
const struct MemBlock *HeapHead(void);
const char *MemBlockLocation(const struct MemBlock *block);
#endif // GUARD_ALLOC_H

View File

@ -147,6 +147,7 @@ struct ProtectStruct
u16 quash:1;
u16 shellTrap:1;
u16 silkTrapped:1;
u16 eatMirrorHerb:1;
u32 physicalDmg;
u32 specialDmg;
u8 physicalBattlerId;
@ -657,6 +658,7 @@ struct BattleStruct
u8 storedLunarDance:4; // Each battler as a bit.
u16 supremeOverlordModifier[MAX_BATTLERS_COUNT];
u8 itemPartyIndex[MAX_BATTLERS_COUNT];
u8 itemMoveIndex[MAX_BATTLERS_COUNT];
bool8 trainerSlideHalfHpMsgDone;
u8 trainerSlideFirstCriticalHitMsgState:2;
u8 trainerSlideFirstSuperEffectiveHitMsgState:2;

View File

@ -1,6 +1,8 @@
#ifndef GUARD_BATTLE_SCRIPTS_H
#define GUARD_BATTLE_SCRIPTS_H
extern const u8 BattleScript_MirrorHerbCopyStatChange[];
extern const u8 BattleScript_MirrorHerbCopyStatChangeEnd2[];
extern const u8 BattleScript_NotAffected[];
extern const u8 BattleScript_HitFromCritCalc[];
extern const u8 BattleScript_MoveEnd[];

View File

@ -8,10 +8,15 @@
#define P_UPDATED_EGG_GROUPS GEN_LATEST // Since Gen 8, certain Pokémon have gained new egg groups.
// Breeding settings
#define P_NIDORAN_M_DITTO_BREED GEN_LATEST // Since Gen 5, when Nidoran♂ breeds with Ditto it can produce Nidoran♀ offspring. Before, it would only yield male offspring. This change also applies to Volbeat.
#define P_INCENSE_BREEDING GEN_LATEST // Since Gen 9, cross-generation Baby Pokémon don't require Incense being held by the parents to be obtained via breeding.
#define P_EGG_HATCH_LEVEL GEN_LATEST // Since Gen 4, Pokémon will hatch from eggs at level 1 instead of 5.
#define P_BALL_INHERITING GEN_LATEST // Since Gen 6, Eggs from the Daycare will inherit the Poké Ball from their mother. From Gen7 onwards, the father can pass it down as well, as long as it's of the same species as the mother.
#define P_NIDORAN_M_DITTO_BREED GEN_LATEST // Since Gen 5, when Nidoran♂ breeds with Ditto it can produce Nidoran♀ offspring. Before, it would only yield male offspring. This change also applies to Volbeat.
#define P_INCENSE_BREEDING GEN_LATEST // Since Gen 9, cross-generation Baby Pokémon don't require Incense being held by the parents to be obtained via breeding.
#define P_EGG_HATCH_LEVEL GEN_LATEST // Since Gen 4, Pokémon will hatch from eggs at level 1 instead of 5.
#define P_BALL_INHERITING GEN_LATEST // Since Gen 6, Eggs from the Daycare will inherit the Poké Ball from their mother. From Gen 7 onwards, the father can pass it down as well, as long as it's of the same species as the mother.
#define P_TM_INHERITANCE GEN_LATEST // Since Gen 6, the father no longer passes down TMs to the baby.
#define P_MOTHER_EGG_MOVE_INHERITANCE GEN_LATEST // Since Gen 6, the mother can also pass down Egg Moves.
#define P_NATURE_INHERITANCE GEN_LATEST // In Gen 3, Everstone grants Ditto and mothers a 50% chance to pass on Nature. Since Gen 4, anyone can pass on nature. Since Gen 5, the chance is 100%.
#define P_ABILITY_INHERITANCE GEN_LATEST // In B2W2, a female Pokémon has an 80% chance of passing down their ability if bred with a male. Since Gen 6, the chance is 80% for normal ability and 60% for Hidden Ability, and anyone can pass down their abilities if bred with Ditto. NOTE: BW's effect: 60% chance to pass down HA and random for normal ability has been omitted.
#define P_EGG_MOVE_TRANSFER GEN_LATEST // Starting in Gen 8, if two Pokémon of the same species are together in the Daycare, one knows an Egg Move, and the other has an empty slot, the other Pokémon will receive the Egg Move in the empty slot. In Gen 9, if a Pokémon holds a Mirror Herb, it will receive Egg Moves from the other regardless of species.
// Species-specific settings
#define P_SHEDINJA_BALL GEN_LATEST // Since Gen 4, Shedinja requires a Poké Ball for its evolution. In Gen 3, Shedinja inherits Nincada's Ball.

View File

@ -659,12 +659,13 @@
#define STRINGID_PKMNFROSTBITEHEALED 657
#define STRINGID_PKMNFROSTBITEHEALED2 658
#define STRINGID_PKMNFROSTBITEHEALEDBY 659
#define STRINGID_STARTEDSNOW 660
#define STRINGID_SNOWCONTINUES 661
#define STRINGID_SNOWSTOPPED 662
#define STRINGID_SNOWWARNINGSNOW 663
#define STRINGID_MIRRORHERBCOPIED 660
#define STRINGID_STARTEDSNOW 661
#define STRINGID_SNOWCONTINUES 662
#define STRINGID_SNOWSTOPPED 663
#define STRINGID_SNOWWARNINGSNOW 664
#define BATTLESTRINGS_COUNT 664
#define BATTLESTRINGS_COUNT 665
// This is the string id that gBattleStringsTable starts with.
// String ids before this (e.g. STRINGID_INTROMSG) are not in the table,

View File

@ -22,6 +22,9 @@
#define INTR_CHECK (*(u16 *)0x3007FF8)
#define INTR_VECTOR (*(void **)0x3007FFC)
#define ROM_START 0x8000000
#define ROM_END 0xA000000
#define EWRAM_START 0x02000000
#define EWRAM_END (EWRAM_START + 0x40000)
#define IWRAM_START 0x03000000

View File

@ -147,6 +147,9 @@
#define CAT(a, b) CAT_(a, b)
#define CAT_(a, b) a ## b
#define STR(a) STR_(a)
#define STR_(a) #a
// Converts a string to a compound literal, essentially making it a pointer to const u8
#define COMPOUND_STRING(str) (const u8[]) _(str)

View File

@ -443,6 +443,7 @@ void BoxMonToMon(const struct BoxPokemon *src, struct Pokemon *dest);
u8 GetLevelFromMonExp(struct Pokemon *mon);
u8 GetLevelFromBoxMonExp(struct BoxPokemon *boxMon);
u16 GiveMoveToMon(struct Pokemon *mon, u16 move);
u16 GiveMoveToBoxMon(struct BoxPokemon *boxMon, u16 move);
u16 GiveMoveToBattleMon(struct BattlePokemon *mon, u16 move);
void SetMonMoveSlot(struct Pokemon *mon, u16 move, u8 slot);
void SetBattleMonMoveSlot(struct BattlePokemon *mon, u16 move, u8 slot);

View File

@ -45,6 +45,8 @@ enum
RECORDED_PARTY_INDEX,
RECORDED_BATTLE_PALACE_ACTION,
RECORDED_ITEM_ID,
RECORDED_ITEM_TARGET,
RECORDED_ITEM_MOVE,
};
extern u32 gRecordedBattleRngSeed;

View File

@ -119,6 +119,7 @@ EWRAM_DATA u8 gBattleAnimAttacker = 0;
EWRAM_DATA u8 gBattleAnimTarget = 0;
EWRAM_DATA u16 gAnimBattlerSpecies[MAX_BATTLERS_COUNT] = {0};
EWRAM_DATA u8 gAnimCustomPanning = 0;
EWRAM_DATA static bool8 sAnimHideHpBoxes = FALSE;
#include "data/battle_anim.h"
@ -232,7 +233,6 @@ void LaunchBattleAnimation(u32 animType, u32 animId)
{
s32 i;
const u8 *const *animsTable;
bool32 hideHpBoxes;
if (gTestRunnerEnabled)
{
@ -261,7 +261,7 @@ void LaunchBattleAnimation(u32 animType, u32 animId)
break;
}
hideHpBoxes = !(animType == ANIM_TYPE_MOVE && animId == MOVE_TRANSFORM);
sAnimHideHpBoxes = !(animType == ANIM_TYPE_MOVE && animId == MOVE_TRANSFORM);
if (animType != ANIM_TYPE_MOVE)
{
switch (animId)
@ -276,10 +276,10 @@ void LaunchBattleAnimation(u32 animType, u32 animId)
case B_ANIM_MEGA_EVOLUTION:
case B_ANIM_PRIMAL_REVERSION:
case B_ANIM_GULP_MISSILE:
hideHpBoxes = TRUE;
sAnimHideHpBoxes = TRUE;
break;
default:
hideHpBoxes = FALSE;
sAnimHideHpBoxes = FALSE;
break;
}
}
@ -287,7 +287,7 @@ void LaunchBattleAnimation(u32 animType, u32 animId)
if (!IsContest())
{
InitPrioritiesForVisibleBattlers();
UpdateOamPriorityInAllHealthboxes(0, hideHpBoxes);
UpdateOamPriorityInAllHealthboxes(0, sAnimHideHpBoxes);
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
if (GetBattlerSide(i) != B_SIDE_PLAYER)
@ -764,7 +764,8 @@ static void Cmd_end(void)
if (!IsContest())
{
InitPrioritiesForVisibleBattlers();
UpdateOamPriorityInAllHealthboxes(1, TRUE);
UpdateOamPriorityInAllHealthboxes(1, sAnimHideHpBoxes);
sAnimHideHpBoxes = FALSE;
}
gAnimScriptActive = FALSE;
}

View File

@ -1442,6 +1442,8 @@ static void RecordedOpponentHandleChooseItem(void)
u8 byte1 = RecordedBattle_GetBattlerAction(RECORDED_ITEM_ID, gActiveBattler);
u8 byte2 = RecordedBattle_GetBattlerAction(RECORDED_ITEM_ID, gActiveBattler);
gBattleStruct->chosenItem[gActiveBattler] = (byte1 << 8) | byte2;
gBattleStruct->itemPartyIndex[gActiveBattler] = RecordedBattle_GetBattlerAction(RECORDED_ITEM_TARGET, gActiveBattler);
gBattleStruct->itemMoveIndex[gActiveBattler] = RecordedBattle_GetBattlerAction(RECORDED_ITEM_MOVE, gActiveBattler);
BtlController_EmitOneReturnValue(BUFFER_B, gBattleStruct->chosenItem[gActiveBattler]);
RecordedOpponentBufferExecCompleted();
}

View File

@ -1466,6 +1466,8 @@ static void RecordedPlayerHandleChooseItem(void)
u8 byte1 = RecordedBattle_GetBattlerAction(RECORDED_ITEM_ID, gActiveBattler);
u8 byte2 = RecordedBattle_GetBattlerAction(RECORDED_ITEM_ID, gActiveBattler);
gBattleStruct->chosenItem[gActiveBattler] = (byte1 << 8) | byte2;
gBattleStruct->itemPartyIndex[gActiveBattler] = RecordedBattle_GetBattlerAction(RECORDED_ITEM_TARGET, gActiveBattler);
gBattleStruct->itemMoveIndex[gActiveBattler] = RecordedBattle_GetBattlerAction(RECORDED_ITEM_MOVE, gActiveBattler);
BtlController_EmitOneReturnValue(BUFFER_B, gBattleStruct->chosenItem[gActiveBattler]);
RecordedPlayerBufferExecCompleted();
}

View File

@ -265,7 +265,7 @@ static const u8 sText_XFoundOneY[] = _("{B_ATK_NAME_WITH_PREFIX} found\none {B_L
static const u8 sText_SoothingAroma[] = _("A soothing aroma wafted\nthrough the area!");
static const u8 sText_ItemsCantBeUsedNow[] = _("Items can't be used now.{PAUSE 64}");
static const u8 sText_ForXCommaYZ[] = _("For {B_SCR_ACTIVE_NAME_WITH_PREFIX},\n{B_LAST_ITEM} {B_BUFF1}");
static const u8 sText_PkmnUsedXToGetPumped[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} used\n{B_LAST_ITEM} to get pumped!");
static const u8 sText_PkmnUsedXToGetPumped[] = _("{B_ACTIVE_NAME_WITH_PREFIX} used\n{B_LAST_ITEM} to get pumped!");
static const u8 sText_PkmnLostFocus[] = _("{B_ATK_NAME_WITH_PREFIX} lost its\nfocus and couldn't move!");
static const u8 sText_PkmnWasDraggedOut[] = _("{B_DEF_NAME_WITH_PREFIX} was\ndragged out!\p");
static const u8 sText_TheWallShattered[] = _("The wall shattered!");
@ -798,9 +798,11 @@ static const u8 sText_ItemRestoredSpeciesHealth[] = _("{B_BUFF1} had its\nHP res
static const u8 sText_ItemCuredSpeciesStatus[] = _("{B_BUFF1} had\nits status healed!");
static const u8 sText_ItemRestoredSpeciesPP[] = _("{B_BUFF1} had its\nPP restored!");
static const u8 sText_AtkTrappedDef[] = _("{B_ATK_NAME_WITH_PREFIX} trapped\nthe {B_DEF_NAME_WITH_PREFIX}!");
static const u8 sText_MirrorHerbCopied[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} used its {B_LAST_ITEM}\nto mirror its opponent's stat changes!");
const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
{
[STRINGID_MIRRORHERBCOPIED - BATTLESTRINGS_TABLE_START] = sText_MirrorHerbCopied,
[STRINGID_THUNDERCAGETRAPPED - BATTLESTRINGS_TABLE_START] = sText_AtkTrappedDef,
[STRINGID_ITEMRESTOREDSPECIESHEALTH - BATTLESTRINGS_TABLE_START] = sText_ItemRestoredSpeciesHealth,
[STRINGID_ITEMCUREDSPECIESSTATUS - BATTLESTRINGS_TABLE_START] = sText_ItemCuredSpeciesStatus,

View File

@ -12132,6 +12132,20 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
{
gBattleCommunication[MULTISTRING_CHOOSER] = (gBattlerTarget == gActiveBattler);
gProtectStructs[gActiveBattler].statRaised = TRUE;
// check mirror herb
for (index = 0; index < gBattlersCount; index++)
{
if (GetBattlerSide(index) == GetBattlerSide(gActiveBattler))
continue; // Only triggers on opposing side
if (GetBattlerHoldEffect(index, TRUE) == HOLD_EFFECT_MIRROR_HERB
&& gBattleMons[index].statStages[statId] < MAX_STAT_STAGE)
{
gProtectStructs[index].eatMirrorHerb = 1;
gTotemBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
gTotemBoosts[index].statChanges[statId - 1] = statValue;
}
}
}
}
@ -16531,16 +16545,20 @@ void BS_ItemIncreaseStat(void) {
void BS_ItemRestorePP(void) {
NATIVE_ARGS();
const u8 *effect = GetItemEffect(gLastUsedItem);
u32 i, pp, maxPP, moveId;
u32 loopEnd = MAX_MON_MOVES;
u32 i, pp, maxPP, moveId, loopEnd;
u32 battlerId = MAX_BATTLERS_COUNT;
struct Pokemon *mon = (GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER) ? &gPlayerParty[gBattleStruct->itemPartyIndex[gBattlerAttacker]] : &gEnemyParty[gBattleStruct->itemPartyIndex[gBattlerAttacker]];
// Check whether to apply to all moves.
if (effect[4] & ITEM4_HEAL_PP_ONE)
{
i = gChosenMovePos;
loopEnd = gChosenMovePos + 1;
i = gBattleStruct->itemMoveIndex[gBattlerAttacker];
loopEnd = i + 1;
}
else
{
i = 0;
loopEnd = MAX_MON_MOVES;
}
// Check if the recipient is an active battler.
@ -16551,7 +16569,7 @@ void BS_ItemRestorePP(void) {
battlerId = BATTLE_PARTNER(gBattlerAttacker);
// Heal PP!
for (i = 0; i < loopEnd; i++)
for (; i < loopEnd; i++)
{
pp = GetMonData(mon, MON_DATA_PP1 + i, NULL);
moveId = GetMonData(mon, MON_DATA_MOVE1 + i, NULL);

View File

@ -6808,6 +6808,26 @@ static bool32 GetMentalHerbEffect(u8 battlerId)
return ret;
}
static u8 TryConsumeMirrorHerb(u8 battlerId, bool32 execute)
{
u8 effect = 0;
if (gProtectStructs[battlerId].eatMirrorHerb) {
gLastUsedItem = gBattleMons[battlerId].item;
gBattleScripting.savedBattler = gBattlerAttacker;
gBattleScripting.battler = gBattlerAttacker = battlerId;
gProtectStructs[battlerId].eatMirrorHerb = 0;
if (execute) {
BattleScriptExecute(BattleScript_MirrorHerbCopyStatChangeEnd2);
} else {
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_MirrorHerbCopyStatChange;
}
effect = ITEM_STATS_CHANGE;
}
return effect;
}
static u8 ItemEffectMoveEnd(u32 battlerId, u16 holdEffect)
{
u8 effect = 0;
@ -7011,6 +7031,9 @@ static u8 ItemEffectMoveEnd(u32 battlerId, u16 holdEffect)
BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet);
effect = ITEM_STATS_CHANGE;
break;
case HOLD_EFFECT_MIRROR_HERB:
effect = TryConsumeMirrorHerb(battlerId, FALSE);
break;
}
return effect;
@ -7575,6 +7598,9 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn)
BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet);
effect = ITEM_STATS_CHANGE;
break;
case HOLD_EFFECT_MIRROR_HERB:
effect = TryConsumeMirrorHerb(battlerId, TRUE);
break;
}
if (effect != 0)
@ -9283,7 +9309,7 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b
#if B_PLUS_MINUS_INTERACTION >= GEN_5
case ABILITY_PLUS:
case ABILITY_MINUS:
if (IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
if (IS_MOVE_SPECIAL(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
{
u32 partnerAbility = GetBattlerAbility(BATTLE_PARTNER(battlerAtk));
if (partnerAbility == ABILITY_PLUS || partnerAbility == ABILITY_MINUS)
@ -9292,11 +9318,11 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b
break;
#else
case ABILITY_PLUS:
if (IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_MINUS)
if (IS_MOVE_SPECIAL(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_MINUS)
MulModifier(&modifier, UQ_4_12(1.5));
break;
case ABILITY_MINUS:
if (IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_PLUS)
if (IS_MOVE_SPECIAL(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_PLUS)
MulModifier(&modifier, UQ_4_12(1.5));
break;
#endif

View File

@ -526,7 +526,7 @@ const struct Item gItems[] =
.pocket = POCKET_ITEMS,
.type = ITEM_USE_PARTY_MENU,
.fieldUseFunc = ItemUseOutOfBattle_Medicine,
.battleUsage = EFFECT_ITEM_RESTORE_HP,
.battleUsage = EFFECT_ITEM_CURE_STATUS,
.flingPower = 30,
},

View File

@ -22,16 +22,20 @@
#include "item.h"
#include "constants/form_change_types.h"
#include "constants/items.h"
#include "constants/hold_effects.h"
#include "constants/moves.h"
#include "constants/region_map_sections.h"
extern const struct Evolution gEvolutionTable[][EVOS_PER_MON];
#define IS_DITTO(species) (gBaseStats[species].eggGroup1 == EGG_GROUP_DITTO || gBaseStats[species].eggGroup2 == EGG_GROUP_DITTO)
static void ClearDaycareMonMail(struct DaycareMail *mail);
static void SetInitialEggData(struct Pokemon *mon, u16 species, struct DayCare *daycare);
static u8 GetDaycareCompatibilityScore(struct DayCare *daycare);
static void DaycarePrintMonInfo(u8 windowId, u32 daycareSlotId, u8 y);
static u8 ModifyBreedingScoreForOvalCharm(u8 score);
static u8 GetEggMoves(struct Pokemon *pokemon, u16 *eggMoves);
// RAM buffers used to assist with BuildEggMoveset()
EWRAM_DATA static u16 sHatchedEggLevelUpMoves[EGG_LVL_UP_MOVES_ARRAY_COUNT] = {0};
@ -162,6 +166,57 @@ static s8 Daycare_FindEmptySpot(struct DayCare *daycare)
return -1;
}
static void ClearHatchedEggMoves(void)
{
u16 i;
for (i = 0; i < EGG_MOVES_ARRAY_COUNT; i++)
sHatchedEggEggMoves[i] = MOVE_NONE;
}
static void TransferEggMoves(void)
{
u32 i, j, k, l;
u16 numEggMoves;
struct Pokemon mon;
for (i = 0; i < DAYCARE_MON_COUNT; i++)
{
if (!GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[i].mon, MON_DATA_SANITY_HAS_SPECIES))
continue;
BoxMonToMon(&gSaveBlock1Ptr->daycare.mons[i].mon, &mon);
ClearHatchedEggMoves();
numEggMoves = GetEggMoves(&mon, sHatchedEggEggMoves);
for (j = 0; j < numEggMoves; j++)
{
// Go through other Daycare mons
for (k = 0; k < DAYCARE_MON_COUNT; k++)
{
if (k == i || !GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[k].mon, MON_DATA_SANITY_HAS_SPECIES))
continue;
// Check if you can inherit from them
if (GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[k].mon, MON_DATA_SPECIES) != GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[i].mon, MON_DATA_SPECIES)
#if P_EGG_MOVE_TRANSFER >= GEN_9
&& GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[i].mon, MON_DATA_HELD_ITEM) != ITEM_MIRROR_HERB
#endif
)
continue;
for (l = 0; l < MAX_MON_MOVES; l++)
{
if (GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[k].mon, MON_DATA_MOVE1 + l) != sHatchedEggEggMoves[j])
continue;
if (GiveMoveToBoxMon(&gSaveBlock1Ptr->daycare.mons[i].mon, sHatchedEggEggMoves[j]) == MON_HAS_MAX_MOVES)
break;
}
}
}
}
}
static void StorePokemonInDaycare(struct Pokemon *mon, struct DaycareMon *daycareMon)
{
if (MonHasMail(mon))
@ -184,6 +239,10 @@ static void StorePokemonInDaycare(struct Pokemon *mon, struct DaycareMon *daycar
ZeroMonData(mon);
CompactPartySlots();
CalculatePlayerPartyCount();
#if P_EGG_MOVE_TRANSFER >= GEN_8
TransferEggMoves();
#endif
}
static void StorePokemonInEmptyDaycareSlot(struct Pokemon *mon, struct DayCare *daycare)
@ -427,43 +486,29 @@ static u16 GetEggSpecies(u16 species)
static s32 GetParentToInheritNature(struct DayCare *daycare)
{
u32 species[DAYCARE_MON_COUNT];
s32 i;
s32 dittoCount;
s32 parent = -1;
u32 i;
u8 numWithEverstone = 0;
s32 slot = -1;
// search for female gender
for (i = 0; i < DAYCARE_MON_COUNT; i++)
{
if (GetBoxMonGender(&daycare->mons[i].mon) == MON_FEMALE)
parent = i;
if (ItemId_GetHoldEffect(GetBoxMonData(&daycare->mons[i].mon, MON_DATA_HELD_ITEM)) == HOLD_EFFECT_PREVENT_EVOLVE
#if P_NATURE_INHERITANCE == GEN_3
&& (GetBoxMonGender(&daycare->mons[i].mon) == MON_FEMALE || IS_DITTO(GetBoxMonData(&daycare->mons[i].mon, MON_DATA_SPECIES)))
#endif
) {
slot = i;
numWithEverstone++;
}
}
// search for ditto
for (dittoCount = 0, i = 0; i < DAYCARE_MON_COUNT; i++)
{
species[i] = GetBoxMonData(&daycare->mons[i].mon, MON_DATA_SPECIES);
if (species[i] == SPECIES_DITTO)
dittoCount++, parent = i;
}
// coin flip on ...two Dittos
if (dittoCount == DAYCARE_MON_COUNT)
{
if (Random() >= USHRT_MAX / 2)
parent = 0;
else
parent = 1;
}
// Don't inherit nature if not holding Everstone
if (GetBoxMonData(&daycare->mons[parent].mon, MON_DATA_HELD_ITEM) != ITEM_EVERSTONE
|| Random() >= USHRT_MAX / 2)
{
return -1;
}
return parent;
if (numWithEverstone >= DAYCARE_MON_COUNT)
return Random() & 1;
#if P_NATURE_INHERITANCE > GEN_4
return slot;
#else
return Random() & 1 ? slot : -1;
#endif
}
static void _TriggerPendingDaycareEgg(struct DayCare *daycare)
@ -543,7 +588,7 @@ static void InheritIVs(struct Pokemon *egg, struct DayCare *daycare)
{
u16 motherItem = GetBoxMonData(&daycare->mons[0].mon, MON_DATA_HELD_ITEM);
u16 fatherItem = GetBoxMonData(&daycare->mons[1].mon, MON_DATA_HELD_ITEM);
u8 i;
u8 i, start;
u8 selectedIvs[5];
u8 availableIVs[NUM_STATS];
u8 whichParents[5];
@ -559,8 +604,33 @@ static void InheritIVs(struct Pokemon *egg, struct DayCare *daycare)
availableIVs[i] = i;
}
start = 0;
if (ItemId_GetHoldEffect(motherItem) == HOLD_EFFECT_POWER_ITEM &&
ItemId_GetHoldEffect(fatherItem) == HOLD_EFFECT_POWER_ITEM)
{
whichParents[0] = Random() % DAYCARE_MON_COUNT;
selectedIvs[0] = ItemId_GetSecondaryId(
GetBoxMonData(&daycare->mons[whichParents[0]].mon, MON_DATA_HELD_ITEM));
RemoveIVIndexFromList(availableIVs, selectedIvs[0]);
start++;
}
else if (ItemId_GetHoldEffect(motherItem) == HOLD_EFFECT_POWER_ITEM)
{
whichParents[0] = 0;
selectedIvs[0] = ItemId_GetSecondaryId(motherItem);
RemoveIVIndexFromList(availableIVs, selectedIvs[0]);
start++;
}
else if (ItemId_GetHoldEffect(fatherItem) == HOLD_EFFECT_POWER_ITEM)
{
whichParents[0] = 1;
selectedIvs[0] = ItemId_GetSecondaryId(fatherItem);
RemoveIVIndexFromList(availableIVs, selectedIvs[0]);
start++;
}
// Select which IVs that will be inherited.
for (i = 0; i < howManyIVs; i++)
for (i = start; i < howManyIVs; i++)
{
// Randomly pick an IV from the available list and stop from being chosen again.
// BUG: Instead of removing the IV that was just picked, this
@ -579,7 +649,7 @@ static void InheritIVs(struct Pokemon *egg, struct DayCare *daycare)
}
// Determine which parent each of the selected IVs should inherit from.
for (i = 0; i < howManyIVs; i++)
for (i = start; i < howManyIVs; i++)
{
whichParents[i] = Random() % DAYCARE_MON_COUNT;
}
@ -644,6 +714,35 @@ static void InheritPokeball(struct Pokemon *egg, struct BoxPokemon *father, stru
SetMonData(egg, MON_DATA_POKEBALL, &inheritBall);
}
static void InheritAbility(struct Pokemon *egg, struct BoxPokemon *father, struct BoxPokemon *mother)
{
u8 fatherAbility = GetBoxMonData(father, MON_DATA_ABILITY_NUM);
u8 motherAbility = GetBoxMonData(mother, MON_DATA_ABILITY_NUM);
u8 motherSpecies = GetBoxMonData(mother, MON_DATA_SPECIES);
u8 inheritAbility = motherAbility;
if (motherSpecies == SPECIES_DITTO)
#if P_ABILITY_INHERITANCE < GEN_6
return;
#else
inheritAbility = fatherAbility;
#endif
if (inheritAbility < 2 && (Random() % 10 < 8))
{
SetMonData(egg, MON_DATA_ABILITY_NUM, &inheritAbility);
}
#if P_ABILITY_INHERITANCE < GEN_6
else if (Random() % 10 < 8)
#else
else if (Random() % 10 < 6)
#endif
{
// Hidden Abilities have a different chance of being passed down
SetMonData(egg, MON_DATA_ABILITY_NUM, &inheritAbility);
}
}
// Counts the number of egg moves a pokemon learns and stores the moves in
// the given array.
static u8 GetEggMoves(struct Pokemon *pokemon, u16 *eggMoves)
@ -691,8 +790,7 @@ static void BuildEggMoveset(struct Pokemon *egg, struct BoxPokemon *father, stru
sHatchedEggFatherMoves[i] = MOVE_NONE;
sHatchedEggFinalMoves[i] = MOVE_NONE;
}
for (i = 0; i < EGG_MOVES_ARRAY_COUNT; i++)
sHatchedEggEggMoves[i] = MOVE_NONE;
ClearHatchedEggMoves();
for (i = 0; i < EGG_LVL_UP_MOVES_ARRAY_COUNT; i++)
sHatchedEggLevelUpMoves[i] = MOVE_NONE;
@ -705,6 +803,28 @@ static void BuildEggMoveset(struct Pokemon *egg, struct BoxPokemon *father, stru
numEggMoves = GetEggMoves(egg, sHatchedEggEggMoves);
#if P_MOTHER_EGG_MOVE_INHERITANCE >= GEN_6
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (sHatchedEggMotherMoves[i] != MOVE_NONE)
{
for (j = 0; j < numEggMoves; j++)
{
if (sHatchedEggMotherMoves[i] == sHatchedEggEggMoves[j])
{
if (GiveMoveToMon(egg, sHatchedEggMotherMoves[i]) == MON_HAS_MAX_MOVES)
DeleteFirstMoveAndGiveMoveToMon(egg, sHatchedEggMotherMoves[i]);
break;
}
}
}
else
{
break;
}
}
#endif
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (sHatchedEggFatherMoves[i] != MOVE_NONE)
@ -724,6 +844,7 @@ static void BuildEggMoveset(struct Pokemon *egg, struct BoxPokemon *father, stru
break;
}
}
#if P_TM_INHERITANCE < GEN_6
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (sHatchedEggFatherMoves[i] != MOVE_NONE)
@ -739,6 +860,7 @@ static void BuildEggMoveset(struct Pokemon *egg, struct BoxPokemon *father, stru
}
}
}
#endif
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (sHatchedEggFatherMoves[i] == MOVE_NONE)
@ -909,6 +1031,9 @@ static void _GiveEggFromDaycare(struct DayCare *daycare)
InheritIVs(&egg, daycare);
InheritPokeball(&egg, &daycare->mons[parentSlots[1]].mon, &daycare->mons[parentSlots[0]].mon);
BuildEggMoveset(&egg, &daycare->mons[parentSlots[1]].mon, &daycare->mons[parentSlots[0]].mon);
#if P_ABILITY_INHERITANCE >= GEN_6
InheritAbility(&egg, &daycare->mons[parentSlots[1]].mon, &daycare->mons[parentSlots[0]].mon);
#endif
GiveMoveIfItem(&egg, daycare);

View File

@ -4944,7 +4944,7 @@ static void TryUseItemOnMove(u8 taskId)
else
{
gBattleStruct->itemPartyIndex[gBattlerInMenuId] = GetPartyIdFromBattleSlot(gPartyMenu.slotId);
gChosenMovePos = ptr->data1;
gBattleStruct->itemMoveIndex[gBattlerInMenuId] = ptr->data1;
gPartyMenuUseExitCallback = TRUE;
RemoveBagItem(gSpecialVar_ItemId, 1);
ScheduleBgCopyTilemapToVram(2);

View File

@ -64,7 +64,6 @@ static union PokemonSubstruct *GetSubstruct(struct BoxPokemon *boxMon, u32 perso
static void EncryptBoxMon(struct BoxPokemon *boxMon);
static void DecryptBoxMon(struct BoxPokemon *boxMon);
static void Task_PlayMapChosenOrBattleBGM(u8 taskId);
static u16 GiveMoveToBoxMon(struct BoxPokemon *boxMon, u16 move);
static bool8 ShouldSkipFriendshipChange(void);
static void RemoveIVIndexFromList(u8 *ivs, u8 selectedIv);
void TrySpecialOverworldEvo();
@ -4196,7 +4195,7 @@ u16 GiveMoveToMon(struct Pokemon *mon, u16 move)
return GiveMoveToBoxMon(&mon->box, move);
}
static u16 GiveMoveToBoxMon(struct BoxPokemon *boxMon, u16 move)
u16 GiveMoveToBoxMon(struct BoxPokemon *boxMon, u16 move)
{
s32 i;
for (i = 0; i < MAX_MON_MOVES; i++)

View File

@ -6,7 +6,7 @@ SINGLE_BATTLE_TEST("Compound Eyes raises accuracy")
PASSES_RANDOMLY(91, 100, RNG_ACCURACY);
GIVEN {
ASSUME(gBattleMoves[MOVE_THUNDER].accuracy == 70);
PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_COMPOUND_EYES); };
PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_COMPOUND_EYES); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_THUNDER); }
@ -16,16 +16,13 @@ SINGLE_BATTLE_TEST("Compound Eyes raises accuracy")
}
}
// This fails even though the ability works correctly. The failure is due to
// a statistical anomaly in the test system where FISSURE hits 3 times more often
// than we expect.
SINGLE_BATTLE_TEST("Compound Eyes does not affect OHKO moves")
{
PASSES_RANDOMLY(30, 100, RNG_ACCURACY);
GIVEN {
ASSUME(gBattleMoves[MOVE_FISSURE].accuracy == 30);
ASSUME(gBattleMoves[MOVE_FISSURE].effect == EFFECT_OHKO);
PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_COMPOUND_EYES); };
PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_COMPOUND_EYES); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_FISSURE); }

View File

@ -33,10 +33,13 @@ SINGLE_BATTLE_TEST("Dry Skin heals 1/8th Max HP in Rain")
SINGLE_BATTLE_TEST("Dry Skin increases damage taken from Fire-type moves by 25%", s16 damage)
{
u32 ability;
PARAMETRIZE { ability = ABILITY_EFFECT_SPORE; }
PARAMETRIZE { ability = ABILITY_DRY_SKIN; }
GIVEN {
ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_PARASECT) { Ability(ABILITY_DRY_SKIN); };
OPPONENT(SPECIES_PARASECT) { Ability(ability); };
} WHEN {
TURN {MOVE(player, MOVE_EMBER); }
} SCENE {

View File

@ -63,8 +63,9 @@ SINGLE_BATTLE_TEST("Volt Absorb is only triggered once on multi strike moves")
}
}
DOUBLE_BATTLE_TEST("Volt Absorb does not stop Electric Typed Explosion from damaging other pokemon", s16 damage1, s16 damage2) // Fixed issue #1961
DOUBLE_BATTLE_TEST("Volt Absorb does not stop Electric Typed Explosion from damaging other pokemon") // Fixed issue #1961
{
s16 damage1, damage2;
GIVEN {
ASSUME(gBattleMoves[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION);
ASSUME(gBattleMoves[MOVE_EXPLOSION].type == TYPE_NORMAL);
@ -78,12 +79,11 @@ DOUBLE_BATTLE_TEST("Volt Absorb does not stop Electric Typed Explosion from dama
ABILITY_POPUP(playerLeft, ABILITY_VOLT_ABSORB);
HP_BAR(playerLeft, hp: TEST_MAX_HP / 4 + 1);
MESSAGE("Jolteon restored HP using its Volt Absorb!");
HP_BAR(playerRight, captureDamage: &results->damage1);
HP_BAR(opponentRight, captureDamage: &results->damage2);
}
FINALLY {
EXPECT_NE(results[0].damage1, 0);
EXPECT_NE(results[0].damage2, 0);
HP_BAR(playerRight, captureDamage: &damage1);
HP_BAR(opponentRight, captureDamage: &damage2);
} THEN {
EXPECT_NE(damage1, 0);
EXPECT_NE(damage2, 0);
}
}

View File

@ -11,14 +11,11 @@ SINGLE_BATTLE_TEST("Berserk Gene sharply raises attack at the start of battle",
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
if (useItem) PASSES_RANDOMLY(66, 100, RNG_CONFUSION);
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { if (useItem) Item(ITEM_BERSERK_GENE); };
PLAYER(SPECIES_WOBBUFFET) { if (useItem) Item(ITEM_BERSERK_GENE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {
MOVE(player, MOVE_TACKLE);
}
TURN { MOVE(player, MOVE_TACKLE, WITH_RNG(RNG_CONFUSION, FALSE)); }
} SCENE {
if (useItem)
{
@ -38,17 +35,13 @@ SINGLE_BATTLE_TEST("Berserk Gene activates on switch in", s16 damage)
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
if (useItem) PASSES_RANDOMLY(66, 100, RNG_CONFUSION);
GIVEN {
PLAYER(SPECIES_WYNAUT);
PLAYER(SPECIES_WOBBUFFET) { if (useItem) Item(ITEM_BERSERK_GENE); };
PLAYER(SPECIES_WOBBUFFET) { if (useItem) Item(ITEM_BERSERK_GENE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {
SWITCH(player, 1);
} TURN {
MOVE(player, MOVE_TACKLE);
}
TURN { SWITCH(player, 1); }
TURN { MOVE(player, MOVE_TACKLE, WITH_RNG(RNG_CONFUSION, FALSE)); }
} SCENE {
if (useItem)
{

View File

@ -0,0 +1,49 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gItems[ITEM_MIRROR_HERB].holdEffect == HOLD_EFFECT_MIRROR_HERB);
}
SINGLE_BATTLE_TEST("Mirror Herb copies all of foe's stat changes in a turn", s16 damage)
{
u32 item;
PARAMETRIZE { item = ITEM_NONE; }
PARAMETRIZE { item = ITEM_MIRROR_HERB; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(4); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(5); Item(item); }
} WHEN {
TURN { MOVE(player, MOVE_DRAGON_DANCE); }
TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE); }
} SCENE {
if (item == ITEM_NONE) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &results[i].damage);
} else {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &results[i].damage);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
}
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);
EXPECT_EQ(player->statStages[STAT_ATK], opponent->statStages[STAT_ATK]);
EXPECT_EQ(player->statStages[STAT_SPEED], opponent->statStages[STAT_SPEED]);
}
}
SINGLE_BATTLE_TEST("Mirror Herb copies all of of Stuff Cheeks")
{
GIVEN {
ASSUME(gItems[ITEM_LIECHI_BERRY].holdEffect == HOLD_EFFECT_ATTACK_UP);
PLAYER(SPECIES_SKWOVET) { Item(ITEM_LIECHI_BERRY); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_MIRROR_HERB); }
} WHEN {
TURN { MOVE(player, MOVE_STUFF_CHEEKS); }
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], opponent->statStages[STAT_ATK]);
EXPECT_EQ(player->statStages[STAT_DEF], opponent->statStages[STAT_DEF]);
}
}

View File

@ -0,0 +1,339 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Paralyze Heal heals a battler from being paralyzed")
{
GIVEN {
ASSUME(gItems[ITEM_PARALYZE_HEAL].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_PARALYZE_HEAL, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Antidote heals a battler from being poisoned")
{
GIVEN {
ASSUME(gItems[ITEM_ANTIDOTE].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_ANTIDOTE, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Antidote heals a battler from being badly poisoned")
{
GIVEN {
ASSUME(gItems[ITEM_ANTIDOTE].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_TOXIC_POISON); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_ANTIDOTE, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Awakening heals a battler from being asleep")
{
GIVEN {
ASSUME(gItems[ITEM_AWAKENING].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_AWAKENING, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Burn Heal heals a battler from being burned")
{
GIVEN {
ASSUME(gItems[ITEM_BURN_HEAL].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_BURN_HEAL, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Ice Heal heals a battler from being paralyzed")
{
GIVEN {
ASSUME(gItems[ITEM_ICE_HEAL].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_ICE_HEAL, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Full Heal heals a battler from any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_FULL_HEAL].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(player, ITEM_FULL_HEAL, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Heal Powder heals a battler from any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_HEAL_POWDER].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(player, ITEM_HEAL_POWDER, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Pewter Crunchies heals a battler from any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_PEWTER_CRUNCHIES].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(player, ITEM_PEWTER_CRUNCHIES, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Lava Cookies heals a battler from any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_LAVA_COOKIE].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(player, ITEM_LAVA_COOKIE, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Rage Candy Bar heals a battler from any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_RAGE_CANDY_BAR].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(player, ITEM_RAGE_CANDY_BAR, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Old Gateu heals a battler from any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_OLD_GATEAU].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(player, ITEM_OLD_GATEAU, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Casteliacone heals a battler from any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_CASTELIACONE].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(player, ITEM_CASTELIACONE, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Lumiose Galette heals a battler from any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_LUMIOSE_GALETTE].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(player, ITEM_LUMIOSE_GALETTE, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");;
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Shalour Sable heals a battler from any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_SHALOUR_SABLE].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(player, ITEM_SHALOUR_SABLE, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Big Malasada heals a battler from any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_BIG_MALASADA].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(player, ITEM_BIG_MALASADA, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Full Heal, Heal Powder and Local Specialties heal a battler from being confused")
{
u16 item;
PARAMETRIZE { item = ITEM_FULL_HEAL; }
PARAMETRIZE { item = ITEM_HEAL_POWDER; }
PARAMETRIZE { item = ITEM_PEWTER_CRUNCHIES; }
PARAMETRIZE { item = ITEM_LAVA_COOKIE; }
PARAMETRIZE { item = ITEM_RAGE_CANDY_BAR; }
PARAMETRIZE { item = ITEM_OLD_GATEAU; }
PARAMETRIZE { item = ITEM_CASTELIACONE; }
PARAMETRIZE { item = ITEM_LUMIOSE_GALETTE; }
PARAMETRIZE { item = ITEM_SHALOUR_SABLE; }
PARAMETRIZE { item = ITEM_BIG_MALASADA; }
GIVEN {
ASSUME(gItems[item].battleUsage == EFFECT_ITEM_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GENGAR);
} WHEN {
TURN { MOVE(opponent, MOVE_CONFUSE_RAY); }
TURN { USE_ITEM(player, item, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its status healed!");
} THEN {
EXPECT_EQ(player->status2, STATUS1_NONE); // because we dont have STATUS2_NONE
}
}

View File

@ -0,0 +1,43 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures any primary status")
{
u16 status;
PARAMETRIZE{ status = STATUS1_BURN; }
PARAMETRIZE{ status = STATUS1_FREEZE; }
PARAMETRIZE{ status = STATUS1_PARALYSIS; }
PARAMETRIZE{ status = STATUS1_POISON; }
PARAMETRIZE{ status = STATUS1_TOXIC_POISON; }
PARAMETRIZE{ status = STATUS1_SLEEP; }
GIVEN {
ASSUME(gItems[ITEM_FULL_RESTORE].battleUsage == EFFECT_ITEM_HEAL_AND_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); Status1(status); };
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN{ USE_ITEM(player, ITEM_FULL_RESTORE, partyIndex: 0); }
} SCENE {
MESSAGE("Wobbuffet had its HP restored!");
} THEN {
EXPECT_EQ(player->hp, player->maxHP);
EXPECT_EQ(player->status1, STATUS1_NONE);
}
}
SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures confusion")
{
GIVEN {
ASSUME(gItems[ITEM_FULL_RESTORE].battleUsage == EFFECT_ITEM_HEAL_AND_CURE_STATUS);
PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300);};
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN{ MOVE(opponent, MOVE_CONFUSE_RAY); }
TURN{ USE_ITEM(player, ITEM_FULL_RESTORE, partyIndex: 0); }
TURN{ MOVE(player, MOVE_TACKLE); }
} SCENE {
MESSAGE("Wobbuffet had its HP restored!");
NONE_OF { MESSAGE("Wobbuffet is confused!"); }
} THEN {
EXPECT_EQ(player->hp, player->maxHP);
}
}

View File

@ -1,7 +1,7 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("X-Attack sharply raises battler's Attack stat", s16 damage)
SINGLE_BATTLE_TEST("X Attack sharply raises battler's Attack stat", s16 damage)
{
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
@ -23,3 +23,231 @@ SINGLE_BATTLE_TEST("X-Attack sharply raises battler's Attack stat", s16 damage)
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);
}
}
SINGLE_BATTLE_TEST("X Defense sharply raises battler's Defense stat", s16 damage)
{
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
GIVEN {
ASSUME(gItems[ITEM_X_DEFENSE].battleUsage == EFFECT_ITEM_INCREASE_STAT);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (useItem) TURN { USE_ITEM(player, ITEM_X_DEFENSE); }
TURN { MOVE(opponent, MOVE_TACKLE); }
} SCENE {
MESSAGE("Foe Wobbuffet used Tackle!");
HP_BAR(player, captureDamage: &results[i].damage);
} FINALLY {
if (B_X_ITEMS_BUFF >= GEN_7)
EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage);
else
EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.66), results[1].damage);
}
}
SINGLE_BATTLE_TEST("X Sp. Atk sharply raises battler's Sp. Attack stat", s16 damage)
{
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
GIVEN {
ASSUME(gItems[ITEM_X_SP_ATK].battleUsage == EFFECT_ITEM_INCREASE_STAT);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (useItem) TURN { USE_ITEM(player, ITEM_X_SP_ATK); }
TURN { MOVE(player, MOVE_DISARMING_VOICE); }
} SCENE {
MESSAGE("Wobbuffet used DisrmngVoice!");
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
if (B_X_ITEMS_BUFF >= GEN_7)
EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage);
else
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);
}
}
SINGLE_BATTLE_TEST("X Sp. Def sharply raises battler's Sp. Defense stat", s16 damage)
{
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
GIVEN {
ASSUME(gItems[ITEM_X_SP_DEF].battleUsage == EFFECT_ITEM_INCREASE_STAT);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (useItem) TURN { USE_ITEM(player, ITEM_X_SP_DEF); }
TURN { MOVE(opponent, MOVE_DISARMING_VOICE); }
} SCENE {
MESSAGE("Foe Wobbuffet used DisrmngVoice!");
HP_BAR(player, captureDamage: &results[i].damage);
} FINALLY {
if (B_X_ITEMS_BUFF >= GEN_7)
EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage);
else
EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.66), results[1].damage);
}
}
SINGLE_BATTLE_TEST("X Speed sharply raises battler's Speed stat", s16 damage)
{
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
GIVEN {
ASSUME(gItems[ITEM_X_SPEED].battleUsage == EFFECT_ITEM_INCREASE_STAT);
if (B_X_ITEMS_BUFF >= GEN_7)
{
PLAYER(SPECIES_WOBBUFFET) { Speed(3); };
OPPONENT(SPECIES_WOBBUFFET) { Speed(4); };
}
else
{
PLAYER(SPECIES_WOBBUFFET) { Speed(4); };
OPPONENT(SPECIES_WOBBUFFET) { Speed(5); };
}
} WHEN {
if (useItem) TURN { USE_ITEM(player, ITEM_X_SPEED); }
TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE); }
} SCENE {
if (useItem)
{
MESSAGE("Wobbuffet used Tackle!");
MESSAGE("Foe Wobbuffet used Tackle!");
} else
{
MESSAGE("Foe Wobbuffet used Tackle!");
MESSAGE("Wobbuffet used Tackle!");
}
}
}
SINGLE_BATTLE_TEST("X Accuracy sharply raises battler's Accuracy stat")
{
ASSUME(gBattleMoves[MOVE_SING].accuracy == 55);
if (B_X_ITEMS_BUFF >= GEN_7)
PASSES_RANDOMLY(gBattleMoves[MOVE_SING].accuracy * 5 / 3, 100, RNG_ACCURACY);
else
PASSES_RANDOMLY(gBattleMoves[MOVE_SING].accuracy * 4 / 3, 100, RNG_ACCURACY);
GIVEN {
ASSUME(gItems[ITEM_X_ACCURACY].battleUsage == EFFECT_ITEM_INCREASE_STAT);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_X_ACCURACY); }
TURN { MOVE(player, MOVE_SING); }
} SCENE {
MESSAGE("Wobbuffet used Sing!");
MESSAGE("Foe Wobbuffet fell asleep!");
}
}
SINGLE_BATTLE_TEST("Max Mushrooms raises battler's Attack stat", s16 damage)
{
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
GIVEN {
ASSUME(gItems[ITEM_MAX_MUSHROOMS].battleUsage == EFFECT_ITEM_INCREASE_ALL_STATS);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (useItem) TURN { USE_ITEM(player, ITEM_MAX_MUSHROOMS); }
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
MESSAGE("Wobbuffet used Tackle!");
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Max Mushrooms raises battler's Defense stat", s16 damage)
{
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
GIVEN {
ASSUME(gItems[ITEM_MAX_MUSHROOMS].battleUsage == EFFECT_ITEM_INCREASE_ALL_STATS);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (useItem) TURN { USE_ITEM(player, ITEM_MAX_MUSHROOMS); }
TURN { MOVE(opponent, MOVE_TACKLE); }
} SCENE {
MESSAGE("Foe Wobbuffet used Tackle!");
HP_BAR(player, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.66), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Max Mushrooms raises battler's Sp. Attack stat", s16 damage)
{
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
GIVEN {
ASSUME(gItems[ITEM_MAX_MUSHROOMS].battleUsage == EFFECT_ITEM_INCREASE_ALL_STATS);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (useItem) TURN { USE_ITEM(player, ITEM_MAX_MUSHROOMS); }
TURN { MOVE(player, MOVE_DISARMING_VOICE); }
} SCENE {
MESSAGE("Wobbuffet used DisrmngVoice!");
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Max Mushrooms battler's Sp. Defense stat", s16 damage)
{
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
GIVEN {
ASSUME(gItems[ITEM_MAX_MUSHROOMS].battleUsage == EFFECT_ITEM_INCREASE_ALL_STATS);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (useItem) TURN { USE_ITEM(player, ITEM_MAX_MUSHROOMS); }
TURN { MOVE(opponent, MOVE_DISARMING_VOICE); }
} SCENE {
MESSAGE("Foe Wobbuffet used DisrmngVoice!");
HP_BAR(player, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.66), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Max Mushrooms raises battler's Speed stat", s16 damage)
{
u16 useItem;
PARAMETRIZE { useItem = FALSE; }
PARAMETRIZE { useItem = TRUE; }
GIVEN {
ASSUME(gItems[ITEM_MAX_MUSHROOMS].battleUsage == EFFECT_ITEM_INCREASE_ALL_STATS);
PLAYER(SPECIES_WOBBUFFET) { Speed(4); };
OPPONENT(SPECIES_WOBBUFFET) { Speed(5); };
} WHEN {
if (useItem) TURN { USE_ITEM(player, ITEM_MAX_MUSHROOMS); }
TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE); }
} SCENE {
if (useItem)
{
MESSAGE("Wobbuffet used Tackle!");
MESSAGE("Foe Wobbuffet used Tackle!");
} else
{
MESSAGE("Foe Wobbuffet used Tackle!");
MESSAGE("Wobbuffet used Tackle!");
}
}
}

View File

@ -1,37 +1,186 @@
#include "global.h"
#include "test_battle.h"
#define TEST_HP 1
#define MAX_HP 400
SINGLE_BATTLE_TEST("Potion restores a battler's HP by 20")
{
s16 damage;
GIVEN {
ASSUME(gItems[ITEM_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(100); }
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_POTION, partyIndex: 0); }
} SCENE {
HP_BAR(player, captureDamage: &damage);
} FINALLY {
EXPECT_EQ(damage, -20);
HP_BAR(player, hp: TEST_HP + 20);
}
}
SINGLE_BATTLE_TEST("Sitrus Berry restores a battler's HP")
SINGLE_BATTLE_TEST("Super Potion restores a battler's HP by 60")
{
GIVEN {
ASSUME(gItems[ITEM_SUPER_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_SUPER_POTION, partyIndex: 0); }
} SCENE {
HP_BAR(player, hp: TEST_HP + 60);
}
}
SINGLE_BATTLE_TEST("Hyper Potion restores a battler's HP by 120")
{
GIVEN {
ASSUME(gItems[ITEM_HYPER_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_HYPER_POTION, partyIndex: 0); }
} SCENE {
HP_BAR(player, hp: TEST_HP + 120);
}
}
SINGLE_BATTLE_TEST("Max Potion restores a battler's HP fully")
{
GIVEN {
ASSUME(gItems[ITEM_MAX_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_MAX_POTION, partyIndex: 0); }
} SCENE {
HP_BAR(player, hp: MAX_HP);
}
}
SINGLE_BATTLE_TEST("Fresh Water restores a battler's HP by 30")
{
GIVEN {
ASSUME(gItems[ITEM_FRESH_WATER].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_FRESH_WATER, partyIndex: 0); }
} SCENE {
HP_BAR(player, hp: TEST_HP + 30);
}
}
SINGLE_BATTLE_TEST("Soda Pop restores a battler's HP by 50")
{
GIVEN {
ASSUME(gItems[ITEM_SODA_POP].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_SODA_POP, partyIndex: 0); }
} SCENE {
HP_BAR(player, hp: TEST_HP + 50);
}
}
SINGLE_BATTLE_TEST("Lemonade restores a battler's HP by 70")
{
GIVEN {
ASSUME(gItems[ITEM_LEMONADE].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_LEMONADE, partyIndex: 0); }
} SCENE {
HP_BAR(player, hp: TEST_HP + 70);
}
}
SINGLE_BATTLE_TEST("Moomoo Milk restores a battler's HP by 100")
{
GIVEN {
ASSUME(gItems[ITEM_MOOMOO_MILK].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_MOOMOO_MILK, partyIndex: 0); }
} SCENE {
HP_BAR(player, hp: TEST_HP + 100);
}
}
SINGLE_BATTLE_TEST("Energy Powder restores a battler's HP by 60(50)")
{
GIVEN {
ASSUME(gItems[ITEM_ENERGY_POWDER].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_ENERGY_POWDER, partyIndex: 0); }
} SCENE {
if (I_HEALTH_RECOVERY >= GEN_7)
HP_BAR(player, hp: TEST_HP + 60);
else
HP_BAR(player, hp: TEST_HP + 50);
}
}
SINGLE_BATTLE_TEST("Energy Root restores a battler's HP by 120(200)")
{
GIVEN {
ASSUME(gItems[ITEM_ENERGY_ROOT].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_ENERGY_ROOT, partyIndex: 0); }
} SCENE {
if (I_HEALTH_RECOVERY >= GEN_7)
HP_BAR(player, hp: TEST_HP + 120);
else
HP_BAR(player, hp: TEST_HP + 200);
}
}
SINGLE_BATTLE_TEST("Sweet Heart restores a battler's HP by 20")
{
GIVEN {
ASSUME(gItems[ITEM_SWEET_HEART].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_SWEET_HEART, partyIndex: 0); }
} SCENE {
HP_BAR(player, hp: TEST_HP + 20);
}
}
SINGLE_BATTLE_TEST("Oran Berry restores a battler's HP by 10")
{
GIVEN {
ASSUME(gItems[ITEM_ORAN_BERRY].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_ORAN_BERRY, partyIndex: 0); }
} SCENE {
HP_BAR(player, hp: TEST_HP + 10);
}
}
SINGLE_BATTLE_TEST("Sitrus Berry restores a battler's HP by 25% of its max HP(30HP flat)")
{
s16 damage;
GIVEN {
ASSUME(gItems[ITEM_SITRUS_BERRY].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(100); }
PLAYER(SPECIES_WOBBUFFET) { HP(TEST_HP); MaxHP(MAX_HP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_SITRUS_BERRY, partyIndex: 0); }
} SCENE {
HP_BAR(player, captureDamage: &damage);
} FINALLY {
if (I_SITRUS_BERRY_HEAL >= GEN_4)
EXPECT_EQ(damage, -25);
HP_BAR(player, hp: TEST_HP + MAX_HP * 0.25);
else
EXPECT_EQ(damage, -30);
HP_BAR(player, hp: TEST_HP + 30);
}
}
#undef TEST_HP
#undef MAX_HP

View File

@ -1,19 +1,66 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Ether restores the PP of one of a battler's moves")
SINGLE_BATTLE_TEST("Ether restores the PP of one of a battler's moves by 10 ")
{
GIVEN {
ASSUME(gItems[ITEM_ETHER].battleUsage == EFFECT_ITEM_RESTORE_PP);
ASSUME(gItems[ITEM_ETHER].type == ITEM_USE_PARTY_MENU_MOVES);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_CONFUSION); }
PLAYER(SPECIES_WOBBUFFET) { MovesWithPP({MOVE_TACKLE, 0}, {MOVE_CONFUSION, 20}); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_CONFUSION); }
TURN { USE_ITEM(player, ITEM_ETHER, partyIndex: 0, move: MOVE_TACKLE); }
} FINALLY {
EXPECT_EQ(player->pp[0], 35);
EXPECT_EQ(player->pp[1], 24);
} THEN {
EXPECT_EQ(player->pp[0], 10);
EXPECT_EQ(player->pp[1], 20);
}
}
SINGLE_BATTLE_TEST("Max Ether restores the PP of one of a battler's moves fully")
{
GIVEN {
ASSUME(gItems[ITEM_MAX_ETHER].battleUsage == EFFECT_ITEM_RESTORE_PP);
ASSUME(gItems[ITEM_MAX_ETHER].type == ITEM_USE_PARTY_MENU_MOVES);
PLAYER(SPECIES_WOBBUFFET) { MovesWithPP({MOVE_TACKLE, 0}, {MOVE_CONFUSION, 20}); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_MAX_ETHER, partyIndex: 0, move: MOVE_TACKLE); }
} THEN {
EXPECT_EQ(player->pp[0], 35);
EXPECT_EQ(player->pp[1], 20);
}
}
SINGLE_BATTLE_TEST("Elixir restores the PP of all of a battler's moves by 10")
{
GIVEN {
ASSUME(gItems[ITEM_ELIXIR].battleUsage == EFFECT_ITEM_RESTORE_PP);
ASSUME(gItems[ITEM_ELIXIR].type == ITEM_USE_PARTY_MENU);
PLAYER(SPECIES_WOBBUFFET) { MovesWithPP({MOVE_TACKLE, 0}, {MOVE_CONFUSION, 0}, {MOVE_SCRATCH, 0}, {MOVE_GROWL, 0}); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_ELIXIR, partyIndex: 0); }
} THEN {
EXPECT_EQ(player->pp[0], 10);
EXPECT_EQ(player->pp[1], 10);
EXPECT_EQ(player->pp[2], 10);
EXPECT_EQ(player->pp[3], 10);
}
}
SINGLE_BATTLE_TEST("Max Elixir restores the PP of all of a battler's moves fully")
{
GIVEN {
ASSUME(gItems[ITEM_MAX_ELIXIR].battleUsage == EFFECT_ITEM_RESTORE_PP);
ASSUME(gItems[ITEM_MAX_ELIXIR].type == ITEM_USE_PARTY_MENU);
PLAYER(SPECIES_WOBBUFFET) { MovesWithPP({MOVE_TACKLE, 0}, {MOVE_CONFUSION, 0}, {MOVE_SCRATCH, 0}, {MOVE_GROWL, 0}); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_MAX_ELIXIR, partyIndex: 0); }
} THEN {
EXPECT_EQ(player->pp[0], 35);
EXPECT_EQ(player->pp[1], 25);
EXPECT_EQ(player->pp[2], 35);
EXPECT_EQ(player->pp[3], 40);
}
}

78
test/item_effect_revive.c Normal file
View File

@ -0,0 +1,78 @@
#include "global.h"
#include "test_battle.h"
#define MAX_HP 200
SINGLE_BATTLE_TEST("Revive restores a fainted battler's HP to half")
{
GIVEN {
ASSUME(gItems[ITEM_REVIVE].battleUsage == EFFECT_ITEM_REVIVE);
PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(MAX_HP); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); }
TURN { USE_ITEM(player, ITEM_REVIVE, partyIndex: 0); }
TURN { SWITCH(player, 0); }
} SCENE {
MESSAGE("Wynaut had its HP restored!");
} THEN {
EXPECT_EQ(player->hp, MAX_HP/2);
}
}
SINGLE_BATTLE_TEST("Max Revive restores a fainted battler's HP fully")
{
GIVEN {
ASSUME(gItems[ITEM_MAX_REVIVE].battleUsage == EFFECT_ITEM_REVIVE);
PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(MAX_HP); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); }
TURN { USE_ITEM(player, ITEM_MAX_REVIVE, partyIndex: 0); }
TURN { SWITCH(player, 0); }
} SCENE {
MESSAGE("Wynaut had its HP restored!");
} THEN {
EXPECT_EQ(player->hp, MAX_HP);
}
}
SINGLE_BATTLE_TEST("Revival Herb restores a fainted battler's HP fully")
{
GIVEN {
ASSUME(gItems[ITEM_REVIVAL_HERB].battleUsage == EFFECT_ITEM_REVIVE);
PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(MAX_HP); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); }
TURN { USE_ITEM(player, ITEM_REVIVAL_HERB, partyIndex: 0); }
TURN { SWITCH(player, 0); }
} SCENE {
MESSAGE("Wynaut had its HP restored!");
} THEN {
EXPECT_EQ(player->hp, MAX_HP);
}
}
SINGLE_BATTLE_TEST("Max Honey restores a fainted battler's HP fully")
{
GIVEN {
ASSUME(gItems[ITEM_MAX_HONEY].battleUsage == EFFECT_ITEM_REVIVE);
PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(MAX_HP); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); }
TURN { USE_ITEM(player, ITEM_MAX_HONEY, partyIndex: 0); }
TURN { SWITCH(player, 0); }
} SCENE {
MESSAGE("Wynaut had its HP restored!");
} THEN {
EXPECT_EQ(player->hp, MAX_HP);
}
}
#undef MAX_HP

View File

@ -0,0 +1,21 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Dire Hit increases a battler's critical hit chance by 2 stages")
{
ASSUME(B_CRIT_CHANCE >= GEN_7);
PASSES_RANDOMLY(1, 2, RNG_CRITICAL_HIT);
GIVEN {
ASSUME(gItems[ITEM_DIRE_HIT].battleUsage == EFFECT_ITEM_SET_FOCUS_ENERGY);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_DIRE_HIT, partyIndex: 0); }
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_ENERGY, player);
MESSAGE("Wobbuffet used Dire Hit to get pumped!");
MESSAGE("Wobbuffet used Scratch!");
MESSAGE("A critical hit!");
}
}

View File

@ -0,0 +1,19 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Guard Spec. sets Mist effect on the battlers side")
{
GIVEN {
ASSUME(gItems[ITEM_GUARD_SPEC].battleUsage == EFFECT_ITEM_SET_MIST);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { USE_ITEM(player, ITEM_GUARD_SPEC, partyIndex: 0); }
TURN { MOVE(opponent, MOVE_GROWL); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_MIST, player);
MESSAGE("Ally became shrouded in MIST!");
MESSAGE("Foe Wobbuffet used Growl!");
MESSAGE("Wobbuffet is protected by MIST!");
}
}

View File

@ -28,7 +28,7 @@ SINGLE_BATTLE_TEST("Bide deals twice the taken damage over two turns")
MESSAGE("Wobbuffet unleashed energy!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_BIDE, player);
HP_BAR(opponent, captureDamage: &bideDamage);
} FINALLY {
EXPECT_EQ(bideDamage, damage1 + damage2);
} THEN {
EXPECT_EQ(bideDamage, 2 * (damage1 + damage2));
}
}

View File

@ -6,7 +6,7 @@ ASSUMPTIONS
ASSUME(gBattleMoves[MOVE_TRIPLE_KICK].effect & EFFECT_TRIPLE_KICK);
}
SINGLE_BATTLE_TEST("Triple Kick damage is increaased by its base damage for each hit")
SINGLE_BATTLE_TEST("Triple Kick damage is increased by its base damage for each hit")
{
s16 firstHit;
s16 secondHit;
@ -24,8 +24,8 @@ SINGLE_BATTLE_TEST("Triple Kick damage is increaased by its base damage for each
HP_BAR(opponent, captureDamage: &secondHit);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRIPLE_KICK, player);
HP_BAR(opponent, captureDamage: &thirdHit);
} FINALLY {
EXPECT_EQ(secondHit, firstHit * 2);
EXPECT_EQ(thirdHit, firstHit * 3);
} THEN {
EXPECT_MUL_EQ(firstHit, Q_4_12(2.0), secondHit);
EXPECT_MUL_EQ(firstHit, Q_4_12(3.0), thirdHit);
}
}

View File

@ -20,7 +20,7 @@ SINGLE_BATTLE_TEST("Three-strike flag turns a move into a 3-hit move")
HP_BAR(opponent, captureDamage: &secondHit);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRIPLE_DIVE, player);
HP_BAR(opponent, captureDamage: &thirdHit);
} FINALLY {
} THEN {
EXPECT_EQ(firstHit, secondHit);
EXPECT_EQ(secondHit, thirdHit);
EXPECT_EQ(firstHit, thirdHit);
@ -49,7 +49,7 @@ SINGLE_BATTLE_TEST("Surging Strikes hits 3 times with each hit being a critical
ANIMATION(ANIM_TYPE_MOVE, MOVE_SURGING_STRIKES, player);
HP_BAR(opponent, captureDamage: &thirdHit);
MESSAGE("A critical hit!");
} FINALLY {
} THEN {
EXPECT_EQ(firstHit, secondHit);
EXPECT_EQ(secondHit, thirdHit);
EXPECT_EQ(firstHit, thirdHit);

View File

@ -34,7 +34,7 @@ SINGLE_BATTLE_TEST("Electric Terrain activates Electric Seed and Mimicry")
MESSAGE("Using Electric Seed, the Defense of Wobbuffet rose!");
ABILITY_POPUP(opponent);
MESSAGE("Foe Stunfisk's type changed to Electr!");
} FINALLY {
} THEN {
EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].type1, TYPE_ELECTRIC);
}
}

View File

@ -30,7 +30,7 @@ SINGLE_BATTLE_TEST("Grassy Terrain activates Grassy Seed and Mimicry")
MESSAGE("Using Grassy Seed, the Defense of Wobbuffet rose!");
ABILITY_POPUP(opponent);
MESSAGE("Foe Stunfisk's type changed to Grass!");
} FINALLY {
} THEN {
EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].type1, TYPE_GRASS);
}
}

View File

@ -34,7 +34,7 @@ SINGLE_BATTLE_TEST("Misty Terrain activates Misty Seed and Mimicry")
MESSAGE("Using Misty Seed, the Sp. Def of Wobbuffet rose!");
ABILITY_POPUP(opponent);
MESSAGE("Foe Stunfisk's type changed to Fairy!");
} FINALLY {
} THEN {
EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].type1, TYPE_FAIRY);
}
}

View File

@ -33,7 +33,7 @@ SINGLE_BATTLE_TEST("Psychic Terrain activates Psychic Seed and Mimicry")
MESSAGE("Using Psychic Seed, the Sp. Def of Wobbuffet rose!");
ABILITY_POPUP(opponent);
MESSAGE("Foe Stunfisk's type changed to Psychc!");
} FINALLY {
} THEN {
EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].type1, TYPE_PSYCHIC);
}
}
@ -61,7 +61,7 @@ SINGLE_BATTLE_TEST("Psychic Terrain increases power of Psychic-type moves by 30/
}
}
SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target the user", s16 damage)
SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target the user")
{
GIVEN {
PLAYER(SPECIES_SABLEYE) { Ability(ABILITY_PRANKSTER); HP(1); }
@ -76,7 +76,7 @@ SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target the
}
}
SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target all battlers", s16 damage)
SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target all battlers")
{
KNOWN_FAILING;
GIVEN {
@ -91,7 +91,7 @@ SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target all
}
}
SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target all opponents", s16 damage)
SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target all opponents")
{
KNOWN_FAILING;
GIVEN {
@ -106,7 +106,7 @@ SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target all
}
}
DOUBLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target allies", s16 damage)
DOUBLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target allies")
{
GIVEN {
PLAYER(SPECIES_SABLEYE) { Ability(ABILITY_PRANKSTER); }
@ -122,7 +122,7 @@ DOUBLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target all
}
}
SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority field moves", s16 damage)
SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority field moves")
{
KNOWN_FAILING;
GIVEN {

View File

@ -46,6 +46,7 @@ struct TestRunnerState
u8 result;
u8 expectedResult;
bool8 expectLeaks:1;
u32 timeoutSeconds;
};
@ -69,6 +70,7 @@ extern struct TestRunnerState gTestRunnerState;
void CB2_TestRunner(void);
void Test_ExpectedResult(enum TestResult);
void Test_ExpectLeaks(bool32);
void Test_ExitWithResult(enum TestResult, const char *fmt, ...);
s32 MgbaPrintf_(const char *fmt, ...);
@ -160,6 +162,9 @@ s32 MgbaPrintf_(const char *fmt, ...);
#define KNOWN_FAILING \
Test_ExpectedResult(TEST_RESULT_FAIL)
#define KNOWN_LEAKING \
Test_ExpectLeaks(TRUE)
#define PARAMETRIZE if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter)
#define TO_DO \

View File

@ -369,7 +369,7 @@
* s16 damage;
* HP_BAR(player, captureDamage: &damage);
* If none of the above are used, causes the test to fail if the HP
* changes at all.
* does not change at all.
*
* MESSAGE(pattern)
* Causes the test to fail if the message in pattern is not displayed.
@ -614,6 +614,7 @@ struct BattleTestRunnerState
bool8 runThen:1;
bool8 runFinally:1;
bool8 runningFinally:1;
bool8 tearDownBattle:1;
struct BattleTestData data;
u8 *results;
u8 checkProgressParameter;
@ -712,6 +713,11 @@ void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext);
/* Given */
struct moveWithPP {
u16 moveId;
u8 pp;
};
#define GIVEN for (; gBattleTestRunnerState->runGiven; gBattleTestRunnerState->runGiven = FALSE)
#define RNGSeed(seed) RNGSeed_(__LINE__, seed)
@ -732,6 +738,7 @@ void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext);
#define Speed(speed) Speed_(__LINE__, speed)
#define Item(item) Item_(__LINE__, item)
#define Moves(move1, ...) Moves_(__LINE__, (const u16 [MAX_MON_MOVES]) { move1, __VA_ARGS__ })
#define MovesWithPP(movewithpp1, ...) MovesWithPP_(__LINE__, (struct moveWithPP[MAX_MON_MOVES]) {movewithpp1, __VA_ARGS__})
#define Friendship(friendship) Friendship_(__LINE__, friendship)
#define Status1(status1) Status1_(__LINE__, status1)
@ -752,6 +759,7 @@ void SpDefense_(u32 sourceLine, u32 spDefense);
void Speed_(u32 sourceLine, u32 speed);
void Item_(u32 sourceLine, u32 item);
void Moves_(u32 sourceLine, const u16 moves[MAX_MON_MOVES]);
void MovesWithPP_(u32 sourceLine, struct moveWithPP moveWithPP[MAX_MON_MOVES]);
void Friendship_(u32 sourceLine, u32 friendship);
void Status1_(u32 sourceLine, u32 status1);
@ -889,7 +897,9 @@ void QueueStatus(u32 sourceLine, struct BattlePokemon *battler, struct StatusEve
/* Finally */
#define FINALLY for (; gBattleTestRunnerState->runFinally; gBattleTestRunnerState->runFinally = FALSE) if ((gBattleTestRunnerState->runningFinally = TRUE))
#define FINALLY for (ValidateFinally(__LINE__); gBattleTestRunnerState->runFinally; gBattleTestRunnerState->runFinally = FALSE) if ((gBattleTestRunnerState->runningFinally = TRUE))
void ValidateFinally(u32 sourceLine);
/* Expect */

View File

@ -42,7 +42,14 @@ static bool32 PrefixMatch(const char *pattern, const char *string)
}
}
enum { STATE_INIT, STATE_NEXT_TEST, STATE_REPORT_RESULT, STATE_EXIT };
enum
{
STATE_INIT,
STATE_NEXT_TEST,
STATE_RUN_TEST,
STATE_REPORT_RESULT,
STATE_EXIT,
};
void CB2_TestRunner(void)
{
@ -81,6 +88,26 @@ void CB2_TestRunner(void)
return;
}
MgbaPrintf_(":N%s", gTestRunnerState.test->name);
gTestRunnerState.result = TEST_RESULT_PASS;
gTestRunnerState.expectedResult = TEST_RESULT_PASS;
gTestRunnerState.expectLeaks = FALSE;
if (gTestRunnerHeadless)
gTestRunnerState.timeoutSeconds = TIMEOUT_SECONDS;
else
gTestRunnerState.timeoutSeconds = UINT_MAX;
InitHeap(gHeap, HEAP_SIZE);
EnableInterrupts(INTR_FLAG_TIMER2);
REG_TM2CNT_L = UINT16_MAX - (274 * 60); // Approx. 1 second.
REG_TM2CNT_H = TIMER_ENABLE | TIMER_INTR_ENABLE | TIMER_1024CLK;
// NOTE: Assumes that the compiler interns __FILE__.
if (gTestRunnerState.skipFilename == gTestRunnerState.test->filename)
{
gTestRunnerState.result = TEST_RESULT_ASSUMPTION_FAIL;
return;
}
// Greedily assign tests to processes based on estimated cost.
// TODO: Make processCosts a min heap.
if (gTestRunnerState.test->runner != &gAssumptionsRunner)
@ -98,39 +125,26 @@ void CB2_TestRunner(void)
}
}
if (minCostProcess == gTestRunnerI)
gTestRunnerState.state = STATE_RUN_TEST;
else
gTestRunnerState.state = STATE_NEXT_TEST;
// XXX: If estimateCost exits only on some processes then
// processCosts will be inconsistent.
if (gTestRunnerState.test->runner->estimateCost)
gTestRunnerState.processCosts[minCostProcess] += gTestRunnerState.test->runner->estimateCost(gTestRunnerState.test->data);
else
gTestRunnerState.processCosts[minCostProcess] += 1;
if (minCostProcess != gTestRunnerI)
return;
}
MgbaPrintf_(":N%s", gTestRunnerState.test->name);
break;
case STATE_RUN_TEST:
gTestRunnerState.state = STATE_REPORT_RESULT;
gTestRunnerState.result = TEST_RESULT_PASS;
gTestRunnerState.expectedResult = TEST_RESULT_PASS;
if (gTestRunnerHeadless)
gTestRunnerState.timeoutSeconds = TIMEOUT_SECONDS;
else
gTestRunnerState.timeoutSeconds = UINT_MAX;
InitHeap(gHeap, HEAP_SIZE);
EnableInterrupts(INTR_FLAG_TIMER2);
REG_TM2CNT_L = UINT16_MAX - (274 * 60); // Approx. 1 second.
REG_TM2CNT_H = TIMER_ENABLE | TIMER_INTR_ENABLE | TIMER_1024CLK;
// NOTE: Assumes that the compiler interns __FILE__.
if (gTestRunnerState.skipFilename == gTestRunnerState.test->filename)
{
gTestRunnerState.result = TEST_RESULT_ASSUMPTION_FAIL;
}
else
{
if (gTestRunnerState.test->runner->setUp)
gTestRunnerState.test->runner->setUp(gTestRunnerState.test->data);
gTestRunnerState.test->runner->run(gTestRunnerState.test->data);
}
if (gTestRunnerState.test->runner->setUp)
gTestRunnerState.test->runner->setUp(gTestRunnerState.test->data);
gTestRunnerState.test->runner->run(gTestRunnerState.test->data);
break;
case STATE_REPORT_RESULT:
@ -141,6 +155,26 @@ void CB2_TestRunner(void)
if (gTestRunnerState.test->runner->tearDown)
gTestRunnerState.test->runner->tearDown(gTestRunnerState.test->data);
if (!gTestRunnerState.expectLeaks)
{
const struct MemBlock *head = HeapHead();
const struct MemBlock *block = head;
do
{
if (block->allocated)
{
const char *location = MemBlockLocation(block);
if (location)
MgbaPrintf_("%s: %d bytes not freed", location, block->size);
else
MgbaPrintf_("<unknown>: %d bytes not freed", block->size);
gTestRunnerState.result = TEST_RESULT_FAIL;
}
block = block->next;
}
while (block != head);
}
if (gTestRunnerState.test->runner == &gAssumptionsRunner)
{
if (gTestRunnerState.result != TEST_RESULT_PASS)
@ -238,6 +272,11 @@ void Test_ExpectedResult(enum TestResult result)
gTestRunnerState.expectedResult = result;
}
void Test_ExpectLeaks(bool32 expectLeaks)
{
gTestRunnerState.expectLeaks = expectLeaks;
}
static void FunctionTest_SetUp(void *data)
{
(void)data;
@ -319,6 +358,8 @@ static void Intr_Timer2(void)
}
else
{
if (gTestRunnerState.state == STATE_RUN_TEST)
gTestRunnerState.state = STATE_REPORT_RESULT;
gTestRunnerState.result = TEST_RESULT_TIMEOUT;
ReinitCallbacks();
IRQ_LR = ((uintptr_t)JumpToAgbMainLoop & ~1) + 4;
@ -328,16 +369,19 @@ static void Intr_Timer2(void)
void Test_ExitWithResult(enum TestResult result, const char *fmt, ...)
{
bool32 handled = FALSE;
gTestRunnerState.result = result;
ReinitCallbacks();
if (gTestRunnerState.test->runner->handleExitWithResult)
handled = gTestRunnerState.test->runner->handleExitWithResult(gTestRunnerState.test->data, result);
if (!handled && gTestRunnerState.result != gTestRunnerState.expectedResult)
if (gTestRunnerState.state == STATE_REPORT_RESULT
&& gTestRunnerState.test->runner->handleExitWithResult)
{
va_list va;
va_start(va, fmt);
MgbaVPrintf_(fmt, va);
if (!gTestRunnerState.test->runner->handleExitWithResult(gTestRunnerState.test->data, result)
&& gTestRunnerState.result != gTestRunnerState.expectedResult)
{
va_list va;
va_start(va, fmt);
MgbaVPrintf_(fmt, va);
va_end(va);
}
}
JumpToAgbMainLoop();
}

View File

@ -134,6 +134,8 @@ static void BattleTest_SetUp(void *data)
Test_ExitWithResult(TEST_RESULT_ERROR, "OOM: STATE = AllocZerod(%d)", sizeof(*STATE));
InvokeTestFunction(test);
STATE->parameters = STATE->parametersCount;
if (STATE->parametersCount == 0 && test->resultsSize > 0)
Test_ExitWithResult(TEST_RESULT_INVALID, "results without PARAMETRIZE");
STATE->results = AllocZeroed(test->resultsSize * STATE->parameters);
if (!STATE->results)
Test_ExitWithResult(TEST_RESULT_ERROR, "OOM: STATE->results = AllocZerod(%d)", sizeof(test->resultsSize * STATE->parameters));
@ -893,6 +895,15 @@ static void BattleTest_TearDown(void *data)
{
if (STATE)
{
// Free resources that aren't cleaned up when the battle was
// aborted unexpectedly.
if (STATE->tearDownBattle)
{
FreeMonSpritesGfx();
FreeBattleSpritesData();
FreeBattleResources();
FreeAllWindowBuffers();
}
FREE_AND_SET_NULL(STATE->results);
FREE_AND_SET_NULL(STATE);
}
@ -923,12 +934,15 @@ static bool32 BattleTest_HandleExitWithResult(void *data, enum TestResult result
}
else
{
STATE->tearDownBattle = TRUE;
return FALSE;
}
}
void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext ctx)
{
const struct BattleTest *test = gTestRunnerState.test->data;
INVALID_IF(test->resultsSize > 0, "PASSES_RANDOMLY is incompatible with results");
INVALID_IF(passes > trials, "%d passes specified, but only %d trials", passes, trials);
STATE->rngTag = ctx.tag;
STATE->runTrial = 0;
@ -1164,6 +1178,21 @@ void Moves_(u32 sourceLine, const u16 moves[MAX_MON_MOVES])
DATA.explicitMoves[DATA.currentSide] |= 1 << DATA.currentPartyIndex;
}
void MovesWithPP_(u32 sourceLine, struct moveWithPP moveWithPP[MAX_MON_MOVES])
{
s32 i;
INVALID_IF(!DATA.currentMon, "Moves outside of PLAYER/OPPONENT");
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moveWithPP[i].moveId == MOVE_NONE)
break;
INVALID_IF(moveWithPP[i].moveId >= MOVES_COUNT, "Illegal move: %d", &moveWithPP[i].moveId);
SetMonData(DATA.currentMon, MON_DATA_MOVE1 + i, &moveWithPP[i].moveId);
SetMonData(DATA.currentMon, MON_DATA_PP1 + i, &moveWithPP[i].pp);
}
DATA.explicitMoves[DATA.currentSide] |= 1 << DATA.currentPartyIndex;
}
void Friendship_(u32 sourceLine, u32 friendship)
{
INVALID_IF(!DATA.currentMon, "Friendship outside of PLAYER/OPPONENT");
@ -1539,13 +1568,15 @@ void UseItem(u32 sourceLine, struct BattlePokemon *battler, struct ItemContext c
}
INVALID_IF(i == MAX_MON_MOVES, "USE_ITEM on invalid move: %d", ctx.move);
}
else
{
i = 0;
}
PushBattlerAction(sourceLine, battlerId, RECORDED_ACTION_TYPE, B_ACTION_USE_ITEM);
PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_ID, (ctx.itemId >> 8) & 0xFF);
PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_ID, ctx.itemId & 0xFF);
if (ctx.explicitPartyIndex)
gBattleStruct->itemPartyIndex[battlerId] = ctx.partyIndex;
if (ctx.explicitMove)
gBattleStruct->itemPartyIndex[battlerId] = i;
PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_TARGET, ctx.partyIndex);
PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_MOVE, i);
DATA.actionBattlers |= 1 << battlerId;
}
@ -1696,7 +1727,6 @@ void QueueMessage(u32 sourceLine, const u8 *pattern)
};
}
void QueueStatus(u32 sourceLine, struct BattlePokemon *battler, struct StatusEventContext ctx)
{
s32 battlerId = battler - gBattleMons;
@ -1736,3 +1766,11 @@ void QueueStatus(u32 sourceLine, struct BattlePokemon *battler, struct StatusEve
}},
};
}
void ValidateFinally(u32 sourceLine)
{
// Defer this error until after estimating the cost.
if (STATE->results == NULL)
return;
INVALID_IF(STATE->parametersCount == 0, "FINALLY without PARAMETRIZE");
}

View File

@ -23,7 +23,9 @@
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#ifndef __APPLE__
#include <sys/prctl.h>
#endif
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
@ -176,9 +178,9 @@ static void exit2(int _)
int main(int argc, char *argv[])
{
if (argc < 3)
if (argc < 4)
{
fprintf(stderr, "usage %s mgba-rom-test rom\n", argv[0]);
fprintf(stderr, "usage %s mgba-rom-test objcopy rom\n", argv[0]);
exit(2);
}
@ -205,7 +207,7 @@ int main(int argc, char *argv[])
}
int elffd;
if ((elffd = open(argv[2], O_RDONLY)) == -1)
if ((elffd = open(argv[3], O_RDONLY)) == -1)
{
perror("open elffd failed");
exit(2);
@ -264,11 +266,13 @@ int main(int argc, char *argv[])
perror("fork mgba-rom-test failed");
exit(2);
} else if (pid == 0) {
#ifndef __APPLE__
if (prctl(PR_SET_PDEATHSIG, SIGTERM) == -1)
{
perror("prctl failed");
_exit(2);
}
#endif
if (getppid() != parent_pid) // Parent died.
{
_exit(2);
@ -332,6 +336,36 @@ int main(int argc, char *argv[])
_exit(2);
}
}
#ifdef __APPLE__
pid_t objcopypid = fork();
if (objcopypid == -1)
{
perror("fork objcopy failed");
_exit(2);
}
else if (objcopypid == 0)
{
if (execlp(argv[2], argv[2], "-O", "binary", rom_path, rom_path, NULL) == -1)
{
perror("execlp objcopy failed");
_exit(2);
}
}
else
{
int wstatus;
if (waitpid(objcopypid, &wstatus, 0) == -1)
{
perror("waitpid objcopy failed");
_exit(2);
}
if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0)
{
fprintf(stderr, "objcopy exited with an error\n");
_exit(2);
}
}
#endif
// stdbuf is required because otherwise mgba never flushes
// stdout.
if (execlp("stdbuf", "stdbuf", "-oL", argv[1], "-l15", "-ClogLevel.gba.dma=16", "-Rr0", rom_path, NULL) == -1)

3147
tools/patchelf/elf.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
#include <ctype.h>
#include <elf.h>
#include "elf.h"
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>