#pragma once
#include <Windows.h>

#include "../game_state.h"
#include "../Module.h"
#include "../ModuleCategory.h"
#include "../../config/CheckboxValue.h"
#include "../../config/FloatSliderValue.h"
#include "../../config/MenuSectionValue.h"
#include "../../config/ActionRowValue.h"
#include "../../config/IntSliderValue.h"
#include "../../config/IntSliderEditableValue.h"
#include "../../config/SelectValue.h"
#include "../../il2cpp/Il2CppObjectInstance.h"
#include "../../il2cpp/il2utils.h"
#include "../../il2cpp/unity.h"
#include "../../util/utils.h"
#include "../../gui/menu/menu.h"

static bool do_speed;
static int speed_value;

static Il2CppObject* fly_main_player = nullptr;

static size_t fly_freefall_time_offset = 0;
static size_t fly_is_grounded_offset = 0;

static bool should_survive;

inline void init_fly_hack_offsets()
{
    if (fly_freefall_time_offset != 0) return;

    const Il2CppImage* image = il2utils::resolve_image("Assembly-CSharp.dll");
    if (!image) return;

    const Il2CppClass* movement_context_class = il2utils::resolve_class(image, "EFT", "MovementContext");
    if (!movement_context_class) return;

    if (const FieldInfo* freefall_field = il2utils::resolve_field(movement_context_class, "FreefallTime"))
        fly_freefall_time_offset = freefall_field->offset;
    if (const FieldInfo* grounded_field = il2utils::resolve_field(movement_context_class, "_isGrounded"))
        fly_is_grounded_offset = grounded_field->offset;
}

inline void try_set_freefall_time(Il2CppObject* movement_ctx, const float value)
{
    if (!movement_ctx || fly_freefall_time_offset == 0) return;
    *reinterpret_cast<float*>(reinterpret_cast<uintptr_t>(movement_ctx) + fly_freefall_time_offset) = value;
}

inline void try_set_is_grounded(Il2CppObject* movement_ctx, const bool grounded)
{
    if (!movement_ctx || !movement_ctx->klass) return;

    const MethodInfo* set_grounded = il2utils::resolve_method(movement_ctx->klass, "set_IsGrounded", 1);
    if (set_grounded && set_grounded->methodPointer)
        reinterpret_cast<void(*)(Il2CppObject*, bool)>(set_grounded->methodPointer)(movement_ctx, grounded);
    else if (fly_is_grounded_offset != 0)
        *reinterpret_cast<bool*>(reinterpret_cast<uintptr_t>(movement_ctx) + fly_is_grounded_offset) = grounded;
}

inline Il2CppObject* try_get_player_bifacial_transform(Il2CppObject* movement_ctx)
{
    if (!movement_ctx || !movement_ctx->klass) return nullptr;
    const FieldInfo* player_transform_field = il2utils::resolve_field(movement_ctx->klass, "_playerTransform");
    if (!player_transform_field) return nullptr;
    return *reinterpret_cast<Il2CppObject**>(
        reinterpret_cast<uintptr_t>(movement_ctx) + player_transform_field->offset);
}

inline bool try_get_movement_position(Il2CppObject* movement_ctx, unity::vector3& out)
{
    if (!movement_ctx || !movement_ctx->klass) return false;

    const MethodInfo* get_pos = il2utils::resolve_method(movement_ctx->klass, "get_TransformPosition", 0);
    if (get_pos && get_pos->methodPointer)
    {
        out = reinterpret_cast<unity::vector3(*)(Il2CppObject*)>(get_pos->methodPointer)(movement_ctx);
        return true;
    }

    if (Il2CppObject* bifacial = try_get_player_bifacial_transform(movement_ctx))
    {
        const MethodInfo* get_position = il2utils::resolve_method(bifacial->klass, "get_position", 0);
        if (get_position && get_position->methodPointer)
        {
            out = reinterpret_cast<unity::vector3(*)(Il2CppObject*)>(get_position->methodPointer)(bifacial);
            return true;
        }
    }

    return false;
}

