#include "GameInteractor.h"
#include <libultraship/bridge.h>
#include "soh/Enhancements/cosmetics/CosmeticsEditor.h"
#include "soh/Enhancements/randomizer/3drando/random.hpp"
#include <math.h>
#include "soh/Enhancements/debugger/colViewer.h"

extern "C" {
#include "variables.h"
#include "macros.h"
#include "soh/cvar_prefixes.h"
#include "functions.h"
extern PlayState* gPlayState;
}

#include "overlays/actors/ovl_En_Niw/z_en_niw.h"
#include "overlays/actors/ovl_En_Bom/z_en_bom.h"

void GameInteractor::RawAction::AddOrRemoveHealthContainers(int16_t amount) {
    gSaveContext.healthCapacity += amount * 0x10;
}

void GameInteractor::RawAction::AddOrRemoveMagic(int8_t amount) {
    // Full single magic = 48
    // Full double magic = 96
    int8_t currentMagicCapacity = (gSaveContext.isDoubleMagicAcquired + 1) * 48;

    if (gSaveContext.isMagicAcquired) {
        gSaveContext.prevMagicState = gSaveContext.magicState;
        if (amount > 0) {
            if (gSaveContext.magic + amount > currentMagicCapacity) {
                gSaveContext.magicFillTarget = currentMagicCapacity;
            } else {
                gSaveContext.magicFillTarget = gSaveContext.magic + amount;
            }
            gSaveContext.magicState = MAGIC_STATE_FILL;
        } else if (amount < 0) {
            if (gSaveContext.magic + amount < 0) {
                gSaveContext.magic = 0;
            } else {
                gSaveContext.magic += amount;
            }
        }
    }
}

void GameInteractor::RawAction::HealOrDamagePlayer(int16_t hearts) {
    if (hearts > 0) {
        Health_ChangeBy(gPlayState, hearts * 0x10);
    } else if (hearts < 0) {
        Player* player = GET_PLAYER(gPlayState);
        Health_ChangeBy(gPlayState, hearts * 0x10);
        func_80837C0C(gPlayState, player, 0, 0, 0, 0, 0);
        player->invincibilityTimer = 28;
    }
}

void GameInteractor::RawAction::SetPlayerHealth(int16_t hearts) {
    gSaveContext.health = hearts * 0x10;
}

void GameInteractor::RawAction::SetLinkInvisibility(bool active) {
    GameInteractor::State::InvisibleLinkActive = active;
    if (!active) {
        Player* player = GET_PLAYER(gPlayState);
        player->actor.shape.shadowDraw = ActorShadow_DrawFeet;
    }
}

void GameInteractor::RawAction::SetWeatherStorm(bool active) {
    if (active) {
        gPlayState->envCtx.unk_F2[0] = 20;    // rain intensity target
        gPlayState->envCtx.gloomySkyMode = 1; // start gloomy sky
        if ((gWeatherMode != 0) || gPlayState->envCtx.unk_17 != 0) {
            gPlayState->envCtx.unk_DE = 1;
        }
        gPlayState->envCtx.lightningMode = LIGHTNING_MODE_ON;
        Environment_PlayStormNatureAmbience(gPlayState);
    } else {
        gPlayState->envCtx.unk_F2[0] = 0;
        if (gPlayState->csCtx.state == CS_STATE_IDLE) {
            Environment_StopStormNatureAmbience(gPlayState);
        } else if (func_800FA0B4(SEQ_PLAYER_BGM_MAIN) == NA_BGM_NATURE_AMBIENCE) {
            Audio_SetNatureAmbienceChannelIO(NATURE_CHANNEL_LIGHTNING, CHANNEL_IO_PORT_1, 0);
            Audio_SetNatureAmbienceChannelIO(NATURE_CHANNEL_RAIN, CHANNEL_IO_PORT_1, 0);
        }
        osSyncPrintf("\n\n\nE_wether_flg=[%d]", gWeatherMode);
        osSyncPrintf("\nrain_evt_trg=[%d]\n\n", gPlayState->envCtx.gloomySkyMode);
        if (gWeatherMode == 0 && (gPlayState->envCtx.gloomySkyMode == 1)) {
            gPlayState->envCtx.gloomySkyMode = 2; // end gloomy sky
        } else {
            gPlayState->envCtx.gloomySkyMode = 0;
            gPlayState->envCtx.unk_DE = 0;
        }
        gPlayState->envCtx.lightningMode = LIGHTNING_MODE_LAST;
    }
}

