diff --git a/asm/macros/event.inc b/asm/macros/event.inc index c62888299..1a4f65dfe 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -1764,6 +1764,15 @@ .byte 0xd6 .endm + @ Equivalent to fadescreen but copies gPlttBufferUnfaded to gDecompressionBuffer 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 + .macro fadescreenswapbuffers mode:req, nowait=0 + .byte 0xdc + .byte \mode + .byte \nowait + .endm + .macro _dynmultichoice left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, shouldSort:req, initialSelected:req, callbacks:req argv:vararg .byte 0xe3 .2byte \left diff --git a/data/event_scripts.s b/data/event_scripts.s index 448024d67..f905447f7 100644 --- a/data/event_scripts.s +++ b/data/event_scripts.s @@ -1087,12 +1087,12 @@ EventScript_GymBadgeFanfare:: return EventScript_OutOfCenterPartyHeal:: - fadescreen FADE_TO_BLACK + fadescreenswapbuffers FADE_TO_BLACK playfanfare MUS_HEAL waitfanfare special HealPlayerParty callnative UpdateFollowingPokemon - fadescreen FADE_FROM_BLACK + fadescreenswapbuffers FADE_FROM_BLACK return EventScript_WallTownMap:: diff --git a/data/maps/MtEmber_Exterior/scripts.inc b/data/maps/MtEmber_Exterior/scripts.inc index 6bfe3cbfa..e0019ed08 100644 --- a/data/maps/MtEmber_Exterior/scripts.inc +++ b/data/maps/MtEmber_Exterior/scripts.inc @@ -51,10 +51,10 @@ MtEmber_Exterior_EventScript_BattleGrunt1:: MtEmber_Exterior_EventScript_DefeatedBothGrunts:: msgbox MtEmber_Exterior_Text_WellRegroupDontStepInsideThere closemessage - fadescreen FADE_TO_BLACK + fadescreenswapbuffers FADE_TO_BLACK removeobject LOCALID_GRUNT1 removeobject LOCALID_GRUNT2 - fadescreen FADE_FROM_BLACK + fadescreenswapbuffers FADE_FROM_BLACK release end diff --git a/data/script_cmd_table.inc b/data/script_cmd_table.inc index 2263c7d01..af639b661 100644 --- a/data/script_cmd_table.inc +++ b/data/script_cmd_table.inc @@ -216,6 +216,12 @@ gScriptCmdTable:: .4byte ScrCmd_bufferitemnameplural @ 0xd4 .4byte ScrCmd_selectapproachingtrainer @ 0xd5 .4byte ScrCmd_lockfortrainer @ 0xd6 + .4byte ScrCmd_nop1 @ 0xd7 + .4byte ScrCmd_nop1 @ 0xd8 + .4byte ScrCmd_nop1 @ 0xd9 + .4byte ScrCmd_nop1 @ 0xda + .4byte ScrCmd_nop1 @ 0xdb + .4byte ScrCmd_fadescreenswapbuffers @ 0xdc gScriptCmdTableEnd:: .4byte ScrCmd_nop diff --git a/data/scripts/static_pokemon.inc b/data/scripts/static_pokemon.inc index ea443ea3e..e401e18b2 100644 --- a/data/scripts/static_pokemon.inc +++ b/data/scripts/static_pokemon.inc @@ -1,14 +1,14 @@ EventScript_RemoveStaticMon:: - fadescreen FADE_TO_BLACK + fadescreenswapbuffers FADE_TO_BLACK removeobject VAR_LAST_TALKED - fadescreen FADE_FROM_BLACK + fadescreenswapbuffers FADE_FROM_BLACK release end EventScript_MonFlewAway:: - fadescreen FADE_TO_BLACK + fadescreenswapbuffers FADE_TO_BLACK removeobject VAR_LAST_TALKED - fadescreen FADE_FROM_BLACK + fadescreenswapbuffers FADE_FROM_BLACK bufferspeciesname STR_VAR_1, VAR_0x8004 msgbox Text_MonFlewAway release diff --git a/include/config/overworld.h b/include/config/overworld.h index 731e76de7..76748317f 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -62,7 +62,7 @@ #define GEN_8_PLA GEN_LATEST + 2 //Time -#define OW_TIMES_OF_DAY GEN_LATEST // Different generations have the times of day change at different times. +#define OW_TIMES_OF_DAY GEN_5 // Different generations have the times of day change at different times. #define OW_USE_FAKE_RTC FALSE // When TRUE, seconds on the in-game clock will only advance once every 60 playTimeVBlanks (every 60 frames). #define OW_ALTERED_TIME_RATIO GEN_LATEST // In GEN_8_PLA, the time in game moves forward 60 seconds for every second in the RTC. In GEN_9, it is 20 seconds. This has no effect if OW_USE_FAKE_RTC is FALSE. diff --git a/include/field_weather.h b/include/field_weather.h index fafd2c281..2e7a34a64 100644 --- a/include/field_weather.h +++ b/include/field_weather.h @@ -139,7 +139,7 @@ void SetWeatherScreenFadeOut(void); void WeatherProcessingIdle(void); u8 GetCurrentWeather(void); void delay(u8, u8, u32); -void UpdateSpritePaletteWithWeather(u8 palIdx); +void UpdateSpritePaletteWithWeather(u8 palIdx, bool32 allowFog); void ResetPreservedPalettesInWeather(void); void PreservePaletteInWeather(u8 palIdx); bool32 IsWeatherAlphaBlend(void); diff --git a/include/global.fieldmap.h b/include/global.fieldmap.h index 01150c39d..d8eb05df2 100644 --- a/include/global.fieldmap.h +++ b/include/global.fieldmap.h @@ -79,7 +79,8 @@ typedef void (*TilesetCB)(void); struct Tileset { - /*0x00*/ bool8 isCompressed; + /*0x00*/ bool8 isCompressed:1; + /*0x00*/ u8 swapPalettes:7; // bitmask determining whether palette has an alternate, night-time palette /*0x01*/ bool8 isSecondary; /*0x04*/ const u32 *tiles; /*0x08*/ const u16 (*palettes)[16]; diff --git a/include/overworld.h b/include/overworld.h index 2a53f4113..4699d50d8 100644 --- a/include/overworld.h +++ b/include/overworld.h @@ -27,6 +27,11 @@ #define MOVEMENT_MODE_FROZEN 1 #define MOVEMENT_MODE_SCRIPTED 2 +#define TIME_OF_DAY_NIGHT 0 +#define TIME_OF_DAY_TWILIGHT 1 +#define TIME_OF_DAY_DAY 2 +#define TIME_OF_DAY_MAX TIME_OF_DAY_DAY + struct LinkPlayerObjectEvent { u8 active; @@ -49,6 +54,14 @@ enum { MUSIC_DISABLE_KEEP, }; +struct __attribute__((packed)) TimeBlendSettings { + u16 weight:9; + u16 time1:3; + u16 time0:3; + u16 unused:1; + u16 altWeight; +}; + extern struct WarpData gLastUsedWarp; extern struct LinkPlayerObjectEvent gLinkPlayerObjectEvents[4]; @@ -65,6 +78,10 @@ extern u8 gExitStairsMovementDisabled; extern u8 gDisableMapMusicChangeOnMapLoad; extern u8 gGlobalFieldTintMode; +extern u8 gTimeOfDay; +extern u16 gTimeUpdateCounter; + +extern struct TimeBlendSettings currentTimeBlend; extern const struct Coords32 gDirectionToVectors[]; @@ -178,10 +195,15 @@ u16 SetLinkWaitingForScript(void); void SetMainCallback1(MainCallback cb); void CB1_Overworld(void); void CB2_ReturnToFieldContinueScript(void); +u8 UpdateTimeOfDay(void); +bool8 MapHasNaturalLight(u8 mapType); +void UpdateAltBgPalettes(u16 palettes); +void UpdatePalettesWithTime(u32); u8 GetLastUsedWarpMapSectionId(void); void StoreInitialPlayerAvatarState(void); void UpdateEscapeWarp(s16 x, s16 y); bool8 SetDiveWarpEmerge(u16 x, u16 y); bool8 SetDiveWarpDive(u16 x, u16 y); +u8 UpdateSpritePaletteWithTime(u8 paletteNum); #endif //GUARD_OVERWORLD_H diff --git a/include/palette.h b/include/palette.h index 73da36fef..eb40e0512 100644 --- a/include/palette.h +++ b/include/palette.h @@ -16,6 +16,8 @@ #define PALETTE_FADE_STATUS_LOADING 0xFF #define PALETTES_BG 0x0000FFFF +// like PALETTES_BG but excludes UI pals [13, 15] +#define PALETTES_MAP 0x00001FFF #define PALETTES_OBJECTS 0xFFFF0000 #define PALETTES_ALL (PALETTES_BG | PALETTES_OBJECTS) @@ -26,6 +28,11 @@ #define OBJ_PLTT_ID(n) (OBJ_PLTT_OFFSET + PLTT_ID(n)) #define OBJ_PLTT_ID2(n) (PLTT_ID((n) + 16)) +// Used to determine whether a sprite palette tag +// should be excluded from time (and weather) blending +#define BLEND_IMMUNE_FLAG (1 << 15) +#define IS_BLEND_IMMUNE_TAG(tag) ((tag) & BLEND_IMMUNE_FLAG) + enum { FAST_FADE_IN_FROM_WHITE, @@ -34,9 +41,19 @@ enum FAST_FADE_OUT_TO_BLACK, }; +struct BlendSettings { + u32 blendColor:24; + u32 isTint:1; + u32 coeff:5; +}; + struct PaletteFadeControl { u32 multipurpose1; + // These three are only used for TOD blending + struct BlendSettings *bld0; + struct BlendSettings *bld1; + u16 weight:9; // [0, 256], so must be 9 bits u8 delayCounter:6; u16 y:5; // blend coefficient u16 targetY:5; // target blend coefficient @@ -55,6 +72,8 @@ struct PaletteFadeControl u32 unused; }; +extern const struct BlendSettings gTimeOfDayBlend[]; + extern struct PaletteFadeControl gPaletteFade; extern u32 gPlttBufferTransferPending; extern u16 gPlttBufferUnfaded[PLTT_BUFFER_SIZE]; @@ -67,11 +86,13 @@ void TransferPlttBuffer(void); u32 UpdatePaletteFade(void); void ResetPaletteFade(void); bool32 BeginNormalPaletteFade(u32 selectedPalettes, s8 delay, u8 startY, u8 targetY, u32 blendColor); +bool32 BeginTimeOfDayPaletteFade(u32, s8, u8, u8, struct BlendSettings *, struct BlendSettings *, u32, u32); void ResetPaletteFadeControl(void); void InvertPlttBuffer(u32 selectedPalettes); void BeginFastPaletteFade(u32 submode); void BeginHardwarePaletteFade(u32 blendCnt, u32 delay, u32 y, u32 targetY, u32 shouldResetBlendRegisters); void BlendPalettes(u32 selectedPalettes, u8 coeff, u32 color); +void BlendPalettesFine(u32 palettes, u16 *src, u16 *dst, u32 coeff, u32 color); void BlendPalettesUnfaded(u32 selectedPalettes, u8 coeff, u32 color); void TintPalette_GrayScale(u16 *palette, u32 count); void TintPalette_GrayScale2(u16 *palette, u32 count); @@ -81,6 +102,9 @@ void CopyPaletteInvertedTint(const u16 *src, u16 *dst, u32 count, u8 tone); void BlendPalettesGradually(u32 selectedPalettes, s8 delay, u8 coeff, u8 coeffTarget, u16 color, u8 priority, u8 id); bool32 IsBlendPalettesGraduallyTaskActive(u8 var); void DestroyBlendPalettesGraduallyTask(void); +void TimeBlendPalette(u16 palOffset, u32 coeff, u32 blendColor); +void TimeMixPalettes(u32, u16 *, u16 *, struct BlendSettings *, struct BlendSettings *, u16); +void AvgPaletteWeighted(u16 *src0, u16 *src1, u16 *dst, u16 weight0); static inline void SetBackdropFromColor(u32 color) { diff --git a/src/battle_bg.c b/src/battle_bg.c index 1f1199d94..81b5049bc 100644 --- a/src/battle_bg.c +++ b/src/battle_bg.c @@ -398,12 +398,18 @@ static u8 GetBattleTerrainByMapScene(u8 mapBattleScene) static void LoadBattleTerrainGfx(u16 terrain) { + u32 palettes = 0; if (terrain >= NELEMS(gBattleTerrainInfo)) terrain = BATTLE_TERRAIN_PLAIN; // Copy to bg3 LZDecompressVram(gBattleTerrainInfo[terrain].background.tileset, (void *)BG_CHAR_ADDR(2)); LZDecompressVram(gBattleTerrainInfo[terrain].background.tilemap, (void *)BG_SCREEN_ADDR(26)); LoadCompressedPalette(gBattleTerrainInfo[terrain].background.palette, BG_PLTT_ID(2), 3 * PLTT_SIZE_4BPP); + + palettes ^= (1 << 2); + palettes ^= (1 << 3); + palettes ^= (1 << 4); + UpdatePalettesWithTime(palettes); } static void LoadBattleTerrainEntryGfx(u16 terrain) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index a70367470..f3f831dbf 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -2093,7 +2093,7 @@ static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool32 shiny) } if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog - UpdateSpritePaletteWithWeather(paletteNum); + UpdateSpritePaletteWithWeather(paletteNum, FALSE); return paletteNum; } @@ -2116,7 +2116,7 @@ static void FollowerSetGraphics(struct ObjectEvent *objEvent, u16 species, u8 fo } else if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog { - UpdateSpritePaletteWithWeather(gSprites[objEvent->spriteId].oam.paletteNum); + UpdateSpritePaletteWithWeather(gSprites[objEvent->spriteId].oam.paletteNum, FALSE); } } @@ -2162,7 +2162,7 @@ static void RefreshFollowerGraphics(struct ObjectEvent *objEvent) { UpdateSpritePalette(&sObjectEventSpritePalettes[i], sprite); if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog - UpdateSpritePaletteWithWeather(sprite->oam.paletteNum); + UpdateSpritePaletteWithWeather(sprite->oam.paletteNum, FALSE); } } @@ -2795,7 +2795,17 @@ static u8 UpdateSpritePalette(const struct SpritePalette *spritePalette, struct sprite->inUse = FALSE; FieldEffectFreePaletteIfUnused(sprite->oam.paletteNum); sprite->inUse = TRUE; + if (IndexOfSpritePaletteTag(spritePalette->tag) == 0xFF) + { + sprite->oam.paletteNum = LoadSpritePalette(spritePalette); + UpdateSpritePaletteWithWeather(sprite->oam.paletteNum, FALSE); + } + else + { + sprite->oam.paletteNum = LoadSpritePalette(spritePalette); + } sprite->oam.paletteNum = LoadSpritePalette(spritePalette); + return sprite->oam.paletteNum; } @@ -3011,6 +3021,8 @@ static u8 LoadSpritePaletteIfTagExists(const struct SpritePalette *spritePalette if (paletteNum != 0xFF) // don't load twice; return return paletteNum; paletteNum = LoadSpritePalette(spritePalette); + if (paletteNum != 0xFF) + UpdateSpritePaletteWithWeather(paletteNum, FALSE); ApplyGlobalFieldPaletteTint(paletteNum); return paletteNum; } diff --git a/src/field_effect.c b/src/field_effect.c index a6a21e0fc..ce52c53a1 100644 --- a/src/field_effect.c +++ b/src/field_effect.c @@ -460,7 +460,7 @@ static void FieldEffectScript_LoadFadedPal(const u8 **script) LoadSpritePalette(spritePalette); if (idx == 0xFF) ApplyGlobalFieldPaletteTint(IndexOfSpritePaletteTag(spritePalette->tag)); - UpdateSpritePaletteWithWeather(IndexOfSpritePaletteTag(spritePalette->tag)); + UpdateSpritePaletteWithWeather(IndexOfSpritePaletteTag(spritePalette->tag), TRUE); *script += sizeof(u32); } diff --git a/src/field_effect_helpers.c b/src/field_effect_helpers.c index 7e20d17e1..4887a9933 100644 --- a/src/field_effect_helpers.c +++ b/src/field_effect_helpers.c @@ -157,7 +157,7 @@ static void LoadObjectRegularReflectionPalette(struct ObjectEvent *objectEvent, ApplyIceFilter(mainSprite->oam.paletteNum, filteredData); paletteNum = LoadSpritePalette(&filteredPal); ApplyGlobalFieldPaletteTint(paletteNum); - UpdateSpritePaletteWithWeather(paletteNum); + UpdateSpritePaletteWithWeather(paletteNum, TRUE); } sprite->oam.paletteNum = paletteNum; sprite->oam.objMode = ST_OAM_OBJ_BLEND; @@ -172,7 +172,7 @@ static void LoadObjectHighBridgeReflectionPalette(struct ObjectEvent *objectEven CpuFill16(0x55C9, blueData, PLTT_SIZE_4BPP); sprite->oam.paletteNum = LoadSpritePalette(&bluePalette); ApplyGlobalFieldPaletteTint(sprite->oam.paletteNum); - UpdateSpritePaletteWithWeather(sprite->oam.paletteNum); + UpdateSpritePaletteWithWeather(sprite->oam.paletteNum, TRUE); } static void UpdateObjectReflectionSprite(struct Sprite *reflectionSprite) @@ -209,7 +209,7 @@ static void UpdateObjectReflectionSprite(struct Sprite *reflectionSprite) ApplyIceFilter(mainSprite->oam.paletteNum, filteredData); paletteNum = LoadSpritePalette(&filteredPal); ApplyGlobalFieldPaletteTint(paletteNum); - UpdateSpritePaletteWithWeather(paletteNum); + UpdateSpritePaletteWithWeather(paletteNum, TRUE); } reflectionSprite->oam.paletteNum = paletteNum; } diff --git a/src/field_specials.c b/src/field_specials.c index 325b87e64..47d365395 100644 --- a/src/field_specials.c +++ b/src/field_specials.c @@ -20,6 +20,7 @@ #include "battle_tower.h" #include "field_camera.h" #include "field_effect.h" +#include "field_weather.h" #include "event_object_movement.h" #include "item.h" #include "menu_indicators.h" @@ -2476,6 +2477,7 @@ static void MoveDeoxysObject(u8 num) { u8 mapObjId; LoadPalette(sDeoxysObjectPals[num], OBJ_PLTT_ID(10), PLTT_SIZEOF(4)); + UpdateSpritePaletteWithWeather(10, FALSE); ApplyGlobalFieldPaletteTint(10); TryGetObjectEventIdByLocalIdAndMap(1, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, &mapObjId); if (num == 0) diff --git a/src/field_weather.c b/src/field_weather.c index 33c900c1b..ce617f4b0 100644 --- a/src/field_weather.c +++ b/src/field_weather.c @@ -5,12 +5,16 @@ #include "field_weather.h" #include "field_weather_util.h" #include "field_weather_effects.h" +#include "overworld.h" #include "task.h" #include "trig.h" #include "constants/field_weather.h" #include "constants/weather.h" #include "constants/songs.h" +#include "quest_log.h" +#include "constants/quest_log.h" + #define DROUGHT_COLOR_INDEX(color) ((((color) >> 1) & 0xF) | (((color) >> 2) & 0xF0) | (((color) >> 3) & 0xF00)) enum @@ -449,15 +453,30 @@ static void ApplyGammaShift(u8 startPalIndex, u8 numPalettes, s8 gammaIndex) if (gammaIndex > 0) { + // Create the palette mask + u32 palettes = PALETTES_ALL; + numPalettes += startPalIndex; + palettes = (palettes >> startPalIndex) << startPalIndex; + palettes = (palettes << (32-numPalettes)) >> (32-numPalettes); + numPalettes -= startPalIndex; + gammaIndex--; palOffset = PLTT_ID(startPalIndex); + UpdateAltBgPalettes(palettes & PALETTES_BG); + // Thunder gamma-shift looks bad on night-blended palettes, so ignore time blending in some situations + if (!(gammaIndex > 3) && MapHasNaturalLight(gMapHeader.mapType)) + UpdatePalettesWithTime(palettes); + else + CpuFastCopy(gPlttBufferUnfaded + palOffset, gPlttBufferFaded + palOffset, PLTT_SIZE_4BPP * numPalettes); + numPalettes += startPalIndex; curPalIndex = startPalIndex; // Loop through the speficied palette range and apply necessary gamma shifts to the colors. while (curPalIndex < numPalettes) { - if (sPaletteGammaTypes[curPalIndex] == GAMMA_NONE) + if (sPaletteGammaTypes[curPalIndex] == GAMMA_NONE || + (curPalIndex >= 16 && IS_BLEND_IMMUNE_TAG(GetSpritePaletteTagByPaletteNum(curPalIndex - 16)))) { // No palette change. CpuFastCopy(&gPlttBufferUnfaded[palOffset], &gPlttBufferFaded[palOffset], PLTT_SIZE_4BPP); @@ -493,8 +512,18 @@ static void ApplyGammaShift(u8 startPalIndex, u8 numPalettes, s8 gammaIndex) } else { - // No palette blending. - CpuFastCopy(&gPlttBufferUnfaded[PLTT_ID(startPalIndex)], &gPlttBufferFaded[PLTT_ID(startPalIndex)], numPalettes * PLTT_SIZE_4BPP); + if (MapHasNaturalLight(gMapHeader.mapType)) + { + // Time-blend + u32 palettes = ((1 << numPalettes) - 1) << startPalIndex; + UpdateAltBgPalettes(palettes & PALETTES_BG); + UpdatePalettesWithTime(palettes); + } + else + { + // No palette blending. + CpuFastCopy(&gPlttBufferUnfaded[PLTT_ID(startPalIndex)], &gPlttBufferFaded[PLTT_ID(startPalIndex)], numPalettes * PLTT_SIZE_4BPP); + } } } @@ -515,6 +544,9 @@ static void ApplyGammaShiftWithBlend(u8 startPalIndex, u8 numPalettes, s8 gammaI while (curPalIndex < numPalettes) { + UpdateAltBgPalettes((1 << (palOffset >> 4)) & PALETTES_BG); + CpuFastCopy(gPlttBufferUnfaded + palOffset, gPlttBufferFaded + palOffset, 16 * sizeof(u16)); + UpdatePalettesWithTime(1 << (palOffset >> 4)); // Apply TOD blend if (sPaletteGammaTypes[curPalIndex] == GAMMA_NONE) { // No gamma shift. Simply blend the colors. @@ -597,49 +629,24 @@ static void ApplyDroughtGammaShiftWithBlend(s8 gammaIndex, u8 blendCoeff, u32 bl static void ApplyFogBlend(u8 blendCoeff, u32 blendColor) { - struct RGBColor color; - u8 rBlend; - u8 gBlend; - u8 bBlend; - u16 curPalIndex; + u32 curPalIndex; + u16 fogCoeff = min((gTimeOfDay + 1) * 4, 12); - BlendPalette(0, 256, blendCoeff, blendColor); - color = *(struct RGBColor *)&blendColor; - rBlend = color.r; - gBlend = color.g; - bBlend = color.b; + // First blend all palettes with time + UpdateAltBgPalettes(PALETTES_BG); + CpuFastCopy(gPlttBufferUnfaded, gPlttBufferFaded, PLTT_BUFFER_SIZE * 2); + UpdatePalettesWithTime(PALETTES_ALL); + // Then blend tile palettes [0, 12] faded->faded with fadeIn color + BlendPalettesFine(PALETTES_MAP, gPlttBufferFaded, gPlttBufferFaded, blendCoeff, blendColor); + // Do fog blending on marked sprite palettes for (curPalIndex = 16; curPalIndex < 32; curPalIndex++) { if (LightenSpritePaletteInFog(curPalIndex)) - { - u16 palEnd = PLTT_ID(curPalIndex + 1); - u16 palOffset = PLTT_ID(curPalIndex); - - while (palOffset < palEnd) - { - struct RGBColor color = *(struct RGBColor *)&gPlttBufferUnfaded[palOffset]; - u8 r = color.r; - u8 g = color.g; - u8 b = color.b; - - r += ((28 - r) * 3) >> 2; - g += ((31 - g) * 3) >> 2; - b += ((28 - b) * 3) >> 2; - - r += ((rBlend - r) * blendCoeff) >> 4; - g += ((gBlend - g) * blendCoeff) >> 4; - b += ((bBlend - b) * blendCoeff) >> 4; - - gPlttBufferFaded[palOffset] = (b << 10) | (g << 5) | r; - palOffset++; - } - } - else - { - BlendPalette(PLTT_ID(curPalIndex), 16, blendCoeff, blendColor); - } + BlendPalettesFine(1, gPlttBufferFaded + PLTT_ID(curPalIndex), gPlttBufferFaded + PLTT_ID(curPalIndex), fogCoeff, RGB(28, 31, 28)); } + // Finally blend all sprite palettes faded->faded with fadeIn color + BlendPalettesFine(PALETTES_OBJECTS, gPlttBufferFaded, gPlttBufferFaded, blendCoeff, blendColor); } static void MarkFogSpritePalToLighten(u8 paletteIndex) @@ -732,8 +739,10 @@ void FadeScreen(u8 mode, s8 delay) if (fadeOut) { - if (useWeatherPal) - CpuFastCopy(gPlttBufferFaded, gPlttBufferUnfaded, PLTT_SIZE); + // Note: Copying faded -> unfaded like this works fine, except if the screen is faded back in + // without transitioning to a different screen + // For cases like that, use fadescreenswapbuffers + CpuFastCopy(gPlttBufferFaded, gPlttBufferUnfaded, PLTT_SIZE); BeginNormalPaletteFade(PALETTES_ALL, delay, 0, 16, fadeColor); gWeatherPtr->palProcessingState = WEATHER_PAL_STATE_SCREEN_FADING_OUT; @@ -741,8 +750,17 @@ void FadeScreen(u8 mode, s8 delay) else { gWeatherPtr->fadeDestColor = fadeColor; + UpdateTimeOfDay(); if (useWeatherPal) gWeatherPtr->fadeScreenCounter = 0; + else if (!QL_IS_PLAYBACK_STATE && MapHasNaturalLight(gMapHeader.mapType)) + { + UpdateAltBgPalettes(PALETTES_BG); + BeginTimeOfDayPaletteFade(PALETTES_ALL, delay, 16, 0, + (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.time0], + (struct BlendSettings *)&gTimeOfDayBlend[currentTimeBlend.time1], + currentTimeBlend.weight, fadeColor); + } else BeginNormalPaletteFade(PALETTES_ALL, delay, 16, 0, fadeColor); @@ -828,7 +846,7 @@ bool8 IsWeatherNotFadingIn(void) return (gWeatherPtr->palProcessingState != WEATHER_PAL_STATE_SCREEN_FADING_IN); } -void UpdateSpritePaletteWithWeather(u8 spritePaletteIndex) +void UpdateSpritePaletteWithWeather(u8 spritePaletteIndex, bool32 allowFog) { u16 paletteIndex = 16 + spritePaletteIndex; u16 i; @@ -855,12 +873,29 @@ void UpdateSpritePaletteWithWeather(u8 spritePaletteIndex) default: if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) { - ApplyGammaShift(paletteIndex, 1, gWeatherPtr->gammaIndex); + if (gWeatherPtr->gammaIndex) + ApplyGammaShift(paletteIndex, 1, gWeatherPtr->gammaIndex); + else + UpdateSpritePaletteWithTime(spritePaletteIndex); } else { - paletteIndex = PLTT_ID(paletteIndex); - BlendPalette(paletteIndex, 16, 12, RGB(28, 31, 28)); + // In horizontal fog, only specific palettes should be fog-blended + if (allowFog) + { + i = min((gTimeOfDay + 1) * 4, 12); // fog coeff, highest in day and lowest at night + paletteIndex = PLTT_ID(paletteIndex); + // First blend with time + CpuFastCopy(gPlttBufferUnfaded + paletteIndex, gPlttBufferFaded + paletteIndex, PLTT_SIZE_4BPP); + UpdateSpritePaletteWithTime(spritePaletteIndex); + // Then blend faded->faded with fog coeff + BlendPalettesFine(1, gPlttBufferFaded + paletteIndex, gPlttBufferFaded + paletteIndex, i, RGB(28, 31, 28)); + } + else + { + // Otherwise, just time-blend the palette + UpdateSpritePaletteWithTime(spritePaletteIndex); + } } break; } @@ -874,7 +909,7 @@ void ApplyWeatherGammaShiftToPal(u8 paletteIndex) void LoadCustomWeatherSpritePalette(const u16 *palette) { LoadPalette(palette, OBJ_PLTT_ID(gWeatherPtr->weatherPicSpritePalIndex), PLTT_SIZE_4BPP); - UpdateSpritePaletteWithWeather(gWeatherPtr->weatherPicSpritePalIndex); + UpdateSpritePaletteWithWeather(gWeatherPtr->weatherPicSpritePalIndex, TRUE); } static void LoadDroughtWeatherPalette(u8 *gammaIndexPtr, u8 *a1) diff --git a/src/overworld.c b/src/overworld.c index a6bf91810..3e55aa002 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -7,6 +7,7 @@ #include "event_data.h" #include "event_object_movement.h" #include "event_scripts.h" +#include "fake_rtc.h" #include "field_camera.h" #include "field_control_avatar.h" #include "field_effect.h" @@ -38,6 +39,7 @@ #include "random.h" #include "renewable_hidden_items.h" #include "roamer.h" +#include "rtc.h" #include "safari_zone.h" #include "save_location.h" #include "scanline_effect.h" @@ -121,6 +123,10 @@ static KeyInterCB sPlayerKeyInterceptCallback; static bool8 sReceivingFromLink; static u8 sRfuKeepAliveTimer; +u8 gTimeOfDay; +struct TimeBlendSettings currentTimeBlend; +u16 gTimeUpdateCounter; // playTimeVBlanks will eventually overflow, so this is used to update TOD + static u8 CountBadgesForOverworldWhiteOutLossCalculation(void); static void Overworld_ResetStateAfterWhitingOut(void); static void Overworld_SetWhiteoutRespawnPoint(void); @@ -1460,6 +1466,168 @@ void CB1_Overworld(void) } } +#define TINT_NIGHT Q_8_8(0.456) | Q_8_8(0.456) << 8 | Q_8_8(0.615) << 16 + +const struct BlendSettings gTimeOfDayBlend[] = +{ + [TIME_OF_DAY_NIGHT] = {.coeff = 10, .blendColor = TINT_NIGHT, .isTint = TRUE}, + [TIME_OF_DAY_TWILIGHT] = {.coeff = 4, .blendColor = 0xA8B0E0, .isTint = TRUE}, + [TIME_OF_DAY_DAY] = {.coeff = 0, .blendColor = 0}, +}; + +#define MORNING_START_MINUTES (MORNING_HOUR_BEGIN * MINUTES_PER_HOUR) +#define MORNING_MID_POINT (MORNING_HOUR_BEGIN * MINUTES_PER_HOUR + (MORNING_HOUR_END * MINUTES_PER_HOUR - MORNING_HOUR_BEGIN * MINUTES_PER_HOUR) / 2) +#define MORNING_END_MINUTES (MORNING_HOUR_END * MINUTES_PER_HOUR) +#define EVENING_START_MINUTES (EVENING_HOUR_BEGIN * MINUTES_PER_HOUR) +#define EVENING_MID_POINT (EVENING_HOUR_BEGIN * MINUTES_PER_HOUR + (EVENING_HOUR_END * MINUTES_PER_HOUR - EVENING_HOUR_BEGIN * MINUTES_PER_HOUR) / 2) +#define EVENING_END_MINUTES (EVENING_HOUR_END * MINUTES_PER_HOUR) + +u8 UpdateTimeOfDay(void) +{ + s32 hours, minutes; + s32 minutesOfDay; + RtcCalcLocalTime(); + hours = gLocalTime.hours; + minutes = gLocalTime.minutes; + minutesOfDay = hours * MINUTES_PER_HOUR + minutes; + if (minutesOfDay < MORNING_START_MINUTES) // night + { + currentTimeBlend.weight = 256; + currentTimeBlend.altWeight = 0; + gTimeOfDay = currentTimeBlend.time0 = currentTimeBlend.time1 = TIME_OF_DAY_NIGHT; + } + else if (minutesOfDay < MORNING_MID_POINT) // night->twilight + { + currentTimeBlend.time0 = TIME_OF_DAY_NIGHT; + currentTimeBlend.time1 = TIME_OF_DAY_TWILIGHT; + currentTimeBlend.weight = 256 - 256 * (hours * 60 - MORNING_START_MINUTES + minutes) / (MORNING_MID_POINT - MORNING_START_MINUTES); + currentTimeBlend.altWeight = (256 - currentTimeBlend.weight) / 2; + gTimeOfDay = TIME_OF_DAY_DAY; + } + else if (minutesOfDay < MORNING_END_MINUTES) // twilight->day + { + currentTimeBlend.time0 = TIME_OF_DAY_TWILIGHT; + currentTimeBlend.time1 = TIME_OF_DAY_DAY; + currentTimeBlend.weight = 256 - 256 * (hours * 60 - MORNING_MID_POINT + minutes) / (MORNING_END_MINUTES - MORNING_MID_POINT); + currentTimeBlend.altWeight = (256 - currentTimeBlend.weight) / 2 + 128; + gTimeOfDay = TIME_OF_DAY_DAY; + } + else if (minutesOfDay < EVENING_START_MINUTES) // day + { + currentTimeBlend.weight = currentTimeBlend.altWeight = 256; + gTimeOfDay = currentTimeBlend.time0 = currentTimeBlend.time1 = TIME_OF_DAY_DAY; + } + else if (minutesOfDay < EVENING_MID_POINT) + { + currentTimeBlend.time0 = TIME_OF_DAY_DAY; + currentTimeBlend.time1 = TIME_OF_DAY_TWILIGHT; + currentTimeBlend.weight = 256 - 256 * (hours * 60 - EVENING_START_MINUTES + minutes) / (EVENING_MID_POINT - EVENING_START_MINUTES); + currentTimeBlend.altWeight = currentTimeBlend.weight / 2 + 128; + gTimeOfDay = TIME_OF_DAY_TWILIGHT; + } + else if (minutesOfDay < EVENING_END_MINUTES) // twilight->night + { + currentTimeBlend.time0 = TIME_OF_DAY_TWILIGHT; + currentTimeBlend.time1 = TIME_OF_DAY_NIGHT; + currentTimeBlend.weight = 256 - 256 * (hours * 60 - EVENING_MID_POINT + minutes) / (EVENING_END_MINUTES - EVENING_MID_POINT); + currentTimeBlend.altWeight = currentTimeBlend.weight / 2; + gTimeOfDay = TIME_OF_DAY_NIGHT; + } + else // 22-24, night + { + currentTimeBlend.weight = 256; + currentTimeBlend.altWeight = 0; + gTimeOfDay = currentTimeBlend.time0 = currentTimeBlend.time1 = TIME_OF_DAY_NIGHT; + } + return gTimeOfDay; +} + +// Whether a map type is naturally lit/outside +bool8 MapHasNaturalLight(u8 mapType) +{ + return (mapType == MAP_TYPE_TOWN + || mapType == MAP_TYPE_CITY + || mapType == MAP_TYPE_ROUTE + || mapType == MAP_TYPE_OCEAN_ROUTE + ); +} + +// Update & mix day / night bg palettes (into unfaded) +void UpdateAltBgPalettes(u16 palettes) +{ + const struct Tileset *primary = gMapHeader.mapLayout->primaryTileset; + const struct Tileset *secondary = gMapHeader.mapLayout->secondaryTileset; + u32 i = 1; + if (QL_IS_PLAYBACK_STATE) + return; + if (!MapHasNaturalLight(gMapHeader.mapType)) + return; + palettes &= ~((1 << NUM_PALS_IN_PRIMARY) - 1) | primary->swapPalettes; + palettes &= ((1 << NUM_PALS_IN_PRIMARY) - 1) | (secondary->swapPalettes << NUM_PALS_IN_PRIMARY); + palettes &= PALETTES_MAP ^ (1 << 0); // don't blend palette 0, [13,15] + palettes >>= 1; // start at palette 1 + if (!palettes) + return; + while (palettes) + { + if (palettes & 1) + { + if (i < NUM_PALS_IN_PRIMARY) + AvgPaletteWeighted(&((u16*)primary->palettes)[i*16], &((u16*)primary->palettes)[((i+9)%16)*16], gPlttBufferUnfaded + i * 16, currentTimeBlend.altWeight); + else + AvgPaletteWeighted(&((u16*)secondary->palettes)[i*16], &((u16*)secondary->palettes)[((i+9)%16)*16], gPlttBufferUnfaded + i * 16, currentTimeBlend.altWeight); + } + i++; + palettes >>= 1; + } +} + +void UpdatePalettesWithTime(u32 palettes) +{ + if (QL_IS_PLAYBACK_STATE) + return; + if (MapHasNaturalLight(gMapHeader.mapType)) + { + u32 i; + u32 mask = 1 << 16; + if (palettes >= (1 << 16)) + for (i = 0; i < 16; i++, mask <<= 1) + 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 + ); + } +} + +u8 UpdateSpritePaletteWithTime(u8 paletteNum) +{ + if (QL_IS_PLAYBACK_STATE) + return paletteNum; + if (MapHasNaturalLight(gMapHeader.mapType)) + { + if (GetSpritePaletteTagByPaletteNum(paletteNum) != 0xFFFF && 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; +} + static void OverworldBasic(void) { ScriptContext_RunScript(); @@ -1472,6 +1640,24 @@ static void OverworldBasic(void) UpdatePaletteFade(); UpdateTilesetAnimations(); DoScheduledBgTilemapCopiesToVram(); + // Every minute if no palette fade is active, update TOD blending as needed + if (!gPaletteFade.active && ++gTimeUpdateCounter >= (SECONDS_PER_MINUTE * 1 / FakeRtc_GetSecondsRatio())) + { + struct TimeBlendSettings cachedBlend = { + .time0 = currentTimeBlend.time0, + .time1 = currentTimeBlend.time1, + .weight = currentTimeBlend.weight, + }; + gTimeUpdateCounter = 0; + UpdateTimeOfDay(); + if (cachedBlend.time0 != currentTimeBlend.time0 + || cachedBlend.time1 != currentTimeBlend.time1 + || cachedBlend.weight != currentTimeBlend.weight) + { + UpdateAltBgPalettes(PALETTES_BG); + UpdatePalettesWithTime(PALETTES_ALL); + } + } } // This CB2 is used when starting diff --git a/src/palette.c b/src/palette.c index 046f11a7e..00b9555cc 100644 --- a/src/palette.c +++ b/src/palette.c @@ -9,11 +9,13 @@ enum NORMAL_FADE, FAST_FADE, HARDWARE_FADE, + TIME_OF_DAY_FADE, }; static u32 UpdateNormalPaletteFade(void); static void BeginFastPaletteFadeInternal(u32); static u32 UpdateFastPaletteFade(void); +static u8 UpdateTimeOfDayPaletteFade(void); static u32 UpdateHardwarePaletteFade(void); static void UpdateBlendRegisters(void); static bool32 IsSoftwarePaletteFadeFinishing(void); @@ -77,6 +79,8 @@ u32 UpdatePaletteFade(void) result = UpdateNormalPaletteFade(); else if (gPaletteFade.mode == FAST_FADE) result = UpdateFastPaletteFade(); + else if (gPaletteFade.mode == TIME_OF_DAY_FADE) + result = UpdateTimeOfDayPaletteFade(); else result = UpdateHardwarePaletteFade(); sPlttBufferTransferPending = gPaletteFade.multipurpose1; @@ -128,6 +132,56 @@ bool32 BeginNormalPaletteFade(u32 selectedPalettes, s8 delay, u8 startY, u8 targ } } +// Like normal palette fade but respects sprite/tile palettes immune to time of day fading +bool32 BeginTimeOfDayPaletteFade(u32 selectedPalettes, s8 delay, u8 startY, u8 targetY, struct BlendSettings *bld0, struct BlendSettings *bld1, u32 weight, u32 color) +{ + u8 temp; + + if (gPaletteFade.active) + { + return FALSE; + } + else + { + gPaletteFade.deltaY = 2; + + if (delay < 0) + { + gPaletteFade.deltaY += (delay * -1); + delay = 0; + } + + gPaletteFade_selectedPalettes = selectedPalettes; + gPaletteFade.delayCounter = delay; + gPaletteFade_delay = delay; + gPaletteFade.y = startY; + gPaletteFade.targetY = targetY; + gPaletteFade.active = 1; + gPaletteFade.mode = TIME_OF_DAY_FADE; + + gPaletteFade.blendColor = color; + gPaletteFade.bld0 = bld0; + gPaletteFade.bld1 = bld1; + gPaletteFade.weight = weight; + + if (startY < targetY) + gPaletteFade.yDec = 0; + else + gPaletteFade.yDec = 1; + + UpdatePaletteFade(); + + temp = gPaletteFade.bufferTransferDisabled; + gPaletteFade.bufferTransferDisabled = 0; + CpuCopy32(gPlttBufferFaded, (void *)PLTT, PLTT_SIZE); + sPlttBufferTransferPending = 0; + if (gPaletteFade.mode == HARDWARE_FADE && gPaletteFade.active) + UpdateBlendRegisters(); + gPaletteFade.bufferTransferDisabled = temp; + return TRUE; + } +} + void ResetPaletteFadeControl(void) { gPaletteFade.multipurpose1 = 0; @@ -148,6 +202,119 @@ void ResetPaletteFadeControl(void) gPaletteFade.deltaY = 2; } +// Like normal palette fade, but respects sprite/tile palettes immune to time of day fading +static u8 UpdateTimeOfDayPaletteFade(void) +{ + u16 paletteOffset; + u16 selectedPalettes; + u16 timePalettes = 0; // palettes passed to the time-blender + u16 copyPalettes; + u16 * src; + u16 * dst; + + if (!gPaletteFade.active) + return PALETTE_FADE_STATUS_DONE; + + if (IsSoftwarePaletteFadeFinishing()) + return gPaletteFade.active ? PALETTE_FADE_STATUS_ACTIVE : PALETTE_FADE_STATUS_DONE; + + if (!gPaletteFade.objPaletteToggle) + { + if (gPaletteFade.delayCounter < gPaletteFade_delay) + { + gPaletteFade.delayCounter++; + return 2; + } + gPaletteFade.delayCounter = 0; + } + + paletteOffset = 0; + + if (!gPaletteFade.objPaletteToggle) + { + selectedPalettes = gPaletteFade_selectedPalettes; + } + else + { + selectedPalettes = gPaletteFade_selectedPalettes >> 16; + paletteOffset = 256; + } + + src = gPlttBufferUnfaded + paletteOffset; + dst = gPlttBufferFaded + paletteOffset; + + // First pply TOD blend to relevant subset of palettes + if (gPaletteFade.objPaletteToggle) // Sprite palettes, don't blend those with tags + { + u32 i; + u32 j = 1; + for (i = 0; i < 16; i++, j <<= 1) // Mask out palettes that should not be light blended + { + if ((selectedPalettes & j) && !IS_BLEND_IMMUNE_TAG(GetSpritePaletteTagByPaletteNum(i))) + timePalettes |= j; + } + } + else // tile palettes, don't blend [13, 15] + { + timePalettes = selectedPalettes & PALETTES_MAP; + } + TimeMixPalettes(timePalettes, src, dst, gPaletteFade.bld0, gPaletteFade.bld1, gPaletteFade.weight); + + // palettes that were not blended above must be copied through + if ((copyPalettes = ~timePalettes)) + { + u16 * src1 = src; + u16 * dst1 = dst; + while (copyPalettes) + { + if (copyPalettes & 1) + CpuFastCopy(src1, dst1, 32); + copyPalettes >>= 1; + src1 += 16; + dst1 += 16; + } + } + + // Then, blend from faded->faded with native BlendPalettes + BlendPalettesFine(selectedPalettes, dst, dst, gPaletteFade.y, gPaletteFade.blendColor); + + gPaletteFade.objPaletteToggle ^= 1; + + if (!gPaletteFade.objPaletteToggle) + { + if ((gPaletteFade.yDec && gPaletteFade.y == 0) || (!gPaletteFade.yDec && gPaletteFade.y == gPaletteFade.targetY)) + { + gPaletteFade_selectedPalettes = 0; + gPaletteFade.softwareFadeFinishing = 1; + } + else + { + s8 val; + + if (!gPaletteFade.yDec) + { + val = gPaletteFade.y; + val += gPaletteFade.deltaY; + if (val > gPaletteFade.targetY) + val = gPaletteFade.targetY; + gPaletteFade.y = val; + } + else + { + val = gPaletteFade.y; + val -= gPaletteFade.deltaY; + if (val < 0) + val = 0; + gPaletteFade.y = val; + } + } + } + + // gPaletteFade.active cannot change since the last time it was checked. So this + // is equivalent to `return PALETTE_FADE_STATUS_ACTIVE;` + return PALETTE_FADE_STATUS_ACTIVE; +} + static u32 UpdateNormalPaletteFade(void) { u16 paletteOffset; @@ -492,6 +659,46 @@ static bool32 IsSoftwarePaletteFadeFinishing(void) } } +// optimized based on lucktyphlosion's BlendPalettesFine +void BlendPalettesFine(u32 palettes, u16 *src, u16 *dst, u32 coeff, u32 color) +{ + s32 newR, newG, newB; + + if (!palettes) + return; + + coeff *= 2; + newR = (color << 27) >> 27; + newG = (color << 22) >> 27; + newB = (color << 17) >> 27; + + do + { + if (palettes & 1) + { + u16 *srcEnd = src + 16; + while (src != srcEnd) // Transparency is blended (for backdrop reasons) + { + u32 srcColor = *src; + s32 r = (srcColor << 27) >> 27; + s32 g = (srcColor << 22) >> 27; + s32 b = (srcColor << 16) >> 26; + + *dst++ = ((r + (((newR - r) * (s32)coeff) >> 5)) << 0) + | ((g + (((newG - g) * (s32)coeff) >> 5)) << 5) + | ((b + (((newB - (b & 31)) * (s32)coeff) >> 5)) << 10); + src++; + } + } + else + { + src += 16; + dst += 16; + } + palettes >>= 1; + } while (palettes); +} + void BlendPalettes(u32 selectedPalettes, u8 coeff, u32 color) { u16 paletteOffset; @@ -504,6 +711,243 @@ void BlendPalettes(u32 selectedPalettes, u8 coeff, u32 color) } } +#define DEFAULT_LIGHT_COLOR 0x3f9f + +// Like BlendPalette, but ignores blendColor if the transparency high bit is set +// Optimization help by lucktyphlosion +void TimeBlendPalette(u16 palOffset, u32 coeff, u32 blendColor) +{ + s32 newR, newG, newB, defR, defG, defB; + u16 * src = gPlttBufferUnfaded + palOffset; + u16 * dst = gPlttBufferFaded + palOffset; + u32 defaultBlendColor = DEFAULT_LIGHT_COLOR; + u16 *srcEnd = src + 16; + u32 altBlendColor = *dst++ = *src++; // color 0 is copied through unchanged + + coeff *= 2; + newR = (blendColor << 27) >> 27; + newG = (blendColor << 22) >> 27; + newB = (blendColor << 17) >> 27; + + if (altBlendColor >> 15) // Transparency high bit set; alt blend color + { + defR = (altBlendColor << 27) >> 27; + defG = (altBlendColor << 22) >> 27; + defB = (altBlendColor << 17) >> 27; + } + else + { + defR = (defaultBlendColor << 27) >> 27; + defG = (defaultBlendColor << 22) >> 27; + defB = (defaultBlendColor << 17) >> 27; + altBlendColor = 0; + } + while (src != srcEnd) + { + u32 srcColor = *src; + s32 r = (srcColor << 27) >> 27; + s32 g = (srcColor << 22) >> 27; + s32 b = (srcColor << 16) >> 26; + + if (srcColor >> 15) + { + *dst = ((r + (((defR - r) * (s32)coeff) >> 5)) << 0) + | ((g + (((defG - g) * (s32)coeff) >> 5)) << 5) + | ((b + (((defB - (b & 31)) * (s32)coeff) >> 5)) << 10); + } + else // Use provided blend color + { + *dst = ((r + (((newR - r) * (s32)coeff) >> 5)) << 0) + | ((g + (((newG - g) * (s32)coeff) >> 5)) << 5) + | ((b + (((newB - (b & 31)) * (s32)coeff) >> 5)) << 10); + } + src++; + dst++; + } +} + +// Blends a weighted average of two blend parameters +// Parameters can be either blended (as in BlendPalettes) or tinted (as in TintPaletteRGB_Copy) +void TimeMixPalettes(u32 palettes, u16 *src, u16 *dst, struct BlendSettings *blend0, struct BlendSettings *blend1, u16 weight0) +{ + s32 r0, g0, b0, r1, g1, b1, defR, defG, defB, altR, altG, altB; + u32 color0, coeff0, color1, coeff1; + bool8 tint0, tint1; + u32 defaultColor = DEFAULT_LIGHT_COLOR; + + if (!palettes) + return; + + color0 = blend0->blendColor; + tint0 = blend0->isTint; + coeff0 = tint0 ? 8*2 : blend0->coeff*2; + color1 = blend1->blendColor; + tint1 = blend1->isTint; + coeff1 = tint1 ? 8*2 : blend1->coeff*2; + + if (tint0) + { + r0 = (color0 << 24) >> 24; + g0 = (color0 << 16) >> 24; + b0 = (color0 << 8) >> 24; + } + else + { + r0 = (color0 << 27) >> 27; + g0 = (color0 << 22) >> 27; + b0 = (color0 << 17) >> 27; + } + if (tint1) + { + r1 = (color1 << 24) >> 24; + g1 = (color1 << 16) >> 24; + b1 = (color1 << 8) >> 24; + } + else + { + r1 = (color1 << 27) >> 27; + g1 = (color1 << 22) >> 27; + b1 = (color1 << 17) >> 27; + } + defR = (defaultColor << 27) >> 27; + defG = (defaultColor << 22) >> 27; + defB = (defaultColor << 17) >> 27; + + do + { + if (palettes & 1) + { + u16 *srcEnd = src + 16; + u32 altBlendColor = *dst++ = *src++; // color 0 is copied through + if (altBlendColor >> 15) // Transparency high bit set; alt blend color + { + altR = (altBlendColor << 27) >> 27; + altG = (altBlendColor << 22) >> 27; + altB = (altBlendColor << 17) >> 27; + } + else + { + altBlendColor = 0; + } + while (src != srcEnd) + { + u32 srcColor = *src; + s32 r = (srcColor << 27) >> 27; + s32 g = (srcColor << 22) >> 27; + s32 b = (srcColor << 17) >> 27; + s32 r2, g2, b2; + + if (srcColor >> 15) + { + if (altBlendColor) + { + // Use alternate blend color + r2 = r + (((altR - r) * (s32)coeff1) >> 5); + g2 = g + (((altG - g) * (s32)coeff1) >> 5); + b2 = b + (((altB - b) * (s32)coeff1) >> 5); + r = r + (((altR - r) * (s32)coeff0) >> 5); + g = g + (((altG - g) * (s32)coeff0) >> 5); + b = b + (((altB - b) * (s32)coeff0) >> 5); + } + else + { + // Use default blend color + r2 = r + (((defR - r) * (s32)coeff1) >> 5); + g2 = g + (((defG - g) * (s32)coeff1) >> 5); + b2 = b + (((defB - b) * (s32)coeff1) >> 5); + r = r + (((defR - r) * (s32)coeff0) >> 5); + g = g + (((defG - g) * (s32)coeff0) >> 5); + b = b + (((defB - b) * (s32)coeff0) >> 5); + } + } + else + { + // Use provided blend colors + if (!tint1) + { + // blend-based + r2 = (r + (((r1 - r) * (s32)coeff1) >> 5)); + g2 = (g + (((g1 - g) * (s32)coeff1) >> 5)); + b2 = (b + (((b1 - b) * (s32)coeff1) >> 5)); + } + else + { + // tint-based + r2 = (u16)((r1 * r)) >> 8; + g2 = (u16)((g1 * g)) >> 8; + b2 = (u16)((b1 * b)) >> 8; + if (r2 > 31) + r2 = 31; + if (g2 > 31) + g2 = 31; + if (b2 > 31) + b2 = 31; + } + if (!tint0) + { + // blend-based + r = (r + (((r0 - r) * (s32)coeff0) >> 5)); + g = (g + (((g0 - g) * (s32)coeff0) >> 5)); + b = (b + (((b0 - b) * (s32)coeff0) >> 5)); + } + else + { + // tint-based + r = (u16)((r0 * r)) >> 8; + g = (u16)((g0 * g)) >> 8; + b = (u16)((b0 * b)) >> 8; + if (r > 31) + r = 31; + if (g > 31) + g = 31; + if (b > 31) + b = 31; + } + } + r = r2 + (((r - r2) * (s32)weight0) >> 8); + g = g2 + (((g - g2) * (s32)weight0) >> 8); + b = b2 + (((b - b2) * (s32)weight0) >> 8); + *dst++ = RGB2(r, g, b); + // *dst++ = RGB2(r, g, b) | (srcColor >> 15) << 15; + src++; + } + } + else + { + src += 16; + dst += 16; + } + palettes >>= 1; + } while (palettes); +} + +// Apply weighted average to palettes, preserving high bits of dst throughout +void AvgPaletteWeighted(u16 *src0, u16 *src1, u16 *dst, u16 weight0) +{ + u16 *srcEnd = src0 + 16; + src0++; + src1++; + dst++; // leave dst transparency unchanged + while (src0 != srcEnd) + { + u32 src0Color = *src0++; + s32 r0 = (src0Color << 27) >> 27; + s32 g0 = (src0Color << 22) >> 27; + s32 b0 = (src0Color << 17) >> 27; + u32 src1Color = *src1++; + s32 r1 = (src1Color << 27) >> 27; + s32 g1 = (src1Color << 22) >> 27; + s32 b1 = (src1Color << 17) >> 27; + + // Average and bitwise-OR + r0 = r1 + (((r0 - r1) * weight0) >> 8); + g0 = g1 + (((g0 - g1) * weight0) >> 8); + b0 = b1 + (((b0 - b1) * weight0) >> 8); + *dst = (*dst & RGB_ALPHA) | RGB2(r0, g0, b0); // preserve high bit of dst + dst++; + } +} + void BlendPalettesUnfaded(u32 selectedPalettes, u8 coeff, u32 color) { // This copy is done via DMA in both RUBY and EMERALD diff --git a/src/palette_util.c b/src/palette_util.c index 060489f08..e40c2d04b 100644 --- a/src/palette_util.c +++ b/src/palette_util.c @@ -1,5 +1,6 @@ #include "global.h" #include "gflib.h" +#include "overworld.h" #include "palette_util.h" #include "util.h" @@ -374,6 +375,8 @@ void UpdatePulseBlend(struct PulseBlend *pulseBlend) if (--pulseBlendPalette->delayCounter == 0xFF) { pulseBlendPalette->delayCounter = pulseBlendPalette->pulseBlendSettings.delay; + CpuFastCopy(gPlttBufferUnfaded + pulseBlendPalette->pulseBlendSettings.paletteOffset, gPlttBufferFaded + pulseBlendPalette->pulseBlendSettings.paletteOffset, PLTT_SIZE_4BPP); + UpdatePalettesWithTime(1 << (pulseBlendPalette->pulseBlendSettings.paletteOffset >> 4)); BlendPalette(pulseBlendPalette->pulseBlendSettings.paletteOffset, pulseBlendPalette->pulseBlendSettings.numColors, pulseBlendPalette->blendCoeff, pulseBlendPalette->pulseBlendSettings.blendColor); switch (pulseBlendPalette->pulseBlendSettings.fadeType) { diff --git a/src/quest_log.c b/src/quest_log.c index 0757f080c..fe941d9e5 100644 --- a/src/quest_log.c +++ b/src/quest_log.c @@ -1246,6 +1246,15 @@ static bool8 RestoreScreenAfterPlayback(u8 taskId) CopyPaletteInvertedTint(&gPlttBufferUnfaded[BG_PLTT_ID(0) + 1], &gPlttBufferFaded[BG_PLTT_ID(0) + 1], 0xDF, 15 - tTimer); CopyPaletteInvertedTint(&gPlttBufferUnfaded[OBJ_PLTT_ID(0)], &gPlttBufferFaded[OBJ_PLTT_ID(0)], 0x100, 15 - tTimer); + + gTimeUpdateCounter = 0; + UpdateTimeOfDay(); + + if (MapHasNaturalLight(gMapHeader.mapType)) + { + UpdateAltBgPalettes(PALETTES_BG); + UpdatePalettesWithTime(PALETTES_ALL); + } FillWindowPixelRect(sWindowIds[WIN_TOP_BAR], 0x00, 0, sWindowTemplates[WIN_TOP_BAR].height * 8 - 1 - tTimer, diff --git a/src/scrcmd.c b/src/scrcmd.c index 623e80843..59528e416 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -3,6 +3,7 @@ #include "clock.h" #include "rtc.h" #include "script.h" +#include "decompress.h" #include "mystery_event_script.h" #include "event_data.h" #include "event_scripts.h" @@ -646,6 +647,32 @@ bool8 ScrCmd_fadescreenspeed(struct ScriptContext * ctx) return TRUE; } +bool8 ScrCmd_fadescreenswapbuffers(struct ScriptContext *ctx) +{ + u8 mode = ScriptReadByte(ctx); + u8 nowait = ScriptReadByte(ctx); + + switch (mode) + { + case FADE_TO_BLACK: + case FADE_TO_WHITE: + default: + CpuCopy32(gPlttBufferUnfaded, gDecompressionBuffer, PLTT_SIZE); + FadeScreen(mode, 0); + break; + case FADE_FROM_BLACK: + case FADE_FROM_WHITE: + CpuCopy32(gDecompressionBuffer, gPlttBufferUnfaded, PLTT_SIZE); + FadeScreen(mode, 0); + break; + } + + if (nowait) + return FALSE; + SetupNativeScript(ctx, IsPaletteNotActive); + return TRUE; +} + static bool8 RunPauseTimer(void) { if (--sPauseCounter == 0)