make this void RLBotBot::RenderBallPredictions(const RLGSC::GameState& gs) {
    rlbot::ScopedRenderer renderer("ball_prediction_renderer");

    // Get the ball prediction from RLBot
    rlbot::BallPrediction ballPrediction = GetBallPrediction();

    // Render 8 prediction points
    const int numPoints = 8;
    const int sliceStep = ballPrediction.slices().size() / numPoints;

    std::vector<rlbot::flat::Vector3> points;
    points.push_back({ gs.ball.pos.x, gs.ball.pos.y, gs.ball.pos.z }); // Start with current ball position

    for (int i = 0; i < numPoints; i++) {
        int sliceIndex = (i + 1) * sliceStep;
        if (sliceIndex < ballPrediction.slices().size()) {
            const auto& slice = ballPrediction.slices()[sliceIndex];
            rlbot::flat::Vector3 point{
                slice->physics()->location()->x(),
                slice->physics()->location()->y(),
                slice->physics()->location()->z()
            };
            points.push_back(point);
        }
    }

    // Render lines connecting the prediction points
    rlbot::Color predictionColor = { 255, 0, 0, 255 }; // Red color
    for (int i = 0; i < points.size() - 1; i++) {
        renderer.DrawLine3D(predictionColor, points[i], points[i + 1]);
    }
} not render the rlbots ballpred but rocketsims #include "RLBotClient.h"
#include <rlbot/platform.h>
#include <rlbot/botmanager.h>
#include <iostream>
#include "rlbot/color.h"
#include <ctime>
#include "rlbot/interface.h"
#include "rlbot/rlbot_generated.h"
#include "rlbot/scopedrenderer.h"
#include "rlbot/statesetting.h"
#include <string>
#include <sstream>
#include <vector>
#include <array>
//#include "C:\test bot\RLGymPPO_CPP-main\EasyXInput-main\Source\EasyXInput.h"
//#include <windows.h>  // For Windows API functions



using namespace RLGSC;
using namespace RLGPC;

RLBotParams g_RLBotParams = {};

rlbot::Bot* BotFactory(int index, int team, std::string name) {
    return new RLBotBot(index, team, name, g_RLBotParams);
}



RLBotBot::RLBotBot(int _index, int _team, std::string _name, const RLBotParams& params)
    : rlbot::Bot(_index, _team, std::move(_name)), params(params), policyInferUnit(nullptr),
    predictionArena(nullptr), ballPredTracker(nullptr) {

    RocketSim::Init("./collision_meshes");

    rewardFunction = new AirDribbleReward();
    //rewardFunction = new FlipResetReward();// Replace with your actual reward function

   

    std::cout << "Creating RLBot bot: index " << _index << ", name: " << _name << "...\n";
    std::cout << " > Loading policy from " << params.policyPath << "...\n";

    policyInferUnit = new PolicyInferUnit(params.obsBuilder, params.actionParser, params.policyPath, params.obsSize, params.policyLayerSizes, false);
    InitializeBallPrediction();

    std::cout << " > Done!\n";
}

RLBotBot::~RLBotBot() {
    delete policyInferUnit;
    delete ballPredTracker;
    delete predictionArena;
    delete rewardFunction;
}

void RLBotBot::InitializeBallPrediction() {
    RocketSim::ArenaConfig arenaConfig;
    predictionArena = RocketSim::Arena::Create(RocketSim::GameMode::SOCCAR, arenaConfig, 120.0f);

    // Add two cars to the prediction arena
    predictionArena->AddCar(RocketSim::Team::BLUE);  // Local player's car
    predictionArena->AddCar(RocketSim::Team::ORANGE);  // Opponent's car

    ballPredTracker = new RocketSim::BallPredTracker(predictionArena, params.NUM_PRED_TICKS);
}

Vec ToVec(const rlbot::flat::Vector3* rlbotVec) {
    return Vec(rlbotVec->x(), rlbotVec->y(), rlbotVec->z());
}

