vxo

ROM Hacker

Age 19
Male
California
Seen 1 Day Ago
Posted 1 Day Ago
60 posts
2.7 Years

Replacing Overworlds and adding Unique Palettes


Part I: Adding Dynamic Overworld Palette System


Source: https://github.com/pret/pokeemerald/wiki/Dynamic-overworld-palette-system

Notice: 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()
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()
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()
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()
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


  1. Open InfraView
  2. Decrease Color Depth to 16
  3. Edit Palette so background color is the first color in the palette list
  4. Export palette and re-open image
  5. Apply Exported Palette to Re-opened Image
  6. Save

2. Replacing a Sprite


  1. Goto src/data/field_event_obj/event_object_graphics.h
  2. Find sprite name you want to replace Eg: const u32 gEventObjectPic_Boy1[] = INCBIN_U32("graphics/event_objects/pics/people/boy_1.4bpp");
  3. Modify directory path if necessary (I changed mine to "graphics/event_objects/pics/newpeople/boy_1.4bpp")
  4. Goto bottom of the same file
  5. Add a new line and insert:
    const u16 gEventObjectPalette_<NAME>[] = INCBIN_U16("<insert path to palette here>.gbapal");
    (This is going to be the palette file created from Step 1)
  6. Then goto spritesheet_rules.mk
  7. Find the name of the sprite you are modifying and make sure the path matches your new path (if needed)
  8. 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


  1. Goto src/event_object_movement.c
  2. Goto Line 430ish and go to the second to last #define and add:
    #define EVENT_OBJ_PAL_<PALETTE NAME> 0x<HEX NUMBER>
    Where <PALETTE NAME> is the name of your palette (which you can define yourself, but try to keep it centered around your sprite's name), and <HEX NUMBER> being the hex number above + 1 (Keep in mind that this is Hex, and it utilizes A B C D E and F)
  3. Then goto Line 460ish and go to the second to last entry and add:
    {gEventObjectPalette_<PALETTE NAME>, EVENT_OBJ_PAL_<PALETTE NAME>},

4. Applying the newly modified Palette

  1. Goto src/data/field_event_obj/event_object_graphics_info.h
  2. Find the sprite you are editing, and modify the second entry in the list to match your newly created palette
  3. 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.