void GameInteractor::RawAction::ForceEquipBoots(int8_t boots) {
    Player* player = GET_PLAYER(gPlayState);
    player->currentBoots = BOOTS_EQUIP_TO_PLAYER(boots);
    Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, boots);
    Player_SetBootData(gPlayState, player);
}

void GameInteractor::RawAction::FreezePlayer() {
    Player* player = GET_PLAYER(gPlayState);
    player->actor.colChkInfo.damage = 0;
    func_80837C0C(gPlayState, player, 3, 0, 0, 0, 0);
}

void GameInteractor::RawAction::BurnPlayer() {
    Player* player = GET_PLAYER(gPlayState);
    for (int i = 0; i < 18; i++) {
        player->bodyFlameTimers[i] = Rand_S16Offset(0, 200);
    }
    player->bodyIsBurning = true;
    func_80837C0C(gPlayState, player, 0, 0, 0, 0, 0);
}

void GameInteractor::RawAction::ElectrocutePlayer() {
    Player* player = GET_PLAYER(gPlayState);
    func_80837C0C(gPlayState, player, 4, 0, 0, 0, 0);
}

void GameInteractor::RawAction::KnockbackPlayer(float strength) {
    Player* player = GET_PLAYER(gPlayState);
    func_8002F71C(gPlayState, &player->actor, strength * 5, player->actor.world.rot.y + 0x8000, strength * 5);
}

void GameInteractor::RawAction::SetSceneFlag(int16_t sceneNum, int16_t flagType, int16_t flag) {
    switch (flagType) {
        case FlagType::FLAG_SCENE_SWITCH:
            if (sceneNum == gPlayState->sceneNum) {
                if (flag < 0x20) {
                    gPlayState->actorCtx.flags.swch |= (1 << flag);
                } else {
                    gPlayState->actorCtx.flags.tempSwch |= (1 << (flag - 0x20));
                }
            }
            if (flag < 0x20) {
                gSaveContext.sceneFlags[sceneNum].swch |= (1 << flag);
            }
            break;
        case FlagType::FLAG_SCENE_CLEAR:
            if (sceneNum == gPlayState->sceneNum) gPlayState->actorCtx.flags.clear |= (1 << flag);
            gSaveContext.sceneFlags[sceneNum].clear |= (1 << flag);
            break;
        case FlagType::FLAG_SCENE_TREASURE:
            if (sceneNum == gPlayState->sceneNum) gPlayState->actorCtx.flags.chest |= (1 << flag);
            gSaveContext.sceneFlags[sceneNum].chest |= (1 << flag);
            break;
        case FlagType::FLAG_SCENE_COLLECTIBLE:
            if (sceneNum == gPlayState->sceneNum) {
                if (flag != 0) {
                    if (flag < 0x20) {
                        gPlayState->actorCtx.flags.collect |= (1 << flag);
                    } else {
                        gPlayState->actorCtx.flags.tempCollect |= (1 << (flag - 0x20));
                    }
                }
            }
            if (flag != 0 && flag < 0x20) {
                gSaveContext.sceneFlags[sceneNum].collect |= (1 << flag);
            }
            break;
    }
};