PhysObj ToPhysObj(const rlbot::flat::Physics* phys) {
    PhysObj obj = {};
    obj.pos = ToVec(phys->location());

    Angle ang = Angle(phys->rotation()->yaw(), phys->rotation()->pitch(), phys->rotation()->roll());
    obj.rotMat = ang.ToRotMat();

    obj.vel = ToVec(phys->velocity());
    obj.angVel = ToVec(phys->angularVelocity());

    return obj;
}

PlayerData ToPlayer(const rlbot::flat::PlayerInfo* playerInfo, RocketSim::Car* rocketSimPlayer, uint64_t ticks, int tickSkip, const rlbot::GameTickPacket& gameTickPacket) {
    PlayerData pd = {};

    pd.carId = playerInfo->spawnId();
    pd.team = (Team)playerInfo->team();
    pd.phys = ToPhysObj(playerInfo->physics());
    pd.physInv = pd.phys.Invert();
    pd.boostFraction = playerInfo->boost() / 100.f;

    // Initialize new fields
    pd.matchGoals = playerInfo->scoreInfo()->goals();
    pd.matchSaves = playerInfo->scoreInfo()->saves();
    pd.matchAssists = playerInfo->scoreInfo()->assists();
    pd.matchShots = playerInfo->scoreInfo()->shots();
    pd.matchShotPasses = 0; // This information might not be available from rlbot
    pd.matchBumps = 0; // This information might not be available from rlbot
    pd.matchDemos = 0; // This information might not be available from rlbot
    pd.boostPickups = 0; // This information might not be available from rlbot

    if (rocketSimPlayer != nullptr) {
        const auto& rocketSimState = rocketSimPlayer->GetState();
        // Populate CarState fields
        pd.carState.updateCounter = rocketSimState.updateCounter;
        pd.carState.isOnGround = rocketSimState.isOnGround;
        memcpy(pd.carState.wheelsWithContact, rocketSimState.wheelsWithContact, sizeof(pd.carState.wheelsWithContact));
        pd.carState.hasJumped = rocketSimState.hasJumped;
        pd.carState.hasDoubleJumped = rocketSimState.hasDoubleJumped;
        pd.carState.hasFlipped = rocketSimState.hasFlipped;
        pd.carState.flipRelTorque = rocketSimState.flipRelTorque;
        pd.carState.jumpTime = rocketSimState.jumpTime;
        pd.carState.flipTime = rocketSimState.flipTime;
        pd.carState.isFlipping = rocketSimState.isFlipping;
        pd.carState.isJumping = rocketSimState.isJumping;
        pd.carState.airTime = rocketSimState.airTime;
        pd.carState.airTimeSinceJump = rocketSimState.airTimeSinceJump;
        pd.carState.boost = playerInfo->boost();
        pd.carState.timeSpentBoosting = rocketSimState.timeSpentBoosting;
        pd.carState.isSupersonic = rocketSimState.isSupersonic;
        pd.carState.supersonicTime = rocketSimState.supersonicTime;
        pd.carState.handbrakeVal = rocketSimState.handbrakeVal;
        pd.carState.isAutoFlipping = rocketSimState.isAutoFlipping;
        pd.carState.autoFlipTimer = rocketSimState.autoFlipTimer;
        pd.carState.autoFlipTorqueScale = rocketSimState.autoFlipTorqueScale;
        pd.carState.worldContact = rocketSimState.worldContact;
        pd.carState.carContact = rocketSimState.carContact;
        pd.carState.isDemoed = rocketSimState.isDemoed;
        pd.carState.demoRespawnTimer = rocketSimState.demoRespawnTimer;
        pd.carState.ballHitInfo = rocketSimState.ballHitInfo;
        pd.carState.lastControls = rocketSimState.lastControls;

       

        // Initialize ball_touched to false
        bool ballTouchedStep;

    }
    else {
        // Fallback to using playerInfo for some fields
        //pd.carState.isOnGround = playerInfo->hasWheelContact();
        pd.carState.hasJumped = playerInfo->jumped();
        pd.carState.hasDoubleJumped = playerInfo->doubleJumped();
        pd.carState.isDemoed = playerInfo->isDemolished();
        pd.carState.boost = playerInfo->boost();
    }

    pd.hasJump = !pd.carState.hasJumped;
    pd.hasFlip = !pd.carState.hasDoubleJumped;

    

    return pd;
}


