#include "global.h"
#include "attributes.h"

/**
 * Branch forward if the provided weekEventReg flag is set
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) flag
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckWeekEventReg(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckWeekEventReg* cmd = (MsgScriptCmdCheckWeekEventReg*)*script;
    u16 flag = SCRIPT_PACK_16(cmd->flagByte, cmd->flagMask);
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (CHECK_WEEKEVENTREG(flag)) {
        *script += skip;
    }
    return false;
}

/**
 * Branch forward if the player is currently in goron form
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_CheckGoron(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    Player* player = GET_PLAYER(play);
    MsgScriptCmdCheckGoron* cmd = (MsgScriptCmdCheckGoron*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (player->transformation == PLAYER_FORM_GORON) {
        *script += skip;
    }
    return false;
}

/**
 * Branch forward if the player is currently in zora form
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_CheckZora(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    Player* player = GET_PLAYER(play);
    MsgScriptCmdCheckZora* cmd = (MsgScriptCmdCheckZora*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (player->transformation == PLAYER_FORM_ZORA) {
        *script += skip;
    }
    return false;
}

/**
 * Branch forward if the player is currently in deku form
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_CheckDeku(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    Player* player = GET_PLAYER(play);
    MsgScriptCmdCheckDeku* cmd = (MsgScriptCmdCheckDeku*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (player->transformation == PLAYER_FORM_DEKU) {
        *script += skip;
    }
    return false;
}

/**
 * Branch forward if the player is currently in human form
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_CheckHuman(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    Player* player = GET_PLAYER(play);
    MsgScriptCmdCheckHuman* cmd = (MsgScriptCmdCheckHuman*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (player->transformation == PLAYER_FORM_HUMAN) {
        *script += skip;
    }
    return false;
}

/**
 * Branch forward depending on the message choice index
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skipChoice1
 *  3:(s16) skipChoice2
 *  5:(s16) skipChoice3
 * Command size: 7
 */
s32 MsgEvent_CheckTextChoice(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckTextChoice* cmd = (MsgScriptCmdCheckTextChoice*)*script;
    s16 skip = 0;

    switch (play->msgCtx.choiceIndex) {
        case 0:
            skip = SCRIPT_PACK_16(cmd->offset0H, cmd->offset0L);
            break;

        case 1:
            skip = SCRIPT_PACK_16(cmd->offset1H, cmd->offset1L);
            break;

        case 2:
            skip = SCRIPT_PACK_16(cmd->offset2H, cmd->offset2L);
            break;

        default:
            break;
    }
    *script += skip;

    return false;
}

/**
 * Branch forward if the actor has a parent, else get item
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) getItemId
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_OfferItem(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdOfferItem* cmd = (MsgScriptCmdOfferItem*)*script;
    s32 getItemId = SCRIPT_PACK_16(cmd->itemIdH, cmd->itemIdL);
    f32 xzRange = actor->xzDistToPlayer + 1.0f;
    f32 yRange = fabsf(actor->playerHeightRel) + 1.0f;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (Actor_HasParent(actor, play)) {
        *script += skip;
    } else {
        Actor_OfferGetItem(actor, play, getItemId, xzRange, yRange);
        return true;
    }
    return false;
}

/**
 * Branch forward if there is a talk request, else autotalk
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_Autotalk(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdAutotalk* cmd = (MsgScriptCmdAutotalk*)*script;
    f32 xzRange = actor->xzDistToPlayer + 1.0f;
    f32 yRange = fabsf(actor->playerHeightRel) + 1.0f;
    f32 xzDist;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (Actor_TalkOfferAccepted(actor, &play->state)) {
        *script += skip;
    } else {
        actor->flags |= ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED;
        xzDist = actor->xzDistToPlayer;
        actor->xzDistToPlayer = 0.0f;
        Actor_OfferTalkExchange(actor, play, xzRange, yRange, PLAYER_IA_NONE);
        actor->xzDistToPlayer = xzDist;
        return true;
    }
    return false;
}

/**
 * Branch forward if the player currently has more rupees
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) rupees
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckRupees(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckRupees* cmd = (MsgScriptCmdCheckRupees*)*script;
    s16 rupees = SCRIPT_PACK_16(cmd->rupeesH, cmd->rupeesL);
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (gSaveContext.save.saveInfo.playerData.rupees >= rupees) {
        *script += skip;
    }
    return false;
}

/**
 * Branch forward if the callback is null or returns non-zero
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_CheckCallback(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckCallback* cmd = (MsgScriptCmdCheckCallback*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if ((callback == NULL) || (callback(actor, play) != 0)) {
        *script += skip;
    } else {
        return true;
    }
    return false;
}

/**
 * Branch forward based on time of day
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skipDay1
 *  3:(s16) skipNight1
 *  5:(s16) skipDay2
 *  7:(s16) skipNight2
 *  9:(s16) skipDay3
 *  11:(s16) skipNight3
 * Command size: 13
 */