void GameInteractor::RawAction::UnsetSceneFlag(int16_t sceneNum, int16_t flagType, int16_t flag) {
    switch (flagType) {
        case FlagType::FLAG_SCENE_SWITCH:
            if (sceneNum == gPlayState->sceneNum) {
                if (flag < 0x20) {
                    gPlayState->actorCtx.flags.swch &= ~(1 << flag);
                } else {
                    gPlayState->actorCtx.flags.tempSwch &= ~(1 << (flag - 0x20));
                }
            }
            if (flag < 0x20) {
                gSaveContext.sceneFlags[sceneNum].swch &= ~(1 << flag);
            }
            break;
        case FlagType::FLAG_SCENE_CLEAR:
            if (sceneNum == gPlayState->sceneNum) gPlayState->actorCtx.flags.clear &= ~(1 << flag);
            gSaveContext.sceneFlags[sceneNum].clear &= ~(1 << flag);
            break;
        case FlagType::FLAG_SCENE_TREASURE:
            if (sceneNum == gPlayState->sceneNum) gPlayState->actorCtx.flags.chest &= ~(1 << flag);
            gSaveContext.sceneFlags[sceneNum].chest &= ~(1 << flag);
            break;
        case FlagType::FLAG_SCENE_COLLECTIBLE:
            if (sceneNum == gPlayState->sceneNum) {
                if (flag != 0) {
                    if (flag < 0x20) {
                        gPlayState->actorCtx.flags.collect &= ~(1 << flag);
                    } else {
                        gPlayState->actorCtx.flags.tempCollect &= ~(1 << (flag - 0x20));
                    }
                }
            }
            if (flag != 0 && flag < 0x20) {
                gSaveContext.sceneFlags[sceneNum].collect &= ~(1 << flag);
            }
            break;
    }
};

bool GameInteractor::RawAction::CheckFlag(int16_t flagType, int16_t flag) {
    switch (flagType) {
        case FlagType::FLAG_EVENT_CHECK_INF:
            return Flags_GetEventChkInf(flag);
        case FlagType::FLAG_ITEM_GET_INF:
            return Flags_GetItemGetInf(flag);
        case FlagType::FLAG_INF_TABLE:
            return Flags_GetInfTable(flag);
        case FlagType::FLAG_EVENT_INF:
            return Flags_GetEventInf(flag);
        case FlagType::FLAG_RANDOMIZER_INF:
            return Flags_GetRandomizerInf(static_cast<RandomizerInf>(flag));
        case FlagType::FLAG_GS_TOKEN:
            return GET_GS_FLAGS((flag & 0x1F00) >> 8);
    }
}

void GameInteractor::RawAction::SetFlag(int16_t flagType, int16_t flag) {
    switch (flagType) {
        case FlagType::FLAG_EVENT_CHECK_INF:
            gSaveContext.eventChkInf[flag >> 4] |= (1 << (flag & 0xF));
            break;
        case FlagType::FLAG_ITEM_GET_INF:
            gSaveContext.itemGetInf[flag >> 4] |= (1 << (flag & 0xF));
            break;
        case FlagType::FLAG_INF_TABLE:
            gSaveContext.infTable[flag >> 4] |= (1 << (flag & 0xF));
            break;
        case FlagType::FLAG_EVENT_INF:
            gSaveContext.eventInf[flag >> 4] |= (1 << (flag & 0xF));
            break;
        case FlagType::FLAG_RANDOMIZER_INF:
            if (!IS_RANDO) {
                LUSLOG_ERROR("Tried to set randomizerInf flag outside of rando (%d)", flag);
                assert(false);
                break;
            }
            gSaveContext.ship.randomizerInf[flag >> 4] |= (1 << (flag & 0xF));
            break;
        case FlagType::FLAG_GS_TOKEN:
            SET_GS_FLAGS((flag & 0x1F00) >> 8, flag & 0xFF);
            break;
    }
};