GameState ToGameState(rlbot::GameTickPacket& gameTickPacket, int ticksElapsed, int tickSkip, const std::vector<RocketSim::Car*>& rocketSimCars, uint64_t tickCount) {
    GameState gs = {};

    const rlbot::flat::Touch* latestTouch = gameTickPacket->ball()->latestTouch();
    float currentTime = gameTickPacket->gameInfo()->secondsElapsed();

    auto players = gameTickPacket->players();
    for (int i = 0; i < players->size(); i++) {
        RocketSim::Car* rocketSimCar = (i < rocketSimCars.size()) ? rocketSimCars[i] : nullptr;
        PlayerData player = ToPlayer(players->Get(i), rocketSimCar, tickCount, tickSkip, gameTickPacket);

        if (latestTouch != nullptr &&
            latestTouch->gameSeconds() > 0 &&
            i == latestTouch->playerIndex() &&
            currentTime - latestTouch->gameSeconds() < tickSkip / 120.0f) {
            player.ballTouchedStep = true;
        }
        else {
            player.ballTouchedStep = false;
        }

        gs.players.push_back(player);
    }

    gs.ball = ToPhysObj(gameTickPacket->ball()->physics());
    gs.ballInv = gs.ball.Invert();

    auto boostPadStates = gameTickPacket->boostPadStates();
    if (boostPadStates->size() != CommonValues::BOOST_LOCATIONS_AMOUNT) {
        if (rand() % 20 == 0) {
            RG_LOG(
                "RLBotClient ToGameState(): Bad boost pad amount, expected " << CommonValues::BOOST_LOCATIONS_AMOUNT << " but got " << boostPadStates->size()
            );
        }
        std::fill(gs.boostPads.begin(), gs.boostPads.end(), 1);
    }
    else {
        for (int i = 0; i < CommonValues::BOOST_LOCATIONS_AMOUNT; i++) {
            gs.boostPads[i] = boostPadStates->Get(i)->isActive();
            gs.boostPadsInv[CommonValues::BOOST_LOCATIONS_AMOUNT - i - 1] = gs.boostPads[i];
            gs.boostPadTimers[i] = boostPadStates->Get(i)->timer();
            gs.boostPadTimersInv[CommonValues::BOOST_LOCATIONS_AMOUNT - i - 1] = gs.boostPadTimers[i];
        }
    }

    return gs;
}

void RLBotBot::RenderReward(const RLGSC::GameState& gs) {
    rlbot::ScopedRenderer renderer("reward_renderer");
    float textScale = 1.0f;
    float xOffset = 20.0f;
    float yOffset = 200.0f;
    float lineHeight = 20.0f;
    float boxWidth = 200.0f;
    float columnWidth = 100.0f;

    // Update min and max rewards
    if (lastReward < minReward) minReward = lastReward;
    if (lastReward > maxReward) maxReward = lastReward;

    // Update average reward
    totalRewards += lastReward;
    numRewards++;
    float avgReward = totalRewards / numRewards;

    // Helper function to format float to string with 2 decimal places
    auto formatFloat = [](float value) {
        std::ostringstream oss;
        oss << std::fixed << std::setprecision(2) << value;
        return oss.str();
        };

    // Render table header
    renderer.DrawString2D("Rewards", rlbot::Color::white, rlbot::flat::Vector3{ xOffset, yOffset, 0 }, textScale, textScale);

    // Render table rows
    std::vector<std::pair<std::string, float>> rows = {
        {"Curr", lastReward},
        {"Min", minReward},
        {"Max", maxReward},
        {"Avg", avgReward}
    };

    for (size_t i = 0; i < rows.size(); i++) {
        float yPos = yOffset + 25 + i * lineHeight;
        std::string rowText = rows[i].first + ": " + formatFloat(rows[i].second);
        renderer.DrawString2D(rowText, rlbot::Color::white, rlbot::flat::Vector3{ xOffset, yPos, 0 }, textScale, textScale);
    }

    // Render 3D text above the player
    rlbot::flat::Vector3 localPlayerLocation = {
        gs.players[index].phys.pos.x,
        gs.players[index].phys.pos.y,
        gs.players[index].phys.pos.z + 150
    };
    std::string currentRewardText = "Reward: " + formatFloat(lastReward);
    renderer.DrawString3D(currentRewardText, rlbot::Color::white, localPlayerLocation, textScale, textScale);
}

