[FE8] Jester's C/EA Tips and Tricks

Hi guys, I’ve been delving into C for the last year or so and wanted to make a list of all the little tips and tricks I’ve found along the way. If this is your first time with C, then be sure to visit the link below to get set up and follow along.

C + Buildfile Setup

Allow units without portrait data to attack on player phase

READ ME

Now, by default the game does not expect you to have playable units on your team with no portrait data (like monsters) and thus doesn’t account for it if you try to access the weapon select screen without setting portrait data in advance.

If we look at the decomp, we can see the function that controls this behaviour is called StartUnitWeaponSelect.

u8 StartUnitWeaponSelect(struct MenuProc* menu, struct MenuItemProc* menuItem) {

    ProcPtr proc = StartOrphanMenu(&gUnknownMenuDef);

    if (gActiveUnit->pClassData->number != CLASS_PHANTOM) {
        StartFace(0, GetUnitPortraitId(gActiveUnit), 0xB0, 0xC, 2);
        SetFaceBlinkControlById(0, 5);
    }

    ForceMenuItemPanel(proc, gActiveUnit, 0xF, 0xB);

    sub_80832C4();

    return MENU_ACT_SKIPCURSOR | MENU_ACT_END | MENU_ACT_SND6A | MENU_ACT_CLEAR;
}

Now, looking closely we can see a special exception is made for the phantom class to avoid displaying a portrait (as it doesn’t have one). Thus, the weapon select screen can display without crashing the game.

So how do we make it work for other classes? Well as it turns out, any unit that doesn’t have portrait data will always have their portrait ID set to 0. So if we just check for that in advance instead of looking for the phantom class, we can make the function much more flexible. E.g.

u8 StartUnitWeaponSelect(struct MenuProc* menu, struct MenuItemProc* menuItem) {
    
    bool noPortraitUnit = false;

    ProcPtr proc = StartOrphanMenu(&gUnknownMenuDef);

    if (gActiveUnit->pCharacterData->portraitId == 0)
        noPortraitUnit = true;

    if (!noPortraitUnit)
    {
        StartFace(0, GetUnitPortraitId(gActiveUnit), 0xB0, 0xC, 2);
        SetFaceBlinkControlById(0, 5);
    }

    ForceMenuItemPanel(proc, gActiveUnit, 0xF, 0xB);

    sub_80832C4();

    return MENU_ACT_SKIPCURSOR | MENU_ACT_END | MENU_ACT_SND6A | MENU_ACT_CLEAR;
}

Now, if we find a unit without portrait data, then we set the noPortraitUnit variable to true. This lets the function know we don’t want to bother displaying anything.

Multiple Defeat Quotes

READ ME

Link to section

Make staves have 100% accuracy

READ ME

Link to section

8 Likes

New section: Multiple Defeat Quotes

READ ME

By default, units only have a singular defeat quote as opposed to multiple battle quotes.

The basic structure for the defeat quote struct is as follows, with a single msg linked to a unique pid or personal ID.

CONST_DATA struct DefeatTalkEnt gDefeatTalkList[] = {
    {
        .pid     = CHARACTER_ONEILL,
        .route   = CHAPTER_MODE_ANY,
        .chapter = 0x00,
        .flag    = EVFLAG_DEFEAT_BOSS,
        .msg     = 0x0917,
    },
}

Now, you may want a situation where characters say different things depending on who they are killed/defeated by. The solution is to create a new struct for gDefeatTalkList using the same structure as the gBattleTalkList struct.

Here, we have a pair of characters pidA and pidB and a pointer to a msg quote between them.

const struct DefeatTalkEntNew gNewDefeatTalkList[] = {
    {
        .pidA    = CHARACTER_ONEILL,
        .pidB    = CHARACTER_EIRIKA,
        .route   = CHAPTER_MODE_ANY,
        .chapter = 0x00,
        .flag    = EVFLAG_DEFEAT_BOSS,
        .msg     = MSG_DEFEAT_QUOTE_EIRIKA_ONEILL,
    },
    {
        .pidA    = CHARACTER_ONEILL,
        .pidB    = CHARACTER_SETH,
        .route   = CHAPTER_MODE_ANY,
        .chapter = 0x00,
        .flag    = EVFLAG_DEFEAT_BOSS,
        .msg     = MSG_DEFEAT_QUOTE_SETH_ONEILL,
    },
}

In order to make this work in-game, you need to replace the relevant function GetDefeatTalkEntry that references gDefeatTalkList and switch it out with the name of your new list. In addition, a few modifications are necessary to make it check for both pid’s.

    struct DefeatTalkEntNew* GetDefeatTalkEntry(u16 pidA) {
        const struct DefeatTalkEntNew* it;

        for (it = gNewDefeatTalkList; it->pidA != 0xFFFF; it++) {

            if (it->chapter != 0xff && it->chapter != gPlaySt.chapterIndex) {
                if (it->chapter != 0xfe || BattleIsTriangleAttack() != 1) {
                    continue;
                }
            }

            if (GetEventTriggerState(it->flag)) {
                continue;
            }

            if ((pidA == it->pidA) && ((GetUnit(gBattleActor.unit.index)->pCharacterData->number == it->pidB) || GetUnit(gBattleTarget.unit.index)->pCharacterData->number == it->pidB)) {
                return (struct DefeatTalkEntNew *)it;
            }
        }

        return NULL;
    }

The last thing to do is to create a new struct for defeat quotes now that we’ve substituted the .pid variable for .pidA and .pidB.

struct DefeatTalkEntNew {
             u16 pidA;
             u16 pidB;
    /* 02 */ u8 route;
    /* 03 */ u8 chapter;
    /* 04 */ u16 flag;
    /* 06 */ u16 msg;
    /* 08 */ EventScr * event;
};


6 Likes

Make staves have 100% accuracy

READ ME

This one is short and sweet. Create an Installer.event file and paste this inside it.

Change FinalStaffAccuracy to whatever accuracy value you want.

#define FinalStaffAccuracy 100

ORG 0xE9F5C // Final calculation for staff accuracy
BYTE FinalStaffAccuracy
BYTE 0x20   // mov r0, [FinalStaffAccuracy]

3 Likes

This one is really interesting. Say, would it be possible to recreate the Thracia mechanic where healing staves (which in most games always hit), be able to miss? It’d be a funny to add to hacks.