void GameInteractor::RawAction::UnsetFlag(int16_t flagType, int16_t flag) {
    switch (flagType) {
        case FlagType::FLAG_EVENT_CHECK_INF:
            gSaveContext.eventChkInf[flag >> 4] &= ~(1 << (flag & 0xF));
            break;
        case FlagType::FLAG_ITEM_GET_INF:
            gSaveContext.itemGetInf[flag >> 4] &= ~(1 << (flag & 0xF));
            break;
        case FlagType::FLAG_INF_TABLE:
            gSaveContext.infTable[flag >> 4] &= ~(1 << (flag & 0xF));
            break;
        case FlagType::FLAG_EVENT_INF:
            gSaveContext.eventInf[flag >> 4] &= ~(1 << (flag & 0xF));
            break;
        case FlagType::FLAG_RANDOMIZER_INF:
            if (!IS_RANDO) {
                LUSLOG_ERROR("Tried to unset randomizerInf flag outside of rando (%d)", flag);
                assert(false);
                break;
            }
            gSaveContext.ship.randomizerInf[flag >> 4] &= ~(1 << (flag & 0xF));
            break;
    }
};

void GameInteractor::RawAction::GiveOrTakeShield(int32_t shield) {
    // When taking a shield, make sure it is unequipped as well.
    // When giving a shield and the player isn't wearing one yet,
    // equip said shield when the player's current age can equip it.
    // 'shield' being a negative number means taking that shield.

    Player* player = GET_PLAYER(gPlayState);
    int8_t shieldToCheck = PLAYER_SHIELD_NONE;

    if (shield < 0) {
        shield = shield * -1;

        switch (shield) {
            case ITEM_SHIELD_DEKU:
                shieldToCheck = PLAYER_SHIELD_DEKU;
                break;
            case ITEM_SHIELD_HYLIAN:
                shieldToCheck = PLAYER_SHIELD_HYLIAN;
                break;
            case ITEM_SHIELD_MIRROR:
                shieldToCheck = PLAYER_SHIELD_MIRROR;
                break;
            default:
                break;
        }

        gSaveContext.inventory.equipment &= ~(gBitFlags[shield - ITEM_SHIELD_DEKU] << gEquipShifts[EQUIP_TYPE_SHIELD]);

        if (player->currentShield == shieldToCheck) {
            player->currentShield = PLAYER_SHIELD_NONE;
            Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_NONE);
        }
    } else {
        Item_Give(gPlayState, shield);
        if (player->currentShield == PLAYER_SHIELD_NONE) {
            if (LINK_IS_CHILD && shield == ITEM_SHIELD_DEKU) {
                player->currentShield = PLAYER_SHIELD_DEKU;
                Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_DEKU);
            } else if (LINK_IS_ADULT && shield == ITEM_SHIELD_MIRROR) {
                player->currentShield = PLAYER_SHIELD_MIRROR;
                Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_MIRROR);
            } else if (shield == ITEM_SHIELD_HYLIAN) {
                player->currentShield = PLAYER_SHIELD_HYLIAN;
                Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_HYLIAN);
            }
        }
    }
}

void GameInteractor::RawAction::ForceInterfaceUpdate() {
    gSaveContext.unk_13E8 = 50;
    Interface_Update(gPlayState);
}

void GameInteractor::RawAction::UpdateActor(void* refActor) {
    // Update actor again outside of their normal update cycle.

    Actor* actor = static_cast<Actor*>(refActor);
    
    // Sometimes the actor is destroyed in the previous Update, so check if the update function still exists.
    if (actor->update != NULL) {
        // Fix for enemies sometimes taking a "fake" hit, where their invincibility timer is
        // reset but damage isn't applied.
        if (actor->colorFilterTimer > 0) {
            actor->colorFilterTimer--;
        }

        // This variable is used to not let the collider subscribe a second time when the actor update function
        // is ran a second time, incorrectly applying double damage in some cases.
        GameInteractor::State::SecondCollisionUpdate = 1;
        actor->update(actor, gPlayState);
        GameInteractor::State::SecondCollisionUpdate = 0;
    }
}