RLGSC::Action ConvertToAction(const rlbot::Controller& controller) {
    RLGSC::Action action;
    action.throttle = controller.throttle;
    action.steer = controller.steer;
    action.pitch = controller.pitch;
    action.yaw = controller.yaw;
    action.roll = controller.roll;
    action.jump = controller.jump;
    action.boost = controller.boost;
    action.handbrake = controller.handbrake;
    return action;
}

void RLBotBot::RenderBallPredictions(const RLGSC::GameState& gs) {
    rlbot::ScopedRenderer renderer("ball_prediction_renderer");

    // Get the ball prediction from RLBot
    rlbot::BallPrediction ballPrediction = GetBallPrediction();

    // Render 8 prediction points
    const int numPoints = 8;
    const int sliceStep = ballPrediction.slices().size() / numPoints;

    std::vector<rlbot::flat::Vector3> points;
    points.push_back({ gs.ball.pos.x, gs.ball.pos.y, gs.ball.pos.z }); // Start with current ball position

    for (int i = 0; i < numPoints; i++) {
        int sliceIndex = (i + 1) * sliceStep;
        if (sliceIndex < ballPrediction.slices().size()) {
            const auto& slice = ballPrediction.slices()[sliceIndex];
            rlbot::flat::Vector3 point{
                slice->physics()->location()->x(),
                slice->physics()->location()->y(),
                slice->physics()->location()->z()
            };
            points.push_back(point);
        }
    }

    // Render lines connecting the prediction points
    rlbot::Color predictionColor = { 255, 0, 0, 255 }; // Red color
    for (int i = 0; i < points.size() - 1; i++) {
        renderer.DrawLine3D(predictionColor, points[i], points[i + 1]);
    }
}