s32 MsgEvent_CheckDay(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckDay* cmd = (MsgScriptCmdCheckDay*)*script;
    s16 skip = 0;

    switch (gSaveContext.save.day) {
        case 1:
            if (!gSaveContext.save.isNight) {
                skip = SCRIPT_PACK_16(cmd->offsetDay1H, cmd->offsetDay1L);
            } else {
                skip = SCRIPT_PACK_16(cmd->offsetNight1H, cmd->offsetNight1L);
            }
            break;

        case 2:
            if (!gSaveContext.save.isNight) {
                skip = SCRIPT_PACK_16(cmd->offsetDay2H, cmd->offsetDay2L);
            } else {
                skip = SCRIPT_PACK_16(cmd->offsetNight2H, cmd->offsetNight2L);
            }
            break;

        case 3:
            if (!gSaveContext.save.isNight) {
                skip = SCRIPT_PACK_16(cmd->offsetDay3H, cmd->offsetDay3L);
            } else {
                skip = SCRIPT_PACK_16(cmd->offsetNight3H, cmd->offsetNight3L);
            }
            break;

        default:
            break;
    }
    *script += skip;
    return false;
}

/**
 * Wait on choice text, skip forward when text closes.
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_AwaitTextJump(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdAwaitTextJump* cmd = (MsgScriptCmdAwaitTextJump*)*script;
    s16 skip;

    switch (Message_GetState(&play->msgCtx)) {
        case TEXT_STATE_CHOICE:
        case TEXT_STATE_EVENT:
            if (!Message_ShouldAdvance(play)) {
                return true;
            }
            FALLTHROUGH;
        case TEXT_STATE_CLOSING:
            skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);
            break;

        default:
            return true;
    }
    *script += skip;
    return false;
}

/**
 * Waits for text to advance
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_AwaitText(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    switch (Message_GetState(&play->msgCtx)) {
        case TEXT_STATE_CHOICE:
        case TEXT_STATE_EVENT:
            if (!Message_ShouldAdvance(play)) {
                return true;
            }
            break;

        case TEXT_STATE_CLOSING:
            break;

        default:
            return true;
    }
    return false;
}

/**
 * Waits for text to end
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_AwaitTextEnd(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    s32 state = Message_GetState(&play->msgCtx);

    *endScript = false;

    switch (state) {
        case TEXT_STATE_CHOICE:
        case TEXT_STATE_EVENT:
            if (!Message_ShouldAdvance(play)) {
                break;
            }
            FALLTHROUGH;
        case TEXT_STATE_CLOSING:
            *endScript = true;
            break;

        default:
            return true;
    }
    return true;
}

/**
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) textId
 * Command size: 3
 */
s32 MsgEvent_BeginText(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdBeginText* cmd = (MsgScriptCmdBeginText*)*script;

    Message_StartTextbox(play, SCRIPT_PACK_16(cmd->textIdH, cmd->textIdL), NULL);
    return false;
}

/**
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) textId
 * Command size: 3
 */
s32 MsgEvent_ContinueText(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdContinueText* cmd = (MsgScriptCmdContinueText*)*script;

    Message_ContinueTextbox(play, SCRIPT_PACK_16(cmd->textIdH, cmd->textIdL));
    return false;
}

/**
 * Script Terminator
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_Done(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    *endScript = true;
    return true;
}

/**
 * Sets weekEventReg flags
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) flag
 * Command size: 3
 */
s32 MsgEvent_SetWeekEventReg(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdSetWeekEventReg* cmd = (MsgScriptCmdSetWeekEventReg*)*script;
    u16 flag = SCRIPT_PACK_16(cmd->flagByte, cmd->flagMask);

    SET_WEEKEVENTREG(flag);
    return false;
}

/**
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_CloseText(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    Message_CloseTextbox(play);
    return false;
}

/**
 *
 * Command structure:
 *  0:(u8)  cmd
 *  0:(u16) flag
 * Command size: 3
 */
