vxo
ROM Hacker
- 65
- Posts
- 7
- Years
- he/him
- California
- Seen Nov 12, 2024
Replacing Overworlds and adding Unique Palettes
Part I: Adding Dynamic Overworld Palette System
Source: https://github.com/pret/pokeemerald/wiki/Dynamic-overworld-palette-systemNotice: The current implementation breaks the reflection palette system, though for ease of adding new overworlds that should be adapted so it is dynamic as well, rather than requiring explicit reflection palettes.
Notice: Depending on your version of Pokeemerald, the function sub_808E894() has been decompiled, so it could be called LoadEventObjectPalette()
Notice: Where it states + or -, add or delete the entire line
Spoiler:
src/overworld.c
sub_8086988()
Code:
static void sub_8086988(u32 a1)
{
ResetTasks();
ResetSpriteData(); ResetPaletteFade();
ScanlineEffect_Clear();
ResetAllPicSprites();
ResetCameraUpdateInfo();
InstallCameraPanAheadCallback();
+ FreeAllSpritePalettes();
- if (!a1)
- InitEventObjectPalettes(0);
- else
- InitEventObjectPalettes(1);
FieldEffectActiveListClear();
sub_80AAFA4();
sub_80AEE84();
if (!a1)
SetUpFieldTasks();
mapheader_run_script_with_tag_x5();
sub_81BE6B8();
}
src/event_object_movement.c
TrySetupEventObjectSprite()
Code:
static u8 TrySetupEventObjectSprite(struct EventObjectTemplate *eventObjectTemplate, struct SpriteTemplate *spriteTemplate, u8 mapNum, u8 mapGroup, s16 cameraX, s16 cameraY)
{
struct EventObject *eventObject;
const struct EventObjectGraphicsInfo *graphicsInfo;
struct Sprite *sprite;
u8 eventObjectId;
- u8 paletteSlot;
u8 spriteId;
eventObjectId = InitEventObjectStateFromTemplate(eventObjectTemplate, mapNum, mapGroup);
if (eventObjectId == NUM_EVENT_OBJECTS)
return NUM_EVENT_OBJECTS;
eventObject = &gEventObjects[eventObjectId];
graphicsInfo = GetEventObjectGraphicsInfo(eventObject->graphicsId);
+ if (spriteTemplate->paletteTag != 0xffff)
+ {
+ sub_808E894(spriteTemplate->paletteTag);
+ }
- paletteSlot = graphicsInfo->paletteSlot;
- if (paletteSlot == 0)
- {
- LoadPlayerObjectReflectionPalette(graphicsInfo->paletteTag1, 0);
- }
- else if (paletteSlot == 10)
- {
- LoadSpecialObjectReflectionPalette(graphicsInfo->paletteTag1, 10);
- }
- else if (paletteSlot >= 16)
- {
- paletteSlot -= 16;
- sub_808EAB0(graphicsInfo->paletteTag1, paletteSlot);
- }
if (eventObject->movementType == 0x4c)
{
eventObject->invisible = TRUE;
}
- *(u16 *)&spriteTemplate->paletteTag = 0xFFFF;
spriteId = CreateSprite(spriteTemplate, 0, 0, 0);
if (spriteId == MAX_SPRITES)
{
gEventObjects[eventObjectId].active = FALSE;
return NUM_EVENT_OBJECTS;
}
sprite = &gSprites[spriteId];
sub_8092FF0(eventObject->currentCoords.x + cameraX, eventObject->currentCoords.y + cameraY, &sprite->pos1.x, &sprite->pos1.y);
sprite->centerToCornerVecX = -(graphicsInfo->width >> 1);
sprite->centerToCornerVecY = -(graphicsInfo->height >> 1);
sprite->pos1.x += 8;
sprite->pos1.y += 16 + sprite->centerToCornerVecY;
- sprite->oam.paletteNum = paletteSlot;
sprite->coordOffsetEnabled = TRUE;
sprite->data[0] = eventObjectId;
eventObject->spriteId = spriteId;
eventObject->inanimate = graphicsInfo->inanimate;
if (!eventObject->inanimate)
{
StartSpriteAnim(sprite, GetFaceDirectionAnimNum(eventObject->facingDirection));
}
SetObjectSubpriorityByZCoord(eventObject->previousElevation, sprite, 1);
UpdateEventObjectVisibility(eventObject, sprite);
return eventObjectId;
}
RemoveEventObjectInternal()
Code:
static void RemoveEventObjectInternal(struct EventObject *eventObject)
{
+ u8 paletteNum;
struct SpriteFrameImage image;
image.size = GetEventObjectGraphicsInfo(eventObject->graphicsId)->size;
gSprites[eventObject->spriteId].images = ℑ
+ paletteNum = gSprites[eventObject->spriteId].oam.paletteNum;
DestroySprite(&gSprites[eventObject->spriteId]);
+ FieldEffectFreePaletteIfUnused(paletteNum);
}
sub_808E1B8()
Code:
static void sub_808E1B8(u8 eventObjectId, s16 x, s16 y)
{
u8 spriteId;
- u8 paletteSlot;
struct EventObject *eventObject;
const struct SubspriteTable *subspriteTables;
const struct EventObjectGraphicsInfo *graphicsInfo;
struct SpriteFrameImage spriteFrameImage;
struct SpriteTemplate spriteTemplate;
struct Sprite *sprite;
#define i spriteId
for (i = 0; i < ARRAY_COUNT(gLinkPlayerEventObjects); i++)
{
if (gLinkPlayerEventObjects[i].active && eventObjectId == gLinkPlayerEventObjects[i].eventObjId)
{
return;
}
}
#undef i
eventObject = &gEventObjects[eventObjectId];
subspriteTables = NULL;
graphicsInfo = GetEventObjectGraphicsInfo(eventObject->graphicsId);
spriteFrameImage.size = graphicsInfo->size;
MakeObjectTemplateFromEventObjectGraphicsInfoWithCallbackIndex(eventObject->graphicsId, eventObject->movementType, &spriteTemplate, &subspriteTables);
spriteTemplate.images = &spriteFrameImage;
+ if (spriteTemplate.paletteTag != 0xffff)
+ {
+ sub_808E894(spriteTemplate.paletteTag);
+ }
- *(u16 *)&spriteTemplate.paletteTag = 0xffff;
- paletteSlot = graphicsInfo->paletteSlot;
- if (paletteSlot == 0)
- {
- LoadPlayerObjectReflectionPalette(graphicsInfo->paletteTag1, graphicsInfo->paletteSlot);
- }
- else if (paletteSlot == 10)
- {
- LoadSpecialObjectReflectionPalette(graphicsInfo->paletteTag1, graphicsInfo->paletteSlot);
- }
- else if (paletteSlot >= 16)
- {
- paletteSlot -= 16;
- sub_808EAB0(graphicsInfo->paletteTag1, paletteSlot);
- }
- *(u16 *)&spriteTemplate.paletteTag = 0xffff;
spriteId = CreateSprite(&spriteTemplate, 0, 0, 0);
if (spriteId != MAX_SPRITES)
{
sprite = &gSprites[spriteId];
sub_8092FF0(x + eventObject->currentCoords.x, y + eventObject->currentCoords.y, &sprite->pos1.x, &sprite->pos1.y);
sprite->centerToCornerVecX = -(graphicsInfo->width >> 1);
sprite->centerToCornerVecY = -(graphicsInfo->height >> 1);
sprite->pos1.x += 8;
sprite->pos1.y += 16 + sprite->centerToCornerVecY;
sprite->images = graphicsInfo->images;
if (eventObject->movementType == 0x0b)
{
SetPlayerAvatarEventObjectIdAndObjectId(eventObjectId, spriteId);
eventObject->warpArrowSpriteId = sub_8154228();
}
if (subspriteTables != NULL)
{
SetSubspriteTables(sprite, subspriteTables);
}
- sprite->oam.paletteNum = paletteSlot;
sprite->coordOffsetEnabled = TRUE;
sprite->data[0] = eventObjectId;
eventObject->spriteId = spriteId;
if (!eventObject->inanimate && eventObject->movementType != 0x0b)
{
StartSpriteAnim(sprite, GetFaceDirectionAnimNum(eventObject->facingDirection));
}
sub_808E38C(eventObject);
SetObjectSubpriorityByZCoord(eventObject->previousElevation, sprite, 1);
}
}
Part II: Replacing Overworlds and Adding Unique Palettes
Source: https://github.com/pret/pokeemerald/wiki/Adding-new-overworlds
Spoiler:
1. Formatting the Image
- Open InfraView
- Decrease Color Depth to 16
- Edit Palette so background color is the first color in the palette list
- Export palette and re-open image
- Apply Exported Palette to Re-opened Image
- Save
2. Replacing a Sprite
- Goto src/data/field_event_obj/object_event_graphics.h
- Find sprite name you want to replace Eg: const u32 gEventObjectPic_Boy1[] = INCBIN_U32("graphics/event_objects/pics/people/boy_1.4bpp");
- Modify directory path if necessary (I changed mine to "graphics/event_objects/pics/newpeople/boy_1.4bpp")
- Goto bottom of the same file
- Add a new line and insert:
Code:const u16 gEventObjectPalette_<NAME>[] = INCBIN_U16("<insert path to palette here>.gbapal");
- Then goto spritesheet_rules.mk
- Find the name of the sprite you are modifying and make sure the path matches your new path (if needed)
- Make sure -mwidth and -mheight are set properly (1 tile is 8x8 pixel, so a default 16x32 would be 2 and 4, respectively)
3. Adding the Palette
- Goto src/event_object_movement.c
- Goto Line 430ish and go to the second to last #define and add:
Code:#define EVENT_OBJ_PAL_<PALETTE NAME> 0x<HEX NUMBER>
- Then goto Line 460ish and go to the second to last entry and add:
Code:{gEventObjectPalette_<PALETTE NAME>, EVENT_OBJ_PAL_<PALETTE NAME>},
4. Applying the newly modified Palette
- Goto src/data/field_event_obj/object_event_graphics_info.h
- Find the sprite you are editing, and modify the second entry in the list to match your newly created palette
- Save
Part III: Troubleshooting
Spoiler:
A lot of times, following these steps may not work correctly. Your sprite may appear glitched and/or invisible. Here are some recommendations and methods that I've done to fix possible issues.Method 1. Re-indexing the Image
It sometimes helps to re-index and re-create the palette files, but make sure you delete the .gbapal and .4bpp files before running the make command.Method 2. Double Checking your modified entries
This seems self explanatory, but sometimes it might be worth to check to see if you've accidentally made a typo of some sort. These mostly apply to src/data/field_event_obj/event_object_graphics.h and spritesheet_rules.mk files, since they actually build the sprite.Also the src/data/field_event_obj/event_object_graphics_info.h file also has some building properties. Arguments 4, 5 and 6 are associated with the sprites dimensions and byte sizes.
Argument 4 (.size) refers to the size in bytes of 1 frame of your overworld. It can be calculated by multiplying the width by height by bit depth (in this case 32x32x4) to get the size in bits, then divide by 8 to convert it into bytes.
Argument 5 (.width) is the width in pixels of one frame of your sprite sheet.
Argument 6 (.height) is the height in pixels of one frame of your sprite sheet.
Last edited: