From a407c7223588dd3d188b5d785314e5ebf223e1e2 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Thu, 25 Jan 2024 22:40:00 -0500 Subject: [PATCH] Cleaned up follower code. Added support for asymmetrical follower pokemon. Fixed bug with changing GraphicsInfo size. Added follower Substitute gfx. Credit: shikashipx --- data/scripts/follower.inc | 11 - gflib/sprite.c | 1 - gflib/sprite.h | 1 + .../object_events/pics/pokemon/substitute.png | Bin 0 -> 8090 bytes .../object_events/pics/pokemon/wailord.png | Bin 10094 -> 1259 bytes include/constants/event_object_movement.h | 5 + include/event_object_movement.h | 1 + include/follower_helper.h | 1 + include/global.fieldmap.h | 5 + remote_build.sh | 22 +- spritesheet_rules.mk | 3 + src/data/object_events/object_event_anims.h | 149 ++- .../object_events/object_event_graphics.h | 3 + .../object_event_graphics_info_followers.h | 2 + .../object_events/object_event_pic_tables.h | 8 + src/data/text/follower_messages.h | 8 +- src/event_object_movement.c | 989 +++++++++--------- src/field_effect_helpers.c | 8 +- src/follower_helper.c | 15 + src/scrcmd.c | 6 +- src/trainer_see.c | 44 +- 21 files changed, 738 insertions(+), 544 deletions(-) create mode 100644 graphics/object_events/pics/pokemon/substitute.png diff --git a/data/scripts/follower.inc b/data/scripts/follower.inc index 3a81e6fabf..6d1d3f0024 100644 --- a/data/scripts/follower.inc +++ b/data/scripts/follower.inc @@ -30,17 +30,6 @@ EventScript_Follower:: bufferlivemonnickname 0 playfirstmoncry callfunc ScrFunc_getfolloweraction - checkpartymove MOVE_FLY - compare VAR_RESULT 6 - goto_if_eq EventScript_FollowerEnd - bufferlivemonnickname 0 - msgbox gText_WantsToFly, MSGBOX_YESNO - switch VAR_RESULT - case NO, EventScript_FollowerEnd - case YES, EventScript_FollowerFly - case MULTI_B_PRESSED, EventScript_FollowerEnd -EventScript_FollowerFly:: - callfunc ScrFunc_followerfly EventScript_FollowerEnd:: waitfieldeffect FLDEFF_EMOTE release diff --git a/gflib/sprite.c b/gflib/sprite.c index 4540f59cc0..0b3447187c 100644 --- a/gflib/sprite.c +++ b/gflib/sprite.c @@ -56,7 +56,6 @@ static void AddSpritesToOamBuffer(void); static u8 CreateSpriteAt(u8 index, const struct SpriteTemplate *template, s16 x, s16 y, u8 subpriority); static void ResetOamMatrices(void); static void ResetSprite(struct Sprite *sprite); -static s16 AllocSpriteTiles(u16 tileCount); static void RequestSpriteFrameImageCopy(u16 index, u16 tileNum, const struct SpriteFrameImage *images); static void ResetAllSprites(void); static void BeginAnim(struct Sprite *sprite); diff --git a/gflib/sprite.h b/gflib/sprite.h index 86527bc4c0..2b2b083103 100644 --- a/gflib/sprite.h +++ b/gflib/sprite.h @@ -297,6 +297,7 @@ void InitSpriteAffineAnim(struct Sprite *sprite); void SetOamMatrixRotationScaling(u8 matrixNum, s16 xScale, s16 yScale, u16 rotation); u16 LoadSpriteSheet(const struct SpriteSheet *sheet); void LoadSpriteSheets(const struct SpriteSheet *sheets); +s16 AllocSpriteTiles(u16 tileCount); u16 AllocTilesForSpriteSheet(struct SpriteSheet *sheet); void AllocTilesForSpriteSheets(struct SpriteSheet *sheets); void LoadTilesForSpriteSheet(const struct SpriteSheet *sheet); diff --git a/graphics/object_events/pics/pokemon/substitute.png b/graphics/object_events/pics/pokemon/substitute.png new file mode 100644 index 0000000000000000000000000000000000000000..6be1e2ddcb7cbebcff17911c34acb44502a8837b GIT binary patch literal 8090 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 V_DMFNP6BQL85x|>FVl6s@PAp&J*ofz literal 0 HcmV?d00001 diff --git a/graphics/object_events/pics/pokemon/wailord.png b/graphics/object_events/pics/pokemon/wailord.png index a1fc3a087a2641b82ead18cbd07042531268a140..a7032a856a462d258c35136f14fbb086810819d9 100644 GIT binary patch delta 1218 zcmV;z1U>uiPU{JOiBL{Q4GJ0x0000DNk~Le0004i0000$1Oos70O5AMi~s-t2XskI zMF-^x0uL)7YX%Va0000NbVXQnQ*UN;cVTj607pzjP)ME|7+^2GoE z1P@6>K~#90?U}o7+b|S{H4Einc<3$5fR7F^>Y2z-2ee0+R-e0+R-e0+R- ze0+R-UOMdY&tW`&AdKB(7jW@oQ{+LAXNPlA0Az@viyxg37=y*ZoD|a}8QBe8{P>Vd zfEjiNaW4FX;i3E)!YIW`9OncQ8L)KG#h)dc`Qe-kj+5j9>j281Bl%{YAIw#S*lWba zpCRn(VtkEFqbL`GlO5gxj2|CD>BX2*`z(TUA^7lscL3vmN9S%sW#)GW@en`>f_bKO z@#Ay5p>X~O==|g${_-6l6oeqyYQ(|0;8fK3$ld{zA0Gl`tkWRMxgdy@7A%6>ir<)i zp6AA&J(ZV&;tpdSK)4W8+$c!WiZAsR!Y@fOuBPa)DeK}Qe5VDI9sgsTCGt8qECxzY za+6>{@`~qw8W(=NzOMESFVlAX34+)66(?711%vVlDXu>u;m1e#lq)Vwf-Wuu85b0T zj0-Y@;C5c|jj2W)B_zj4$rl=vOY~%S{s5D4o{MuAZMeKXh5cp=sKKC^Rs7OaWFb68 zYuv@dvgPRx9{#{6-=vVX;ZiVz{ZbEwL6ESD8(*t`usbY*J)vKGkEbRmn2e^oIovbW zhF5}esW%IfiW{e)qN;Jz15%T);#vbmviB1i(dM4l#brWBlwe}IgW;mi4YQbQ7Aoq4 zywO8>Z>kD|=6=h?rC?qc)YptKt@EYc0VSwP#Pr_0t*--(8Y1v-^bX)$3c?l=RpSZV z=+A(EEbBbg8`x&?zWul`Oai3N8$Z_t=dHLBlnZ@XYr3FlqvD29Kt8VD@l!bbaQfXe zNb-s|mzjp(jDG63;_93w7nHAjBS;N`MV*^QX}&)BE(AZ`YJw^i*Dn4Bpr{K*{Z?E? zu4CmqpBdh|!a84=1YfOA#8->k{#T?*=RXHMsdg!j85g{3#~%gDSUZ4mzLKotWg4H_z8)fcHR!(PBM)Xveq7T}y?#4h z3YLnQtphmcxr>V|w!Dy{ob)<_*N~-2!H-oGHJ?(+;mesgbgoXS?y&I|y0|nfGQ=N$ z;j3Y9(CHL6!>G84qNv{-go?9t6^TRVQjizeV9@Q_z8<=`2m`>s?AbV3N4+>cT@Rx^ z_tWpM;xQXW5i@a_P0Sxuw=;|_U*BAuUq-w#V&7KnoE!4=A{j#`U0j{h>vlSW;i+v9 zI=I;NGL|gt9l*F4;_)yt)z6c-Jm)t#@8R%={lk`n2fujJ?97TCoG=wz`z|gbID%dL g=>PEe`26?$1;xtqUc5*DdH?_b07*qoM6N<$f)gM;{{R30 literal 10094 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 diff --git a/include/constants/event_object_movement.h b/include/constants/event_object_movement.h index f7c2d36284..cac4333689 100755 --- a/include/constants/event_object_movement.h +++ b/include/constants/event_object_movement.h @@ -276,6 +276,11 @@ #define ANIM_RUN_WEST (ANIM_STD_COUNT + 2) #define ANIM_RUN_EAST (ANIM_STD_COUNT + 3) +#define ANIM_EXIT_POKEBALL_FAST_SOUTH (ANIM_STD_COUNT + 0) +#define ANIM_EXIT_POKEBALL_FAST_NORTH (ANIM_STD_COUNT + 1) +#define ANIM_EXIT_POKEBALL_FAST_WEST (ANIM_STD_COUNT + 2) +#define ANIM_EXIT_POKEBALL_FAST_EAST (ANIM_STD_COUNT + 3) + #define ANIM_BUNNY_HOP_BACK_WHEEL_SOUTH (ANIM_STD_COUNT + 0) #define ANIM_BUNNY_HOP_BACK_WHEEL_NORTH (ANIM_STD_COUNT + 1) #define ANIM_BUNNY_HOP_BACK_WHEEL_WEST (ANIM_STD_COUNT + 2) diff --git a/include/event_object_movement.h b/include/event_object_movement.h index 1b4f1e1d9f..9c7ab50ede 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -289,6 +289,7 @@ void MovementType_FollowPlayer(struct Sprite *); u8 GetSlideMovementAction(u32); u8 GetJumpMovementAction(u32); u8 GetJump2MovementAction(u32); +u8 CopySprite(struct Sprite *sprite, s16 x, s16 y, u8 subpriority); u8 CreateCopySpriteAt(struct Sprite *sprite, s16 x, s16 y, u8 subpriority); u8 MovementType_WanderAround_Step0(struct ObjectEvent *, struct Sprite *); diff --git a/include/follower_helper.h b/include/follower_helper.h index 115242fdb9..d8ab0621b1 100644 --- a/include/follower_helper.h +++ b/include/follower_helper.h @@ -125,5 +125,6 @@ enum { }; extern const struct FollowerMsgInfoExtended gFollowerConditionalMessages[COND_MSG_COUNT]; +extern const struct FollowerMessagePool gFollowerBasicMessages[FOLLOWER_EMOTION_LENGTH]; #endif //GUARD_FOLLOWER_HELPER_H diff --git a/include/global.fieldmap.h b/include/global.fieldmap.h index 0c07148ba2..9f1a210a27 100644 --- a/include/global.fieldmap.h +++ b/include/global.fieldmap.h @@ -272,6 +272,11 @@ enum { #define PLAYER_AVATAR_FLAG_FORCED_MOVE (1 << 6) #define PLAYER_AVATAR_FLAG_DASH (1 << 7) +#define PLAYER_AVATAR_FLAG_BIKE (PLAYER_AVATAR_FLAG_MACH_BIKE | PLAYER_AVATAR_FLAG_ACRO_BIKE) +// Player avatar flags for which follower pokemon are hidden +#define FOLLOWER_INVISIBLE_FLAGS (PLAYER_AVATAR_FLAG_SURFING | PLAYER_AVATAR_FLAG_UNDERWATER | \ + PLAYER_AVATAR_FLAG_BIKE | PLAYER_AVATAR_FLAG_FORCED_MOVE) + enum { ACRO_BIKE_NORMAL, diff --git a/remote_build.sh b/remote_build.sh index 03e3e54b1a..835520290d 100644 --- a/remote_build.sh +++ b/remote_build.sh @@ -11,7 +11,23 @@ remote_path=$(git remote get-url build | sed -nr -e "s/ssh:\/\/\w+@?\w*://p") # make_cmd=$(git config --local remake.make) make_product=$(git config --local remake.src) make_dest=$(git config --local remake.dest) -set -x +old_head=$(git rev-parse --short @) +set +e +# Make a temp commit for unstaged changes +temp_commit_msg="temp build" +git commit -aem "$temp_commit_msg" +retVal=$? +set -e +# set -x git push build --force-with-lease -ssh $remote_host "cd $remote_path && git reset --hard && git checkout $git_branch && $make_cmd" -scp "$remote_host:$remote_path/$make_product" $make_dest +# { set +x; } 2>/dev/null +# Reset temp commit +if [[ $retVal -eq 0 ]]; then + commit_msg=$(git log -1 --pretty=%B) + if [[ "$commit_msg" == "$temp_commit_msg" ]]; then + git reset "$old_head" &>/dev/null + fi +fi +set -x +ssh -o "VisualHostKey=no" $remote_host "cd $remote_path && git reset --hard && git checkout $git_branch && $make_cmd" +scp -o "VisualHostKey=no" "$remote_host:$remote_path/$make_product" $make_dest diff --git a/spritesheet_rules.mk b/spritesheet_rules.mk index 902eb1c892..5eb25228a7 100644 --- a/spritesheet_rules.mk +++ b/spritesheet_rules.mk @@ -687,6 +687,9 @@ $(FLDEFFGFXDIR)/secret_power_tree.4bpp: %.4bpp: %.png $(FLDEFFGFXDIR)/record_mix_lights.4bpp: %.4bpp: %.png $(GFX) $< $@ -mwidth 4 -mheight 1 +$(OBJEVENTGFXDIR)/pokemon/substitute.4bpp: %.4bpp: %.png + $(GFX) $< $@ -mwidth 4 -mheight 4 + $(OBJEVENTGFXDIR)/pokemon/bulbasaur.4bpp: %.4bpp: %.png $(GFX) $< $@ -mwidth 4 -mheight 4 diff --git a/src/data/object_events/object_event_anims.h b/src/data/object_events/object_event_anims.h index 8bb579e01b..1faf48e759 100755 --- a/src/data/object_events/object_event_anims.h +++ b/src/data/object_events/object_event_anims.h @@ -217,6 +217,12 @@ static const union AnimCmd sAnim_FaceEast2F[] = ANIMCMD_JUMP(0), }; +static const union AnimCmd sAnim_FaceEast2F_Asym[] = +{ + ANIMCMD_FRAME(6, 16), + ANIMCMD_JUMP(0), +}; + static const union AnimCmd sAnim_GoSouth[] = { ANIMCMD_FRAME(3, 8), @@ -246,11 +252,11 @@ static const union AnimCmd sAnim_GoNorth[] = static const union AnimCmd sAnim_GoNorth2F[] = { - ANIMCMD_FRAME(2, 6), - ANIMCMD_FRAME(3, 6), - ANIMCMD_FRAME(3, 6), - ANIMCMD_FRAME(2, 6), - ANIMCMD_JUMP(0), + ANIMCMD_FRAME(2, 6), + ANIMCMD_FRAME(3, 6), + ANIMCMD_FRAME(3, 6), + ANIMCMD_FRAME(2, 6), + ANIMCMD_JUMP(0), }; static const union AnimCmd sAnim_GoWest[] = @@ -289,6 +295,15 @@ static const union AnimCmd sAnim_GoEast2F[] = ANIMCMD_JUMP(0), }; +static const union AnimCmd sAnim_GoEast2F_Asym[] = +{ + ANIMCMD_FRAME(6, 6), + ANIMCMD_FRAME(7, 6), + ANIMCMD_FRAME(7, 6), + ANIMCMD_FRAME(6, 6), + ANIMCMD_JUMP(0), +}; + static const union AnimCmd sAnim_GoFastSouth[] = { ANIMCMD_FRAME(3, 4), @@ -361,6 +376,15 @@ static const union AnimCmd sAnim_GoFastEast2F[] = ANIMCMD_JUMP(0), }; +static const union AnimCmd sAnim_GoFastEast2F_Asym[] = +{ + ANIMCMD_FRAME(6, 4), + ANIMCMD_FRAME(7, 4), + ANIMCMD_FRAME(7, 4), + ANIMCMD_FRAME(6, 4), + ANIMCMD_JUMP(0), +}; + static const union AnimCmd sAnim_GoFasterSouth[] = { ANIMCMD_FRAME(3, 2), @@ -422,6 +446,19 @@ static const union AnimCmd sAnim_ExitPokeballEast[] = ANIMCMD_JUMP(0), }; +static const union AnimCmd sAnim_ExitPokeballEast_Asym[] = +{ + ANIMCMD_FRAME(0, 1), + ANIMCMD_FRAME(0, 3), + ANIMCMD_FRAME(0, 1), + ANIMCMD_FRAME(1, 1), + ANIMCMD_FRAME(2, 1), + ANIMCMD_FRAME(3, 1), + ANIMCMD_FRAME(4, 1), + ANIMCMD_FRAME(6, 8), + ANIMCMD_JUMP(0), +}; + static const union AnimCmd sAnim_EnterSouth[] = { ANIMCMD_FRAME(0, 8), @@ -497,6 +534,18 @@ static const union AnimCmd sAnim_EnterEast[] = ANIMCMD_JUMP(0), }; +static const union AnimCmd sAnim_EnterEast_Asym[] = +{ + ANIMCMD_FRAME(6, 8), + ANIMCMD_FRAME(4, 1), + ANIMCMD_FRAME(3, 1), + ANIMCMD_FRAME(2, 1), + ANIMCMD_FRAME(1, 1), + ANIMCMD_FRAME(0, 1), + ANIMCMD_FRAME(0, 3), + ANIMCMD_JUMP(0), +}; + static const union AnimCmd sAnim_GoFastestSouth[] = { ANIMCMD_FRAME(3, 1), @@ -554,6 +603,18 @@ static const union AnimCmd sAnim_ExitPokeballFastEast[] = ANIMCMD_JUMP(0), }; +static const union AnimCmd sAnim_ExitPokeballFastEast_Asym[] = +{ + ANIMCMD_FRAME(0, 1), + ANIMCMD_FRAME(1, 1), + ANIMCMD_FRAME(2, 1), + ANIMCMD_FRAME(3, 1), + ANIMCMD_FRAME(4, 1), + ANIMCMD_FRAME(6, 2), + ANIMCMD_FRAME(6, 1), + ANIMCMD_JUMP(0), +}; + static const union AnimCmd sAnim_GoFastestNorth[] = { ANIMCMD_FRAME(5, 1), @@ -1084,31 +1145,59 @@ static const union AnimCmd *const sAnimTable_Standard[] = { }; static const union AnimCmd *const sAnimTable_Following[] = { - sAnim_FaceSouth, - sAnim_FaceNorth2F, - sAnim_FaceWest2F, - sAnim_FaceEast2F, - sAnim_GoSouth2F, - sAnim_GoNorth2F, - sAnim_GoWest2F, - sAnim_GoEast2F, - sAnim_GoFastSouth2F, - sAnim_GoFastNorth2F, - sAnim_GoFastWest2F, - sAnim_GoFastEast2F, - sAnim_EnterSouth, - sAnim_EnterNorth, - sAnim_EnterWest, - sAnim_EnterEast, - sAnim_ExitPokeballSouth, - sAnim_ExitPokeballNorth, - sAnim_ExitPokeballWest, - sAnim_ExitPokeballEast, - // ANIM_STD_COUNT = - sAnim_ExitPokeballFastSouth, - sAnim_ExitPokeballFastNorth, - sAnim_ExitPokeballFastWest, - sAnim_ExitPokeballFastEast, + [ANIM_STD_FACE_SOUTH] = sAnim_FaceSouth, + [ANIM_STD_FACE_NORTH] = sAnim_FaceNorth2F, + [ANIM_STD_FACE_WEST] = sAnim_FaceWest2F, + [ANIM_STD_FACE_EAST] = sAnim_FaceEast2F, + [ANIM_STD_GO_SOUTH] = sAnim_GoSouth2F, + [ANIM_STD_GO_NORTH] = sAnim_GoNorth2F, + [ANIM_STD_GO_WEST] = sAnim_GoWest2F, + [ANIM_STD_GO_EAST] = sAnim_GoEast2F, + [ANIM_STD_GO_FAST_SOUTH] = sAnim_GoFastSouth2F, + [ANIM_STD_GO_FAST_NORTH] = sAnim_GoFastNorth2F, + [ANIM_STD_GO_FAST_WEST] = sAnim_GoFastWest2F, + [ANIM_STD_GO_FAST_EAST] = sAnim_GoFastEast2F, + // 'Faster' and above used for entering/exiting pokeball + [ANIM_STD_GO_FASTER_SOUTH] = sAnim_EnterSouth, + [ANIM_STD_GO_FASTER_NORTH] = sAnim_EnterNorth, + [ANIM_STD_GO_FASTER_WEST] = sAnim_EnterWest, + [ANIM_STD_GO_FASTER_EAST] = sAnim_EnterEast, + [ANIM_STD_GO_FASTEST_SOUTH] = sAnim_ExitPokeballSouth, + [ANIM_STD_GO_FASTEST_NORTH] = sAnim_ExitPokeballNorth, + [ANIM_STD_GO_FASTEST_WEST] = sAnim_ExitPokeballWest, + [ANIM_STD_GO_FASTEST_EAST] = sAnim_ExitPokeballEast, + [ANIM_EXIT_POKEBALL_FAST_SOUTH] = sAnim_ExitPokeballFastSouth, + [ANIM_EXIT_POKEBALL_FAST_NORTH] = sAnim_ExitPokeballFastNorth, + [ANIM_EXIT_POKEBALL_FAST_WEST] = sAnim_ExitPokeballFastWest, + [ANIM_EXIT_POKEBALL_FAST_EAST] = sAnim_ExitPokeballFastEast, +}; + +// Like the above, but has separate frames for facing right +static const union AnimCmd *const sAnimTable_Following_Asym[] = { + [ANIM_STD_FACE_SOUTH] = sAnim_FaceSouth, + [ANIM_STD_FACE_NORTH] = sAnim_FaceNorth2F, + [ANIM_STD_FACE_WEST] = sAnim_FaceWest2F, + [ANIM_STD_FACE_EAST] = sAnim_FaceEast2F_Asym, + [ANIM_STD_GO_SOUTH] = sAnim_GoSouth2F, + [ANIM_STD_GO_NORTH] = sAnim_GoNorth2F, + [ANIM_STD_GO_WEST] = sAnim_GoWest2F, + [ANIM_STD_GO_EAST] = sAnim_GoEast2F_Asym, + [ANIM_STD_GO_FAST_SOUTH] = sAnim_GoFastSouth2F, + [ANIM_STD_GO_FAST_NORTH] = sAnim_GoFastNorth2F, + [ANIM_STD_GO_FAST_WEST] = sAnim_GoFastWest2F, + [ANIM_STD_GO_FAST_EAST] = sAnim_GoFastEast2F_Asym, + [ANIM_STD_GO_FASTER_SOUTH] = sAnim_EnterSouth, + [ANIM_STD_GO_FASTER_NORTH] = sAnim_EnterNorth, + [ANIM_STD_GO_FASTER_WEST] = sAnim_EnterWest, + [ANIM_STD_GO_FASTER_EAST] = sAnim_EnterEast_Asym, + [ANIM_STD_GO_FASTEST_SOUTH] = sAnim_ExitPokeballSouth, + [ANIM_STD_GO_FASTEST_NORTH] = sAnim_ExitPokeballNorth, + [ANIM_STD_GO_FASTEST_WEST] = sAnim_ExitPokeballWest, + [ANIM_STD_GO_FASTEST_EAST] = sAnim_ExitPokeballEast_Asym, + [ANIM_EXIT_POKEBALL_FAST_SOUTH] = sAnim_ExitPokeballFastSouth, + [ANIM_EXIT_POKEBALL_FAST_NORTH] = sAnim_ExitPokeballFastNorth, + [ANIM_EXIT_POKEBALL_FAST_WEST] = sAnim_ExitPokeballFastWest, + [ANIM_EXIT_POKEBALL_FAST_EAST] = sAnim_ExitPokeballFastEast_Asym, }; static const union AnimCmd *const sAnimTable_HoOh[] = { diff --git a/src/data/object_events/object_event_graphics.h b/src/data/object_events/object_event_graphics.h index 3a658aa15e..4d1a2cae1c 100755 --- a/src/data/object_events/object_event_graphics.h +++ b/src/data/object_events/object_event_graphics.h @@ -392,6 +392,7 @@ const u32 gObjectEventPic_StrangeBall[] = INCBIN_U32("graphics/object_events/pic #endif #endif +const u32 gObjectEventPic_Substitute[] = INCBIN_U32("graphics/object_events/pics/pokemon/substitute.4bpp"); const u32 gObjectEventPic_Bulbasaur[] = INCBIN_U32("graphics/object_events/pics/pokemon/bulbasaur.4bpp"); const u32 gObjectEventPic_Ivysaur[] = INCBIN_U32("graphics/object_events/pics/pokemon/ivysaur.4bpp"); const u32 gObjectEventPic_Venusaur[] = INCBIN_U32("graphics/object_events/pics/pokemon/venusaur.4bpp"); @@ -836,6 +837,8 @@ const u32 gObjectEventPic_RayquazaCutscene[] = INCBIN_U32("graphics/object_event const u16 gObjectEventPal_HoOh[] = INCBIN_U16("graphics/object_events/palettes/ho_oh.gbapal"); const u16 gObjectEventPal_Lugia[] = INCBIN_U16("graphics/object_events/palettes/lugia.gbapal"); +const u16 gObjectEventPal_Substitute[] = INCBIN_U16("graphics/object_events/pics/pokemon/substitute.gbapal"); + const u16 gObjectEventPaletteEmotes[] = INCBIN_U16("graphics/misc/emotes.gbapal"); #if OW_MON_POKEBALLS diff --git a/src/data/object_events/object_event_graphics_info_followers.h b/src/data/object_events/object_event_graphics_info_followers.h index 6eeccde765..744df41706 100644 --- a/src/data/object_events/object_event_graphics_info_followers.h +++ b/src/data/object_events/object_event_graphics_info_followers.h @@ -1,5 +1,7 @@ // Species-indexed pokemon object event table const struct ObjectEventGraphicsInfo gPokemonObjectGraphics[] = { + // placeholder gfx, used when other gfx aren't found + [SPECIES_NONE] = {0xFFFF, OBJ_EVENT_PAL_TAG_SUBSTITUTE, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_NONE, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_Substitute, gDummySpriteAffineAnimTable}, [SPECIES_BULBASAUR] = {0xFFFF, OBJ_EVENT_PAL_TAG_DYNAMIC, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_FOOT, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_Bulbasaur, gDummySpriteAffineAnimTable}, [SPECIES_IVYSAUR] = {0xFFFF, OBJ_EVENT_PAL_TAG_DYNAMIC, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_FOOT, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_Ivysaur, gDummySpriteAffineAnimTable}, [SPECIES_VENUSAUR] = {0xFFFF, OBJ_EVENT_PAL_TAG_DYNAMIC, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_FOOT, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_Venusaur, gDummySpriteAffineAnimTable}, diff --git a/src/data/object_events/object_event_pic_tables.h b/src/data/object_events/object_event_pic_tables.h index bcd5f64e90..b2d2d15689 100755 --- a/src/data/object_events/object_event_pic_tables.h +++ b/src/data/object_events/object_event_pic_tables.h @@ -2117,6 +2117,14 @@ static const struct SpriteFrameImage sPicTable_Ball_STRANGE[] = { #endif +static const struct SpriteFrameImage sPicTable_Substitute[] = { + overworld_frame(gObjectEventPic_Substitute, 4, 4, 0), + overworld_frame(gObjectEventPic_Substitute, 4, 4, 1), + overworld_frame(gObjectEventPic_Substitute, 4, 4, 2), + overworld_frame(gObjectEventPic_Substitute, 4, 4, 3), + overworld_frame(gObjectEventPic_Substitute, 4, 4, 4), + overworld_frame(gObjectEventPic_Substitute, 4, 4, 5), +}; static const struct SpriteFrameImage sPicTable_Bulbasaur[] = { overworld_frame(gObjectEventPic_Bulbasaur, 4, 4, 0), overworld_frame(gObjectEventPic_Bulbasaur, 4, 4, 1), diff --git a/src/data/text/follower_messages.h b/src/data/text/follower_messages.h index 3fbec109cc..7678992f22 100644 --- a/src/data/text/follower_messages.h +++ b/src/data/text/follower_messages.h @@ -50,7 +50,7 @@ static const u8 sHappyMsg27[] = _("Your POKéMON is smelling the scent\nof flowe static const u8 sHappyMsg28[] = _("{STR_VAR_1} seems very happy to see\nyou!"); static const u8 sHappyMsg29[] = _("{STR_VAR_1} faced this way and\ngrinned."); static const u8 sHappyMsg30[] = _("{STR_VAR_1} happily cuddled up to\nyou!"); - // Conditional messages begin here, index 31 +// Conditional messages begin here, index 31 static const u8 sHappyMsg31[] = _("Your POKéMON seems happy about the\ngreat weather."); static const u8 sHappyMsg32[] = _("{STR_VAR_1} is very composed and\nsure of itself!"); @@ -106,7 +106,7 @@ const struct FollowerMsgInfo gFollowerNeutralMessages[] = { static const u8 sSadMsg00[] = _("{STR_VAR_1} is dizzy."); static const u8 sSadMsg01[] = _("{STR_VAR_1} is stepping on your\nfeet!"); static const u8 sSadMsg02[] = _("{STR_VAR_1} seems a little tired."); - // Conditional messages begin, index 3 +// Conditional messages begin, index 3 static const u8 sSadMsg03[] = _("{STR_VAR_1} is not happy."); static const u8 sSadMsg04[] = _("{STR_VAR_1} is going to fall down!\n"); static const u8 sSadMsg05[] = _("{STR_VAR_1} seems to be about to\nfall over!"); @@ -123,7 +123,7 @@ const struct FollowerMsgInfo gFollowerSadMessages[] = { static const u8 sUpsetMsg00[] = _("{STR_VAR_1} seems unhappy somehow…"); static const u8 sUpsetMsg01[] = _("{STR_VAR_1} is making an unhappy\nface."); static const u8 sUpsetMsg02[] = _("…Your POKéMON seems a little\ncold."); - // Conditional messages, index 3 +// Conditional messages, index 3 static const u8 sUpsetMsg03[] = _("{STR_VAR_1} is taking shelter in the\ngrass from the rain."); const struct FollowerMsgInfo gFollowerUpsetMessages[] = { @@ -225,7 +225,7 @@ static const u8 sSurpriseMsg16[] = _("{STR_VAR_1} sensed something strange\nand static const u8 sSurpriseMsg17[] = _("{STR_VAR_1} is scared and snuggled\nup to you!"); static const u8 sSurpriseMsg18[] = _("{STR_VAR_1} is feeling an unusual\npresence…"); static const u8 sSurpriseMsg19[] = _("{STR_VAR_1} is getting tense with\nnervous energy."); - // Conditional messages, index 20 +// Conditional messages, index 20 static const u8 sSurpriseMsg20[] = _("{STR_VAR_1} seems to be very\nsurprised that it is raining!"); const struct FollowerMsgInfo gFollowerSurpriseMessages[] = { diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 0c2116437f..a428dd2aed 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -520,6 +520,8 @@ const u8 gInitialMovementTypeFacingDirections[] = { // Gen VIII #define OBJ_EVENT_PAL_TAG_BALL_STRANGE 0x116A #endif +// Used as a placeholder follower graphic +#define OBJ_EVENT_PAL_TAG_SUBSTITUTE 0x7611 #define OBJ_EVENT_PAL_TAG_EMOTES 0x8002 // Not a real OW palette tag; used for the white flash applied to followers #define OBJ_EVENT_PAL_TAG_WHITE (OBJ_EVENT_PAL_TAG_NONE - 1) @@ -620,6 +622,7 @@ static const struct SpritePalette sObjectEventSpritePalettes[] = { {gObjectEventPal_StrangeBall, OBJ_EVENT_PAL_TAG_BALL_STRANGE}, #endif #endif + {gObjectEventPal_Substitute, OBJ_EVENT_PAL_TAG_SUBSTITUTE}, {gObjectEventPaletteEmotes, OBJ_EVENT_PAL_TAG_EMOTES}, {NULL, OBJ_EVENT_PAL_TAG_NONE}, }; @@ -1578,47 +1581,47 @@ static u8 TrySetupObjectEventSprite(const struct ObjectEventTemplate *objectEven static u8 TrySpawnObjectEventTemplate(const struct ObjectEventTemplate *objectEventTemplate, u8 mapNum, u8 mapGroup, s16 cameraX, s16 cameraY) { - u8 objectEventId; - struct SpriteTemplate spriteTemplate; - struct SpriteFrameImage spriteFrameImage; - const struct ObjectEventGraphicsInfo *graphicsInfo; - const struct SubspriteTable *subspriteTables = NULL; + u8 objectEventId; + struct SpriteTemplate spriteTemplate; + struct SpriteFrameImage spriteFrameImage; + const struct ObjectEventGraphicsInfo *graphicsInfo; + const struct SubspriteTable *subspriteTables = NULL; - graphicsInfo = GetObjectEventGraphicsInfo(objectEventTemplate->graphicsId); - MakeSpriteTemplateFromObjectEventTemplate(objectEventTemplate, &spriteTemplate, &subspriteTables); - spriteFrameImage.size = graphicsInfo->size; - spriteTemplate.images = &spriteFrameImage; - objectEventId = TrySetupObjectEventSprite(objectEventTemplate, &spriteTemplate, mapNum, mapGroup, cameraX, cameraY); - if (objectEventId == OBJECT_EVENTS_COUNT) - return OBJECT_EVENTS_COUNT; + graphicsInfo = GetObjectEventGraphicsInfo(objectEventTemplate->graphicsId); + MakeSpriteTemplateFromObjectEventTemplate(objectEventTemplate, &spriteTemplate, &subspriteTables); + spriteFrameImage.size = graphicsInfo->size; + spriteTemplate.images = &spriteFrameImage; + objectEventId = TrySetupObjectEventSprite(objectEventTemplate, &spriteTemplate, mapNum, mapGroup, cameraX, cameraY); + if (objectEventId == OBJECT_EVENTS_COUNT) + return OBJECT_EVENTS_COUNT; - gSprites[gObjectEvents[objectEventId].spriteId].images = graphicsInfo->images; - if (subspriteTables) - SetSubspriteTables(&gSprites[gObjectEvents[objectEventId].spriteId], subspriteTables); + gSprites[gObjectEvents[objectEventId].spriteId].images = graphicsInfo->images; + if (subspriteTables) + SetSubspriteTables(&gSprites[gObjectEvents[objectEventId].spriteId], subspriteTables); - // Set species based on script header - if (objectEventTemplate->graphicsId == OBJ_EVENT_GFX_OW_MON && objectEventTemplate->script) { - const u8 *script = objectEventTemplate->script; - if (script[0] == 0x7d) { // bufferspeciesname - u16 species; - u8 form; - bool8 shiny; - gObjectEvents[objectEventId].extra.asU16 = script[2] | script[3] << 8; - species = gObjectEvents[objectEventId].extra.mon.species; - form = gObjectEvents[objectEventId].extra.mon.form; - shiny = gObjectEvents[objectEventId].extra.mon.shiny; - FollowerSetGraphics(&gObjectEvents[objectEventId], species, form, shiny); + // Set species based on script header + if (objectEventTemplate->graphicsId == OBJ_EVENT_GFX_OW_MON && objectEventTemplate->script) { + const u8 *script = objectEventTemplate->script; + if (script[0] == 0x7d) { // bufferspeciesname + u16 species; + u8 form; + bool8 shiny; + gObjectEvents[objectEventId].extra.asU16 = script[2] | script[3] << 8; + species = gObjectEvents[objectEventId].extra.mon.species; + form = gObjectEvents[objectEventId].extra.mon.form; + shiny = gObjectEvents[objectEventId].extra.mon.shiny; + FollowerSetGraphics(&gObjectEvents[objectEventId], species, form, shiny); + } + // Set runtime species based on VAR_TEMP_4, if template has a dynamic graphics ID + } else if (objectEventTemplate->graphicsId >= OBJ_EVENT_GFX_VARS && VarGetObjectEventGraphicsId(objectEventTemplate->graphicsId - OBJ_EVENT_GFX_VARS) == OBJ_EVENT_GFX_OW_MON) { + gObjectEvents[objectEventId].extra.asU16 = VarGet(VAR_TEMP_4); + FollowerSetGraphics(&gObjectEvents[objectEventId], + gObjectEvents[objectEventId].extra.mon.species, + gObjectEvents[objectEventId].extra.mon.form, + gObjectEvents[objectEventId].extra.mon.form); } - // Set runtime species based on VAR_TEMP_4, if template has a dynamic graphics ID - } else if (objectEventTemplate->graphicsId >= OBJ_EVENT_GFX_VARS && VarGetObjectEventGraphicsId(objectEventTemplate->graphicsId - OBJ_EVENT_GFX_VARS) == OBJ_EVENT_GFX_OW_MON) { - gObjectEvents[objectEventId].extra.asU16 = VarGet(VAR_TEMP_4); - FollowerSetGraphics(&gObjectEvents[objectEventId], - gObjectEvents[objectEventId].extra.mon.species, - gObjectEvents[objectEventId].extra.mon.form, - gObjectEvents[objectEventId].extra.mon.form); - } - return objectEventId; + return objectEventId; } u8 SpawnSpecialObjectEvent(struct ObjectEventTemplate *objectEventTemplate) @@ -1781,85 +1784,126 @@ u8 CreateVirtualObject(u8 graphicsId, u8 virtualObjId, s16 x, s16 y, u8 elevatio } struct Pokemon * GetFirstLiveMon(void) { // Return address of first conscious party mon or NULL - u8 i; - for (i=0; i 0 && !(gPlayerParty[i].box.isEgg || gPlayerParty[i].box.isBadEgg)) - return &gPlayerParty[i]; - } - return NULL; + u32 i; + for (i = 0; i < PARTY_SIZE; i++) { + if (gPlayerParty[i].hp > 0 && !(gPlayerParty[i].box.isEgg || gPlayerParty[i].box.isBadEgg)) + return &gPlayerParty[i]; + } + return NULL; } struct ObjectEvent * GetFollowerObject(void) { // Return follower ObjectEvent or NULL - u8 i; - for (i=0; i < OBJECT_EVENTS_COUNT; i++) { - if (gObjectEvents[i].localId == OBJ_EVENT_ID_FOLLOWER && gObjectEvents[i].active) - return &gObjectEvents[i]; - } - return NULL; + u32 i; + for (i = 0; i < OBJECT_EVENTS_COUNT; i++) { + if (gObjectEvents[i].localId == OBJ_EVENT_ID_FOLLOWER && gObjectEvents[i].active) + return &gObjectEvents[i]; + } + return NULL; } // Return graphicsInfo for a pokemon species & form static const struct ObjectEventGraphicsInfo * SpeciesToGraphicsInfo(u16 species, u8 form) { - const struct ObjectEventGraphicsInfo *graphicsInfo; - switch (species) { + const struct ObjectEventGraphicsInfo *graphicsInfo; + switch (species) + { case SPECIES_UNOWN: // Letters >A are defined as species >= NUM_SPECIES, so are not contiguous with A - form %= NUM_UNOWN_FORMS; - graphicsInfo = &gPokemonObjectGraphics[form ? SPECIES_UNOWN_B + form - 1 : species]; - break; + form %= NUM_UNOWN_FORMS; + graphicsInfo = &gPokemonObjectGraphics[form ? SPECIES_UNOWN_B + form - 1 : species]; + break; case SPECIES_CASTFORM: // Sunny, rainy, snowy forms stored separately - graphicsInfo = &gCastformObjectGraphics[form % NUM_CASTFORM_FORMS]; - break; + graphicsInfo = &gCastformObjectGraphics[form % NUM_CASTFORM_FORMS]; + break; default: - graphicsInfo = &gPokemonObjectGraphics[species]; - break; - } - return graphicsInfo->tileTag == 0xFFFF ? graphicsInfo : &gPokemonObjectGraphics[SPECIES_PORYGON]; // avoid OOB access + graphicsInfo = &gPokemonObjectGraphics[species]; + break; + } + // Try to avoid OOB or undefined access + if (graphicsInfo->tileTag == 0 && species < NUM_SPECIES) + return &gPokemonObjectGraphics[SPECIES_NONE]; + else if (graphicsInfo->tileTag != TAG_NONE && species >= NUM_SPECIES) + return &gPokemonObjectGraphics[SPECIES_NONE]; + else + return graphicsInfo; } // Find, or load, the palette for the specified pokemon info static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool8 shiny) { - u8 paletteNum; + u32 paletteNum; // Note that the shiny palette tag is `species + SPECIES_SHINY_TAG`, which must be increased with more pokemon // so that palette tags do not overlap const struct CompressedSpritePalette *spritePalette = &(shiny ? gMonShinyPaletteTable : gMonPaletteTable)[species]; if ((paletteNum = IndexOfSpritePaletteTag(spritePalette->tag)) == 0xFF) { // Load compressed palette - LoadCompressedSpritePalette(spritePalette); - paletteNum = IndexOfSpritePaletteTag(spritePalette->tag); // Tag is always present - if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog - UpdateSpritePaletteWithWeather(paletteNum); + LoadCompressedSpritePalette(spritePalette); + paletteNum = IndexOfSpritePaletteTag(spritePalette->tag); // Tag is always present + if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog + UpdateSpritePaletteWithWeather(paletteNum); } return paletteNum; } // Set graphics & sprite for a follower object event by species & shininess. static void FollowerSetGraphics(struct ObjectEvent *objEvent, u16 species, u8 form, bool8 shiny) { - const struct ObjectEventGraphicsInfo *graphicsInfo = SpeciesToGraphicsInfo(species, form); - objEvent->graphicsId = OBJ_EVENT_GFX_OW_MON; - ObjectEventSetGraphics(objEvent, graphicsInfo); - objEvent->graphicsId = OBJ_EVENT_GFX_OW_MON; - objEvent->extra.mon.species = species; - objEvent->extra.mon.form = form; - objEvent->extra.mon.shiny = shiny; - if (graphicsInfo->paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) { // Use palette from species palette table - struct Sprite *sprite = &gSprites[objEvent->spriteId]; - // Free palette if otherwise unused - sprite->inUse = FALSE; - FieldEffectFreePaletteIfUnused(sprite->oam.paletteNum); - sprite->inUse = TRUE; - sprite->oam.paletteNum = LoadDynamicFollowerPalette(species, form, shiny); - } else if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog - UpdateSpritePaletteWithWeather(gSprites[objEvent->spriteId].oam.paletteNum); + const struct ObjectEventGraphicsInfo *graphicsInfo = SpeciesToGraphicsInfo(species, form); + objEvent->graphicsId = OBJ_EVENT_GFX_OW_MON; + ObjectEventSetGraphics(objEvent, graphicsInfo); + objEvent->graphicsId = OBJ_EVENT_GFX_OW_MON; + objEvent->extra.mon.species = species; + objEvent->extra.mon.form = form; + objEvent->extra.mon.shiny = shiny; + if (graphicsInfo->paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) { // Use palette from species palette table + struct Sprite *sprite = &gSprites[objEvent->spriteId]; + // Free palette if otherwise unused + sprite->inUse = FALSE; + FieldEffectFreePaletteIfUnused(sprite->oam.paletteNum); + sprite->inUse = TRUE; + sprite->oam.paletteNum = LoadDynamicFollowerPalette(species, form, shiny); + } else if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog + UpdateSpritePaletteWithWeather(gSprites[objEvent->spriteId].oam.paletteNum); } -// Like FollowerSetGraphics, but does not reposition sprite; intended to be used for mid-movement form changes, etc. -// TODO: Reposition sprite if size changes +// Free a sprite's current tiles and reallocate with a new size +// Used when changing to a gfx info with a larger size +static s16 ReallocSpriteTiles(struct Sprite *sprite, u32 byteSize) { + s16 i; + bool32 wasVisible = sprite->invisible; + sprite->invisible = TRUE; + + i = CopySprite(sprite, sprite->x, sprite->y, 0xFF); + if (i < MAX_SPRITES) { + DestroySprite(&gSprites[i]); + i = AllocSpriteTiles(byteSize / TILE_SIZE_4BPP); + if (i >= 0) { + // Fill the allocated area with zeroes + // To avoid visual glitches if the frame hasn't been copied yet + CpuFastFill16(0, (u8 *)OBJ_VRAM0 + TILE_SIZE_4BPP * i, byteSize); + sprite->oam.tileNum = i; + } + } else { + i = -1; + } + + sprite->invisible = wasVisible; + return i; +} + +// Like FollowerSetGraphics, but does not recenter sprite on a metatile +// Intended to be used for mid-movement form changes, etc. static void RefreshFollowerGraphics(struct ObjectEvent *objEvent) { u16 species = objEvent->extra.mon.species; u8 form = objEvent->extra.mon.form; u8 shiny = objEvent->extra.mon.shiny; const struct ObjectEventGraphicsInfo *graphicsInfo = SpeciesToGraphicsInfo(species, form); struct Sprite *sprite = &gSprites[objEvent->spriteId]; - u8 i = FindObjectEventPaletteIndexByTag(graphicsInfo->paletteTag); + u32 i = FindObjectEventPaletteIndexByTag(graphicsInfo->paletteTag); + + #if LARGE_OW_SUPPORT + // If gfx size changes, we need to reallocate tiles + if (graphicsInfo->oam->size != sprite->oam.size) { + ReallocSpriteTiles(sprite, graphicsInfo->images->size); + // Add difference in Y vectors + sprite->y += -(graphicsInfo->height >> 1) - sprite->centerToCornerVecY; + } + #endif sprite->oam.shape = graphicsInfo->oam->shape; sprite->oam.size = graphicsInfo->oam->size; @@ -1924,118 +1968,112 @@ static bool8 GetFollowerInfo(u16 *species, u8 *form, u8 *shiny) { } void UpdateFollowingPokemon(void) { // Update following pokemon if any - struct ObjectEvent *objEvent = GetFollowerObject(); - struct Sprite *sprite; - u16 species; - bool8 shiny; - u8 form; - // Avoid spawning large (>32x32) follower pokemon inside buildings - if (GetFollowerInfo(&species, &form, &shiny) && !(gMapHeader.mapType == MAP_TYPE_INDOOR && SpeciesToGraphicsInfo(species, 0)->height > 32) && !FlagGet(FLAG_TEMP_HIDE_FOLLOWER)) { + struct ObjectEvent *objEvent = GetFollowerObject(); + struct Sprite *sprite; + u16 species; + bool8 shiny; + u8 form; + // Don't spawn follower if: + // 1. GetFollowerInfo returns FALSE + // 2. Map is indoors and gfx is larger than 32x32 + // 3. flag is set + if (!GetFollowerInfo(&species, &form, &shiny) || + (gMapHeader.mapType == MAP_TYPE_INDOOR && SpeciesToGraphicsInfo(species, 0)->oam->size > ST_OAM_SIZE_2) || + FlagGet(FLAG_TEMP_HIDE_FOLLOWER)) + { + RemoveFollowingPokemon(); + return; + } + if (objEvent == NULL) { // Spawn follower - struct ObjectEventTemplate template = { - .localId = OBJ_EVENT_ID_FOLLOWER, - .graphicsId = OBJ_EVENT_GFX_OW_MON, - .flagId = 0, - .x = gSaveBlock1Ptr->pos.x, - .y = gSaveBlock1Ptr->pos.y, - // If player active, copy player elevation - .elevation = gObjectEvents[gPlayerAvatar.objectEventId].active ? gObjectEvents[gPlayerAvatar.objectEventId].currentElevation : 3, - .movementType = MOVEMENT_TYPE_FOLLOW_PLAYER, - }; - objEvent = &gObjectEvents[SpawnSpecialObjectEvent(&template)]; - objEvent->invisible = TRUE; + u32 objId = gPlayerAvatar.objectEventId; + struct ObjectEventTemplate template = { + .localId = OBJ_EVENT_ID_FOLLOWER, + .graphicsId = OBJ_EVENT_GFX_OW_MON, + .flagId = 0, + .x = gSaveBlock1Ptr->pos.x, + .y = gSaveBlock1Ptr->pos.y, + // If player active, copy player elevation + .elevation = gObjectEvents[objId].active ? gObjectEvents[objId].currentElevation : 3, + .movementType = MOVEMENT_TYPE_FOLLOW_PLAYER, + }; + if ((objId = SpawnSpecialObjectEvent(&template)) >= OBJECT_EVENTS_COUNT) + return; + objEvent = &gObjectEvents[objId]; + objEvent->invisible = TRUE; } sprite = &gSprites[objEvent->spriteId]; // Follower appearance changed; move to player and set invisible - if (species != objEvent->extra.mon.species || shiny != objEvent->extra.mon.shiny || form != objEvent->extra.mon.form) { - MoveObjectEventToMapCoords(objEvent, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y); - objEvent->invisible = TRUE; + if (species != objEvent->extra.mon.species || + shiny != objEvent->extra.mon.shiny || + form != objEvent->extra.mon.form) + { + MoveObjectEventToMapCoords(objEvent, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y); + objEvent->invisible = TRUE; } FollowerSetGraphics(objEvent, species, form, shiny); sprite->data[6] = 0; // set animation data objEvent->extra.mon.species = species; objEvent->extra.mon.shiny = shiny; objEvent->extra.mon.form = form; - } else { - RemoveFollowingPokemon(); - } } void RemoveFollowingPokemon(void) { // Remove follower object. Idempotent. - struct ObjectEvent *objectEvent = GetFollowerObject(); - if (objectEvent == NULL) - return; - RemoveObjectEvent(objectEvent); + struct ObjectEvent *objectEvent = GetFollowerObject(); + if (objectEvent == NULL) + return; + RemoveObjectEvent(objectEvent); } -static bool8 IsFollowerVisible(void) { // Determine whether follower *should* be visible - return - !(TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING | PLAYER_AVATAR_FLAG_ACRO_BIKE | PLAYER_AVATAR_FLAG_MACH_BIKE) - || MetatileBehavior_IsSurfableWaterOrUnderwater(gObjectEvents[gPlayerAvatar.objectEventId].currentMetatileBehavior) - || MetatileBehavior_IsSurfableWaterOrUnderwater(gObjectEvents[gPlayerAvatar.objectEventId].previousMetatileBehavior) - || MetatileBehavior_IsForcedMovementTile(gObjectEvents[gPlayerAvatar.objectEventId].currentMetatileBehavior) - || MetatileBehavior_IsForcedMovementTile(gObjectEvents[gPlayerAvatar.objectEventId].previousMetatileBehavior) - || gWeatherPtr->currWeather == WEATHER_UNDERWATER - || gWeatherPtr->currWeather == WEATHER_UNDERWATER_BUBBLES); +static bool32 IsFollowerVisible(void) { // Determine whether follower *should* be visible + return + !(TestPlayerAvatarFlags(FOLLOWER_INVISIBLE_FLAGS) + || MetatileBehavior_IsSurfableWaterOrUnderwater(gObjectEvents[gPlayerAvatar.objectEventId].previousMetatileBehavior) + || MetatileBehavior_IsForcedMovementTile(gObjectEvents[gPlayerAvatar.objectEventId].currentMetatileBehavior)); } static bool8 SpeciesHasType(u16 species, u8 type) { - return gSpeciesInfo[species].types[0] == type || gSpeciesInfo[species].types[1] == type; + return gSpeciesInfo[species].types[0] == type || gSpeciesInfo[species].types[1] == type; } // Returns a random index according to a list of weights static u8 RandomWeightedIndex(u8 *weights, u8 length) { - u8 i; - u16 random_value; - u16 cum_weight = 0; - for (i = 0; i < length; i++) - cum_weight += weights[i]; - random_value = Random() % cum_weight; - cum_weight = 0; - for (i = 0; i < length; i++) { - cum_weight += weights[i]; - if (random_value <= cum_weight) - return i; - } + u32 i; + u16 random_value; + u16 weightSum = 0; + for (i = 0; i < length; i++) + weightSum += weights[i]; + random_value = Random() % weightSum; + weightSum = 0; + for (i = 0; i < length; i++) { + weightSum += weights[i]; + if (random_value <= weightSum) + return i; + } } -// Pool of "unconditional" follower messages TODO: Should this be elsewhere ? -static const struct FollowerMessagePool followerBasicMessages[] = { - [FOLLOWER_EMOTION_HAPPY] = {gFollowerHappyMessages, EventScript_FollowerGeneric, N_FOLLOWER_HAPPY_MESSAGES}, - [FOLLOWER_EMOTION_NEUTRAL] = {gFollowerNeutralMessages, EventScript_FollowerGeneric, N_FOLLOWER_NEUTRAL_MESSAGES}, - [FOLLOWER_EMOTION_SAD] = {gFollowerSadMessages, EventScript_FollowerGeneric, N_FOLLOWER_SAD_MESSAGES}, - [FOLLOWER_EMOTION_UPSET] = {gFollowerUpsetMessages, EventScript_FollowerGeneric, N_FOLLOWER_UPSET_MESSAGES}, - [FOLLOWER_EMOTION_ANGRY] = {gFollowerAngryMessages, EventScript_FollowerGeneric, N_FOLLOWER_ANGRY_MESSAGES}, - [FOLLOWER_EMOTION_PENSIVE] = {gFollowerPensiveMessages, EventScript_FollowerGeneric, N_FOLLOWER_PENSIVE_MESSAGES}, - [FOLLOWER_EMOTION_LOVE] = {gFollowerLoveMessages, EventScript_FollowerGeneric, N_FOLLOWER_LOVE_MESSAGES}, - [FOLLOWER_EMOTION_SURPRISE] = {gFollowerSurpriseMessages, EventScript_FollowerGeneric, N_FOLLOWER_SURPRISE_MESSAGES}, - [FOLLOWER_EMOTION_CURIOUS] = {gFollowerCuriousMessages, EventScript_FollowerGeneric, N_FOLLOWER_CURIOUS_MESSAGES}, - [FOLLOWER_EMOTION_MUSIC] = {gFollowerMusicMessages, EventScript_FollowerGeneric, N_FOLLOWER_MUSIC_MESSAGES}, - [FOLLOWER_EMOTION_POISONED] = {gFollowerPoisonedMessages, EventScript_FollowerGeneric, N_FOLLOWER_POISONED_MESSAGES}, -}; - // Display an emote above an object event // Note that this is not a movement action static void ObjectEventEmote(struct ObjectEvent *objEvent, u8 emotion) { - emotion %= FOLLOWER_EMOTION_LENGTH; - ObjectEventGetLocalIdAndMap(objEvent, &gFieldEffectArguments[0], &gFieldEffectArguments[1], &gFieldEffectArguments[2]); - gFieldEffectArguments[7] = emotion; - FieldEffectStart(FLDEFF_EMOTE); + emotion %= FOLLOWER_EMOTION_LENGTH; + ObjectEventGetLocalIdAndMap(objEvent, &gFieldEffectArguments[0], &gFieldEffectArguments[1], &gFieldEffectArguments[2]); + gFieldEffectArguments[7] = emotion; + FieldEffectStart(FLDEFF_EMOTE); } // Script-accessible version of the above bool8 ScrFunc_emote(struct ScriptContext *ctx) { - u8 localId = ScriptReadByte(ctx); - u8 emotion = ScriptReadByte(ctx) % FOLLOWER_EMOTION_LENGTH; - u8 i = GetObjectEventIdByLocalId(localId); - if (i < OBJECT_EVENTS_COUNT) - ObjectEventEmote(&gObjectEvents[i], emotion); - return FALSE; + u8 localId = ScriptReadByte(ctx); + u8 emotion = ScriptReadByte(ctx) % FOLLOWER_EMOTION_LENGTH; + u8 i = GetObjectEventIdByLocalId(localId); + if (i < OBJECT_EVENTS_COUNT) + ObjectEventEmote(&gObjectEvents[i], emotion); + return FALSE; } struct SpecialEmote { // Used for storing conditional emotes - u16 index; - u8 emotion; + u16 index; + u8 emotion; }; // Find and return direction of metatile behavior within distance @@ -2060,184 +2098,198 @@ static u32 FindMetatileBehaviorWithinRange(s32 x, s32 y, u32 mb, u8 distance) { // Call an applicable follower message script bool8 ScrFunc_getfolloweraction(struct ScriptContext *ctx) // Essentially a big switch for follower messages { - u16 species; - s32 multi, multi2; - struct SpecialEmote cond_emotes[16] = {0}; - u8 emotion, n_choices = 0; - struct ObjectEvent *objEvent = GetFollowerObject(); - struct Pokemon *mon = GetFirstLiveMon(); - u8 emotion_weight[FOLLOWER_EMOTION_LENGTH] = { - [FOLLOWER_EMOTION_HAPPY] = 10, - [FOLLOWER_EMOTION_NEUTRAL] = 15, - [FOLLOWER_EMOTION_SAD] = 5, - [FOLLOWER_EMOTION_UPSET] = 15, - [FOLLOWER_EMOTION_ANGRY] = 15, - [FOLLOWER_EMOTION_PENSIVE] = 15, - [FOLLOWER_EMOTION_SURPRISE] = 10, // TODO: Scale this with how long the follower has been out? - [FOLLOWER_EMOTION_CURIOUS] = 10, // TODO: Increase this if there is an item nearby? - [FOLLOWER_EMOTION_MUSIC] = 15, - }; - u32 i, j; - bool32 pickedCondition = FALSE; - if (mon == NULL) { - ScriptCall(ctx, EventScript_FollowerLovesYou); - return FALSE; - } - // If map is not flyable, set the script to jump past the fly check TODO: Should followers ask to fly? - if (TRUE || !Overworld_MapTypeAllowsTeleportAndFly(gMapHeader.mapType)) + u32 species; + s32 multi, multi2; + struct SpecialEmote condEmotes[16] = {0}; + u32 condCount = 0; + u32 emotion; + struct ObjectEvent *objEvent = GetFollowerObject(); + struct Pokemon *mon = GetFirstLiveMon(); + u8 emotion_weight[FOLLOWER_EMOTION_LENGTH] = { + [FOLLOWER_EMOTION_HAPPY] = 10, + [FOLLOWER_EMOTION_NEUTRAL] = 15, + [FOLLOWER_EMOTION_SAD] = 5, + [FOLLOWER_EMOTION_UPSET] = 15, + [FOLLOWER_EMOTION_ANGRY] = 15, + [FOLLOWER_EMOTION_PENSIVE] = 15, + [FOLLOWER_EMOTION_SURPRISE] = 10, + [FOLLOWER_EMOTION_CURIOUS] = 10, + [FOLLOWER_EMOTION_MUSIC] = 15, + }; + u32 i, j; + bool32 pickedCondition = FALSE; + if (mon == NULL) { // failsafe + ScriptCall(ctx, EventScript_FollowerLovesYou); + return FALSE; + } + // Set the script to the very end; we'll be calling another script dynamically ScriptJump(ctx, EventScript_FollowerEnd); - species = GetMonData(mon, MON_DATA_SPECIES); - multi = GetMonData(mon, MON_DATA_FRIENDSHIP); - if (multi > 80) { - emotion_weight[FOLLOWER_EMOTION_HAPPY] = 20; - emotion_weight[FOLLOWER_EMOTION_UPSET] = 5; - emotion_weight[FOLLOWER_EMOTION_ANGRY] = 5; - emotion_weight[FOLLOWER_EMOTION_LOVE] = 20; - emotion_weight[FOLLOWER_EMOTION_MUSIC] = 20; - } - if (multi > 170) { - emotion_weight[FOLLOWER_EMOTION_HAPPY] = 30; - emotion_weight[FOLLOWER_EMOTION_LOVE] = 30; - } - // Conditional messages follow - // Weather-related - if (GetCurrentWeather() == WEATHER_SUNNY_CLOUDS) - cond_emotes[n_choices++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_HAPPY, .index=31}; - // Health & status-related - multi = mon->hp * 100 / mon->maxHP; - if (multi < 20) { - emotion_weight[FOLLOWER_EMOTION_SAD] = 30; - cond_emotes[n_choices++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_SAD, .index=4}; - cond_emotes[n_choices++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_SAD, .index=5}; - } - if (multi < 50 || mon->status & STATUS1_PARALYSIS) { - emotion_weight[FOLLOWER_EMOTION_SAD] = 30; - cond_emotes[n_choices++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_SAD, .index=6}; - } - // Gym type advantage/disadvantage scripts - if (GetCurrentMapMusic() == MUS_GYM || GetCurrentMapMusic() == MUS_RG_GYM) { - switch (gMapHeader.regionMapSectionId) - { - case MAPSEC_RUSTBORO_CITY: - case MAPSEC_PEWTER_CITY: - multi = TYPE_ROCK; - break; - case MAPSEC_DEWFORD_TOWN: - multi = TYPE_FIGHTING; - break; - case MAPSEC_MAUVILLE_CITY: - case MAPSEC_VERMILION_CITY: - multi = TYPE_ELECTRIC; - break; - case MAPSEC_LAVARIDGE_TOWN: - case MAPSEC_CINNABAR_ISLAND: - multi = TYPE_FIRE; - break; - case MAPSEC_PETALBURG_CITY: - multi = TYPE_NORMAL; - break; - case MAPSEC_FORTREE_CITY: - multi = TYPE_FLYING; - break; - case MAPSEC_MOSSDEEP_CITY: - case MAPSEC_SAFFRON_CITY: - multi = TYPE_PSYCHIC; - break; - case MAPSEC_SOOTOPOLIS_CITY: - case MAPSEC_CERULEAN_CITY: - multi = TYPE_WATER; - break; - case MAPSEC_CELADON_CITY: - multi = TYPE_GRASS; - break; - case MAPSEC_FUCHSIA_CITY: - multi = TYPE_POISON; - break; - case MAPSEC_VIRIDIAN_CITY: - multi = TYPE_GROUND; - break; - default: - multi = NUMBER_OF_MON_TYPES; + species = GetMonData(mon, MON_DATA_SPECIES); + multi = GetMonData(mon, MON_DATA_FRIENDSHIP); + if (multi > 80) { + emotion_weight[FOLLOWER_EMOTION_HAPPY] = 20; + emotion_weight[FOLLOWER_EMOTION_UPSET] = 5; + emotion_weight[FOLLOWER_EMOTION_ANGRY] = 5; + emotion_weight[FOLLOWER_EMOTION_LOVE] = 20; + emotion_weight[FOLLOWER_EMOTION_MUSIC] = 20; } - if (multi < NUMBER_OF_MON_TYPES) { - multi = GetTypeEffectiveness(mon, multi); - if (multi & (MOVE_RESULT_NOT_VERY_EFFECTIVE | MOVE_RESULT_DOESNT_AFFECT_FOE | MOVE_RESULT_NO_EFFECT)) - cond_emotes[n_choices++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_HAPPY, .index=32}; - else if (multi & MOVE_RESULT_SUPER_EFFECTIVE) - cond_emotes[n_choices++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_SAD, .index=7}; + if (multi > 170) { + emotion_weight[FOLLOWER_EMOTION_HAPPY] = 30; + emotion_weight[FOLLOWER_EMOTION_LOVE] = 30; + } + // Special C-based conditions follower + // Weather-related + if (GetCurrentWeather() == WEATHER_SUNNY_CLOUDS) + condEmotes[condCount++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_HAPPY, .index=31}; + // Health & status-related + multi = mon->hp * 100 / mon->maxHP; + if (multi < 20) { + emotion_weight[FOLLOWER_EMOTION_SAD] = 30; + condEmotes[condCount++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_SAD, .index=4}; + condEmotes[condCount++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_SAD, .index=5}; + } + if (multi < 50 || mon->status & STATUS1_PARALYSIS) { + emotion_weight[FOLLOWER_EMOTION_SAD] = 30; + condEmotes[condCount++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_SAD, .index=6}; + } + // Gym type advantage/disadvantage + if (GetCurrentMapMusic() == MUS_GYM || GetCurrentMapMusic() == MUS_RG_GYM) { + switch (gMapHeader.regionMapSectionId) + { + case MAPSEC_RUSTBORO_CITY: + case MAPSEC_PEWTER_CITY: + multi = TYPE_ROCK; + break; + case MAPSEC_DEWFORD_TOWN: + multi = TYPE_FIGHTING; + break; + case MAPSEC_MAUVILLE_CITY: + case MAPSEC_VERMILION_CITY: + multi = TYPE_ELECTRIC; + break; + case MAPSEC_LAVARIDGE_TOWN: + case MAPSEC_CINNABAR_ISLAND: + multi = TYPE_FIRE; + break; + case MAPSEC_PETALBURG_CITY: + multi = TYPE_NORMAL; + break; + case MAPSEC_FORTREE_CITY: + multi = TYPE_FLYING; + break; + case MAPSEC_MOSSDEEP_CITY: + case MAPSEC_SAFFRON_CITY: + multi = TYPE_PSYCHIC; + break; + case MAPSEC_SOOTOPOLIS_CITY: + case MAPSEC_CERULEAN_CITY: + multi = TYPE_WATER; + break; + case MAPSEC_CELADON_CITY: + multi = TYPE_GRASS; + break; + case MAPSEC_FUCHSIA_CITY: + multi = TYPE_POISON; + break; + case MAPSEC_VIRIDIAN_CITY: + multi = TYPE_GROUND; + break; + default: + multi = NUMBER_OF_MON_TYPES; + } + if (multi < NUMBER_OF_MON_TYPES) { + multi = GetTypeEffectiveness(mon, multi); + if (multi & (MOVE_RESULT_NOT_VERY_EFFECTIVE | MOVE_RESULT_DOESNT_AFFECT_FOE | MOVE_RESULT_NO_EFFECT)) + condEmotes[condCount++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_HAPPY, .index=32}; + else if (multi & MOVE_RESULT_SUPER_EFFECTIVE) + condEmotes[condCount++] = (struct SpecialEmote) {.emotion=FOLLOWER_EMOTION_SAD, .index=7}; + } } - } - emotion = RandomWeightedIndex(emotion_weight, FOLLOWER_EMOTION_LENGTH); - #ifdef BATTLE_ENGINE - if ((mon->status & STATUS1_PSN_ANY) && GetMonAbility(mon) != ABILITY_POISON_HEAL) - #else - if (mon->status & STATUS1_PSN_ANY) - #endif - emotion = FOLLOWER_EMOTION_POISONED; - multi = Random() % followerBasicMessages[emotion].length; - // With 50% chance, select special message using reservoir sampling - for (i = (Random() & 1) ? n_choices : 0, j = 1; i < n_choices; i++) { - if (cond_emotes[i].emotion == emotion && (Random() < 0x10000 / (j++))) // Replace item with 1/j chance - multi = cond_emotes[i].index; - } - // Match scripted conditional messages - // With 50% chance, try to match scripted conditional messages - for (i = (Random() & 1) ? COND_MSG_COUNT : 0, j = 1; i < COND_MSG_COUNT; i++) { - const struct FollowerMsgInfoExtended *info = &gFollowerConditionalMessages[i]; - if (info->stFlags == 1 && species != info->st.species) - continue; - if (info->stFlags == 2 && (info->st.types.type2 >= NUMBER_OF_MON_TYPES ? SpeciesHasType(species, info->st.types.type1) : !(SpeciesHasType(species, info->st.types.type1) || SpeciesHasType(species, info->st.types.type2)))) - continue; - if (info->stFlags == 3 && !(mon->status & info->st.status)) - continue; - if (info->mmFlags == 1 && gMapHeader.regionMapSectionId != info->mm.mapSec.mapSec) - continue; - if (info->mmFlags == 2 && !(gSaveBlock1Ptr->location.mapNum == info->mm.map.mapNum && gSaveBlock1Ptr->location.mapGroup == info->mm.map.mapGroup)) - continue; - if (info->mmFlags == 3 && !(objEvent->currentMetatileBehavior == info->mm.mb.behavior1 || objEvent->currentMetatileBehavior == info->mm.mb.behavior2)) - continue; - if (info->wtFlags == 1 && !(GetCurrentWeather() == info->wt.weather.weather1 || GetCurrentWeather() == info->wt.weather.weather2)) - continue; - if (info->wtFlags == 2 && GetCurrentMapMusic() != info->wt.song) - continue; - if (info->nearFlags == 1) { - if ((multi2 = FindMetatileBehaviorWithinRange(objEvent->currentCoords.x, objEvent->currentCoords.y, info->near.mb.behavior, info->near.mb.distance))) - gSpecialVar_Result = multi2; - else - continue; - } + emotion = RandomWeightedIndex(emotion_weight, FOLLOWER_EMOTION_LENGTH); + #ifdef BATTLE_ENGINE + if ((mon->status & STATUS1_PSN_ANY) && GetMonAbility(mon) != ABILITY_POISON_HEAL) + #else + if (mon->status & STATUS1_PSN_ANY) + #endif + emotion = FOLLOWER_EMOTION_POISONED; - // replace choice with weight/j chance - if (Random() < (0x10000 / (j++)) * (info->weight ? info->weight : 1)) { - multi = i; - pickedCondition = TRUE; - } - } - if (pickedCondition) { // conditional message was chosen - emotion = gFollowerConditionalMessages[multi].emotion; - ObjectEventEmote(objEvent, emotion); - ctx->data[0] = (u32) gFollowerConditionalMessages[multi].text; - // text choices are spread across array; pick a random one - if (gFollowerConditionalMessages[multi].textSpread) { - for (i = 0; i < 4; i++) - if (!((u32*)gFollowerConditionalMessages[multi].text)[i]) - break; - ctx->data[0] = i ? ((u32*)gFollowerConditionalMessages[multi].text)[Random() % i] : 0; - } - ScriptCall(ctx, gFollowerConditionalMessages[multi].script ? gFollowerConditionalMessages[multi].script : followerBasicMessages[emotion].script); - return FALSE; - } - ObjectEventEmote(objEvent, emotion); - ctx->data[0] = (u32) followerBasicMessages[emotion].messages[multi].text; // Load message text - ScriptCall(ctx, followerBasicMessages[emotion].messages[multi].script ? - followerBasicMessages[emotion].messages[multi].script : followerBasicMessages[emotion].script); - return FALSE; -} + // end special conditions -bool8 ScrFunc_followerfly(struct ScriptContext *ctx) { - SetMainCallback2(CB2_OpenFlyMap); - return FALSE; + // roll for basic/unconditional message + multi = Random() % gFollowerBasicMessages[emotion].length; + // (50% chance) Select special condition using reservoir sampling + for (i = (Random() & 1) ? condCount : 0, j = 1; i < condCount; i++) { + if (condEmotes[i].emotion == emotion && (Random() < 0x10000 / (j++))) // Replace each item with 1/j chance + multi = condEmotes[i].index; + } + // (50% chance) Match *scripted* conditional messages, from follower_helper.c + for (i = (Random() & 1) ? COND_MSG_COUNT : 0, j = 1; i < COND_MSG_COUNT; i++) { + const struct FollowerMsgInfoExtended *info = &gFollowerConditionalMessages[i]; + if (info->stFlags == 1 && species != info->st.species) + continue; + if (info->stFlags == 2 && + (info->st.types.type2 >= NUMBER_OF_MON_TYPES ? + SpeciesHasType(species, info->st.types.type1) : + !(SpeciesHasType(species, info->st.types.type1) || SpeciesHasType(species, info->st.types.type2))) + ) + continue; + if (info->stFlags == 3 && !(mon->status & info->st.status)) + continue; + if (info->mmFlags == 1 && gMapHeader.regionMapSectionId != info->mm.mapSec.mapSec) + continue; + if (info->mmFlags == 2 && + !(gSaveBlock1Ptr->location.mapNum == info->mm.map.mapNum && + gSaveBlock1Ptr->location.mapGroup == info->mm.map.mapGroup) + ) + continue; + if (info->mmFlags == 3 && + !(objEvent->currentMetatileBehavior == info->mm.mb.behavior1 || + objEvent->currentMetatileBehavior == info->mm.mb.behavior2) + ) + continue; + if (info->wtFlags == 1 && + !(GetCurrentWeather() == info->wt.weather.weather1 || + GetCurrentWeather() == info->wt.weather.weather2) + ) + continue; + if (info->wtFlags == 2 && GetCurrentMapMusic() != info->wt.song) + continue; + if (info->nearFlags == 1) { + if ((multi2 = FindMetatileBehaviorWithinRange(objEvent->currentCoords.x, objEvent->currentCoords.y, info->near.mb.behavior, info->near.mb.distance))) + gSpecialVar_Result = multi2; + else + continue; + } + + // replace choice with weight/j chance + if (Random() < (0x10000 / (j++)) * (info->weight ? info->weight : 1)) { + multi = i; + pickedCondition = TRUE; + } + } + // condition message was chosen + if (pickedCondition) { + emotion = gFollowerConditionalMessages[multi].emotion; + ObjectEventEmote(objEvent, emotion); + ctx->data[0] = (u32) gFollowerConditionalMessages[multi].text; + // text choices are spread across array; pick a random one + if (gFollowerConditionalMessages[multi].textSpread) { + for (i = 0; i < 4; i++) + if (!((u32*)gFollowerConditionalMessages[multi].text)[i]) + break; + ctx->data[0] = i ? ((u32*)gFollowerConditionalMessages[multi].text)[Random() % i] : 0; + } + ScriptCall(ctx, gFollowerConditionalMessages[multi].script ? gFollowerConditionalMessages[multi].script : gFollowerBasicMessages[emotion].script); + return FALSE; + } + // otherwise, a basic or C-based message was picked + ObjectEventEmote(objEvent, emotion); + ctx->data[0] = (u32) gFollowerBasicMessages[emotion].messages[multi].text; // Load message text + ScriptCall(ctx, gFollowerBasicMessages[emotion].messages[multi].script ? + gFollowerBasicMessages[emotion].messages[multi].script : + gFollowerBasicMessages[emotion].script); + return FALSE; } void TrySpawnObjectEvents(s16 cameraX, s16 cameraY) @@ -2371,9 +2423,10 @@ static void SpawnObjectEventOnReturnToField(u8 objectEventId, s16 x, s16 y) sprite->coordOffsetEnabled = TRUE; sprite->sObjEventId = objectEventId; objectEvent->spriteId = i; - if (objectEvent->graphicsId == OBJ_EVENT_GFX_OW_MON) { // Set pokemon graphics - FollowerSetGraphics(objectEvent, objectEvent->extra.mon.species, objectEvent->extra.mon.form, objectEvent->extra.mon.shiny); - } + + if (objectEvent->graphicsId == OBJ_EVENT_GFX_OW_MON) // Set pokemon graphics + FollowerSetGraphics(objectEvent, objectEvent->extra.mon.species, objectEvent->extra.mon.form, objectEvent->extra.mon.shiny); + if (!objectEvent->inanimate && objectEvent->movementType != MOVEMENT_TYPE_PLAYER) StartSpriteAnim(sprite, GetFaceDirectionAnimNum(objectEvent->facingDirection)); @@ -2405,43 +2458,47 @@ static void SetPlayerAvatarObjectEventIdAndObjectId(u8 objectEventId, u8 spriteI // Update sprite's palette, freeing old palette if necessary static u8 UpdateSpritePalette(const struct SpritePalette * spritePalette, struct Sprite * sprite) { - // Free palette if otherwise unused - sprite->inUse = FALSE; - FieldEffectFreePaletteIfUnused(sprite->oam.paletteNum); - sprite->inUse = TRUE; - return sprite->oam.paletteNum = LoadSpritePalette(spritePalette); + // Free palette if otherwise unused + sprite->inUse = FALSE; + FieldEffectFreePaletteIfUnused(sprite->oam.paletteNum); + sprite->inUse = TRUE; + return sprite->oam.paletteNum = LoadSpritePalette(spritePalette); } // Find and update based on template's paletteTag -// TODO: Add a better way to associate tags -> palettes besides listing them in sObjectEventSpritePalettes u8 UpdateSpritePaletteByTemplate(const struct SpriteTemplate * template, struct Sprite * sprite) { - u8 i = FindObjectEventPaletteIndexByTag(template->paletteTag); - if (i == 0xFF) - return i; - return UpdateSpritePalette(&sObjectEventSpritePalettes[i], sprite); + u8 i = FindObjectEventPaletteIndexByTag(template->paletteTag); + if (i == 0xFF) + return i; + return UpdateSpritePalette(&sObjectEventSpritePalettes[i], sprite); } // Set graphics *by info* static void ObjectEventSetGraphics(struct ObjectEvent *objectEvent, const struct ObjectEventGraphicsInfo *graphicsInfo) { - struct Sprite *sprite = &gSprites[objectEvent->spriteId]; - u8 i = FindObjectEventPaletteIndexByTag(graphicsInfo->paletteTag); - if (i != 0xFF) - UpdateSpritePalette(&sObjectEventSpritePalettes[i], sprite); - sprite->oam.shape = graphicsInfo->oam->shape; - sprite->oam.size = graphicsInfo->oam->size; - sprite->images = graphicsInfo->images; - sprite->anims = graphicsInfo->anims; - sprite->subspriteTables = graphicsInfo->subspriteTables; - objectEvent->inanimate = graphicsInfo->inanimate; - SetSpritePosToMapCoords(objectEvent->currentCoords.x, objectEvent->currentCoords.y, &sprite->x, &sprite->y); - sprite->centerToCornerVecX = -(graphicsInfo->width >> 1); - sprite->centerToCornerVecY = -(graphicsInfo->height >> 1); - sprite->x += 8; - sprite->y += 16 + sprite->centerToCornerVecY; - if (objectEvent->trackedByCamera) - { - CameraObjectReset1(); - } + struct Sprite *sprite = &gSprites[objectEvent->spriteId]; + u32 i = FindObjectEventPaletteIndexByTag(graphicsInfo->paletteTag); + if (i != 0xFF) + UpdateSpritePalette(&sObjectEventSpritePalettes[i], sprite); + + #if LARGE_OW_SUPPORT + // If gfx size changes, we need to reallocate tiles + if (graphicsInfo->oam->size != sprite->oam.size) + ReallocSpriteTiles(sprite, graphicsInfo->images->size); + #endif + + sprite->oam.shape = graphicsInfo->oam->shape; + sprite->oam.size = graphicsInfo->oam->size; + sprite->images = graphicsInfo->images; + sprite->anims = graphicsInfo->anims; + sprite->subspriteTables = graphicsInfo->subspriteTables; + objectEvent->inanimate = graphicsInfo->inanimate; + SetSpritePosToMapCoords(objectEvent->currentCoords.x, objectEvent->currentCoords.y, &sprite->x, &sprite->y); + sprite->centerToCornerVecX = -(graphicsInfo->width >> 1); + sprite->centerToCornerVecY = -(graphicsInfo->height >> 1); + sprite->x += 8; + sprite->y += 16 + sprite->centerToCornerVecY; + if (objectEvent->trackedByCamera) + CameraObjectReset1(); } void ObjectEventSetGraphicsId(struct ObjectEvent *objectEvent, u8 graphicsId) @@ -2483,26 +2540,24 @@ void PlayerObjectTurn(struct PlayerAvatar *playerAvatar, u8 direction) } static void SetBerryTreeGraphics(struct ObjectEvent *objectEvent, u8 berryId, u8 berryStage) { - const u8 graphicsId = gBerryTreeObjectEventGraphicsIdTablePointers[berryId][berryStage]; - const struct ObjectEventGraphicsInfo *graphicsInfo = GetObjectEventGraphicsInfo(graphicsId); - struct Sprite *sprite = &gSprites[objectEvent->spriteId]; - UpdateSpritePalette(&sObjectEventSpritePalettes[gBerryTreePaletteSlotTablePointers[berryId][berryStage]-2], sprite); - sprite->oam.shape = graphicsInfo->oam->shape; - sprite->oam.size = graphicsInfo->oam->size; - sprite->images = gBerryTreePicTablePointers[berryId]; - sprite->anims = graphicsInfo->anims; - sprite->subspriteTables = graphicsInfo->subspriteTables; - objectEvent->inanimate = graphicsInfo->inanimate; - objectEvent->graphicsId = graphicsId; - SetSpritePosToMapCoords(objectEvent->currentCoords.x, objectEvent->currentCoords.y, &sprite->x, &sprite->y); - sprite->centerToCornerVecX = -(graphicsInfo->width >> 1); - sprite->centerToCornerVecY = -(graphicsInfo->height >> 1); - sprite->x += 8; - sprite->y += 16 + sprite->centerToCornerVecY; - if (objectEvent->trackedByCamera) - { - CameraObjectReset1(); - } + const u8 graphicsId = gBerryTreeObjectEventGraphicsIdTablePointers[berryId][berryStage]; + const struct ObjectEventGraphicsInfo *graphicsInfo = GetObjectEventGraphicsInfo(graphicsId); + struct Sprite *sprite = &gSprites[objectEvent->spriteId]; + UpdateSpritePalette(&sObjectEventSpritePalettes[gBerryTreePaletteSlotTablePointers[berryId][berryStage]-2], sprite); + sprite->oam.shape = graphicsInfo->oam->shape; + sprite->oam.size = graphicsInfo->oam->size; + sprite->images = gBerryTreePicTablePointers[berryId]; + sprite->anims = graphicsInfo->anims; + sprite->subspriteTables = graphicsInfo->subspriteTables; + objectEvent->inanimate = graphicsInfo->inanimate; + objectEvent->graphicsId = graphicsId; + SetSpritePosToMapCoords(objectEvent->currentCoords.x, objectEvent->currentCoords.y, &sprite->x, &sprite->y); + sprite->centerToCornerVecX = -(graphicsInfo->width >> 1); + sprite->centerToCornerVecY = -(graphicsInfo->height >> 1); + sprite->x += 8; + sprite->y += 16 + sprite->centerToCornerVecY; + if (objectEvent->trackedByCamera) + CameraObjectReset1(); } static void get_berry_tree_graphics(struct ObjectEvent *objectEvent, struct Sprite *sprite) @@ -4962,7 +5017,7 @@ bool8 CopyablePlayerMovement_Jump2(struct ObjectEvent *objectEvent, struct Sprit return TRUE; } -static bool8 EndFollowerTransformEffect(struct ObjectEvent *objectEvent, struct Sprite *sprite) { +static bool32 EndFollowerTransformEffect(struct ObjectEvent *objectEvent, struct Sprite *sprite) { if (!sprite) return FALSE; SetGpuReg(REG_OFFSET_MOSAIC, 0); @@ -4973,7 +5028,7 @@ static bool8 EndFollowerTransformEffect(struct ObjectEvent *objectEvent, struct return FALSE; } -static bool8 TryStartFollowerTransformEffect(struct ObjectEvent *objectEvent, struct Sprite *sprite) { +static bool32 TryStartFollowerTransformEffect(struct ObjectEvent *objectEvent, struct Sprite *sprite) { u32 multi; if (objectEvent->extra.mon.species == SPECIES_CASTFORM && objectEvent->extra.mon.form != (multi = GetOverworldCastformForm())) { sprite->data[7] = TRANSFORM_TYPE_PERMANENT << 8; @@ -5035,10 +5090,10 @@ bool8 MovementType_FollowPlayer_Shadow(struct ObjectEvent *objectEvent, struct S { ClearObjectEventMovement(objectEvent, sprite); if (!IsFollowerVisible()) { // Shadow player's position - objectEvent->invisible = TRUE; - MoveObjectEventToMapCoords(objectEvent, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y); - objectEvent->triggerGroundEffectsOnMove = FALSE; // Stop endless reflection spawning - return FALSE; + objectEvent->invisible = TRUE; + MoveObjectEventToMapCoords(objectEvent, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y); + objectEvent->triggerGroundEffectsOnMove = FALSE; // Stop endless reflection spawning + return FALSE; } // Move follower to player, in case we end up in the shadowing state for only 1 frame // This way the player cannot talk to the invisible follower before it appears @@ -5064,7 +5119,6 @@ bool8 MovementType_FollowPlayer_Active(struct ObjectEvent *objectEvent, struct S sprite->sTypeFuncId = 2; // movement action sets state to 0 return TRUE; } - // TODO: Remove dependence on PlayerGetCopyableMovement return gFollowPlayerMovementFuncs[PlayerGetCopyableMovement()](objectEvent, sprite, GetPlayerMovementDirection(), NULL); } @@ -5079,12 +5133,11 @@ bool8 MovementType_FollowPlayer_Moving(struct ObjectEvent *objectEvent, struct S if (ObjectEventExecSingleMovementAction(objectEvent, sprite)) { #endif objectEvent->singleMovementActive = 0; - if (sprite->sTypeFuncId) { // restore nonzero state + if (sprite->sTypeFuncId) // restore nonzero state sprite->sTypeFuncId = 1; - } } else if (objectEvent->movementActionId < MOVEMENT_ACTION_EXIT_POKEBALL) { UpdateFollowerTransformEffect(objectEvent, sprite); - #if OW_MON_BOBBING == TRUE + #if OW_MON_BOBBING if ((sprite->data[5] & 7) == 2) sprite->y2 ^= -1; #endif @@ -5105,7 +5158,7 @@ bool8 FollowablePlayerMovement_Idle(struct ObjectEvent *objectEvent, struct Spri { // finish movement action objectEvent->singleMovementActive = 0; } - #if OW_MON_BOBBING == TRUE + #if OW_MON_BOBBING else if ((sprite->data[3] & 7) == 2) sprite->y2 ^= -1; #endif @@ -5129,29 +5182,29 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri x = gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x; y = gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y; - if ((x == targetX && y == targetY) || !IsFollowerVisible()) { // don't move on player collision or if not visible - return FALSE; - } + if ((x == targetX && y == targetY) || !IsFollowerVisible()) // don't move on player collision or if not visible + return FALSE; + x = objectEvent->currentCoords.x; y = objectEvent->currentCoords.y; ClearObjectEventMovement(objectEvent, sprite); if (objectEvent->invisible) { // Animate exiting pokeball - // Player is jumping, but follower is invisible - if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2) { - sprite->sTypeFuncId = 0; // return to shadowing state - return FALSE; - } - MoveObjectEventToMapCoords(objectEvent, targetX, targetY); - ObjectEventSetSingleMovement(objectEvent, sprite, MOVEMENT_ACTION_EXIT_POKEBALL); - objectEvent->singleMovementActive = 1; - sprite->sTypeFuncId = 2; - #if OW_MON_BOBBING == TRUE - sprite->y2 = 0; - #endif - return TRUE; + // Player is jumping, but follower is invisible + if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2) { + sprite->sTypeFuncId = 0; // return to shadowing state + return FALSE; + } + MoveObjectEventToMapCoords(objectEvent, targetX, targetY); + ObjectEventSetSingleMovement(objectEvent, sprite, MOVEMENT_ACTION_EXIT_POKEBALL); + objectEvent->singleMovementActive = 1; + sprite->sTypeFuncId = 2; + #if OW_MON_BOBBING + sprite->y2 = 0; + #endif + return TRUE; } else if (x == targetX && y == targetY) { // don't move if already in the player's last position - return FALSE; + return FALSE; } // Follow player @@ -5174,7 +5227,7 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction)); else { objectEvent->movementActionId = GetWalkNormalMovementAction(direction); - #if OW_MON_BOBBING == TRUE + #if OW_MON_BOBBING sprite->y2 = -1; #endif } @@ -5190,7 +5243,7 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction)); else { ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkNormalMovementAction(direction)); - #if OW_MON_BOBBING == TRUE + #if OW_MON_BOBBING sprite->y2 = -1; #endif } @@ -5627,7 +5680,7 @@ bool8 ScrFunc_IsFollowerFieldMoveUser(struct ScriptContext *ctx) { if (follower && obj && !obj->invisible) { u16 followIndex = ((u32)follower - (u32)gPlayerParty) / sizeof(struct Pokemon); *var = userIndex == followIndex; - } else + } return FALSE; } @@ -6747,10 +6800,10 @@ bool8 MovementAction_WalkInPlaceSlowDown_Step0(struct ObjectEvent *objectEvent, // Update sprite with a palette filled with a solid color static u8 LoadFillColorPalette(u16 color, u16 paletteTag, struct Sprite *sprite) { - u16 paletteData[16]; - struct SpritePalette dynamicPalette = {.tag = paletteTag, .data = paletteData}; - CpuFill16(color, paletteData, PLTT_SIZE_4BPP); - return UpdateSpritePalette(&dynamicPalette, sprite); + u16 paletteData[16]; + struct SpritePalette dynamicPalette = {.tag = paletteTag, .data = paletteData}; + CpuFill16(color, paletteData, PLTT_SIZE_4BPP); + return UpdateSpritePalette(&dynamicPalette, sprite); } static void ObjectEventSetPokeballGfx(struct ObjectEvent *objEvent) { @@ -6773,19 +6826,24 @@ static void ObjectEventSetPokeballGfx(struct ObjectEvent *objEvent) { ObjectEventSetGraphicsId(objEvent, OBJ_EVENT_GFX_ANIMATED_BALL); } +#define sDuration data[3] +#define sSpeedFlip data[6] + bool8 MovementAction_ExitPokeball_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite) { - u8 direction = gObjectEvents[gPlayerAvatar.objectEventId].facingDirection; + u32 direction = gObjectEvents[gPlayerAvatar.objectEventId].facingDirection; objectEvent->invisible = FALSE; if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH)) { // If player is dashing, the pokemon must come out faster - StartSpriteAnimInDirection(objectEvent, sprite, direction, GetMoveDirectionFastestAnimNum(direction) + 4); - sprite->data[3] = 8; // duration - sprite->data[6] = 0; // fast speed + StartSpriteAnimInDirection(objectEvent, sprite, direction, GetJumpSpecialDirectionAnimNum(direction)); + sprite->sDuration = 8; + sprite->sSpeedFlip = 0; // fast speed } else { StartSpriteAnimInDirection(objectEvent, sprite, direction, GetMoveDirectionFastestAnimNum(direction)); - sprite->data[3] = 16; // duration - sprite->data[6] = 1; // slow speed + sprite->sDuration = 16; + sprite->sSpeedFlip = 1; // normal speed } - sprite->data[6] |= (direction == DIR_EAST ? 1 : 0) << 4; + // If mon's right-facing sprite is h-flipped, we need to use a different affine anim + if (direction == DIR_EAST && sprite->anims[ANIM_STD_FACE_EAST]->frame.hFlip) + sprite->sSpeedFlip |= 1 << 4; ObjectEventSetPokeballGfx(objectEvent); objectEvent->inanimate = FALSE; return MovementAction_ExitPokeball_Step1(objectEvent, sprite); @@ -6837,32 +6895,30 @@ static const union AffineAnimCmd *const sAffineAnims_PokeballFollower[] = bool8 MovementAction_ExitPokeball_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite) { - u8 duration = sprite->data[6] & 0xF; - sprite->data[3]--; - if (sprite->data[3] == 0) + // for different speeds, anim steps occur on different frame #s + u32 animStepFrame = (sprite->sSpeedFlip & 1) ? 7 : 3; // 0 -> 3, 1 -> 7 + if (--sprite->sDuration == 0) { sprite->sActionFuncId = 2; sprite->animCmdIndex = 0; sprite->animPaused = TRUE; return TRUE; // Set graphics, palette, and affine animation - } else if ((duration == 0 && sprite->data[3] == 3) || (duration == 1 && sprite->data[3] == 7)) { + } else if (sprite->sDuration == animStepFrame) { FollowerSetGraphics(objectEvent, objectEvent->extra.mon.species, objectEvent->extra.mon.form, objectEvent->extra.mon.shiny); LoadFillColorPalette(RGB_WHITE, OBJ_EVENT_PAL_TAG_WHITE, sprite); // Initialize affine animation sprite->affineAnims = sAffineAnims_PokeballFollower; #if LARGE_OW_SUPPORT - if (IS_POW_OF_TWO(-sprite->centerToCornerVecX)) { - #endif - sprite->affineAnims = sAffineAnims_PokeballFollower; - sprite->oam.affineMode = ST_OAM_AFFINE_NORMAL; - InitSpriteAffineAnim(sprite); - StartSpriteAffineAnim(sprite, sprite->data[6] >> 4); - #if LARGE_OW_SUPPORT - } + if (!IS_POW_OF_TWO(-sprite->centerToCornerVecX)) + return FALSE; #endif + sprite->affineAnims = sAffineAnims_PokeballFollower; + sprite->oam.affineMode = ST_OAM_AFFINE_NORMAL; + InitSpriteAffineAnim(sprite); + StartSpriteAffineAnim(sprite, sprite->sSpeedFlip >> 4); // Restore original palette & disable affine - } else if ((duration == 0 && sprite->data[3] == 1) || (duration == 1 && sprite->data[3] == 3)) { + } else if (sprite->sDuration == (animStepFrame >> 1)) { sprite->affineAnimEnded = TRUE; FreeSpriteOamMatrix(sprite); sprite->oam.affineMode = ST_OAM_AFFINE_OFF; @@ -6872,36 +6928,37 @@ bool8 MovementAction_ExitPokeball_Step1(struct ObjectEvent *objectEvent, struct } bool8 MovementAction_EnterPokeball_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite) { - u8 direction = objectEvent->facingDirection; + u32 direction = objectEvent->facingDirection; StartSpriteAnimInDirection(objectEvent, sprite, direction, GetMoveDirectionFasterAnimNum(direction)); - sprite->data[3] = 16; // duration - sprite->data[6] = direction == DIR_EAST ? 3 : 2; // affine animation number + sprite->sDuration = 16; + // If mon's right-facing sprite is h-flipped, we need to use a different affine anim + if (direction == DIR_EAST && sprite->anims[ANIM_STD_FACE_EAST]->frame.hFlip) + sprite->sSpeedFlip = 3; + else + sprite->sSpeedFlip = 2; EndFollowerTransformEffect(objectEvent, sprite); return MovementAction_EnterPokeball_Step1(objectEvent, sprite); } bool8 MovementAction_EnterPokeball_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite) { - sprite->data[3]--; - if (sprite->data[3] == 0) { - sprite->data[2] = 2; + if (--sprite->sDuration == 0) { + sprite->sActionFuncId = 2; return FALSE; - } else if (sprite->data[3] == 11) { // Set palette to white & start affine + } else if (sprite->sDuration == 11) { // Set palette to white & start affine LoadFillColorPalette(RGB_WHITE, OBJ_EVENT_PAL_TAG_WHITE, sprite); + sprite->subspriteTableNum = 0; #if LARGE_OW_SUPPORT // Only do affine if sprite width is power of 2 // (effect looks weird on sprites composed of subsprites like 48x48, etc) - if (IS_POW_OF_TWO(-sprite->centerToCornerVecX)) { + if (!IS_POW_OF_TWO(-sprite->centerToCornerVecX)) + return FALSE; #endif - sprite->affineAnims = sAffineAnims_PokeballFollower; - sprite->oam.affineMode = ST_OAM_AFFINE_NORMAL; - InitSpriteAffineAnim(sprite); - StartSpriteAffineAnim(sprite, sprite->data[6]); - #if LARGE_OW_SUPPORT - } - #endif - sprite->subspriteTableNum = 0; - } else if (sprite->data[3] == 7) { // Free white palette and change to pokeball, disable affine + sprite->affineAnims = sAffineAnims_PokeballFollower; + sprite->oam.affineMode = ST_OAM_AFFINE_NORMAL; + InitSpriteAffineAnim(sprite); + StartSpriteAffineAnim(sprite, sprite->sSpeedFlip); + } else if (sprite->sDuration == 7) { // Free white palette and change to pokeball, disable affine sprite->affineAnimEnded = TRUE; FreeSpriteOamMatrix(sprite); sprite->oam.affineMode = ST_OAM_AFFINE_OFF; @@ -6915,12 +6972,15 @@ bool8 MovementAction_EnterPokeball_Step2(struct ObjectEvent *objectEvent, struct { FollowerSetGraphics(objectEvent, objectEvent->extra.mon.species, objectEvent->extra.mon.form, objectEvent->extra.mon.shiny); objectEvent->invisible = TRUE; - sprite->data[1] = 0; - sprite->data[6] = 0; + sprite->sTypeFuncId = 0; + sprite->sSpeedFlip = 0; sprite->animPaused = TRUE; return TRUE; } +#undef sDuration +#undef sSpeedFlip + bool8 MovementAction_WalkInPlaceSlowUp_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite) { InitMoveInPlace(objectEvent, sprite, DIR_NORTH, GetMoveDirectionAnimNum(DIR_NORTH), 32); @@ -8927,19 +8987,16 @@ static void UpdateObjectEventElevationAndPriority(struct ObjectEvent *objEvent, return; ObjectEventUpdateElevation(objEvent, sprite); - #if LARGE_OW_SUPPORT if (objEvent->localId == OBJ_EVENT_ID_FOLLOWER) { + #if LARGE_OW_SUPPORT // keep subspriteMode synced with player's // so that it disappears under bridges when they do sprite->subspriteMode |= gSprites[gPlayerAvatar.spriteId].subspriteMode & SUBSPRITES_IGNORE_PRIORITY; + #endif + // if transitioning between elevations, use the player's elevation if (!objEvent->currentElevation) objEvent = &gObjectEvents[gPlayerAvatar.objectEventId]; } - #else - // if transitioning between elevations, use the player's elevation - if (!objEvent->currentElevation && objEvent->localId == OBJ_EVENT_ID_FOLLOWER) - objEvent = &gObjectEvents[gPlayerAvatar.objectEventId]; - #endif sprite->subspriteTableNum = sElevationToSubspriteTableNum[objEvent->previousElevation]; sprite->oam.priority = sElevationToPriority[objEvent->previousElevation]; @@ -9320,7 +9377,7 @@ static void (*const sGroundEffectFuncs[])(struct ObjectEvent *objEvent, struct S }; static void GroundEffect_Shadow(struct ObjectEvent *objEvent, struct Sprite *sprite) { - SetUpShadow(objEvent, sprite); + SetUpShadow(objEvent, sprite); } static void DoFlaggedGroundEffects(struct ObjectEvent *objEvent, struct Sprite *sprite, u32 flags) diff --git a/src/field_effect_helpers.c b/src/field_effect_helpers.c index 7dd55a6262..2d6b7978a4 100755 --- a/src/field_effect_helpers.c +++ b/src/field_effect_helpers.c @@ -55,10 +55,10 @@ u32 FldEff_Shadow(void); #define sIsStillReflection data[7] void SetUpShadow(struct ObjectEvent *objectEvent, struct Sprite *sprite) { - gFieldEffectArguments[0] = objectEvent->localId; - gFieldEffectArguments[1] = gSaveBlock1Ptr->location.mapNum; - gFieldEffectArguments[2] = gSaveBlock1Ptr->location.mapGroup; - FldEff_Shadow(); + gFieldEffectArguments[0] = objectEvent->localId; + gFieldEffectArguments[1] = gSaveBlock1Ptr->location.mapNum; + gFieldEffectArguments[2] = gSaveBlock1Ptr->location.mapGroup; + FldEff_Shadow(); } void SetUpReflection(struct ObjectEvent *objectEvent, struct Sprite *sprite, bool8 stillReflection) diff --git a/src/follower_helper.c b/src/follower_helper.c index 2dbf51dadf..ace1eec45c 100644 --- a/src/follower_helper.c +++ b/src/follower_helper.c @@ -295,3 +295,18 @@ const struct FollowerMsgInfoExtended gFollowerConditionalMessages[COND_MSG_COUNT .emotion = FOLLOWER_EMOTION_SAD, }, }; + +// Pool of "unconditional" follower messages +const struct FollowerMessagePool gFollowerBasicMessages[FOLLOWER_EMOTION_LENGTH] = { + [FOLLOWER_EMOTION_HAPPY] = {gFollowerHappyMessages, EventScript_FollowerGeneric, N_FOLLOWER_HAPPY_MESSAGES}, + [FOLLOWER_EMOTION_NEUTRAL] = {gFollowerNeutralMessages, EventScript_FollowerGeneric, N_FOLLOWER_NEUTRAL_MESSAGES}, + [FOLLOWER_EMOTION_SAD] = {gFollowerSadMessages, EventScript_FollowerGeneric, N_FOLLOWER_SAD_MESSAGES}, + [FOLLOWER_EMOTION_UPSET] = {gFollowerUpsetMessages, EventScript_FollowerGeneric, N_FOLLOWER_UPSET_MESSAGES}, + [FOLLOWER_EMOTION_ANGRY] = {gFollowerAngryMessages, EventScript_FollowerGeneric, N_FOLLOWER_ANGRY_MESSAGES}, + [FOLLOWER_EMOTION_PENSIVE] = {gFollowerPensiveMessages, EventScript_FollowerGeneric, N_FOLLOWER_PENSIVE_MESSAGES}, + [FOLLOWER_EMOTION_LOVE] = {gFollowerLoveMessages, EventScript_FollowerGeneric, N_FOLLOWER_LOVE_MESSAGES}, + [FOLLOWER_EMOTION_SURPRISE] = {gFollowerSurpriseMessages, EventScript_FollowerGeneric, N_FOLLOWER_SURPRISE_MESSAGES}, + [FOLLOWER_EMOTION_CURIOUS] = {gFollowerCuriousMessages, EventScript_FollowerGeneric, N_FOLLOWER_CURIOUS_MESSAGES}, + [FOLLOWER_EMOTION_MUSIC] = {gFollowerMusicMessages, EventScript_FollowerGeneric, N_FOLLOWER_MUSIC_MESSAGES}, + [FOLLOWER_EMOTION_POISONED] = {gFollowerPoisonedMessages, EventScript_FollowerGeneric, N_FOLLOWER_POISONED_MESSAGES}, +}; diff --git a/src/scrcmd.c b/src/scrcmd.c index abef4a85fe..bb163f650a 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -2074,9 +2074,9 @@ bool8 ScrCmd_playmoncry(struct ScriptContext *ctx) bool8 ScrFunc_playfirstmoncry(struct ScriptContext *ctx) { - u16 species = GetMonData(GetFirstLiveMon(), MON_DATA_SPECIES); - PlayCry_Script(species, 0); - return FALSE; + u16 species = GetMonData(GetFirstLiveMon(), MON_DATA_SPECIES); + PlayCry_Script(species, 0); + return FALSE; } bool8 ScrCmd_waitmoncry(struct ScriptContext *ctx) diff --git a/src/trainer_see.c b/src/trainer_see.c index 95b11447f2..ffa365ef95 100644 --- a/src/trainer_see.c +++ b/src/trainer_see.c @@ -152,28 +152,28 @@ static const struct SpriteFrameImage sSpriteImageTable_HeartIcon[] = static const struct SpriteFrameImage sSpriteImageTable_Emotes[] = { - { .data = (u8 *)sEmotion_Gfx+0*0x80, .size = 0x80}, // FOLLOWER_EMOTION_HAPPY - { .data = (u8 *)sEmotion_Gfx+1*0x80, .size = 0x80}, // FOLLOWER_EMOTION_HAPPY - { .data = (u8 *)sEmotion_Gfx+2*0x80, .size = 0x80}, // FOLLOWER_EMOTION_NEUTRAL - { .data = (u8 *)sEmotion_Gfx+3*0x80, .size = 0x80}, // FOLLOWER_EMOTION_NEUTRAL - { .data = (u8 *)sEmotion_Gfx+4*0x80, .size = 0x80}, // FOLLOWER_EMOTION_SAD - { .data = (u8 *)sEmotion_Gfx+5*0x80, .size = 0x80}, // FOLLOWER_EMOTION_SAD - { .data = (u8 *)sEmotion_Gfx+6*0x80, .size = 0x80}, // FOLLOWER_EMOTION_UPSET - { .data = (u8 *)sEmotion_Gfx+7*0x80, .size = 0x80}, // FOLLOWER_EMOTION_UPSET - { .data = (u8 *)sEmotion_Gfx+8*0x80, .size = 0x80}, // FOLLOWER_EMOTION_ANGRY - { .data = (u8 *)sEmotion_Gfx+9*0x80, .size = 0x80}, // FOLLOWER_EMOTION_ANGRY - { .data = (u8 *)sEmotion_Gfx+10*0x80, .size = 0x80}, // FOLLOWER_EMOTION_PENSIVE - { .data = (u8 *)sEmotion_Gfx+11*0x80, .size = 0x80}, // FOLLOWER_EMOTION_PENSIVE - { .data = (u8 *)sEmotion_Gfx+12*0x80, .size = 0x80}, // FOLLOWER_EMOTION_LOVE - { .data = (u8 *)sEmotion_Gfx+13*0x80, .size = 0x80}, // FOLLOWER_EMOTION_LOVE - { .data = (u8 *)sEmotion_Gfx+14*0x80, .size = 0x80}, // FOLLOWER_EMOTION_SURPRISE - { .data = (u8 *)sEmotion_Gfx+15*0x80, .size = 0x80}, // FOLLOWER_EMOTION_SURPRISE - { .data = (u8 *)sEmotion_Gfx+16*0x80, .size = 0x80}, // FOLLOWER_EMOTION_CURIOUS - { .data = (u8 *)sEmotion_Gfx+17*0x80, .size = 0x80}, // FOLLOWER_EMOTION_CURIOUS - { .data = (u8 *)sEmotion_Gfx+18*0x80, .size = 0x80}, // FOLLOWER_EMOTION_MUSIC - { .data = (u8 *)sEmotion_Gfx+19*0x80, .size = 0x80}, // FOLLOWER_EMOTION_MUSIC - { .data = (u8 *)sEmotion_Gfx+20*0x80, .size = 0x80}, // FOLLOWER_EMOTION_POISONED - { .data = (u8 *)sEmotion_Gfx+21*0x80, .size = 0x80}, // FOLLOWER_EMOTION_POISONED + overworld_frame(sEmotion_Gfx, 2, 2, 0), // FOLLOWER_EMOTION_HAPPY + overworld_frame(sEmotion_Gfx, 2, 2, 1), // FOLLOWER_EMOTION_HAPPY + overworld_frame(sEmotion_Gfx, 2, 2, 2), // FOLLOWER_EMOTION_NEUTRAL + overworld_frame(sEmotion_Gfx, 2, 2, 3), // FOLLOWER_EMOTION_NEUTRAL + overworld_frame(sEmotion_Gfx, 2, 2, 4), // FOLLOWER_EMOTION_SAD + overworld_frame(sEmotion_Gfx, 2, 2, 5), // FOLLOWER_EMOTION_SAD + overworld_frame(sEmotion_Gfx, 2, 2, 6), // FOLLOWER_EMOTION_UPSET + overworld_frame(sEmotion_Gfx, 2, 2, 7), // FOLLOWER_EMOTION_UPSET + overworld_frame(sEmotion_Gfx, 2, 2, 8), // FOLLOWER_EMOTION_ANGRY + overworld_frame(sEmotion_Gfx, 2, 2, 9), // FOLLOWER_EMOTION_ANGRY + overworld_frame(sEmotion_Gfx, 2, 2, 10), // FOLLOWER_EMOTION_PENSIVE + overworld_frame(sEmotion_Gfx, 2, 2, 11), // FOLLOWER_EMOTION_PENSIVE + overworld_frame(sEmotion_Gfx, 2, 2, 12), // FOLLOWER_EMOTION_LOVE + overworld_frame(sEmotion_Gfx, 2, 2, 13), // FOLLOWER_EMOTION_LOVE + overworld_frame(sEmotion_Gfx, 2, 2, 14), // FOLLOWER_EMOTION_SURPRISE + overworld_frame(sEmotion_Gfx, 2, 2, 15), // FOLLOWER_EMOTION_SURPRISE + overworld_frame(sEmotion_Gfx, 2, 2, 16), // FOLLOWER_EMOTION_CURIOUS + overworld_frame(sEmotion_Gfx, 2, 2, 17), // FOLLOWER_EMOTION_CURIOUS + overworld_frame(sEmotion_Gfx, 2, 2, 18), // FOLLOWER_EMOTION_MUSIC + overworld_frame(sEmotion_Gfx, 2, 2, 19), // FOLLOWER_EMOTION_MUSIC + overworld_frame(sEmotion_Gfx, 2, 2, 20), // FOLLOWER_EMOTION_POISONED + overworld_frame(sEmotion_Gfx, 2, 2, 21), // FOLLOWER_EMOTION_POISONED }; static const union AnimCmd sSpriteAnim_Emotes0[] = {