s32 MsgEvent_SetCollectible(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdSetCollectible* cmd = (MsgScriptCmdSetCollectible*)*script;
    s32 flag = SCRIPT_PACK_16(cmd->flagH, cmd->flagL);

    if (!Flags_GetCollectible(play, flag)) {
        Flags_SetCollectible(play, flag);
    }
    actor->parent = NULL;
    return false;
}

/**
 *
 * Command structure:
 *  0:(u8)  cmd
 *  0:(s16) rupees
 * Command size: 3
 */
s32 MsgEvent_ChangeRupees(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdChangeRupees* cmd = (MsgScriptCmdChangeRupees*)*script;
    s16 rupees = SCRIPT_PACK_16(cmd->rupeesH, cmd->rupeesL);

    Rupees_ChangeBy(rupees);
    return false;
}

/**
 * Pause the message system
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_Pause(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    play->msgCtx.msgMode = MSGMODE_PAUSED;
    return false;
}

/**
 * Unsets ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED for the actor executing the cmd
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_UnsetAutotalk(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    actor->flags &= ~ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED;
    return false;
}

/**
 * Sets player focus & talk actor to the child of the actor executing the cmd
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_FocusToChild(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    if (actor->child != NULL) {
        Actor_ChangeFocus(actor, play, actor->child);
    }
    return false;
}

/**
 * Sets player focus & talk actor to the actor executing the cmd
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_FocusToSelf(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    if (actor->child != NULL) {
        Actor_ChangeFocus(actor->child, play, actor);
    }
    return false;
}

/**
 * Unconditional branch by `skip`
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_Jump(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdJump* cmd = (MsgScriptCmdJump*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    *script += skip;
    return false;
}

/**
 * Branch forwards if the specified quest item is obtained
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) questItem
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckQuestItem(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckQuestItem* cmd = (MsgScriptCmdCheckQuestItem*)*script;
    u16 questItem = SCRIPT_PACK_16(cmd->questItemH, cmd->questItemL);
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (CHECK_QUEST_ITEM(questItem)) {
        *script += skip;
    }
    return false;
}

/**
 * Branch forwards if event_inf flag is set
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) event_inf flag
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckEventInf(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckEventInf* cmd = (MsgScriptCmdCheckEventInf*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (gSaveContext.eventInf[cmd->flagByte] & cmd->flagMask) {
        *script += skip;
    }
    return false;
}

/**
 * Sets event_inf flag
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) event_inf flag
 * Command size: 3
 */
s32 MsgEvent_SetEventInf(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdSetEventInf* cmd = (MsgScriptCmdSetEventInf*)*script;

    gSaveContext.eventInf[cmd->flagByte] |= cmd->flagMask;
    return false;
}

/**
 * Unsets event_inf flag
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) event_inf flag
 * Command size: 3
 */
s32 MsgEvent_UnsetEventInf(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdUnsetEventInf* cmd = (MsgScriptCmdUnsetEventInf*)*script;

    gSaveContext.eventInf[cmd->flagByte] &= cmd->flagMask ^ 0xFF;
    return false;
}

/**
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) input player item-action
 *  3:(s16) skip if player item-action matches input player item-action
 *  5:(s16) skip if player item-action does not match input player item-action
 *  7:(s16) skip if player item-action is negative
 * Command size: 9
 */
s32 MsgEvent_CheckItemAction(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckItemAction* cmd = (MsgScriptCmdCheckItemAction*)*script;
    PlayerItemAction checkItemAction = SCRIPT_PACK_16(cmd->itemActionH, cmd->itemActionL);
    s16 skip;
    PlayerItemAction curItemAction;

    switch (Message_GetState(&play->msgCtx)) {
        case TEXT_STATE_CHOICE:
        case TEXT_STATE_EVENT:
            if (!Message_ShouldAdvance(play)) {
                return true;
            }
            FALLTHROUGH;
        case TEXT_STATE_PAUSE_MENU:
            curItemAction = func_80123810(play);

            if (curItemAction == PLAYER_IA_NONE) {
                return true;
            } else if (curItemAction < 0) {
                skip = SCRIPT_PACK_16(cmd->offsetContinueH, cmd->offsetContinueL);
            } else if (curItemAction == checkItemAction) {
                skip = SCRIPT_PACK_16(cmd->offsetEqualH, cmd->offsetEqualL);
            } else {
                skip = SCRIPT_PACK_16(cmd->offsetDefaultH, cmd->offsetDefaultL);
            }
            break;

        default:
            return true;
    }
    *script += skip;
    return false;
}

