diff --git a/asm/macros/event.inc b/asm/macros/event.inc index a5e37967c6..e460f8e2ff 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -1823,9 +1823,10 @@ .4byte \text .endm - @ Equivalent to fadescreen but copies gPlttBufferUnfaded to an allocated buffer on the fade out - @ and the reverse on the fade in, in effect saving gPlttBufferUnfaded to restore it. - @ If nowait set, does not wait for the fade to complete + @ Equivalent to fadescreen but uses a hardware fade and darken/lighten blend modes, + @ to avoid modifying palettes at all. + @ Useful for fade-out/fade-in without leaving the overworld or entering a new scene. + @ If nowait set, doesn't wait for the fade to complete .macro fadescreenswapbuffers mode:req, nowait=0 .byte SCR_OP_FADESCREENSWAPBUFFERS .byte \mode @@ -2539,3 +2540,11 @@ compare \a, \b cant_see_if 5 .endm + + @ hide any follower pokemon if present, + @ putting them into their pokeball; + @ by default waits for their movement to finish + .macro hidefollower wait=1 + callfunc ScrFunc_hidefollower + .2byte \wait + .endm diff --git a/asm/macros/field_effect_script.inc b/asm/macros/field_effect_script.inc index 38f7e31750..68d8502a9d 100644 --- a/asm/macros/field_effect_script.inc +++ b/asm/macros/field_effect_script.inc @@ -1,3 +1,4 @@ +#include "constants/field_weather.h" @ The first .byte argument of each macro below is an index into gFieldEffectScriptFuncs .macro field_eff_loadtiles address:req @@ -5,9 +6,10 @@ .4byte \address .endm - .macro field_eff_loadfadedpal address:req + .macro field_eff_loadfadedpal address:req, color_map_type=COLOR_MAP_DARK_CONTRAST .byte 1 .4byte \address + .byte \color_map_type .endm .macro field_eff_loadpal address:req @@ -24,10 +26,11 @@ .byte 4 .endm - .macro field_eff_loadgfx_callnative tiles_address:req, palette_address:req, function_address:req + .macro field_eff_loadgfx_callnative tiles_address:req, palette_address:req, function_address:req, color_map_type=COLOR_MAP_DARK_CONTRAST .byte 5 .4byte \tiles_address .4byte \palette_address + .byte \color_map_type .4byte \function_address .endm @@ -37,8 +40,9 @@ .4byte \function_address .endm - .macro field_eff_loadfadedpal_callnative palette_address:req, function_address:req + .macro field_eff_loadfadedpal_callnative palette_address:req, function_address:req, color_map_type=COLOR_MAP_DARK_CONTRAST .byte 7 .4byte \palette_address + .byte \color_map_type .4byte \function_address .endm diff --git a/data/scripts/field_move_scripts.inc b/data/scripts/field_move_scripts.inc index f1bd3618e1..30393b771d 100644 --- a/data/scripts/field_move_scripts.inc +++ b/data/scripts/field_move_scripts.inc @@ -118,7 +118,6 @@ EventScript_FollowerSwap: return EventScript_FollowerMoveNorth: - applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkUp applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkDown waitmovement 0 applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceUp @@ -126,7 +125,6 @@ EventScript_FollowerMoveNorth: return EventScript_FollowerMoveEast: - applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkRight applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkLeft waitmovement 0 applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceRight @@ -134,7 +132,6 @@ EventScript_FollowerMoveEast: return EventScript_FollowerMoveSouth: - applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkDown applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkUp waitmovement 0 applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceDown @@ -142,7 +139,6 @@ EventScript_FollowerMoveSouth: return EventScript_FollowerMoveWest: - applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkLeft applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkRight waitmovement 0 applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceLeft diff --git a/data/scripts/pkmn_center_nurse.inc b/data/scripts/pkmn_center_nurse.inc index 4aca9d768b..ef33e09395 100644 --- a/data/scripts/pkmn_center_nurse.inc +++ b/data/scripts/pkmn_center_nurse.inc @@ -33,6 +33,7 @@ EventScript_PkmnCenterNurse_IllTakeYourPkmn2:: return EventScript_PkmnCenterNurse_TakeAndHealPkmn:: + hidefollower 0 applymovement VAR_0x800B, Movement_PkmnCenterNurse_Turn @ Changed from Common_Movement_WalkInPlaceFasterLeft to force the follower to enter their Poké Ball waitmovement 0 dofieldeffect FLDEFF_POKECENTER_HEAL diff --git a/data/scripts/std_msgbox.inc b/data/scripts/std_msgbox.inc index c46da56cbf..40026c8ea0 100644 --- a/data/scripts/std_msgbox.inc +++ b/data/scripts/std_msgbox.inc @@ -8,11 +8,13 @@ Std_MsgboxNPC: return Std_MsgboxSign: + setflag FLAG_SAFE_FOLLOWER_MOVEMENT lockall message NULL waitmessage waitbuttonpress releaseall + clearflag FLAG_SAFE_FOLLOWER_MOVEMENT return Std_MsgboxDefault: diff --git a/include/config/overworld.h b/include/config/overworld.h index a9a4f93727..4ed3b251cf 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -60,6 +60,33 @@ #define OW_FOLLOWERS_WEATHER_FORMS FALSE // If TRUE, Castform and Cherrim gain FORM_CHANGE_OVERWORLD_WEATHER, which will make them transform in the overworld based on the weather. #define OW_FOLLOWERS_COPY_WILD_PKMN FALSE // If TRUE, follower Pokémon that know Transform or have Illusion/Imposter will copy wild Pokémon at random. +// New/old handling for followers during scripts; +// TRUE: Script collisions hide follower, FLAG_SAFE_FOLLOWER_MOVEMENT on by default +// (scripted player movement moves follower too!) +// FALSE: Script collisions unhandled, FLAG_SAFE_FOLLOWER_MOVEMENT off by default +#define OW_MON_SCRIPT_MOVEMENT TRUE + +// If set, the only pokemon allowed to follow you +// will be those matching species, met location, +// and/or met level; +// These accept vars, too: VAR_TEMP_1, etc +#define OW_MON_ALLOWED_SPECIES (0) +#define OW_MON_ALLOWED_MET_LVL (0) +#define OW_MON_ALLOWED_MET_LOC (0) +// Examples: +// Yellow Pikachu: +// #define OW_MON_ALLOWED_SPECIES (SPECIES_PIKACHU) +// #define OW_MON_ALLOWED_MET_LVL (0) +// #define OW_MON_ALLOWED_MET_LOC (MAPSEC_PALLET_TOWN) +// Hoenn Starter: +// #define OW_MON_ALLOWED_SPECIES (0) +// #define OW_MON_ALLOWED_MET_LVL (5) +// #define OW_MON_ALLOWED_MET_LOC (MAPSEC_ROUTE_101) +// Species set in VAR_XXXX: +// #define OW_MON_ALLOWED_SPECIES (VAR_XXXX) +// #define OW_MON_ALLOWED_MET_LVL (0) +// #define OW_MON_ALLOWED_MET_LOC (0) + // Out-of-battle Ability effects #define OW_SYNCHRONIZE_NATURE GEN_LATEST // In Gen8+, if a Pokémon with Synchronize leads the party, wild Pokémon will always have their same Nature as opposed to the 50% chance in previous games. Gift Pokémon excluded. // In USUM (here GEN_7), if a Pokémon with Synchronize leads the party, gift Pokémon will always have their same Nature regardless of their Egg Group. diff --git a/include/constants/field_weather.h b/include/constants/field_weather.h index d4f17e261c..4f0e05d54d 100644 --- a/include/constants/field_weather.h +++ b/include/constants/field_weather.h @@ -3,6 +3,14 @@ #include "config/overworld.h" +// sPaletteColorMapTypes & field_effect_scripts +enum +{ + COLOR_MAP_NONE, + COLOR_MAP_DARK_CONTRAST, + COLOR_MAP_CONTRAST, +}; + #define MAX_RAIN_SPRITES 24 #define NUM_CLOUD_SPRITES 3 #define NUM_FOG_HORIZONTAL_SPRITES 20 diff --git a/include/constants/flags.h b/include/constants/flags.h index b1fcae5eb7..9450c6de1c 100644 --- a/include/constants/flags.h +++ b/include/constants/flags.h @@ -1647,7 +1647,9 @@ #define FLAG_ENABLE_MULTI_CORRIDOR_DOOR (SPECIAL_FLAGS_START + 0x2) #define FLAG_SPECIAL_FLAG_UNUSED_0x4003 (SPECIAL_FLAGS_START + 0x3) // Unused Flag #define FLAG_STORING_ITEMS_IN_PYRAMID_BAG (SPECIAL_FLAGS_START + 0x4) -#define FLAG_SAFE_FOLLOWER_MOVEMENT (SPECIAL_FLAGS_START + 0x5) // When set, applymovement does not put the follower inside a pokeball +// When set, `applymovement` does not hide follower pokemon; +// Also, scripted movements on the player will move follower(s), too +#define FLAG_SAFE_FOLLOWER_MOVEMENT (SPECIAL_FLAGS_START + 0x5) // FLAG_SPECIAL_FLAG_0x4005 - 0x407F also exist and are unused #define SPECIAL_FLAGS_END (SPECIAL_FLAGS_START + 0x7F) #define NUM_SPECIAL_FLAGS (SPECIAL_FLAGS_END - SPECIAL_FLAGS_START + 1) diff --git a/include/event_object_movement.h b/include/event_object_movement.h index d7999ee1f9..81db48d3a9 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -190,6 +190,7 @@ u8 GetWalkInPlaceFastMovementAction(u32); u8 GetWalkInPlaceNormalMovementAction(u32); u8 GetWalkInPlaceSlowMovementAction(u32); u8 GetCollisionAtCoords(struct ObjectEvent *, s16 x, s16 y, u32 dir); +u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords); void MoveCoords(u8 direction, s16 *x, s16 *y); bool8 ObjectEventIsHeldMovementActive(struct ObjectEvent *); u8 ObjectEventClearHeldMovementIfFinished(struct ObjectEvent *); diff --git a/include/field_weather.h b/include/field_weather.h index 02cc560e45..a8cbcc572a 100644 --- a/include/field_weather.h +++ b/include/field_weather.h @@ -148,6 +148,7 @@ void SetCurrentAndNextWeatherNoDelay(u8 weather); void ApplyWeatherColorMapIfIdle(s8 colorMapIndex); void ApplyWeatherColorMapIfIdle_Gradual(u8 colorMapIndex, u8 targetColorMapIndex, u8 colorMapStepDelay); void FadeScreen(u8 mode, s8 delay); +u16 FadeScreenHardware(u8 mode, s8 delay); bool8 IsWeatherNotFadingIn(void); void UpdateSpritePaletteWithWeather(u8 spritePaletteIndex, bool8 allowFog); void ApplyWeatherColorMapToPals(u8 startPalIndex, u8 numPalettes); @@ -165,7 +166,9 @@ void PlayRainStoppingSoundEffect(void); u8 IsWeatherChangeComplete(void); void SetWeatherScreenFadeOut(void); void SetWeatherPalStateIdle(void); +const u8* SetPaletteColorMapType(u8 paletteIndex, u8 colorMapType); void PreservePaletteInWeather(u8 preservedPalIndex); +void ResetPaletteColorMapType(u8 paletteIndex); void ResetPreservedPalettesInWeather(void); bool32 IsWeatherAlphaBlend(void); diff --git a/include/gba/io_reg.h b/include/gba/io_reg.h index db219aeb35..aebee69665 100644 --- a/include/gba/io_reg.h +++ b/include/gba/io_reg.h @@ -599,6 +599,7 @@ #define BLDCNT_EFFECT_BLEND (1 << 6) // 1st+2nd targets mixed (controlled by BLDALPHA) #define BLDCNT_EFFECT_LIGHTEN (2 << 6) // 1st target becomes whiter (controlled by BLDY) #define BLDCNT_EFFECT_DARKEN (3 << 6) // 1st target becomes blacker (controlled by BLDY) +#define BLDCNT_EFFECT_EFF_MASK (3 << 6) // mask to check effect // Bits 8-13 select layers for the 2nd target #define BLDCNT_TGT2_BG0 (1 << 8) #define BLDCNT_TGT2_BG1 (1 << 9) @@ -611,6 +612,8 @@ // BLDALPHA #define BLDALPHA_BLEND(target1, target2) (((target2) << 8) | (target1)) +#define BLDALPHA_TGT1(bld) ((bld) & 0x1F) +#define BLDALPHA_TGT2(bld) (((bld) >> 8) & 0x1F) // SOUNDCNT_H #define SOUND_CGB_MIX_QUARTER 0x0000 diff --git a/include/overworld.h b/include/overworld.h index 2107a00b50..f716b51015 100644 --- a/include/overworld.h +++ b/include/overworld.h @@ -26,6 +26,11 @@ #define SKIP_OBJECT_EVENT_LOAD 1 +// trigger a time-of-day blend once +#define HOURS_BLEND_ONCE 25 +// don't update currentTimeBlend +#define HOURS_FREEZE_BLEND 26 + struct InitialPlayerAvatarState { u8 transitionFlags; @@ -40,15 +45,7 @@ struct LinkPlayerObjectEvent u8 movementMode; }; -struct __attribute__((packed)) TimeBlendSettings -{ - u16 weight:9; - u16 finalTimeOfDay:3; - u16 initialTimeOfDay:3; - u16 unused:1; - u16 altWeight; -}; - +// Exported RAM declarations extern struct WarpData gLastUsedWarp; extern struct LinkPlayerObjectEvent gLinkPlayerObjectEvents[4]; @@ -63,7 +60,7 @@ extern u8 gFieldLinkPlayerCount; extern bool8 gExitStairsMovementDisabled; extern bool8 gSkipShowMonAnim; extern u8 gTimeOfDay; -extern u16 gTimeUpdateCounter; +extern s16 gTimeUpdateCounter; extern struct TimeBlendSettings currentTimeBlend; @@ -173,6 +170,7 @@ bool32 Overworld_RecvKeysFromLinkIsRunning(void); bool32 Overworld_SendKeysToLinkIsRunning(void); bool32 IsSendingKeysOverCable(void); void ClearLinkPlayerObjectEvents(void); +bool16 SetTimeOfDay(u16 hours); // Item Description Headers enum ItemObtainFlags diff --git a/include/palette.h b/include/palette.h index 4d918e9001..ab61068e73 100644 --- a/include/palette.h +++ b/include/palette.h @@ -46,6 +46,13 @@ struct BlendSettings u32 unused:2; }; +struct __attribute__((packed)) TimeBlendSettings { + struct BlendSettings bld0; + struct BlendSettings bld1; + u16 weight; + u16 altWeight; +}; + struct PaletteFadeControl { u32 multipurpose1; // This field needs to exist or errors will occur diff --git a/include/sprite.h b/include/sprite.h index 8f69aba8c6..64ea88b293 100644 --- a/include/sprite.h +++ b/include/sprite.h @@ -303,8 +303,7 @@ void FreeOamMatrix(u8 matrixNum); void InitSpriteAffineAnim(struct Sprite *sprite); void SetOamMatrixRotationScaling(u8 matrixNum, s16 xScale, s16 yScale, u16 rotation); u16 LoadSpriteSheet(const struct SpriteSheet *sheet); -u16 LoadSpriteSheetByTemplateWithOffset(const struct SpriteTemplate *template, u32 frame, s32 offset); -u16 LoadSpriteSheetByTemplate(const struct SpriteTemplate *template, u8 frame); +u16 LoadSpriteSheetByTemplate(const struct SpriteTemplate *template, u32 frame, s32 offset); void LoadSpriteSheets(const struct SpriteSheet *sheets); s16 AllocSpriteTiles(u16 tileCount); u16 AllocTilesForSpriteSheet(struct SpriteSheet *sheet); diff --git a/src/battle_controllers.c b/src/battle_controllers.c index 856da30d1b..c7d7230342 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -2061,18 +2061,23 @@ static void SetBattlerMonData(u32 battler, struct Pokemon *party, u32 monId) HandleLowHpMusicChange(&party[gBattlerPartyIndexes[battler]], battler); } -// In normal singles, if follower pokemon is out, have it slide in instead of being thrown -static bool8 ShouldDoSlideInAnim(void) +// In normal singles, if follower Pokémon exists, and the Pokémon following is being sent out, have it slide in instead of being thrown +static bool8 ShouldDoSlideInAnim(u32 battler) { struct ObjectEvent *followerObj = GetFollowerObject(); if (!followerObj || followerObj->invisible) return FALSE; + if (gBattleTypeFlags & ( BATTLE_TYPE_LINK | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_WALLY_TUTORIAL | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_INGAME_PARTNER | BATTLE_TYPE_RECORDED | BATTLE_TYPE_TRAINER_HILL) ) return FALSE; + + if (GetFirstLiveMon() != &gPlayerParty[gBattlerPartyIndexes[battler]]) + return FALSE; + return TRUE; } @@ -2847,7 +2852,7 @@ void BtlController_HandleIntroTrainerBallThrow(u32 battler, u16 tagTrainerPal, c if (side == B_SIDE_PLAYER) { StoreSpriteCallbackInData6(&gSprites[gBattleStruct->trainerSlideSpriteIds[battler]], SpriteCB_FreePlayerSpriteLoadMonSprite); - StartSpriteAnim(&gSprites[gBattleStruct->trainerSlideSpriteIds[battler]], ShouldDoSlideInAnim() ? 2 : 1); + StartSpriteAnim(&gSprites[gBattleStruct->trainerSlideSpriteIds[battler]], ShouldDoSlideInAnim(battler) ? 2 : 1); paletteNum = AllocSpritePalette(tagTrainerPal); LoadCompressedPalette(trainerPal, OBJ_PLTT_ID(paletteNum), PLTT_SIZE_4BPP); @@ -2906,17 +2911,17 @@ static void Task_StartSendOutAnim(u8 taskId) if (TwoMonsAtSendOut(battler)) { gBattleResources->bufferA[battler][1] = gBattlerPartyIndexes[battler]; - StartSendOutAnim(battler, FALSE, FALSE, ShouldDoSlideInAnim()); + StartSendOutAnim(battler, FALSE, FALSE, ShouldDoSlideInAnim(battler)); battlerPartner = battler ^ BIT_FLANK; gBattleResources->bufferA[battlerPartner][1] = gBattlerPartyIndexes[battlerPartner]; BattleLoadMonSpriteGfx(&gPlayerParty[gBattlerPartyIndexes[battlerPartner]], battlerPartner); - StartSendOutAnim(battlerPartner, FALSE, FALSE, ShouldDoSlideInAnim()); + StartSendOutAnim(battlerPartner, FALSE, FALSE, ShouldDoSlideInAnim(battler)); } else { gBattleResources->bufferA[battler][1] = gBattlerPartyIndexes[battler]; - StartSendOutAnim(battler, FALSE, FALSE, ShouldDoSlideInAnim()); + StartSendOutAnim(battler, FALSE, FALSE, ShouldDoSlideInAnim(battler)); } gBattlerControllerFuncs[battler] = (void*)(GetWordTaskArg(taskId, tControllerFunc_1)); DestroyTask(taskId); diff --git a/src/decompress.c b/src/decompress.c index e90b665366..8c2f335614 100644 --- a/src/decompress.c +++ b/src/decompress.c @@ -73,7 +73,7 @@ u32 LoadCompressedSpriteSheetByTemplate(const struct SpriteTemplate *template, s // Check for LZ77 header and read uncompressed size, or fallback if not compressed (zero size) if ((size = IsLZ77Data(template->images->data, TILE_SIZE_4BPP, MAX_DECOMPRESSION_BUFFER_SIZE)) == 0) - return LoadSpriteSheetByTemplateWithOffset(template, 0, offset); + return LoadSpriteSheetByTemplate(template, 0, offset); void *buffer = malloc_and_decompress(template->images->data, NULL); myImage.data = buffer; @@ -81,7 +81,7 @@ u32 LoadCompressedSpriteSheetByTemplate(const struct SpriteTemplate *template, s myTemplate.images = &myImage; myTemplate.tileTag = template->tileTag; - u32 ret = LoadSpriteSheetByTemplateWithOffset(&myTemplate, 0, offset); + u32 ret = LoadSpriteSheetByTemplate(&myTemplate, 0, offset); Free(buffer); return ret; } diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 28ba7fc83c..cc5d11e10a 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -187,7 +187,7 @@ static void SetSpriteDataForNormalStep(struct Sprite *, u8, u8); static void InitSpriteForFigure8Anim(struct Sprite *); static bool8 AnimateSpriteInFigure8(struct Sprite *); u8 GetDirectionToFace(s16 x1, s16 y1, s16 x2, s16 y2); -static void FollowerSetGraphics(struct ObjectEvent *, u16, u8, bool8); +static void FollowerSetGraphics(struct ObjectEvent *, u16, u8, bool8, bool8); static void ObjectEventSetGraphics(struct ObjectEvent *, const struct ObjectEventGraphicsInfo *); static void SpriteCB_VirtualObject(struct Sprite *); static void DoShadowFieldEffect(struct ObjectEvent *); @@ -379,7 +379,7 @@ static const bool8 sMovementTypeHasRange[NUM_MOVEMENT_TYPES] = { [MOVEMENT_TYPE_COPY_PLAYER_CLOCKWISE_IN_GRASS] = TRUE, }; -const u8 gInitialMovementTypeFacingDirections[] = { +const u8 gInitialMovementTypeFacingDirections[NUM_MOVEMENT_TYPES] = { [MOVEMENT_TYPE_NONE] = DIR_SOUTH, [MOVEMENT_TYPE_LOOK_AROUND] = DIR_SOUTH, [MOVEMENT_TYPE_WANDER_AROUND] = DIR_SOUTH, @@ -461,6 +461,7 @@ const u8 gInitialMovementTypeFacingDirections[] = { [MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_UP] = DIR_NORTH, [MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_LEFT] = DIR_WEST, [MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_RIGHT] = DIR_EAST, + [MOVEMENT_TYPE_FOLLOW_PLAYER] = DIR_SOUTH, }; #include "data/object_events/object_event_graphics_info_pointers.h" @@ -1999,6 +2000,14 @@ struct Pokemon *GetFirstLiveMon(void) u32 i; for (i = 0; i < PARTY_SIZE; i++) { + struct Pokemon *mon = &gPlayerParty[i]; + if ((OW_MON_ALLOWED_SPECIES && GetMonData(mon, MON_DATA_SPECIES_OR_EGG) != VarGet(OW_MON_ALLOWED_SPECIES)) + || (OW_MON_ALLOWED_MET_LVL && GetMonData(mon, MON_DATA_MET_LEVEL) != VarGet(OW_MON_ALLOWED_MET_LVL)) + || (OW_MON_ALLOWED_MET_LOC && GetMonData(mon, MON_DATA_MET_LOCATION) != VarGet(OW_MON_ALLOWED_MET_LOC)) + ) { + continue; + } + if (gPlayerParty[i].hp > 0 && !(gPlayerParty[i].box.isEgg || gPlayerParty[i].box.isBadEgg)) return &gPlayerParty[i]; } @@ -2121,14 +2130,14 @@ static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool32 shiny) } // Set graphics & sprite for a follower object event by species & shininess. -static void FollowerSetGraphics(struct ObjectEvent *objEvent, u16 species, u8 form, bool8 shiny) +static void FollowerSetGraphics(struct ObjectEvent *objEvent, u16 species, u8 form, bool8 shiny, bool8 doPalette) { const struct ObjectEventGraphicsInfo *graphicsInfo = SpeciesToGraphicsInfo(species, form); ObjectEventSetGraphics(objEvent, graphicsInfo); objEvent->graphicsId = (OBJ_EVENT_GFX_MON_BASE + species) & OBJ_EVENT_GFX_SPECIES_MASK; objEvent->graphicsId |= form << OBJ_EVENT_GFX_SPECIES_BITS; objEvent->shiny = shiny; - if (graphicsInfo->paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) // Use palette from species palette table + if (graphicsInfo->paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC && doPalette) // Use palette from species palette table { struct Sprite *sprite = &gSprites[objEvent->spriteId]; // Free palette if otherwise unused @@ -2137,10 +2146,6 @@ static void FollowerSetGraphics(struct ObjectEvent *objEvent, u16 species, u8 fo 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, FALSE); - } } // Like FollowerSetGraphics, but does not recenter sprite on a metatile @@ -2289,10 +2294,8 @@ void UpdateFollowingPokemon(void) // Follower appearance changed; move to player and set invisible if (species != OW_SPECIES(objEvent) || shiny != objEvent->shiny || form != OW_FORM(objEvent)) { - MoveObjectEventToMapCoords(objEvent, - gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x, - gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y); - FollowerSetGraphics(objEvent, species, form, shiny); + MoveObjectEventToMapCoords(objEvent, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y); + FollowerSetGraphics(objEvent, species, form, shiny, TRUE); objEvent->invisible = TRUE; } sprite->data[6] = 0; // set animation data @@ -2658,6 +2661,7 @@ void UpdateLightSprite(struct Sprite *sprite) return; } + // Note: Don't set window registers during hardware fade! switch (sprite->sLightType) { default: @@ -2703,7 +2707,7 @@ static void SpawnLightSprite(s16 x, s16 y, s16 camX, s16 camY, u32 lightType) } lightType = min(lightType, ARRAY_COUNT(gFieldEffectLightTemplates) - 1); // bounds checking template = gFieldEffectLightTemplates[lightType]; - LoadSpriteSheetByTemplate(template, 0); + LoadSpriteSheetByTemplate(template, 0, 0); sprite = &gSprites[CreateSprite(template, 0, 0, 0)]; if (lightType == 0 && (i = IndexOfSpritePaletteTag(template->paletteTag + 1)) < 16) sprite->oam.paletteNum = i; @@ -5680,6 +5684,7 @@ bool8 MovementType_FollowPlayer_Moving(struct ObjectEvent *objectEvent, struct S objectEvent->movementActionId = MOVEMENT_ACTION_NONE; sprite->sActionFuncId = 0; objectEvent->singleMovementActive = FALSE; + objectEvent->facingDirectionLocked = FALSE; if (sprite->sTypeFuncId) // restore nonzero state sprite->sTypeFuncId = 1; } @@ -5749,9 +5754,8 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri if (objectEvent->invisible) { // Animate exiting pokeball - // Player is jumping, but follower is invisible - if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2) - { + // don't emerge if player is jumping or moving via script + if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2 || ArePlayerFieldControlsLocked()) { sprite->sTypeFuncId = 0; // return to shadowing state return FALSE; } @@ -5771,60 +5775,35 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri // Follow player direction = GetDirectionToFace(x, y, targetX, targetY); + // During a script, if player sidesteps or backsteps, + // mirror player's direction instead + if (ArePlayerFieldControlsLocked() + && gObjectEvents[gPlayerAvatar.objectEventId].facingDirection != gObjectEvents[gPlayerAvatar.objectEventId].movementDirection + ) { + direction = gObjectEvents[gPlayerAvatar.objectEventId].movementDirection; + objectEvent->facingDirectionLocked = TRUE; + } + MoveCoords(direction, &x, &y); GetCollisionAtCoords(objectEvent, x, y, direction); // Sets directionOverwrite for stairs if (GetLedgeJumpDirection(x, y, direction) != DIR_NONE) - { // InitJumpRegular will set the proper speed ObjectEventSetSingleMovement(objectEvent, sprite, GetJump2MovementAction(direction)); - } - else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH)) - { - // Set follow speed according to player's speed - if (playerAction >= MOVEMENT_ACTION_RUN_DOWN_SLOW && playerAction <= MOVEMENT_ACTION_RUN_RIGHT_SLOW) + else if (playerAction >= MOVEMENT_ACTION_WALK_SLOW_DOWN && playerAction <= MOVEMENT_ACTION_WALK_SLOW_RIGHT) { + if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH)) // on sideways stairs objectEvent->movementActionId = GetWalkNormalMovementAction(direction); else - objectEvent->movementActionId = GetWalkFastMovementAction(direction); - } - else if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2) - { - ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction)); - } - else - { - if (playerAction >= MOVEMENT_ACTION_WALK_SLOW_DOWN && playerAction <= MOVEMENT_ACTION_WALK_SLOW_RIGHT) - { ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction)); - } - else - { - objectEvent->movementActionId = GetWalkNormalMovementAction(direction); - if (OW_FOLLOWERS_BOBBING == TRUE) - sprite->y2 = -1; - } - } - sprite->sActionFuncId = 0; - if (GetLedgeJumpDirection(x, y, direction) != DIR_NONE) - { - // InitJumpRegular will set the proper speed - ObjectEventSetSingleMovement(objectEvent, sprite, GetJump2MovementAction(direction)); - } - else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH)) - { - // Set follow speed according to player's speed - ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkFastMovementAction(direction)); - } - else if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2) - { - // If *player* jumps, make step take twice as long + } else if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2) { ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction)); - } - else - { - ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkNormalMovementAction(direction)); + } else if (gSprites[gPlayerAvatar.spriteId].data[4] == MOVE_SPEED_FAST_1) { + objectEvent->movementActionId = GetWalkFastMovementAction(direction); + } else { + objectEvent->movementActionId = GetWalkNormalMovementAction(direction); if (OW_FOLLOWERS_BOBBING == TRUE) sprite->y2 = -1; } + sprite->sActionFuncId = 0; objectEvent->singleMovementActive = TRUE; sprite->sTypeFuncId = 2; return TRUE; @@ -6488,13 +6467,17 @@ static bool8 IsMetatileDirectionallyImpassable(struct ObjectEvent *objectEvent, return FALSE; } -static bool8 DoesObjectCollideWithObjectAt(struct ObjectEvent *objectEvent, s16 x, s16 y) -{ +u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords) { u8 i; struct ObjectEvent *curObject; if (objectEvent->localId == OBJ_EVENT_ID_FOLLOWER) - return FALSE; // follower cannot collide with other objects, but they can collide with it + return OBJECT_EVENTS_COUNT; // follower cannot collide with other objects, but they can collide with it + + if (addCoords) { + x += objectEvent->currentCoords.x; + y += objectEvent->currentCoords.y; + } for (i = 0; i < OBJECT_EVENTS_COUNT; i++) { @@ -6505,11 +6488,16 @@ static bool8 DoesObjectCollideWithObjectAt(struct ObjectEvent *objectEvent, s16 if ((curObject->currentCoords.x == x && curObject->currentCoords.y == y) || (curObject->previousCoords.x == x && curObject->previousCoords.y == y)) { if (AreElevationsCompatible(objectEvent->currentElevation, curObject->currentElevation)) - return TRUE; + return i; } } } - return FALSE; + return OBJECT_EVENTS_COUNT; +} + +static bool8 DoesObjectCollideWithObjectAt(struct ObjectEvent *objectEvent, s16 x, s16 y) +{ + return (GetObjectObjectCollidesWith(objectEvent, x, y, FALSE) < OBJECT_EVENTS_COUNT); } bool8 IsBerryTreeSparkling(u8 localId, u8 mapNum, u8 mapGroup) @@ -6661,6 +6649,19 @@ static u8 TryUpdateMovementActionOnStairs(struct ObjectEvent *objectEvent, u8 mo } } +static const u8 sActionIdToCopyableMovement[] = { + [MOVEMENT_ACTION_FACE_DOWN ... MOVEMENT_ACTION_FACE_RIGHT] = COPY_MOVE_FACE, + [MOVEMENT_ACTION_WALK_SLOW_DOWN ... MOVEMENT_ACTION_WALK_NORMAL_RIGHT] = COPY_MOVE_WALK, + [MOVEMENT_ACTION_JUMP_2_DOWN ... MOVEMENT_ACTION_JUMP_2_RIGHT] = COPY_MOVE_JUMP2, + [MOVEMENT_ACTION_WALK_FAST_DOWN ... MOVEMENT_ACTION_WALK_FAST_RIGHT] = COPY_MOVE_WALK, + [MOVEMENT_ACTION_RIDE_WATER_CURRENT_DOWN ... MOVEMENT_ACTION_PLAYER_RUN_RIGHT] = COPY_MOVE_WALK, + // Not a typo; follower needs to take an action with a duration == JUMP's, + // and JUMP2 here will lead to WALK_SLOW later + [MOVEMENT_ACTION_JUMP_DOWN ... MOVEMENT_ACTION_JUMP_RIGHT] = COPY_MOVE_JUMP2, + + [MOVEMENT_ACTION_NONE] = COPY_MOVE_NONE, +}; + bool8 ObjectEventSetHeldMovement(struct ObjectEvent *objectEvent, u8 movementActionId) { if (ObjectEventIsMovementOverridden(objectEvent)) @@ -6673,6 +6674,16 @@ bool8 ObjectEventSetHeldMovement(struct ObjectEvent *objectEvent, u8 movementAct objectEvent->heldMovementActive = TRUE; objectEvent->heldMovementFinished = FALSE; gSprites[objectEvent->spriteId].sActionFuncId = 0; + + // When player is moved via script, set copyable movement + // for any followers via a lookup table + if (ArePlayerFieldControlsLocked() && + objectEvent->isPlayer && + FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT)) + { + objectEvent->playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId]; + } + return FALSE; } @@ -6696,6 +6707,15 @@ void ObjectEventClearHeldMovement(struct ObjectEvent *objectEvent) objectEvent->heldMovementFinished = FALSE; gSprites[objectEvent->spriteId].sTypeFuncId = 0; gSprites[objectEvent->spriteId].sActionFuncId = 0; + + // When player is moved via script, set copyable movement + // for any followers via a lookup table + if (ArePlayerFieldControlsLocked() && + objectEvent->isPlayer && + FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT)) + { + objectEvent->playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId]; + } } u8 ObjectEventCheckHeldMovementStatus(struct ObjectEvent *objectEvent) @@ -7683,7 +7703,7 @@ bool8 MovementAction_ExitPokeball_Step1(struct ObjectEvent *objectEvent, struct // Set graphics, palette, and affine animation else if (sprite->sDuration == animStepFrame) { - FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny); + FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny, FALSE); LoadFillColorPalette(RGB_WHITE, OBJ_EVENT_PAL_TAG_WHITE, sprite); // Initialize affine animation sprite->affineAnims = sAffineAnims_PokeballFollower; @@ -7700,7 +7720,7 @@ bool8 MovementAction_ExitPokeball_Step1(struct ObjectEvent *objectEvent, struct sprite->affineAnimEnded = TRUE; FreeSpriteOamMatrix(sprite); sprite->oam.affineMode = ST_OAM_AFFINE_OFF; - FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny); + FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny, TRUE); } return FALSE; } @@ -7756,7 +7776,7 @@ bool8 MovementAction_EnterPokeball_Step1(struct ObjectEvent *objectEvent, struct bool8 MovementAction_EnterPokeball_Step2(struct ObjectEvent *objectEvent, struct Sprite *sprite) { - FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny); + FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny, FALSE); objectEvent->invisible = TRUE; sprite->sTypeFuncId = 0; sprite->sSpeedFlip = 0; diff --git a/src/field_effect.c b/src/field_effect.c index 0d52aadb1c..dc3a87b955 100644 --- a/src/field_effect.c +++ b/src/field_effect.c @@ -791,9 +791,11 @@ void FieldEffectScript_LoadTiles(u8 **script) void FieldEffectScript_LoadFadedPalette(u8 **script) { struct SpritePalette *palette = (struct SpritePalette *)FieldEffectScript_ReadWord(script); - LoadSpritePalette(palette); - UpdateSpritePaletteWithWeather(IndexOfSpritePaletteTag(palette->tag), TRUE); + u32 paletteSlot = LoadSpritePalette(palette); (*script) += 4; + SetPaletteColorMapType(paletteSlot + 16, T1_READ_8(*script)); + UpdateSpritePaletteWithWeather(paletteSlot, TRUE); + (*script)++; } void FieldEffectScript_LoadPalette(u8 **script) @@ -849,6 +851,7 @@ void FieldEffectFreePaletteIfUnused(u8 paletteNum) for (i = 0; i < MAX_SPRITES; i++) if (gSprites[i].inUse && gSprites[i].oam.paletteNum == paletteNum) return; + ResetPaletteColorMapType(paletteNum + 16); FreeSpritePaletteByTag(tag); } } diff --git a/src/field_effect_helpers.c b/src/field_effect_helpers.c index a8dfb3d2e7..8bbc16b905 100755 --- a/src/field_effect_helpers.c +++ b/src/field_effect_helpers.c @@ -57,8 +57,8 @@ u32 FldEff_Shadow(void); void SetUpShadow(struct ObjectEvent *objectEvent, struct Sprite *sprite) { gFieldEffectArguments[0] = objectEvent->localId; - gFieldEffectArguments[1] = gSaveBlock1Ptr->location.mapNum; - gFieldEffectArguments[2] = gSaveBlock1Ptr->location.mapGroup; + gFieldEffectArguments[1] = objectEvent->mapNum; + gFieldEffectArguments[2] = objectEvent->mapGroup; FldEff_Shadow(); } @@ -339,19 +339,23 @@ u32 FldEff_Shadow(void) for (i = MAX_SPRITES - 1; i > -1; i--) // Search backwards, because of CreateSpriteAtEnd { // Return early if a shadow sprite already exists - if (gSprites[i].data[0] == gFieldEffectArguments[0] && gSprites[i].callback == UpdateShadowFieldEffect) + if (gSprites[i].callback == UpdateShadowFieldEffect && + gSprites[i].sLocalId == gFieldEffectArguments[0] && + gSprites[i].sMapNum == gFieldEffectArguments[1] && + gSprites[i].sMapGroup == gFieldEffectArguments[2] + ) return 0; } objectEventId = GetObjectEventIdByLocalIdAndMap(gFieldEffectArguments[0], gFieldEffectArguments[1], gFieldEffectArguments[2]); graphicsInfo = GetObjectEventGraphicsInfo(gObjectEvents[objectEventId].graphicsId); if (graphicsInfo->shadowSize == SHADOW_SIZE_NONE) // don't create a shadow at all return 0; - LoadSpriteSheetByTemplate(gFieldEffectObjectTemplatePointers[sShadowEffectTemplateIds[graphicsInfo->shadowSize]], 0); + LoadSpriteSheetByTemplate(gFieldEffectObjectTemplatePointers[sShadowEffectTemplateIds[graphicsInfo->shadowSize]], 0, 0); spriteId = CreateSpriteAtEnd(gFieldEffectObjectTemplatePointers[sShadowEffectTemplateIds[graphicsInfo->shadowSize]], 0, 0, OW_OBJECT_SUBPRIORITY + 1); if (spriteId != MAX_SPRITES) { // SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(8, 12)); - gSprites[spriteId].oam.objMode = 1; // BLEND + gSprites[spriteId].oam.objMode = ST_OAM_OBJ_BLEND; gSprites[spriteId].coordOffsetEnabled = TRUE; gSprites[spriteId].sLocalId = gFieldEffectArguments[0]; gSprites[spriteId].sMapNum = gFieldEffectArguments[1]; diff --git a/src/field_weather.c b/src/field_weather.c index 73299a9a55..0326e6528e 100644 --- a/src/field_weather.c +++ b/src/field_weather.c @@ -23,13 +23,6 @@ #define DROUGHT_COLOR_INDEX(color) ((((color) >> 1) & 0xF) | (((color) >> 2) & 0xF0) | (((color) >> 3) & 0xF00)) -enum -{ - COLOR_MAP_NONE, - COLOR_MAP_DARK_CONTRAST, - COLOR_MAP_CONTRAST, -}; - struct RGBColor { u16 r:5; @@ -799,10 +792,7 @@ void FadeScreen(u8 mode, s8 delay) else if (MapHasNaturalLight(gMapHeader.mapType)) { UpdateAltBgPalettes(PALETTES_BG); - BeginTimeOfDayPaletteFade(PALETTES_ALL, delay, 16, 0, - (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.initialTimeOfDay], - (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.finalTimeOfDay], - currentTimeBlend.weight, fadeColor); + BeginTimeOfDayPaletteFade(PALETTES_ALL, delay, 16, 0, ¤tTimeBlend.bld0, ¤tTimeBlend.bld1, currentTimeBlend.weight, fadeColor); } else { @@ -817,6 +807,36 @@ void FadeScreen(u8 mode, s8 delay) } } +// fades screen using BLDY +// Note: This enables blending in all windows; +// These bits may need to be disabled later +// (i.e if blending lighting effects using WINOBJ) +u16 FadeScreenHardware(u8 mode, s8 delay) { + u16 bldCnt = GetGpuReg(REG_OFFSET_BLDCNT) & BLDCNT_TGT2_ALL; + bldCnt |= BLDCNT_TGT1_ALL; + // enable blend in all windows + SetGpuRegBits(REG_OFFSET_WININ, WININ_WIN0_CLR | WININ_WIN1_CLR); + SetGpuRegBits(REG_OFFSET_WINOUT, WINOUT_WIN01_CLR); + + switch (mode) + { + case FADE_FROM_BLACK: + BeginHardwarePaletteFade(bldCnt | BLDCNT_EFFECT_DARKEN, delay, 16, 0, TRUE); + break; + case FADE_TO_BLACK: + BeginHardwarePaletteFade(bldCnt | BLDCNT_EFFECT_DARKEN, delay, 0, 16, FALSE); + break; + case FADE_FROM_WHITE: + BeginHardwarePaletteFade(bldCnt | BLDCNT_EFFECT_LIGHTEN, delay, 16, 0, TRUE); + break; + case FADE_TO_WHITE: + BeginHardwarePaletteFade(bldCnt | BLDCNT_EFFECT_LIGHTEN, delay, 0, 16, FALSE); + break; + } + + return 0; +} + bool8 IsWeatherNotFadingIn(void) { return (gWeatherPtr->palProcessingState != WEATHER_PAL_STATE_SCREEN_FADING_IN); @@ -986,7 +1006,10 @@ void Weather_SetBlendCoeffs(u8 eva, u8 evb) gWeatherPtr->currBlendEVB = evb; gWeatherPtr->targetBlendEVA = eva; gWeatherPtr->targetBlendEVB = evb; - SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(eva, evb)); + + // don't update BLDALPHA if a hardware fade is on-screen + if ((GetGpuReg(REG_OFFSET_BLDCNT) & BLDCNT_EFFECT_EFF_MASK) < BLDCNT_EFFECT_LIGHTEN) + SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(eva, evb)); } void Weather_SetTargetBlendCoeffs(u8 eva, u8 evb, int delay) @@ -1136,11 +1159,27 @@ void SetWeatherPalStateIdle(void) gWeatherPtr->palProcessingState = WEATHER_PAL_STATE_IDLE; } +const u8* SetPaletteColorMapType(u8 paletteIndex, u8 colorMapType) { + if (sPaletteColorMapTypes[paletteIndex] == colorMapType) + return sPaletteColorMapTypes; + // setup field effect color map + if (sPaletteColorMapTypes != sFieldEffectPaletteColorMapTypes) { + CpuFastCopy(sBasePaletteColorMapTypes, sFieldEffectPaletteColorMapTypes, 32); + sPaletteColorMapTypes = sFieldEffectPaletteColorMapTypes; + } + sFieldEffectPaletteColorMapTypes[paletteIndex] = colorMapType; + return sPaletteColorMapTypes; +} + void PreservePaletteInWeather(u8 preservedPalIndex) { - CpuCopy16(sBasePaletteColorMapTypes, sFieldEffectPaletteColorMapTypes, 32); - sFieldEffectPaletteColorMapTypes[preservedPalIndex] = COLOR_MAP_NONE; - sPaletteColorMapTypes = sFieldEffectPaletteColorMapTypes; + SetPaletteColorMapType(preservedPalIndex, COLOR_MAP_NONE); +} + +void ResetPaletteColorMapType(u8 paletteIndex) { + if (sPaletteColorMapTypes == sBasePaletteColorMapTypes) + return; + sFieldEffectPaletteColorMapTypes[paletteIndex] = sBasePaletteColorMapTypes[paletteIndex]; } void ResetPreservedPalettesInWeather(void) diff --git a/src/fieldmap.c b/src/fieldmap.c index 83cc406c51..2cd8f0db85 100644 --- a/src/fieldmap.c +++ b/src/fieldmap.c @@ -888,7 +888,7 @@ static void LoadTilesetPalette(struct Tileset const *tileset, u16 destOffset, u1 { // LoadPalette(&black, destOffset, 2); if (skipFaded) - CpuFastCopy(tileset->palettes, &gPlttBufferUnfaded[destOffset], size); + CpuFastCopy(tileset->palettes, &gPlttBufferUnfaded[destOffset], size); // always word-aligned else LoadPaletteFast(tileset->palettes, destOffset, size); gPlttBufferFaded[destOffset] = gPlttBufferUnfaded[destOffset] = RGB_BLACK; // why does it have to be black? @@ -898,11 +898,11 @@ static void LoadTilesetPalette(struct Tileset const *tileset, u16 destOffset, u1 } else if (tileset->isSecondary == TRUE) { - // (void*) is to silence 'source potentially unaligned' error - // All 'gTilesetPalettes_' arrays should have ALIGNED(4) in them - if (skipFaded) - CpuFastCopy((void*)tileset->palettes[NUM_PALS_IN_PRIMARY], &gPlttBufferUnfaded[destOffset], size); - else + // All 'gTilesetPalettes_' arrays should have ALIGNED(4) in them, + // but we use SmartCopy here just in case they don't + if (skipFaded) { + CpuSmartCopy16(tileset->palettes[NUM_PALS_IN_PRIMARY], &gPlttBufferUnfaded[destOffset], size); + } else LoadPaletteFast(tileset->palettes[NUM_PALS_IN_PRIMARY], destOffset, size); low = NUM_PALS_IN_PRIMARY; high = NUM_PALS_TOTAL; diff --git a/src/overworld.c b/src/overworld.c index ecd285e8da..88fa06b95f 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -200,7 +200,7 @@ COMMON_DATA u8 gFieldLinkPlayerCount = 0; u8 gTimeOfDay; struct TimeBlendSettings currentTimeBlend; -u16 gTimeUpdateCounter; // playTimeVBlanks will eventually overflow, so this is used to update TOD +s16 gTimeUpdateCounter; // playTimeVBlanks will eventually overflow, so this is used to update TOD // EWRAM vars EWRAM_DATA static u8 sObjectEventLoadFlag = 0; @@ -212,6 +212,7 @@ EWRAM_DATA static u16 sLastMapSectionId = 0; EWRAM_DATA static struct InitialPlayerAvatarState sInitialPlayerAvatarState = {0}; EWRAM_DATA static u16 sAmbientCrySpecies = 0; EWRAM_DATA static bool8 sIsAmbientCryWaterMon = FALSE; +EWRAM_DATA static u8 sHoursOverride = 0; // used to override apparent time of day hours EWRAM_DATA struct LinkPlayerObjectEvent gLinkPlayerObjectEvents[4] = {0}; EWRAM_DATA bool8 gExitStairsMovementDisabled = FALSE; @@ -910,6 +911,8 @@ static void LoadMapFromWarp(bool32 a1) TrySetMapSaveWarpStatus(); ClearTempFieldEventData(); ResetDexNavSearch(); + // reset hours override on every warp + sHoursOverride = 0; ResetCyclingRoadChallengeData(); RestartWildEncounterImmunitySteps(); #if FREE_MATCH_CALL == FALSE @@ -1557,37 +1560,37 @@ void UpdateTimeOfDay(void) { s32 hours, minutes; RtcCalcLocalTime(); - hours = gLocalTime.hours; - minutes = gLocalTime.minutes; + hours = sHoursOverride ? sHoursOverride : gLocalTime.hours; + minutes = sHoursOverride ? 0 : gLocalTime.minutes; if (IsBetweenHours(hours, MORNING_HOUR_BEGIN, MORNING_HOUR_MIDDLE)) // night->morning { - currentTimeBlend.initialTimeOfDay = TIME_NIGHT; - currentTimeBlend.finalTimeOfDay = TIME_MORNING; + currentTimeBlend.bld0 = gTimeOfDayBlend[TIME_NIGHT]; + currentTimeBlend.bld1 = gTimeOfDayBlend[TIME_MORNING]; currentTimeBlend.weight = TIME_BLEND_WEIGHT(MORNING_HOUR_BEGIN, MORNING_HOUR_MIDDLE); currentTimeBlend.altWeight = (DEFAULT_WEIGHT - currentTimeBlend.weight) / 2; gTimeOfDay = TIME_MORNING; } else if (IsBetweenHours(hours, MORNING_HOUR_MIDDLE, MORNING_HOUR_END)) // morning->day { - currentTimeBlend.initialTimeOfDay = TIME_MORNING; - currentTimeBlend.finalTimeOfDay = TIME_DAY; + currentTimeBlend.bld0 = gTimeOfDayBlend[TIME_MORNING]; + currentTimeBlend.bld1 = gTimeOfDayBlend[TIME_DAY]; currentTimeBlend.weight = TIME_BLEND_WEIGHT(MORNING_HOUR_MIDDLE, MORNING_HOUR_END); currentTimeBlend.altWeight = (DEFAULT_WEIGHT - currentTimeBlend.weight) / 2 + (DEFAULT_WEIGHT / 2); gTimeOfDay = TIME_MORNING; } else if (IsBetweenHours(hours, EVENING_HOUR_BEGIN, EVENING_HOUR_END)) // evening { - currentTimeBlend.initialTimeOfDay = TIME_DAY; - currentTimeBlend.finalTimeOfDay = TIME_EVENING; + currentTimeBlend.bld0 = gTimeOfDayBlend[TIME_DAY]; + currentTimeBlend.bld1 = gTimeOfDayBlend[TIME_EVENING]; currentTimeBlend.weight = TIME_BLEND_WEIGHT(EVENING_HOUR_BEGIN, EVENING_HOUR_END); currentTimeBlend.altWeight = currentTimeBlend.weight / 2 + (DEFAULT_WEIGHT / 2); gTimeOfDay = TIME_EVENING; } else if (IsBetweenHours(hours, NIGHT_HOUR_BEGIN, NIGHT_HOUR_BEGIN + 1)) // evening->night { - currentTimeBlend.initialTimeOfDay = TIME_EVENING; - currentTimeBlend.finalTimeOfDay = TIME_NIGHT; + currentTimeBlend.bld0 = gTimeOfDayBlend[TIME_EVENING]; + currentTimeBlend.bld1 = gTimeOfDayBlend[TIME_NIGHT]; currentTimeBlend.weight = TIME_BLEND_WEIGHT(NIGHT_HOUR_BEGIN, NIGHT_HOUR_BEGIN + 1); currentTimeBlend.altWeight = currentTimeBlend.weight / 2; gTimeOfDay = TIME_NIGHT; @@ -1596,12 +1599,14 @@ void UpdateTimeOfDay(void) { currentTimeBlend.weight = DEFAULT_WEIGHT; currentTimeBlend.altWeight = 0; - gTimeOfDay = currentTimeBlend.initialTimeOfDay = currentTimeBlend.finalTimeOfDay = TIME_NIGHT; + currentTimeBlend.bld0 = currentTimeBlend.bld1 = gTimeOfDayBlend[TIME_NIGHT]; + gTimeOfDay = TIME_NIGHT; } else // day { currentTimeBlend.weight = currentTimeBlend.altWeight = DEFAULT_WEIGHT; - gTimeOfDay = currentTimeBlend.initialTimeOfDay = currentTimeBlend.finalTimeOfDay = TIME_DAY; + currentTimeBlend.bld0 = currentTimeBlend.bld1 = gTimeOfDayBlend[TIME_DAY]; + gTimeOfDay = TIME_DAY; } } @@ -1658,10 +1663,10 @@ void UpdatePalettesWithTime(u32 palettes) palettes &= ~(mask); } - palettes &= PALETTES_MAP | PALETTES_OBJECTS; // Don't blend UI pals - if (!palettes) - return; - TimeMixPalettes(palettes, gPlttBufferUnfaded, gPlttBufferFaded, (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.initialTimeOfDay], (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.finalTimeOfDay], currentTimeBlend.weight); + palettes &= PALETTES_MAP | PALETTES_OBJECTS; // Don't blend UI pals + if (!palettes) + return; + TimeMixPalettes(palettes, gPlttBufferUnfaded, gPlttBufferFaded, ¤tTimeBlend.bld0, ¤tTimeBlend.bld1, currentTimeBlend.weight); } } @@ -1671,8 +1676,7 @@ u8 UpdateSpritePaletteWithTime(u8 paletteNum) { if (IS_BLEND_IMMUNE_TAG(GetSpritePaletteTagByPaletteNum(paletteNum))) return paletteNum; - TimeMixPalettes(1, &gPlttBufferUnfaded[OBJ_PLTT_ID(paletteNum)], &gPlttBufferFaded[OBJ_PLTT_ID(paletteNum)], (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.initialTimeOfDay], (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.finalTimeOfDay], currentTimeBlend.weight - ); + TimeMixPalettes(1, &gPlttBufferUnfaded[OBJ_PLTT_ID(paletteNum)], &gPlttBufferFaded[OBJ_PLTT_ID(paletteNum)], ¤tTimeBlend.bld0, ¤tTimeBlend.bld1, currentTimeBlend.weight); } return paletteNum; } @@ -1689,20 +1693,17 @@ static void OverworldBasic(void) UpdateTilesetAnimations(); DoScheduledBgTilemapCopiesToVram(); // Every minute if no palette fade is active, update TOD blending as needed - if (!gPaletteFade.active && ++gTimeUpdateCounter >= (SECONDS_PER_MINUTE * 60 / FakeRtc_GetSecondsRatio())) - { - struct TimeBlendSettings cachedBlend = { - .initialTimeOfDay = currentTimeBlend.initialTimeOfDay, - .finalTimeOfDay = currentTimeBlend.finalTimeOfDay, - .weight = currentTimeBlend.weight, - }; - gTimeUpdateCounter = 0; + if (!gPaletteFade.active && --gTimeUpdateCounter <= 0) { + struct TimeBlendSettings cachedBlend = currentTimeBlend; + u32 *bld0 = (u32*)&cachedBlend; + u32 *bld1 = (u32*)¤tTimeBlend; + gTimeUpdateCounter = (SECONDS_PER_MINUTE * 60 / FakeRtc_GetSecondsRatio()); UpdateTimeOfDay(); FormChangeTimeUpdate(); - if (cachedBlend.initialTimeOfDay != currentTimeBlend.initialTimeOfDay - || cachedBlend.finalTimeOfDay != currentTimeBlend.finalTimeOfDay - || cachedBlend.weight != currentTimeBlend.weight) - { + if (bld0[0] != bld1[0] + || bld0[1] != bld1[1] + || bld0[2] != bld1[2] + ) { UpdateAltBgPalettes(PALETTES_BG); UpdatePalettesWithTime(PALETTES_ALL); } @@ -3679,3 +3680,15 @@ void ScriptHideItemDescription(struct ScriptContext *ctx) #endif // OW_SHOW_ITEM_DESCRIPTIONS +// returns old sHoursOverride +u16 SetTimeOfDay(u16 hours) { + u16 oldHours = sHoursOverride; + sHoursOverride = hours; + gTimeUpdateCounter = 0; + return oldHours; +} + +bool8 ScrFunc_settimeofday(struct ScriptContext *ctx) { + SetTimeOfDay(ScriptReadByte(ctx)); + return FALSE; +} diff --git a/src/palette.c b/src/palette.c index 890fdfdb6a..f079b0d3d5 100644 --- a/src/palette.c +++ b/src/palette.c @@ -714,7 +714,8 @@ static u32 UpdateHardwarePaletteFade(void) { if (gPaletteFade.shouldResetBlendRegisters) { - gPaletteFade_blendCnt = 0; + // clear TGT1 + gPaletteFade_blendCnt &= ~0xFF; gPaletteFade.y = 0; } gPaletteFade.shouldResetBlendRegisters = FALSE; @@ -725,10 +726,43 @@ static u32 UpdateHardwarePaletteFade(void) return gPaletteFade.active ? PALETTE_FADE_STATUS_ACTIVE : PALETTE_FADE_STATUS_DONE; } +// Only called for hardware fades static void UpdateBlendRegisters(void) { SetGpuReg(REG_OFFSET_BLDCNT, (u16)gPaletteFade_blendCnt); SetGpuReg(REG_OFFSET_BLDY, gPaletteFade.y); + // If fade-out, also adjust BLDALPHA and DISPCNT + if (!gPaletteFade.yDec /*&& gPaletteFade.mode == HARDWARE_FADE*/) { + u16 bldAlpha = GetGpuReg(REG_OFFSET_BLDALPHA); + u8 tgt1 = BLDALPHA_TGT1(bldAlpha); + u8 tgt2 = BLDALPHA_TGT2(bldAlpha); + u8 bldFade; + + switch (gPaletteFade_blendCnt & BLDCNT_EFFECT_EFF_MASK) + { + // FADE_TO_BLACK + case BLDCNT_EFFECT_DARKEN: + bldFade = BLDALPHA_TGT1(max(0, 16 - gPaletteFade.y)); + SetGpuReg( + REG_OFFSET_BLDALPHA, + BLDALPHA_BLEND(min(tgt1, bldFade), min(tgt2, bldFade)) + ); + break; + // FADE_TO_WHITE + case BLDCNT_EFFECT_LIGHTEN: + SetGpuReg( + REG_OFFSET_BLDALPHA, + BLDALPHA_BLEND(min(++tgt1, 31), min(++tgt2, 31)) + ); + // cause display to show white when finished + // (otherwise blend-mode sprites will still be visible) + if (gPaletteFade.hardwareFadeFinishing && gPaletteFade.y >= 16) + SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_FORCED_BLANK); + break; + } + } else + ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_FORCED_BLANK); + if (gPaletteFade.hardwareFadeFinishing) { gPaletteFade.hardwareFadeFinishing = FALSE; diff --git a/src/scrcmd.c b/src/scrcmd.c index cd7ff72b53..499792cb70 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -24,6 +24,7 @@ #include "field_tasks.h" #include "field_weather.h" #include "fieldmap.h" +#include "gpu_regs.h" #include "item.h" #include "lilycove_lady.h" #include "main.h" @@ -836,27 +837,19 @@ bool8 ScrCmd_fadescreenswapbuffers(struct ScriptContext *ctx) switch (mode) { - case FADE_TO_BLACK: - case FADE_TO_WHITE: - default: - if (sPalBuffer == NULL) - { - sPalBuffer = Alloc(PLTT_SIZE); - CpuCopy32(gPlttBufferUnfaded, sPalBuffer, PLTT_SIZE); - FadeScreen(mode, 0); - } - break; case FADE_FROM_BLACK: case FADE_FROM_WHITE: - if (sPalBuffer != NULL) - { - CpuCopy32(sPalBuffer, gPlttBufferUnfaded, PLTT_SIZE); - FadeScreen(mode, 0); - FREE_AND_SET_NULL(sPalBuffer); - } + // Restore last weather blend before fading in, + // since BLDALPHA was modified by fade-out + SetGpuReg( + REG_OFFSET_BLDALPHA, + BLDALPHA_BLEND(gWeatherPtr->currBlendEVA, gWeatherPtr->currBlendEVB) + ); break; } + FadeScreenHardware(mode, 0); + if (nowait) return FALSE; SetupNativeScript(ctx, IsPaletteNotActive); @@ -1265,6 +1258,20 @@ bool8 ScrCmd_fadeinbgm(struct ScriptContext *ctx) return FALSE; } +struct ObjectEvent * ScriptHideFollower(void) { + struct ObjectEvent *obj = GetFollowerObject(); + + if (obj == NULL || obj->invisible) + return NULL; + + ClearObjectEventMovement(obj, &gSprites[obj->spriteId]); + gSprites[obj->spriteId].animCmdIndex = 0; // Reset start frame of animation + // Note: ScriptMovement_ returns TRUE on error + if (ScriptMovement_StartObjectMovementScript(obj->localId, obj->mapGroup, obj->mapNum, EnterPokeballMovement)) + return NULL; + return obj; +} + bool8 ScrCmd_applymovement(struct ScriptContext *ctx) { u16 localId = VarGet(ScriptReadHalfword(ctx)); @@ -1283,17 +1290,11 @@ bool8 ScrCmd_applymovement(struct ScriptContext *ctx) gObjectEvents[GetObjectEventIdByLocalId(localId)].directionOverwrite = DIR_NONE; ScriptMovement_StartObjectMovementScript(localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, movementScript); sMovingNpcId = localId; - objEvent = GetFollowerObject(); - // Force follower into pokeball - if (localId != OBJ_EVENT_ID_FOLLOWER - && !FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT) - && (movementScript < Common_Movement_FollowerSafeStart || movementScript > Common_Movement_FollowerSafeEnd) - && (objEvent = GetFollowerObject()) - && !objEvent->invisible) + if (localId != OBJ_EVENT_ID_FOLLOWER && + !FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT) + && (movementScript < Common_Movement_FollowerSafeStart || movementScript > Common_Movement_FollowerSafeEnd)) { - ClearObjectEventMovement(objEvent, &gSprites[objEvent->spriteId]); - gSprites[objEvent->spriteId].animCmdIndex = 0; // Reset start frame of animation - ScriptMovement_StartObjectMovementScript(OBJ_EVENT_ID_FOLLOWER, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, EnterPokeballMovement); + ScriptHideFollower(); } return FALSE; } @@ -1555,8 +1556,11 @@ bool8 ScrCmd_lockall(struct ScriptContext *ctx) } else { + struct ObjectEvent *followerObj = GetFollowerObject(); FreezeObjects_WaitForPlayer(); SetupNativeScript(ctx, IsFreezePlayerFinished); + if (FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT) && followerObj) // Unfreeze follower object (conditionally) + UnfreezeObjectEvent(followerObj); return TRUE; } } @@ -3114,3 +3118,23 @@ void Script_EndTrainerCanSeeIf(struct ScriptContext *ctx) if (ctx->breakOnTrainerBattle && sScriptConditionTable[condition][ctx->comparisonResult] == 1) StopScript(ctx); } + +bool8 ScrFunc_hidefollower(struct ScriptContext *ctx) { + bool16 wait = VarGet(ScriptReadHalfword(ctx)); + struct ObjectEvent *obj; + + if ((obj = ScriptHideFollower()) != NULL && wait) { + sMovingNpcId = obj->localId; + sMovingNpcMapGroup = obj->mapGroup; + sMovingNpcMapNum = obj->mapNum; + SetupNativeScript(ctx, WaitForMovementFinish); + } + + // Just in case, prevent `applymovement` + // from hiding the follower again + if (obj) + FlagSet(FLAG_SAFE_FOLLOWER_MOVEMENT); + + // execute next script command with no delay + return TRUE; +} diff --git a/src/script.c b/src/script.c index 7111f3228d..2197c67694 100644 --- a/src/script.c +++ b/src/script.c @@ -6,6 +6,7 @@ #include "trainer_see.h" #include "util.h" #include "constants/event_objects.h" +#include "constants/flags.h" #include "constants/map_scripts.h" #include "field_message_box.h" @@ -259,6 +260,8 @@ void ScriptContext_SetupScript(const u8 *ptr) InitScriptContext(&sGlobalScriptContext, gScriptCmdTable, gScriptCmdTableEnd); SetupBytecodeScript(&sGlobalScriptContext, ptr); LockPlayerFieldControls(); + if (OW_MON_SCRIPT_MOVEMENT) + FlagSet(FLAG_SAFE_FOLLOWER_MOVEMENT); sGlobalScriptContextStatus = CONTEXT_RUNNING; } diff --git a/src/script_movement.c b/src/script_movement.c index d67afd9972..021be6d621 100644 --- a/src/script_movement.c +++ b/src/script_movement.c @@ -1,6 +1,7 @@ #include "global.h" #include "script_movement.h" #include "event_object_movement.h" +#include "event_scripts.h" #include "task.h" #include "util.h" #include "constants/event_objects.h" @@ -205,13 +206,34 @@ static void ScriptMovement_MoveObjects(u8 taskId) } } +// from event_object_movement +#define sTypeFuncId data[1] +#define sTimer data[5] + static void ScriptMovement_TakeStep(u8 taskId, u8 moveScrId, u8 objEventId, const u8 *movementScript) { u8 nextMoveActionId; + struct ObjectEvent *obj = &gObjectEvents[objEventId]; - if (ObjectEventIsHeldMovementActive(&gObjectEvents[objEventId]) - && !ObjectEventClearHeldMovementIfFinished(&gObjectEvents[objEventId])) + if (ObjectEventIsHeldMovementActive(obj) && + !ObjectEventClearHeldMovementIfFinished(obj)) + { + // If, while undergoing scripted movement, + // a non-player object collides with an active follower pokemon, + // put that follower into a pokeball + // (sTimer helps limit this expensive check to once per step) + if (OW_MON_SCRIPT_MOVEMENT && + gSprites[obj->spriteId].sTimer == 1 && + (objEventId = GetObjectObjectCollidesWith(obj, 0, 0, TRUE)) < OBJECT_EVENTS_COUNT && + // switch `obj` to follower + ((obj = &gObjectEvents[objEventId])->movementType == MOVEMENT_TYPE_FOLLOW_PLAYER) && + gSprites[obj->spriteId].sTypeFuncId != 0) + { + ClearObjectEventMovement(obj, &gSprites[obj->spriteId]); + ScriptMovement_StartObjectMovementScript(obj->localId, obj->mapNum, obj->mapGroup, EnterPokeballMovement); + } return; + } nextMoveActionId = *movementScript; if (nextMoveActionId == MOVEMENT_ACTION_STEP_END) @@ -229,3 +251,5 @@ static void ScriptMovement_TakeStep(u8 taskId, u8 moveScrId, u8 objEventId, cons } } +#undef sTypeFuncId +#undef sTimer diff --git a/src/sprite.c b/src/sprite.c index 20324a10d4..be6564a110 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -1465,7 +1465,7 @@ u16 LoadSpriteSheet(const struct SpriteSheet *sheet) } // Like LoadSpriteSheet, but checks if already loaded, and uses template image frames -u16 LoadSpriteSheetByTemplateWithOffset(const struct SpriteTemplate *template, u32 frame, s32 offset) +u16 LoadSpriteSheetByTemplate(const struct SpriteTemplate *template, u32 frame, s32 offset) { u16 tileStart; struct SpriteSheet sheet; @@ -1480,22 +1480,6 @@ u16 LoadSpriteSheetByTemplateWithOffset(const struct SpriteTemplate *template, u return LoadSpriteSheetWithOffset(&sheet, offset); } -// Like LoadSpriteSheet, but checks if already, and uses template image frames -u16 LoadSpriteSheetByTemplate(const struct SpriteTemplate *template, u8 frame) -{ - u16 tileStart; - struct SpriteSheet tempSheet; - // error if template is null or tile tag or images not set - if (!template || template->tileTag == TAG_NONE || !template->images) - return 0xFFFF; - if ((tileStart = GetSpriteTileStartByTag(template->tileTag)) != 0xFFFF) // return if already loaded - return tileStart; - tempSheet.data = template->images[frame].data; - tempSheet.size = template->images[frame].size; - tempSheet.tag = template->tileTag; - return LoadSpriteSheet(&tempSheet); -} - void LoadSpriteSheets(const struct SpriteSheet *sheets) { u32 i;