void GameInteractor::RawAction::TeleportPlayer(int32_t nextEntrance) {
    Audio_PlaySoundGeneral(NA_SE_EN_GANON_LAUGH, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
    gPlayState->nextEntranceIndex = nextEntrance;
    gPlayState->transitionTrigger = TRANS_TRIGGER_START;
    gPlayState->transitionType = TRANS_TYPE_FADE_BLACK;
    gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK;
}

void GameInteractor::RawAction::ClearAssignedButtons(uint8_t buttonSet) {
    switch (buttonSet) {
        case GI_BUTTONS_CBUTTONS:
            gSaveContext.equips.buttonItems[1] = gSaveContext.equips.buttonItems[2] =
                gSaveContext.equips.buttonItems[3] = ITEM_NONE;
            break;
        case GI_BUTTONS_DPAD:
            gSaveContext.equips.buttonItems[4] = gSaveContext.equips.buttonItems[5] =
                gSaveContext.equips.buttonItems[6] = gSaveContext.equips.buttonItems[7] = ITEM_NONE;
            break;
    }
}

void GameInteractor::RawAction::SetTimeOfDay(uint32_t time) {
    if (time > 0xFFFF) {
        gSaveContext.dayTime = 0xFFFF;
    } else {
        gSaveContext.dayTime = time;
    }
}

void GameInteractor::RawAction::SetCollisionViewer(bool active) {
    CVarSetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), active);
    CVarSetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Decal"), active);
    
    if (active) {
        CVarSetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Scene"), COLVIEW_TRANSPARENT);
        CVarSetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.BGActors"), COLVIEW_TRANSPARENT);
        CVarSetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.ColCheck"), COLVIEW_TRANSPARENT);
        CVarSetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Waterbox"), COLVIEW_TRANSPARENT);
    } else {
        CVarSetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Scene"), COLVIEW_DISABLED);
        CVarSetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.BGActors"), COLVIEW_DISABLED);
        CVarSetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.ColCheck"), COLVIEW_DISABLED);
        CVarSetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Waterbox"), COLVIEW_DISABLED);
    }
}

void GameInteractor::RawAction::SetCosmeticsColor(uint8_t cosmeticCategory, uint8_t colorValue) {
    Color_RGBA8 newColor;
    newColor.r = 255;
    newColor.g = 255;
    newColor.b = 255;
    newColor.a = 255;
    
    switch (colorValue) { 
        case GI_COLOR_RED:
            newColor.r = 200;
            newColor.g = 30;
            newColor.b = 30;
            break;
        case GI_COLOR_GREEN:
            newColor.r = 50;
            newColor.g = 200;
            newColor.b = 50;
            break;
        case GI_COLOR_BLUE:
            newColor.r = 50;
            newColor.g = 50;
            newColor.b = 200;
            break;
        case GI_COLOR_ORANGE:
            newColor.r = 200;
            newColor.g = 120;
            newColor.b = 0;
            break;
        case GI_COLOR_YELLOW:
            newColor.r = 234;
            newColor.g = 240;
            newColor.b = 33;
            break;
        case GI_COLOR_PURPLE:
            newColor.r = 144;
            newColor.g = 13;
            newColor.b = 178;
            break;
        case GI_COLOR_PINK:
            newColor.r = 215;
            newColor.g = 93;
            newColor.b = 246;
            break;
        case GI_COLOR_BROWN:
            newColor.r = 108;
            newColor.g = 72;
            newColor.b = 15;
            break;
        case GI_COLOR_BLACK:
            newColor.r = 0;
            newColor.g = 0;
            newColor.b = 0;
            break;
        default:
            break;
    }

    switch (cosmeticCategory) { 
        case GI_COSMETICS_TUNICS:
            CVarSetColor(CVAR_COSMETIC("Link.KokiriTunic.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Link.KokiriTunic.Changed"), 1);
            CVarSetColor(CVAR_COSMETIC("Link.GoronTunic.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Link.GoronTunic.Changed"), 1);
            CVarSetColor(CVAR_COSMETIC("Link.ZoraTunic.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Link.ZoraTunic.Changed"), 1);
            break;
        case GI_COSMETICS_NAVI:
            CVarSetColor(CVAR_COSMETIC("Navi.EnemyPrimary.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Navi.EnemyPrimary.Changed"), 1);
            CVarSetColor(CVAR_COSMETIC("Navi.EnemySecondary.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Navi.EnemySecondary.Changed"), 1);
            CVarSetColor(CVAR_COSMETIC("Navi.IdlePrimary.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Navi.IdlePrimary.Changed"), 1);
            CVarSetColor(CVAR_COSMETIC("Navi.IdleSecondary.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Navi.IdleSecondary.Changed"), 1);
            CVarSetColor(CVAR_COSMETIC("Navi.NPCPrimary.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Navi.NPCPrimary.Changed"), 1);
            CVarSetColor(CVAR_COSMETIC("Navi.NPCSecondary.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Navi.NPCSecondary.Changed"), 1);
            CVarSetColor(CVAR_COSMETIC("Navi.PropsPrimary.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Navi.PropsPrimary.Changed"), 1);
            CVarSetColor(CVAR_COSMETIC("Navi.PropsSecondary.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Navi.PropsSecondary.Changed"), 1);
            break;
        case GI_COSMETICS_HAIR:
            CVarSetColor(CVAR_COSMETIC("Link.Hair.Value"), newColor);
            CVarSetInteger(CVAR_COSMETIC("Link.Hair.Changed"), 1);
            break;
    }

    Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
    ApplyOrResetCustomGfxPatches();
}