/**
 * Branches forward if song is obtained
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) song
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckHasSong(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckHasSong* cmd = (MsgScriptCmdCheckHasSong*)*script;
    u16 song = SCRIPT_PACK_16(cmd->songH, cmd->songL);
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (CHECK_QUEST_ITEM(QUEST_SONG_SONATA + song)) {
        *script += skip;
    }
    return false;
}

/**
 * Branches forward if the player's current mask matches
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) mask
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckWornMask(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckWornMask* cmd = (MsgScriptCmdCheckWornMask*)*script;
    s32 mask = SCRIPT_PACK_16(cmd->maskH, cmd->maskL);
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (Player_GetMask(play) == mask) {
        *script += skip;
    }
    return false;
}

/**
 * Branches forward if the current time is greater than the provided time
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u8)  hour
 *  1:(u8)  min
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckAfterTime(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckAfterTime* cmd = (MsgScriptCmdCheckAfterTime*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);
    f32 f;
    u16 time;
    u16 now;

    SCRIPT_CALC_TIME(cmd->hr, cmd->min, time, f);
    now = SCRIPT_TIME_NOW;

    if (time < now) {
        *script += skip;
    }
    return false;
}

/**
 * Branches forward if the current time is less or equal to the provided time
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u8)  hour
 *  1:(u8)  min
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckBeforeTime(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckBeforeTime* cmd = (MsgScriptCmdCheckBeforeTime*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);
    f32 f;
    u16 time;
    u16 now;

    SCRIPT_CALC_TIME(cmd->hr, cmd->min, time, f);
    now = SCRIPT_TIME_NOW;

    if (time >= now) {
        *script += skip;
    }
    return false;
}

/**
 * Branches forward if switch flag is set
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) flag
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckSwitchFlag(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckSwitchFlag* cmd = (MsgScriptCmdCheckSwitchFlag*)*script;
    s16 flag = SCRIPT_PACK_16(cmd->flagH, cmd->flagL);
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (Flags_GetSwitch(play, flag)) {
        *script += skip;
    }
    return false;
}

/**
 * Sets switch flag
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) flag
 * Command size: 3
 */
s32 MsgEvent_SetSwitchFlag(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdSetSwitchFlag* cmd = (MsgScriptCmdSetSwitchFlag*)*script;
    s16 flag = SCRIPT_PACK_16(cmd->flagH, cmd->flagL);

    Flags_SetSwitch(play, flag);
    return false;
}

/**
 * Branches forward if item is in inventory
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) item
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckItem(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckItem* cmd = (MsgScriptCmdCheckItem*)*script;
    u16 item = SCRIPT_PACK_16(cmd->itemH, cmd->itemL);
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (INV_CONTENT(item) == item) {
        *script += skip;
    }
    return false;
}

/**
 * Branches forward if current time is within the specified interval
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) startTime
 *  3:(u16) endTime
 *  5:(s16) skip
 * Command size: 7
 */
s32 MsgEvent_CheckBetweenTime(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckBetweenTime* cmd = (MsgScriptCmdCheckBetweenTime*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);
    f32 f;
    u16 startTime;
    u16 endTime;
    u16 now;

    SCRIPT_CALC_TIME(cmd->startHr, cmd->startMin, startTime, f);
    SCRIPT_CALC_TIME(cmd->endHr, cmd->endMin, endTime, f);
    endTime--;
    now = SCRIPT_TIME_NOW;

    if ((startTime >= now) || (now >= endTime)) {
        *script += skip;
    }
    return false;
}

/**
 * Branches forward if the current day matches
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) day
 *  3:(s16) skip
 * Command size: 5
 */
s32 MsgEvent_CheckOnDay(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckOnDay* cmd = (MsgScriptCmdCheckOnDay*)*script;
    s16 day = SCRIPT_PACK_16(cmd->dayH, cmd->dayL);
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if (gSaveContext.save.day == day) {
        *script += skip;
    }
    return false;
}

