From 8795c15ab9d2fb255ee2c11db436ff0c1ebe5c70 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sun, 1 Sep 2024 16:17:51 -0400 Subject: [PATCH 01/18] feat: Allowed custom `COLOR_MAP_*` in `field_effect_scripts`. Credit: SDH --- asm/macros/field_effect_script.inc | 12 +++++++++--- include/field_weather.h | 2 ++ src/field_effect.c | 7 +++++-- src/field_weather.c | 22 +++++++++++++++++++--- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/asm/macros/field_effect_script.inc b/asm/macros/field_effect_script.inc index 38f7e31750..3e00bc734d 100644 --- a/asm/macros/field_effect_script.inc +++ b/asm/macros/field_effect_script.inc @@ -5,9 +5,11 @@ .4byte \address .endm - .macro field_eff_loadfadedpal address:req + @ 1 = COLOR_MAP_DARK_CONTRAST + .macro field_eff_loadfadedpal address:req, color_map_type=1 .byte 1 .4byte \address + .byte \color_map_type .endm .macro field_eff_loadpal address:req @@ -24,10 +26,12 @@ .byte 4 .endm - .macro field_eff_loadgfx_callnative tiles_address:req, palette_address:req, function_address:req + @ 1 = COLOR_MAP_DARK_CONTRAST + .macro field_eff_loadgfx_callnative tiles_address:req, palette_address:req, function_address:req, color_map_type=1 .byte 5 .4byte \tiles_address .4byte \palette_address + .byte \color_map_type .4byte \function_address .endm @@ -37,8 +41,10 @@ .4byte \function_address .endm - .macro field_eff_loadfadedpal_callnative palette_address:req, function_address:req + @ 1 = COLOR_MAP_DARK_CONTRAST + .macro field_eff_loadfadedpal_callnative palette_address:req, function_address:req, color_map_type=1 .byte 7 .4byte \palette_address + .byte \color_map_type .4byte \function_address .endm diff --git a/include/field_weather.h b/include/field_weather.h index fbd90f2405..d741bdd2b0 100644 --- a/include/field_weather.h +++ b/include/field_weather.h @@ -170,7 +170,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); // field_weather_effect.c diff --git a/src/field_effect.c b/src/field_effect.c index 6ae2d509a4..5df664908d 100644 --- a/src/field_effect.c +++ b/src/field_effect.c @@ -781,9 +781,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) @@ -839,6 +841,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_weather.c b/src/field_weather.c index 1a3a150c4c..e2fb1e77e6 100644 --- a/src/field_weather.c +++ b/src/field_weather.c @@ -1136,11 +1136,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) From d3a48398084612f40bc2d05b7bca3e2ae3cf4f83 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:02:51 -0400 Subject: [PATCH 02/18] meta: included `COLOR_MAP` constants in `field_effect_script`. Credit: SDH --- asm/macros/field_effect_script.inc | 10 ++++------ include/constants/field_weather.h | 5 +++++ src/field_weather.c | 7 ------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/asm/macros/field_effect_script.inc b/asm/macros/field_effect_script.inc index 3e00bc734d..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,8 +6,7 @@ .4byte \address .endm - @ 1 = COLOR_MAP_DARK_CONTRAST - .macro field_eff_loadfadedpal address:req, color_map_type=1 + .macro field_eff_loadfadedpal address:req, color_map_type=COLOR_MAP_DARK_CONTRAST .byte 1 .4byte \address .byte \color_map_type @@ -26,8 +26,7 @@ .byte 4 .endm - @ 1 = COLOR_MAP_DARK_CONTRAST - .macro field_eff_loadgfx_callnative tiles_address:req, palette_address:req, function_address:req, color_map_type=1 + .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 @@ -41,8 +40,7 @@ .4byte \function_address .endm - @ 1 = COLOR_MAP_DARK_CONTRAST - .macro field_eff_loadfadedpal_callnative palette_address:req, function_address:req, color_map_type=1 + .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 diff --git a/include/constants/field_weather.h b/include/constants/field_weather.h index e84dbc48c4..dedd1a3c0a 100644 --- a/include/constants/field_weather.h +++ b/include/constants/field_weather.h @@ -1,6 +1,11 @@ #ifndef GUARD_CONSTANTS_FIELD_WEATHER_H #define GUARD_CONSTANTS_FIELD_WEATHER_H +// sPaletteColorMapTypes & field_effect_scripts +#define COLOR_MAP_NONE 0 +#define COLOR_MAP_DARK_CONTRAST 1 +#define COLOR_MAP_CONTRAST 2 + #define MAX_RAIN_SPRITES 24 #define NUM_CLOUD_SPRITES 3 #define NUM_FOG_HORIZONTAL_SPRITES 20 diff --git a/src/field_weather.c b/src/field_weather.c index e2fb1e77e6..eaa25078b6 100644 --- a/src/field_weather.c +++ b/src/field_weather.c @@ -20,13 +20,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; From 3e3229190728b2d7ac502d3851b9c9bb5d36e4bb Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sun, 8 Sep 2024 17:05:17 -0400 Subject: [PATCH 03/18] fix: fixed shadows not spawning across maps for OWs with the same `localId` --- src/event_object_movement.c | 4 ++-- src/field_effect_helpers.c | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 878aeac807..f8dd625410 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -2441,7 +2441,7 @@ static void SpawnLightSprite(s16 x, s16 y, s16 camX, s16 camY, u32 lightType) { sprite->centerToCornerVecX = -(32 >> 1); sprite->centerToCornerVecY = -(32 >> 1); sprite->oam.priority = 1; - sprite->oam.objMode = 1; // BLEND + sprite->oam.objMode = ST_OAM_OBJ_BLEND; sprite->oam.affineMode = ST_OAM_AFFINE_NORMAL; sprite->x += 8; sprite->y += 22 + sprite->centerToCornerVecY; @@ -2451,7 +2451,7 @@ static void SpawnLightSprite(s16 x, s16 y, s16 camX, s16 camY, u32 lightType) { sprite->centerToCornerVecY = -(16 >> 1); sprite->oam.priority = 2; sprite->subpriority = 0xFF; - sprite->oam.objMode = 1; // BLEND + sprite->oam.objMode = ST_OAM_OBJ_BLEND; } } diff --git a/src/field_effect_helpers.c b/src/field_effect_helpers.c index 70f0db7f1b..606f3b1f1a 100755 --- a/src/field_effect_helpers.c +++ b/src/field_effect_helpers.c @@ -56,8 +56,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(); } @@ -329,7 +329,11 @@ u32 FldEff_Shadow(void) s32 i; 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]); @@ -341,7 +345,7 @@ u32 FldEff_Shadow(void) 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]; From 4056804575c064ff3f4e85ebbb877950b5cd167e Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sun, 20 Oct 2024 03:15:38 -0400 Subject: [PATCH 04/18] feat: improved follower handling during scripted movements --- asm/macros/event.inc | 8 +++ data/scripts/pkmn_center_nurse.inc | 1 + include/constants/event_objects.h | 6 ++ include/constants/flags.h | 4 +- include/event_object_movement.h | 1 + src/event_object_movement.c | 88 ++++++++++++++++++------------ src/scrcmd.c | 48 ++++++++++++---- src/script.c | 3 + src/script_movement.c | 28 +++++++++- 9 files changed, 139 insertions(+), 48 deletions(-) diff --git a/asm/macros/event.inc b/asm/macros/event.inc index ef1ba30270..aa8ff5773d 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -1944,3 +1944,11 @@ setvar VAR_0x8006, \item special CreateEnemyEventMon .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/data/scripts/pkmn_center_nurse.inc b/data/scripts/pkmn_center_nurse.inc index 9c5afba8d6..b154d290b0 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 waitmovement 0 dofieldeffect FLDEFF_POKECENTER_HEAL diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index 59f34334cb..1d0f5a22e8 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -289,6 +289,12 @@ // instead of a normal pokeball #define OW_MON_POKEBALLS TRUE +// 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 + #define SHADOW_SIZE_S 0 #define SHADOW_SIZE_M 1 #define SHADOW_SIZE_L 2 diff --git a/include/constants/flags.h b/include/constants/flags.h index f0d80c5498..161d39fcaf 100644 --- a/include/constants/flags.h +++ b/include/constants/flags.h @@ -1643,7 +1643,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 6a1e2df766..45a3c6863a 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -161,6 +161,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/src/event_object_movement.c b/src/event_object_movement.c index 436f048a5a..26127caab9 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -5222,9 +5222,7 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri s16 y; s16 targetX; s16 targetY; - #ifdef MB_SIDEWAYS_STAIRS_RIGHT_SIDE u32 playerAction = gObjectEvents[gPlayerAvatar.objectEventId].movementActionId; - #endif targetX = gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.x; targetY = gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.y; @@ -5240,8 +5238,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; } @@ -5262,43 +5260,25 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri MoveCoords(direction, &x, &y); #ifdef MB_SIDEWAYS_STAIRS_RIGHT_SIDE // https://github.com/ghoulslash/pokeemerald/tree/sideways_stairs GetCollisionAtCoords(objectEvent, x, y, direction); // Sets directionOverwrite for stairs - if (GetLedgeJumpDirection(x, y, direction) != DIR_NONE) { + #endif + 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_MON_BOBBING == TRUE) - sprite->y2 = -1; - } - } - sprite->sActionFuncId = 0; - #else - 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 ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction)); + } else if (gSprites[gPlayerAvatar.spriteId].data[4] == MOVE_SPEED_FAST_1) { + objectEvent->movementActionId = GetWalkFastMovementAction(direction); } else { - ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkNormalMovementAction(direction)); + objectEvent->movementActionId = GetWalkNormalMovementAction(direction); if (OW_MON_BOBBING == TRUE) sprite->y2 = -1; } - #endif + sprite->sActionFuncId = 0; objectEvent->singleMovementActive = 1; sprite->sTypeFuncId = 2; return TRUE; @@ -5823,13 +5803,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++) { @@ -5839,11 +5823,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) @@ -5972,6 +5961,16 @@ bool8 ObjectEventIsHeldMovementActive(struct ObjectEvent *objectEvent) return FALSE; } +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, + + [MOVEMENT_ACTION_NONE] = COPY_MOVE_NONE, +}; + bool8 ObjectEventSetHeldMovement(struct ObjectEvent *objectEvent, u8 movementActionId) { if (ObjectEventIsMovementOverridden(objectEvent)) @@ -5982,6 +5981,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->extra.playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId]; + } + return FALSE; } @@ -6004,6 +6013,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->extra.playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId]; + } } u8 ObjectEventCheckHeldMovementStatus(struct ObjectEvent *objectEvent) diff --git a/src/scrcmd.c b/src/scrcmd.c index d29df1748d..79b13534ab 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -997,6 +997,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)); @@ -1010,17 +1024,11 @@ bool8 ScrCmd_applymovement(struct ScriptContext *ctx) } 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; } @@ -2369,3 +2377,23 @@ bool8 ScrCmd_warpwhitefade(struct ScriptContext *ctx) ResetInitialPlayerAvatarState(); return TRUE; } + +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 c252c95f04..2008b83424 100644 --- a/src/script.c +++ b/src/script.c @@ -4,6 +4,7 @@ #include "mystery_gift.h" #include "util.h" #include "constants/event_objects.h" +#include "constants/flags.h" #include "constants/map_scripts.h" #define RAM_SCRIPT_MAGIC 51 @@ -243,6 +244,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 10517dfc8e..fdec56c081 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 From d12a24aef2f41d82288422fa3a41b18cbc2cecd4 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sat, 14 Dec 2024 23:07:02 -0500 Subject: [PATCH 05/18] fix: gave MOVEMENT_TYPE_FOLLOW_PLAYER an initial facing direction --- src/event_object_movement.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 26127caab9..acecd5d61d 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -362,7 +362,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, @@ -444,6 +444,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, }; #define OBJ_EVENT_PAL_TAG_BRENDAN 0x1100 From 5536a5535c5dd958896df5204c3f98a51266ef89 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Mon, 30 Dec 2024 20:52:05 -0500 Subject: [PATCH 06/18] fix: made slitherTracks_Transitions a proper 2D array. Credit: jaizu --- src/event_object_movement.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index acecd5d61d..8d682bce63 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -9309,10 +9309,10 @@ static void DoTracksGroundEffect_SlitherTracks(struct ObjectEvent *objEvent, str // each byte in that row is for the next direction of the bike in the order // of down, up, left, right. static const u8 slitherTracks_Transitions[4][4] = { - 1, 2, 7, 8, - 1, 2, 6, 5, - 5, 8, 3, 4, - 6, 7, 3, 4, + {1, 2, 7, 8}, + {1, 2, 6, 5}, + {5, 8, 3, 4}, + {6, 7, 3, 4}, }; if (objEvent->currentCoords.x != objEvent->previousCoords.x || objEvent->currentCoords.y != objEvent->previousCoords.y) From 57a198bca0adb08ad0f1d7d4c520f3203f3ceb6c Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Mon, 30 Dec 2024 20:44:07 -0500 Subject: [PATCH 07/18] feat: make follower sidestep or backstep with player during scripts --- src/event_object_movement.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 8d682bce63..5f1b3eba77 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -5189,6 +5189,7 @@ bool8 MovementType_FollowPlayer_Moving(struct ObjectEvent *objectEvent, struct S if (ObjectEventExecSingleMovementAction(objectEvent, sprite)) { #endif objectEvent->singleMovementActive = 0; + objectEvent->facingDirectionLocked = FALSE; if (sprite->sTypeFuncId) // restore nonzero state sprite->sTypeFuncId = 1; } else if (objectEvent->movementActionId < MOVEMENT_ACTION_EXIT_POKEBALL) { @@ -5258,6 +5259,15 @@ 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); #ifdef MB_SIDEWAYS_STAIRS_RIGHT_SIDE // https://github.com/ghoulslash/pokeemerald/tree/sideways_stairs GetCollisionAtCoords(objectEvent, x, y, direction); // Sets directionOverwrite for stairs From 3fffb9fac6c3eb9c232609565f5e84957630a32c Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:33:17 -0500 Subject: [PATCH 08/18] feat: added configuration to restrict following pokemon. Closes #42 --- include/constants/event_objects.h | 21 +++++++++++++++++++++ src/event_object_movement.c | 10 +++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index 1d0f5a22e8..63d5f9c733 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -295,6 +295,27 @@ // 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) + #define SHADOW_SIZE_S 0 #define SHADOW_SIZE_M 1 #define SHADOW_SIZE_L 2 diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 5f1b3eba77..6498ceb455 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -1785,8 +1785,16 @@ 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 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]; + return &gPlayerParty[i]; } return NULL; } From 9e2057a9d473ac949f9dfde20a3df6b7a404a1c1 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:35:13 -0500 Subject: [PATCH 09/18] meta: updated README with new config settings --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 07427e2bd5..1dd20434ee 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,20 @@ A: Configuration for the follower system is mostly in [event_objects.h](include/ // Followers will emerge from the pokeball they are stored in, // instead of a normal pokeball #define OW_MON_POKEBALLS TRUE + +// 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) ``` ### `(lighting)` Q: How do I mark certain colors in a palette as light-blended? From 01335fa453a90d9f48ce6d8f92a947cd1782b23c Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Thu, 23 Jun 2022 22:13:21 -0400 Subject: [PATCH 10/18] Unfreeze follower object during lockall (if safe movement flag set). MSGBOX_SIGN no longer freezes follower. --- data/scripts/std_msgbox.inc | 2 ++ src/scrcmd.c | 3 +++ 2 files changed, 5 insertions(+) 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/src/scrcmd.c b/src/scrcmd.c index 79b13534ab..a648bf105c 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -1248,8 +1248,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; } } From faf773a54bc04a3fd23569a711a72f1469194468 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Wed, 25 Dec 2024 19:26:24 -0500 Subject: [PATCH 11/18] feat: added time-blend override --- include/overworld.h | 16 +++-- include/palette.h | 7 +++ src/cable_car.c | 1 - src/field_weather.c | 4 +- src/overworld.c | 139 +++++++++++++++++++++++++++----------------- 5 files changed, 103 insertions(+), 64 deletions(-) diff --git a/include/overworld.h b/include/overworld.h index a9354ad80c..ba34836410 100644 --- a/include/overworld.h +++ b/include/overworld.h @@ -30,6 +30,11 @@ #define TIME_OF_DAY_DAY 2 #define TIME_OF_DAY_MAX TIME_OF_DAY_DAY +// 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; @@ -44,14 +49,6 @@ struct LinkPlayerObjectEvent u8 movementMode; }; -struct __attribute__((packed)) TimeBlendSettings { - u16 weight:9; - u16 time1:3; - u16 time0:3; - u16 unused:1; - u16 altWeight; -}; - // Exported RAM declarations extern struct WarpData gLastUsedWarp; extern struct LinkPlayerObjectEvent gLinkPlayerObjectEvents[4]; @@ -65,7 +62,7 @@ extern bool8 (*gFieldCallback2)(void); extern u8 gLocalLinkPlayerId; extern u8 gFieldLinkPlayerCount; extern u8 gTimeOfDay; -extern u16 gTimeUpdateCounter; +extern s16 gTimeUpdateCounter; extern struct TimeBlendSettings currentTimeBlend; @@ -175,5 +172,6 @@ bool32 Overworld_RecvKeysFromLinkIsRunning(void); bool32 Overworld_SendKeysToLinkIsRunning(void); bool32 IsSendingKeysOverCable(void); void ClearLinkPlayerObjectEvents(void); +bool16 SetTimeOfDay(u16 hours); #endif // GUARD_OVERWORLD_H diff --git a/include/palette.h b/include/palette.h index 930499b877..f86ab00a63 100644 --- a/include/palette.h +++ b/include/palette.h @@ -45,6 +45,13 @@ struct BlendSettings { u32 coeff:5; }; +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/src/cable_car.c b/src/cable_car.c index 8a666a536e..d4fcfec59d 100644 --- a/src/cable_car.c +++ b/src/cable_car.c @@ -1058,4 +1058,3 @@ static void InitGroundTilemapData(bool8 goingDown) sCableCar->groundTimer = 0; } - diff --git a/src/field_weather.c b/src/field_weather.c index eaa25078b6..9f41014ff3 100644 --- a/src/field_weather.c +++ b/src/field_weather.c @@ -799,8 +799,8 @@ void FadeScreen(u8 mode, s8 delay) if (MapHasNaturalLight(gMapHeader.mapType)) { UpdateAltBgPalettes(PALETTES_BG); BeginTimeOfDayPaletteFade(PALETTES_ALL, delay, 16, 0, - (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.time0], - (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.time1], + ¤tTimeBlend.bld0, + ¤tTimeBlend.bld1, currentTimeBlend.weight, fadeColor); } else { BeginNormalPaletteFade(PALETTES_ALL, delay, 16, 0, fadeColor); diff --git a/src/overworld.c b/src/overworld.c index cbe0347768..8a99173c6c 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -194,7 +194,7 @@ u8 gFieldLinkPlayerCount; 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; @@ -206,6 +206,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}; static const struct WarpData sDummyWarpData = @@ -853,6 +854,8 @@ static void LoadMapFromWarp(bool32 a1) CheckLeftFriendsSecretBase(); TrySetMapSaveWarpStatus(); ClearTempFieldEventData(); + // reset hours override on every warp + sHoursOverride = 0; ResetCyclingRoadChallengeData(); RestartWildEncounterImmunitySteps(); TryUpdateRandomTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); @@ -1481,44 +1484,65 @@ const struct BlendSettings gTimeOfDayBlend[] = u8 UpdateTimeOfDay(void) { s32 hours, minutes; RtcCalcLocalTime(); - hours = gLocalTime.hours; - minutes = gLocalTime.minutes; - if (hours < 4) { // night + hours = sHoursOverride ? sHoursOverride : gLocalTime.hours; + minutes = sHoursOverride ? 0 : gLocalTime.minutes; + switch (hours) + { + case 0 ... 3: // night + gTimeOfDay = TIME_OF_DAY_NIGHT; + currentTimeBlend.bld0 = currentTimeBlend.bld1 = gTimeOfDayBlend[gTimeOfDay]; currentTimeBlend.weight = 256; currentTimeBlend.altWeight = 0; - gTimeOfDay = currentTimeBlend.time0 = currentTimeBlend.time1 = TIME_OF_DAY_NIGHT; - } else if (hours < 7) { // night->twilight - currentTimeBlend.time0 = TIME_OF_DAY_NIGHT; - currentTimeBlend.time1 = TIME_OF_DAY_TWILIGHT; + break; + case 4 ... 6: // night -> twilight + currentTimeBlend.bld0 = gTimeOfDayBlend[TIME_OF_DAY_NIGHT]; + currentTimeBlend.bld1 = gTimeOfDayBlend[TIME_OF_DAY_TWILIGHT]; currentTimeBlend.weight = 256 - 256 * ((hours - 4) * 60 + minutes) / ((7-4)*60); currentTimeBlend.altWeight = (256 - currentTimeBlend.weight) / 2; gTimeOfDay = TIME_OF_DAY_DAY; - } else if (hours < 10) { // twilight->day - currentTimeBlend.time0 = TIME_OF_DAY_TWILIGHT; - currentTimeBlend.time1 = TIME_OF_DAY_DAY; + break; + case 7 ... 9: // twilight -> day + currentTimeBlend.bld0 = gTimeOfDayBlend[TIME_OF_DAY_TWILIGHT]; + currentTimeBlend.bld1 = gTimeOfDayBlend[TIME_OF_DAY_DAY]; currentTimeBlend.weight = 256 - 256 * ((hours - 7) * 60 + minutes) / ((10-7)*60); currentTimeBlend.altWeight = (256 - currentTimeBlend.weight) / 2 + 128; gTimeOfDay = TIME_OF_DAY_DAY; - } else if (hours < 18) { // day + break; + case 10 ... 17: // day + gTimeOfDay = TIME_OF_DAY_DAY; + currentTimeBlend.bld0 = currentTimeBlend.bld1 = gTimeOfDayBlend[gTimeOfDay]; currentTimeBlend.weight = currentTimeBlend.altWeight = 256; - gTimeOfDay = currentTimeBlend.time0 = currentTimeBlend.time1 = TIME_OF_DAY_DAY; - } else if (hours < 20) { // day->twilight - currentTimeBlend.time0 = TIME_OF_DAY_DAY; - currentTimeBlend.time1 = TIME_OF_DAY_TWILIGHT; + break; + case 18 ... 19: // day -> twilight + currentTimeBlend.bld0 = gTimeOfDayBlend[TIME_OF_DAY_DAY]; + currentTimeBlend.bld1 = gTimeOfDayBlend[TIME_OF_DAY_TWILIGHT]; currentTimeBlend.weight = 256 - 256 * ((hours - 18) * 60 + minutes) / ((20-18)*60); currentTimeBlend.altWeight = currentTimeBlend.weight / 2 + 128; gTimeOfDay = TIME_OF_DAY_TWILIGHT; - } else if (hours < 22) { // twilight->night - currentTimeBlend.time0 = TIME_OF_DAY_TWILIGHT; - currentTimeBlend.time1 = TIME_OF_DAY_NIGHT; + break; + case 20 ... 21: // twilight -> night + currentTimeBlend.bld0 = gTimeOfDayBlend[TIME_OF_DAY_TWILIGHT]; + currentTimeBlend.bld1 = gTimeOfDayBlend[TIME_OF_DAY_NIGHT]; currentTimeBlend.weight = 256 - 256 * ((hours - 20) * 60 + minutes) / ((22-20)*60); currentTimeBlend.altWeight = currentTimeBlend.weight / 2; gTimeOfDay = TIME_OF_DAY_NIGHT; - } else { // 22-24, night + break; + case 22 ... 24: + gTimeOfDay = TIME_OF_DAY_NIGHT; + currentTimeBlend.bld0 = currentTimeBlend.bld1 = gTimeOfDayBlend[gTimeOfDay]; currentTimeBlend.weight = 256; currentTimeBlend.altWeight = 0; - gTimeOfDay = currentTimeBlend.time0 = currentTimeBlend.time1 = TIME_OF_DAY_NIGHT; + break; + // special value; always causes a blend update, + // and increments sHoursOverride + case HOURS_BLEND_ONCE: + currentTimeBlend.weight ^= 1; + sHoursOverride++; + break; + case HOURS_FREEZE_BLEND: + break; } + return gTimeOfDay; } @@ -1565,32 +1589,32 @@ void UpdatePalettesWithTime(u32 palettes) { if (IS_BLEND_IMMUNE_TAG(GetSpritePaletteTagByPaletteNum(i))) palettes &= ~(mask); - palettes &= PALETTES_MAP | PALETTES_OBJECTS; // Don't blend UI pals - if (!palettes) - return; - TimeMixPalettes( - palettes, - gPlttBufferUnfaded, - gPlttBufferFaded, - (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.time0], - (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.time1], - 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 + ); } } u8 UpdateSpritePaletteWithTime(u8 paletteNum) { if (MapHasNaturalLight(gMapHeader.mapType)) { 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.time0], - (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.time1], - currentTimeBlend.weight - ); + return paletteNum; + TimeMixPalettes( + 1, + &gPlttBufferUnfaded[OBJ_PLTT_ID(paletteNum)], + &gPlttBufferFaded[OBJ_PLTT_ID(paletteNum)], + ¤tTimeBlend.bld0, + ¤tTimeBlend.bld1, + currentTimeBlend.weight + ); } return paletteNum; } @@ -1607,18 +1631,16 @@ static void OverworldBasic(void) UpdateTilesetAnimations(); DoScheduledBgTilemapCopiesToVram(); // Every minute if no palette fade is active, update TOD blending as needed - if (!gPaletteFade.active && ++gTimeUpdateCounter >= 3600) { - struct TimeBlendSettings cachedBlend = { - .time0 = currentTimeBlend.time0, - .time1 = currentTimeBlend.time1, - .weight = currentTimeBlend.weight, - }; - gTimeUpdateCounter = 0; + if (!gPaletteFade.active && --gTimeUpdateCounter <= 0) { + struct TimeBlendSettings cachedBlend = currentTimeBlend; + u32 *bld0 = (u32*)&cachedBlend; + u32 *bld1 = (u32*)¤tTimeBlend; + gTimeUpdateCounter = 3600; UpdateTimeOfDay(); - if (cachedBlend.time0 != currentTimeBlend.time0 - || cachedBlend.time1 != currentTimeBlend.time1 - || cachedBlend.weight != currentTimeBlend.weight) - { + if (bld0[0] != bld1[0] + || bld0[1] != bld1[1] + || bld0[2] != bld1[2] + ) { UpdateAltBgPalettes(PALETTES_BG); UpdatePalettesWithTime(PALETTES_ALL); } @@ -3380,3 +3402,16 @@ static void SpriteCB_LinkPlayer(struct Sprite *sprite) sprite->data[7]++; } } + +// 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; +} From 4047f9b7644ece47f0f4c7240641b7e9b4c52cd4 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Wed, 25 Dec 2024 19:48:54 -0500 Subject: [PATCH 12/18] feat: added FadeScreenHardware utility --- include/field_weather.h | 1 + include/palette.h | 2 +- src/field_weather.c | 29 +++++++++++++++++++++++++++++ src/palette.c | 5 +++-- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/include/field_weather.h b/include/field_weather.h index d741bdd2b0..a8550845cc 100644 --- a/include/field_weather.h +++ b/include/field_weather.h @@ -152,6 +152,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 ApplyWeatherColorMapToPal(u8 paletteIndex); diff --git a/include/palette.h b/include/palette.h index f86ab00a63..30592a4ecb 100644 --- a/include/palette.h +++ b/include/palette.h @@ -99,7 +99,7 @@ void InvertPlttBuffer(u32 selectedPalettes); void TintPlttBuffer(u32 selectedPalettes, s8 r, s8 g, s8 b); void UnfadePlttBuffer(u32 selectedPalettes); void BeginFastPaletteFade(u8 submode); -void BeginHardwarePaletteFade(u8 blendCnt, u8 delay, u8 y, u8 targetY, u8 shouldResetBlendRegisters); +void BeginHardwarePaletteFade(u16 blendCnt, u8 delay, u8 y, u8 targetY, u8 shouldResetBlendRegisters); void BlendPalettes(u32 selectedPalettes, u8 coeff, u16 color); void BlendPalettesFine(u32 palettes, u16 *src, u16 *dst, u32 coeff, u32 color); void BlendPalettesUnfaded(u32 selectedPalettes, u8 coeff, u16 color); diff --git a/src/field_weather.c b/src/field_weather.c index 9f41014ff3..2bcaa2f857 100644 --- a/src/field_weather.c +++ b/src/field_weather.c @@ -815,6 +815,35 @@ 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 +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); diff --git a/src/palette.c b/src/palette.c index ddc0688209..3ea2a08195 100644 --- a/src/palette.c +++ b/src/palette.c @@ -896,7 +896,7 @@ static u8 UpdateFastPaletteFade(void) return gPaletteFade.active ? PALETTE_FADE_STATUS_ACTIVE : PALETTE_FADE_STATUS_DONE; } -void BeginHardwarePaletteFade(u8 blendCnt, u8 delay, u8 y, u8 targetY, u8 shouldResetBlendRegisters) +void BeginHardwarePaletteFade(u16 blendCnt, u8 delay, u8 y, u8 targetY, u8 shouldResetBlendRegisters) { gPaletteFade_blendCnt = blendCnt; gPaletteFade.delayCounter = delay; @@ -950,7 +950,8 @@ static u8 UpdateHardwarePaletteFade(void) { if (gPaletteFade.shouldResetBlendRegisters) { - gPaletteFade_blendCnt = 0; + // clear TGT1 + gPaletteFade_blendCnt &= ~0xFF; gPaletteFade.y = 0; } gPaletteFade.shouldResetBlendRegisters = FALSE; From c492bb3fecd64c69a6a7d03d3a77e739451c871a Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:27:28 -0500 Subject: [PATCH 13/18] fix: follower field movements after scripted movement update --- data/scripts/field_move_scripts.inc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/data/scripts/field_move_scripts.inc b/data/scripts/field_move_scripts.inc index dc161eb560..1db60ea0b8 100644 --- a/data/scripts/field_move_scripts.inc +++ b/data/scripts/field_move_scripts.inc @@ -126,7 +126,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 @@ -134,7 +133,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 @@ -142,7 +140,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 @@ -150,7 +147,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 From ce9f3b95f38dc741d671f82b61c0c0f01ae50b73 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:27:57 -0500 Subject: [PATCH 14/18] fix: non-following pokemon using slide-in battle animation --- src/battle_controller_player.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index bdff3ad77d..417ee70105 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -2198,17 +2198,24 @@ static void PlayerHandleSwitchInAnim(void) gBattlerControllerFuncs[gActiveBattler] = SwitchIn_TryShinyAnimShowHealthbox; } -// In normal singles, if follower pokemon is out, have it slide in instead of being thrown +// In normal singles, if follower pokemon exists, +// and the pokemon following is being sent out, +// have it slide in instead of being thrown static bool8 ShouldDoSlideInAnim(void) { 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[gActiveBattler]]) + return FALSE; + return TRUE; } From 090c4f39362d2852ff3071e012425a8ab569b803 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Fri, 24 Jan 2025 02:02:21 -0500 Subject: [PATCH 15/18] feat: hardware fades now affect BLDALPHA too --- include/gba/io_reg.h | 3 +++ src/palette.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/include/gba/io_reg.h b/include/gba/io_reg.h index 53021185e3..75e3b56117 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/src/palette.c b/src/palette.c index 3ea2a08195..303e6ced42 100644 --- a/src/palette.c +++ b/src/palette.c @@ -962,10 +962,43 @@ static u8 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; From 16c31d61b44e7ece18e0d2039449d2ae4a0710e0 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sat, 25 Jan 2025 20:58:22 -0500 Subject: [PATCH 16/18] fix: removed fadescreenswapbuffers palette dependence, via hardware fade --- asm/macros/event.inc | 9 +++++---- src/event_object_movement.c | 2 ++ src/field_weather.c | 6 +++++- src/scrcmd.c | 17 +++++++++-------- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/asm/macros/event.inc b/asm/macros/event.inc index 7125e71643..06396af8d2 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -1674,13 +1674,14 @@ .4byte \text .endm - @ Equivalent to fadescreen but copies gPlttBufferUnfaded to gPaletteDecompressionBuffer 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 0xdc .byte \mode - .byte \nowait + .byte \nowait .endm @ Buffers the specified trainer's class name to the given string var. diff --git a/src/event_object_movement.c b/src/event_object_movement.c index f7f0f3e2df..18ca30bb96 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -2397,6 +2397,8 @@ void UpdateLightSprite(struct Sprite *sprite) { return; } + // Note to self: Don't set window registers during hardware fade! + switch (sprite->data[5]) { // lightType case 0: if (gPaletteFade.active) { // if palette fade is active, don't flicker since the timer won't be updated diff --git a/src/field_weather.c b/src/field_weather.c index 2bcaa2f857..199cff15e1 100644 --- a/src/field_weather.c +++ b/src/field_weather.c @@ -818,6 +818,7 @@ 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; @@ -1008,7 +1009,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) diff --git a/src/scrcmd.c b/src/scrcmd.c index a51e81aa91..edca8dbbbe 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -23,6 +23,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" @@ -661,19 +662,19 @@ bool8 ScrCmd_fadescreenswapbuffers(struct ScriptContext *ctx) switch (mode) { - case FADE_TO_BLACK: - case FADE_TO_WHITE: - default: - CpuCopy32(gPlttBufferUnfaded, gPaletteDecompressionBuffer, PLTT_SIZE); - FadeScreen(mode, 0); - break; case FADE_FROM_BLACK: case FADE_FROM_WHITE: - CpuCopy32(gPaletteDecompressionBuffer, gPlttBufferUnfaded, PLTT_SIZE); - FadeScreen(mode, 0); + // 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); From 521bea6bc3b3446bc69f4911df68653256a12b14 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sat, 25 Jan 2025 20:59:08 -0500 Subject: [PATCH 17/18] fix: allowed loading non-word-aligned tileset palettes --- src/fieldmap.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/fieldmap.c b/src/fieldmap.c index 1c792cb10e..dbda92b2f7 100644 --- a/src/fieldmap.c +++ b/src/fieldmap.c @@ -881,7 +881,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? @@ -891,10 +891,14 @@ 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 + // All 'gTilesetPalettes_' arrays should have ALIGNED(4) in them, + // but we use SmartCopy here just in case they don't if (skipFaded) - CpuFastCopy((void*)tileset->palettes[NUM_PALS_IN_PRIMARY], &gPlttBufferUnfaded[destOffset], size); + #ifdef CpuSmartCopy16 + CpuSmartCopy16(tileset->palettes[NUM_PALS_IN_PRIMARY], &gPlttBufferUnfaded[destOffset], size); + #else + CpuCopy16(tileset->palettes[NUM_PALS_IN_PRIMARY], &gPlttBufferUnfaded[destOffset], size); + #endif else LoadPaletteFast(tileset->palettes[NUM_PALS_IN_PRIMARY], destOffset, size); low = NUM_PALS_IN_PRIMARY; From d73167e262beb20eb529357692365ab9ed8414e6 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sat, 25 Jan 2025 21:37:16 -0500 Subject: [PATCH 18/18] fix: treat 1-tile jumps as slow copyable movement for follower pokemon --- src/event_object_movement.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 6498ceb455..30f81e0c81 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -5986,6 +5986,9 @@ static const u8 sActionIdToCopyableMovement[] = { [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, };