void GameInteractor::RawAction::RandomizeCosmeticsColors(bool excludeBiddingWarColors) {
    const char* cvarsToLock[12] = { 
        CVAR_COSMETIC("Link.KokiriTunic.Locked"),
        CVAR_COSMETIC("Link.GoronTunic.Locked"),
        CVAR_COSMETIC("Link.ZoraTunic.Locked"),
        CVAR_COSMETIC("Navi.EnemyPrimary.Locked"),
        CVAR_COSMETIC("Navi.EnemySecondary.Locked"),
        CVAR_COSMETIC("Navi.IdlePrimary.Locked"),
        CVAR_COSMETIC("Navi.IdleSecondary.Locked"),
        CVAR_COSMETIC("Navi.NPCPrimary.Locked"),
        CVAR_COSMETIC("Navi.NPCSecondary.Locked"),
        CVAR_COSMETIC("Navi.PropsPrimary.Locked"),
        CVAR_COSMETIC("Navi.PropsSecondary.Locked"),
        CVAR_COSMETIC("Link.Hair.Locked")
    };

    if (excludeBiddingWarColors) {
        for (uint8_t i = 0; i < 12; i++) {
            CVarSetInteger(cvarsToLock[i], 1);
        }
    }

    CosmeticsEditor_RandomizeAll();

    if (excludeBiddingWarColors) {
        for (uint8_t i = 0; i < 12; i++) {
            CVarSetInteger(cvarsToLock[i], 0);
        }
    }
}

void GameInteractor::RawAction::EmulateButtonPress(int32_t button) {
    GameInteractor::State::EmulatedButtons |= button;
}

void GameInteractor::RawAction::EmulateRandomButtonPress(uint32_t chancePercentage) {
    uint32_t emulatedButton;
    uint32_t randomNumber = rand();
    uint32_t possibleButtons[14] = { BTN_CRIGHT, BTN_CLEFT, BTN_CDOWN, BTN_CUP,   BTN_R, BTN_L, BTN_DRIGHT,
                             BTN_DLEFT,  BTN_DDOWN, BTN_DUP,   BTN_START, BTN_Z, BTN_B, BTN_A };

    emulatedButton = possibleButtons[randomNumber % 14];

    if (randomNumber % 100 < chancePercentage) {
        GameInteractor::State::EmulatedButtons |= emulatedButton;
    }
}