rlbot::Controller RLBotBot::GetOutput(rlbot::GameTickPacket gameTickPacket) {
    float curTime = gameTickPacket->gameInfo()->secondsElapsed();
    float deltaTime = curTime - prevTime;
    prevTime = curTime;

    int ticksElapsed = roundf(deltaTime * 120);
    ticks += ticksElapsed;



    std::vector<RocketSim::Car*> rocketSimCars;
    if (predictionArena != nullptr) {
        auto cars = predictionArena->GetCars();
        rocketSimCars.assign(cars.begin(), cars.end());
    }

    //GameState gs = ToGameState(gameTickPacket, ticksElapsed, params.tickSkip, rocketSimCars);
    
   

    GameState gs = ToGameState(gameTickPacket, ticksElapsed, params.tickSkip, rocketSimCars, ticks);
    auto& localPlayer = gs.players[index];
    lastReward = rewardFunction->GetReward(localPlayer, gs, controls);

    RenderReward(gs);

    RenderBallPredictions(gs);

    if (predictionArena != nullptr) {
        RocketSim::BallState ballState = predictionArena->ball->GetState();
        ballState.pos = RocketSim::Vec(gs.ball.pos.x, gs.ball.pos.y, gs.ball.pos.z);
        ballState.vel = RocketSim::Vec(gs.ball.vel.x, gs.ball.vel.y, gs.ball.vel.z);
        ballState.angVel = RocketSim::Vec(gs.ball.angVel.x, gs.ball.angVel.y, gs.ball.angVel.z);
        predictionArena->ball->SetState(ballState);

        auto cars = predictionArena->GetCars();
        if (cars.size() >= 2) {
            auto localCar = *cars.begin();
            auto opponentCar = *(std::next(cars.begin()));

            RocketSim::CarState localCarState = localCar->GetState();
            localCarState.pos = RocketSim::Vec(localPlayer.phys.pos.x, localPlayer.phys.pos.y, localPlayer.phys.pos.z);
            localCarState.vel = RocketSim::Vec(localPlayer.phys.vel.x, localPlayer.phys.vel.y, localPlayer.phys.vel.z);
            localCarState.angVel = RocketSim::Vec(localPlayer.phys.angVel.x, localPlayer.phys.angVel.y, localPlayer.phys.angVel.z);
            localCarState.rotMat = localPlayer.phys.rotMat;
            localCar->SetState(localCarState);

            RocketSim::CarControls localControls;
            localControls.throttle = controls.throttle;
            localControls.steer = controls.steer;
            localControls.pitch = controls.pitch;
            localControls.yaw = controls.yaw;
            localControls.roll = controls.roll;
            localControls.boost = controls.boost;
            localControls.jump = controls.jump;
            localControls.handbrake = controls.handbrake;
            localCar->controls = localControls;

            const PlayerData* opponent = nullptr;
            for (const auto& player : gs.players) {
                if (player.team != localPlayer.team) {
                    opponent = &player;
                    break;
                }
            }

            if (opponent) {
                RocketSim::CarState opponentCarState = opponentCar->GetState();
                opponentCarState.pos = RocketSim::Vec(opponent->phys.pos.x, opponent->phys.pos.y, opponent->phys.pos.z);
                opponentCarState.vel = RocketSim::Vec(opponent->phys.vel.x, opponent->phys.vel.y, opponent->phys.vel.z);
                opponentCarState.angVel = RocketSim::Vec(opponent->phys.angVel.x, opponent->phys.angVel.y, opponent->phys.angVel.z);
                opponentCarState.rotMat = opponent->phys.rotMat;
                opponentCarState.hasJumped = opponent->carState.hasJumped;
                opponentCarState.hasDoubleJumped = opponent->carState.hasDoubleJumped;
                opponentCar->SetState(opponentCarState);

                predictionArena->Step(1);

                // Debug output removed for brevity
            }
            else {
                std::cout << "Opponent not found in game state" << std::endl;
            }
        }
        else {
            std::cout << "Not enough cars in prediction arena" << std::endl;
        }
    }
    else {
        std::cout << "Prediction arena is null" << std::endl;
    }

    gs.lastArena = predictionArena;

    if (g_RLBotParams.userInputMode) {
        controls = ConvertToAction(g_RLBotParams.userControls);
    }
    else {
        if (updateAction) {
            updateAction = false;
            action = policyInferUnit->InferPolicySingle(localPlayer, gs, controls, true);
        }

        if (ticks >= params.tickSkip || ticks == -1) {
            controls = action;
            ticks = 0;
            updateAction = true;
        }
    }

    auto rc = rlbot::Controller();
    rc.throttle = controls.throttle;
    rc.steer = controls.steer;
    rc.pitch = controls.pitch;
    rc.yaw = controls.yaw;
    rc.roll = controls.roll;
    rc.boost = controls.boost;
    rc.jump = controls.jump;
    rc.handbrake = controls.handbrake;

    if (!g_RLBotParams.userInputMode) {
        if ((localPlayer.team == Team::BLUE && localPlayer.phys.pos.x < 0.0f) ||
            (localPlayer.team == Team::ORANGE && localPlayer.phys.pos.x > 0.0f)) {
            rc.steer *= -1.0f;
            rc.yaw *= -1.0f;
            rc.roll *= -1.0f;
        }
    }

    return rc;
}