/**
 * Branches forward if callback exists and returns non-zero
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_CheckCallbackContinue(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback,
                                   s32* endScript) {
    MsgScriptCmdCheckCallbackContinue* cmd = (MsgScriptCmdCheckCallbackContinue*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if ((callback != NULL) && (callback(actor, play) != 0)) {
        *script += skip;
    }
    return false;
}

/**
 * Skip forward if a powder keg is in inventory or if a powder keg actor exists
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_CheckHasPowderKeg(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdCheckHasPowderKeg* cmd = (MsgScriptCmdCheckHasPowderKeg*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    if ((AMMO(ITEM_POWDER_KEG) != 0) || (play->actorCtx.flags & ACTORCTX_FLAG_0)) {
        *script += skip;
    }
    return false;
}

/**
 * Deletes the specified item
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) item
 * Command size: 3
 */
s32 MsgEvent_DeleteItem(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdDeleteItem* cmd = (MsgScriptCmdDeleteItem*)*script;
    s16 item = SCRIPT_PACK_16(cmd->itemH, cmd->itemL);

    Inventory_DeleteItem(item, SLOT(item));
    return false;
}

/**
 * Runs the callback and selects a skip based on the result, default skip is the first one
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 *  3:(s16) skip
 *  5:(s16) skip
 * Command size: 7
 */
s32 MsgEvent_CheckCallbackMulti(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback,
                                s32* endScript) {
    MsgScriptCmdCheckCallbackMulti* cmd = (MsgScriptCmdCheckCallbackMulti*)*script;
    s32 ret = 1;
    s16 skip;

    if (callback != NULL) {
        ret = callback(actor, play);
    }
    switch (ret) {
        case 3:
            skip = SCRIPT_PACK_16(cmd->offset3H, cmd->offset3L);
            break;

        case 2:
            skip = SCRIPT_PACK_16(cmd->offset2H, cmd->offset2L);
            break;

        case 1:
            skip = SCRIPT_PACK_16(cmd->offset1H, cmd->offset1L);
            break;

        default:
            return true;
    }
    *script += skip;
    return false;
}

/**
 * Close the current textbox and set the player actor text id
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) textId
 * Command size: 3
 */
s32 MsgEvent_PlayerTalk(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    Player* player = GET_PLAYER(play);
    MsgScriptCmdPlayerTalk* cmd = (MsgScriptCmdPlayerTalk*)*script;
    u16 textId = SCRIPT_PACK_16(cmd->textIdH, cmd->textIdL);

    player->actor.textId = textId;
    Message_CloseTextbox(play);
    return false;
}

/**
 * Queue a bomber's notebook event
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(u16) event
 * Command size: 3
 */
s32 MsgEvent_NotebookEvent(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdBombersNotebook* cmd = (MsgScriptCmdBombersNotebook*)*script;
    u16 event = SCRIPT_PACK_16(cmd->eventH, cmd->eventL);

    Message_BombersNotebookQueueEvent(play, event);
    return false;
}

/**
 *
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_AwaitTextDone(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    switch (Message_GetState(&play->msgCtx)) {
        case TEXT_STATE_CLOSING:
            break;

        case TEXT_STATE_CHOICE:
        case TEXT_STATE_EVENT:
            if (!Message_ShouldAdvance(play)) {
                return true;
            }
            FALLTHROUGH;
        case TEXT_STATE_DONE:
            if (!Message_ShouldAdvance(play)) {
                return true;
            }
            break;

        default:
            return true;
    }
    return false;
}

/**
 *
 * Command structure:
 *  0:(u8)  cmd
 *  1:(s16) skip
 * Command size: 3
 */
s32 MsgEvent_Jump3(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    MsgScriptCmdJump3* cmd = (MsgScriptCmdJump3*)*script;
    s16 skip = SCRIPT_PACK_16(cmd->offsetH, cmd->offsetL);

    *script += skip;
    *script += 3;
    return true;
}

/**
 * Plays Decide sfx
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_PlayDecide(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    Audio_PlaySfx_MessageDecide();
    return false;
}

/**
 * Plays Cancel sfx
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_PlayCancel(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    Audio_PlaySfx_MessageCancel();
    return false;
}

/**
 * Plays NA_SE_SY_ERROR sfx
 *
 * Command structure:
 *  0:(u8)  cmd
 * Command size: 1
 */
s32 MsgEvent_PlayError(Actor* actor, PlayState* play, u8** script, MsgScriptCallback callback, s32* endScript) {
    Audio_PlaySfx(NA_SE_SY_ERROR);
    return false;
}