void GameInteractor::RawAction::AddOrTakeAmmo(int16_t amount, int16_t item) {
    Inventory_ChangeAmmo(item, amount);
}

void GameInteractor::RawAction::SetRandomWind(bool active) {
    Player* player = GET_PLAYER(gPlayState);
    if (active) {
        GameInteractor::State::RandomWindActive = 1;
        if (GameInteractor::State::RandomWindSecondsSinceLastDirectionChange == 0) {
            player->pushedYaw = (rand() % 49152) - 32767;
            GameInteractor::State::RandomWindSecondsSinceLastDirectionChange = 5;
        } else {
            GameInteractor::State::RandomWindSecondsSinceLastDirectionChange--;
        }
    } else {
        GameInteractor::State::RandomWindActive = 0;
        GameInteractor::State::RandomWindSecondsSinceLastDirectionChange = 0;
        player->pushedSpeed = 0.0f;
        player->pushedYaw = 0.0f;
    }
}

void GameInteractor::RawAction::SetPlayerInvincibility(bool active) {
    Player* player = GET_PLAYER(gPlayState);
    if (active) {
        player->invincibilityTimer = 1000;
    } else {
        player->invincibilityTimer = 0;
    }
}

/// Clears the cutscene pointer to a value safe for wrong warps.
void GameInteractor::RawAction::ClearCutscenePointer() {
    if (!gPlayState) return;
    static uint32_t null_cs[] = {0, 0};
    gPlayState->csCtx.segment = &null_cs;
}

GameInteractionEffectQueryResult GameInteractor::RawAction::SpawnEnemyWithOffset(uint32_t enemyId, int32_t enemyParams) {

    if (!GameInteractor::CanSpawnActor()) {
        return GameInteractionEffectQueryResult::TemporarilyNotPossible;
    }

    int16_t sceneNum = gPlayState->sceneNum;
    int16_t roomNum = gPlayState->roomCtx.curRoom.num;
    Player* player = GET_PLAYER(gPlayState);

    // Disallow enemy spawns in the painting Poe rooms in Forest Temple.
    // Killing a spawned enemy before the Poe can softlock the rooms entirely.
    if (sceneNum == SCENE_FOREST_TEMPLE && (roomNum == 12 || roomNum == 13 || roomNum == 16)) {
        return GameInteractionEffectQueryResult::NotPossible;
    }

    // Only one Dark Link may exist at a time.
    if (enemyId == ACTOR_EN_TORCH2) {
        Actor* nearbyEnTest = Actor_FindNearby(gPlayState, &player->actor, ACTOR_EN_TORCH2, ACTORCAT_ENEMY, 8000.0f);
        if (nearbyEnTest != NULL) {
            return GameInteractionEffectQueryResult::TemporarilyNotPossible;
        }
    }

    if (enemyId == ACTOR_EN_CLEAR_TAG) {
        // Don't allow Arwings in certain areas because they cause issues.
        // Locations: King dodongo room, Morpha room, Twinrova room, Ganondorf room, Fishing pond, Ganon's room
        // TODO: Swap this to disabling the option in CC options menu instead.
        if (sceneNum == SCENE_DODONGOS_CAVERN_BOSS || sceneNum == SCENE_WATER_TEMPLE_BOSS || sceneNum == SCENE_SPIRIT_TEMPLE_BOSS ||
            sceneNum == SCENE_GANONDORF_BOSS || sceneNum == SCENE_FISHING_POND || sceneNum == SCENE_GANON_BOSS) {
            return GameInteractionEffectQueryResult::NotPossible;
        }
    }

    // Generate point in random angle with a radius.
    float angle = Random(0, 2 * M_PI);
    float radius = 150;
    float posXOffset = radius * cos(angle);
    float posZOffset = radius * sin(angle);

    // Raycast to the ground from randomly generated point.
    CollisionPoly poly;
    Vec3f pos;
    f32 raycastResult;

    pos.x = player->actor.world.pos.x + posXOffset;
    pos.y = player->actor.world.pos.y + 50;
    pos.z = player->actor.world.pos.z + posZOffset;
    raycastResult = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &poly, &pos);

    // If ground is found below actor, move actor to that height.
    // If not it's likely out of bounds, so make it temporarily not possible and try again later.
    if (raycastResult > BGCHECK_Y_MIN) {
        pos.y = raycastResult;
    } else {
        return GameInteractionEffectQueryResult::TemporarilyNotPossible;
    }

    // Offset flying enemies off the ground
    if (enemyId == ACTOR_EN_CLEAR_TAG || enemyId == ACTOR_EN_FIREFLY) {
        pos.y += 100;
    }

    if (enemyId == ACTOR_EN_FIREFLY && enemyParams == 2) {
        // Spawn Flock of Keese (5x)
        for (uint8_t i = 0; i < 5; i++) {
            pos.x += 10;
            pos.y += 10;
            pos.z += 10;
            if (Actor_Spawn(&gPlayState->actorCtx, gPlayState, enemyId, pos.x, pos.y, pos.z, 0, 0, 0, enemyParams, 0) == NULL) {
                return GameInteractionEffectQueryResult::TemporarilyNotPossible;
            }
        }
        return GameInteractionEffectQueryResult::Possible;
    } else {
        if (Actor_Spawn(&gPlayState->actorCtx, gPlayState, enemyId, pos.x, pos.y, pos.z, 0, 0, 0, enemyParams, 0) != NULL) {
            return GameInteractionEffectQueryResult::Possible;
        }
    }

    return GameInteractionEffectQueryResult::TemporarilyNotPossible;
}

