diff --git a/Makefile b/Makefile index aae215c587..facc0a8d83 100644 --- a/Makefile +++ b/Makefile @@ -372,6 +372,13 @@ ifneq ($(NODEP),1) -include $(addprefix $(OBJ_DIR)/,$(C_SRCS:.c=.d)) endif +$(TEST_BUILDDIR)/%.o: $(TEST_SUBDIR)/%.c + @echo "$(CC1) -o $@ $<" + @$(CPP) $(CPPFLAGS) $< | $(PREPROC) -i $< charmap.txt | $(CC1) $(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $(AS) $(ASFLAGS) -o $@ - + +$(TEST_BUILDDIR)/%.d: $(TEST_SUBDIR)/%.c + $(SCANINC) -M $@ $(INCLUDE_SCANINC_ARGS) -I tools/agbcc/include $< + $(ASM_BUILDDIR)/%.o: $(ASM_SUBDIR)/%.s $(AS) $(ASFLAGS) -o $@ $< @@ -415,14 +422,6 @@ $(OBJ_DIR)/sym_ewram.ld: sym_ewram.txt $(DATA_SRC_SUBDIR)/pokemon/teachable_learnsets.h: $(DATA_ASM_BUILDDIR)/event_scripts.o python3 $(TOOLS_DIR)/learnset_helpers/teachable.py -# NOTE: Based on C_DEP above, but without NODEP and KEEP_TEMPS handling. -define TEST_DEP -$1: $2 $$(shell $(SCANINC) -I include -I $(TOOLS_DIR)/agbcc/include $2) - @echo "$$(CC1) -o $$@ $$<" - @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) -i $$< charmap.txt | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - -endef -$(foreach src, $(TEST_SRCS), $(eval $(call TEST_DEP,$(patsubst $(TEST_SUBDIR)/%.c,$(TEST_BUILDDIR)/%.o,$(src)),$(src),$(patsubst $(TEST_SUBDIR)/%.c,%,$(src))))) - # Linker script LD_SCRIPT := ld_script_modern.ld LD_SCRIPT_DEPS := diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index 0aa5e1b95b..3729a4f73f 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -931,7 +931,7 @@ void HandleSpeciesGfxDataChange(u8 battlerAtk, u8 battlerDef, bool32 megaEvo, bo if (GetBattlerSide(battlerAtk) == B_SIDE_PLAYER) { - if (B_TRANSFORM_SHINY >= GEN_4 && trackEnemyPersonality) + if (B_TRANSFORM_SHINY >= GEN_4 && trackEnemyPersonality && !megaEvo) { personalityValue = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerDef]], MON_DATA_PERSONALITY); isShiny = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerDef]], MON_DATA_IS_SHINY); @@ -949,7 +949,7 @@ void HandleSpeciesGfxDataChange(u8 battlerAtk, u8 battlerDef, bool32 megaEvo, bo } else { - if (B_TRANSFORM_SHINY >= GEN_4 && trackEnemyPersonality) + if (B_TRANSFORM_SHINY >= GEN_4 && trackEnemyPersonality && !megaEvo) { personalityValue = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerDef]], MON_DATA_PERSONALITY); isShiny = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerDef]], MON_DATA_IS_SHINY); diff --git a/src/battle_util.c b/src/battle_util.c index 1dd7b41938..0b4496464b 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2297,14 +2297,14 @@ s32 GetDrainedBigRootHp(u32 battler, s32 hp) return hp * -1; } -#define MAGIC_GUARD_CHECK \ -if (ability == ABILITY_MAGIC_GUARD) \ -{\ - RecordAbilityBattle(battler, ability);\ - gBattleStruct->turnEffectsTracker++;\ - break;\ -} +static inline bool32 IsBattlerProtectedByMagicGuard(u32 battler, u32 ability) +{ + if (ability != ABILITY_MAGIC_GUARD) + return FALSE; + RecordAbilityBattle(battler, ability); + return TRUE; +} u8 DoBattlerEndTurnEffects(void) { @@ -2432,10 +2432,9 @@ u8 DoBattlerEndTurnEffects(void) case ENDTURN_LEECH_SEED: // leech seed if ((gStatuses3[battler] & STATUS3_LEECHSEED) && IsBattlerAlive(gStatuses3[battler] & STATUS3_LEECHSEED_BATTLER) - && IsBattlerAlive(battler)) + && IsBattlerAlive(battler) + && !IsBattlerProtectedByMagicGuard(battler, ability)) { - MAGIC_GUARD_CHECK; - gBattlerTarget = gStatuses3[battler] & STATUS3_LEECHSEED_BATTLER; // Notice gBattlerTarget is actually the HP receiver. gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / 8; if (gBattleMoveDamage == 0) @@ -2449,10 +2448,9 @@ u8 DoBattlerEndTurnEffects(void) break; case ENDTURN_POISON: // poison if ((gBattleMons[battler].status1 & STATUS1_POISON) - && IsBattlerAlive(battler)) + && IsBattlerAlive(battler) + && !IsBattlerProtectedByMagicGuard(battler, ability)) { - MAGIC_GUARD_CHECK; - if (ability == ABILITY_POISON_HEAL) { if (!BATTLER_MAX_HP(battler) && !(gStatuses3[battler] & STATUS3_HEAL_BLOCK)) @@ -2478,10 +2476,9 @@ u8 DoBattlerEndTurnEffects(void) break; case ENDTURN_BAD_POISON: // toxic poison if ((gBattleMons[battler].status1 & STATUS1_TOXIC_POISON) - && IsBattlerAlive(battler)) + && IsBattlerAlive(battler) + && !IsBattlerProtectedByMagicGuard(battler, ability)) { - MAGIC_GUARD_CHECK; - if (ability == ABILITY_POISON_HEAL) { if (!BATTLER_MAX_HP(battler) && !(gStatuses3[battler] & STATUS3_HEAL_BLOCK)) @@ -2510,9 +2507,9 @@ u8 DoBattlerEndTurnEffects(void) break; case ENDTURN_BURN: // burn if ((gBattleMons[battler].status1 & STATUS1_BURN) - && IsBattlerAlive(battler)) + && IsBattlerAlive(battler) + && !IsBattlerProtectedByMagicGuard(battler, ability)) { - MAGIC_GUARD_CHECK; gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / (B_BURN_DAMAGE >= GEN_7 ? 16 : 8); if (ability == ABILITY_HEATPROOF) { @@ -2529,9 +2526,9 @@ u8 DoBattlerEndTurnEffects(void) break; case ENDTURN_FROSTBITE: // burn if ((gBattleMons[battler].status1 & STATUS1_FROSTBITE) - && IsBattlerAlive(battler)) + && IsBattlerAlive(battler) + && !IsBattlerProtectedByMagicGuard(battler, ability)) { - MAGIC_GUARD_CHECK; gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / (B_BURN_DAMAGE >= GEN_7 ? 16 : 8); if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -2542,9 +2539,9 @@ u8 DoBattlerEndTurnEffects(void) break; case ENDTURN_NIGHTMARES: // spooky nightmares if ((gBattleMons[battler].status2 & STATUS2_NIGHTMARE) - && IsBattlerAlive(battler)) + && IsBattlerAlive(battler) + && !IsBattlerProtectedByMagicGuard(battler, ability)) { - MAGIC_GUARD_CHECK; // R/S does not perform this sleep check, which causes the nightmare effect to // persist even after the affected Pokémon has been awakened by Shed Skin. if (gBattleMons[battler].status1 & STATUS1_SLEEP) @@ -2564,9 +2561,9 @@ u8 DoBattlerEndTurnEffects(void) break; case ENDTURN_CURSE: // curse if ((gBattleMons[battler].status2 & STATUS2_CURSED) - && IsBattlerAlive(battler)) + && IsBattlerAlive(battler) + && !IsBattlerProtectedByMagicGuard(battler, ability)) { - MAGIC_GUARD_CHECK; gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / 4; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -2580,7 +2577,11 @@ u8 DoBattlerEndTurnEffects(void) { if (--gDisableStructs[battler].wrapTurns != 0) // damaged by wrap { - MAGIC_GUARD_CHECK; + if (IsBattlerProtectedByMagicGuard(battler, ability)) + { + gBattleStruct->turnEffectsTracker++; + break; + } gBattleScripting.animArg1 = gBattleStruct->wrappedMove[battler]; gBattleScripting.animArg2 = gBattleStruct->wrappedMove[battler] >> 8; @@ -2884,7 +2885,9 @@ u8 DoBattlerEndTurnEffects(void) gBattleStruct->turnEffectsTracker++; break; case ENDTURN_SALT_CURE: - if (gStatuses4[battler] & STATUS4_SALT_CURE && IsBattlerAlive(battler)) + if (gStatuses4[battler] & STATUS4_SALT_CURE + && IsBattlerAlive(battler) + && !IsBattlerProtectedByMagicGuard(battler, ability)) { gBattlerTarget = battler; if (IS_BATTLER_OF_TYPE(gBattlerTarget, TYPE_STEEL) || IS_BATTLER_OF_TYPE(gBattlerTarget, TYPE_WATER)) diff --git a/test/battle/move_effect/fixed_damage_arg.c b/test/battle/move_effect/fixed_damage_arg.c new file mode 100644 index 0000000000..484601be05 --- /dev/null +++ b/test/battle/move_effect/fixed_damage_arg.c @@ -0,0 +1,41 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_SONIC_BOOM].effect == EFFECT_FIXED_DAMAGE_ARG); +} + +SINGLE_BATTLE_TEST("Sonic Boom deals fixed damage", s16 damage) +{ + u16 mon; + PARAMETRIZE { mon = SPECIES_RATTATA; } + PARAMETRIZE { mon = SPECIES_ARON; } + + GIVEN { + ASSUME(gMovesInfo[MOVE_SONIC_BOOM].argument == 20); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(mon); + } WHEN { + TURN { MOVE(player, MOVE_SONIC_BOOM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SONIC_BOOM, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT(results[0].damage == 20); + EXPECT(results[1].damage == 20); + } +} + +SINGLE_BATTLE_TEST("Sonic Boom doesn't affect ghost types") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GASTLY); + } WHEN { + TURN { MOVE(player, MOVE_SONIC_BOOM); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SONIC_BOOM, player); + MESSAGE("It doesn't affect the opposing Gastly…"); + } +} diff --git a/test/battle/move_effect/salt_cure.c b/test/battle/move_effect/salt_cure.c index 006a55d642..495a7e8c80 100644 --- a/test/battle/move_effect/salt_cure.c +++ b/test/battle/move_effect/salt_cure.c @@ -99,3 +99,21 @@ SINGLE_BATTLE_TEST("Salt Cure does not get applied if hitting a Substitute") NOT MESSAGE("The opposing Wobbuffet is being salt cured!"); } } + +SINGLE_BATTLE_TEST("Salt Cure residual damage does not inflict any damage against Magic Guard") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CLEFABLE) { Ability(ABILITY_MAGIC_GUARD); }; + } WHEN { + TURN { MOVE(player, MOVE_SALT_CURE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SALT_CURE, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SALT_CURE_DAMAGE, opponent); + HP_BAR(opponent); + MESSAGE("The opposing Clefable is hurt by Salt Cure!"); + } + } +} diff --git a/test/battle/terrain/starting_terrain.c b/test/battle/terrain/starting_terrain.c index e505dbd185..ab31bbbb96 100644 --- a/test/battle/terrain/starting_terrain.c +++ b/test/battle/terrain/starting_terrain.c @@ -67,7 +67,7 @@ SINGLE_BATTLE_TEST("Terrain started after the one which started the battle lasts VarSet(B_VAR_STARTING_STATUS_TIMER, 0); GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Ability(viaMove == TRUE ? ABILITY_SHADOW_TAG : ABILITY_GRASSY_SURGE); } + PLAYER(SPECIES_TAPU_BULU) { Ability(viaMove == TRUE ? ABILITY_TELEPATHY : ABILITY_GRASSY_SURGE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { // More than 5 turns @@ -84,7 +84,7 @@ SINGLE_BATTLE_TEST("Terrain started after the one which started the battle lasts ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); // Player uses Grassy Terrain if (viaMove) { - MESSAGE("Wobbuffet used GrssyTerrain!"); + MESSAGE("Tapu Bulu used Grassy Terrain!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, player); MESSAGE("Grass grew to cover the battlefield!"); } else { @@ -94,13 +94,13 @@ SINGLE_BATTLE_TEST("Terrain started after the one which started the battle lasts } // 5 turns - MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Tapu Bulu used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); - MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Tapu Bulu used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); - MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Tapu Bulu used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); MESSAGE("The grass disappeared from the battlefield."); diff --git a/tools/mgba-rom-test-hydra/Makefile b/tools/mgba-rom-test-hydra/Makefile index f93f991d9b..ef0523adc9 100644 --- a/tools/mgba-rom-test-hydra/Makefile +++ b/tools/mgba-rom-test-hydra/Makefile @@ -12,7 +12,7 @@ all: mgba-rom-test-hydra$(EXE) @: mgba-rom-test-hydra$(EXE): $(SRCS) - $(CC) $(SRCS) -o $@ -lm $(LDFLAGS) + $(CC) $(SRCS) -Werror=implicit-function-declaration -o $@ -lm $(LDFLAGS) clean: $(RM) mgba-rom-test-hydra$(EXE) diff --git a/tools/mgba-rom-test-hydra/main.c b/tools/mgba-rom-test-hydra/main.c index 841b7328e5..d4c0f4e080 100644 --- a/tools/mgba-rom-test-hydra/main.c +++ b/tools/mgba-rom-test-hydra/main.c @@ -106,6 +106,28 @@ static const struct Symbol *lookup_address(uint32_t address) return NULL; } +#ifndef _GNU_SOURCE +// Very naive implementation of 'memmem' for systems which don't make it +// available by default. +void *memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) +{ + const char *haystack_ = haystack; + const char *needle_ = needle; + for (size_t i = 0; i < haystacklen - needlelen; i++) + { + size_t j; + for (j = 0; j < needlelen; j++) + { + if (haystack_[i+j] != needle_[j]) + break; + } + if (j == needlelen) + return (void *)&haystack_[i]; + } + return NULL; +} +#endif + // Similar to 'fwrite(buffer, 1, size, f)' except that anything which // looks like the output of '%p' (i.e. '<0x\d{7}>') is translated into // the name of a symbol (if it represents one).