typedef s32 (*MsgScriptCmdHandler)(struct Actor*, struct PlayState*, u8**, MsgScriptCallback, s32*);

MsgScriptCmdHandler sMsgScriptCmdHandlers[] = {
    MsgEvent_CheckWeekEventReg,     // MSCRIPT_CMD_ID_CHECK_WEEK_EVENT_REG
    MsgEvent_CheckGoron,            // MSCRIPT_CMD_ID_CHECK_GORON
    MsgEvent_CheckZora,             // MSCRIPT_CMD_ID_CHECK_ZORA
    MsgEvent_CheckDeku,             // MSCRIPT_CMD_ID_CHECK_DEKU
    MsgEvent_CheckHuman,            // MSCRIPT_CMD_ID_CHECK_HUMAN
    MsgEvent_CheckTextChoice,       // MSCRIPT_CMD_ID_CHECK_TEXT_CHOICE
    MsgEvent_OfferItem,             // MSCRIPT_CMD_ID_OFFER_ITEM
    MsgEvent_Autotalk,              // MSCRIPT_CMD_ID_AUTOTALK
    MsgEvent_CheckRupees,           // MSCRIPT_CMD_ID_CHECK_RUPEES
    MsgEvent_CheckCallback,         // MSCRIPT_CMD_ID_CHECK_CALLBACK
    MsgEvent_CheckDay,              // MSCRIPT_CMD_ID_CHECK_DAY
    MsgEvent_AwaitTextJump,         // MSCRIPT_CMD_ID_AWAIT_TEXT_JUMP
    MsgEvent_AwaitText,             // MSCRIPT_CMD_ID_AWAIT_TEXT
    MsgEvent_AwaitTextEnd,          // MSCRIPT_CMD_ID_AWAIT_TEXT_END
    MsgEvent_BeginText,             // MSCRIPT_CMD_ID_BEGIN_TEXT
    MsgEvent_ContinueText,          // MSCRIPT_CMD_ID_CONTINUE_TEXT
    MsgEvent_Done,                  // MSCRIPT_CMD_ID_DONE
    MsgEvent_SetWeekEventReg,       // MSCRIPT_CMD_ID_SET_WEEK_EVENT_REG
    MsgEvent_CloseText,             // MSCRIPT_CMD_ID_CLOSE_TEXT
    MsgEvent_SetCollectible,        // MSCRIPT_CMD_ID_SET_COLLECTIBLE
    MsgEvent_ChangeRupees,          // MSCRIPT_CMD_ID_CHANGE_RUPEES
    MsgEvent_Pause,                 // MSCRIPT_CMD_ID_PAUSE
    MsgEvent_UnsetAutotalk,         // MSCRIPT_CMD_ID_UNSET_AUTOTALK
    MsgEvent_FocusToChild,          // MSCRIPT_CMD_ID_FOCUS_TO_CHILD
    MsgEvent_FocusToSelf,           // MSCRIPT_CMD_ID_FOCUS_TO_SELF
    MsgEvent_Jump,                  // MSCRIPT_CMD_ID_JUMP
    MsgEvent_CheckQuestItem,        // MSCRIPT_CMD_ID_CHECK_QUEST_ITEM
    MsgEvent_CheckEventInf,         // MSCRIPT_CMD_ID_CHECK_EVENT_INF
    MsgEvent_SetEventInf,           // MSCRIPT_CMD_ID_SET_EVENT_INF
    MsgEvent_UnsetEventInf,         // MSCRIPT_CMD_ID_UNSET_EVENT_INF
    MsgEvent_CheckItemAction,       // MSCRIPT_CMD_ID_CHECK_ITEM_ACTION
    MsgEvent_CheckHasSong,          // MSCRIPT_CMD_ID_CHECK_HAS_SONG
    MsgEvent_CheckWornMask,         // MSCRIPT_CMD_ID_CHECK_WORN_MASK
    MsgEvent_CheckAfterTime,        // MSCRIPT_CMD_ID_CHECK_AFTER_TIME
    MsgEvent_CheckBeforeTime,       // MSCRIPT_CMD_ID_CHECK_BEFORE_TIME
    MsgEvent_CheckSwitchFlag,       // MSCRIPT_CMD_ID_CHECK_SWITCH_FLAG
    MsgEvent_SetSwitchFlag,         // MSCRIPT_CMD_ID_SET_SWITCH_FLAG
    MsgEvent_CheckItem,             // MSCRIPT_CMD_ID_CHECK_ITEM
    MsgEvent_CheckBetweenTime,      // MSCRIPT_CMD_ID_CHECK_BETWEEN_TIME
    MsgEvent_CheckOnDay,            // MSCRIPT_CMD_ID_CHECK_ON_DAY
    MsgEvent_CheckCallbackContinue, // MSCRIPT_CMD_ID_CHECK_CALLBACK_CONTINUE
    MsgEvent_CheckHasPowderKeg,     // MSCRIPT_CMD_ID_CHECK_HAS_POWDER_KEG
    MsgEvent_DeleteItem,            // MSCRIPT_CMD_ID_DELETE_ITEM
    MsgEvent_CheckCallbackMulti,    // MSCRIPT_CMD_ID_CHECK_CALLBACK_MULTI
    MsgEvent_PlayerTalk,            // MSCRIPT_CMD_ID_PLAYER_TALK
    MsgEvent_NotebookEvent,         // MSCRIPT_CMD_ID_NOTEBOOK_EVENT
    MsgEvent_AwaitTextDone,         // MSCRIPT_CMD_ID_AWAIT_TEXT_DONE
    MsgEvent_Jump3,                 // MSCRIPT_CMD_ID_JUMP_3
    MsgEvent_PlayDecide,            // MSCRIPT_CMD_ID_PLAY_DECIDE
    MsgEvent_PlayCancel,            // MSCRIPT_CMD_ID_PLAY_CANCEL
    MsgEvent_PlayError,             // MSCRIPT_CMD_ID_PLAY_ERROR
};

