/*
Plugin-SDK (Grand Theft Auto) source file
Authors: GTA Community. See more here
https://github.com/DK22Pac/plugin-sdk
Do not delete this comment block. Respect others' work!

This plugin based by DK22Pac's GPS, modified SA:MP version by janglapuk (for SA:MP)
https://github.com/janglapuk/SAMP-GPS
*/

#include "plugin.h"
#include "RenderWare.h"
#include "common.h"
#include "CMenuManager.h"
#include "CRadar.h"
#include "CWorld.h"
#include "RenderWare.h"
#include "CFont.h"
#include "d3d9.h"

#include <Psapi.h>

#pragma comment( lib, "psapi.lib" )

#define MAX_NODE_POINTS 2000
#define GPS_LINE_WIDTH  4.0f
#define GPS_LINE_R  180
#define GPS_LINE_G  24
#define GPS_LINE_B  24
#define GPS_LINE_A  255
#define MAX_TARGET_DISTANCE 20.0f

#define E_ADDR_GAMEPROCESS	0x53E981

using namespace plugin;

#pragma pack(push, 1)
typedef struct stOpcodeRelCall
{
	BYTE bOpcode;
	DWORD dwRelAddr;
} OpcodeRelCall;
#pragma pack(pop)

class GPS {
private:
	HANDLE hThread = NULL;

public:
	GPS() {
		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)GPS::init, (LPVOID)this, 0, (LPDWORD)NULL);
	}

	~GPS() {
		// Check if thread still running on process
		if (hThread != NULL)
			TerminateThread(hThread, 0);
	}

	static LPVOID WINAPI init(LPVOID *lpParam) {
		MODULEINFO miSampDll;
		DWORD dwSampDllBaseAddr, dwSampDllEndAddr, dwCallAddr;

		GPS *sender = (GPS *)lpParam;

		stOpcodeRelCall *fnGameProc = (stOpcodeRelCall *)E_ADDR_GAMEPROCESS;

		// Check if E_ADDR_GAMEPROCESS opcode is a relative call (0xE8)
		while (fnGameProc->bOpcode != 0xE8)
			Sleep(100);

		while (true) {
			Sleep(100);

			// Get samp.dll module information to get base address and end address
			if (!GetModuleInformation(GetCurrentProcess(), GetModuleHandle("samp.dll"), &miSampDll, sizeof(MODULEINFO))) {
				continue;
			}

			// Some stupid calculation
			dwSampDllBaseAddr = (DWORD)miSampDll.lpBaseOfDll;
			dwSampDllEndAddr = dwSampDllBaseAddr + miSampDll.SizeOfImage;

			// Calculate destination address by offset and relative call opcode size
			dwCallAddr = fnGameProc->dwRelAddr + E_ADDR_GAMEPROCESS + 5;

			// Check if dwCallAddr is a samp.dll's hook address, 
			// to make sure this plugin hook (Events::gameProcessEvent) not replaced by samp.dll
			if (dwCallAddr >= dwSampDllBaseAddr && dwCallAddr <= dwSampDllEndAddr)
				break;
		}

		// Just wait a few secs for the game loaded fully to avoid any conflicts and crashes
		// I don't know what the elegant way is :)
		while (!FindPlayerPed(0))
			Sleep(5000);

		// Run the plugin
		sender->run();

		// Reset the thread handle
		sender->hThread = NULL;

		return NULL;
	}

	void run() {
		static bool gpsShown;
		static float gpsDistance;
		static CNodeAddress resultNodes[MAX_NODE_POINTS];
		static CVector2D nodePoints[MAX_NODE_POINTS];
		static RwIm2DVertex lineVerts[MAX_NODE_POINTS * 4];
		
		Events::gameProcessEvent += []() {
			if (FrontEndMenuManager.m_nTargetBlipIndex
				&& CRadar::ms_RadarTrace[LOWORD(FrontEndMenuManager.m_nTargetBlipIndex)].m_nCounter == HIWORD(FrontEndMenuManager.m_nTargetBlipIndex)
				&& CRadar::ms_RadarTrace[LOWORD(FrontEndMenuManager.m_nTargetBlipIndex)].m_nBlipDisplay
				&& FindPlayerPed()
				&& DistanceBetweenPoints(CVector2D(FindPlayerCoors(0)),
					CVector2D(CRadar::ms_RadarTrace[LOWORD(FrontEndMenuManager.m_nTargetBlipIndex)].m_vecPos)) < MAX_TARGET_DISTANCE)
			{
				CRadar::ClearBlip(FrontEndMenuManager.m_nTargetBlipIndex);
				FrontEndMenuManager.m_nTargetBlipIndex = 0;
			}
		};

		Events::drawRadarOverlayEvent += []() {
			gpsShown = false;
			CPed *playa = FindPlayerPed(0);
			if (playa
				&& playa->m_pVehicle
				&& playa->m_nPedFlags.bInVehicle
				&& playa->m_pVehicle->m_nVehicleSubClass != VEHICLE_PLANE
				&& playa->m_pVehicle->m_nVehicleSubClass != VEHICLE_HELI
				&& playa->m_pVehicle->m_nVehicleSubClass != VEHICLE_BMX
				&& FrontEndMenuManager.m_nTargetBlipIndex
				&& CRadar::ms_RadarTrace[LOWORD(FrontEndMenuManager.m_nTargetBlipIndex)].m_nCounter == HIWORD(FrontEndMenuManager.m_nTargetBlipIndex)
				&& CRadar::ms_RadarTrace[LOWORD(FrontEndMenuManager.m_nTargetBlipIndex)].m_nBlipDisplay)
			{
				CVector destPosn = CRadar::ms_RadarTrace[LOWORD(FrontEndMenuManager.m_nTargetBlipIndex)].m_vecPos;
				destPosn.z = CWorld::FindGroundZForCoord(destPosn.x, destPosn.y);

				short nodesCount = 0;

				ThePaths.DoPathSearch(0, FindPlayerCoors(0), CNodeAddress(), destPosn, resultNodes, &nodesCount, MAX_NODE_POINTS, &gpsDistance,
					999999.0f, NULL, 999999.0f, false, CNodeAddress(), false, playa->m_pVehicle->m_nVehicleSubClass == VEHICLE_BOAT);

				if (nodesCount > 0) {
					for (short i = 0; i < nodesCount; i++) {
						CVector nodePosn = ThePaths.GetPathNode(resultNodes[i])->GetNodeCoors();
						CVector2D tmpPoint;
						CRadar::TransformRealWorldPointToRadarSpace(tmpPoint, CVector2D(nodePosn.x, nodePosn.y));
						if (!FrontEndMenuManager.m_bDrawRadarOrMap)
							CRadar::TransformRadarPointToScreenSpace(nodePoints[i], tmpPoint);
						else {
							CRadar::LimitRadarPoint(tmpPoint);
							CRadar::TransformRadarPointToScreenSpace(nodePoints[i], tmpPoint);
							nodePoints[i].x *= static_cast<float>(RsGlobal.maximumWidth) / 640.0f;
							nodePoints[i].y *= static_cast<float>(RsGlobal.maximumHeight) / 448.0f;
							CRadar::LimitToMap(&nodePoints[i].x, &nodePoints[i].y);
						}
					}

					if (!FrontEndMenuManager.m_bDrawRadarOrMap
						&& reinterpret_cast<D3DCAPS9 const*>(RwD3D9GetCaps())->RasterCaps & D3DPRASTERCAPS_SCISSORTEST)
					{
						RECT rect;
						CVector2D posn;
						CRadar::TransformRadarPointToScreenSpace(posn, CVector2D(-1.0f, -1.0f));
						rect.left = static_cast<LONG>(posn.x + 2.0f);
                                                rect.bottom = static_cast<LONG>(posn.y - 2.0f);
						CRadar::TransformRadarPointToScreenSpace(posn, CVector2D(1.0f, 1.0f));
						rect.right = posn.x - 2.0f; rect.top = posn.y + 2.0f;
						GetD3DDevice()->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
						GetD3DDevice()->SetScissorRect(&rect);
					}

					RwRenderStateSet(rwRENDERSTATETEXTURERASTER, NULL);

					unsigned int vertIndex = 0;
					for (short i = 0; i < (nodesCount - 1); i++) {
						CVector2D point[4], shift[2];
						CVector2D dir = nodePoints[i + 1] - nodePoints[i];
						float angle = atan2(dir.y, dir.x);
						if (!FrontEndMenuManager.m_bDrawRadarOrMap) {
							shift[0].x = cosf(angle - 1.5707963f) * GPS_LINE_WIDTH;
							shift[0].y = sinf(angle - 1.5707963f) * GPS_LINE_WIDTH;
							shift[1].x = cosf(angle + 1.5707963f) * GPS_LINE_WIDTH;
							shift[1].y = sinf(angle + 1.5707963f) * GPS_LINE_WIDTH;
						}
						else {
							float mp = FrontEndMenuManager.m_fMapZoom - 140.0f;
							if (mp < 140.0f)
								mp = 140.0f;
							else if (mp > 960.0f)
								mp = 960.0f;
							mp = mp / 960.0f + 0.4f;
							shift[0].x = cosf(angle - 1.5707963f) * GPS_LINE_WIDTH * mp;
							shift[0].y = sinf(angle - 1.5707963f) * GPS_LINE_WIDTH * mp;
							shift[1].x = cosf(angle + 1.5707963f) * GPS_LINE_WIDTH * mp;
							shift[1].y = sinf(angle + 1.5707963f) * GPS_LINE_WIDTH * mp;
						}
						Setup2dVertex(lineVerts[vertIndex + 0], nodePoints[i].x + shift[0].x, nodePoints[i].y + shift[0].y);
						Setup2dVertex(lineVerts[vertIndex + 1], nodePoints[i + 1].x + shift[0].x, nodePoints[i + 1].y + shift[0].y);
						Setup2dVertex(lineVerts[vertIndex + 2], nodePoints[i].x + shift[1].x, nodePoints[i].y + shift[1].y);
						Setup2dVertex(lineVerts[vertIndex + 3], nodePoints[i + 1].x + shift[1].x, nodePoints[i + 1].y + shift[1].y);
						vertIndex += 4;
					}

					RwIm2DRenderPrimitive(rwPRIMTYPETRISTRIP, lineVerts, 4 * (nodesCount - 1));

					if (!FrontEndMenuManager.m_bDrawRadarOrMap
						&& reinterpret_cast<D3DCAPS9 const*>(RwD3D9GetCaps())->RasterCaps & D3DPRASTERCAPS_SCISSORTEST)
					{
						GetD3DDevice()->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
					}

					gpsDistance += DistanceBetweenPoints(FindPlayerCoors(0), ThePaths.GetPathNode(resultNodes[0])->GetNodeCoors());
					gpsShown = true;
				}
			}
		};

		Events::drawHudEvent += [] {
			if (gpsShown) {
				CFont::SetOrientation(ALIGN_CENTER);
				CFont::SetColor(CRGBA(200, 200, 200, 255));
				CFont::SetBackground(false, false);
				CFont::SetWrapx(500.0f);
				CFont::SetScale(0.4f * static_cast<float>(RsGlobal.maximumWidth) / 640.0f,
					0.8f * static_cast<float>(RsGlobal.maximumHeight) / 448.0f);
				CFont::SetFontStyle(FONT_SUBTITLES);
				CFont::SetProportional(true);
				CFont::SetDropShadowPosition(1);
				CFont::SetDropColor(CRGBA(0, 0, 0, 255));
				CVector2D radarBottom;
				CRadar::TransformRadarPointToScreenSpace(radarBottom, CVector2D(0.0f, -1.0f));
				char text[16];
				if (gpsDistance > 1000.0f)
					sprintf(text, "%.2fkm", gpsDistance / 1000.0f);
				else
					sprintf(text, "%dm", static_cast<int>(gpsDistance));
				CFont::PrintString(radarBottom.x, radarBottom.y + 8.0f * static_cast<float>(RsGlobal.maximumHeight) / 448.0f, text);
			}
		};
	}

	static void Setup2dVertex(RwIm2DVertex &vertex, float x, float y) {
		vertex.x = x;
		vertex.y = y;
		vertex.u = vertex.v = 0.0f;
		vertex.z = CSprite2d::NearScreenZ + 0.0001f;
		vertex.rhw = CSprite2d::RecipNearClip;
		vertex.emissiveColor = RWRGBALONG(GPS_LINE_R, GPS_LINE_G, GPS_LINE_B, GPS_LINE_A);
	}

} gps;; как этот код рисует линии? как в целом работает? как написать на lua для moonloader альтернативу? дай советы, не пиши за меня lua код полностью