Added follower transform effect and Castform forms. Credit: larryturbo

Castform forms from https://www.deviantart.com/larryturbo/art/351-Castform-Froms-830190053
This commit is contained in:
Ariel A 2022-05-17 00:54:08 -04:00
parent ec756d3d12
commit 01f73f54e1
13 changed files with 202 additions and 22 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 B

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -6,7 +6,7 @@ JASC-PAL
205 90 74
238 123 74
255 156 65
0 0 0
255 197 136
255 246 106
230 213 106
255 156 98

View file

@ -26,6 +26,13 @@ enum ReflectionTypes
NUM_REFLECTION_TYPES
};
enum FollowerTransformTypes
{
TRANSFORM_TYPE_NONE,
TRANSFORM_TYPE_PERMANENT,
TRANSFORM_TYPE_RANDOM_WILD,
};
#define FIGURE_8_LENGTH 72
#define GROUND_EFFECT_FLAG_TALL_GRASS_ON_SPAWN (1 << 0)

View file

@ -1820,6 +1820,15 @@ $(OBJEVENTGFXDIR)/pokemon/milotic.4bpp: %.4bpp: %.png
$(OBJEVENTGFXDIR)/pokemon/castform.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4
$(OBJEVENTGFXDIR)/pokemon/castform_sunny.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4
$(OBJEVENTGFXDIR)/pokemon/castform_rainy.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4
$(OBJEVENTGFXDIR)/pokemon/castform_snowy.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4
$(OBJEVENTGFXDIR)/pokemon/kecleon.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4

View file