inline bool try_set_movement_position(Il2CppObject* movement_ctx, Il2CppObject* player, const unity::vector3& pos)
{
    if (movement_ctx && movement_ctx->klass)
    {
        const MethodInfo* set_pos = il2utils::resolve_method(movement_ctx->klass, "set_TransformPosition", 1);
        if (set_pos && set_pos->methodPointer)
        {
            reinterpret_cast<void(*)(Il2CppObject*, unity::vector3)>(set_pos->methodPointer)(movement_ctx, pos);
            return true;
        }
    }

    if (movement_ctx)
    {
        Il2CppObject* bifacial = try_get_player_bifacial_transform(movement_ctx);
        if (bifacial && bifacial->klass)
        {
            const MethodInfo* set_position = il2utils::resolve_method(bifacial->klass, "set_position", 1);
            if (set_position && set_position->methodPointer)
            {
                reinterpret_cast<void(*)(Il2CppObject*, unity::vector3)>(set_position->methodPointer)(bifacial, pos);
                return true;
            }
        }
    }

    if (player && player->klass)
    {
        const MethodInfo* set_position = il2utils::resolve_method(player->klass, "set_Position", 1);
        if (set_position && set_position->methodPointer)
        {
            reinterpret_cast<void(*)(Il2CppObject*, unity::vector3)>(set_position->methodPointer)(player, pos);
            return true;
        }
    }

    return false;
}

inline unity::vector3 try_get_movement_up(Il2CppObject* movement_ctx)
{
    if (!movement_ctx) return {0.f, 1.f, 0.f};

    if (Il2CppObject* bifacial = try_get_player_bifacial_transform(movement_ctx))
    {
        const MethodInfo* get_up = il2utils::resolve_method(bifacial->klass, "get_up", 0);
        if (get_up && get_up->methodPointer)
            return reinterpret_cast<unity::vector3(*)(Il2CppObject*)>(get_up->methodPointer)(bifacial);
    }

    return {0.f, 1.f, 0.f};
}