u8 sMsgScriptCmdSizes[] = {
    sizeof(MsgScriptCmdCheckWeekEventReg),     // MSCRIPT_CMD_ID_CHECK_WEEK_EVENT_REG
    sizeof(MsgScriptCmdCheckGoron),            // MSCRIPT_CMD_ID_CHECK_GORON
    sizeof(MsgScriptCmdCheckZora),             // MSCRIPT_CMD_ID_CHECK_ZORA
    sizeof(MsgScriptCmdCheckDeku),             // MSCRIPT_CMD_ID_CHECK_DEKU
    sizeof(MsgScriptCmdCheckHuman),            // MSCRIPT_CMD_ID_CHECK_HUMAN
    sizeof(MsgScriptCmdCheckTextChoice),       // MSCRIPT_CMD_ID_CHECK_TEXT_CHOICE
    sizeof(MsgScriptCmdOfferItem),             // MSCRIPT_CMD_ID_OFFER_ITEM
    sizeof(MsgScriptCmdAutotalk),              // MSCRIPT_CMD_ID_AUTOTALK
    sizeof(MsgScriptCmdCheckRupees),           // MSCRIPT_CMD_ID_CHECK_RUPEES
    sizeof(MsgScriptCmdCheckCallback),         // MSCRIPT_CMD_ID_CHECK_CALLBACK
    sizeof(MsgScriptCmdCheckDay),              // MSCRIPT_CMD_ID_CHECK_DAY,
    sizeof(MsgScriptCmdAwaitTextJump),         // MSCRIPT_CMD_ID_AWAIT_TEXT_JUMP
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_AWAIT_TEXT
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_AWAIT_TEXT_END
    sizeof(MsgScriptCmdBeginText),             // MSCRIPT_CMD_ID_BEGIN_TEXT
    sizeof(MsgScriptCmdContinueText),          // MSCRIPT_CMD_ID_CONTINUE_TEXT
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_DONE
    sizeof(MsgScriptCmdSetWeekEventReg),       // MSCRIPT_CMD_ID_SET_WEEK_EVENT_REG
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_CLOSE_TEXT
    sizeof(MsgScriptCmdSetCollectible),        // MSCRIPT_CMD_ID_SET_COLLECTIBLE
    sizeof(MsgScriptCmdChangeRupees),          // MSCRIPT_CMD_ID_CHANGE_RUPEES
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_PAUSE
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_UNSET_AUTOTALK
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_FOCUS_TO_CHILD
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_FOCUS_TO_SELF
    sizeof(MsgScriptCmdJump),                  // MSCRIPT_CMD_ID_JUMP
    sizeof(MsgScriptCmdCheckQuestItem),        // MSCRIPT_CMD_ID_CHECK_QUEST_ITEM
    sizeof(MsgScriptCmdCheckEventInf),         // MSCRIPT_CMD_ID_CHECK_EVENT_INF
    sizeof(MsgScriptCmdSetEventInf),           // MSCRIPT_CMD_ID_SET_EVENT_INF
    sizeof(MsgScriptCmdUnsetEventInf),         // MSCRIPT_CMD_ID_UNSET_EVENT_INF
    sizeof(MsgScriptCmdCheckItemAction),       // MSCRIPT_CMD_ID_CHECK_ITEM_ACTION
    sizeof(MsgScriptCmdCheckHasSong),          // MSCRIPT_CMD_ID_CHECK_HAS_SONG
    sizeof(MsgScriptCmdCheckWornMask),         // MSCRIPT_CMD_ID_CHECK_WORN_MASK
    sizeof(MsgScriptCmdCheckAfterTime),        // MSCRIPT_CMD_ID_CHECK_AFTER_TIME
    sizeof(MsgScriptCmdCheckBeforeTime),       // MSCRIPT_CMD_ID_CHECK_BEFORE_TIME
    sizeof(MsgScriptCmdCheckSwitchFlag),       // MSCRIPT_CMD_ID_CHECK_SWITCH_FLAG
    sizeof(MsgScriptCmdSetSwitchFlag),         // MSCRIPT_CMD_ID_SET_SWITCH_FLAG
    sizeof(MsgScriptCmdCheckItem),             // MSCRIPT_CMD_ID_CHECK_ITEM
    sizeof(MsgScriptCmdCheckBetweenTime),      // MSCRIPT_CMD_ID_CHECK_BETWEEN_TIME
    sizeof(MsgScriptCmdCheckOnDay),            // MSCRIPT_CMD_ID_CHECK_ON_DAY
    sizeof(MsgScriptCmdCheckCallbackContinue), // MSCRIPT_CMD_ID_CHECK_CALLBACK_CONTINUE
    sizeof(MsgScriptCmdCheckHasPowderKeg),     // MSCRIPT_CMD_ID_CHECK_HAS_POWDER_KEG
    sizeof(MsgScriptCmdDeleteItem),            // MSCRIPT_CMD_ID_DELETE_ITEM
    sizeof(MsgScriptCmdCheckCallbackMulti),    // MSCRIPT_CMD_ID_CHECK_CALLBACK_MULTI
    sizeof(MsgScriptCmdPlayerTalk),            // MSCRIPT_CMD_ID_PLAYER_TALK
    sizeof(MsgScriptCmdBombersNotebook),       // MSCRIPT_CMD_ID_NOTEBOOK_EVENT
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_AWAIT_TEXT_DONE
    sizeof(MsgScriptCmdJump3),                 // MSCRIPT_CMD_ID_JUMP_3
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_PLAY_DECIDE
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_PLAY_CANCEL
    sizeof(MsgScriptCmdBase),                  // MSCRIPT_CMD_ID_PLAY_ERROR
};