@ -721,6 +721,12 @@ const u32 gObjectEventPic_Armaldo[] = INCBIN_U32("graphics/object_events/pics/po
const u32 gObjectEventPic_Feebas[] = INCBIN_U32("graphics/object_events/pics/pokemon/feebas.4bpp");
const u32 gObjectEventPic_Milotic[] = INCBIN_U32("graphics/object_events/pics/pokemon/milotic.4bpp");
const u32 gObjectEventPic_Castform[] = INCBIN_U32("graphics/object_events/pics/pokemon/castform.4bpp");
const u32 gObjectEventPic_CastformSunny[] = INCBIN_U32("graphics/object_events/pics/pokemon/castform_sunny.4bpp");
const u16 gObjectEventPal_CastformSunny[] = INCBIN_U16("graphics/pokemon/castform/normal_sunny_form.gbapal");
const u32 gObjectEventPic_CastformRainy[] = INCBIN_U32("graphics/object_events/pics/pokemon/castform_rainy.4bpp");
const u16 gObjectEventPal_CastformRainy[] = INCBIN_U16("graphics/pokemon/castform/normal_rainy_form.gbapal");
const u32 gObjectEventPic_CastformSnowy[] = INCBIN_U32("graphics/object_events/pics/pokemon/castform_snowy.4bpp");
const u16 gObjectEventPal_CastformSnowy[] = INCBIN_U16("graphics/pokemon/castform/normal_snowy_form.gbapal");
const u32 gObjectEventPic_Kecleon[] = INCBIN_U32("graphics/object_events/pics/pokemon/kecleon.4bpp");
const u32 gObjectEventPic_Shuppet[] = INCBIN_U32("graphics/object_events/pics/pokemon/shuppet.4bpp");
const u32 gObjectEventPic_Banette[] = INCBIN_U32("graphics/object_events/pics/pokemon/banette.4bpp");

View file

@ -414,3 +414,11 @@ const struct ObjectEventGraphicsInfo gPokemonObjectGraphics[] = {
[SPECIES_JIRACHI] = {0xFFFF, OBJ_EVENT_PAL_TAG_DYNAMIC, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_NONE, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_Jirachi, gDummySpriteAffineAnimTable},
[SPECIES_DEOXYS] = {0xFFFF, OBJ_EVENT_PAL_TAG_DYNAMIC, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_FOOT, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_Deoxys, gDummySpriteAffineAnimTable},
};
// Normal, sunny, rainy, snowy
const struct ObjectEventGraphicsInfo gCastformObjectGraphics[] = {
{0xFFFF, OBJ_EVENT_PAL_TAG_DYNAMIC, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_NONE, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_Castform, gDummySpriteAffineAnimTable},
{0xFFFF, OBJ_EVENT_PAL_TAG_CASTFORM_SUNNY, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_NONE, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_CastformSunny, gDummySpriteAffineAnimTable},
{0xFFFF, OBJ_EVENT_PAL_TAG_CASTFORM_RAINY, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_NONE, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_CastformRainy, gDummySpriteAffineAnimTable},
{0xFFFF, OBJ_EVENT_PAL_TAG_CASTFORM_SNOWY, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_NONE, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_CastformSnowy, gDummySpriteAffineAnimTable},
};

View file

@ -5014,6 +5014,30 @@ static const struct SpriteFrameImage sPicTable_Castform[] = {
overworld_frame(gObjectEventPic_Castform, 4, 4, 4),
overworld_frame(gObjectEventPic_Castform, 4, 4, 5),
};
static const struct SpriteFrameImage sPicTable_CastformSunny[] = {
overworld_frame(gObjectEventPic_CastformSunny, 4, 4, 0),
overworld_frame(gObjectEventPic_CastformSunny, 4, 4, 1),
overworld_frame(gObjectEventPic_CastformSunny, 4, 4, 2),
overworld_frame(gObjectEventPic_CastformSunny, 4, 4, 3),
overworld_frame(gObjectEventPic_CastformSunny, 4, 4, 4),
overworld_frame(gObjectEventPic_CastformSunny, 4, 4, 5),
};
static const struct SpriteFrameImage sPicTable_CastformRainy[] = {
overworld_frame(gObjectEventPic_CastformRainy, 4, 4, 0),
overworld_frame(gObjectEventPic_CastformRainy, 4, 4, 1),
overworld_frame(gObjectEventPic_CastformRainy, 4, 4, 2),
overworld_frame(gObjectEventPic_CastformRainy, 4, 4, 3),
overworld_frame(gObjectEventPic_CastformRainy, 4, 4, 4),
overworld_frame(gObjectEventPic_CastformRainy, 4, 4, 5),
};
static const struct SpriteFrameImage sPicTable_CastformSnowy[] = {
overworld_frame(gObjectEventPic_CastformSnowy, 4, 4, 0),
overworld_frame(gObjectEventPic_CastformSnowy, 4, 4, 1),
overworld_frame(gObjectEventPic_CastformSnowy, 4, 4, 2),
overworld_frame(gObjectEventPic_CastformSnowy, 4, 4, 3),
overworld_frame(gObjectEventPic_CastformSnowy, 4, 4, 4),
overworld_frame(gObjectEventPic_CastformSnowy, 4, 4, 5),
};
static const struct SpriteFrameImage sPicTable_Kecleon[] = {
overworld_frame(gObjectEventPic_Kecleon, 4, 4, 0),
overworld_frame(gObjectEventPic_Kecleon, 4, 4, 1),

View file

@ -17,6 +17,7 @@
#include "field_weather.h"
#include "fieldmap.h"
#include "follower_helper.h"
#include "gpu_regs.h"
#include "mauville_old_man.h"
#include "metatile_behavior.h"
#include "overworld.h"
@ -31,6 +32,7 @@
#include "trainer_see.h"
#include "trainer_hill.h"
#include "util.h"
#include "wild_encounter.h"
#include "constants/event_object_movement.h"
#include "constants/abilities.h"
#include "constants/battle.h"
@ -157,6 +159,7 @@ static struct ObjectEventTemplate *GetObjectEventTemplateByLocalIdAndMap(u8, u8,
static void RemoveObjectEventIfOutsideView(struct ObjectEvent *);
static void SpawnObjectEventOnReturnToField(u8, s16, s16);
static void SetPlayerAvatarObjectEventIdAndObjectId(u8, u8);
static u8 UpdateSpritePalette(const struct SpritePalette * spritePalette, struct Sprite * sprite);
static void ResetObjectEventFldEffData(struct ObjectEvent *);
static u8 LoadSpritePaletteIfTagExists(const struct SpritePalette *);
static u8 FindObjectEventPaletteIndexByTag(u16);
@ -462,6 +465,9 @@ const u8 gInitialMovementTypeFacingDirections[] = {
#define OBJ_EVENT_PAL_TAG_RS_BRENDAN 0x1122
#define OBJ_EVENT_PAL_TAG_RS_MAY 0x1123
#define OBJ_EVENT_PAL_TAG_DYNAMIC 0x1124
#define OBJ_EVENT_PAL_TAG_CASTFORM_SUNNY 0x1125
#define OBJ_EVENT_PAL_TAG_CASTFORM_RAINY 0x1126
#define OBJ_EVENT_PAL_TAG_CASTFORM_SNOWY 0x1127
#define OBJ_EVENT_PAL_TAG_EMOTES 0x8002
#define OBJ_EVENT_PAL_TAG_NONE 0x11FF
@ -510,7 +516,9 @@ static const struct SpritePalette sObjectEventSpritePalettes[] = {
{gObjectEventPal_Lugia, OBJ_EVENT_PAL_TAG_LUGIA},
{gObjectEventPal_RubySapphireBrendan, OBJ_EVENT_PAL_TAG_RS_BRENDAN},
{gObjectEventPal_RubySapphireMay, OBJ_EVENT_PAL_TAG_RS_MAY},
{gObjectEventPal_Npc1, OBJ_EVENT_PAL_TAG_DYNAMIC},
{gObjectEventPal_CastformSunny, OBJ_EVENT_PAL_TAG_CASTFORM_SUNNY},
{gObjectEventPal_CastformRainy, OBJ_EVENT_PAL_TAG_CASTFORM_RAINY},
{gObjectEventPal_CastformSnowy, OBJ_EVENT_PAL_TAG_CASTFORM_SNOWY},
{gObjectEventPaletteEmotes, OBJ_EVENT_PAL_TAG_EMOTES},
{NULL, OBJ_EVENT_PAL_TAG_NONE},
};
@ -1701,6 +1709,9 @@ static const struct ObjectEventGraphicsInfo * SpeciesToGraphicsInfo(u16 species,
form %= NUM_UNOWN_FORMS;
graphicsInfo = &gPokemonObjectGraphics[form ? SPECIES_UNOWN_B + form - 1 : species];
break;
case SPECIES_CASTFORM: // Sunny, rainy, snowy forms stored separately
graphicsInfo = &gCastformObjectGraphics[form % NUM_CASTFORM_FORMS];
break;
default:
graphicsInfo = &gPokemonObjectGraphics[species];
break;
@ -1739,7 +1750,56 @@ static void FollowerSetGraphics(struct ObjectEvent *objEvent, u16 species, u8 fo
FieldEffectFreePaletteIfUnused(sprite->oam.paletteNum);
sprite->inUse = TRUE;
sprite->oam.paletteNum = LoadDynamicFollowerPalette(species, form, shiny);
}
} else if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog
UpdateSpritePaletteWithWeather(gSprites[objEvent->spriteId].oam.paletteNum);
}
// Like FollowerSetGraphics, but does not reposition sprite; intended to be used for mid-movement form changes, etc.
// TODO: Reposition sprite if size changes
static void RefreshFollowerGraphics(struct ObjectEvent *objEvent) {
u16 species = objEvent->extra.mon.species;
u8 form = objEvent->extra.mon.form;
u8 shiny = objEvent->extra.mon.shiny;
const struct ObjectEventGraphicsInfo *graphicsInfo = SpeciesToGraphicsInfo(species, form);
struct Sprite *sprite = &gSprites[objEvent->spriteId];
u8 i = FindObjectEventPaletteIndexByTag(graphicsInfo->paletteTag);
sprite->oam.shape = graphicsInfo->oam->shape;
sprite->oam.size = graphicsInfo->oam->size;
sprite->images = graphicsInfo->images;
sprite->anims = graphicsInfo->anims;
sprite->subspriteTables = graphicsInfo->subspriteTables;
objEvent->inanimate = graphicsInfo->inanimate;
sprite->centerToCornerVecX = -(graphicsInfo->width >> 1);
sprite->centerToCornerVecY = -(graphicsInfo->height >> 1);
if (graphicsInfo->paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) {
sprite->inUse = FALSE;
FieldEffectFreePaletteIfUnused(sprite->oam.paletteNum);
sprite->inUse = TRUE;
sprite->oam.paletteNum = LoadDynamicFollowerPalette(species, form, shiny);
} else if (i != 0xFF) {
UpdateSpritePalette(&sObjectEventSpritePalettes[i], sprite);
if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog
UpdateSpritePaletteWithWeather(sprite->oam.paletteNum);
}
}
// Like CastformDataTypeChange, but for overworld weather
static u8 GetOverworldCastformForm(void) {
switch (GetCurrentWeather())
{
case WEATHER_SUNNY_CLOUDS:
case WEATHER_DROUGHT:
return CASTFORM_FIRE;
case WEATHER_RAIN:
case WEATHER_RAIN_THUNDERSTORM:
case WEATHER_DOWNPOUR:
return CASTFORM_WATER;
case WEATHER_SNOW:
return CASTFORM_ICE;
}
return CASTFORM_NORMAL;
}
// Retrieve graphic information about the following pokemon, if any
@ -1753,7 +1813,16 @@ static bool8 GetFollowerInfo(u16 *species, u8 *form, u8 *shiny) {
}
*species = GetMonData(mon, MON_DATA_SPECIES);
*shiny = IsMonShiny(mon);
*form = *species == SPECIES_UNOWN ? GET_UNOWN_LETTER(mon->box.personality) : 0;
*form = 0; // default
switch (*species)
{
case SPECIES_UNOWN:
*form = GET_UNOWN_LETTER(mon->box.personality);
break;
case SPECIES_CASTFORM: // form is based on overworld weather
*form = GetOverworldCastformForm();
break;
}
return TRUE;
}
@ -1764,7 +1833,7 @@ void UpdateFollowingPokemon(void) { // Update following pokemon if any
bool8 shiny;
u8 form;
// Avoid spawning large (64x64) follower pokemon inside buildings
if (GetFollowerInfo(&species, &form, &shiny) && !(gMapHeader.mapType == MAP_TYPE_INDOOR && SpeciesToGraphicsInfo(species, 0)->width == 64)) {
if (GetFollowerInfo(&species, &form, &shiny) && !(gMapHeader.mapType == MAP_TYPE_INDOOR && SpeciesToGraphicsInfo(species, 0)->height == 64)) {
if (objEvent == NULL) { // Spawn follower
struct ObjectEventTemplate template = {
.localId = OBJ_EVENT_ID_FOLLOWER,
@ -2242,13 +2311,7 @@ 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);
} else {
sprite->oam.paletteNum = LoadSpritePalette(spritePalette);
}
return sprite->oam.paletteNum;
return sprite->oam.paletteNum = LoadSpritePalette(spritePalette);
}
// Find and update based on template's paletteTag
@ -4803,6 +4866,73 @@ bool8 CopyablePlayerMovement_Jump2(struct ObjectEvent *objectEvent, struct Sprit
return TRUE;
}
static bool8 EndFollowerTransformEffect(struct ObjectEvent *objectEvent, struct Sprite *sprite) {
if (!sprite)
return FALSE;
SetGpuReg(REG_OFFSET_MOSAIC, 0);
if (!sprite->data[7])
return FALSE;
sprite->oam.mosaic = FALSE;
sprite->data[7] = 0;
return FALSE;
}
static bool8 TryStartFollowerTransformEffect(struct ObjectEvent *objectEvent, struct Sprite *sprite) {
u32 multi;
if (objectEvent->extra.mon.species == SPECIES_CASTFORM && objectEvent->extra.mon.form != (multi = GetOverworldCastformForm())) {
sprite->data[7] = TRANSFORM_TYPE_PERMANENT << 8;
objectEvent->extra.mon.form = multi;
return TRUE;
} else if ((gRngValue >> 16) < 18 && GetLocalWildMon(FALSE)
&& (objectEvent->extra.mon.species == SPECIES_MEW || objectEvent->extra.mon.species == SPECIES_DITTO)) {
sprite->data[7] = TRANSFORM_TYPE_RANDOM_WILD << 8;
PlaySE(SE_M_MINIMIZE);
return TRUE;
}
return FALSE;
}
static bool8 UpdateFollowerTransformEffect(struct ObjectEvent *objectEvent, struct Sprite *sprite) {
u8 type = sprite->data[7] >> 8;
u8 frames = sprite->data[7] & 0xFF;
u8 stretch;
u32 multi;
if (!type)
return TryStartFollowerTransformEffect(objectEvent, sprite);
sprite->oam.mosaic = TRUE;
if (frames < 8)
stretch = frames >> 1;
else if (frames < 16)
stretch = (16 - frames) >> 1;
else {
return EndFollowerTransformEffect(objectEvent, sprite);
}
if (frames == 8) {
switch (type)
{
case TRANSFORM_TYPE_PERMANENT:
RefreshFollowerGraphics(objectEvent);
break;
case TRANSFORM_TYPE_RANDOM_WILD:
multi = objectEvent->extra.asU16;
objectEvent->extra.mon.species = GetLocalWildMon(FALSE);
if (!objectEvent->extra.mon.species) {
objectEvent->extra.asU16 = multi;
break;
}
objectEvent->extra.mon.form = 0;
RefreshFollowerGraphics(objectEvent);
objectEvent->extra.asU16 = multi;
break;
}
}
SetGpuReg(REG_OFFSET_MOSAIC, (stretch << 12) | (stretch << 8));
frames++;
sprite->data[7] = (sprite->data[7] & 0xFF00) | frames;
return TRUE;
}
movement_type_def(MovementType_FollowPlayer, gMovementTypeFuncs_FollowPlayer)
bool8 MovementType_FollowPlayer_Shadow(struct ObjectEvent *objectEvent, struct Sprite *sprite)
@ -4859,6 +4989,8 @@ bool8 MovementType_FollowPlayer_Moving(struct ObjectEvent *objectEvent, struct S
if (sprite->data[1]) { // restore nonzero state
sprite->data[1] = 1;
}
} else if (objectEvent->movementActionId != MOVEMENT_ACTION_EXIT_POKEBALL) {
UpdateFollowerTransformEffect(objectEvent, sprite);
}
return FALSE;
}
@ -4874,6 +5006,7 @@ bool8 FollowablePlayerMovement_Idle(struct ObjectEvent *objectEvent, struct Spri
} else if (ObjectEventExecSingleMovementAction(objectEvent, sprite)) { // finish movement action
objectEvent->singleMovementActive = 0;
}
UpdateFollowerTransformEffect(objectEvent, sprite);
return FALSE;
}
@ -6542,6 +6675,7 @@ bool8 MovementAction_EnterPokeball_Step0(struct ObjectEvent *objectEvent, struct
u8 direction = objectEvent->facingDirection;
InitMoveInPlace(objectEvent, sprite, direction, GetMoveDirectionFasterAnimNum(direction), 16);
sprite->data[6] = direction == DIR_EAST ? 3 : 2; // affine animation number
EndFollowerTransformEffect(objectEvent, sprite);
return MovementAction_EnterPokeball_Step1(objectEvent, sprite);
}

View file

@ -2998,12 +2998,8 @@ static void SurfFieldEffect_Init(struct Task *task)
FreezeObjectEvents();
// Put follower into pokeball before using Surf
if (followerObject && !followerObject->invisible) {
// TODO: ClearObjectEventMovement (
followerObject->singleMovementActive = 0;
ObjectEventClearHeldMovement(followerObject);
gSprites[followerObject->spriteId].data[1] = 0;
ClearObjectEventMovement(followerObject, &gSprites[followerObject->spriteId]);
gSprites[followerObject->spriteId].animCmdIndex = 0; // Needed because of weird animCmdIndex stuff
// )
ObjectEventSetHeldMovement(followerObject, MOVEMENT_ACTION_ENTER_POKEBALL);
}
gPlayerAvatar.preventStep = TRUE;

View file

@ -688,12 +688,8 @@ static void Task_DoDoorWarp(u8 taskId)
PlayerGetDestCoords(x, y);
PlaySE(GetDoorSoundEffect(*x, *y - 1));
if (followerObject) { // Put follower into pokeball
// TODO: ClearObjectEventMovement (
followerObject->singleMovementActive = 0;
ObjectEventClearHeldMovement(followerObject);
gSprites[followerObject->spriteId].data[1] = 0;
ClearObjectEventMovement(followerObject, &gSprites[followerObject->spriteId]);
gSprites[followerObject->spriteId].animCmdIndex = 0; // Needed because of weird animCmdIndex stuff
// )
ObjectEventSetHeldMovement(followerObject, MOVEMENT_ACTION_ENTER_POKEBALL);
}
task->data[1] = FieldAnimateDoorOpen(*x, *y - 1);