Merge branch 'RHH/upcoming' into RHH/pr/feature/formBattleChange

# Conflicts:
#	src/battle_util.c
This commit is contained in:
Eduardo Quezada 2023-02-02 17:56:13 -03:00
commit 101ea6522d
114 changed files with 7063 additions and 356 deletions

View File

@ -29,7 +29,7 @@ jobs:
repository: pret/agbcc repository: pret/agbcc
- name: Install binutils - name: Install binutils
run: sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi run: sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi libelf-dev
# build-essential, git, and libpng-dev are already installed # build-essential, git, and libpng-dev are already installed
# gcc-arm-none-eabi is only needed for the modern build # gcc-arm-none-eabi is only needed for the modern build
# as an alternative to dkP # as an alternative to dkP
@ -41,10 +41,15 @@ jobs:
working-directory: agbcc working-directory: agbcc
- name: Agbcc - name: Agbcc
run: make -j${nproc} all run: make -j${nproc} -O all
- name: Modern - name: Modern
env: env:
MODERN: 1 MODERN: 1
COMPARE: 0 COMPARE: 0
run: make -j${nproc} all run: make -j${nproc} -O all
- name: Test
run: |
make -j${nproc} -O pokeemerald-test.elf
make -j${nproc} check

View File

@ -79,6 +79,9 @@ ELF = $(ROM:.gba=.elf)
MAP = $(ROM:.gba=.map) MAP = $(ROM:.gba=.map)
SYM = $(ROM:.gba=.sym) SYM = $(ROM:.gba=.sym)
TESTELF = $(ROM:.gba=-test.elf)
HEADLESSELF = $(ROM:.gba=-test-headless.elf)
C_SUBDIR = src C_SUBDIR = src
GFLIB_SUBDIR = gflib GFLIB_SUBDIR = gflib
ASM_SUBDIR = asm ASM_SUBDIR = asm
@ -88,6 +91,7 @@ SONG_SUBDIR = sound/songs
MID_SUBDIR = sound/songs/midi MID_SUBDIR = sound/songs/midi
SAMPLE_SUBDIR = sound/direct_sound_samples SAMPLE_SUBDIR = sound/direct_sound_samples
CRY_SUBDIR = sound/direct_sound_samples/cries CRY_SUBDIR = sound/direct_sound_samples/cries
TEST_SUBDIR = test
C_BUILDDIR = $(OBJ_DIR)/$(C_SUBDIR) C_BUILDDIR = $(OBJ_DIR)/$(C_SUBDIR)
GFLIB_BUILDDIR = $(OBJ_DIR)/$(GFLIB_SUBDIR) GFLIB_BUILDDIR = $(OBJ_DIR)/$(GFLIB_SUBDIR)
@ -95,6 +99,7 @@ ASM_BUILDDIR = $(OBJ_DIR)/$(ASM_SUBDIR)
DATA_ASM_BUILDDIR = $(OBJ_DIR)/$(DATA_ASM_SUBDIR) DATA_ASM_BUILDDIR = $(OBJ_DIR)/$(DATA_ASM_SUBDIR)
SONG_BUILDDIR = $(OBJ_DIR)/$(SONG_SUBDIR) SONG_BUILDDIR = $(OBJ_DIR)/$(SONG_SUBDIR)
MID_BUILDDIR = $(OBJ_DIR)/$(MID_SUBDIR) MID_BUILDDIR = $(OBJ_DIR)/$(MID_SUBDIR)
TEST_BUILDDIR = $(OBJ_DIR)/$(TEST_SUBDIR)
ASFLAGS := -mcpu=arm7tdmi --defsym MODERN=$(MODERN) ASFLAGS := -mcpu=arm7tdmi --defsym MODERN=$(MODERN)
@ -131,10 +136,13 @@ RAMSCRGEN := tools/ramscrgen/ramscrgen$(EXE)
FIX := tools/gbafix/gbafix$(EXE) FIX := tools/gbafix/gbafix$(EXE)
MAPJSON := tools/mapjson/mapjson$(EXE) MAPJSON := tools/mapjson/mapjson$(EXE)
JSONPROC := tools/jsonproc/jsonproc$(EXE) JSONPROC := tools/jsonproc/jsonproc$(EXE)
PATCHELF := tools/patchelf/patchelf$(EXE)
ROMTEST ?= $(shell { command -v mgba-rom-test || command -v tools/mgba/mgba-rom-test$(EXE); } 2>/dev/null)
ROMTESTHYDRA := tools/mgba-rom-test-hydra/mgba-rom-test-hydra$(EXE)
PERL := perl PERL := perl
TOOLDIRS := $(filter-out tools/agbcc tools/binutils,$(wildcard tools/*)) TOOLDIRS := $(filter-out tools/mgba tools/agbcc tools/binutils,$(wildcard tools/*))
TOOLBASE = $(TOOLDIRS:tools/%=%) TOOLBASE = $(TOOLDIRS:tools/%=%)
TOOLS = $(foreach tool,$(TOOLBASE),tools/$(tool)/$(tool)$(EXE)) TOOLS = $(foreach tool,$(TOOLBASE),tools/$(tool)/$(tool)$(EXE))
@ -150,7 +158,7 @@ MAKEFLAGS += --no-print-directory
# Secondary expansion is required for dependency variables in object rules. # Secondary expansion is required for dependency variables in object rules.
.SECONDEXPANSION: .SECONDEXPANSION:
.PHONY: all rom clean compare tidy tools mostlyclean clean-tools $(TOOLDIRS) libagbsyscall modern tidymodern tidynonmodern .PHONY: all rom clean compare tidy tools mostlyclean clean-tools $(TOOLDIRS) libagbsyscall modern tidymodern tidynonmodern check
infoshell = $(foreach line, $(shell $1 | sed "s/ /__SPACE__/g"), $(info $(subst __SPACE__, ,$(line)))) infoshell = $(foreach line, $(shell $1 | sed "s/ /__SPACE__/g"), $(info $(subst __SPACE__, ,$(line))))
@ -158,7 +166,7 @@ infoshell = $(foreach line, $(shell $1 | sed "s/ /__SPACE__/g"), $(info $(subst
# Disable dependency scanning for clean/tidy/tools # Disable dependency scanning for clean/tidy/tools
# Use a separate minimal makefile for speed # Use a separate minimal makefile for speed
# Since we don't need to reload most of this makefile # Since we don't need to reload most of this makefile
ifeq (,$(filter-out all rom compare modern libagbsyscall syms,$(MAKECMDGOALS))) ifeq (,$(filter-out all rom compare modern check libagbsyscall syms,$(MAKECMDGOALS)))
$(call infoshell, $(MAKE) -f make_tools.mk) $(call infoshell, $(MAKE) -f make_tools.mk)
else else
NODEP ?= 1 NODEP ?= 1
@ -182,6 +190,11 @@ C_SRCS_IN := $(wildcard $(C_SUBDIR)/*.c $(C_SUBDIR)/*/*.c $(C_SUBDIR)/*/*/*.c)
C_SRCS := $(foreach src,$(C_SRCS_IN),$(if $(findstring .inc.c,$(src)),,$(src))) C_SRCS := $(foreach src,$(C_SRCS_IN),$(if $(findstring .inc.c,$(src)),,$(src)))
C_OBJS := $(patsubst $(C_SUBDIR)/%.c,$(C_BUILDDIR)/%.o,$(C_SRCS)) C_OBJS := $(patsubst $(C_SUBDIR)/%.c,$(C_BUILDDIR)/%.o,$(C_SRCS))
TEST_SRCS_IN := $(wildcard $(TEST_SUBDIR)/*.c $(TEST_SUBDIR)/*/*.c $(TEST_SUBDIR)/*/*/*.c)
TEST_SRCS := $(foreach src,$(TEST_SRCS_IN),$(if $(findstring .inc.c,$(src)),,$(src)))
TEST_OBJS := $(patsubst $(TEST_SUBDIR)/%.c,$(TEST_BUILDDIR)/%.o,$(TEST_SRCS))
TEST_OBJS_REL := $(patsubst $(OBJ_DIR)/%,%,$(TEST_OBJS))
GFLIB_SRCS := $(wildcard $(GFLIB_SUBDIR)/*.c) GFLIB_SRCS := $(wildcard $(GFLIB_SUBDIR)/*.c)
GFLIB_OBJS := $(patsubst $(GFLIB_SUBDIR)/%.c,$(GFLIB_BUILDDIR)/%.o,$(GFLIB_SRCS)) GFLIB_OBJS := $(patsubst $(GFLIB_SUBDIR)/%.c,$(GFLIB_BUILDDIR)/%.o,$(GFLIB_SRCS))
@ -206,7 +219,7 @@ MID_OBJS := $(patsubst $(MID_SUBDIR)/%.mid,$(MID_BUILDDIR)/%.o,$(MID_SRCS))
OBJS := $(C_OBJS) $(GFLIB_OBJS) $(C_ASM_OBJS) $(ASM_OBJS) $(DATA_ASM_OBJS) $(SONG_OBJS) $(MID_OBJS) OBJS := $(C_OBJS) $(GFLIB_OBJS) $(C_ASM_OBJS) $(ASM_OBJS) $(DATA_ASM_OBJS) $(SONG_OBJS) $(MID_OBJS)
OBJS_REL := $(patsubst $(OBJ_DIR)/%,%,$(OBJS)) OBJS_REL := $(patsubst $(OBJ_DIR)/%,%,$(OBJS))
SUBDIRS := $(sort $(dir $(OBJS))) SUBDIRS := $(sort $(dir $(OBJS) $(dir $(TEST_OBJS))))
$(shell mkdir -p $(SUBDIRS)) $(shell mkdir -p $(SUBDIRS))
endif endif
@ -407,6 +420,14 @@ $(OBJ_DIR)/sym_common.ld: sym_common.txt $(C_OBJS) $(wildcard common_syms/*.txt)
$(OBJ_DIR)/sym_ewram.ld: sym_ewram.txt $(OBJ_DIR)/sym_ewram.ld: sym_ewram.txt
$(RAMSCRGEN) ewram_data $< ENGLISH > $@ $(RAMSCRGEN) ewram_data $< ENGLISH > $@
# NOTE: Based on C_DEP above, but without NODEP and KEEP_TEMPS handling.
define TEST_DEP
$1: $2 $$(shell $(SCANINC) -I include -I tools/agbcc/include -I gflib -I test $2)
@echo "$$(CC1) <flags> -o $$@ $$<"
@$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) $$< charmap.txt -i | $$(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)))))
ifeq ($(MODERN),0) ifeq ($(MODERN),0)
LD_SCRIPT := ld_script.txt LD_SCRIPT := ld_script.txt
LD_SCRIPT_DEPS := $(OBJ_DIR)/sym_bss.ld $(OBJ_DIR)/sym_common.ld $(OBJ_DIR)/sym_ewram.ld LD_SCRIPT_DEPS := $(OBJ_DIR)/sym_bss.ld $(OBJ_DIR)/sym_common.ld $(OBJ_DIR)/sym_ewram.ld
@ -429,6 +450,28 @@ $(ROM): $(ELF)
modern: all modern: all
LD_SCRIPT_TEST := ld_script_test.txt
$(OBJ_DIR)/ld_script_test.ld: $(LD_SCRIPT_TEST) $(LD_SCRIPT_DEPS)
cd $(OBJ_DIR) && sed "s#tools/#../../tools/#g" ../../$(LD_SCRIPT_TEST) > ld_script_test.ld
$(TESTELF): $(OBJ_DIR)/ld_script_test.ld $(OBJS) $(TEST_OBJS) libagbsyscall
@echo "cd $(OBJ_DIR) && $(LD) -T ld_script_test.ld -o ../../$@ <objects> <test-objects> <lib>"
@cd $(OBJ_DIR) && $(LD) $(TESTLDFLAGS) -T ld_script_test.ld -o ../../$@ $(OBJS_REL) $(TEST_OBJS_REL) $(LIB)
$(FIX) $@ -t"$(TITLE)" -c$(GAME_CODE) -m$(MAKER_CODE) -r$(REVISION) --silent
$(PATCHELF) pokeemerald-test.elf gTestRunnerArgv "$(TESTS)\0"
ifeq ($(GITHUB_REPOSITORY_OWNER),rh-hideout)
TEST_SKIP_IS_FAIL := \x01
else
TEST_SKIP_IS_FAIL := \x00
endif
check: $(TESTELF)
@cp $< $(HEADLESSELF)
$(PATCHELF) $(HEADLESSELF) gTestRunnerHeadless '\x01' gTestRunnerSkipIsFail "$(TEST_SKIP_IS_FAIL)"
$(ROMTESTHYDRA) $(ROMTEST) $(HEADLESSELF)
libagbsyscall: libagbsyscall:
@$(MAKE) -C libagbsyscall TOOLCHAIN=$(TOOLCHAIN) MODERN=$(MODERN) @$(MAKE) -C libagbsyscall TOOLCHAIN=$(TOOLCHAIN) MODERN=$(MODERN)

View File

@ -2237,3 +2237,8 @@
.endif .endif
waitmessage B_WAIT_TIME_LONG waitmessage B_WAIT_TIME_LONG
.endm .endm
.macro jumpifemergencyexited battler:req, ptr:req
various \battler, VARIOUS_JUMP_IF_EMERGENCY_EXITED
.4byte \ptr
.endm

View File

@ -7,3 +7,4 @@ gIntrTable
gLinkVSyncDisabled gLinkVSyncDisabled
IntrMain_Buffer IntrMain_Buffer
gPcmDmaCounter gPcmDmaCounter
gAgbMainLoop_sp

View File

@ -3055,6 +3055,7 @@ BattleScript_EffectHitEscape:
jumpifbyte CMP_NOT_EQUAL gBattleOutcome 0, BattleScript_HitEscapeEnd jumpifbyte CMP_NOT_EQUAL gBattleOutcome 0, BattleScript_HitEscapeEnd
jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_HitEscapeEnd jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_HitEscapeEnd
jumpifcantswitch SWITCH_IGNORE_ESCAPE_PREVENTION | BS_ATTACKER, BattleScript_HitEscapeEnd jumpifcantswitch SWITCH_IGNORE_ESCAPE_PREVENTION | BS_ATTACKER, BattleScript_HitEscapeEnd
jumpifemergencyexited BS_TARGET, BattleScript_HitEscapeEnd
openpartyscreen BS_ATTACKER, BattleScript_HitEscapeEnd openpartyscreen BS_ATTACKER, BattleScript_HitEscapeEnd
switchoutabilities BS_ATTACKER switchoutabilities BS_ATTACKER
waitstate waitstate

View File

@ -258,7 +258,7 @@ gSpecials::
def_special CallSlateportTentFunction def_special CallSlateportTentFunction
def_special ChoosePartyForBattleFrontier def_special ChoosePartyForBattleFrontier
def_special ValidateEReaderTrainer def_special ValidateEReaderTrainer
def_special GetBestBattleTowerStreak def_special GetBattleTowerSinglesStreak
def_special ReducePlayerPartyToSelectedMons def_special ReducePlayerPartyToSelectedMons
def_special BedroomPC def_special BedroomPC
def_special PlayerPC def_special PlayerPC

View File

@ -196,6 +196,7 @@ struct SpecialStatus
// End of byte // End of byte
u8 weatherAbilityDone:1; u8 weatherAbilityDone:1;
u8 terrainAbilityDone:1; u8 terrainAbilityDone:1;
u8 emergencyExited:1;
}; };
struct SideTimer struct SideTimer
@ -483,7 +484,6 @@ struct MegaEvolutionData
u8 battlerId; u8 battlerId;
bool8 playerSelect; bool8 playerSelect;
u8 triggerSpriteId; u8 triggerSpriteId;
bool8 isWishMegaEvo;
}; };
struct Illusion struct Illusion

View File

@ -5,6 +5,14 @@
#include "constants/battle_anim.h" #include "constants/battle_anim.h"
#include "task.h" #include "task.h"
enum
{
ANIM_TYPE_GENERAL,
ANIM_TYPE_MOVE,
ANIM_TYPE_STATUS,
ANIM_TYPE_SPECIAL,
};
enum enum
{ {
BG_ANIM_SCREEN_SIZE, BG_ANIM_SCREEN_SIZE,
@ -54,7 +62,7 @@ extern u16 gAnimMoveIndex;
void ClearBattleAnimationVars(void); void ClearBattleAnimationVars(void);
void DoMoveAnim(u16 move); void DoMoveAnim(u16 move);
void LaunchBattleAnimation(const u8 *const animsTable[], u16 tableId, bool8 isMoveAnim); void LaunchBattleAnimation(u32 animType, u32 animId);
void DestroyAnimSprite(struct Sprite *sprite); void DestroyAnimSprite(struct Sprite *sprite);
void DestroyAnimVisualTask(u8 taskId); void DestroyAnimVisualTask(u8 taskId);
void DestroyAnimSoundTask(u8 taskId); void DestroyAnimSoundTask(u8 taskId);

View File

@ -23,7 +23,7 @@ bool8 BattleInitAllSprites(u8 *state1, u8 *battlerId);
void ClearSpritesHealthboxAnimData(void); void ClearSpritesHealthboxAnimData(void);
void CopyAllBattleSpritesInvisibilities(void); void CopyAllBattleSpritesInvisibilities(void);
void CopyBattleSpriteInvisibility(u8 battlerId); void CopyBattleSpriteInvisibility(u8 battlerId);
void HandleSpeciesGfxDataChange(u8 attacker, u8 target, bool8 notTransform, bool32 megaEvo); void HandleSpeciesGfxDataChange(u8 attacker, u8 target, bool8 notTransform, bool32 megaEvo, bool8 trackEnemyPersonality);
void BattleLoadSubstituteOrMonSpriteGfx(u8 battlerId, bool8 loadMonSprite); void BattleLoadSubstituteOrMonSpriteGfx(u8 battlerId, bool8 loadMonSprite);
void LoadBattleMonGfxAndAnimate(u8 battlerId, bool8 loadMonSprite, u8 spriteId); void LoadBattleMonGfxAndAnimate(u8 battlerId, bool8 loadMonSprite, u8 spriteId);
void TrySetBehindSubstituteSpriteBit(u8 battlerId, u16 move); void TrySetBehindSubstituteSpriteBit(u8 battlerId, u16 move);

View File

@ -255,6 +255,7 @@
#define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 164 #define VARIOUS_ACTIVATE_WEATHER_CHANGE_ABILITIES 164
#define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 165 #define VARIOUS_ACTIVATE_TERRAIN_CHANGE_ABILITIES 165
#define VARIOUS_JUMP_IF_NO_VALID_TARGETS 166 #define VARIOUS_JUMP_IF_NO_VALID_TARGETS 166
#define VARIOUS_JUMP_IF_EMERGENCY_EXITED 167
// Cmd_manipulatedamage // Cmd_manipulatedamage
#define DMG_CHANGE_SIGN 0 #define DMG_CHANGE_SIGN 0

View File

@ -13,7 +13,6 @@
#define DAYCARE_ONE_MON 2 #define DAYCARE_ONE_MON 2
#define DAYCARE_TWO_MONS 3 #define DAYCARE_TWO_MONS 3
#define INHERITED_IV_COUNT 3
#if P_EGG_HATCH_LEVEL >= GEN_4 #if P_EGG_HATCH_LEVEL >= GEN_4
#define EGG_HATCH_LEVEL 1 #define EGG_HATCH_LEVEL 1
#else #else

View File

@ -33,7 +33,7 @@
#define GAME_STAT_CONSECUTIVE_ROULETTE_WINS 29 #define GAME_STAT_CONSECUTIVE_ROULETTE_WINS 29
#define GAME_STAT_ENTERED_BATTLE_TOWER 30 #define GAME_STAT_ENTERED_BATTLE_TOWER 30
#define GAME_STAT_UNKNOWN_31 31 #define GAME_STAT_UNKNOWN_31 31
#define GAME_STAT_BATTLE_TOWER_BEST_STREAK 32 #define GAME_STAT_BATTLE_TOWER_SINGLES_STREAK 32
#define GAME_STAT_POKEBLOCKS 33 #define GAME_STAT_POKEBLOCKS 33
#define GAME_STAT_POKEBLOCKS_WITH_FRIENDS 34 #define GAME_STAT_POKEBLOCKS_WITH_FRIENDS 34
#define GAME_STAT_WON_LINK_CONTEST 35 #define GAME_STAT_WON_LINK_CONTEST 35

View File

@ -928,7 +928,13 @@
#define ITEM_RUBY 756 #define ITEM_RUBY 756
#define ITEM_SAPPHIRE 757 #define ITEM_SAPPHIRE 757
#define ITEMS_COUNT 758 #define ITEM_ABILITY_SHIELD 758
#define ITEM_CLEAR_AMULET 759
#define ITEM_PUNCHING_GLOVE 760
#define ITEM_COVERT_CLOAK 761
#define ITEM_LOADED_DICE 762
#define ITEMS_COUNT 763
#define ITEM_FIELD_ARROW ITEMS_COUNT #define ITEM_FIELD_ARROW ITEMS_COUNT
// A special item id associated with "Cancel"/"Exit" etc. in a list of items or decorations // A special item id associated with "Cancel"/"Exit" etc. in a list of items or decorations

View File

@ -139,6 +139,14 @@
#define NUM_FLAG_BYTES ROUND_BITS_TO_BYTES(FLAGS_COUNT) #define NUM_FLAG_BYTES ROUND_BITS_TO_BYTES(FLAGS_COUNT)
#define NUM_ADDITIONAL_PHRASE_BYTES ROUND_BITS_TO_BYTES(NUM_ADDITIONAL_PHRASES) #define NUM_ADDITIONAL_PHRASE_BYTES ROUND_BITS_TO_BYTES(NUM_ADDITIONAL_PHRASES)
// Calls m0/m1/.../m8 depending on how many arguments are passed.
#define VARARG_8(m, ...) CAT(m, NARG_8(__VA_ARGS__))(__VA_ARGS__)
#define NARG_8(...) NARG_8_(_, ##__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define NARG_8_(_, a, b, c, d, e, f, g, h, N, ...) N
#define CAT(a, b) CAT_(a, b)
#define CAT_(a, b) a ## b
// This produces an error at compile-time if expr is zero. // This produces an error at compile-time if expr is zero.
// It looks like file.c:line: size of array `id' is negative // It looks like file.c:line: size of array `id' is negative
#define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1]; #define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1];

View File

@ -57,6 +57,7 @@ extern u32 IntrMain_Buffer[];
extern s8 gPcmDmaCounter; extern s8 gPcmDmaCounter;
void AgbMain(void); void AgbMain(void);
void AgbMainLoop(void);
void SetMainCallback2(MainCallback callback); void SetMainCallback2(MainCallback callback);
void InitKeys(void); void InitKeys(void);
void SetVBlankCallback(IntrCallback callback); void SetVBlankCallback(IntrCallback callback);

View File

@ -566,5 +566,6 @@ bool32 ShouldShowFemaleDifferences(u16 species, u32 personality);
bool32 TryFormChange(u32 monId, u32 side, u16 method); bool32 TryFormChange(u32 monId, u32 side, u16 method);
void TryToSetBattleFormChangeMoves(struct Pokemon *mon, u16 method); void TryToSetBattleFormChangeMoves(struct Pokemon *mon, u16 method);
u32 GetMonFriendshipScore(struct Pokemon *pokemon); u32 GetMonFriendshipScore(struct Pokemon *pokemon);
void UpdateMonPersonality(struct BoxPokemon *boxMon, u32 personality);
#endif // GUARD_POKEMON_H #endif // GUARD_POKEMON_H

View File

@ -1,6 +1,51 @@
#ifndef GUARD_RECORDED_BATTLE_H #ifndef GUARD_RECORDED_BATTLE_H
#define GUARD_RECORDED_BATTLE_H #define GUARD_RECORDED_BATTLE_H
#include "constants/battle.h"
#define BATTLER_RECORD_SIZE 664
struct RecordedBattleSave
{
struct Pokemon playerParty[PARTY_SIZE];
struct Pokemon opponentParty[PARTY_SIZE];
u8 playersName[MAX_BATTLERS_COUNT][PLAYER_NAME_LENGTH + 1];
u8 playersGender[MAX_BATTLERS_COUNT];
u32 playersTrainerId[MAX_BATTLERS_COUNT];
u8 playersLanguage[MAX_BATTLERS_COUNT];
u32 rngSeed;
u32 battleFlags;
u8 playersBattlers[MAX_BATTLERS_COUNT];
u16 opponentA;
u16 opponentB;
u16 partnerId;
u16 multiplayerId;
u8 lvlMode;
u8 frontierFacility;
u8 frontierBrainSymbol;
u8 battleScene:1;
u8 textSpeed:3;
u32 AI_scripts;
u8 recordMixFriendName[PLAYER_NAME_LENGTH + 1];
u8 recordMixFriendClass;
u8 apprenticeId;
u16 easyChatSpeech[EASY_CHAT_BATTLE_WORDS_COUNT];
u8 recordMixFriendLanguage;
u8 apprenticeLanguage;
u8 battleRecord[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE];
u32 checksum;
};
enum
{
RECORDED_BYTE, // Generic.
RECORDED_ACTION_TYPE,
RECORDED_MOVE_SLOT,
RECORDED_MOVE_TARGET,
RECORDED_PARTY_INDEX,
RECORDED_BATTLE_PALACE_ACTION,
};
extern u32 gRecordedBattleRngSeed; extern u32 gRecordedBattleRngSeed;
extern u32 gBattlePalaceMoveSelectionRngValue; extern u32 gBattlePalaceMoveSelectionRngValue;
extern u8 gRecordedBattleMultiplayerId; extern u8 gRecordedBattleMultiplayerId;
@ -12,11 +57,12 @@ void RecordedBattle_Init(u8 mode);
void RecordedBattle_SetTrainerInfo(void); void RecordedBattle_SetTrainerInfo(void);
void RecordedBattle_SetBattlerAction(u8 battlerId, u8 action); void RecordedBattle_SetBattlerAction(u8 battlerId, u8 action);
void RecordedBattle_ClearBattlerAction(u8 battlerId, u8 bytesToClear); void RecordedBattle_ClearBattlerAction(u8 battlerId, u8 bytesToClear);
u8 RecordedBattle_GetBattlerAction(u8 battlerId); u8 RecordedBattle_GetBattlerAction(u32 actionType, u8 battlerId);
u8 RecordedBattle_BufferNewBattlerData(u8 *dst); u8 RecordedBattle_BufferNewBattlerData(u8 *dst);
void RecordedBattle_RecordAllBattlerData(u8 *data); void RecordedBattle_RecordAllBattlerData(u8 *data);
bool32 CanCopyRecordedBattleSaveData(void); bool32 CanCopyRecordedBattleSaveData(void);
bool32 MoveRecordedBattleToSaveData(void); bool32 MoveRecordedBattleToSaveData(void);
void SetVariablesForRecordedBattle(struct RecordedBattleSave *);
void PlayRecordedBattle(void (*CB2_After)(void)); void PlayRecordedBattle(void (*CB2_After)(void));
u8 GetRecordedBattleFrontierFacility(void); u8 GetRecordedBattleFrontierFacility(void);
u8 GetRecordedBattleFronterBrainSymbol(void); u8 GetRecordedBattleFronterBrainSymbol(void);

17
include/test_runner.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef GUARD_TEST_RUNNER_H
#define GUARD_TEST_RUNNER_H
extern const bool8 gTestRunnerEnabled;
extern const bool8 gTestRunnerHeadless;
extern const bool8 gTestRunnerSkipIsFail;
void TestRunner_Battle_RecordAbilityPopUp(u32 battlerId, u32 ability);
void TestRunner_Battle_RecordAnimation(u32 animType, u32 animId);
void TestRunner_Battle_RecordHP(u32 battlerId, u32 oldHP, u32 newHP);
void TestRunner_Battle_RecordMessage(const u8 *message);
void TestRunner_Battle_RecordStatus1(u32 battlerId, u32 status1);
void TestRunner_Battle_AfterLastTurn(void);
void BattleTest_CheckBattleRecordActionType(u32 battlerId, u32 recordIndex, u32 actionType);
#endif

View File

@ -2,6 +2,7 @@ ENTRY(Start)
gNumMusicPlayers = 4; gNumMusicPlayers = 4;
gMaxLines = 0; gMaxLines = 0;
gInitialMainCB2 = CB2_InitCopyrightScreenAfterBootup;
SECTIONS { SECTIONS {
. = 0x2000000; . = 0x2000000;

View File

@ -2,6 +2,7 @@ ENTRY(Start)
gNumMusicPlayers = 4; gNumMusicPlayers = 4;
gMaxLines = 0; gMaxLines = 0;
gInitialMainCB2 = CB2_InitCopyrightScreenAfterBootup;
SECTIONS { SECTIONS {
. = 0x2000000; . = 0x2000000;

140
ld_script_test.txt Normal file
View File

@ -0,0 +1,140 @@
ENTRY(Start)
gNumMusicPlayers = 4;
gMaxLines = 0;
gInitialMainCB2 = CB2_TestRunner;
SECTIONS {
. = 0x2000000;
ewram (NOLOAD) :
ALIGN(4)
{
gHeap = .;
. = 0x1C000;
src/*.o(ewram_data);
gflib/*.o(ewram_data);
test/*.o(ewram_data);
. = 0x40000;
}
. = 0x3000000;
iwram (NOLOAD) :
ALIGN(4)
{
/* .bss starts at 0x3000000 */
src/*.o(.bss);
gflib/*.o(.bss);
data/*.o(.bss);
test/*.o(.bss);
*libc.a:*.o(.bss*);
*libgcc.a:*.o(.bss*);
*libnosys.a:*.o(.bss*);
/* .bss.code starts at 0x3001AA8 */
src/m4a.o(.bss.code);
/* COMMON starts at 0x30022A8 */
src/*.o(COMMON);
gflib/*.o(COMMON);
data/*.o(COMMON);
test/*.o(COMMON);
*libc.a:sbrkr.o(COMMON);
end = .;
. = 0x8000;
}
. = 0x8000000;
.text :
ALIGN(4)
{
src/rom_header.o(.text);
src/rom_header_gf.o(.text.*);
src/*.o(.text);
gflib/*.o(.text);
} =0
script_data :
ALIGN(4)
{
data/*.o(script_data);
} =0
lib_text :
ALIGN(4)
{
*libagbsyscall.a:*.o(.text*);
*libgcc.a:*.o(.text*);
*libc.a:*.o(.text*);
*libnosys.a:*.o(.text*);
} =0
.rodata :
ALIGN(4)
{
src/*.o(.rodata);
gflib/*.o(.rodata);
data/*.o(.rodata);
} =0
song_data :
ALIGN(4)
{
sound/songs/*.o(.rodata);
} =0
lib_rodata :
SUBALIGN(4)
{
*libgcc.a:*.o(.rodata*);
*libc.a:*.o(.rodata*);
*libc.a:*.o(.data*);
src/libisagbprn.o(.rodata);
} =0
tests :
ALIGN(4)
{
__start_tests = .;
test/*.o(.tests);
__stop_tests = .;
test/*.o(.text);
test/*.o(.rodata);
} =0
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* Discard everything not specifically mentioned above. */
/DISCARD/ :
{
*(*);
}
}

View File

@ -1,7 +1,7 @@
MAKEFLAGS += --no-print-directory MAKEFLAGS += --no-print-directory
TOOLDIRS := $(filter-out tools/agbcc tools/binutils,$(wildcard tools/*)) TOOLDIRS := $(filter-out tools/mgba tools/agbcc tools/binutils,$(wildcard tools/*))
.PHONY: all $(TOOLDIRS) .PHONY: all $(TOOLDIRS)

View File

@ -16,6 +16,7 @@
#include "sound.h" #include "sound.h"
#include "sprite.h" #include "sprite.h"
#include "task.h" #include "task.h"
#include "test_runner.h"
#include "constants/battle_anim.h" #include "constants/battle_anim.h"
#include "constants/moves.h" #include "constants/moves.h"
@ -27,7 +28,10 @@
#define ANIM_SPRITE_INDEX_COUNT 8 #define ANIM_SPRITE_INDEX_COUNT 8
extern const u16 gMovesWithQuietBGM[]; extern const u16 gMovesWithQuietBGM[];
extern const u8 *const gBattleAnims_General[];
extern const u8 *const gBattleAnims_Moves[]; extern const u8 *const gBattleAnims_Moves[];
extern const u8 *const gBattleAnims_Special[];
extern const u8 *const gBattleAnims_StatusConditions[];
static void Cmd_loadspritegfx(void); static void Cmd_loadspritegfx(void);
static void Cmd_unloadspritegfx(void); static void Cmd_unloadspritegfx(void);
@ -211,17 +215,50 @@ void DoMoveAnim(u16 move)
gBattleAnimTarget = 0; gBattleAnimTarget = 0;
} }
} }
LaunchBattleAnimation(gBattleAnims_Moves, move, TRUE); LaunchBattleAnimation(ANIM_TYPE_MOVE, move);
} }
void LaunchBattleAnimation(const u8 *const animsTable[], u16 tableId, bool8 isMoveAnim) static void Nop(void)
{
}
void LaunchBattleAnimation(u32 animType, u32 animId)
{ {
s32 i; s32 i;
bool32 hideHpBoxes = (tableId == MOVE_TRANSFORM) ? FALSE : TRUE; const u8 *const *animsTable;
bool32 hideHpBoxes;
if (!isMoveAnim) if (gTestRunnerEnabled)
{ {
switch (tableId) TestRunner_Battle_RecordAnimation(animType, animId);
if (gTestRunnerHeadless)
{
gAnimScriptCallback = Nop;
gAnimScriptActive = FALSE;
return;
}
}
switch (animType)
{
case ANIM_TYPE_GENERAL:
animsTable = gBattleAnims_General;
break;
case ANIM_TYPE_MOVE:
animsTable = gBattleAnims_Moves;
break;
case ANIM_TYPE_STATUS:
animsTable = gBattleAnims_StatusConditions;
break;
case ANIM_TYPE_SPECIAL:
animsTable = gBattleAnims_Special;
break;
}
hideHpBoxes = !(animType == ANIM_TYPE_MOVE && animId == MOVE_TRANSFORM);
if (animType != ANIM_TYPE_MOVE)
{
switch (animId)
{ {
case B_ANIM_TURN_TRAP: case B_ANIM_TURN_TRAP:
case B_ANIM_LEECH_SEED_DRAIN: case B_ANIM_LEECH_SEED_DRAIN:
@ -258,17 +295,17 @@ void LaunchBattleAnimation(const u8 *const animsTable[], u16 tableId, bool8 isMo
gAnimBattlerSpecies[i] = gContestResources->moveAnim->species; gAnimBattlerSpecies[i] = gContestResources->moveAnim->species;
} }
if (!isMoveAnim) if (animType != ANIM_TYPE_MOVE)
gAnimMoveIndex = 0; gAnimMoveIndex = 0;
else else
gAnimMoveIndex = tableId; gAnimMoveIndex = animId;
for (i = 0; i < ANIM_ARGS_COUNT; i++) for (i = 0; i < ANIM_ARGS_COUNT; i++)
gBattleAnimArgs[i] = 0; gBattleAnimArgs[i] = 0;
sMonAnimTaskIdArray[0] = TASK_NONE; sMonAnimTaskIdArray[0] = TASK_NONE;
sMonAnimTaskIdArray[1] = TASK_NONE; sMonAnimTaskIdArray[1] = TASK_NONE;
sBattleAnimScriptPtr = animsTable[tableId]; sBattleAnimScriptPtr = animsTable[animId];
gAnimScriptActive = TRUE; gAnimScriptActive = TRUE;
sAnimFramesToWait = 0; sAnimFramesToWait = 0;
gAnimScriptCallback = RunAnimScriptCommand; gAnimScriptCallback = RunAnimScriptCommand;
@ -276,11 +313,11 @@ void LaunchBattleAnimation(const u8 *const animsTable[], u16 tableId, bool8 isMo
for (i = 0; i < ANIM_SPRITE_INDEX_COUNT; i++) for (i = 0; i < ANIM_SPRITE_INDEX_COUNT; i++)
sAnimSpriteIndexArray[i] = 0xFFFF; sAnimSpriteIndexArray[i] = 0xFFFF;
if (isMoveAnim) if (animType == ANIM_TYPE_MOVE)
{ {
for (i = 0; gMovesWithQuietBGM[i] != 0xFFFF; i++) for (i = 0; gMovesWithQuietBGM[i] != 0xFFFF; i++)
{ {
if (tableId == gMovesWithQuietBGM[i]) if (animId == gMovesWithQuietBGM[i])
{ {
m4aMPlayVolumeControl(&gMPlayInfo_BGM, TRACKS_ALL, 128); m4aMPlayVolumeControl(&gMPlayInfo_BGM, TRACKS_ALL, 128);
break; break;

View File

@ -2364,7 +2364,7 @@ void AnimTask_TransformMon(u8 taskId)
} }
break; break;
case 2: case 2:
HandleSpeciesGfxDataChange(gBattleAnimAttacker, gBattleAnimTarget, gTasks[taskId].data[10], gBattleAnimArgs[1]); HandleSpeciesGfxDataChange(gBattleAnimAttacker, gBattleAnimTarget, gTasks[taskId].data[10], gBattleAnimArgs[1], TRUE);
GetBgDataForTransform(&animBg, gBattleAnimAttacker); GetBgDataForTransform(&animBg, gBattleAnimAttacker);
if (IsContest()) if (IsContest())
@ -2451,7 +2451,7 @@ void AnimTask_IsMonInvisible(u8 taskId)
void AnimTask_CastformGfxDataChange(u8 taskId) void AnimTask_CastformGfxDataChange(u8 taskId)
{ {
HandleSpeciesGfxDataChange(gBattleAnimAttacker, gBattleAnimTarget, TRUE, FALSE); HandleSpeciesGfxDataChange(gBattleAnimAttacker, gBattleAnimTarget, TRUE, FALSE, FALSE);
DestroyAnimVisualTask(taskId); DestroyAnimVisualTask(taskId);
} }

View File

@ -13,7 +13,6 @@
extern const struct CompressedSpriteSheet gBattleAnimPicTable[]; extern const struct CompressedSpriteSheet gBattleAnimPicTable[];
extern const struct CompressedSpritePalette gBattleAnimPaletteTable[]; extern const struct CompressedSpritePalette gBattleAnimPaletteTable[];
extern const u8 *const gBattleAnims_StatusConditions[];
extern const struct OamData gOamData_AffineOff_ObjNormal_8x8; extern const struct OamData gOamData_AffineOff_ObjNormal_8x8;
extern const struct OamData gOamData_AffineOff_ObjBlend_64x64; extern const struct OamData gOamData_AffineOff_ObjBlend_64x64;
@ -568,7 +567,7 @@ void LaunchStatusAnimation(u8 battlerId, u8 statusAnimId)
gBattleAnimAttacker = battlerId; gBattleAnimAttacker = battlerId;
gBattleAnimTarget = battlerId; gBattleAnimTarget = battlerId;
LaunchBattleAnimation(gBattleAnims_StatusConditions, statusAnimId, FALSE); LaunchBattleAnimation(ANIM_TYPE_STATUS, statusAnimId);
taskId = CreateTask(Task_DoStatusAnimation, 10); taskId = CreateTask(Task_DoStatusAnimation, 10);
gTasks[taskId].data[0] = battlerId; gTasks[taskId].data[0] = battlerId;
} }

View File

@ -22,6 +22,7 @@
#include "sound.h" #include "sound.h"
#include "string_util.h" #include "string_util.h"
#include "task.h" #include "task.h"
#include "test_runner.h"
#include "text.h" #include "text.h"
#include "util.h" #include "util.h"
#include "window.h" #include "window.h"
@ -1386,6 +1387,17 @@ static void RecordedOpponentHandlePrintString(void)
gBattle_BG0_Y = 0; gBattle_BG0_Y = 0;
stringId = (u16 *)(&gBattleResources->bufferA[gActiveBattler][2]); stringId = (u16 *)(&gBattleResources->bufferA[gActiveBattler][2]);
BufferStringBattle(*stringId); BufferStringBattle(*stringId);
if (gTestRunnerEnabled)
{
TestRunner_Battle_RecordMessage(gDisplayedStringBattle);
if (gTestRunnerHeadless)
{
RecordedOpponentBufferExecCompleted();
return;
}
}
BattlePutTextOnWindow(gDisplayedStringBattle, B_WIN_MSG); BattlePutTextOnWindow(gDisplayedStringBattle, B_WIN_MSG);
gBattlerControllerFuncs[gActiveBattler] = CompleteOnInactiveTextPrinter; gBattlerControllerFuncs[gActiveBattler] = CompleteOnInactiveTextPrinter;
} }
@ -1397,7 +1409,7 @@ static void RecordedOpponentHandlePrintSelectionString(void)
static void RecordedOpponentHandleChooseAction(void) static void RecordedOpponentHandleChooseAction(void)
{ {
BtlController_EmitTwoReturnValues(BUFFER_B, RecordedBattle_GetBattlerAction(gActiveBattler), 0); BtlController_EmitTwoReturnValues(BUFFER_B, RecordedBattle_GetBattlerAction(RECORDED_ACTION_TYPE, gActiveBattler), 0);
RecordedOpponentBufferExecCompleted(); RecordedOpponentBufferExecCompleted();
} }
@ -1414,8 +1426,8 @@ static void RecordedOpponentHandleChooseMove(void)
} }
else else
{ {
u8 moveId = RecordedBattle_GetBattlerAction(gActiveBattler); u8 moveId = RecordedBattle_GetBattlerAction(RECORDED_MOVE_SLOT, gActiveBattler);
u8 target = RecordedBattle_GetBattlerAction(gActiveBattler); u8 target = RecordedBattle_GetBattlerAction(RECORDED_MOVE_TARGET, gActiveBattler);
BtlController_EmitTwoReturnValues(BUFFER_B, 10, moveId | (target << 8)); BtlController_EmitTwoReturnValues(BUFFER_B, 10, moveId | (target << 8));
} }
@ -1429,7 +1441,7 @@ static void RecordedOpponentHandleChooseItem(void)
static void RecordedOpponentHandleChoosePokemon(void) static void RecordedOpponentHandleChoosePokemon(void)
{ {
*(gBattleStruct->monToSwitchIntoId + gActiveBattler) = RecordedBattle_GetBattlerAction(gActiveBattler); *(gBattleStruct->monToSwitchIntoId + gActiveBattler) = RecordedBattle_GetBattlerAction(RECORDED_PARTY_INDEX, gActiveBattler);
BtlController_EmitChosenMonReturnValue(BUFFER_B, *(gBattleStruct->monToSwitchIntoId + gActiveBattler), NULL); BtlController_EmitChosenMonReturnValue(BUFFER_B, *(gBattleStruct->monToSwitchIntoId + gActiveBattler), NULL);
RecordedOpponentBufferExecCompleted(); RecordedOpponentBufferExecCompleted();
} }
@ -1442,22 +1454,23 @@ static void RecordedOpponentHandleCmd23(void)
static void RecordedOpponentHandleHealthBarUpdate(void) static void RecordedOpponentHandleHealthBarUpdate(void)
{ {
s16 hpVal; s16 hpVal;
s32 maxHP, curHP;
LoadBattleBarGfx(0); LoadBattleBarGfx(0);
hpVal = gBattleResources->bufferA[gActiveBattler][2] | (gBattleResources->bufferA[gActiveBattler][3] << 8); hpVal = gBattleResources->bufferA[gActiveBattler][2] | (gBattleResources->bufferA[gActiveBattler][3] << 8);
maxHP = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gActiveBattler]], MON_DATA_MAX_HP);
curHP = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gActiveBattler]], MON_DATA_HP);
if (hpVal != INSTANT_HP_BAR_DROP) if (hpVal != INSTANT_HP_BAR_DROP)
{ {
u32 maxHP = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gActiveBattler]], MON_DATA_MAX_HP);
u32 curHP = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gActiveBattler]], MON_DATA_HP);
SetBattleBarStruct(gActiveBattler, gHealthboxSpriteIds[gActiveBattler], maxHP, curHP, hpVal); SetBattleBarStruct(gActiveBattler, gHealthboxSpriteIds[gActiveBattler], maxHP, curHP, hpVal);
TestRunner_Battle_RecordHP(gActiveBattler, curHP, min(maxHP, max(0, curHP - hpVal)));
} }
else else
{ {
u32 maxHP = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gActiveBattler]], MON_DATA_MAX_HP);
SetBattleBarStruct(gActiveBattler, gHealthboxSpriteIds[gActiveBattler], maxHP, 0, hpVal); SetBattleBarStruct(gActiveBattler, gHealthboxSpriteIds[gActiveBattler], maxHP, 0, hpVal);
TestRunner_Battle_RecordHP(gActiveBattler, curHP, 0);
} }
gBattlerControllerFuncs[gActiveBattler] = CompleteOnHealthbarDone; gBattlerControllerFuncs[gActiveBattler] = CompleteOnHealthbarDone;
@ -1478,6 +1491,9 @@ static void RecordedOpponentHandleStatusIconUpdate(void)
battlerId = gActiveBattler; battlerId = gActiveBattler;
gBattleSpritesDataPtr->healthBoxesData[battlerId].statusAnimActive = 0; gBattleSpritesDataPtr->healthBoxesData[battlerId].statusAnimActive = 0;
gBattlerControllerFuncs[gActiveBattler] = CompleteOnFinishedStatusAnimation; gBattlerControllerFuncs[gActiveBattler] = CompleteOnFinishedStatusAnimation;
if (gTestRunnerEnabled)
TestRunner_Battle_RecordStatus1(battlerId, GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerId]], MON_DATA_STATUS));
} }
} }

View File

@ -19,6 +19,7 @@
#include "sound.h" #include "sound.h"
#include "string_util.h" #include "string_util.h"
#include "task.h" #include "task.h"
#include "test_runner.h"
#include "text.h" #include "text.h"
#include "util.h" #include "util.h"
#include "window.h" #include "window.h"
@ -1394,6 +1395,17 @@ static void RecordedPlayerHandlePrintString(void)
gBattle_BG0_Y = 0; gBattle_BG0_Y = 0;
stringId = (u16 *)(&gBattleResources->bufferA[gActiveBattler][2]); stringId = (u16 *)(&gBattleResources->bufferA[gActiveBattler][2]);
BufferStringBattle(*stringId); BufferStringBattle(*stringId);
if (gTestRunnerEnabled)
{
TestRunner_Battle_RecordMessage(gDisplayedStringBattle);
if (gTestRunnerHeadless)
{
RecordedPlayerBufferExecCompleted();
return;
}
}
BattlePutTextOnWindow(gDisplayedStringBattle, B_WIN_MSG); BattlePutTextOnWindow(gDisplayedStringBattle, B_WIN_MSG);
gBattlerControllerFuncs[gActiveBattler] = CompleteOnInactiveTextPrinter; gBattlerControllerFuncs[gActiveBattler] = CompleteOnInactiveTextPrinter;
} }
@ -1407,7 +1419,7 @@ static void ChooseActionInBattlePalace(void)
{ {
if (gBattleCommunication[4] >= gBattlersCount / 2) if (gBattleCommunication[4] >= gBattlersCount / 2)
{ {
BtlController_EmitTwoReturnValues(BUFFER_B, RecordedBattle_GetBattlerAction(gActiveBattler), 0); BtlController_EmitTwoReturnValues(BUFFER_B, RecordedBattle_GetBattlerAction(RECORDED_BATTLE_PALACE_ACTION, gActiveBattler), 0);
RecordedPlayerBufferExecCompleted(); RecordedPlayerBufferExecCompleted();
} }
} }
@ -1420,7 +1432,7 @@ static void RecordedPlayerHandleChooseAction(void)
} }
else else
{ {
BtlController_EmitTwoReturnValues(BUFFER_B, RecordedBattle_GetBattlerAction(gActiveBattler), 0); BtlController_EmitTwoReturnValues(BUFFER_B, RecordedBattle_GetBattlerAction(RECORDED_ACTION_TYPE, gActiveBattler), 0);
RecordedPlayerBufferExecCompleted(); RecordedPlayerBufferExecCompleted();
} }
} }
@ -1438,8 +1450,8 @@ static void RecordedPlayerHandleChooseMove(void)
} }
else else
{ {
u8 moveId = RecordedBattle_GetBattlerAction(gActiveBattler); u8 moveId = RecordedBattle_GetBattlerAction(RECORDED_MOVE_SLOT, gActiveBattler);
u8 target = RecordedBattle_GetBattlerAction(gActiveBattler); u8 target = RecordedBattle_GetBattlerAction(RECORDED_MOVE_TARGET, gActiveBattler);
BtlController_EmitTwoReturnValues(BUFFER_B, 10, moveId | (target << 8)); BtlController_EmitTwoReturnValues(BUFFER_B, 10, moveId | (target << 8));
} }
@ -1453,7 +1465,7 @@ static void RecordedPlayerHandleChooseItem(void)
static void RecordedPlayerHandleChoosePokemon(void) static void RecordedPlayerHandleChoosePokemon(void)
{ {
*(gBattleStruct->monToSwitchIntoId + gActiveBattler) = RecordedBattle_GetBattlerAction(gActiveBattler); *(gBattleStruct->monToSwitchIntoId + gActiveBattler) = RecordedBattle_GetBattlerAction(RECORDED_PARTY_INDEX, gActiveBattler);
BtlController_EmitChosenMonReturnValue(BUFFER_B, *(gBattleStruct->monToSwitchIntoId + gActiveBattler), NULL); BtlController_EmitChosenMonReturnValue(BUFFER_B, *(gBattleStruct->monToSwitchIntoId + gActiveBattler), NULL);
RecordedPlayerBufferExecCompleted(); RecordedPlayerBufferExecCompleted();
} }
@ -1466,23 +1478,24 @@ static void RecordedPlayerHandleCmd23(void)
static void RecordedPlayerHandleHealthBarUpdate(void) static void RecordedPlayerHandleHealthBarUpdate(void)
{ {
s16 hpVal; s16 hpVal;
s32 maxHP, curHP;
LoadBattleBarGfx(0); LoadBattleBarGfx(0);
hpVal = gBattleResources->bufferA[gActiveBattler][2] | (gBattleResources->bufferA[gActiveBattler][3] << 8); hpVal = gBattleResources->bufferA[gActiveBattler][2] | (gBattleResources->bufferA[gActiveBattler][3] << 8);
maxHP = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gActiveBattler]], MON_DATA_MAX_HP);
curHP = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gActiveBattler]], MON_DATA_HP);
if (hpVal != INSTANT_HP_BAR_DROP) if (hpVal != INSTANT_HP_BAR_DROP)
{ {
u32 maxHP = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gActiveBattler]], MON_DATA_MAX_HP);
u32 curHP = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gActiveBattler]], MON_DATA_HP);
SetBattleBarStruct(gActiveBattler, gHealthboxSpriteIds[gActiveBattler], maxHP, curHP, hpVal); SetBattleBarStruct(gActiveBattler, gHealthboxSpriteIds[gActiveBattler], maxHP, curHP, hpVal);
TestRunner_Battle_RecordHP(gActiveBattler, curHP, min(maxHP, max(0, curHP - hpVal)));
} }
else else
{ {
u32 maxHP = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gActiveBattler]], MON_DATA_MAX_HP);
SetBattleBarStruct(gActiveBattler, gHealthboxSpriteIds[gActiveBattler], maxHP, 0, hpVal); SetBattleBarStruct(gActiveBattler, gHealthboxSpriteIds[gActiveBattler], maxHP, 0, hpVal);
UpdateHpTextInHealthbox(gHealthboxSpriteIds[gActiveBattler], HP_CURRENT, 0, maxHP); UpdateHpTextInHealthbox(gHealthboxSpriteIds[gActiveBattler], HP_CURRENT, 0, maxHP);
TestRunner_Battle_RecordHP(gActiveBattler, curHP, 0);
} }
gBattlerControllerFuncs[gActiveBattler] = CompleteOnHealthbarDone; gBattlerControllerFuncs[gActiveBattler] = CompleteOnHealthbarDone;
@ -1503,6 +1516,9 @@ static void RecordedPlayerHandleStatusIconUpdate(void)
battlerId = gActiveBattler; battlerId = gActiveBattler;
gBattleSpritesDataPtr->healthBoxesData[battlerId].statusAnimActive = 0; gBattleSpritesDataPtr->healthBoxesData[battlerId].statusAnimActive = 0;
gBattlerControllerFuncs[gActiveBattler] = CompleteOnFinishedStatusAnimation; gBattlerControllerFuncs[gActiveBattler] = CompleteOnFinishedStatusAnimation;
if (gTestRunnerEnabled)
TestRunner_Battle_RecordStatus1(battlerId, GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_STATUS));
} }
} }

View File

@ -1107,7 +1107,7 @@ static void Task_DebugMenuProcessInput(u8 taskId)
struct BattleDebugMenu *data = GetStructPtr(taskId); struct BattleDebugMenu *data = GetStructPtr(taskId);
// Exit the menu. // Exit the menu.
if (JOY_NEW(SELECT_BUTTON)) if (JOY_NEW(SELECT_BUTTON) || ((JOY_NEW(B_BUTTON)) && data->activeWindow == ACTIVE_WIN_MAIN))
{ {
BeginNormalPaletteFade(-1, 0, 0, 0x10, 0); BeginNormalPaletteFade(-1, 0, 0, 0x10, 0);
gTasks[taskId].func = Task_DebugMenuFadeOut; gTasks[taskId].func = Task_DebugMenuFadeOut;

View File

@ -4237,7 +4237,7 @@ static u8 Task_GetInfoCardInput(u8 taskId)
#undef tUsingAlternateSlot #undef tUsingAlternateSlot
// allocatedArray below needs to be large enough to hold stat totals for each mon, or totals of each type of move points // allocatedArray below needs to be large enough to hold stat totals for each mon, or totals of each type of move points
#define ALLOC_ARRAY_SIZE (NUM_STATS * FRONTIER_PARTY_SIZE >= NUM_MOVE_POINT_TYPES ? (NUM_STATS * FRONTIER_PARTY_SIZE) : NUM_MOVE_POINT_TYPES) #define ALLOC_ARRAY_SIZE max(NUM_STATS * FRONTIER_PARTY_SIZE, NUM_MOVE_POINT_TYPES)
static void DisplayTrainerInfoOnCard(u8 flags, u8 trainerTourneyId) static void DisplayTrainerInfoOnCard(u8 flags, u8 trainerTourneyId)
{ {

View File

@ -27,8 +27,6 @@
#include "constants/battle_palace.h" #include "constants/battle_palace.h"
extern const u8 gBattlePalaceNatureToMoveTarget[]; extern const u8 gBattlePalaceNatureToMoveTarget[];
extern const u8 *const gBattleAnims_General[];
extern const u8 *const gBattleAnims_Special[];
extern const struct CompressedSpriteSheet gSpriteSheet_EnemyShadow; extern const struct CompressedSpriteSheet gSpriteSheet_EnemyShadow;
extern const struct SpriteTemplate gSpriteTemplate_EnemyShadow; extern const struct SpriteTemplate gSpriteTemplate_EnemyShadow;
@ -465,7 +463,7 @@ bool8 TryHandleLaunchBattleTableAnimation(u8 activeBattler, u8 atkBattler, u8 de
gBattleAnimAttacker = atkBattler; gBattleAnimAttacker = atkBattler;
gBattleAnimTarget = defBattler; gBattleAnimTarget = defBattler;
gBattleSpritesDataPtr->animationData->animArg = argument; gBattleSpritesDataPtr->animationData->animArg = argument;
LaunchBattleAnimation(gBattleAnims_General, tableId, FALSE); LaunchBattleAnimation(ANIM_TYPE_GENERAL, tableId);
taskId = CreateTask(Task_ClearBitWhenBattleTableAnimDone, 10); taskId = CreateTask(Task_ClearBitWhenBattleTableAnimDone, 10);
gTasks[taskId].tBattlerId = activeBattler; gTasks[taskId].tBattlerId = activeBattler;
gBattleSpritesDataPtr->healthBoxesData[gTasks[taskId].tBattlerId].animFromTableActive = 1; gBattleSpritesDataPtr->healthBoxesData[gTasks[taskId].tBattlerId].animFromTableActive = 1;
@ -509,7 +507,7 @@ void InitAndLaunchSpecialAnimation(u8 activeBattler, u8 atkBattler, u8 defBattle
gBattleAnimAttacker = atkBattler; gBattleAnimAttacker = atkBattler;
gBattleAnimTarget = defBattler; gBattleAnimTarget = defBattler;
LaunchBattleAnimation(gBattleAnims_Special, tableId, FALSE); LaunchBattleAnimation(ANIM_TYPE_SPECIAL, tableId);
taskId = CreateTask(Task_ClearBitWhenSpecialAnimDone, 10); taskId = CreateTask(Task_ClearBitWhenSpecialAnimDone, 10);
gTasks[taskId].tBattlerId = activeBattler; gTasks[taskId].tBattlerId = activeBattler;
gBattleSpritesDataPtr->healthBoxesData[gTasks[taskId].tBattlerId].specialAnimActive = 1; gBattleSpritesDataPtr->healthBoxesData[gTasks[taskId].tBattlerId].specialAnimActive = 1;
@ -849,7 +847,7 @@ void CopyBattleSpriteInvisibility(u8 battlerId)
gBattleSpritesDataPtr->battlerData[battlerId].invisible = gSprites[gBattlerSpriteIds[battlerId]].invisible; gBattleSpritesDataPtr->battlerData[battlerId].invisible = gSprites[gBattlerSpriteIds[battlerId]].invisible;
} }
void HandleSpeciesGfxDataChange(u8 battlerAtk, u8 battlerDef, bool8 castform, bool32 megaEvo) void HandleSpeciesGfxDataChange(u8 battlerAtk, u8 battlerDef, bool8 castform, bool32 megaEvo, bool8 trackEnemyPersonality)
{ {
u32 personalityValue, otId, position, paletteOffset, targetSpecies; u32 personalityValue, otId, position, paletteOffset, targetSpecies;
const void *lzPaletteData, *src; const void *lzPaletteData, *src;
@ -878,6 +876,9 @@ void HandleSpeciesGfxDataChange(u8 battlerAtk, u8 battlerDef, bool8 castform, bo
if (GetBattlerSide(battlerAtk) == B_SIDE_PLAYER) if (GetBattlerSide(battlerAtk) == B_SIDE_PLAYER)
{ {
if (trackEnemyPersonality)
personalityValue = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerAtk]], MON_DATA_PERSONALITY);
else
personalityValue = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerAtk]], MON_DATA_PERSONALITY); personalityValue = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerAtk]], MON_DATA_PERSONALITY);
otId = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerAtk]], MON_DATA_OT_ID); otId = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerAtk]], MON_DATA_OT_ID);
@ -888,6 +889,9 @@ void HandleSpeciesGfxDataChange(u8 battlerAtk, u8 battlerDef, bool8 castform, bo
} }
else else
{ {
if (trackEnemyPersonality)
personalityValue = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerAtk]], MON_DATA_PERSONALITY);
else
personalityValue = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerAtk]], MON_DATA_PERSONALITY); personalityValue = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerAtk]], MON_DATA_PERSONALITY);
otId = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerAtk]], MON_DATA_OT_ID); otId = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerAtk]], MON_DATA_OT_ID);

View File

@ -28,6 +28,7 @@
#include "item.h" #include "item.h"
#include "item_icon.h" #include "item_icon.h"
#include "item_use.h" #include "item_use.h"
#include "test_runner.h"
#include "constants/battle_anim.h" #include "constants/battle_anim.h"
#include "constants/rgb.h" #include "constants/rgb.h"
#include "constants/songs.h" #include "constants/songs.h"
@ -3069,6 +3070,13 @@ void CreateAbilityPopUp(u8 battlerId, u32 ability, bool32 isDoubleBattle)
const s16 (*coords)[2]; const s16 (*coords)[2];
u8 spriteId1, spriteId2, battlerPosition, taskId; u8 spriteId1, spriteId2, battlerPosition, taskId;
if (gTestRunnerEnabled)
{
TestRunner_Battle_RecordAbilityPopUp(battlerId, ability);
if (gTestRunnerHeadless)
return;
}
if (gBattleScripting.abilityPopupOverwrite != 0) if (gBattleScripting.abilityPopupOverwrite != 0)
ability = gBattleScripting.abilityPopupOverwrite; ability = gBattleScripting.abilityPopupOverwrite;
@ -3184,9 +3192,12 @@ static void SpriteCb_AbilityPopUp(struct Sprite *sprite)
void DestroyAbilityPopUp(u8 battlerId) void DestroyAbilityPopUp(u8 battlerId)
{ {
if (gBattleStruct->activeAbilityPopUps & gBitTable[battlerId])
{
gSprites[gBattleStruct->abilityPopUpSpriteIds[battlerId][0]].tFrames = 0; gSprites[gBattleStruct->abilityPopUpSpriteIds[battlerId][0]].tFrames = 0;
gSprites[gBattleStruct->abilityPopUpSpriteIds[battlerId][1]].tFrames = 0; gSprites[gBattleStruct->abilityPopUpSpriteIds[battlerId][1]].tFrames = 0;
gBattleScripting.fixedPopup = FALSE; gBattleScripting.fixedPopup = FALSE;
}
} }
static void Task_FreeAbilityPopUpGfx(u8 taskId) static void Task_FreeAbilityPopUpGfx(u8 taskId)

View File

@ -45,6 +45,7 @@
#include "string_util.h" #include "string_util.h"
#include "strings.h" #include "strings.h"
#include "task.h" #include "task.h"
#include "test_runner.h"
#include "text.h" #include "text.h"
#include "trig.h" #include "trig.h"
#include "tv.h" #include "tv.h"
@ -474,6 +475,7 @@ const u8 *const gStatusConditionStringsTable[][2] =
void CB2_InitBattle(void) void CB2_InitBattle(void)
{ {
if (!gTestRunnerEnabled)
MoveSaveBlocks_ResetHeap(); MoveSaveBlocks_ResetHeap();
AllocateBattleResources(); AllocateBattleResources();
AllocateBattleSpritesData(); AllocateBattleSpritesData();
@ -1793,6 +1795,8 @@ void CB2_QuitRecordedBattle(void)
{ {
m4aMPlayStop(&gMPlayInfo_SE1); m4aMPlayStop(&gMPlayInfo_SE1);
m4aMPlayStop(&gMPlayInfo_SE2); m4aMPlayStop(&gMPlayInfo_SE2);
if (gTestRunnerEnabled)
TestRunner_Battle_AfterLastTurn();
FreeRestoreBattleData(); FreeRestoreBattleData();
FreeAllWindowBuffers(); FreeAllWindowBuffers();
SetMainCallback2(gMain.savedCallback); SetMainCallback2(gMain.savedCallback);
@ -4859,9 +4863,14 @@ static void CheckMegaEvolutionBeforeTurn(void)
if (gBattleStruct->mega.toEvolve & gBitTable[gActiveBattler] if (gBattleStruct->mega.toEvolve & gBitTable[gActiveBattler]
&& !(gProtectStructs[gActiveBattler].noValidMoves)) && !(gProtectStructs[gActiveBattler].noValidMoves))
{ {
struct Pokemon *mon;
if (GetBattlerSide(gActiveBattler) == B_SIDE_OPPONENT)
mon = &gEnemyParty[gBattlerPartyIndexes[gActiveBattler]];
else
mon = &gPlayerParty[gBattlerPartyIndexes[gActiveBattler]];
gBattleStruct->mega.toEvolve &= ~(gBitTable[gActiveBattler]); gBattleStruct->mega.toEvolve &= ~(gBitTable[gActiveBattler]);
gLastUsedItem = gBattleMons[gActiveBattler].item; gLastUsedItem = gBattleMons[gActiveBattler].item;
if (gBattleStruct->mega.isWishMegaEvo == TRUE) if (GetBattleFormChangeTargetSpecies(gActiveBattler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE) != SPECIES_NONE)
BattleScriptExecute(BattleScript_WishMegaEvolution); BattleScriptExecute(BattleScript_WishMegaEvolution);
else else
BattleScriptExecute(BattleScript_MegaEvolution); BattleScriptExecute(BattleScript_MegaEvolution);
@ -5204,6 +5213,8 @@ static void HandleEndTurn_FinishBattle(void)
} }
RecordedBattle_SetPlaybackFinished(); RecordedBattle_SetPlaybackFinished();
if (gTestRunnerEnabled)
TestRunner_Battle_AfterLastTurn();
BeginFastPaletteFade(3); BeginFastPaletteFade(3);
FadeOutMapMusic(5); FadeOutMapMusic(5);
#if B_TRAINERS_KNOCK_OFF_ITEMS == TRUE #if B_TRAINERS_KNOCK_OFF_ITEMS == TRUE

View File

@ -209,8 +209,8 @@ static const u8 sText_PkmnFellIntoNightmare[] = _("{B_DEF_NAME_WITH_PREFIX} fell
static const u8 sText_PkmnLockedInNightmare[] = _("{B_ATK_NAME_WITH_PREFIX} is locked\nin a NIGHTMARE!"); static const u8 sText_PkmnLockedInNightmare[] = _("{B_ATK_NAME_WITH_PREFIX} is locked\nin a NIGHTMARE!");
static const u8 sText_PkmnLaidCurse[] = _("{B_ATK_NAME_WITH_PREFIX} cut its own HP and\nlaid a CURSE on {B_DEF_NAME_WITH_PREFIX}!"); static const u8 sText_PkmnLaidCurse[] = _("{B_ATK_NAME_WITH_PREFIX} cut its own HP and\nlaid a CURSE on {B_DEF_NAME_WITH_PREFIX}!");
static const u8 sText_PkmnAfflictedByCurse[] = _("{B_ATK_NAME_WITH_PREFIX} is afflicted\nby the CURSE!"); static const u8 sText_PkmnAfflictedByCurse[] = _("{B_ATK_NAME_WITH_PREFIX} is afflicted\nby the CURSE!");
static const u8 sText_SpikesScattered[] = _("SPIKES were scattered all around\nthe opponent's side!"); static const u8 sText_SpikesScattered[] = _("Spikes were scattered all around\nthe opponent's side!");
static const u8 sText_PkmnHurtBySpikes[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} is hurt\nby SPIKES!"); static const u8 sText_PkmnHurtBySpikes[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} is hurt\nby spikes!");
static const u8 sText_PkmnIdentified[] = _("{B_ATK_NAME_WITH_PREFIX} identified\n{B_DEF_NAME_WITH_PREFIX}!"); static const u8 sText_PkmnIdentified[] = _("{B_ATK_NAME_WITH_PREFIX} identified\n{B_DEF_NAME_WITH_PREFIX}!");
static const u8 sText_PkmnPerishCountFell[] = _("{B_ATK_NAME_WITH_PREFIX}'s PERISH count\nfell to {B_BUFF1}!"); static const u8 sText_PkmnPerishCountFell[] = _("{B_ATK_NAME_WITH_PREFIX}'s PERISH count\nfell to {B_BUFF1}!");
static const u8 sText_PkmnBracedItself[] = _("{B_ATK_NAME_WITH_PREFIX} braced\nitself!"); static const u8 sText_PkmnBracedItself[] = _("{B_ATK_NAME_WITH_PREFIX} braced\nitself!");
@ -220,7 +220,7 @@ static const u8 sText_PkmnCutHPMaxedAttack[] = _("{B_ATK_NAME_WITH_PREFIX} cut i
static const u8 sText_PkmnCopiedStatChanges[] = _("{B_ATK_NAME_WITH_PREFIX} copied\n{B_DEF_NAME_WITH_PREFIX}'s stat changes!"); static const u8 sText_PkmnCopiedStatChanges[] = _("{B_ATK_NAME_WITH_PREFIX} copied\n{B_DEF_NAME_WITH_PREFIX}'s stat changes!");
static const u8 sText_PkmnGotFree[] = _("{B_ATK_NAME_WITH_PREFIX} got free of\n{B_DEF_NAME_WITH_PREFIX}'s {B_BUFF1}!"); static const u8 sText_PkmnGotFree[] = _("{B_ATK_NAME_WITH_PREFIX} got free of\n{B_DEF_NAME_WITH_PREFIX}'s {B_BUFF1}!");
static const u8 sText_PkmnShedLeechSeed[] = _("{B_ATK_NAME_WITH_PREFIX} shed\nLEECH SEED!"); static const u8 sText_PkmnShedLeechSeed[] = _("{B_ATK_NAME_WITH_PREFIX} shed\nLEECH SEED!");
static const u8 sText_PkmnBlewAwaySpikes[] = _("{B_ATK_NAME_WITH_PREFIX} blew away\nSPIKES!"); static const u8 sText_PkmnBlewAwaySpikes[] = _("{B_ATK_NAME_WITH_PREFIX} blew away\nspikes!");
static const u8 sText_PkmnFledFromBattle[] = _("{B_ATK_NAME_WITH_PREFIX} fled from\nbattle!"); static const u8 sText_PkmnFledFromBattle[] = _("{B_ATK_NAME_WITH_PREFIX} fled from\nbattle!");
static const u8 sText_PkmnForesawAttack[] = _("{B_ATK_NAME_WITH_PREFIX} foresaw\nan attack!"); static const u8 sText_PkmnForesawAttack[] = _("{B_ATK_NAME_WITH_PREFIX} foresaw\nan attack!");
static const u8 sText_PkmnTookAttack[] = _("{B_DEF_NAME_WITH_PREFIX} took the\n{B_BUFF1} attack!"); static const u8 sText_PkmnTookAttack[] = _("{B_DEF_NAME_WITH_PREFIX} took the\n{B_BUFF1} attack!");
@ -230,7 +230,7 @@ static const u8 sText_PkmnCenterAttention[] = _("{B_DEF_NAME_WITH_PREFIX} became
static const u8 sText_PkmnChargingPower[] = _("{B_ATK_NAME_WITH_PREFIX} began\ncharging power!"); static const u8 sText_PkmnChargingPower[] = _("{B_ATK_NAME_WITH_PREFIX} began\ncharging power!");
static const u8 sText_NaturePowerTurnedInto[] = _("NATURE POWER turned into\n{B_CURRENT_MOVE}!"); static const u8 sText_NaturePowerTurnedInto[] = _("NATURE POWER turned into\n{B_CURRENT_MOVE}!");
static const u8 sText_PkmnStatusNormal[] = _("{B_ATK_NAME_WITH_PREFIX}'s status\nreturned to normal!"); static const u8 sText_PkmnStatusNormal[] = _("{B_ATK_NAME_WITH_PREFIX}'s status\nreturned to normal!");
static const u8 sText_PkmnSubjectedToTorment[] = _("{B_DEF_NAME_WITH_PREFIX} was subjected\nto TORMENT!"); static const u8 sText_PkmnSubjectedToTorment[] = _("{B_DEF_NAME_WITH_PREFIX} was subjected\nto torment!");
static const u8 sText_PkmnTighteningFocus[] = _("{B_ATK_NAME_WITH_PREFIX} is tightening\nits focus!"); static const u8 sText_PkmnTighteningFocus[] = _("{B_ATK_NAME_WITH_PREFIX} is tightening\nits focus!");
static const u8 sText_PkmnFellForTaunt[] = _("{B_DEF_NAME_WITH_PREFIX} fell for\nthe Taunt!"); static const u8 sText_PkmnFellForTaunt[] = _("{B_DEF_NAME_WITH_PREFIX} fell for\nthe Taunt!");
static const u8 sText_PkmnReadyToHelp[] = _("{B_ATK_NAME_WITH_PREFIX} is ready to\nhelp {B_DEF_NAME_WITH_PREFIX}!"); static const u8 sText_PkmnReadyToHelp[] = _("{B_ATK_NAME_WITH_PREFIX} is ready to\nhelp {B_DEF_NAME_WITH_PREFIX}!");
@ -433,13 +433,19 @@ static const u8 sText_ExclamationMark2[] = _("!");
static const u8 sText_ExclamationMark3[] = _("!"); static const u8 sText_ExclamationMark3[] = _("!");
static const u8 sText_ExclamationMark4[] = _("!"); static const u8 sText_ExclamationMark4[] = _("!");
static const u8 sText_ExclamationMark5[] = _("!"); static const u8 sText_ExclamationMark5[] = _("!");
static const u8 sText_HP[] = _("HP");
static const u8 sText_Attack[] = _("attack");
static const u8 sText_Defense[] = _("defense");
static const u8 sText_Speed[] = _("speed");
static const u8 sText_SpAttack[] = _("sp. attack");
static const u8 sText_SpDefense[] = _("sp. defense");
static const u8 sText_Accuracy[] = _("accuracy"); static const u8 sText_Accuracy[] = _("accuracy");
static const u8 sText_Evasiveness[] = _("evasiveness"); static const u8 sText_Evasiveness[] = _("evasiveness");
const u8 *const gStatNamesTable[NUM_BATTLE_STATS] = const u8 *const gStatNamesTable[NUM_BATTLE_STATS] =
{ {
gText_HP3, gText_Attack, gText_Defense, sText_HP, sText_Attack, sText_Defense,
gText_Speed, gText_SpAtk, gText_SpDef, sText_Speed, sText_SpAttack, sText_SpDefense,
sText_Accuracy, sText_Evasiveness sText_Accuracy, sText_Evasiveness
}; };
@ -515,14 +521,14 @@ static const u8 sText_TwoInGameTrainersDefeated[];
static const u8 sText_Trainer2LoseText[]; static const u8 sText_Trainer2LoseText[];
// New battle strings. // New battle strings.
static const s8 sText_EnduredViaSturdy[] = _("{B_DEF_NAME_WITH_PREFIX} Endured\nthe hit using {B_DEF_ABILITY}!"); static const s8 sText_EnduredViaSturdy[] = _("{B_DEF_NAME_WITH_PREFIX} endured\nthe hit using {B_DEF_ABILITY}!");
static const s8 sText_PowerHerbActivation[] = _("{B_ATK_NAME_WITH_PREFIX} became fully charged\ndue to its {B_LAST_ITEM}!"); static const s8 sText_PowerHerbActivation[] = _("{B_ATK_NAME_WITH_PREFIX} became fully charged\ndue to its {B_LAST_ITEM}!");
static const s8 sText_HurtByItem[] = _("{B_ATK_NAME_WITH_PREFIX} was hurt\nby its {B_LAST_ITEM}!"); static const s8 sText_HurtByItem[] = _("{B_ATK_NAME_WITH_PREFIX} was hurt\nby its {B_LAST_ITEM}!");
static const s8 sText_BadlyPoisonedByItem[] = _("{B_EFF_NAME_WITH_PREFIX} was badly \npoisoned by the {B_LAST_ITEM}!"); static const s8 sText_BadlyPoisonedByItem[] = _("{B_EFF_NAME_WITH_PREFIX} was badly\npoisoned by the {B_LAST_ITEM}!");
static const s8 sText_BurnedByItem[] = _("{B_EFF_NAME_WITH_PREFIX} was burned\nby the {B_LAST_ITEM}!"); static const s8 sText_BurnedByItem[] = _("{B_EFF_NAME_WITH_PREFIX} was burned\nby the {B_LAST_ITEM}!");
static const s8 sText_TargetAbilityActivates[] = _("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} activates!"); static const s8 sText_TargetAbilityActivates[] = _("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} activates!");
static const u8 sText_GravityIntensified[] = _("Gravity intensified!"); static const u8 sText_GravityIntensified[] = _("Gravity intensified!");
static const u8 sText_TargetIdentified[] = _("{B_DEF_NAME_WITH_PREFIX} was \nidentified!"); static const u8 sText_TargetIdentified[] = _("{B_DEF_NAME_WITH_PREFIX} was\nidentified!");
static const u8 sText_TargetWokeUp[] = _("{B_DEF_NAME_WITH_PREFIX} woke up!"); static const u8 sText_TargetWokeUp[] = _("{B_DEF_NAME_WITH_PREFIX} woke up!");
static const u8 sText_PkmnStoleAndAteItem[] = _("{B_ATK_NAME_WITH_PREFIX} stole and\nate {B_DEF_NAME_WITH_PREFIX}'s {B_LAST_ITEM}!"); static const u8 sText_PkmnStoleAndAteItem[] = _("{B_ATK_NAME_WITH_PREFIX} stole and\nate {B_DEF_NAME_WITH_PREFIX}'s {B_LAST_ITEM}!");
static const u8 sText_TailWindBlew[] = _("The tailwind blew from\nbehind {B_ATK_TEAM2} team!"); static const u8 sText_TailWindBlew[] = _("The tailwind blew from\nbehind {B_ATK_TEAM2} team!");
@ -591,7 +597,7 @@ static const u8 sText_TargetsStatWasMaxedOut[] = _("{B_DEF_NAME_WITH_PREFIX}'s {
static const u8 sText_PoisonHealHpUp[] = _("The poisoning healed {B_ATK_NAME_WITH_PREFIX}\na little bit!"); static const u8 sText_PoisonHealHpUp[] = _("The poisoning healed {B_ATK_NAME_WITH_PREFIX}\na little bit!");
static const u8 sText_BadDreamsDmg[] = _("{B_DEF_NAME_WITH_PREFIX} is tormented!"); static const u8 sText_BadDreamsDmg[] = _("{B_DEF_NAME_WITH_PREFIX} is tormented!");
static const u8 sText_MoldBreakerEnters[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} breaks the mold!"); static const u8 sText_MoldBreakerEnters[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} breaks the mold!");
static const u8 sText_TeravoltEnters[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} is radiating \na bursting aura!"); static const u8 sText_TeravoltEnters[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} is radiating\na bursting aura!");
static const u8 sText_TurboblazeEnters[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} is radiating\na blazing aura!"); static const u8 sText_TurboblazeEnters[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} is radiating\na blazing aura!");
static const u8 sText_SlowStartEnters[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} can't get it going!"); static const u8 sText_SlowStartEnters[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} can't get it going!");
static const u8 sText_SlowStartEnd[] = _("{B_ATK_NAME_WITH_PREFIX} finally got\nits act together!"); static const u8 sText_SlowStartEnd[] = _("{B_ATK_NAME_WITH_PREFIX} finally got\nits act together!");
@ -636,7 +642,7 @@ static const u8 sText_TargetElectrified[] = _("The {B_DEF_NAME_WITH_PREFIX}'s mo
static const u8 sText_AssaultVestDoesntAllow[] = _("{B_LAST_ITEM}'s effects prevent\nstatus moves from being used!\p"); static const u8 sText_AssaultVestDoesntAllow[] = _("{B_LAST_ITEM}'s effects prevent\nstatus moves from being used!\p");
static const u8 sText_GravityPreventsUsage[] = _("{B_ATK_NAME_WITH_PREFIX} can't use {B_CURRENT_MOVE}\nbecause of gravity!\p"); static const u8 sText_GravityPreventsUsage[] = _("{B_ATK_NAME_WITH_PREFIX} can't use {B_CURRENT_MOVE}\nbecause of gravity!\p");
static const u8 sText_HealBlockPreventsUsage[] = _("{B_ATK_NAME_WITH_PREFIX} was\nprevented from healing!\p"); static const u8 sText_HealBlockPreventsUsage[] = _("{B_ATK_NAME_WITH_PREFIX} was\nprevented from healing!\p");
static const u8 sText_MegaEvoReacting[] = _("{B_ATK_NAME_WITH_PREFIX}'s {B_LAST_ITEM} is \nreacting to {B_ATK_TRAINER_NAME}'s Mega Ring!"); static const u8 sText_MegaEvoReacting[] = _("{B_ATK_NAME_WITH_PREFIX}'s {B_LAST_ITEM} is\nreacting to {B_ATK_TRAINER_NAME}'s Mega Ring!");
static const u8 sText_FerventWishReached[] = _("{B_ATK_TRAINER_NAME}'s fervent wish\nhas reached {B_ATK_NAME_WITH_PREFIX}!"); static const u8 sText_FerventWishReached[] = _("{B_ATK_TRAINER_NAME}'s fervent wish\nhas reached {B_ATK_NAME_WITH_PREFIX}!");
static const u8 sText_MegaEvoEvolved[] = _("{B_ATK_NAME_WITH_PREFIX} has Mega Evolved into\nMega {B_BUFF1}!"); static const u8 sText_MegaEvoEvolved[] = _("{B_ATK_NAME_WITH_PREFIX} has Mega Evolved into\nMega {B_BUFF1}!");
static const u8 sText_drastically[] = _("drastically "); static const u8 sText_drastically[] = _("drastically ");

View File

@ -834,7 +834,7 @@ static bool8 DoesAbilityPreventStatus(struct Pokemon *mon, u32 status)
ret = TRUE; ret = TRUE;
break; break;
case STATUS1_TOXIC_POISON: case STATUS1_TOXIC_POISON:
if (ability == ABILITY_IMMUNITY) if (ability == ABILITY_IMMUNITY || ability == ABILITY_PASTEL_VEIL)
ret = TRUE; ret = TRUE;
break; break;
} }

View File

@ -2880,11 +2880,11 @@ void SetMoveEffect(bool32 primary, u32 certain)
statusChanged = TRUE; statusChanged = TRUE;
break; break;
case STATUS1_POISON: case STATUS1_POISON:
if (battlerAbility == ABILITY_IMMUNITY if ((battlerAbility == ABILITY_IMMUNITY || battlerAbility == ABILITY_PASTEL_VEIL)
&& (primary == TRUE || certain == MOVE_EFFECT_CERTAIN)) && (primary == TRUE || certain == MOVE_EFFECT_CERTAIN))
{ {
gLastUsedAbility = ABILITY_IMMUNITY; gLastUsedAbility = battlerAbility;
RecordAbilityBattle(gEffectBattler, ABILITY_IMMUNITY); RecordAbilityBattle(gEffectBattler, battlerAbility);
BattleScriptPush(gBattlescriptCurrInstr + 1); BattleScriptPush(gBattlescriptCurrInstr + 1);
gBattlescriptCurrInstr = BattleScript_PSNPrevention; gBattlescriptCurrInstr = BattleScript_PSNPrevention;
@ -3004,10 +3004,11 @@ void SetMoveEffect(bool32 primary, u32 certain)
statusChanged = TRUE; statusChanged = TRUE;
break; break;
case STATUS1_TOXIC_POISON: case STATUS1_TOXIC_POISON:
if (battlerAbility == ABILITY_IMMUNITY && (primary == TRUE || certain == MOVE_EFFECT_CERTAIN)) if ((battlerAbility == ABILITY_IMMUNITY || battlerAbility == ABILITY_PASTEL_VEIL)
&& (primary == TRUE || certain == MOVE_EFFECT_CERTAIN))
{ {
gLastUsedAbility = ABILITY_IMMUNITY; gLastUsedAbility = battlerAbility;
RecordAbilityBattle(gEffectBattler, ABILITY_IMMUNITY); RecordAbilityBattle(gEffectBattler, battlerAbility);
BattleScriptPush(gBattlescriptCurrInstr + 1); BattleScriptPush(gBattlescriptCurrInstr + 1);
gBattlescriptCurrInstr = BattleScript_PSNPrevention; gBattlescriptCurrInstr = BattleScript_PSNPrevention;
@ -3485,7 +3486,9 @@ void SetMoveEffect(bool32 primary, u32 certain)
} }
break; break;
case MOVE_EFFECT_FLAME_BURST: case MOVE_EFFECT_FLAME_BURST:
if (IsBattlerAlive(BATTLE_PARTNER(gBattlerTarget)) && GetBattlerAbility(BATTLE_PARTNER(gBattlerTarget)) != ABILITY_MAGIC_GUARD) if (IsBattlerAlive(BATTLE_PARTNER(gBattlerTarget))
&& !(gStatuses3[BATTLE_PARTNER(gBattlerTarget)] & STATUS3_SEMI_INVULNERABLE)
&& GetBattlerAbility(BATTLE_PARTNER(gBattlerTarget)) != ABILITY_MAGIC_GUARD)
{ {
gBattleScripting.savedBattler = BATTLE_PARTNER(gBattlerTarget); gBattleScripting.savedBattler = BATTLE_PARTNER(gBattlerTarget);
gBattleMoveDamage = gBattleMons[BATTLE_PARTNER(gBattlerTarget)].hp / 16; gBattleMoveDamage = gBattleMons[BATTLE_PARTNER(gBattlerTarget)].hp / 16;
@ -5402,7 +5405,7 @@ static void Cmd_moveend(void)
gStatuses3[gBattlerTarget] |= STATUS3_SMACKED_DOWN; gStatuses3[gBattlerTarget] |= STATUS3_SMACKED_DOWN;
gStatuses3[gBattlerTarget] &= ~(STATUS3_MAGNET_RISE | STATUS3_TELEKINESIS | STATUS3_ON_AIR); gStatuses3[gBattlerTarget] &= ~(STATUS3_MAGNET_RISE | STATUS3_TELEKINESIS | STATUS3_ON_AIR);
effect = TRUE; effect = TRUE;
BattleScriptPush(gBattlescriptCurrInstr + 1); BattleScriptPush(gBattlescriptCurrInstr);
gBattlescriptCurrInstr = BattleScript_MoveEffectSmackDown; gBattlescriptCurrInstr = BattleScript_MoveEffectSmackDown;
} }
break; break;
@ -5415,7 +5418,7 @@ static void Cmd_moveend(void)
BtlController_EmitSetMonData(0, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[gActiveBattler].status1); BtlController_EmitSetMonData(0, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[gActiveBattler].status1);
MarkBattlerForControllerExec(gActiveBattler); MarkBattlerForControllerExec(gActiveBattler);
effect = TRUE; effect = TRUE;
BattleScriptPush(gBattlescriptCurrInstr + 1); BattleScriptPush(gBattlescriptCurrInstr);
switch (gBattleMoves[gCurrentMove].argument) switch (gBattleMoves[gCurrentMove].argument)
{ {
case STATUS1_PARALYSIS: case STATUS1_PARALYSIS:
@ -5912,6 +5915,7 @@ static void Cmd_moveend(void)
if (gBattleResources->flags->flags[i] & RESOURCE_FLAG_EMERGENCY_EXIT) if (gBattleResources->flags->flags[i] & RESOURCE_FLAG_EMERGENCY_EXIT)
{ {
gBattleResources->flags->flags[i] &= ~RESOURCE_FLAG_EMERGENCY_EXIT; gBattleResources->flags->flags[i] &= ~RESOURCE_FLAG_EMERGENCY_EXIT;
gSpecialStatuses[i].emergencyExited = TRUE;
gBattlerTarget = gBattlerAbility = i; gBattlerTarget = gBattlerAbility = i;
BattleScriptPushCursor(); BattleScriptPushCursor();
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER || GetBattlerSide(i) == B_SIDE_PLAYER) if (gBattleTypeFlags & BATTLE_TYPE_TRAINER || GetBattlerSide(i) == B_SIDE_PLAYER)
@ -6754,6 +6758,7 @@ static void Cmd_switchineffects(void)
if (!(gBattleMons[gActiveBattler].status1 & STATUS1_ANY) if (!(gBattleMons[gActiveBattler].status1 & STATUS1_ANY)
&& !IS_BATTLER_OF_TYPE(gActiveBattler, TYPE_STEEL) && !IS_BATTLER_OF_TYPE(gActiveBattler, TYPE_STEEL)
&& GetBattlerAbility(gActiveBattler) != ABILITY_IMMUNITY && GetBattlerAbility(gActiveBattler) != ABILITY_IMMUNITY
&& !IsAbilityOnSide(gActiveBattler, ABILITY_PASTEL_VEIL)
&& !(gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SAFEGUARD) && !(gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SAFEGUARD)
&& !(gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN)) && !(gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN))
{ {
@ -10048,9 +10053,9 @@ static void Cmd_various(void)
return; return;
case VARIOUS_JUMP_IF_LAST_USED_ITEM_BERRY: case VARIOUS_JUMP_IF_LAST_USED_ITEM_BERRY:
if (ItemId_GetPocket(gLastUsedItem) == POCKET_BERRIES) if (ItemId_GetPocket(gLastUsedItem) == POCKET_BERRIES)
gBattlescriptCurrInstr += 7;
else
gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 3); gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 3);
else
gBattlescriptCurrInstr += 7;
return; return;
case VARIOUS_JUMP_IF_LAST_USED_ITEM_HOLD_EFFECT: case VARIOUS_JUMP_IF_LAST_USED_ITEM_HOLD_EFFECT:
if (ItemId_GetHoldEffect(gLastUsedItem) == gBattlescriptCurrInstr[3]) if (ItemId_GetHoldEffect(gLastUsedItem) == gBattlescriptCurrInstr[3])
@ -10133,6 +10138,8 @@ static void Cmd_various(void)
break; break;
} }
PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); PREPARE_STAT_BUFFER(gBattleTextBuff1, statId);
gBattlescriptCurrInstr += 4;
return;
} }
break; break;
case VARIOUS_TEATIME_TARGETS: case VARIOUS_TEATIME_TARGETS:
@ -10214,6 +10221,12 @@ static void Cmd_various(void)
gBattlescriptCurrInstr += 7; gBattlescriptCurrInstr += 7;
} }
return; return;
case VARIOUS_JUMP_IF_EMERGENCY_EXITED:
if (gSpecialStatuses[gActiveBattler].emergencyExited)
gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 3);
else
gBattlescriptCurrInstr += 7;
return;
} // End of switch (gBattlescriptCurrInstr[2]) } // End of switch (gBattlescriptCurrInstr[2])
gBattlescriptCurrInstr += 3; gBattlescriptCurrInstr += 3;
@ -12058,7 +12071,9 @@ static void Cmd_trysetencore(void)
break; break;
} }
if (gLastMoves[gBattlerTarget] == MOVE_STRUGGLE if (gLastMoves[gBattlerTarget] == MOVE_NONE
|| gLastMoves[gBattlerTarget] == MOVE_UNAVAILABLE
|| gLastMoves[gBattlerTarget] == MOVE_STRUGGLE
|| gLastMoves[gBattlerTarget] == MOVE_ENCORE || gLastMoves[gBattlerTarget] == MOVE_ENCORE
|| gLastMoves[gBattlerTarget] == MOVE_MIRROR_MOVE || gLastMoves[gBattlerTarget] == MOVE_MIRROR_MOVE
|| gLastMoves[gBattlerTarget] == MOVE_SHELL_TRAP) || gLastMoves[gBattlerTarget] == MOVE_SHELL_TRAP)

View File

@ -515,9 +515,11 @@ void HandleAction_UseMove(void)
if (gBattleTypeFlags & BATTLE_TYPE_ARENA) if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
BattleArena_AddMindPoints(gBattlerAttacker); BattleArena_AddMindPoints(gBattlerAttacker);
// Record HP of each battler
for (i = 0; i < MAX_BATTLERS_COUNT; i++) for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
gBattleStruct->hpBefore[i] = gBattleMons[i].hp; gBattleStruct->hpBefore[i] = gBattleMons[i].hp;
gSpecialStatuses[i].emergencyExited = FALSE;
}
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT; gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
} }
@ -5566,7 +5568,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
&& gBattleMons[gBattlerAttacker].hp != 0 && gBattleMons[gBattlerAttacker].hp != 0
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg && !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& TARGET_TURN_DAMAGED && TARGET_TURN_DAMAGED
&& CanBePoisoned(gBattlerAttacker, gBattlerTarget) && CanBePoisoned(gBattlerTarget, gBattlerAttacker)
&& IsMoveMakingContact(move, gBattlerAttacker) && IsMoveMakingContact(move, gBattlerAttacker)
&& (Random() % 3) == 0) && (Random() % 3) == 0)
{ {
@ -5803,7 +5805,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg && !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& IS_MOVE_PHYSICAL(gCurrentMove) && IS_MOVE_PHYSICAL(gCurrentMove)
&& TARGET_TURN_DAMAGED && TARGET_TURN_DAMAGED
&& !(gSideStatuses[gBattlerAttacker] & SIDE_STATUS_TOXIC_SPIKES) && !(gSideStatuses[GetBattlerSide(gBattlerAttacker)] & SIDE_STATUS_TOXIC_SPIKES)
&& IsBattlerAlive(gBattlerTarget)) && IsBattlerAlive(gBattlerTarget))
{ {
gBattlerTarget = gBattlerAttacker; gBattlerTarget = gBattlerAttacker;
@ -5895,6 +5897,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
switch (GetBattlerAbility(battler)) switch (GetBattlerAbility(battler))
{ {
case ABILITY_IMMUNITY: case ABILITY_IMMUNITY:
case ABILITY_PASTEL_VEIL:
if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON | STATUS1_TOXIC_COUNTER)) if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON | STATUS1_TOXIC_COUNTER))
{ {
StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn); StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn);
@ -8150,11 +8153,9 @@ bool32 IsMoveMakingContact(u16 move, u8 battlerAtk)
else else
return FALSE; return FALSE;
} }
else if (GetBattlerAbility(battlerAtk) == ABILITY_LONG_REACH || atkHoldEffect == HOLD_EFFECT_PUNCHING_GLOVE) else if ((atkHoldEffect == HOLD_EFFECT_PUNCHING_GLOVE && (gBattleMoves[move].flags & FLAG_IRON_FIST_BOOST))
{ || atkHoldEffect == HOLD_EFFECT_PROTECTIVE_PADS
return FALSE; || GetBattlerAbility(battlerAtk) == ABILITY_LONG_REACH)
}
else if (atkHoldEffect == HOLD_EFFECT_PROTECTIVE_PADS)
{ {
return FALSE; return FALSE;
} }
@ -10027,18 +10028,12 @@ bool32 CanMegaEvolve(u8 battlerId)
// Can Mega Evolve via Mega Stone. // Can Mega Evolve via Mega Stone.
if (holdEffect == HOLD_EFFECT_MEGA_STONE) if (holdEffect == HOLD_EFFECT_MEGA_STONE)
{
gBattleStruct->mega.isWishMegaEvo = FALSE;
return TRUE; return TRUE;
} }
}
// Check if there is an entry in the evolution table for Wish Mega Evolution. // Check if there is an entry in the evolution table for Wish Mega Evolution.
if (GetBattleFormChangeTargetSpecies(battlerId, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE) != SPECIES_NONE) if (GetBattleFormChangeTargetSpecies(battlerId, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE) != SPECIES_NONE)
{
gBattleStruct->mega.isWishMegaEvo = TRUE;
return TRUE; return TRUE;
}
// No checks passed, the mon CAN'T mega evolve. // No checks passed, the mon CAN'T mega evolve.
return FALSE; return FALSE;

View File

@ -8150,7 +8150,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_Z] =
[MOVE_WOOD_HAMMER] = [MOVE_WOOD_HAMMER] =
{ {
.effect = EFFECT_RECOIL_25, .effect = EFFECT_RECOIL_33,
.power = 120, .power = 120,
.type = TYPE_GRASS, .type = TYPE_GRASS,
.accuracy = 100, .accuracy = 100,

View File

@ -1930,3 +1930,18 @@ const u32 gItemIcon_Gem[] = INCBIN_U32("graphics/items/icons/gem.4bpp.lz");
const u32 gItemIconPalette_Ruby[] = INCBIN_U32("graphics/items/icon_palettes/ruby.gbapal.lz"); const u32 gItemIconPalette_Ruby[] = INCBIN_U32("graphics/items/icon_palettes/ruby.gbapal.lz");
const u32 gItemIconPalette_Sapphire[] = INCBIN_U32("graphics/items/icon_palettes/sapphire.gbapal.lz"); const u32 gItemIconPalette_Sapphire[] = INCBIN_U32("graphics/items/icon_palettes/sapphire.gbapal.lz");
//const u32 gItemIcon_AbilityShield[] = INCBIN_U32("graphics/items/icons/ability_shield.4bpp.lz");
//const u32 gItemIconPalette_AbilityShield[] = INCBIN_U32("graphics/items/icon_palettes/ability_shield.gbapal.lz");
//const u32 gItemIcon_ClearAmulet[] = INCBIN_U32("graphics/items/icons/clear_amulet.4bpp.lz");
//const u32 gItemIconPalette_ClearAmulet[] = INCBIN_U32("graphics/items/icon_palettes/clear_amulet.gbapal.lz");
//const u32 gItemIcon_PunchingGlove[] = INCBIN_U32("graphics/items/icons/punching_glove.4bpp.lz");
//const u32 gItemIconPalette_PunchingGlove[] = INCBIN_U32("graphics/items/icon_palettes/punching_glove.gbapal.lz");
//const u32 gItemIcon_CovertCloak[] = INCBIN_U32("graphics/items/icons/covert_cloak.4bpp.lz");
//const u32 gItemIconPalette_CovertCloak[] = INCBIN_U32("graphics/items/icon_palettes/covert_cloak.gbapal.lz");
//const u32 gItemIcon_LoadedDice[] = INCBIN_U32("graphics/items/icons/loaded_dice.4bpp.lz");
//const u32 gItemIconPalette_LoadedDice[] = INCBIN_U32("graphics/items/icon_palettes/loaded_dice.gbapal.lz");

View File

@ -803,6 +803,11 @@ const u32 *const gItemIconTable[ITEMS_COUNT + 1][2] =
[ITEM_TEA] = {gItemIcon_Tea, gItemIconPalette_Tea}, [ITEM_TEA] = {gItemIcon_Tea, gItemIconPalette_Tea},
[ITEM_RUBY] = {gItemIcon_Gem, gItemIconPalette_Ruby}, [ITEM_RUBY] = {gItemIcon_Gem, gItemIconPalette_Ruby},
[ITEM_SAPPHIRE] = {gItemIcon_Gem, gItemIconPalette_Sapphire}, [ITEM_SAPPHIRE] = {gItemIcon_Gem, gItemIconPalette_Sapphire},
[ITEM_ABILITY_SHIELD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_AbilityShield, gItemIconPalette_AbilityShield},
[ITEM_CLEAR_AMULET] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_ClearAmulet, gItemIconPalette_ClearAmulet},
[ITEM_PUNCHING_GLOVE] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_PunchingGlove, gItemIconPalette_PunchingGlove},
[ITEM_COVERT_CLOAK] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_CovertCloak, gItemIconPalette_CovertCloak},
[ITEM_LOADED_DICE] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_LoadedDice, gItemIconPalette_LoadedDice},
// Return to field arrow // Return to field arrow
[ITEMS_COUNT] = {gItemIcon_ReturnToFieldArrow, gItemIconPalette_ReturnToFieldArrow}, [ITEMS_COUNT] = {gItemIcon_ReturnToFieldArrow, gItemIconPalette_ReturnToFieldArrow},
}; };

View File

@ -9841,4 +9841,70 @@ const struct Item gItems[] =
.type = ITEM_USE_BAG_MENU, .type = ITEM_USE_BAG_MENU,
.fieldUseFunc = ItemUseOutOfBattle_CannotUse, .fieldUseFunc = ItemUseOutOfBattle_CannotUse,
}, },
[ITEM_ABILITY_SHIELD] =
{
.name = _("AbilityShield"),
.itemId = ITEM_ABILITY_SHIELD,
.price = 20000,
.holdEffect = HOLD_EFFECT_ABILITY_SHIELD,
.description = sAbilityShieldDesc,
.pocket = POCKET_ITEMS,
.type = ITEM_USE_BAG_MENU,
.fieldUseFunc = ItemUseOutOfBattle_CannotUse,
.flingPower = 30,
},
[ITEM_CLEAR_AMULET] =
{
.name = _("Clear Amulet"),
.itemId = ITEM_CLEAR_AMULET,
.price = 30000,
.holdEffect = HOLD_EFFECT_CLEAR_AMULET,
.description = sClearAmuletDesc,
.pocket = POCKET_ITEMS,
.type = ITEM_USE_BAG_MENU,
.fieldUseFunc = ItemUseOutOfBattle_CannotUse,
.flingPower = 30,
},
[ITEM_PUNCHING_GLOVE] =
{
.name = _("PunchingGlove"),
.itemId = ITEM_PUNCHING_GLOVE,
.price = 15000,
.holdEffect = HOLD_EFFECT_PUNCHING_GLOVE,
.description = sPunchingGloveDesc,
.pocket = POCKET_ITEMS,
.type = ITEM_USE_BAG_MENU,
.fieldUseFunc = ItemUseOutOfBattle_CannotUse,
.flingPower = 30,
},
[ITEM_COVERT_CLOAK] =
{
.name = _("Covert Cloak"),
.itemId = ITEM_COVERT_CLOAK,
.price = 20000,
.holdEffect = HOLD_EFFECT_COVERT_CLOAK,
.description = sCovertCloakDesc,
.pocket = POCKET_ITEMS,
.type = ITEM_USE_BAG_MENU,
.fieldUseFunc = ItemUseOutOfBattle_CannotUse,
.flingPower = 30,
},
[ITEM_LOADED_DICE] =
{
//YellwApricorn
.name = _("Loaded Dice"),
.itemId = ITEM_LOADED_DICE,
.price = 20000,
.holdEffect = HOLD_EFFECT_LOADED_DICE,
.description = sLoadedDiceDesc,
.pocket = POCKET_ITEMS,
.type = ITEM_USE_BAG_MENU,
.fieldUseFunc = ItemUseOutOfBattle_CannotUse,
.flingPower = 30,
},
}; };

View File

@ -3792,3 +3792,28 @@ static const u8 sSapphireDesc[] = _(
"A brilliant blue gem\n" "A brilliant blue gem\n"
"that symbolizes\n" "that symbolizes\n"
"honesty."); "honesty.");
static const u8 sAbilityShieldDesc[] = _(
"Ability changes are\n"
"prevented for this\n"
"items's holder.");
static const u8 sClearAmuletDesc[] = _(
"Stat lowering is\n"
"prevented for this\n"
"items's holder.");
static const u8 sPunchingGloveDesc[] = _(
"Powers up punching\n"
"moves and removes\n"
"their contact.");
static const u8 sCovertCloakDesc[] = _(
"Protects the holder\n"
"from secondary\n"
"move effects.");
static const u8 sLoadedDiceDesc[] = _(
"Rolls high numbers.\n"
"Multihit strikes\n"
"hit more times.");

View File

@ -541,11 +541,17 @@ static void RemoveIVIndexFromList(u8 *ivs, u8 selectedIv)
static void InheritIVs(struct Pokemon *egg, struct DayCare *daycare) 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;
u8 selectedIvs[INHERITED_IV_COUNT]; u8 selectedIvs[5];
u8 availableIVs[NUM_STATS]; u8 availableIVs[NUM_STATS];
u8 whichParents[INHERITED_IV_COUNT]; u8 whichParents[5];
u8 iv; u8 iv;
u8 howManyIVs = 3;
if (motherItem == ITEM_DESTINY_KNOT || fatherItem == ITEM_DESTINY_KNOT)
howManyIVs = 5;
// Initialize a list of IV indices. // Initialize a list of IV indices.
for (i = 0; i < NUM_STATS; i++) for (i = 0; i < NUM_STATS; i++)
@ -553,8 +559,8 @@ static void InheritIVs(struct Pokemon *egg, struct DayCare *daycare)
availableIVs[i] = i; availableIVs[i] = i;
} }
// Select the 3 IVs that will be inherited. // Select which IVs that will be inherited.
for (i = 0; i < INHERITED_IV_COUNT; i++) for (i = 0; i < howManyIVs; i++)
{ {
// Randomly pick an IV from the available list and stop from being chosen again. // 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 // BUG: Instead of removing the IV that was just picked, this
@ -573,13 +579,13 @@ static void InheritIVs(struct Pokemon *egg, struct DayCare *daycare)
} }
// Determine which parent each of the selected IVs should inherit from. // Determine which parent each of the selected IVs should inherit from.
for (i = 0; i < INHERITED_IV_COUNT; i++) for (i = 0; i < howManyIVs; i++)
{ {
whichParents[i] = Random() % DAYCARE_MON_COUNT; whichParents[i] = Random() % DAYCARE_MON_COUNT;
} }
// Set each of inherited IVs on the egg mon. // Set each of inherited IVs on the egg mon.
for (i = 0; i < INHERITED_IV_COUNT; i++) for (i = 0; i < howManyIVs; i++)
{ {
switch (selectedIvs[i]) switch (selectedIvs[i])
{ {

View File

@ -867,17 +867,17 @@ static const u8 *const sEasyChatKeyboardAlphabet[NUM_ALPHABET_ROWS] =
static const struct SpriteSheet sSpriteSheets[] = { static const struct SpriteSheet sSpriteSheets[] = {
{ {
.data = sTriangleCursor_Gfx, .data = sTriangleCursor_Gfx,
.size = 0x20, .size = sizeof(sTriangleCursor_Gfx),
.tag = GFXTAG_TRIANGLE_CURSOR .tag = GFXTAG_TRIANGLE_CURSOR
}, },
{ {
.data = sScrollIndicator_Gfx, .data = sScrollIndicator_Gfx,
.size = 0x100, .size = sizeof(sScrollIndicator_Gfx),
.tag = GFXTAG_SCROLL_INDICATOR .tag = GFXTAG_SCROLL_INDICATOR
}, },
{ {
.data = sStartSelectButtons_Gfx, .data = sStartSelectButtons_Gfx,
.size = 0x100, .size = sizeof(sStartSelectButtons_Gfx),
.tag = GFXTAG_START_SELECT_BUTTONS .tag = GFXTAG_START_SELECT_BUTTONS
}, },
{0} {0}

View File

@ -145,14 +145,14 @@ static const union AnimCmd *const sSpriteAnimTable_Egg[] =
static const struct SpriteSheet sEggHatch_Sheet = static const struct SpriteSheet sEggHatch_Sheet =
{ {
.data = sEggHatchTiles, .data = sEggHatchTiles,
.size = 0x800, .size = sizeof(sEggHatchTiles),
.tag = GFXTAG_EGG, .tag = GFXTAG_EGG,
}; };
static const struct SpriteSheet sEggShards_Sheet = static const struct SpriteSheet sEggShards_Sheet =
{ {
.data = sEggShardTiles, .data = sEggShardTiles,
.size = 0x80, .size = sizeof(sEggShardTiles),
.tag = GFXTAG_EGG_SHARD, .tag = GFXTAG_EGG_SHARD,
}; };
@ -378,9 +378,6 @@ static void AddHatchedMonToParty(u8 id)
GetMonNickname2(mon, gStringVar1); GetMonNickname2(mon, gStringVar1);
ball = ITEM_POKE_BALL;
SetMonData(mon, MON_DATA_POKEBALL, &ball);
// A met level of 0 is interpreted on the summary screen as "hatched at" // A met level of 0 is interpreted on the summary screen as "hatched at"
metLevel = 0; metLevel = 0;
SetMonData(mon, MON_DATA_MET_LEVEL, &metLevel); SetMonData(mon, MON_DATA_MET_LEVEL, &metLevel);

View File

@ -1240,9 +1240,9 @@ void GetSecretBaseNearbyMapName(void)
GetMapName(gStringVar1, VarGet(VAR_SECRET_BASE_MAP), 0); GetMapName(gStringVar1, VarGet(VAR_SECRET_BASE_MAP), 0);
} }
u16 GetBestBattleTowerStreak(void) u16 GetBattleTowerSinglesStreak(void)
{ {
return GetGameStat(GAME_STAT_BATTLE_TOWER_BEST_STREAK); return GetGameStat(GAME_STAT_BATTLE_TOWER_SINGLES_STREAK);
} }
void BufferEReaderTrainerName(void) void BufferEReaderTrainerName(void)

View File

@ -2068,7 +2068,7 @@ static void IncrementWinStreak(void)
gSaveBlock2Ptr->frontier.towerWinStreaks[battleMode][lvlMode]++; gSaveBlock2Ptr->frontier.towerWinStreaks[battleMode][lvlMode]++;
if (battleMode == FRONTIER_MODE_SINGLES) if (battleMode == FRONTIER_MODE_SINGLES)
{ {
SetGameStat(GAME_STAT_BATTLE_TOWER_BEST_STREAK, gSaveBlock2Ptr->frontier.towerWinStreaks[battleMode][lvlMode]); SetGameStat(GAME_STAT_BATTLE_TOWER_SINGLES_STREAK, gSaveBlock2Ptr->frontier.towerWinStreaks[battleMode][lvlMode]);
gSaveBlock2Ptr->frontier.towerSinglesStreak = gSaveBlock2Ptr->frontier.towerWinStreaks[battleMode][lvlMode]; gSaveBlock2Ptr->frontier.towerSinglesStreak = gSaveBlock2Ptr->frontier.towerWinStreaks[battleMode][lvlMode];
} }
} }

View File

@ -205,7 +205,7 @@ static const union AffineAnimCmd *const sRotatingBallAnimCmds_FullRotation[] =
static const struct SpriteSheet sRotatingBallTable = static const struct SpriteSheet sRotatingBallTable =
{ {
sRotatingBall_Gfx, 0x80, TAG_ROTATING_BALL_GFX sRotatingBall_Gfx, sizeof(sRotatingBall_Gfx), TAG_ROTATING_BALL_GFX
}; };
static const struct SpritePalette sRotatingBallPaletteTable = static const struct SpritePalette sRotatingBallPaletteTable =

View File

@ -134,7 +134,7 @@ static const u8 sWireless_ASCIItoRSETable[256] = {
0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94
}; };
static const u8 sWireless_RSEtoASCIITable[256] = { const u8 gWireless_RSEtoASCIITable[256] = {
[CHAR_SPACE] = ' ', [CHAR_SPACE] = ' ',
0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d,
0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
@ -612,7 +612,7 @@ static void PkmnStrToASCII(u8 *asciiStr, const u8 *pkmnStr)
s32 i; s32 i;
for (i = 0; pkmnStr[i] != EOS; i++) for (i = 0; pkmnStr[i] != EOS; i++)
asciiStr[i] = sWireless_RSEtoASCIITable[pkmnStr[i]]; asciiStr[i] = gWireless_RSEtoASCIITable[pkmnStr[i]];
asciiStr[i] = 0; asciiStr[i] = 0;
} }

View File

@ -31,6 +31,9 @@ static void VCountIntr(void);
static void SerialIntr(void); static void SerialIntr(void);
static void IntrDummy(void); static void IntrDummy(void);
// Defined in the linker script so that the test build can override it.
extern void gInitialMainCB2(void);
const u8 gGameVersion = GAME_VERSION; const u8 gGameVersion = GAME_VERSION;
const u8 gGameLanguage = GAME_LANGUAGE; // English const u8 gGameLanguage = GAME_LANGUAGE; // English
@ -68,6 +71,7 @@ IntrFunc gIntrTable[INTR_COUNT];
u8 gLinkVSyncDisabled; u8 gLinkVSyncDisabled;
u32 IntrMain_Buffer[0x200]; u32 IntrMain_Buffer[0x200];
s8 gPcmDmaCounter; s8 gPcmDmaCounter;
void *gAgbMainLoop_sp;
static EWRAM_DATA u16 sTrainerId = 0; static EWRAM_DATA u16 sTrainerId = 0;
@ -126,6 +130,12 @@ void AgbMain()
AGBPrintfInit(); AGBPrintfInit();
#endif #endif
#endif #endif
gAgbMainLoop_sp = __builtin_frame_address(0);
AgbMainLoop();
}
void AgbMainLoop(void)
{
for (;;) for (;;)
{ {
ReadKeys(); ReadKeys();
@ -178,7 +188,7 @@ static void InitMainCallbacks(void)
gTrainerHillVBlankCounter = NULL; gTrainerHillVBlankCounter = NULL;
gMain.vblankCounter2 = 0; gMain.vblankCounter2 = 0;
gMain.callback1 = NULL; gMain.callback1 = NULL;
SetMainCallback2(CB2_InitCopyrightScreenAfterBootup); SetMainCallback2(gInitialMainCB2);
gSaveBlock2Ptr = &gSaveblock2.block; gSaveBlock2Ptr = &gSaveblock2.block;
gPokemonStoragePtr = &gPokemonStorage.block; gPokemonStoragePtr = &gPokemonStorage.block;
} }

View File

@ -96,7 +96,7 @@ static const s16 sCeilingCrumblePositions[][3] =
static const struct SpriteSheet sCeilingCrumbleSpriteSheets[] = static const struct SpriteSheet sCeilingCrumbleSpriteSheets[] =
{ {
{sMirageTowerCrumbles_Gfx, 0x80, TAG_CEILING_CRUMBLE}, {sMirageTowerCrumbles_Gfx, sizeof(sMirageTowerCrumbles_Gfx), TAG_CEILING_CRUMBLE},
{} {}
}; };

View File

@ -145,12 +145,19 @@
// The different versions of hearts are selected using animation // The different versions of hearts are selected using animation
// commands. // commands.
#define APPEAL_HEART_EMPTY 0 enum {
#define APPEAL_HEART_FULL 1 APPEAL_HEART_EMPTY,
#define JAM_HEART_EMPTY 2 APPEAL_HEART_FULL,
#define JAM_HEART_FULL 3 JAM_HEART_EMPTY,
JAM_HEART_FULL,
};
#define MAX_RELEARNER_MOVES (MAX_LEVEL_UP_MOVES > 25 ? MAX_LEVEL_UP_MOVES : 25) #define TAG_MODE_ARROWS 5325
#define TAG_LIST_ARROWS 5425
#define GFXTAG_UI 5525
#define PALTAG_UI 5526
#define MAX_RELEARNER_MOVES max(MAX_LEVEL_UP_MOVES, 25)
static EWRAM_DATA struct static EWRAM_DATA struct
{ {
@ -174,11 +181,11 @@ static EWRAM_DATA struct {
bool8 showContestInfo; bool8 showContestInfo;
} sMoveRelearnerMenuSate = {0}; } sMoveRelearnerMenuSate = {0};
static const u16 sMoveRelearnerPaletteData[] = INCBIN_U16("graphics/interface/ui_learn_move.gbapal"); static const u16 sUI_Pal[] = INCBIN_U16("graphics/interface/ui_learn_move.gbapal");
// The arrow sprites in this spritesheet aren't used. The scroll-arrow system provides its own // The arrow sprites in this spritesheet aren't used. The scroll-arrow system provides its own
// arrow sprites. // arrow sprites.
static const u8 sMoveRelearnerSpriteSheetData[] = INCBIN_U8("graphics/interface/ui_learn_move.4bpp"); static const u8 sUI_Tiles[] = INCBIN_U8("graphics/interface/ui_learn_move.4bpp");
static const struct OamData sHeartSpriteOamData = static const struct OamData sHeartSpriteOamData =
{ {
@ -233,15 +240,15 @@ static const struct OamData sUnusedOam2 =
static const struct SpriteSheet sMoveRelearnerSpriteSheet = static const struct SpriteSheet sMoveRelearnerSpriteSheet =
{ {
.data = sMoveRelearnerSpriteSheetData, .data = sUI_Tiles,
.size = 0x180, .size = sizeof(sUI_Tiles),
.tag = 5525 .tag = GFXTAG_UI
}; };
static const struct SpritePalette sMoveRelearnerPalette = static const struct SpritePalette sMoveRelearnerPalette =
{ {
.data = sMoveRelearnerPaletteData, .data = sUI_Pal,
.tag = 5526 .tag = PALTAG_UI
}; };
static const struct ScrollArrowsTemplate sDisplayModeArrowsTemplate = static const struct ScrollArrowsTemplate sDisplayModeArrowsTemplate =
@ -254,8 +261,8 @@ static const struct ScrollArrowsTemplate sDisplayModeArrowsTemplate =
.secondY = 16, .secondY = 16,
.fullyUpThreshold = -1, .fullyUpThreshold = -1,
.fullyDownThreshold = -1, .fullyDownThreshold = -1,
.tileTag = 5325, .tileTag = TAG_MODE_ARROWS,
.palTag = 5325, .palTag = TAG_MODE_ARROWS,
.palNum = 0, .palNum = 0,
}; };
@ -269,8 +276,8 @@ static const struct ScrollArrowsTemplate sMoveListScrollArrowsTemplate =
.secondY = 104, .secondY = 104,
.fullyUpThreshold = 0, .fullyUpThreshold = 0,
.fullyDownThreshold = 0, .fullyDownThreshold = 0,
.tileTag = 5425, .tileTag = TAG_LIST_ARROWS,
.palTag = 5425, .palTag = TAG_LIST_ARROWS,
.palNum = 0, .palNum = 0,
}; };
@ -308,8 +315,8 @@ static const union AnimCmd *const sHeartSpriteAnimationCommands[] =
static const struct SpriteTemplate sConstestMoveHeartSprite = static const struct SpriteTemplate sConstestMoveHeartSprite =
{ {
.tileTag = 5525, .tileTag = GFXTAG_UI,
.paletteTag = 5526, .paletteTag = PALTAG_UI,
.oam = &sHeartSpriteOamData, .oam = &sHeartSpriteOamData,
.anims = sHeartSpriteAnimationCommands, .anims = sHeartSpriteAnimationCommands,
.images = NULL, .images = NULL,

View File

@ -27,6 +27,8 @@
#define WAVEFORM_WINDOW_HEIGHT 56 #define WAVEFORM_WINDOW_HEIGHT 56
#define TAG_NEEDLE 0x2000
struct PokedexCryMeterNeedle { struct PokedexCryMeterNeedle {
s8 rotation; s8 rotation;
s8 targetRotation; s8 targetRotation;
@ -202,8 +204,8 @@ static const struct OamData sOamData_CryMeterNeedle =
static const struct SpriteTemplate sCryMeterNeedleSpriteTemplate = static const struct SpriteTemplate sCryMeterNeedleSpriteTemplate =
{ {
.tileTag = 0x2000, .tileTag = TAG_NEEDLE,
.paletteTag = 0x2000, .paletteTag = TAG_NEEDLE,
.oam = &sOamData_CryMeterNeedle, .oam = &sOamData_CryMeterNeedle,
.anims = sSpriteAnimTable_CryMeterNeedle, .anims = sSpriteAnimTable_CryMeterNeedle,
.images = NULL, .images = NULL,
@ -213,13 +215,13 @@ static const struct SpriteTemplate sCryMeterNeedleSpriteTemplate =
static const struct SpriteSheet sCryMeterNeedleSpriteSheets[] = static const struct SpriteSheet sCryMeterNeedleSpriteSheets[] =
{ {
{sCryMeterNeedle_Gfx, 0x800, 0x2000}, {sCryMeterNeedle_Gfx, sizeof(sCryMeterNeedle_Gfx), TAG_NEEDLE},
{} {}
}; };
static const struct SpritePalette sCryMeterNeedleSpritePalettes[] = static const struct SpritePalette sCryMeterNeedleSpritePalettes[] =
{ {
{sCryMeterNeedle_Pal, 0x2000}, {sCryMeterNeedle_Pal, TAG_NEEDLE},
{} {}
}; };

View File

@ -8737,3 +8737,32 @@ u32 GetMonFriendshipScore(struct Pokemon *pokemon)
return FRIENDSHIP_NONE; return FRIENDSHIP_NONE;
} }
void UpdateMonPersonality(struct BoxPokemon *boxMon, u32 personality)
{
struct PokemonSubstruct0 *old0, *new0;
struct PokemonSubstruct1 *old1, *new1;
struct PokemonSubstruct2 *old2, *new2;
struct PokemonSubstruct3 *old3, *new3;
struct BoxPokemon old;
old = *boxMon;
old0 = &(GetSubstruct(&old, old.personality, 0)->type0);
old1 = &(GetSubstruct(&old, old.personality, 1)->type1);
old2 = &(GetSubstruct(&old, old.personality, 2)->type2);
old3 = &(GetSubstruct(&old, old.personality, 3)->type3);
new0 = &(GetSubstruct(boxMon, personality, 0)->type0);
new1 = &(GetSubstruct(boxMon, personality, 1)->type1);
new2 = &(GetSubstruct(boxMon, personality, 2)->type2);
new3 = &(GetSubstruct(boxMon, personality, 3)->type3);
DecryptBoxMon(&old);
boxMon->personality = personality;
*new0 = *old0;
*new1 = *old1;
*new2 = *old2;
*new3 = *old3;
boxMon->checksum = CalculateBoxMonChecksum(boxMon);
EncryptBoxMon(boxMon);
}

View File

@ -258,7 +258,7 @@ enum {
// The maximum number of Pokémon icons that can appear on-screen. // The maximum number of Pokémon icons that can appear on-screen.
// By default the limit is 40 (though in practice only 37 can be). // By default the limit is 40 (though in practice only 37 can be).
#define MAX_MON_ICONS (IN_BOX_COUNT + PARTY_SIZE + 1 >= 40 ? IN_BOX_COUNT + PARTY_SIZE + 1 : 40) #define MAX_MON_ICONS max(IN_BOX_COUNT + PARTY_SIZE + 1, 40)
// The maximum number of item icons that can appear on-screen while // The maximum number of item icons that can appear on-screen while
// moving held items. 1 in the cursor, and 2 more while switching // moving held items. 1 in the cursor, and 2 more while switching
@ -1246,7 +1246,7 @@ static const union AffineAnimCmd *const sAffineAnims_ReleaseMon[] =
static const u16 sUnusedColor = RGB(26, 29, 8); static const u16 sUnusedColor = RGB(26, 29, 8);
static const struct SpriteSheet sSpriteSheet_Arrow = {sArrow_Gfx, 0x80, GFXTAG_ARROW}; static const struct SpriteSheet sSpriteSheet_Arrow = {sArrow_Gfx, sizeof(sArrow_Gfx), GFXTAG_ARROW};
static const struct OamData sOamData_BoxTitle = static const struct OamData sOamData_BoxTitle =
{ {

View File

@ -14,14 +14,13 @@
#include "malloc.h" #include "malloc.h"
#include "util.h" #include "util.h"
#include "task.h" #include "task.h"
#include "test_runner.h"
#include "text.h" #include "text.h"
#include "battle_setup.h" #include "battle_setup.h"
#include "frontier_util.h" #include "frontier_util.h"
#include "constants/trainers.h" #include "constants/trainers.h"
#include "constants/rgb.h" #include "constants/rgb.h"
#define BATTLER_RECORD_SIZE 664
struct PlayerInfo struct PlayerInfo
{ {
u32 trainerId; u32 trainerId;
@ -31,37 +30,6 @@ struct PlayerInfo
u16 language; u16 language;
}; };
struct RecordedBattleSave
{
struct Pokemon playerParty[PARTY_SIZE];
struct Pokemon opponentParty[PARTY_SIZE];
u8 playersName[MAX_BATTLERS_COUNT][PLAYER_NAME_LENGTH + 1];
u8 playersGender[MAX_BATTLERS_COUNT];
u32 playersTrainerId[MAX_BATTLERS_COUNT];
u8 playersLanguage[MAX_BATTLERS_COUNT];
u32 rngSeed;
u32 battleFlags;
u8 playersBattlers[MAX_BATTLERS_COUNT];
u16 opponentA;
u16 opponentB;
u16 partnerId;
u16 multiplayerId;
u8 lvlMode;
u8 frontierFacility;
u8 frontierBrainSymbol;
u8 battleScene:1;
u8 textSpeed:3;
u32 AI_scripts;
u8 recordMixFriendName[PLAYER_NAME_LENGTH + 1];
u8 recordMixFriendClass;
u8 apprenticeId;
u16 easyChatSpeech[EASY_CHAT_BATTLE_WORDS_COUNT];
u8 recordMixFriendLanguage;
u8 apprenticeLanguage;
u8 battleRecord[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE];
u32 checksum;
};
// Save data using TryWriteSpecialSaveSector is allowed to exceed SECTOR_DATA_SIZE (up to the counter field) // Save data using TryWriteSpecialSaveSector is allowed to exceed SECTOR_DATA_SIZE (up to the counter field)
STATIC_ASSERT(sizeof(struct RecordedBattleSave) <= SECTOR_COUNTER_OFFSET, RecordedBattleSaveFreeSpace); STATIC_ASSERT(sizeof(struct RecordedBattleSave) <= SECTOR_COUNTER_OFFSET, RecordedBattleSaveFreeSpace);
@ -205,8 +173,11 @@ void RecordedBattle_ClearBattlerAction(u8 battlerId, u8 bytesToClear)
} }
} }
u8 RecordedBattle_GetBattlerAction(u8 battlerId) u8 RecordedBattle_GetBattlerAction(u32 actionType, u8 battlerId)
{ {
if (gTestRunnerEnabled)
BattleTest_CheckBattleRecordActionType(battlerId, sBattlerRecordSizes[battlerId], actionType);
// Trying to read past array or invalid action byte, battle is over. // Trying to read past array or invalid action byte, battle is over.
if (sBattlerRecordSizes[battlerId] >= BATTLER_RECORD_SIZE || sBattleRecords[battlerId][sBattlerRecordSizes[battlerId]] == 0xFF) if (sBattlerRecordSizes[battlerId] >= BATTLER_RECORD_SIZE || sBattleRecords[battlerId][sBattlerRecordSizes[battlerId]] == 0xFF)
{ {
@ -522,7 +493,7 @@ static void Task_StartAfterCountdown(u8 taskId)
} }
} }
static void SetVariablesForRecordedBattle(struct RecordedBattleSave *src) void SetVariablesForRecordedBattle(struct RecordedBattleSave *src)
{ {
bool8 var; bool8 var;
s32 i, j; s32 i, j;
@ -755,14 +726,14 @@ void RecordedBattle_CheckMovesetChanges(u8 mode)
// We know the current action is ACTION_MOVE_CHANGE, retrieve // We know the current action is ACTION_MOVE_CHANGE, retrieve
// it without saving it to move on to the next action. // it without saving it to move on to the next action.
RecordedBattle_GetBattlerAction(battlerId); RecordedBattle_GetBattlerAction(RECORDED_BYTE, battlerId);
for (j = 0; j < MAX_MON_MOVES; j++) for (j = 0; j < MAX_MON_MOVES; j++)
ppBonuses[j] = ((gBattleMons[battlerId].ppBonuses & (3 << (j << 1))) >> (j << 1)); ppBonuses[j] = ((gBattleMons[battlerId].ppBonuses & (3 << (j << 1))) >> (j << 1));
for (j = 0; j < MAX_MON_MOVES; j++) for (j = 0; j < MAX_MON_MOVES; j++)
{ {
moveSlots[j] = RecordedBattle_GetBattlerAction(battlerId); moveSlots[j] = RecordedBattle_GetBattlerAction(RECORDED_BYTE, battlerId);
movePp.moves[j] = gBattleMons[battlerId].moves[moveSlots[j]]; movePp.moves[j] = gBattleMons[battlerId].moves[moveSlots[j]];
movePp.currentPp[j] = gBattleMons[battlerId].pp[moveSlots[j]]; movePp.currentPp[j] = gBattleMons[battlerId].pp[moveSlots[j]];
movePp.maxPp[j] = ppBonuses[moveSlots[j]]; movePp.maxPp[j] = ppBonuses[moveSlots[j]];

View File

@ -264,14 +264,14 @@ static const struct OamData sOamData_RotatingGateRegular =
static const struct SpriteSheet sRotatingGatesGraphicsTable[] = static const struct SpriteSheet sRotatingGatesGraphicsTable[] =
{ {
{sRotatingGateTiles_1, 0x200, ROTATING_GATE_TILE_TAG + GATE_SHAPE_L1}, {sRotatingGateTiles_1, sizeof(sRotatingGateTiles_1), ROTATING_GATE_TILE_TAG + GATE_SHAPE_L1},
{sRotatingGateTiles_2, 0x800, ROTATING_GATE_TILE_TAG + GATE_SHAPE_L2}, {sRotatingGateTiles_2, sizeof(sRotatingGateTiles_2), ROTATING_GATE_TILE_TAG + GATE_SHAPE_L2},
{sRotatingGateTiles_3, 0x800, ROTATING_GATE_TILE_TAG + GATE_SHAPE_L3}, {sRotatingGateTiles_3, sizeof(sRotatingGateTiles_3), ROTATING_GATE_TILE_TAG + GATE_SHAPE_L3},
{sRotatingGateTiles_4, 0x800, ROTATING_GATE_TILE_TAG + GATE_SHAPE_L4}, {sRotatingGateTiles_4, sizeof(sRotatingGateTiles_4), ROTATING_GATE_TILE_TAG + GATE_SHAPE_L4},
{sRotatingGateTiles_5, 0x200, ROTATING_GATE_TILE_TAG + GATE_SHAPE_T1}, {sRotatingGateTiles_5, sizeof(sRotatingGateTiles_5), ROTATING_GATE_TILE_TAG + GATE_SHAPE_T1},
{sRotatingGateTiles_6, 0x800, ROTATING_GATE_TILE_TAG + GATE_SHAPE_T2}, {sRotatingGateTiles_6, sizeof(sRotatingGateTiles_6), ROTATING_GATE_TILE_TAG + GATE_SHAPE_T2},
{sRotatingGateTiles_7, 0x800, ROTATING_GATE_TILE_TAG + GATE_SHAPE_T3}, {sRotatingGateTiles_7, sizeof(sRotatingGateTiles_7), ROTATING_GATE_TILE_TAG + GATE_SHAPE_T3},
{sRotatingGateTiles_8, 0x800, ROTATING_GATE_TILE_TAG + GATE_SHAPE_T4}, {sRotatingGateTiles_8, sizeof(sRotatingGateTiles_8), ROTATING_GATE_TILE_TAG + GATE_SHAPE_T4},
{NULL}, {NULL},
}; };

46
src/test_runner_stub.c Normal file
View File

@ -0,0 +1,46 @@
#include "global.h"
#include "test_runner.h"
__attribute__((weak))
const bool8 gTestRunnerEnabled = FALSE;
// The Makefile patches gTestRunnerHeadless as part of make test.
// This allows us to open the ROM in an mgba with a UI and see the
// animations and messages play, which helps when debugging a test.
const bool8 gTestRunnerHeadless = FALSE;
const bool8 gTestRunnerSkipIsFail = FALSE;
__attribute__((weak))
void TestRunner_Battle_RecordAbilityPopUp(u32 battlerId, u32 ability)
{
}
__attribute__((weak))
void TestRunner_Battle_RecordAnimation(u32 animType, u32 animId)
{
}
__attribute__((weak))
void TestRunner_Battle_RecordHP(u32 battlerId, u32 oldHP, u32 newHP)
{
}
__attribute__((weak))
void TestRunner_Battle_RecordMessage(const u8 *string)
{
}
__attribute__((weak))
void TestRunner_Battle_RecordStatus1(u32 battlerId, u32 status1)
{
}
__attribute__((weak))
void TestRunner_Battle_AfterLastTurn(void)
{
}
__attribute__((weak))
void BattleTest_CheckBattleRecordActionType(u32 battlerId, u32 recordIndex, u32 actionType)
{
}

20
test/ability_blaze.c Normal file
View File

@ -0,0 +1,20 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage)
{
u16 hp;
PARAMETRIZE { hp = 99; }
PARAMETRIZE { hp = 33; }
GIVEN {
ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE);
PLAYER(SPECIES_CHARMANDER) { Ability(ABILITY_BLAZE); MaxHP(99); HP(hp); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_EMBER); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_GT(results[1].damage, results[0].damage);
}
}

48
test/ability_cute_charm.c Normal file
View File

@ -0,0 +1,48 @@
#include "global.h"
#include "test_battle.h"
// TODO: Currently PASSES_RANDOMLY is incapable of testing Cute Charm
// because it only activates 33% of the time, but we only want to
// measure the 50% of the time that the infatuation prevents our move.
SINGLE_BATTLE_TEST("Cute Charm inflicts infatuation on contact")
{
u32 move;
PARAMETRIZE { move = MOVE_TACKLE; }
PARAMETRIZE { move = MOVE_SWIFT; }
GIVEN {
ASSUME(gBattleMoves[MOVE_TACKLE].flags & FLAG_MAKES_CONTACT);
ASSUME(!(gBattleMoves[MOVE_SWIFT].flags & FLAG_MAKES_CONTACT));
PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); }
OPPONENT(SPECIES_CLEFAIRY) { Gender(MON_FEMALE); Ability(ABILITY_CUTE_CHARM); }
} WHEN {
TURN { MOVE(player, move); }
TURN { MOVE(player, move); }
} SCENE {
if (gBattleMoves[move].flags & FLAG_MAKES_CONTACT) {
ABILITY_POPUP(opponent, ABILITY_CUTE_CHARM);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_INFATUATION, player);
MESSAGE("Foe Clefairy's Cute Charm infatuated Wobbuffet!");
MESSAGE("Wobbuffet is in love with Foe Clefairy!");
} else {
NOT ABILITY_POPUP(opponent, ABILITY_CUTE_CHARM);
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_INFATUATION, player);
NOT MESSAGE("Foe Clefairy's Cute Charm infatuated Wobbuffet!");
NOT MESSAGE("Wobbuffet is in love with Foe Clefairy!");
}
}
}
SINGLE_BATTLE_TEST("Cute Charm cannot infatuate same gender")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); }
OPPONENT(SPECIES_CLEFAIRY) { Gender(MON_MALE); Ability(ABILITY_CUTE_CHARM); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
NOT ABILITY_POPUP(opponent, ABILITY_CUTE_CHARM);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
}
}

29
test/ability_flame_body.c Normal file
View File

@ -0,0 +1,29 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Flame Body inflicts burn on contact")
{
u32 move;
PARAMETRIZE { move = MOVE_TACKLE; }
PARAMETRIZE { move = MOVE_SWIFT; }
GIVEN {
ASSUME(gBattleMoves[MOVE_TACKLE].flags & FLAG_MAKES_CONTACT);
ASSUME(!(gBattleMoves[MOVE_SWIFT].flags & FLAG_MAKES_CONTACT));
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_MAGMAR) { Ability(ABILITY_FLAME_BODY); }
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
if (gBattleMoves[move].flags & FLAG_MAKES_CONTACT) {
ABILITY_POPUP(opponent, ABILITY_FLAME_BODY);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, player);
MESSAGE("Foe Magmar's Flame Body burned Wobbuffet!");
STATUS_ICON(player, burn: TRUE);
} else {
NOT ABILITY_POPUP(opponent, ABILITY_FLAME_BODY);
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, player);
NOT MESSAGE("Foe Magmar's Flame Body burned Wobbuffet!");
NOT STATUS_ICON(player, burn: TRUE);
}
}
}

47
test/ability_immunity.c Normal file
View File

@ -0,0 +1,47 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Immunity prevents Poison Sting poison")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_POISON_STING].effect == EFFECT_POISON_HIT);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_SNORLAX) { Ability(ABILITY_IMMUNITY); }
} WHEN {
TURN { MOVE(player, MOVE_POISON_STING); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, player);
NOT STATUS_ICON(opponent, poison: TRUE);
}
}
SINGLE_BATTLE_TEST("Immunity prevents Toxic bad poison")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_TOXIC].effect == EFFECT_TOXIC);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_SNORLAX) { Ability(ABILITY_IMMUNITY); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC); }
} SCENE {
MESSAGE("Wobbuffet used Toxic!");
ABILITY_POPUP(opponent, ABILITY_IMMUNITY);
MESSAGE("Foe Snorlax's Immunity prevents poisoning!");
NOT STATUS_ICON(opponent, poison: TRUE);
}
}
SINGLE_BATTLE_TEST("Immunity prevents Toxic Spikes poison")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_TOXIC_SPIKES].effect == EFFECT_TOXIC_SPIKES);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_SNORLAX) { Ability(ABILITY_IMMUNITY); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); }
TURN { SWITCH(opponent, 1); }
} SCENE {
NOT STATUS_ICON(opponent, poison: TRUE);
}
}

169
test/ability_pastel_veil.c Normal file
View File

@ -0,0 +1,169 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Pastel Veil prevents Poison Sting poison")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_POISON_STING].effect == EFFECT_POISON_HIT);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_PONYTA_GALARIAN) { Ability(ABILITY_PASTEL_VEIL); }
} WHEN {
TURN { MOVE(player, MOVE_POISON_STING); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, player);
NOT STATUS_ICON(opponent, poison: TRUE);
}
}
DOUBLE_BATTLE_TEST("Pastel Veil prevents Poison Sting poison on partner")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_POISON_STING].effect == EFFECT_POISON_HIT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_PONYTA_GALARIAN) { Ability(ABILITY_PASTEL_VEIL); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_POISON_STING, target: opponentRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, playerLeft);
NOT STATUS_ICON(opponentRight, poison: TRUE);
}
}
SINGLE_BATTLE_TEST("Pastel Veil immediately cures Mold Breaker poison")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_TOXIC].effect == EFFECT_TOXIC);
PLAYER(SPECIES_DRILBUR) { Ability(ABILITY_MOLD_BREAKER); }
OPPONENT(SPECIES_PONYTA_GALARIAN) { Ability(ABILITY_PASTEL_VEIL); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player);
STATUS_ICON(opponent, badPoison: TRUE);
ABILITY_POPUP(opponent, ABILITY_PASTEL_VEIL);
MESSAGE("Foe Ponyta's Pastel Veil cured its poison problem!");
STATUS_ICON(opponent, none: TRUE);
}
}
DOUBLE_BATTLE_TEST("Pastel Veil does not cure Mold Breaker poison on partner")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_TOXIC].effect == EFFECT_TOXIC);
PLAYER(SPECIES_DRILBUR) { Ability(ABILITY_MOLD_BREAKER); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_PONYTA_GALARIAN) { Ability(ABILITY_PASTEL_VEIL); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_TOXIC, target: opponentRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, playerLeft, target: opponentRight);
STATUS_ICON(opponentRight, badPoison: TRUE);
NOT STATUS_ICON(opponentRight, none: TRUE);
}
}
SINGLE_BATTLE_TEST("Pastel Veil prevents Toxic bad poison")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_TOXIC].effect == EFFECT_TOXIC);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_PONYTA_GALARIAN) { Ability(ABILITY_PASTEL_VEIL); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC); }
} SCENE {
MESSAGE("Wobbuffet used Toxic!");
ABILITY_POPUP(opponent, ABILITY_PASTEL_VEIL);
MESSAGE("Foe Ponyta is protected by a pastel veil!");
NOT STATUS_ICON(opponent, badPoison: TRUE);
}
}
DOUBLE_BATTLE_TEST("Pastel Veil prevents Toxic bad poison on partner")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_TOXIC].effect == EFFECT_TOXIC);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_PONYTA_GALARIAN) { Ability(ABILITY_PASTEL_VEIL); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_TOXIC, target: opponentRight); }
} SCENE {
MESSAGE("Wobbuffet used Toxic!");
ABILITY_POPUP(opponentLeft, ABILITY_PASTEL_VEIL);
MESSAGE("Foe Wynaut is protected by a pastel veil!");
NOT STATUS_ICON(opponentRight, badPoison: TRUE);
}
}
SINGLE_BATTLE_TEST("Pastel Veil prevents Toxic Spikes poison")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_TOXIC_SPIKES].effect == EFFECT_TOXIC_SPIKES);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_PONYTA_GALARIAN) { Ability(ABILITY_PASTEL_VEIL); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); }
TURN { SWITCH(opponent, 1); }
} SCENE {
MESSAGE("2 sent out Ponyta!");
NOT STATUS_ICON(opponent, poison: TRUE);
}
}
DOUBLE_BATTLE_TEST("Pastel Veil prevents Toxic Spikes poison on partner")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_TOXIC_SPIKES].effect == EFFECT_TOXIC_SPIKES);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_PONYTA_GALARIAN) { Ability(ABILITY_PASTEL_VEIL); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_TOXIC_SPIKES); }
TURN { SWITCH(opponentRight, 2); }
} SCENE {
MESSAGE("2 sent out Wynaut!");
NOT STATUS_ICON(opponentRight, poison: TRUE);
}
}
DOUBLE_BATTLE_TEST("Pastel Veil cures partner's poison on initial switch in")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); }
OPPONENT(SPECIES_PONYTA_GALARIAN) { Ability(ABILITY_PASTEL_VEIL); }
} WHEN {
TURN {}
} SCENE {
MESSAGE("2 sent out Wobbuffet and Ponyta!");
ABILITY_POPUP(opponentRight, ABILITY_PASTEL_VEIL);
MESSAGE("Foe Wobbuffet was cured of its poisoning!");
STATUS_ICON(opponentLeft, none: TRUE);
}
}
DOUBLE_BATTLE_TEST("Pastel Veil cures partner's poison on switch in")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); }
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_PONYTA_GALARIAN) { Ability(ABILITY_PASTEL_VEIL); }
} WHEN {
TURN { SWITCH(opponentRight, 2); }
} SCENE {
MESSAGE("2 sent out Ponyta!");
ABILITY_POPUP(opponentRight, ABILITY_PASTEL_VEIL);
MESSAGE("Foe Wobbuffet was cured of its poisoning!");
STATUS_ICON(opponentLeft, none: TRUE);
}
}

View File

@ -0,0 +1,30 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Poison Point inflicts poison on contact")
{
u32 move;
PARAMETRIZE { move = MOVE_TACKLE; }
PARAMETRIZE { move = MOVE_SWIFT; }
GIVEN {
ASSUME(gBattleMoves[MOVE_TACKLE].flags & FLAG_MAKES_CONTACT);
ASSUME(!(gBattleMoves[MOVE_SWIFT].flags & FLAG_MAKES_CONTACT));
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_NIDORAN_M) { Ability(ABILITY_POISON_POINT); }
} WHEN {
TURN { MOVE(player, move); }
TURN {}
} SCENE {
if (gBattleMoves[move].flags & FLAG_MAKES_CONTACT) {
ABILITY_POPUP(opponent, ABILITY_POISON_POINT);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player);
MESSAGE("Wobbuffet was poisoned by Foe Nidoran♂'s Poison Point!");
STATUS_ICON(player, poison: TRUE);
} else {
NOT ABILITY_POPUP(opponent, ABILITY_POISON_POINT);
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player);
NOT MESSAGE("Wobbuffet was poisoned by Foe Nidoran♂'s Poison Point!");
NOT STATUS_ICON(player, poison: TRUE);
}
}
}

29
test/ability_static.c Normal file
View File

@ -0,0 +1,29 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Static inflicts paralysis on contact")
{
u32 move;
PARAMETRIZE { move = MOVE_TACKLE; }
PARAMETRIZE { move = MOVE_SWIFT; }
GIVEN {
ASSUME(gBattleMoves[MOVE_TACKLE].flags & FLAG_MAKES_CONTACT);
ASSUME(!(gBattleMoves[MOVE_SWIFT].flags & FLAG_MAKES_CONTACT));
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_PIKACHU) { Ability(ABILITY_STATIC); }
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
if (gBattleMoves[move].flags & FLAG_MAKES_CONTACT) {
ABILITY_POPUP(opponent, ABILITY_STATIC);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player);
MESSAGE("Foe Pikachu's Static paralyzed Wobbuffet! It may be unable to move!");
STATUS_ICON(player, paralysis: TRUE);
} else {
NOT ABILITY_POPUP(opponent, ABILITY_STATIC);
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player);
NOT MESSAGE("Foe Pikachu's Static paralyzed Wobbuffet! It may be unable to move!");
NOT STATUS_ICON(player, paralysis: TRUE);
}
}
}

47
test/ability_sturdy.c Normal file
View File

@ -0,0 +1,47 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Sturdy prevents OHKO moves")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_FISSURE].effect == EFFECT_OHKO);
PLAYER(SPECIES_GEODUDE) { Ability(ABILITY_STURDY); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_FISSURE); }
} SCENE {
MESSAGE("Foe Wobbuffet used Fissure!");
ABILITY_POPUP(player, ABILITY_STURDY);
MESSAGE("Geodude was protected by Sturdy!");
} THEN {
EXPECT_EQ(player->hp, player->maxHP);
}
}
SINGLE_BATTLE_TEST("Sturdy prevents OHKOs")
{
GIVEN {
PLAYER(SPECIES_GEODUDE) { Ability(ABILITY_STURDY); MaxHP(100); HP(100); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_SEISMIC_TOSS); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, opponent);
HP_BAR(player, hp: 1);
ABILITY_POPUP(player, ABILITY_STURDY);
MESSAGE("Geodude endured the hit using Sturdy!");
}
}
SINGLE_BATTLE_TEST("Sturdy does not prevent non-OHKOs")
{
GIVEN {
PLAYER(SPECIES_GEODUDE) { Ability(ABILITY_STURDY); MaxHP(100); HP(99); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_SEISMIC_TOSS); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, opponent);
HP_BAR(player, hp: 0);
}
}

View File

@ -0,0 +1,54 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
gItems[ITEM_LEFTOVERS].holdEffect == HOLD_EFFECT_LEFTOVERS;
};
SINGLE_BATTLE_TEST("Leftovers recovers 1/16th HP at end of turn")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(1); Item(ITEM_LEFTOVERS); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {}
} SCENE {
s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
MESSAGE("Wobbuffet's Leftovers restored its HP a little!");
HP_BAR(player, damage: -maxHP / 16);
}
}
SINGLE_BATTLE_TEST("Leftovers does nothing if max HP")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LEFTOVERS); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {}
} SCENE {
NONE_OF {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
MESSAGE("Wobbuffet's Leftovers restored its HP a little!");
HP_BAR(player);
}
}
}
SINGLE_BATTLE_TEST("Leftovers does nothing if Heal Block applies")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(1); Item(ITEM_LEFTOVERS); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_HEAL_BLOCK); }
} SCENE {
NONE_OF {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
MESSAGE("Wobbuffet's Leftovers restored its HP a little!");
HP_BAR(player);
}
}
}

68
test/mega_evolution.c Normal file
View File

@ -0,0 +1,68 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Venusaur can Mega Evolve holding Venusaurite")
{
GIVEN {
PLAYER(SPECIES_VENUSAUR) { Item(ITEM_VENUSAURITE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); }
} SCENE {
MESSAGE("Venusaur's Venusaurite is reacting to 1's Mega Ring!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player);
MESSAGE("Venusaur has Mega Evolved into Mega Venusaur!");
} THEN {
EXPECT_EQ(player->species, SPECIES_VENUSAUR_MEGA);
}
}
SINGLE_BATTLE_TEST("Rayquaza can Mega Evolve knowing Dragon Ascent")
{
GIVEN {
PLAYER(SPECIES_RAYQUAZA) { Moves(MOVE_DRAGON_ASCENT, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); }
} SCENE {
MESSAGE("1's fervent wish has reached Rayquaza!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player);
MESSAGE("Rayquaza has Mega Evolved into Mega Rayquaza!");
} THEN {
EXPECT_EQ(player->species, SPECIES_RAYQUAZA_MEGA);
}
}
SINGLE_BATTLE_TEST("Mega Evolution affects turn order")
{
GIVEN {
ASSUME(B_MEGA_EVO_TURN_ORDER);
PLAYER(SPECIES_DIANCIE) { Item(ITEM_DIANCITE); Speed(105); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(106); }
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); }
} SCENE {
MESSAGE("Diancie used Celebrate!");
MESSAGE("Foe Wobbuffet used Celebrate!");
} THEN {
ASSUME(player->speed == 225);
}
}
SINGLE_BATTLE_TEST("Abilities replaced by Mega Evolution do not affect turn order")
{
GIVEN {
ASSUME(B_MEGA_EVO_TURN_ORDER);
ASSUME(gSpeciesInfo[SPECIES_SABLEYE_MEGA].abilities[0] != ABILITY_STALL
&& gSpeciesInfo[SPECIES_SABLEYE_MEGA].abilities[1] != ABILITY_STALL);
PLAYER(SPECIES_SABLEYE) { Item(ITEM_SABLENITE); Ability(ABILITY_STALL); Speed(105); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(44); }
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); }
} SCENE {
MESSAGE("Sableye used Celebrate!");
MESSAGE("Foe Wobbuffet used Celebrate!");
} THEN {
ASSUME(player->speed == 45);
}
}

158
test/move.c Normal file
View File

@ -0,0 +1,158 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Accuracy controls the proportion of misses")
{
u32 move;
PARAMETRIZE { move = MOVE_DYNAMIC_PUNCH; }
PARAMETRIZE { move = MOVE_THUNDER; }
PARAMETRIZE { move = MOVE_HYDRO_PUMP; }
PARAMETRIZE { move = MOVE_RAZOR_LEAF; }
PARAMETRIZE { move = MOVE_SCRATCH; }
ASSUME(0 < gBattleMoves[move].accuracy && gBattleMoves[move].accuracy <= 100);
PASSES_RANDOMLY(gBattleMoves[move].accuracy, 100);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, move, player);
}
}
SINGLE_BATTLE_TEST("Secondary Effect Chance controls the proportion of secondary effects")
{
u32 move;
PARAMETRIZE { move = MOVE_THUNDER_SHOCK; }
PARAMETRIZE { move = MOVE_DISCHARGE; }
PARAMETRIZE { move = MOVE_NUZZLE; }
ASSUME(gBattleMoves[move].accuracy == 100);
ASSUME(gBattleMoves[move].effect == EFFECT_PARALYZE_HIT);
ASSUME(0 < gBattleMoves[move].secondaryEffectChance && gBattleMoves[move].secondaryEffectChance <= 100);
PASSES_RANDOMLY(gBattleMoves[move].secondaryEffectChance, 100);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, move); }
} SCENE {
STATUS_ICON(opponent, paralysis: TRUE);
}
}
SINGLE_BATTLE_TEST("Turn order is determined by priority")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_QUICK_ATTACK].priority > gBattleMoves[MOVE_TACKLE].priority);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_QUICK_ATTACK); MOVE(opponent, MOVE_TACKLE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
}
}
SINGLE_BATTLE_TEST("Turn order is determined by speed if priority ties")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(2); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(1); }
} WHEN {
TURN { MOVE(player, MOVE_QUICK_ATTACK); MOVE(opponent, MOVE_QUICK_ATTACK); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, opponent);
}
}
SINGLE_BATTLE_TEST("Turn order is determined randomly if priority and speed tie")
{
PASSES_RANDOMLY(1, 2);
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(1); }
} WHEN {
TURN { MOVE(player, MOVE_QUICK_ATTACK); MOVE(opponent, MOVE_QUICK_ATTACK); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, opponent);
}
}
SINGLE_BATTLE_TEST("Critical hits occur at a 1/24 rate")
{
ASSUME(B_CRIT_CHANCE >= GEN_7);
ASSUME(gBattleMoves[MOVE_SCRATCH].accuracy == 100);
PASSES_RANDOMLY(100 / 24, 100);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE {
MESSAGE("It's a critical hit!");
}
}
SINGLE_BATTLE_TEST("Critical hits deal 50% more damage", s16 damage)
{
bool32 criticalHit;
PARAMETRIZE { criticalHit = FALSE; }
PARAMETRIZE { criticalHit = TRUE; }
GIVEN {
ASSUME(B_CRIT_MULTIPLIER >= GEN_6);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH, criticalHit: criticalHit); }
} SCENE {
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("Critical hits do not ignore positive stat stages", s16 damage)
{
u32 move;
PARAMETRIZE { move = MOVE_CELEBRATE; }
PARAMETRIZE { move = MOVE_HOWL; }
PARAMETRIZE { move = MOVE_TAIL_WHIP; }
GIVEN {
ASSUME(gBattleMoves[MOVE_SCRATCH].split == SPLIT_PHYSICAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, move); }
TURN { MOVE(player, MOVE_SCRATCH, criticalHit: TRUE); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} THEN {
if (i > 0)
EXPECT_LT(results[0].damage, results[i].damage);
}
}
SINGLE_BATTLE_TEST("Critical hits ignore negative stat stages", s16 damage)
{
u32 move;
PARAMETRIZE { move = MOVE_CELEBRATE; }
PARAMETRIZE { move = MOVE_HARDEN; }
PARAMETRIZE { move = MOVE_GROWL; }
GIVEN {
ASSUME(gBattleMoves[MOVE_SCRATCH].split == SPLIT_PHYSICAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, move); }
TURN { MOVE(player, MOVE_SCRATCH, criticalHit: TRUE); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} THEN {
if (i > 0)
EXPECT_EQ(results[0].damage, results[i].damage);
}
}

41
test/move_effect_absorb.c Normal file
View File

@ -0,0 +1,41 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_ABSORB].effect == EFFECT_ABSORB);
}
SINGLE_BATTLE_TEST("Absorb recovers 50% of the damage dealt")
{
s16 damage;
s16 healed;
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_ABSORB); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player);
HP_BAR(opponent, captureDamage: &damage);
HP_BAR(player, captureDamage: &healed);
} THEN {
EXPECT_MUL_EQ(damage, Q_4_12(-0.5), healed);
}
}
SINGLE_BATTLE_TEST("Absorb fails if Heal Block applies")
{
ASSUME(B_HEAL_BLOCKING >= GEN_6);
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_HEAL_BLOCK); MOVE(player, MOVE_ABSORB); }
} SCENE {
MESSAGE("Wobbuffet was prevented from healing!");
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player);
NOT HP_BAR(opponent);
NOT HP_BAR(player);
}
}

View File

@ -0,0 +1,24 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_SAND_ATTACK].effect == EFFECT_ACCURACY_DOWN);
}
SINGLE_BATTLE_TEST("Sand Attack lowers Accuracy")
{
ASSUME(gBattleMoves[MOVE_SCRATCH].accuracy == 100);
PASSES_RANDOMLY(gBattleMoves[MOVE_SCRATCH].accuracy * 3 / 4, 100);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SAND_ATTACK); MOVE(opponent, MOVE_SCRATCH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Foe Wobbuffet's accuracy fell!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent);
}
}

View File

@ -0,0 +1,54 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_AFTER_YOU].effect == EFFECT_AFTER_YOU);
}
DOUBLE_BATTLE_TEST("After You makes the target move after user")
{
if (B_RECALC_TURN_AFTER_ACTIONS >= GEN_8) KNOWN_FAILING; // #2615.
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(4); }
PLAYER(SPECIES_WYNAUT) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(3); }
OPPONENT(SPECIES_WYNAUT) { Speed(2); }
} WHEN {
TURN {
MOVE(playerLeft, MOVE_AFTER_YOU, target: playerRight);
MOVE(playerRight, MOVE_CELEBRATE);
MOVE(opponentLeft, MOVE_CELEBRATE);
MOVE(opponentRight, MOVE_CELEBRATE);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_AFTER_YOU, playerLeft);
MESSAGE("Wynaut took the kind offer!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight);
}
}
DOUBLE_BATTLE_TEST("After You does nothing if the target has already moved")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(4); }
PLAYER(SPECIES_WYNAUT) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(3); }
OPPONENT(SPECIES_WYNAUT) { Speed(2); }
} WHEN {
TURN {
MOVE(playerLeft, MOVE_CELEBRATE);
MOVE(playerRight, MOVE_CELEBRATE);
MOVE(opponentLeft, MOVE_CELEBRATE);
MOVE(opponentRight, MOVE_AFTER_YOU, target: opponentLeft);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft);
MESSAGE("Foe Wynaut used After You!");
MESSAGE("But it failed!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight);
}
}

View File

@ -0,0 +1,32 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_GROWL].effect == EFFECT_ATTACK_DOWN);
}
SINGLE_BATTLE_TEST("Growl lowers Attack", s16 damage)
{
bool32 lowerAttack;
PARAMETRIZE { lowerAttack = FALSE; }
PARAMETRIZE { lowerAttack = TRUE; }
GIVEN {
ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (lowerAttack) TURN { MOVE(player, MOVE_GROWL); }
TURN { MOVE(opponent, MOVE_TACKLE); }
} SCENE {
if (lowerAttack) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Foe Wobbuffet's attack fell!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.5), results[0].damage);
}
}

View File

@ -0,0 +1,32 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_MEDITATE].effect == EFFECT_ATTACK_UP);
}
SINGLE_BATTLE_TEST("Meditate raises Attack", s16 damage)
{
bool32 raiseAttack;
PARAMETRIZE { raiseAttack = FALSE; }
PARAMETRIZE { raiseAttack = TRUE; }
GIVEN {
ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (raiseAttack) TURN { MOVE(player, MOVE_MEDITATE); }
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
if (raiseAttack) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_MEDITATE, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's attack rose!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);
}
}

34
test/move_effect_bide.c Normal file
View File

@ -0,0 +1,34 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_BIDE].effect == EFFECT_BIDE);
}
SINGLE_BATTLE_TEST("Bide deals twice the taken damage over two turns")
{
s16 damage1;
s16 damage2;
s16 bideDamage;
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_BIDE); MOVE(opponent, MOVE_TACKLE); }
TURN { SKIP_TURN(player); MOVE(opponent, MOVE_TACKLE); }
TURN { SKIP_TURN(player); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_BIDE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &damage1);
MESSAGE("Wobbuffet is storing energy!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &damage2);
MESSAGE("Wobbuffet unleashed energy!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_BIDE, player);
HP_BAR(opponent, captureDamage: &bideDamage);
} FINALLY {
EXPECT_EQ(bideDamage, damage1 + damage2);
}
}

View File

@ -0,0 +1,38 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_EMBER].effect == EFFECT_BURN_HIT);
}
SINGLE_BATTLE_TEST("Ember inflicts burn")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_EMBER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponent);
STATUS_ICON(opponent, burn: TRUE);
}
}
SINGLE_BATTLE_TEST("Ember cannot burn a Fire-type")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_CHARMANDER].types[0] == TYPE_FIRE);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_CHARMANDER);
} WHEN {
TURN { MOVE(player, MOVE_EMBER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player);
HP_BAR(opponent);
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponent);
NOT STATUS_ICON(opponent, burn: TRUE);
}
}

View File

@ -0,0 +1,32 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_TAIL_WHIP].effect == EFFECT_DEFENSE_DOWN);
}
SINGLE_BATTLE_TEST("Tail Whip lowers Defense", s16 damage)
{
bool32 lowerDefense;
PARAMETRIZE { lowerDefense = FALSE; }
PARAMETRIZE { lowerDefense = TRUE; }
GIVEN {
ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (lowerDefense) TURN { MOVE(player, MOVE_TAIL_WHIP); }
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
if (lowerDefense) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TAIL_WHIP, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Foe Wobbuffet's defense fell!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);
}
}

View File

@ -0,0 +1,32 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_HARDEN].effect == EFFECT_DEFENSE_UP);
}
SINGLE_BATTLE_TEST("Harden raises Defense", s16 damage)
{
bool32 raiseDefense;
PARAMETRIZE { raiseDefense = FALSE; }
PARAMETRIZE { raiseDefense = TRUE; }
GIVEN {
ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (raiseDefense) TURN { MOVE(player, MOVE_HARDEN); }
TURN { MOVE(opponent, MOVE_TACKLE); }
} SCENE {
if (raiseDefense) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's defense rose!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.5), results[0].damage);
}
}

View File

@ -0,0 +1,54 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_DREAM_EATER].effect == EFFECT_DREAM_EATER);
}
SINGLE_BATTLE_TEST("Dream Eater recovers 50% of the damage dealt")
{
s16 damage;
s16 healed;
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); }
} WHEN {
TURN { MOVE(player, MOVE_DREAM_EATER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, player);
HP_BAR(opponent, captureDamage: &damage);
HP_BAR(player, captureDamage: &healed);
} THEN {
EXPECT_MUL_EQ(damage, Q_4_12(-1.0/2.0), healed);
}
}
SINGLE_BATTLE_TEST("Dream Eater fails on awake targets")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_DREAM_EATER); }
} SCENE {
MESSAGE("Wobbuffet used Dream Eater!");
MESSAGE("Foe Wobbuffet wasn't affected!");
}
}
SINGLE_BATTLE_TEST("Dream Eater fails if Heal Block applies")
{
ASSUME(B_HEAL_BLOCKING >= GEN_6);
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_HEAL_BLOCK); MOVE(player, MOVE_DREAM_EATER); }
} SCENE {
MESSAGE("Wobbuffet was prevented from healing!");
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, player);
NOT HP_BAR(opponent);
NOT HP_BAR(player);
}
}

54
test/move_effect_encore.c Normal file
View File

@ -0,0 +1,54 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_ENCORE].effect == EFFECT_ENCORE);
}
SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 2 turns")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_ENCORE); }
TURN { FORCED_MOVE(player); }
TURN { FORCED_MOVE(player); }
TURN { MOVE(player, MOVE_SPLASH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, player);
}
}
SINGLE_BATTLE_TEST("Encore has no effect if no previous move")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_ENCORE); MOVE(player, MOVE_CELEBRATE); }
} SCENE {
MESSAGE("Foe Wobbuffet used Encore!");
MESSAGE("But it failed!");
}
}
SINGLE_BATTLE_TEST("Encore overrides the chosen move if it occurs first")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(opponent, MOVE_ENCORE); MOVE(player, MOVE_SPLASH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
}
}

View File

@ -0,0 +1,24 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_DOUBLE_TEAM].effect == EFFECT_EVASION_UP);
}
SINGLE_BATTLE_TEST("Double Team raises Evasion")
{
ASSUME(gBattleMoves[MOVE_SCRATCH].accuracy == 100);
PASSES_RANDOMLY(gBattleMoves[MOVE_SCRATCH].accuracy * 3 / 4, 100);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_DOUBLE_TEAM); MOVE(opponent, MOVE_SCRATCH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_TEAM, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's evasiveness rose!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent);
}
}

View File

@ -0,0 +1,53 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION);
}
SINGLE_BATTLE_TEST("Explosion causes the user to faint")
{
u16 remainingHP;
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_EXPLOSION); }
} SCENE {
HP_BAR(player, hp: 0);
ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player);
}
}
SINGLE_BATTLE_TEST("Explosion causes the user to faint even if it misses")
{
u16 remainingHP;
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_EXPLOSION, hit: FALSE); }
} SCENE {
HP_BAR(player, hp: 0);
ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player);
}
}
SINGLE_BATTLE_TEST("Explosion causes the user to faint even if it has no effect")
{
u16 remainingHP;
GIVEN {
ASSUME(gBattleMoves[MOVE_EXPLOSION].type == TYPE_NORMAL);
ASSUME(gSpeciesInfo[SPECIES_GASTLY].types[0] == TYPE_GHOST);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GASTLY);
} WHEN {
TURN { MOVE(player, MOVE_EXPLOSION); }
} SCENE {
HP_BAR(player, hp: 0);
ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player);
MESSAGE("It doesn't affect Foe Gastly…");
NOT HP_BAR(opponent);
}
}

View File

@ -0,0 +1,38 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_POWDER_SNOW].effect == EFFECT_FREEZE_HIT);
}
SINGLE_BATTLE_TEST("Powder Snow inflicts freeze")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_POWDER_SNOW); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER_SNOW, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent);
STATUS_ICON(opponent, freeze: TRUE);
}
}
SINGLE_BATTLE_TEST("Powder Snow cannot freeze an Ice-type")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_SNORUNT].types[0] == TYPE_ICE);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_SNORUNT);
} WHEN {
TURN { MOVE(player, MOVE_POWDER_SNOW); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER_SNOW, player);
HP_BAR(opponent);
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_FRZ, opponent);
NOT STATUS_ICON(opponent, freeze: TRUE);
}
}

32
test/move_effect_haze.c Normal file
View File

@ -0,0 +1,32 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_HAZE].effect == EFFECT_HAZE);
}
SINGLE_BATTLE_TEST("Haze resets stat changes", s16 damage)
{
bool32 haze;
PARAMETRIZE { haze = FALSE; }
PARAMETRIZE { haze = TRUE; }
GIVEN {
ASSUME(gBattleMoves[MOVE_MEDITATE].effect == EFFECT_ATTACK_UP);
ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (haze) TURN { MOVE(player, MOVE_MEDITATE); MOVE(opponent, MOVE_HAZE); }
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
if (haze) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_HAZE, opponent);
MESSAGE("All stat changes were eliminated!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_EQ(results[0].damage, results[1].damage);
}
}

33
test/move_effect_hex.c Normal file
View File

@ -0,0 +1,33 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_HEX].effect == EFFECT_HEX);
}
SINGLE_BATTLE_TEST("Hex deals double damage to foes with a status", s16 damage)
{
u32 status1;
PARAMETRIZE { status1 = STATUS1_NONE; }
PARAMETRIZE { status1 = STATUS1_SLEEP; }
PARAMETRIZE { status1 = STATUS1_POISON; }
PARAMETRIZE { status1 = STATUS1_BURN; }
PARAMETRIZE { status1 = STATUS1_FREEZE; }
PARAMETRIZE { status1 = STATUS1_PARALYSIS; }
PARAMETRIZE { status1 = STATUS1_TOXIC_POISON; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Status1(status1); }
} WHEN {
TURN { MOVE(player, MOVE_HEX); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_HEX, player);
HP_BAR(opponent, captureDamage: &results[i].damage);
} THEN {
if (i > 0)
EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[i].damage);
if (i > 1)
EXPECT_EQ(results[i-1].damage, results[i].damage);
}
}

View File

@ -0,0 +1,96 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE);
}
SINGLE_BATTLE_TEST("U-turn switches the user out")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player);
HP_BAR(opponent);
MESSAGE("Go! Wynaut!");
}
}
SINGLE_BATTLE_TEST("U-turn does not switch the user out if the battle ends")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET) { HP(1); }
} WHEN {
TURN { MOVE(player, MOVE_U_TURN); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player);
HP_BAR(opponent);
}
}
SINGLE_BATTLE_TEST("U-turn does not switch the user out if no replacements")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_U_TURN); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player);
HP_BAR(opponent);
}
}
SINGLE_BATTLE_TEST("U-turn does not switch the user out if replacements fainted")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT) { HP(0); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_U_TURN); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player);
HP_BAR(opponent);
}
}
SINGLE_BATTLE_TEST("U-turn does not switch the user out if Wimp Out activates")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WIMPOD) { MaxHP(100); HP(51); Ability(ABILITY_WIMP_OUT); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(opponent, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player);
HP_BAR(opponent);
ABILITY_POPUP(opponent, ABILITY_WIMP_OUT);
MESSAGE("2 sent out Wobbuffet!");
}
}
SINGLE_BATTLE_TEST("U-turn switches the user out if Wimp Out fails to activate")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WIMPOD) { MaxHP(100); HP(51); Ability(ABILITY_WIMP_OUT); }
} WHEN {
TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player);
HP_BAR(opponent);
NOT ABILITY_POPUP(opponent);
MESSAGE("Your foe's weak! Get 'em, Wynaut!");
}
}

View File

@ -0,0 +1,39 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_THUNDER_SHOCK].effect == EFFECT_PARALYZE_HIT);
}
SINGLE_BATTLE_TEST("Thunder Shock inflicts paralysis")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_THUNDER_SHOCK); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_SHOCK, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent);
STATUS_ICON(opponent, paralysis: TRUE);
}
}
SINGLE_BATTLE_TEST("Thunder Shock cannot paralyze an Electric-type")
{
GIVEN {
ASSUME(B_PARALYZE_ELECTRIC >= GEN_6);
ASSUME(gSpeciesInfo[SPECIES_PIKACHU].types[0] == TYPE_ELECTRIC);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_PIKACHU);
} WHEN {
TURN { MOVE(player, MOVE_THUNDER_SHOCK); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_SHOCK, player);
HP_BAR(opponent);
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent);
NOT STATUS_ICON(opponent, paralysis: TRUE);
}
}

View File

@ -0,0 +1,39 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_POISON_STING].effect == EFFECT_POISON_HIT);
}
SINGLE_BATTLE_TEST("Poison Sting inflicts poison")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_POISON_STING); }
TURN {}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent);
STATUS_ICON(opponent, poison: TRUE);
}
}
SINGLE_BATTLE_TEST("Poison Sting cannot poison Poison-type")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_NIDORAN_M].types[0] == TYPE_POISON);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_NIDORAN_M);
} WHEN {
TURN { MOVE(player, MOVE_POISON_STING); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, player);
HP_BAR(opponent);
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent);
NOT STATUS_ICON(opponent, poison: TRUE);
}
}

View File

@ -0,0 +1,91 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_THRASH].effect == EFFECT_RAMPAGE);
}
SINGLE_BATTLE_TEST("Thrash lasts for 2 or 3 turns")
{
PASSES_RANDOMLY(1, 2);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_THRASH); }
TURN { SKIP_TURN(player); }
TURN { SKIP_TURN(player); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player);
}
}
SINGLE_BATTLE_TEST("Thrash confuses the user after it finishes")
{
GIVEN {
RNGSeed(0x00000000);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_THRASH); }
TURN { SKIP_TURN(player); }
TURN { SKIP_TURN(player); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_THRASH, player);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player);
}
}
SINGLE_BATTLE_TEST("Thrash does not confuse the user if it is canceled on turn 1 of 3")
{
GIVEN {
ASSUME(B_RAMPAGE_CANCELLING >= GEN_5);
RNGSeed(0x00000000);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_THRASH); }
TURN { MOVE(opponent, MOVE_PROTECT); SKIP_TURN(player); }
TURN { SKIP_TURN(player); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player);
}
}
SINGLE_BATTLE_TEST("Thrash does not confuse the user if it is canceled on turn 2 of 3")
{
GIVEN {
ASSUME(B_RAMPAGE_CANCELLING >= GEN_5);
RNGSeed(0x00000000);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_THRASH); }
TURN { MOVE(opponent, MOVE_PROTECT); SKIP_TURN(player); }
TURN { SKIP_TURN(player); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player);
}
}
SINGLE_BATTLE_TEST("Thrash confuses the user if it is canceled on turn 3 of 3")
{
KNOWN_FAILING;
GIVEN {
ASSUME(B_RAMPAGE_CANCELLING >= GEN_5);
RNGSeed(0x00000000);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_THRASH); }
TURN { SKIP_TURN(player); }
TURN { MOVE(opponent, MOVE_PROTECT); SKIP_TURN(player); }
} SCENE {
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player);
}
}

View File

@ -0,0 +1,57 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_JUMP_KICK].effect == EFFECT_RECOIL_IF_MISS);
}
SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on miss")
{
s16 recoil;
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_JUMP_KICK, hit: FALSE); }
} SCENE {
s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP);
MESSAGE("Wobbuffet used Jump Kick!");
MESSAGE("Wobbuffet's attack missed!");
MESSAGE("Wobbuffet kept going and crashed!");
HP_BAR(player, damage: maxHP / 2);
}
}
SINGLE_BATTLE_TEST("Jump Kick has 50% recoil on protect")
{
s16 recoil;
GIVEN {
ASSUME(gBattleMoves[MOVE_JUMP_KICK].flags & FLAG_PROTECT_AFFECTED);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_JUMP_KICK, hit: FALSE); }
} SCENE {
s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP);
ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_JUMP_KICK, player);
HP_BAR(player, damage: maxHP / 2);
}
}
SINGLE_BATTLE_TEST("Jump Kick has no recoil if no target")
{
KNOWN_FAILING; // #2596.
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(opponent, MOVE_HEALING_WISH); MOVE(player, MOVE_JUMP_KICK, hit: FALSE); SEND_OUT(opponent, 1); }
} SCENE {
s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP);
ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, opponent);
NOT HP_BAR(player, damage: maxHP / 2);
}
}

View File

@ -0,0 +1,77 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_REFLECT].effect == EFFECT_REFLECT);
}
SINGLE_BATTLE_TEST("Reflect reduces physical damage", s16 damage)
{
u32 move;
PARAMETRIZE { move = MOVE_CELEBRATE; }
PARAMETRIZE { move = MOVE_REFLECT; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, move); MOVE(opponent, MOVE_TACKLE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, move, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_LT(results[1].damage, results[0].damage);
}
}
SINGLE_BATTLE_TEST("Reflect applies for 5 turns")
{
u16 damage[6];
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_REFLECT); MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(opponent, MOVE_TACKLE); }
TURN { MOVE(opponent, MOVE_TACKLE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &damage[1]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &damage[2]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &damage[3]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &damage[4]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
HP_BAR(player, captureDamage: &damage[5]);
} THEN {
EXPECT_MUL_EQ(damage[0], Q_4_12(1.0), damage[1]);
EXPECT_MUL_EQ(damage[0], Q_4_12(1.0), damage[2]);
EXPECT_MUL_EQ(damage[0], Q_4_12(1.0), damage[3]);
EXPECT_MUL_EQ(damage[0], Q_4_12(1.0), damage[4]);
EXPECT_LT(damage[0], damage[5]);
}
}
SINGLE_BATTLE_TEST("Reflect fails if already active")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_REFLECT); }
TURN { MOVE(player, MOVE_REFLECT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT, player);
MESSAGE("Wobbuffet used Reflect!");
MESSAGE("But it failed!");
}
}

21
test/move_effect_sleep.c Normal file
View File

@ -0,0 +1,21 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_HYPNOSIS].effect == EFFECT_SLEEP);
}
SINGLE_BATTLE_TEST("Hypnosis inflicts sleep")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_HYPNOSIS); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPNOSIS, player);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent);
STATUS_ICON(opponent, sleep: TRUE);
}
}

View File

@ -0,0 +1,32 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_CONFIDE].effect == EFFECT_SPECIAL_ATTACK_DOWN);
}
SINGLE_BATTLE_TEST("Confide lowers Special Attack", s16 damage)
{
bool32 lowerSpecialAttack;
PARAMETRIZE { lowerSpecialAttack = FALSE; }
PARAMETRIZE { lowerSpecialAttack = TRUE; }
GIVEN {
ASSUME(gBattleMoves[MOVE_GUST].split == SPLIT_SPECIAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (lowerSpecialAttack) TURN { MOVE(player, MOVE_CONFIDE); }
TURN { MOVE(opponent, MOVE_GUST); }
} SCENE {
if (lowerSpecialAttack) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFIDE, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Foe Wobbuffet's sp. attack fell!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_GUST, opponent);
HP_BAR(player, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.5), results[0].damage);
}
}

View File

@ -0,0 +1,32 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_TAIL_GLOW].effect == EFFECT_SPECIAL_ATTACK_UP_3);
}
SINGLE_BATTLE_TEST("Tail Glow drastically raises Special Attack", s16 damage)
{
bool32 raiseSpecialAttack;
PARAMETRIZE { raiseSpecialAttack = FALSE; }
PARAMETRIZE { raiseSpecialAttack = TRUE; }
GIVEN {
ASSUME(gBattleMoves[MOVE_GUST].split == SPLIT_SPECIAL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (raiseSpecialAttack) TURN { MOVE(player, MOVE_TAIL_GLOW); }
TURN { MOVE(player, MOVE_GUST); }
} SCENE {
if (raiseSpecialAttack) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TAIL_GLOW, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's sp. attack drastically rose!");
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_GUST, player);
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.5), results[1].damage);
}
}

135
test/move_effect_spikes.c Normal file
View File

@ -0,0 +1,135 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_SPIKES].effect == EFFECT_SPIKES);
}
SINGLE_BATTLE_TEST("Spikes damage on switch in")
{
u32 layers;
u32 divisor;
PARAMETRIZE { layers = 1; divisor = 8; }
PARAMETRIZE { layers = 2; divisor = 6; }
PARAMETRIZE { layers = 3; divisor = 4; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
u32 count;
for (count = 0; count < layers; ++count) {
TURN { MOVE(player, MOVE_SPIKES); }
}
TURN { SWITCH(opponent, 1); }
} SCENE {
u32 count;
s32 maxHP = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP);
for (count = 0; count < layers; ++count) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, player);
MESSAGE("Spikes were scattered all around the opponent's side!");
}
MESSAGE("2 sent out Wynaut!");
HP_BAR(opponent, damage: maxHP / divisor);
MESSAGE("Foe Wynaut is hurt by spikes!");
}
}
SINGLE_BATTLE_TEST("Spikes fails after 3 layers")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_SPIKES); }
TURN { MOVE(player, MOVE_SPIKES); }
TURN { MOVE(player, MOVE_SPIKES); }
TURN { MOVE(player, MOVE_SPIKES); }
TURN { SWITCH(opponent, 1); }
} SCENE {
s32 maxHP = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, player);
MESSAGE("Spikes were scattered all around the opponent's side!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, player);
MESSAGE("Spikes were scattered all around the opponent's side!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, player);
MESSAGE("Spikes were scattered all around the opponent's side!");
MESSAGE("Wobbuffet used Spikes!");
MESSAGE("But it failed!");
MESSAGE("2 sent out Wynaut!");
HP_BAR(opponent, damage: maxHP / 4);
MESSAGE("Foe Wynaut is hurt by spikes!");
}
}
SINGLE_BATTLE_TEST("Spikes damage on subsequent switch ins")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_SPIKES); }
TURN { SWITCH(opponent, 1); }
TURN { SWITCH(opponent, 0); }
} SCENE {
s32 maxHP0 = GetMonData(&OPPONENT_PARTY[0], MON_DATA_MAX_HP);
s32 maxHP1 = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP);
MESSAGE("2 sent out Wynaut!");
HP_BAR(opponent, damage: maxHP1 / 8);
MESSAGE("Foe Wynaut is hurt by spikes!");
MESSAGE("2 sent out Wobbuffet!");
HP_BAR(opponent, damage: maxHP0 / 8);
MESSAGE("Foe Wobbuffet is hurt by spikes!");
}
}
SINGLE_BATTLE_TEST("Spikes do not damage airborne Pokemon")
{
u32 species = SPECIES_WOBBUFFET;
u32 item = ITEM_NONE;
u32 move1 = MOVE_CELEBRATE;
u32 move2 = MOVE_CELEBRATE;
bool32 airborne;
ASSUME(gSpeciesInfo[SPECIES_PIDGEY].types[1] == TYPE_FLYING);
PARAMETRIZE { species = SPECIES_PIDGEY; airborne = TRUE; }
PARAMETRIZE { species = SPECIES_PIDGEY; item = ITEM_IRON_BALL; airborne = FALSE; }
PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_GRAVITY; airborne = FALSE; }
PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_INGRAIN; airborne = FALSE; }
ASSUME(gSpeciesInfo[SPECIES_UNOWN].abilities[0] == ABILITY_LEVITATE);
PARAMETRIZE { species = SPECIES_UNOWN; airborne = TRUE; }
PARAMETRIZE { species = SPECIES_UNOWN; item = ITEM_IRON_BALL; airborne = FALSE; }
PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_GRAVITY; airborne = FALSE; }
PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_INGRAIN; airborne = FALSE; }
PARAMETRIZE { move1 = MOVE_MAGNET_RISE; airborne = TRUE; }
PARAMETRIZE { move1 = MOVE_MAGNET_RISE; item = ITEM_IRON_BALL; airborne = FALSE; }
PARAMETRIZE { move1 = MOVE_MAGNET_RISE; move2 = MOVE_GRAVITY; airborne = FALSE; }
// Magnet Rise fails under Gravity.
// Magnet Rise fails under Ingrain and vice-versa.
PARAMETRIZE { item = ITEM_AIR_BALLOON; airborne = TRUE; }
PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_GRAVITY; airborne = FALSE; }
PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_INGRAIN; airborne = FALSE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(species) { Item(item); }
} WHEN {
TURN { MOVE(player, MOVE_SPIKES); MOVE(opponent, move1); }
TURN { MOVE(opponent, move2); }
TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); }
} SCENE {
s32 maxHP = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP);
if (airborne) {
NOT HP_BAR(opponent, damage: maxHP / 8);
} else {
HP_BAR(opponent, damage: maxHP / 8);
}
}
}

View File

@ -0,0 +1,55 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_TAILWIND].effect == EFFECT_TAILWIND);
}
SINGLE_BATTLE_TEST("Tailwind applies for 4 turns")
{
GIVEN {
ASSUME(B_TAILWIND_TURNS >= GEN_5);
PLAYER(SPECIES_WOBBUFFET) { Speed(10); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(15); }
} WHEN {
TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_TAILWIND); }
TURN {}
TURN {}
TURN {}
TURN {}
} SCENE {
MESSAGE("Foe Wobbuffet used Celebrate!");
MESSAGE("Wobbuffet used Tailwind!");
MESSAGE("Wobbuffet used Celebrate!");
MESSAGE("Foe Wobbuffet used Celebrate!");
MESSAGE("Wobbuffet used Celebrate!");
MESSAGE("Foe Wobbuffet used Celebrate!");
MESSAGE("Wobbuffet used Celebrate!");
MESSAGE("Foe Wobbuffet used Celebrate!");
MESSAGE("Foe Wobbuffet used Celebrate!");
MESSAGE("Wobbuffet used Celebrate!");
}
}
DOUBLE_BATTLE_TEST("Tailwind affects partner on first turn")
{
GIVEN {
ASSUME(B_RECALC_TURN_AFTER_ACTIONS);
PLAYER(SPECIES_WOBBUFFET) { Speed(20); }
PLAYER(SPECIES_WYNAUT) { Speed(10); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(15); }
OPPONENT(SPECIES_WYNAUT) { Speed(14); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_TAILWIND); }
} SCENE {
MESSAGE("Wobbuffet used Tailwind!");
MESSAGE("Wynaut used Celebrate!");
MESSAGE("Foe Wobbuffet used Celebrate!");
MESSAGE("Foe Wynaut used Celebrate!");
}
}

View File

@ -0,0 +1,53 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_TORMENT].effect == EFFECT_TORMENT);
}
SINGLE_BATTLE_TEST("Torment prevents consecutive move uses")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, MOVE_CELEBRATE); }
} WHEN {
TURN { MOVE(player, MOVE_TORMENT); MOVE(opponent, MOVE_SPLASH); }
TURN { MOVE(opponent, MOVE_SPLASH, allowed: FALSE); MOVE(opponent, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TORMENT, player);
MESSAGE("Foe Wobbuffet was subjected to torment!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
}
}
SINGLE_BATTLE_TEST("Torment forces Struggle if the only move is prevented")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH); }
} WHEN {
TURN { MOVE(player, MOVE_TORMENT); MOVE(opponent, MOVE_SPLASH); }
TURN { MOVE(opponent, MOVE_SPLASH, allowed: FALSE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, opponent);
}
}
SINGLE_BATTLE_TEST("Torment allows non-consecutive move uses")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_TORMENT); MOVE(opponent, MOVE_SPLASH); }
TURN { MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(opponent, MOVE_SPLASH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponent);
}
}

48
test/move_effect_toxic.c Normal file
View File

@ -0,0 +1,48 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_TOXIC].effect == EFFECT_TOXIC);
}
SINGLE_BATTLE_TEST("Toxic inflicts bad poison")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_TOXIC); }
TURN {}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent);
STATUS_ICON(opponent, badPoison: TRUE);
}
}
SINGLE_BATTLE_TEST("Toxic cannot miss if used by a Poison-type")
{
u32 species;
bool32 hit;
PARAMETRIZE { species = SPECIES_WOBBUFFET; hit = FALSE; }
PARAMETRIZE { species = SPECIES_NIDORAN_M; hit = TRUE; }
GIVEN {
ASSUME(B_TOXIC_NEVER_MISS >= GEN_6);
ASSUME(gSpeciesInfo[SPECIES_NIDORAN_M].types[0] == TYPE_POISON);
PLAYER(species);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_TOXIC, hit: FALSE); }
} SCENE {
if (hit) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent);
STATUS_ICON(opponent, badPoison: TRUE);
} else {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player);
NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent);
NOT STATUS_ICON(opponent, badPoison: TRUE);
}
}
}

View File

@ -0,0 +1,210 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_TOXIC_SPIKES].effect == EFFECT_TOXIC_SPIKES);
}
SINGLE_BATTLE_TEST("Toxic Spikes inflicts poison on switch in")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); }
TURN { SWITCH(opponent, 1); }
TURN {}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, player);
MESSAGE("Poison Spikes were scattered all around the opposing team's feet!");
MESSAGE("2 sent out Wynaut!");
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent);
STATUS_ICON(opponent, poison: TRUE);
}
}
SINGLE_BATTLE_TEST("Toxic Spikes inflicts bad poison on switch in")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); }
TURN { MOVE(player, MOVE_TOXIC_SPIKES); }
TURN { SWITCH(opponent, 1); }
TURN {}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, player);
MESSAGE("Poison Spikes were scattered all around the opposing team's feet!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, player);
MESSAGE("Poison Spikes were scattered all around the opposing team's feet!");
MESSAGE("2 sent out Wynaut!");
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent);
STATUS_ICON(opponent, badPoison: TRUE);
}
}
SINGLE_BATTLE_TEST("Toxic Spikes fails after 2 layers")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); }
TURN { MOVE(player, MOVE_TOXIC_SPIKES); }
TURN { MOVE(player, MOVE_TOXIC_SPIKES); }
TURN { SWITCH(opponent, 1); }
TURN {}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, player);
MESSAGE("Poison Spikes were scattered all around the opposing team's feet!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, player);
MESSAGE("Poison Spikes were scattered all around the opposing team's feet!");
MESSAGE("Wobbuffet used Toxic Spikes!");
MESSAGE("But it failed!");
MESSAGE("2 sent out Wynaut!");
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent);
STATUS_ICON(opponent, badPoison: TRUE);
}
}
SINGLE_BATTLE_TEST("Toxic Spikes inflicts poison on subsequent switch ins")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); }
TURN { SWITCH(opponent, 1); }
TURN { SWITCH(opponent, 0); }
TURN {}
} SCENE {
MESSAGE("2 sent out Wynaut!");
STATUS_ICON(opponent, poison: TRUE);
}
}
SINGLE_BATTLE_TEST("Toxic Spikes do not poison airborne Pokemon")
{
u32 species = SPECIES_WOBBUFFET;
u32 item = ITEM_NONE;
u32 move1 = MOVE_CELEBRATE;
u32 move2 = MOVE_CELEBRATE;
bool32 airborne;
ASSUME(gSpeciesInfo[SPECIES_PIDGEY].types[1] == TYPE_FLYING);
PARAMETRIZE { species = SPECIES_PIDGEY; airborne = TRUE; }
PARAMETRIZE { species = SPECIES_PIDGEY; item = ITEM_IRON_BALL; airborne = FALSE; }
PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_GRAVITY; airborne = FALSE; }
PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_INGRAIN; airborne = FALSE; }
ASSUME(gSpeciesInfo[SPECIES_UNOWN].abilities[0] == ABILITY_LEVITATE);
PARAMETRIZE { species = SPECIES_UNOWN; airborne = TRUE; }
PARAMETRIZE { species = SPECIES_UNOWN; item = ITEM_IRON_BALL; airborne = FALSE; }
PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_GRAVITY; airborne = FALSE; }
PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_INGRAIN; airborne = FALSE; }
PARAMETRIZE { move1 = MOVE_MAGNET_RISE; airborne = TRUE; }
PARAMETRIZE { move1 = MOVE_MAGNET_RISE; item = ITEM_IRON_BALL; airborne = FALSE; }
PARAMETRIZE { move1 = MOVE_MAGNET_RISE; move2 = MOVE_GRAVITY; airborne = FALSE; }
// Magnet Rise fails under Gravity.
// Magnet Rise fails under Ingrain and vice-versa.
PARAMETRIZE { item = ITEM_AIR_BALLOON; airborne = TRUE; }
PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_GRAVITY; airborne = FALSE; }
PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_INGRAIN; airborne = FALSE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(species) { Item(item); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); MOVE(opponent, move1); }
TURN { MOVE(opponent, move2); }
TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); }
} SCENE {
if (airborne) {
NOT STATUS_ICON(opponent, poison: TRUE);
} else {
STATUS_ICON(opponent, poison: TRUE);
}
}
}
SINGLE_BATTLE_TEST("Toxic Spikes do not affect Steel-types")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_STEELIX].types[0] == TYPE_STEEL);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_STEELIX);
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); }
TURN { SWITCH(opponent, 1); }
} SCENE {
NOT STATUS_ICON(opponent, poison: TRUE);
}
}
SINGLE_BATTLE_TEST("Toxic Spikes are removed by grounded Poison-types")
{
u32 species;
u32 item = ITEM_NONE;
u32 move = MOVE_CELEBRATE;
bool32 grounded;
PARAMETRIZE { species = SPECIES_EKANS; grounded = TRUE; }
PARAMETRIZE { species = SPECIES_ZUBAT; grounded = FALSE; }
PARAMETRIZE { species = SPECIES_ZUBAT; item = ITEM_IRON_BALL; grounded = TRUE; }
PARAMETRIZE { species = SPECIES_ZUBAT; move = MOVE_GRAVITY; grounded = TRUE; }
PARAMETRIZE { species = SPECIES_ZUBAT; move = MOVE_INGRAIN; grounded = TRUE; }
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_EKANS].types[0] == TYPE_POISON);
ASSUME(gSpeciesInfo[SPECIES_ZUBAT].types[0] == TYPE_POISON);
ASSUME(gSpeciesInfo[SPECIES_ZUBAT].types[1] == TYPE_FLYING);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(species) { Item(item); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); MOVE(opponent, move); }
TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); }
TURN { SWITCH(opponent, 0); }
} SCENE {
if (grounded) {
NOT STATUS_ICON(opponent, poison: TRUE);
MESSAGE("The poison spikes disappeared from around the opposing team's feet!");
NOT STATUS_ICON(opponent, poison: TRUE);
} else {
NOT STATUS_ICON(opponent, poison: TRUE);
ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent);
STATUS_ICON(opponent, poison: TRUE);
}
}
}
// This would test for what I believe to be a bug in the mainline games.
// A Pokémon that gets passed magnet rise should still remove the Toxic
// Spikes even though it is airborne.
// The test currently fails, because we don't incorporate this bug.
SINGLE_BATTLE_TEST("Toxic Spikes are removed by Poison-types affected by Magnet Rise")
{
KNOWN_FAILING;
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_EKANS].types[0] == TYPE_POISON);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_EKANS);
} WHEN {
TURN { MOVE(opponent, MOVE_MAGNET_RISE); }
TURN { MOVE(player, MOVE_TOXIC_SPIKES); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); }
TURN { SWITCH(opponent, 0); }
} SCENE {
NOT STATUS_ICON(opponent, poison: TRUE);
MESSAGE("The poison spikes disappeared from around the opposing team's feet!");
NOT STATUS_ICON(opponent, poison: TRUE);
}
}

194
test/status1.c Normal file
View File

@ -0,0 +1,194 @@
#include "global.h"
#include "test_battle.h"
SINGLE_BATTLE_TEST("Sleep prevents the battler from using a move")
{
u32 turns;
PARAMETRIZE { turns = 1; }
PARAMETRIZE { turns = 2; }
PARAMETRIZE { turns = 3; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP_TURN(turns)); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
for (i = 0; i < turns; i++)
TURN { MOVE(player, MOVE_CELEBRATE); }
} SCENE {
for (i = 0; i < turns - 1; i++)
MESSAGE("Wobbuffet is fast asleep.");
MESSAGE("Wobbuffet woke up!");
STATUS_ICON(player, none: TRUE);
MESSAGE("Wobbuffet used Celebrate!");
}
}
SINGLE_BATTLE_TEST("Poison deals 1/8th damage per turn")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
for (i = 0; i < 4; i++)
TURN {}
} SCENE {
s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP);
for (i = 0; i < 4; i++)
HP_BAR(player, damage: maxHP / 8);
}
}
SINGLE_BATTLE_TEST("Burn deals 1/16th damage per turn")
{
GIVEN {
ASSUME(B_BURN_DAMAGE >= GEN_LATEST);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
for (i = 0; i < 4; i++)
TURN {}
} SCENE {
s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP);
for (i = 0; i < 4; i++)
HP_BAR(player, damage: maxHP / 16);
}
}
SINGLE_BATTLE_TEST("Burn reduces attack by 50%", s16 damage)
{
bool32 burned;
PARAMETRIZE { burned = FALSE; }
PARAMETRIZE { burned = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { if (burned) Status1(STATUS1_BURN); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage);
}
}
SINGLE_BATTLE_TEST("Freeze has a 20% chance of being thawed")
{
PASSES_RANDOMLY(20, 100);
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); }
} SCENE {
STATUS_ICON(player, none: TRUE);
}
}
SINGLE_BATTLE_TEST("Freeze is thawed by opponent's Fire-type attacks")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_EMBER); }
} SCENE {
MESSAGE("Wobbuffet is frozen solid!");
MESSAGE("Foe Wobbuffet used Ember!");
MESSAGE("Wobbuffet was defrosted!");
STATUS_ICON(player, none: TRUE);
}
}
SINGLE_BATTLE_TEST("Freeze is thawed by user's Flame Wheel")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_FLAME_WHEEL].flags & FLAG_THAW_USER);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_FLAME_WHEEL); }
} SCENE {
MESSAGE("Wobbuffet was defrosted by Flame Wheel!");
STATUS_ICON(player, none: TRUE);
MESSAGE("Wobbuffet used Flame Wheel!");
}
}
SINGLE_BATTLE_TEST("Paralysis reduces speed by 50%")
{
u16 playerSpeed;
bool32 playerFirst;
PARAMETRIZE { playerSpeed = 98; playerFirst = FALSE; }
PARAMETRIZE { playerSpeed = 102; playerFirst = TRUE; }
GIVEN {
ASSUME(B_PARALYSIS_SPEED >= GEN_7);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); Speed(playerSpeed); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(50); }
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); }
} SCENE {
if (playerFirst) {
ONE_OF {
MESSAGE("Wobbuffet used Celebrate!");
MESSAGE("Wobbuffet is paralyzed! It can't move!");
}
MESSAGE("Foe Wobbuffet used Celebrate!");
} else {
MESSAGE("Foe Wobbuffet used Celebrate!");
ONE_OF {
MESSAGE("Wobbuffet used Celebrate!");
MESSAGE("Wobbuffet is paralyzed! It can't move!");
}
}
}
}
SINGLE_BATTLE_TEST("Paralysis has a 25% chance of skipping the turn")
{
PASSES_RANDOMLY(25, 100);
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); }
} SCENE {
MESSAGE("Wobbuffet is paralyzed! It can't move!");
}
}
SINGLE_BATTLE_TEST("Bad poison deals 1/16th cumulative damage per turn")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_TOXIC_POISON); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
for (i = 0; i < 4; i++)
TURN {}
} SCENE {
s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP);
for (i = 0; i < 4; i++)
HP_BAR(player, damage: maxHP / 16 * (i + 1));
}
}
SINGLE_BATTLE_TEST("Bad poison cumulative damage resets on switch")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_TOXIC_POISON); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {}
TURN {}
TURN { SWITCH(player, 1); }
TURN { SWITCH(player, 0); }
TURN {}
TURN {}
} SCENE {
s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP);
for (i = 0; i < 2; i++)
HP_BAR(player, damage: maxHP / 16 * (i + 1));
for (i = 0; i < 2; i++)
HP_BAR(player, damage: maxHP / 16 * (i + 1));
}
}

142
test/test.h Normal file
View File

@ -0,0 +1,142 @@
#ifndef GUARD_TEST_H
#define GUARD_TEST_H
#include "test_runner.h"
#define MAX_PROCESSES 32 // See also tools/mgba-rom-test-hydra/main.c
enum TestResult
{
TEST_RESULT_FAIL,
TEST_RESULT_PASS,
TEST_RESULT_SKIP,
TEST_RESULT_INVALID,
TEST_RESULT_ERROR,
TEST_RESULT_TIMEOUT,
};
struct TestRunner
{
u32 (*estimateCost)(void *);
void (*setUp)(void *);
void (*run)(void *);
void (*tearDown)(void *);
bool32 (*checkProgress)(void *);
bool32 (*handleExitWithResult)(void *, enum TestResult);
};
struct Test
{
const char *name;
const char *filename;
const struct TestRunner *runner;
void *data;
};
struct TestRunnerState
{
u8 state;
u8 exitCode;
s32 tests;
s32 passes;
s32 skips;
const char *skipFilename;
const struct Test *test;
u32 processCosts[MAX_PROCESSES];
u8 result;
u8 expectedResult;
u32 timeoutSeconds;
};
extern const u8 gTestRunnerN;
extern const u8 gTestRunnerI;
extern const char gTestRunnerArgv[256];
extern const struct TestRunner gAssumptionsRunner;
extern struct TestRunnerState gTestRunnerState;
void CB2_TestRunner(void);
void Test_ExpectedResult(enum TestResult);
void Test_ExitWithResult(enum TestResult, const char *fmt, ...);
s32 MgbaPrintf_(const char *fmt, ...);
#define ASSUMPTIONS \
static void Assumptions(void); \
__attribute__((section(".tests"))) static const struct Test sAssumptions = \
{ \
.name = "ASSUMPTIONS: " __FILE__, \
.filename = __FILE__, \
.runner = &gAssumptionsRunner, \
.data = Assumptions, \
}; \
static void Assumptions(void)
#define ASSUME(c) \
do \
{ \
if (!(c)) \
Test_ExitWithResult(TEST_RESULT_SKIP, "%s:%d: ASSUME failed", gTestRunnerState.test->filename, __LINE__); \
} while (0)
#define EXPECT(c) \
do \
{ \
if (!(c)) \
Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT failed", gTestRunnerState.test->filename, __LINE__); \
} while (0)
#define EXPECT_EQ(a, b) \
do \
{ \
typeof(a) _a = (a), _b = (b); \
if (_a != _b) \
Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_EQ(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
} while (0)
#define EXPECT_NE(a, b) \
do \
{ \
typeof(a) _a = (a), _b = (b); \
if (_a == _b) \
Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_NE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
} while (0)
#define EXPECT_LT(a, b) \
do \
{ \
typeof(a) _a = (a), _b = (b); \
if (_a >= _b) \
Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_LT(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
} while (0)
#define EXPECT_LE(a, b) \
do \
{ \
typeof(a) _a = (a), _b = (b); \
if (_a > _b) \
Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_LE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
} while (0)
#define EXPECT_GT(a, b) \
do \
{ \
typeof(a) _a = (a), _b = (b); \
if (_a <= _b) \
Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_GT(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
} while (0)
#define EXPECT_GE(a, b) \
do \
{ \
typeof(a) _a = (a), _b = (b); \
if (_a < _b) \
Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_GE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
} while (0)
#define KNOWN_FAILING \
Test_ExpectedResult(TEST_RESULT_FAIL)
#endif

Some files were not shown because too many files have changed in this diff Show More