GameInteractionEffectQueryResult GameInteractor::RawAction::SpawnActor(uint32_t actorId, int32_t actorParams) {

    if (!GameInteractor::CanSpawnActor()) {
        return GameInteractionEffectQueryResult::TemporarilyNotPossible;
    }

    Player* player = GET_PLAYER(gPlayState);

    if (actorId == ACTOR_EN_NIW) {
        // Spawn Cucco and make it angry
        EnNiw* cucco = (EnNiw*)Actor_Spawn(&gPlayState->actorCtx, gPlayState, actorId, player->actor.world.pos.x,
                                           player->actor.world.pos.y + 2200, player->actor.world.pos.z, 0, 0, 0, actorParams, 0);
        if (cucco == NULL) {
            return GameInteractionEffectQueryResult::TemporarilyNotPossible;
        }

        cucco->actionFunc = func_80AB70A0_nocutscene;
        return GameInteractionEffectQueryResult::Possible;
    } else if (actorId == ACTOR_EN_BOM) {
        // Spawn a bomb, make it explode instantly when params is set to 1 to emulate spawning an explosion
        EnBom* bomb = (EnBom*)Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_BOM, player->actor.world.pos.x,
                                   player->actor.world.pos.y + 30, player->actor.world.pos.z, 0, 0, 0, BOMB_BODY, true);

        if (bomb == NULL) {
            return GameInteractionEffectQueryResult::TemporarilyNotPossible;
        }

        // Make bomb explode immediately
        if (actorParams == 1) {
            bomb->timer = 2;
        }
        return GameInteractionEffectQueryResult::Possible;
    } else {
        // Generic spawn an actor at Link's position
        if (Actor_Spawn(&gPlayState->actorCtx, gPlayState, actorId, player->actor.world.pos.x,
                        player->actor.world.pos.y, player->actor.world.pos.z, 0, 0, 0, actorParams, 0) != NULL) {
            return GameInteractionEffectQueryResult::Possible;
        }
    }

    return GameInteractionEffectQueryResult::TemporarilyNotPossible;
    
}
