From 1c5ea66878e29b74a622c17dbab56238189d79a9 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sat, 7 Feb 2026 08:46:40 +0100 Subject: [PATCH 1/6] Move tutors makefile (#9058) --- Makefile | 8 ++-- tools/learnset_helpers/make_teachables.py | 48 ++++++++++++++++------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 02a4ad17fe..20e1312a1f 100644 --- a/Makefile +++ b/Makefile @@ -469,8 +469,8 @@ $(TEST_BUILDDIR)/%.o: CFLAGS := -mthumb -mthumb-interwork -O2 -mabi=apcs-gnu -mt # Dependency rules (for the *.c & *.s sources to .o files) # Have to be explicit or else missing files won't be reported. -$(C_BUILDDIR)/pokemon.o: $(C_SUBDIR)/pokemon.c $(DATA_SRC_SUBDIR)/pokemon/teachable_learnsets.h $(C_BUILDDIR)/move_relearner.o: $(C_SUBDIR)/move_relearner.c $(DATA_SRC_SUBDIR)/tutor_moves.h +$(C_BUILDDIR)/pokemon.o: $(C_SUBDIR)/pokemon.c $(DATA_SRC_SUBDIR)/pokemon/teachable_learnsets.h # As a side effect, they're evaluated immediately instead of when the rule is invoked. # It doesn't look like $(shell) can be deferred so there might not be a better way (Icedude_907: there is soon). @@ -546,7 +546,7 @@ $(OBJ_DIR)/sym_common.ld: sym_common.txt $(C_OBJS) $(wildcard common_syms/*.txt) $(OBJ_DIR)/sym_ewram.ld: sym_ewram.txt $(RAMSCRGEN) ewram_data $< ENGLISH > $@ -TEACHABLE_DEPS := $(ALL_LEARNABLES_JSON) $(INCLUDE_DIRS)/constants/tms_hms.h $(INCLUDE_DIRS)/config/pokemon.h $(DATA_SRC_SUBDIR)/pokemon/special_movesets.json $(INCLUDE_DIRS)/config/pokedex_plus_hgss.h $(LEARNSET_HELPERS_DIR)/make_teachables.py +TEACHABLE_DEPS := $(ALL_LEARNABLES_JSON) $(INCLUDE_DIRS)/constants/tms_hms.h $(INCLUDE_DIRS)/config/pokemon.h $(DATA_SRC_SUBDIR)/pokemon/special_movesets.json $(INCLUDE_DIRS)/config/pokedex_plus_hgss.h $(LEARNSET_HELPERS_DIR)/make_teachables.py $(LEARNSET_HELPERS_BUILD_DIR): @mkdir -p $@ @@ -563,8 +563,8 @@ $(ALL_TEACHING_TYPES_JSON): $(wildcard $(DATA_SRC_SUBDIR)/pokemon/species_info/* $(DATA_SRC_SUBDIR)/pokemon/teachable_learnsets.h: $(TEACHABLE_DEPS) | $(ALL_TUTORS_JSON) $(ALL_TEACHING_TYPES_JSON) python3 $(LEARNSET_HELPERS_DIR)/make_teachables.py $(LEARNSET_HELPERS_BUILD_DIR) -$(DATA_SRC_SUBDIR)/tutor_moves.h: | $(ALL_TUTORS_JSON) - python3 $(LEARNSET_HELPERS_DIR)/make_teachables.py $(LEARNSET_HELPERS_BUILD_DIR) +$(DATA_SRC_SUBDIR)/tutor_moves.h: $(DATA_SRC_SUBDIR)/pokemon/special_movesets.json | $(ALL_TUTORS_JSON) + python3 $(LEARNSET_HELPERS_DIR)/make_teachables.py --tutors $(LEARNSET_HELPERS_BUILD_DIR) # Linker script LD_SCRIPT := ld_script_modern.ld diff --git a/tools/learnset_helpers/make_teachables.py b/tools/learnset_helpers/make_teachables.py index d95502b662..92e7f2c0d8 100644 --- a/tools/learnset_helpers/make_teachables.py +++ b/tools/learnset_helpers/make_teachables.py @@ -169,26 +169,52 @@ def create_tutor_moves_array(tutors): with open("./src/data/tutor_moves.h", "w") as f: f.write(header + "\n".join(lines)) +def make_move_tutors(build_dir, special_movesets): + SOURCE_TUTORS_JSON = build_dir / "all_tutors.json" + + assert SOURCE_TUTORS_JSON.exists(), f"{SOURCE_TUTORS_JSON=} does not exist" + assert SOURCE_TUTORS_JSON.is_file(), f"{SOURCE_TUTORS_JSON=} is not a file" + + with open(SOURCE_TUTORS_JSON, "r") as fp: + repo_tutors = json.load(fp) + + repo_tutors = sorted(repo_tutors + special_movesets["extraTutors"]) + create_tutor_moves_array(repo_tutors) + + return repo_tutors + def main(): if not enabled(): quit() - if len(sys.argv) < 2: - print("Missing required arguments", file=sys.stderr) + tutor_mode = False + if len(sys.argv) < 2 or len(sys.argv) > 3: + print("Invalid number of arguments", file=sys.stderr) print(__doc__, file=sys.stderr) quit(1) - SOURCE_DIR = pathlib.Path(sys.argv[1]) + if len(sys.argv) == 2: + SOURCE_DIR = pathlib.Path(sys.argv[1]) + elif len(sys.argv) == 3: + if sys.argv[1] != "--tutors": + print("Unknown make_teachables mode", file=sys.stderr) + quit(1) + tutor_mode = True + SOURCE_DIR = pathlib.Path(sys.argv[2]) + + with open("src/data/pokemon/special_movesets.json", "r") as file: + special_movesets = json.load(file) + + repo_tutors = make_move_tutors(SOURCE_DIR, special_movesets) + if tutor_mode: + quit(0) + SOURCE_LEARNSETS_JSON = pathlib.Path("./src/data/pokemon/all_learnables.json") - SOURCE_TUTORS_JSON = SOURCE_DIR / "all_tutors.json" SOURCE_TEACHING_TYPES_JSON = SOURCE_DIR / "all_teaching_types.json" assert SOURCE_LEARNSETS_JSON.exists(), f"{SOURCE_LEARNSETS_JSON=} does not exist" assert SOURCE_LEARNSETS_JSON.is_file(), f"{SOURCE_LEARNSETS_JSON=} is not a file" - assert SOURCE_TUTORS_JSON.exists(), f"{SOURCE_TUTORS_JSON=} does not exist" - assert SOURCE_TUTORS_JSON.is_file(), f"{SOURCE_TUTORS_JSON=} is not a file" - assert SOURCE_TEACHING_TYPES_JSON.exists(), f"{SOURCE_TEACHING_TYPES_JSON=} does not exist" assert SOURCE_TEACHING_TYPES_JSON.is_file(), f"{SOURCE_TEACHING_TYPES_JSON=} is not a file" @@ -201,14 +227,6 @@ def main(): if cfg_defined is None or cfg_defined.group("cfg_val") in ("FALSE", "0"): repo_tms = sorted(repo_tms) - with open(SOURCE_TUTORS_JSON, "r") as fp: - repo_tutors = json.load(fp) - - with open("src/data/pokemon/special_movesets.json", "r") as file: - special_movesets = json.load(file) - - repo_tutors = sorted(repo_tutors + special_movesets["extraTutors"]) - create_tutor_moves_array(repo_tutors) h_align = max(map(lambda move: len(move), chain(special_movesets["universalMoves"], repo_tms, repo_tutors))) + 2 header = prepare_header(h_align, repo_tms, repo_tutors, special_movesets["universalMoves"]) From 318baaa5aa7785156d6a2c9cfd622a1ac5023148 Mon Sep 17 00:00:00 2001 From: surskitty Date: Sat, 7 Feb 2026 18:13:38 -0500 Subject: [PATCH 2/6] Prevent Coaching score inflation in certain circumstances. (#9154) --- src/battle_ai_main.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 36e604ea51..050efd3cef 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -3318,10 +3318,35 @@ static s32 AI_DoubleBattle(enum BattlerId battlerAtk, enum BattlerId battlerDef, } else { - ADJUST_SCORE(IncreaseStatUpScore(battlerAtkPartner, BATTLE_OPPOSITE(battlerAtk), STAT_CHANGE_ATK)); - ADJUST_SCORE(IncreaseStatUpScore(battlerAtkPartner, BATTLE_OPPOSITE(battlerAtk), STAT_CHANGE_DEF)); - ADJUST_SCORE(IncreaseStatUpScore(battlerAtkPartner, BATTLE_OPPOSITE(battlerAtkPartner), STAT_CHANGE_ATK)); - ADJUST_SCORE(IncreaseStatUpScore(battlerAtkPartner, BATTLE_OPPOSITE(battlerAtkPartner), STAT_CHANGE_DEF)); + s32 statUpScore = 0; + s32 tempScore = 0; + enum BattlerId foe = LEFT_FOE(battlerAtk); + + if (IsBattlerAlive(foe)) + { + tempScore += IncreaseStatUpScore(battlerAtkPartner, foe, STAT_CHANGE_ATK); + tempScore += IncreaseStatUpScore(battlerAtkPartner, foe, STAT_CHANGE_DEF); + if (tempScore == 0) + break; + statUpScore += tempScore; + } + + tempScore = 0; + foe = RIGHT_FOE(battlerAtk); + + if (IsBattlerAlive(foe)) + { + tempScore += IncreaseStatUpScore(battlerAtkPartner, foe, STAT_CHANGE_ATK); + tempScore += IncreaseStatUpScore(battlerAtkPartner, foe, STAT_CHANGE_DEF); + if (tempScore == 0) + break; + statUpScore += tempScore; + } + + if (statUpScore > BEST_EFFECT) + ADJUST_SCORE(BEST_EFFECT); + else + ADJUST_SCORE(statUpScore); } break; default: From 63f5617c290dee306c7702e70cdedba816fa15f6 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:26:09 +0100 Subject: [PATCH 3/6] Fix Pickpocket timing (#9165) --- include/constants/battle_move_resolution.h | 2 +- src/battle_move_resolution.c | 82 +++++++++++----------- test/battle/ability/pickpocket.c | 17 +++++ 3 files changed, 59 insertions(+), 42 deletions(-) diff --git a/include/constants/battle_move_resolution.h b/include/constants/battle_move_resolution.h index faf941ebff..e3a073b19d 100644 --- a/include/constants/battle_move_resolution.h +++ b/include/constants/battle_move_resolution.h @@ -120,8 +120,8 @@ enum MoveEndState MOVEEND_WHITE_HERB, MOVEEND_OPPORTUNIST, MOVEEND_MIRROR_HERB, - MOVEEND_PICKPOCKET, MOVEEND_THIRD_MOVE_BLOCK, + MOVEEND_PICKPOCKET, MOVEEND_CLEAR_BITS, MOVEEND_DANCER, MOVEEND_PURSUIT_NEXT_ACTION, diff --git a/src/battle_move_resolution.c b/src/battle_move_resolution.c index d3f1b45abb..cd3eb24c46 100644 --- a/src/battle_move_resolution.c +++ b/src/battle_move_resolution.c @@ -3168,46 +3168,6 @@ static enum MoveEndResult MoveEndMirrorHerb(void) return MOVEEND_RESULT_CONTINUE; } -static enum MoveEndResult MoveEndPickpocket(void) -{ - enum MoveEndResult result = MOVEEND_RESULT_CONTINUE; - - if (IsBattlerAlive(gBattlerAttacker) - && gBattleMons[gBattlerAttacker].item != ITEM_NONE - && !GetBattlerPartyState(gBattlerAttacker)->isKnockedOff) // Gen3 edge case where the knocked of item was not removed - { - enum BattlerId battlers[MAX_BATTLERS_COUNT] = {0, 1, 2, 3}; - SortBattlersBySpeed(battlers, FALSE); // Pickpocket activates for fastest mon without item - for (u32 i = 0; i < gBattlersCount; i++) - { - enum BattlerId battlerDef = battlers[i]; - if (battlerDef != gBattlerAttacker - && !IsBattlerUnaffectedByMove(battlerDef) - && GetBattlerAbility(battlerDef) == ABILITY_PICKPOCKET - && IsMoveMakingContact(gBattlerAttacker, battlerDef, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove) - && IsBattlerTurnDamaged(battlerDef) - && !DoesSubstituteBlockMove(gBattlerAttacker, battlerDef, gCurrentMove) - && IsBattlerAlive(battlerDef) - && gBattleMons[battlerDef].item == ITEM_NONE - && CanStealItem(battlerDef, gBattlerAttacker, gBattleMons[gBattlerAttacker].item)) - { - gBattlerTarget = gBattlerAbility = battlerDef; - // Battle scripting is super brittle so we shall do the item exchange now (if possible) - if (GetBattlerAbility(gBattlerAttacker) != ABILITY_STICKY_HOLD) - StealTargetItem(battlerDef, gBattlerAttacker); // Target takes attacker's item - - gEffectBattler = gBattlerAttacker; - BattleScriptCall(BattleScript_Pickpocket); // Includes sticky hold check to print separate string - result = MOVEEND_RESULT_RUN_SCRIPT; - break; // Pickpocket activates on fastest mon, so exit loop. - } - } - } - - gBattleScripting.moveendState++; - return result; -} - static enum MoveEndResult MoveEndThirdMoveBlock(void) { enum MoveEndResult result = MOVEEND_RESULT_CONTINUE; @@ -3262,6 +3222,46 @@ static enum MoveEndResult MoveEndThirdMoveBlock(void) return result; } +static enum MoveEndResult MoveEndPickpocket(void) +{ + enum MoveEndResult result = MOVEEND_RESULT_CONTINUE; + + if (IsBattlerAlive(gBattlerAttacker) + && gBattleMons[gBattlerAttacker].item != ITEM_NONE + && !GetBattlerPartyState(gBattlerAttacker)->isKnockedOff) // Gen3 edge case where the knocked of item was not removed + { + enum BattlerId battlers[MAX_BATTLERS_COUNT] = {0, 1, 2, 3}; + SortBattlersBySpeed(battlers, FALSE); // Pickpocket activates for fastest mon without item + for (u32 i = 0; i < gBattlersCount; i++) + { + enum BattlerId battlerDef = battlers[i]; + if (battlerDef != gBattlerAttacker + && !IsBattlerUnaffectedByMove(battlerDef) + && GetBattlerAbility(battlerDef) == ABILITY_PICKPOCKET + && IsMoveMakingContact(gBattlerAttacker, battlerDef, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove) + && IsBattlerTurnDamaged(battlerDef) + && !DoesSubstituteBlockMove(gBattlerAttacker, battlerDef, gCurrentMove) + && IsBattlerAlive(battlerDef) + && gBattleMons[battlerDef].item == ITEM_NONE + && CanStealItem(battlerDef, gBattlerAttacker, gBattleMons[gBattlerAttacker].item)) + { + gBattlerTarget = gBattlerAbility = battlerDef; + // Battle scripting is super brittle so we shall do the item exchange now (if possible) + if (GetBattlerAbility(gBattlerAttacker) != ABILITY_STICKY_HOLD) + StealTargetItem(battlerDef, gBattlerAttacker); // Target takes attacker's item + + gEffectBattler = gBattlerAttacker; + BattleScriptCall(BattleScript_Pickpocket); // Includes sticky hold check to print separate string + result = MOVEEND_RESULT_RUN_SCRIPT; + break; // Pickpocket activates on fastest mon, so exit loop. + } + } + } + + gBattleScripting.moveendState++; + return result; +} + static bool32 ShouldSetStompingTantrumTimer(void) { u32 numNotAffectedTargets = 0; @@ -3471,8 +3471,8 @@ static enum MoveEndResult (*const sMoveEndHandlers[])(void) = [MOVEEND_WHITE_HERB] = MoveEndWhiteHerb, [MOVEEND_OPPORTUNIST] = MoveEndOpportunist, [MOVEEND_MIRROR_HERB] = MoveEndMirrorHerb, - [MOVEEND_PICKPOCKET] = MoveEndPickpocket, [MOVEEND_THIRD_MOVE_BLOCK] = MoveEndThirdMoveBlock, + [MOVEEND_PICKPOCKET] = MoveEndPickpocket, [MOVEEND_CLEAR_BITS] = MoveEndClearBits, [MOVEEND_DANCER] = MoveEndDancer, [MOVEEND_PURSUIT_NEXT_ACTION] = MoveEndPursuitNextAction, diff --git a/test/battle/ability/pickpocket.c b/test/battle/ability/pickpocket.c index 32f4d0d264..9f3ba77d46 100644 --- a/test/battle/ability/pickpocket.c +++ b/test/battle/ability/pickpocket.c @@ -310,3 +310,20 @@ SINGLE_BATTLE_TEST("Pickpocket does not prevent King's Rock or Razor Fang flinch EXPECT(player->item == ITEM_NONE); } } + +SINGLE_BATTLE_TEST("Pickpocket activates after an Item was knocked off") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_POTION); } + OPPONENT(SPECIES_SNEASEL) { Item(ITEM_POTION); Ability(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + } THEN { + EXPECT(opponent->item == ITEM_POTION); + EXPECT(player->item == ITEM_NONE); + } +} From 9f1cdbdcaac0e88e19a4124ec1fa18767cd2cecb Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Mon, 9 Feb 2026 15:10:04 +0100 Subject: [PATCH 4/6] Add wander around slower movement type (#9164) --- include/constants/event_object_movement.h | 5 ++--- include/event_object_movement.h | 2 ++ src/data/object_events/movement_type_func_tables.h | 10 ++++++++++ src/event_object_movement.c | 10 ++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/include/constants/event_object_movement.h b/include/constants/event_object_movement.h index aca4a4a292..6da9ca071a 100755 --- a/include/constants/event_object_movement.h +++ b/include/constants/event_object_movement.h @@ -1,8 +1,6 @@ #ifndef GUARD_CONSTANTS_EVENT_OBJECT_MOVEMENT_H #define GUARD_CONSTANTS_EVENT_OBJECT_MOVEMENT_H -#define MOVEMENT_TYPE_WANDER_AROUND_SLOWER MOVEMENT_TYPE_WANDER_AROUND - #define MOVEMENT_TYPE_NONE 0x0 #define MOVEMENT_TYPE_LOOK_AROUND 0x1 @@ -86,7 +84,8 @@ #define MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_LEFT 0x4F #define MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_RIGHT 0x50 #define MOVEMENT_TYPE_FOLLOW_PLAYER 0x51 -#define NUM_MOVEMENT_TYPES 0x52 +#define MOVEMENT_TYPE_WANDER_AROUND_SLOWER 0x52 +#define NUM_MOVEMENT_TYPES 0x53 #define MOVEMENT_ACTION_FACE_DOWN 0x0 #define MOVEMENT_ACTION_FACE_UP 0x1 diff --git a/include/event_object_movement.h b/include/event_object_movement.h index 9241e161a7..d6ddac1c4f 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -270,6 +270,7 @@ void CopyObjectGraphicsInfoToSpriteTemplate(u16 graphicsId, void (*callback)(str void MovementType_None(struct Sprite *sprite); void MovementType_LookAround(struct Sprite *sprite); void MovementType_WanderAround(struct Sprite *sprite); +void MovementType_WanderAroundSlower(struct Sprite *sprite); void MovementType_WanderUpAndDown(struct Sprite *sprite); void MovementType_WanderLeftAndRight(struct Sprite *sprite); void MovementType_FaceDirection(struct Sprite *sprite); @@ -335,6 +336,7 @@ u8 MovementType_WanderAround_Step2(struct ObjectEvent *objectEvent, struct Sprit u8 MovementType_Wander_Step3(struct ObjectEvent *objectEvent, struct Sprite *sprite); u8 MovementType_WanderAround_Step4(struct ObjectEvent *objectEvent, struct Sprite *sprite); u8 MovementType_WanderAround_Step5(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_WanderAround_Step5Slower(struct ObjectEvent *objectEvent, struct Sprite *sprite); u8 MovementType_WanderAround_Step6(struct ObjectEvent *objectEvent, struct Sprite *sprite); enum Direction GetVectorDirection(s16 dx, s16 dy, s16 absdx, s16 absdy); enum Direction GetLimitedVectorDirection_SouthNorth(s16 dx, s16 dy, s16 absdx, s16 absdy); diff --git a/src/data/object_events/movement_type_func_tables.h b/src/data/object_events/movement_type_func_tables.h index 72fd7557ff..3c81369aa1 100755 --- a/src/data/object_events/movement_type_func_tables.h +++ b/src/data/object_events/movement_type_func_tables.h @@ -8,6 +8,16 @@ u8 (*const gMovementTypeFuncs_WanderAround[])(struct ObjectEvent *, struct Sprit MovementType_WanderAround_Step6, }; +u8 (*const gMovementTypeFuncs_WanderAroundSlower[])(struct ObjectEvent *, struct Sprite *) = { + MovementType_WanderAround_Step0, + MovementType_WanderAround_Step1, + MovementType_WanderAround_Step2, + MovementType_Wander_Step3, + MovementType_WanderAround_Step4, + MovementType_WanderAround_Step5Slower, + MovementType_WanderAround_Step6, +}; + const enum Direction gStandardDirections[] = {DIR_SOUTH, DIR_NORTH, DIR_WEST, DIR_EAST}; enum Direction (*const gGetVectorDirectionFuncs[])(s16, s16, s16, s16) = { diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 5b7601e9cf..faadea63c5 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -266,6 +266,7 @@ static void (*const sMovementTypeCallbacks[])(struct Sprite *) = [MOVEMENT_TYPE_NONE] = MovementType_None, [MOVEMENT_TYPE_LOOK_AROUND] = MovementType_LookAround, [MOVEMENT_TYPE_WANDER_AROUND] = MovementType_WanderAround, + [MOVEMENT_TYPE_WANDER_AROUND_SLOWER] = MovementType_WanderAroundSlower, [MOVEMENT_TYPE_WANDER_UP_AND_DOWN] = MovementType_WanderUpAndDown, [MOVEMENT_TYPE_WANDER_DOWN_AND_UP] = MovementType_WanderUpAndDown, [MOVEMENT_TYPE_WANDER_LEFT_AND_RIGHT] = MovementType_WanderLeftAndRight, @@ -3874,6 +3875,7 @@ u16 GetObjectPaletteTag(u8 palSlot) movement_type_empty_callback(MovementType_None) movement_type_def(MovementType_WanderAround, gMovementTypeFuncs_WanderAround) +movement_type_def(MovementType_WanderAroundSlower, gMovementTypeFuncs_WanderAroundSlower) bool8 MovementType_WanderAround_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite) { @@ -3938,6 +3940,14 @@ bool8 MovementType_WanderAround_Step5(struct ObjectEvent *objectEvent, struct Sp return TRUE; } +bool8 MovementType_WanderAround_Step5Slower(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(objectEvent->movementDirection)); + objectEvent->singleMovementActive = TRUE; + sprite->data[1] = 6; + return TRUE; +} + bool8 MovementType_WanderAround_Step6(struct ObjectEvent *objectEvent, struct Sprite *sprite) { if (ObjectEventExecSingleMovementAction(objectEvent, sprite)) From b7c400ea9c4eb77f661284222b0f6763e67d990b Mon Sep 17 00:00:00 2001 From: Kildemal <206095739+izrofid@users.noreply.github.com> Date: Mon, 9 Feb 2026 19:43:26 +0530 Subject: [PATCH 5/6] fix(fanclub): use sText instead of non-existent gText (#9163) --- src/trainer_fan_club.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/trainer_fan_club.c b/src/trainer_fan_club.c index 8a0692ff42..dac9d63de7 100644 --- a/src/trainer_fan_club.c +++ b/src/trainer_fan_club.c @@ -348,10 +348,10 @@ static void BufferFanClubTrainerName(u8 whichLinkTrainer, u8 whichNPCTrainer) break; #endif case 1: - StringCopy(gStringVar1, gText_LtSurge); + StringCopy(gStringVar1, sText_LtSurge); break; case 2: - StringCopy(gStringVar1, gText_Koga); + StringCopy(gStringVar1, sText_Koga); break; } } From e2c5ce3d8eace4ddbce4cd48c8c710952c0360d4 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:47:35 -0500 Subject: [PATCH 6/6] Fix doubles switch looping (#9167) --- src/battle_ai_switch.c | 10 +++++----- test/battle/ai/ai_switching.c | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/battle_ai_switch.c b/src/battle_ai_switch.c index 37519a223a..16c1af9707 100644 --- a/src/battle_ai_switch.c +++ b/src/battle_ai_switch.c @@ -471,7 +471,7 @@ static bool32 ShouldSwitchIfAllMovesBad(enum BattlerId battler) ctx.holdEffectDef = gAiLogicData->holdEffects[ctx.battlerDef]; // Switch if no moves affect opponents - if (HasTwoOpponents(battler)) + if (IsDoubleBattle()) { enum BattlerId opposingPartner = BATTLE_PARTNER(opposingBattler); for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) @@ -998,7 +998,7 @@ static bool32 CanUseSuperEffectiveMoveAgainstOpponents(enum BattlerId battler) if (CanUseSuperEffectiveMoveAgainstOpponent(battler, opposingBattler)) return TRUE; - if (HasTwoOpponents(battler) && CanUseSuperEffectiveMoveAgainstOpponent(battler, BATTLE_PARTNER(BATTLE_OPPOSITE(battler)))) + if (IsDoubleBattle() && CanUseSuperEffectiveMoveAgainstOpponent(battler, BATTLE_PARTNER(BATTLE_OPPOSITE(battler)))) return TRUE; return FALSE; @@ -1159,7 +1159,7 @@ static bool32 ShouldSwitchIfBadChoiceLock(enum BattlerId battler) u32 moveIndex = GetMoveIndex(battler, choicedMove); - if (HasTwoOpponents(battler)) + if (IsDoubleBattle()) { enum BattlerId opposingPartner = BATTLE_PARTNER(opposingBattler); if (IsHoldEffectChoice(ctx.holdEffectAtk) && IsBattlerItemEnabled(battler)) @@ -1359,7 +1359,7 @@ bool32 ShouldSwitchIfAllScoresBad(enum BattlerId battler) for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { - if (HasTwoOpponents(battler)) + if (IsDoubleBattle()) { u32 score1 = gAiBattleData->finalScore[battler][opposingBattler][moveIndex]; u32 score2 = gAiBattleData->finalScore[battler][BATTLE_PARTNER(opposingBattler)][moveIndex]; @@ -1398,7 +1398,7 @@ bool32 ShouldStayInToUseMove(enum BattlerId battler) continue; if (gAiBattleData->finalScore[battler][opposingBattler][moveIndex] > AI_GOOD_SCORE_THRESHOLD - || (HasTwoOpponents(battler) && gAiBattleData->finalScore[battler][BATTLE_PARTNER(opposingBattler)][moveIndex] > AI_GOOD_SCORE_THRESHOLD)) + || (IsDoubleBattle() && gAiBattleData->finalScore[battler][BATTLE_PARTNER(opposingBattler)][moveIndex] > AI_GOOD_SCORE_THRESHOLD)) return TRUE; } } diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index b88697df33..9239c919cd 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -2175,3 +2175,25 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RANDOMIZE_SWITCHIN: AI will consider all mons tha TURN { MOVE(player, MOVE_WATER_GUN); EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 2); } } } + +AI_MULTI_BATTLE_TEST("AI will not switch out if the opposite battler is absent and its moves can still affect the other opponent") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + MULTI_PLAYER(SPECIES_WOBBUFFET) { HP(41); Speed(1); } + MULTI_PARTNER(SPECIES_DRAKLOAK) { HP(41); MaxHP(100); Speed(4); Item(ITEM_LIFE_ORB); Moves(MOVE_DRAGON_RAGE, MOVE_SHADOW_BALL); } + MULTI_PARTNER(SPECIES_DRAGAPULT) { Speed(4); Moves(MOVE_SHADOW_BALL); } + MULTI_OPPONENT_A(SPECIES_CYCLIZAR) { HP(41); MaxHP(100); Speed(3); Item(ITEM_LIFE_ORB); Moves(MOVE_BODY_SLAM, MOVE_DRAGON_RAGE); } + MULTI_OPPONENT_A(SPECIES_DRAMPA) { Speed(3); Moves(MOVE_BODY_SLAM); } + MULTI_OPPONENT_B(SPECIES_WYNAUT) { Speed(2); HP(41); } + } WHEN { + TURN { + EXPECT_MOVE(opponentLeft, MOVE_BODY_SLAM, target: playerLeft); + EXPECT_MOVE(playerRight, MOVE_SHADOW_BALL, target: opponentRight); + } + TURN { + EXPECT_MOVE(opponentLeft, MOVE_DRAGON_RAGE, target: playerRight); + EXPECT_MOVE(playerRight, MOVE_DRAGON_RAGE, target: opponentLeft); + } + } +}