inline bool is_fly_key_held(const int key_code)
{
    if (key_code == 0) return false;
    if (key_code == VK_CONTROL)
        return (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0
            || (GetAsyncKeyState(VK_LCONTROL) & 0x8000) != 0
            || (GetAsyncKeyState(VK_RCONTROL) & 0x8000) != 0;
    return (GetAsyncKeyState(key_code) & 0x8000) != 0;
}

inline void fly_hack_tick(Il2CppObject* player, const int speed, const int up_key, const int down_key)
{
    init_fly_hack_offsets();

    if (!player || !player->klass) return;
    if (!unity::get_current_camera()) return;

    const MethodInfo* get_mc = il2utils::resolve_method(player->klass, "get_MovementContext", 0);
    if (!get_mc || !get_mc->methodPointer) return;

    Il2CppObject* movement_ctx = reinterpret_cast<Il2CppObject*(*)(Il2CppObject*)>(get_mc->methodPointer)(player);
    if (!movement_ctx) return;

    try_set_freefall_time(movement_ctx, 0.f);
    try_set_is_grounded(movement_ctx, true);

    const bool up_held = is_fly_key_held(up_key);
    const bool down_held = is_fly_key_held(down_key);

    if (!up_held && !down_held) return;

    if (up_held)
        try_set_freefall_time(movement_ctx, -0.4f);

    unity::vector3 pos;
    if (!try_get_movement_position(movement_ctx, pos)) return;

    constexpr float fly_step_per_frame = 0.012f;
    const float step = fly_step_per_frame * static_cast<float>(speed);
    const unity::vector3 up = try_get_movement_up(movement_ctx);
    const unity::vector3 delta = {up.x * step, up.y * step, up.z * step};

    if (up_held)
        pos = pos.add(delta);
    if (down_held)
        pos = {pos.x - delta.x, pos.y - delta.y, pos.z - delta.z};

    try_set_movement_position(movement_ctx, player, pos);
}

using fixed_update_sig = void(*)(Il2CppObject*);
static fixed_update_sig o_player_camera_controller_fixed_update = nullptr;

inline void hk_player_camera_controller_fixed_update(Il2CppObject* instance)
{
    if (do_speed)
    {
        Il2CppObjectInstance controller_instance(instance);
        Il2CppObjectInstance main_player(controller_instance.get_method<Il2CppObject*(*)(Il2CppObject*)>(
            "get_Player", 0)(instance));
        Il2CppObjectInstance character_controller(main_player.get_field<Il2CppObject*>("_characterController"));
        unity::vector3 velocity = character_controller.get_method<unity::vector3(*)(Il2CppObject*)>("get_velocity", 0)(
            character_controller.get_instance());
        velocity.x /= 100;
        velocity.y = 0;
        velocity.z /= 100;
        for (int i = 0; i < speed_value; ++i)
        {
            character_controller.get_method<int(*)(Il2CppObject*, unity::vector3, float)>("Move", 2)(
                character_controller.get_instance(), velocity, 100.0f);
        }
    }

    o_player_camera_controller_fixed_update(instance);
}

using local_game_stop_sig = void(*)(Il2CppObject*, Il2CppString*, int, Il2CppString*, float);

static local_game_stop_sig o_local_game_stop;

inline void hk_local_game_stop(Il2CppObject* instance, Il2CppString* profile_id, int exit_status,
                               Il2CppString* exit_name, float delay)
{
    if (should_survive) exit_status = 0; // Survived

    o_local_game_stop(instance, profile_id, exit_status, exit_name, delay);
}

class PlayerModule : Module
{
public:
    PlayerModule() : Module("Player", Exploit, "player_mod")
    {
    }

    CheckboxValue* god_mode = conf(new CheckboxValue(false, "god_mode_cb"));
    CheckboxValue* god_mode2 = conf(new CheckboxValue(false, "god_mode_negative_on_cb"));
    CheckboxValue* infinite_stamina = conf(new CheckboxValue(false, "infinite_stamina_cb"));
    CheckboxValue* no_hunger_thirst = conf(new CheckboxValue(false, "no_hunger_thirst_cb"));
    CheckboxValue* instant_search = conf(new CheckboxValue(false, "instant_search_cb"));

    MenuSectionValue* section_profile_mods_ = conf(new MenuSectionValue("profile_modifiers_sec"));
    static constexpr const char* EXP_TYPE_KEYS[] = {"exp_door_breached_sel", "exp_item_looting_sel", "exp_damage_sel", "exp_heal_sel", "exp_examine_sel", "exp_kill_base_sel", "exp_item_looting_2_sel"};
    static constexpr const char* EXP_TYPE_API[] = {"ExpDoorBreached", "ExpItemLooting", "ExpDamage", "ExpHeal", "ExpExamine", "ExpKillBase", "ExpItemLooting"};
    static constexpr const char* TRADER_KEYS[] = {"trader_lightkeeper_sel", "trader_ragman_sel", "trader_arena_manager_sel", "trader_peacekeeper_sel", "trader_skier_sel", "trader_prapor_sel", "trader_jaeger_sel", "trader_mechanic_sel", "trader_terapevt_sel", "trader_btr_sel"};
    static constexpr const char* TRADER_API[] = {"LIGHT_KEEPER_TRADER", "RAGMAN_TRADER", "ARENA_MANAGER_TRADER", "PEACEKEEPER", "SKIER", "PRAPOR_TRADER", "JAEGER_TRADER", "MECHANIC_TRADER", "TERAPEVT_TRADER", "BTR_TRADER"};
    static constexpr const char* SKILL_KEYS[] = {"skill_pistol_sel", "skill_revolver_sel", "skill_smg_sel", "skill_assault_sel", "skill_shotgun_sel", "skill_sniper_sel", "skill_lmg_sel", "skill_hmg_sel", "skill_launcher_sel", "skill_attached_launcher_sel", "skill_misc_sel", "skill_melee_sel", "skill_dmr_sel", "skill_throwing_sel", "skill_aim_drills_sel", "skill_recoil_control_sel", "skill_trouble_shooting_sel", "skill_perception_sel", "skill_attention_sel", "skill_intellect_sel", "skill_immunity_sel", "skill_charisma_sel", "skill_memory_sel", "skill_endurance_sel", "skill_strength_sel", "skill_vitality_sel", "skill_health_sel", "skill_metabolism_sel", "skill_stress_resistance_sel", "skill_sniping_sel", "skill_covert_movement_sel", "skill_prone_movement_sel", "skill_light_vests_sel", "skill_heavy_vests_sel", "skill_weapon_modding_sel", "skill_advanced_modding_sel", "skill_lockpicking_sel", "skill_surgery_sel", "skill_search_sel", "skill_weapon_treatment_sel", "skill_mag_drills_sel", "skill_freetrading_sel", "skill_barter_sel", "skill_crafting_sel", "skill_hideout_management_sel", "skill_bear_assaultoperations_sel", "skill_bear_authority_sel", "skill_bear_aksystems_sel", "skill_bear_heavycaliber_sel", "skill_bear_rawpower_sel", "skill_usec_arsystems_sel", "skill_usec_deepweaponmodding_sel", "skill_usec_longrangeoptics_sel", "skill_usec_negotiations_sel", "skill_usec_tactics_sel"};
    static constexpr const char* SKILL_API[] = {"Pistol", "Revolver", "SMG", "Assault", "Shotgun", "Sniper", "LMG", "HMG", "Launcher", "AttachedLauncher", "Misc", "Melee", "DMR", "Throwing", "AimDrills", "RecoilControl", "TroubleShooting", "Perception", "Attention", "Intellect", "Immunity", "Charisma", "Memory", "Endurance", "Strength", "Vitality", "Health", "Metabolism", "StressResistance", "Sniping", "CovertMovement", "ProneMovement", "LightVests", "HeavyVests", "WeaponModding", "AdvancedModding", "Lockpicking", "Surgery", "Search", "WeaponTreatment", "MagDrills", "Freetrading", "Barter", "Crafting", "HideoutManagement", "BearAssaultoperations", "BearAuthority", "BearAksystems", "BearHeavycaliber", "BearRawpower", "UsecArsystems", "UsecDeepweaponmodding", "UsecLongrangeoptics", "UsecNegotiations", "UsecTactics"};

    CheckboxValue* safety_switch = conf(new CheckboxValue(false, "safety_switch_cb", true));

    SelectValue* exp_type = conf(new SelectValue(0, EXP_TYPE_KEYS, std::size(EXP_TYPE_KEYS), "exp_type_sel"));
    IntSliderValue* exp_amount = conf(new IntSliderValue(10000, 0, 500000, "exp_amount_itm"));
    ActionRowValue* add_exp = conf(new ActionRowValue([&]
    {
        add_exp_impl();
    }, "add_exp_btn"));

    SelectValue* fence_rep_source = conf(new SelectValue(0, {
                                                             "boss_help_sel", "scav_help_sel", "scav_kill_sel", "boss_kill_sel",
                                                             "traitor_kill_sel", "exit_standing_sel"
                                                         }, "fence_rep_source_sel"));
    FloatSliderValue* fence_rep_amount = conf(new FloatSliderValue(0.1f, 0, 5, "fence_rep_amount_itm"));
    ActionRowValue* add_fence_rep = conf(new ActionRowValue([&]
    {
        add_fence_rep_impl();
    }, "add_fence_rep_btn"));

    SelectValue* trader_select = conf(new SelectValue(0, TRADER_KEYS, std::size(TRADER_KEYS), "trader_sel"));
    FloatSliderValue* trader_rep_amount = conf(new FloatSliderValue(0.1f, 0, 5, "trader_rep_amount_itm"));
    ActionRowValue* add_trader_rep = conf(new ActionRowValue([&]
    {
        add_trader_rep_impl();
    }, "add_trader_rep_btn"));

    SelectValue* skill_select = conf(new SelectValue(0, SKILL_KEYS, std::size(SKILL_KEYS), "skill_sel"));
    IntSliderValue* skill_level = conf(new IntSliderValue(51, 0, 51, "skill_level_itm"));
    ActionRowValue* set_skill = conf(new ActionRowValue([&]
    {
        set_skill_impl();
    }, "set_skill_btn"));

    MenuSectionValue* section_movement_ = conf(new MenuSectionValue("movement_sec"));
    CheckboxValue* speed_hack = conf(new CheckboxValue(false, "speed_hack_cb"));
    IntSliderEditableValue* speed = conf(new IntSliderEditableValue(2, 1, 10, "speed_itm"));

    CheckboxValue* fly_hack = conf(new CheckboxValue(false, "fly_hack_cb"));
    IntSliderEditableValue* fly_speed = conf(new IntSliderEditableValue(2, 1, 10, "fly_speed_itm"));
    int fly_up_key = VK_SPACE;
    int fly_down_key = VK_CONTROL;
    CheckboxValue* always_survive = conf(new CheckboxValue(false, "always_survive_cb"));

    bool queue_add_exp = false;
    bool queue_add_fence_rep = false;
    bool queue_add_trader_rep = false;
    bool queue_set_skill = false;

    void add_exp_impl()
    {
        if (!safety_switch->get_value() || !game_state::is_in_raid) return;
        queue_add_exp = true;
    }

    void add_fence_rep_impl()
    {
        if (!safety_switch->get_value() || !game_state::is_in_raid) return;
        queue_add_fence_rep = true;
    }

    void add_trader_rep_impl()
    {
        if (!safety_switch->get_value() || !game_state::is_in_raid) return;
        queue_add_trader_rep = true;
    }

    void set_skill_impl()
    {
        if (!safety_switch->get_value() || !game_state::is_in_raid) return;
        queue_set_skill = true;
    }

    void sync_profile_mod_controls_lock()
    {
        const bool unlocked = safety_switch->get_value();
        section_profile_mods_->set_section_active(unlocked);

        const auto dis = [](ConfigValue* v, const bool disabled) { v->set_disabled(disabled); };
        dis(exp_type, !unlocked);
        dis(exp_amount, !unlocked);
        dis(fence_rep_source, !unlocked);
        dis(fence_rep_amount, !unlocked);
        dis(trader_select, !unlocked);
        dis(trader_rep_amount, !unlocked);
        dis(skill_select, !unlocked);
        dis(skill_level, !unlocked);

        add_exp->set_disabled(!unlocked);
        add_fence_rep->set_disabled(!unlocked);
        add_trader_rep->set_disabled(!unlocked);
        set_skill->set_disabled(!unlocked);
    }

    void sync_movement_controls_lock()
    {
        const auto dis = [](ConfigValue* v, const bool disabled) { v->set_disabled(disabled); };
        dis(speed, !speed_hack->get_value());
        dis(fly_speed, !fly_hack->get_value());
    }

    void draw_overlay(ImDrawList* draw_list) override
    {
    }

    void sync_god_mode_state()
    {
        const bool gm1 = god_mode->get_value();
        const bool gm2 = god_mode2->get_value();

        if (gm1 && gm2)
            god_mode2->set_value(false);

        god_mode2->set_disabled(gm1);
        god_mode->set_disabled(gm2);
    }

    void application_update() override
    {
        sync_god_mode_state();
        sync_profile_mod_controls_lock();
        sync_movement_controls_lock();

        if (!game_state::is_in_raid)
        {
            fly_main_player = nullptr;
            return;
        }

        if (fly_hack->get_value() && fly_main_player)
            fly_hack_tick(fly_main_player, fly_speed->get_value(), fly_up_key, fly_down_key);
    }

    void gameworld_update(const Il2CppClass* game_world_class, Il2CppObjectInstance game_world_instance,
                          Il2CppObjectInstance main_player) override
    {
        sync_god_mode_state();
        sync_profile_mod_controls_lock();
        sync_movement_controls_lock();
        fly_main_player = main_player.get_instance();

        if (!safety_switch->get_value())
        {
            queue_add_exp = false;
            queue_add_fence_rep = false;
            queue_add_trader_rep = false;
            queue_set_skill = false;
        }

        const auto get_profile = main_player.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_Profile", 0);
        Il2CppObjectInstance main_profile(get_profile(main_player.get_instance()));

        if (infinite_stamina->get_value())
        {
            Il2CppObjectInstance physical(main_player.get_field<Il2CppObject*>("Physical"));

            Il2CppObjectInstance stamina(physical.get_field<Il2CppObject*>("Stamina"));
            stamina.set_field("Current", 100.0f);

            Il2CppObjectInstance stamina_hands(physical.get_field<Il2CppObject*>("HandsStamina"));
            stamina_hands.set_field("Current", 100.0f);

            Il2CppObjectInstance oxygen(physical.get_field<Il2CppObject*>("Oxygen"));
            oxygen.set_field("Current", 100.0f);
        }

        Il2CppObjectInstance health_controller(
            main_player.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_ActiveHealthController", 0)(
                main_player.get_instance()));
        static bool god_was_on = false;
        static float saved_fall_height = 0.f;
        static bool fall_saved = false;

        if (god_mode->get_value())
        {
            // cache the real fall-safe height once so we can restore it later
            if (!fall_saved)
            {
                saved_fall_height = health_controller.get_field<float>("_fallSafeHeight");
                fall_saved = true;
            }

            health_controller.get_method<void(*)(Il2CppObject*, float)>("SetDamageCoeff", 1)(
                health_controller.get_instance(), -1.0f);
            health_controller.get_method<void(*)(Il2CppObject*, int)>("RemoveNegativeEffects", 1)(
                health_controller.get_instance(), 7); // 7 = Common
            health_controller.get_method<void(*)(Il2CppObject*)>("RestoreFullHealth", 0)(
                health_controller.get_instance());
            health_controller.set_field("_fallSafeHeight", 99999999.0f);
            god_was_on = true;
        }
        else if (god_mode2->get_value())
        {
            // cache the real fall-safe height once so we can restore it later
            if (!fall_saved)
            {
                saved_fall_height = health_controller.get_field<float>("_fallSafeHeight");
                fall_saved = true;
            }

            health_controller.get_method<void(*)(Il2CppObject*, float)>("SetDamageCoeff", 1)(
                health_controller.get_instance(), 0.00001f);
            health_controller.set_field("_fallSafeHeight", 99999999.0f);
            god_was_on = true;
        }
        else if (god_was_on)
        {
            // god mode turned off -> restore normal damage + fall height (once)
            health_controller.get_method<void(*)(Il2CppObject*, float)>("SetDamageCoeff", 1)(
                health_controller.get_instance(), 1.0f);
            if (fall_saved)
                health_controller.set_field("_fallSafeHeight", saved_fall_height);
            god_was_on = false;
        }

        if (no_hunger_thirst->get_value())
        {
            health_controller.get_method<void(*)(Il2CppObject*, float)>("ChangeEnergy", 1)(
                health_controller.get_instance(), 100.0f);
            health_controller.get_method<void(*)(Il2CppObject*, float)>("ChangeHydration", 1)(
                health_controller.get_instance(), 100.0f);
        }

        Il2CppObjectInstance skill_manager(
            main_player.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_Skills", 0)(main_player.get_instance()));
        if (instant_search->get_value())
        {
            Il2CppObjectInstance attention_elite_lucky_search(
                skill_manager.get_field<Il2CppObject*>("AttentionEliteLuckySearch"));

            attention_elite_lucky_search.set_field("Value", 100000.0f);
        }

        if (queue_add_exp && safety_switch->get_value())
        {
            queue_add_exp = false;
            Il2CppObjectInstance eft_stats(
                main_profile.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_EftStats", 0)(
                    main_profile.get_instance()));

            Il2CppObjectInstance session_counters(eft_stats.get_field<Il2CppObject*>("SessionCounters"));

            const Il2CppImage* assembly_csharp = il2utils::resolve_image("Assembly-CSharp.dll");
            const Il2CppClass* predefined_counters = il2utils::resolve_class(assembly_csharp, "EFT.Counters",
                                                                             "PredefinedCounters");
            const auto reason = il2utils::get_static_field<Il2CppObject
                *>(predefined_counters, EXP_TYPE_API[exp_type->get_value()]);

            session_counters.get_method<void(*)(Il2CppObject*, int, Il2CppObject*)>("AddInt", 2)(
                session_counters.get_instance(), exp_amount->get_value(), reason);

            if (globals::verbose) logger::info("Added EXP");
        }

        if (queue_add_fence_rep && safety_switch->get_value())
        {
            queue_add_fence_rep = false;
            Il2CppObjectInstance loyalty_data(
                main_player.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_Loyalty", 0)(
                    main_player.get_instance()));

            loyalty_data.get_method<void(*)(Il2CppObject*, double, int)>("KillAsSavage", 2)(
                loyalty_data.get_instance(), fence_rep_amount->get_value(), fence_rep_source->get_value());

            if (globals::verbose) logger::info("Added Fence Rep");
        }

        if (queue_add_trader_rep && safety_switch->get_value())
        {
            queue_add_trader_rep = false;
            std::string trader_id_key = std::string(TRADER_API[trader_select->get_value()]) + "_ID";

            logger::info("Resolving Profile class");
            const Il2CppClass* profile_class = il2utils::resolve_class(game_world_class->image, "EFT", "Profile");
            logger::info(std::string("Getting TraderInfo class, got profile (is_null=") + (profile_class == nullptr ? "true" : "false") + ")");
            const Il2CppClass* trader_info_class = il2utils::resolve_class_nested(profile_class, "TraderInfo");
            if (!trader_info_class) { logger::warn("trader_info_class null"); return; }
            logger::info("Resolve field " + trader_id_key);
            FieldInfo* id_field = il2utils::resolve_field(trader_info_class, trader_id_key.c_str());
            if (!id_field) { logger::warn("id_field null for " + trader_id_key); return; }
            if (!id_field->type) { logger::warn("id_field->type null for " + trader_id_key); return; }

            Il2CppObject* trader_id_obj = nullptr;
            il2cpp::il2cpp_field_static_get_value(id_field, &trader_id_obj);
            Il2CppString* trader_id = reinterpret_cast<Il2CppString*>(trader_id_obj);

            const MethodInfo* try_get_method = il2utils::resolve_method(profile_class, "TryGetTraderInfo", 2);
            if (!try_get_method || !try_get_method->methodPointer)
            {
                logger::warn("TryGetTraderInfo method not found");
                return;
            }
            const auto try_get_trader_info = static_cast<bool(*)(Il2CppObject*, Il2CppString*, Il2CppObject**)>(try_get_method->methodPointer);

            logger::info("Finding trader info...");
            Il2CppObject* trader_info = nullptr;
            if (try_get_trader_info(main_profile.get_instance(), trader_id, &trader_info) && trader_info)
            {
                const FieldInfo* standing_field = il2utils::resolve_field(trader_info->klass, "<Standing>k__BackingField");
                if (standing_field)
                {
                    auto* standing_ptr = reinterpret_cast<double*>(
                        reinterpret_cast<uintptr_t>(trader_info) + standing_field->offset);
                    *standing_ptr += trader_rep_amount->get_value();
                }
            }

            logger::info("Added Trader Rep");
        }

        if (queue_set_skill && safety_switch->get_value())
        {
            queue_set_skill = false;

            Il2CppObjectInstance skill_controller(
                main_player.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_Skills", 0)(main_player.get_instance()));

            if (const auto skill_ptr = skill_controller.get_field<Il2CppObject*>(SKILL_API[skill_select->get_value()]))
            {
                Il2CppObjectInstance skill_instance(skill_ptr);

                skill_instance.get_method<void(*)(Il2CppObject*, int)>("SetLevel", 1)(
                    skill_instance.get_instance(), skill_level->get_value());

                if (globals::verbose)
                    logger::info(
                        "Set Skill: " + std::string(SKILL_API[skill_select->get_value()]) + " Level: " +
                        std::to_string(skill_level->get_value()));
            }
            else
            {
                if (globals::verbose) logger::warn("Skill not found: " + std::string(SKILL_API[skill_select->get_value()]));
            }
        }

        do_speed = speed_hack->get_value();
        speed_value = speed->get_value();

        should_survive = always_survive->get_value();
    }

    void init() override
    {
        const Il2CppImage* image = il2utils::resolve_image("Assembly-CSharp.dll");
        init_fly_hack_offsets();

        // need a fixed update here so speed hack is consistent, this is a good one i found with access to player
        const Il2CppClass* player_camera_controller_class = il2utils::resolve_class(
            image, "EFT.CameraControl", "PlayerCameraController");
        il2utils::hook_method(player_camera_controller_class, "FixedUpdate", 0,
                              hk_player_camera_controller_fixed_update,
                              &o_player_camera_controller_fixed_update);

        // local game stop
        const Il2CppClass* local_game = il2utils::resolve_class(image, "EFT", "LocalGame");
        il2utils::hook_method(local_game, "Stop", 4, hk_local_game_stop, &o_local_game_stop);
    }
};