/**
 * Runs a message event script
 *
 * @param actor         The actor associated with the cmd
 * @param play          Play game state
 * @param script        The script to run
 * @param callback      Callback function used by various commands for different purposes
 * @param[in,out] pos   Position to resume the cmd from, the point at which the cmd stops executing is also
 *                      written out here
 * @return s32  Whether the cmd has reached an endpoint
 */
s32 MsgEvent_RunScript(Actor* actor, PlayState* play, MsgScript* script, MsgScriptCallback callback, s32* pos) {
    u8* start;
    u8* cur;
    s32 scriptDone = false;
    s32 i;
    s32 cmdLen;
    u8 cmdId;
    s32 stop;

    start = script;
    script += *pos;

    if (sREG(95) != 0) {}

    cmdLen = 0;
    do {
        // Skip data from previous command
        script += cmdLen;

        // Get command id
        cmdId = *script;

        // Get command length
        if (cmdId < ARRAY_COUNTU(sMsgScriptCmdSizes)) {
            cmdLen = sMsgScriptCmdSizes[cmdId];
        } else {
            cmdLen = -1;
            //! @bug command handler still runs even if cmdId is invalid
        }

        // Debug loop?
        if (sREG(95) != 0) {
            for (i = 0; i < cmdLen; i++) {}
        }

        stop = sMsgScriptCmdHandlers[cmdId](actor, play, &script, callback, &scriptDone);
    } while (!stop);

    cur = script;
    if (!scriptDone) {
        *pos = cur - start;
    } else {
        *pos = 0;
    }
    return scriptDone;
}
