#NoEnv
#MaxHotkeysPerInterval 99000000
#HotkeyInterval 99000000
#KeyHistory 0
#Include Lib\Gdip.ahk
#Include Lib\AHK-ViGEm-Bus.ahk

hModule := DllCall("LoadLibrary", "Str", A_LineFile "\..\Lib\SuperSleep.dll", "Ptr")
SuperSleep := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandle", "Str", A_LineFile "\..\Lib\SuperSleep.dll", "Ptr"), "AStr", "super_sleep", "Ptr")

ListLines Off
Process, Priority, , A
SetBatchLines, -1
SetKeyDelay, -1, -1
SetMouseDelay, -1
SetDefaultMouseSpeed, 0
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.

SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
DetectHiddenWindows, On
#Persistent

; --------- Controls
accel := "Enter"
turnLeft := "Left"
turnRight := "Right"
brake := "Up"
nitros := "Down"


; --------- Constants 
; Time at turn in seconds and Stablizing control
Global t := 220000
intensity := 230
delay := 300
init_delay := 10000

MenuDirect := "Right"
Menu_loops := 6
menu_s := 1
box_size := 3
color_check1 :=  0xBBE044
color_check2 :=  0xBBE044
color_2_delay := 500

color_tyre_overheat := 0xD42304
tyres_overheating := false
tyreX := 166
tyreY := 364

; resolution parameters and pixel search locations
ps_win_width  := 640
ps_win_height := 360
pix1x := 458
pix1y := 114
pix2x := 607
pix2y := 319
tolerance := 20
bm_delay := 100
box_size := 2

ps_load_time1 := 12000
ps_load_time2 := 6000 
ps_load_time3 := 9000

; Create a new controller controller
Global controller := new ViGEmDS4()
controller.SubscribeFeedback(Func("OnFeedback"))

OnFeedback(largeMotor, smallMotor, lightbarColor){
	;OutputDebug % "AHK| Feedback received - LargeMotor: " largeMotor ", SmallMotor:  " smallMotor ", LightBarColor: " lightbarColor
}

; ---------- Gui Setup -------------
Gui, -MaximizeBox
Gui, 2: -MaximizeBox
Gui, 2: -MinimizeBox
Gui, Color, c282a36, c6272a4
Gui, Add, Button, x15 y10 w70 default, Start
Gui, Add, Button, x15 y40 w70 default gVariableWindow, Variables
Gui, Add, Button, x15 y70 w70 default gGetColo_p, Stuck Leaderboard
Gui, Add, Button, x110 y70 w70 default gGetColo_g, Stuck Replay
Gui, Add, Button, x110 y10 w70 default gMenuTest, MenuTest
Gui, Add, DropDownList, w50 Choose1 vMenuDirect, Right|Left
Gui, Add, Edit, vMenu_loops w20 x165 y39, 6
Gui, Font, ce8dfe3 s9 w550 Bold
Gui, Add, Radio, Group x15 y115 altsubmit Checked gPSystem vSysCheck, PS5
Gui, Add, Radio, x70 y115 altsubmit  gPSystem, PS4
Gui, Add, Radio, x125 y115 altsubmit  gPSystem, PS4 Pro
;Gui, Add, Radio, Group x15 y130 altsubmit Checked gMenuSel vMenuCheck, Pixel
;Gui, Add, Radio, x70 y120 altsubmit  gMenuSel, Timing
Gui, Add, Button, x70 y135 w70 default gReset, Reset
Gui, Font, ce8dfe3 s9 w550 Bold

;--------- Gui 2 Setup --------------
Gui, 2: Color, c535770, c6272a4
Gui, 2: Font, c11f s9 Bold
Gui, 2: Add, Text,, Turn Length (time miliseconds)
Gui, 2: Add, Edit,  w70 vA, %t%
Gui, 2: Add, Text,, Turn Intensity
Gui, 2: Add, Edit,  w40 vB, %intensity%
Gui, 2: Add, Text,, Turn Delay
Gui, 2: Add, Edit,  w40 vC, %delay%
Gui, 2: Add, Text, x100 y90, Color 2 Delay
Gui, 2: Add, Edit, x100 y110 w40 vD, %color_2_delay%

Gui, 2: Add, Button, x20  y170 gSaveVars, Save 
Gui, 2: Add, Button, x100 y170 gVarDef, Defaults
Gui, Show,w220 h170,  GT7 Pan American AFK
return

VariableWindow:
    Gui, 2: Show, w220 h205, Variables
    return

SaveVars:
    Gui, 2:Submit
    GuiControlGet, t, 2:, A
    GuiControlGet, intensity, 2:, B
    GuiControlGet, delay, 2:, C
    GuiControlGet, color_2_delay, 2:, D
    return

VarDef:
t := 220000
intensity := 230
delay := 300
    GuiControl, 2:, A, %t%
    GuiControl, 2:, B, %intensity%
    GuiControl, 2:, C, %delay%
    GuiControl, 2:, D, %color_2_delay%
    return

ButtonStart:
    Gui, Submit, NoHide
    id := ""
    SetKeyDelay, 10
    Process, priority, , High
    gosub, GrabRemotePlay
    if  (id = "")
        return
    gosub, PauseLoop
    CoordMode, Pixel, Screen
    CoordMode, ToolTip, Screen
    sleep 1000
    gosub, AFKLoop
; ---------- Gui Setup End-------------

	
AFKLoop:
    loop{
		gosub, ResetControllerState
		gosub, PressX
        gosub, Race
        gosub, Menu_End_Race
		gosub, Menu_Start_Race
    }
    return

PressX:
; Just for menuing, does not hold X down
    controller.Buttons.Cross.SetState(true)
	DllCall("Sleep", "UInt", 200)
    controller.Buttons.Cross.SetState(false)
    return
    
PressO:
; Just for menuing, does not hold O down
    controller.Buttons.Circle.SetState(true)
	DllCall("Sleep", "UInt", 200)
    controller.Buttons.Circle.SetState(false)
    return
    
PressRight:
; For turning 
    controller.Dpad.SetState("Right") 
    Sleep, 50
    controller.Dpad.SetState("None")
    return
    
; given time t in miliseconds, turn right for that long, with intensity being how much the turn button is held for
TurnRight:
	t0 := A_TickCount
	tf := t0+t
    
	loop {
        Sleep(100)
	} until  A_TickCount > tf
    return

TurnLeft:
	t0 := A_TickCount
	tf := t0+t
	
	loop {	
		Sleep(100)
	} until A_TickCount > tf
    return

; Set the time you want to turn for in miliseconds and how hard (50, 100), 100 being the most, 50 being neutral
Turn_Right(sleept, inten){
    t := sleept
    controller.Axes.LX.SetState(inten)
    gosub, TurnRight
    controller.Axes.LX.SetState(50)
}

; Set the time you want to turn for in miliseconds and how hard (0, 50), 0 being the most
Turn_Left(sleept, inten){   
    t := sleept
    controller.Axes.LX.SetState(inten)
    gosub, TurnLeft
    controller.Axes.LX.SetState(50)
}


ResetControllerState:
	controller.Axes.RT.SetState(0)
	controller.Buttons.R2.SetState(false) 
	controller.Dpad.SetState("None") 
    controller.Axes.LX.SetState(50)
    controller.Axes.LY.SetState(50)
	controller.Axes.RX.SetState(50)
	controller.Axes.RY.SetState(50)
	controller.Button.LS.SetState(false)
    controller.Button.RS.SetState(false)
	controller.Button.RT.SetState(false)
	controller.Buttons.Cross.SetState(false)
	controller.Buttons.Circle.SetState(false)
	controller.Buttons.Square.SetState(false)
	controller.Buttons.Triangle.SetState(false)	
    return


;This will do only an oil change. will leave user at race menu to resume.
DoOilChange:
    Sleep, 1000
    controller.Buttons.Circle.SetState(true)
    Sleep, 200
    controller.Buttons.Circle.SetState(false)
    Sleep, 2800
    loop, 2 {
    controller.Dpad.SetState("Right") 
        Sleep, 140
        controller.Dpad.SetState("None")
        Sleep, 200
     }
    controller.Dpad.SetState("Down") 
    Sleep, 200
    controller.Dpad.SetState("None") 
    Sleep, 200
    
    loop, 2 {
		gosub, PressX
		Sleep, 1000
	}
    
    Sleep, 2000
    controller.Dpad.SetState("Down") 
    Sleep, 140
    controller.Dpad.SetState("None")
    Sleep, 200
    loop, 2 {
		gosub, PressX
		Sleep, 500
	}
    Sleep, 7000
    gosub, PressX
    Sleep, 500

	controller.Buttons.Circle.SetState(true)
	Sleep, 200
	controller.Buttons.Circle.SetState(false)
	Sleep, 200
	Sleep, 3000
	controller.Dpad.SetState("Up") 
	Sleep, 140
	controller.Dpad.SetState("None")
	Sleep, 200
	controller.Dpad.SetState("Left") 
	Sleep, 200
	controller.Dpad.SetState("None")
	Sleep, 500
	controller.Dpad.SetState("Left") 
	Sleep, 200
	controller.Dpad.SetState("None")
	Sleep, 500
	gosub, PressX
	Sleep, 4800
	return


;This will do complete maintenance on the car including oil, engine and body. will leave user at race menu to resume.
DoMaintenance:
	Sleep, 1000
	controller.Buttons.Circle.SetState(true)
	Sleep, 200
	controller.Buttons.Circle.SetState(false)
	Sleep, 2800
	loop, 2 {
		controller.Dpad.SetState("Right") 
		Sleep, 140
		controller.Dpad.SetState("None")
		Sleep, 200
	}
	controller.Dpad.SetState("Down") 
	Sleep, 200
	controller.Dpad.SetState("None") 
	Sleep, 200
    
	loop, 2 {
		gosub, PressX
		Sleep, 1000
	}
    
	Sleep, 2000
	controller.Dpad.SetState("Down") 
	Sleep, 140
	controller.Dpad.SetState("None")
	Sleep, 200
	loop, 2 {
		gosub, PressX
		Sleep, 500
	}
	Sleep, 7000
	gosub, PressX
	Sleep, 500

	controller.Dpad.SetState("Down") 
	Sleep, 140
    controller.Dpad.SetState("None")
	Sleep, 200
	controller.Dpad.SetState("Down") 
	Sleep, 140
	controller.Dpad.SetState("None")
	Sleep, 200
	loop, 2 {
		gosub, PressX
		Sleep, 500
	}
	Sleep, 7000
	gosub, PressX
	Sleep, 500

	controller.Dpad.SetState("Down") 
	Sleep, 140
	controller.Dpad.SetState("None")
	Sleep, 200
	controller.Dpad.SetState("Down") 
	Sleep, 140
	controller.Dpad.SetState("None")
	Sleep, 200
	controller.Dpad.SetState("Down") 
	Sleep, 140
	controller.Dpad.SetState("None")
	Sleep, 200
	loop, 2 {
		gosub, PressX
		Sleep, 500
	}
	Sleep, 8500
	gosub, PressX
	Sleep, 500

	controller.Buttons.Circle.SetState(true)
	Sleep, 200
	controller.Buttons.Circle.SetState(false)
	Sleep, 200
	Sleep, 3000
	controller.Dpad.SetState("Up") 
	Sleep, 140
	controller.Dpad.SetState("None")
	Sleep, 200
	controller.Dpad.SetState("Left") 
	Sleep, 200
	controller.Dpad.SetState("None")
	Sleep, 500
	controller.Dpad.SetState("Left") 
	Sleep, 200
	controller.Dpad.SetState("None")
	Sleep, 500
	gosub, PressX
	Sleep, 4800
	return


Menu_Start_Race:
	controller.Dpad.SetState("Down") 
	Sleep, 100
	controller.Dpad.SetState("None") 
	Sleep, 250
	loop, %menu_loops% {
		controller.Dpad.SetState(MenuDirect) 
		Sleep, 100
		controller.Dpad.SetState("None") 
		Sleep, 250
	}
	loop, 2{
		gosub, PressX
		Sleep, 500
	}
	Sleep, %ps_load_time3%
	gosub, PressX
	Sleep, 1000
	gosub, PressX
	return


Race:
; Hold Acceleration and manage turning
    Tooltip
	controller.Buttons.Cross.SetState(true)
	controller.Dpad.SetState("Down") 
	Sleep, 17500
	controller.Axes.LX.SetState(67)

	CheckForOilChange := Mod(A_Index, 29)
	CheckForMaintenance := Mod(A_Index, 107)
	OCremain := 30 - CheckForOilChange
	SetFormat, IntegerFast, d
	EOremain := 108 - CheckForMaintenance
	SetFormat, IntegerFast, d
	ToolTip, Oil: %OCremain% lap(s) remaining`nEngine: %EOremain% lap(s) remaining, 250, 45, Screen

	race_complete := false
	SetTimer, RaceComplete, 225000
	
	; Retry race if time is taking more than 5.5 mins 
	; (assume something went wrong with race)
	SetTimer, RetryRace, 330000

    loop {

		;ToolTip, Racing, 100, 100, Screen
        break_point := false
        c1 := BitGrab(pix1x, pix1y, box_size)
        for i, c in c1
        {
            d1 := Distance(c, color_check1)
            if (d1 < tolerance ){
                break_point := true
                break
            }
        }
        if (break_point)
            break
		if (race_complete) {
			controller.Dpad.SetState("Right") 
			Sleep, 50
			controller.Dpad.SetState("None")
		}
        Sleep, 2000
    }
	ToolTip, Found color 1, 100, 100, Screen
	SetTimer, RetryRace, off
	gosub, ResetControllerState
	Sleep, 500
	return


RaceComplete:
	race_complete := true
	return


Menu_End_Race:
    ToolTip, Menuing, 100, 100, Screen
     loop {
        break_point := false
        c2 := BitGrab(pix2x, pix2y, box_size)
        for i, c in c2
        {
            d2 := Distance(c, color_check2)
            if (d2 < tolerance ){
                break_point := true
                break
            }
        }
        if (break_point)
            break
        gosub, PressX
        sleep, %color_2_delay%
    }
	ToolTip, Found color 2, 100, 100, Screen
	Sleep, 1000
	controller.Buttons.Circle.SetState(true)
	Sleep, 200
	controller.Buttons.Circle.SetState(false)
	Sleep, 200
	controller.Dpad.SetState("Right") 
	Sleep, 200
	controller.Dpad.SetState("None") 
	Sleep, 500
	gosub, PressX
	Tooltip
	Sleep, %ps_load_time1%
	gosub, PressX
	Sleep, 1000
	controller.Buttons.Circle.SetState(true)
	Sleep, 200
	controller.Buttons.Circle.SetState(false)
	loop, 2 {
		gosub, PressX
		Sleep, 500
	}
	Sleep, %ps_load_time2% 
	;Conduct Maintenance here.
	ifEqual, CheckForOilChange, 0
	{
		gosub, DoOilChange
	}
	ifEqual, CheckForMaintenance, 0
	{
		gosub, DoMaintenance
	}
	return


RetryRace:
	ToolTip, Retry Race, 100, 100, Screen
    WinActivate, ahk_id %id%
    Sleep, 500
    Gosub, ResetControllerState
    Sleep 200
    controller.Buttons.Options.SetState(true)
    Sleep 200
	controller.Buttons.Options.SetState(false)
    Sleep, 200
    controller.Dpad.SetState("Right") 
    Sleep, 200
    controller.Dpad.SetState("None")
	Sleep, 200
    Goto, AFKLoop
    return


;; General Functions for AHK


PixelTuning:
	x_ratio := ps_win_width/640
	y_ratio := ps_win_height/360
	pix1x := Floor(pix1x*x_ratio)
	pix1y := Floor(pix1y*y_ratio)
	pix2x := Floor(pix2x*x_ratio)
	pix2y := Floor(pix2y*y_ratio)
	tyreX := Floor(tyreX*x_ratio)
	tyreY := Floor(tyreY*y_ratio)
	return


GrabRemotePlay:
	WinGet, remotePlay_id, List, ahk_exe RemotePlay.exe
	if (remotePlay_id = 0)
	{
		MsgBox, PS4 Remote Play not found
		return
	}
	Loop, %remotePlay_id%
	{
	  id := remotePlay_id%A_Index%
	  WinGetTitle, title, % "ahk_id " id
	  If InStr(title, "PS Remote Play")
		break
	}    
	WinGetClass, remotePlay_class, ahk_id %id%
	WinMove, ahk_id %id%,,  0, 0, 640, 360
	ControlFocus,, ahk_class %remotePlay_class%
	WinActivate, ahk_id %id%
	GetClientSize(id, ps_win_width, ps_win_height)
	gosub, PixelTuning
	return


PixelColorSimple(pc_x, pc_y)
{
    WinGet, remotePlay_id, List, ahk_exe RemotePlay.exe
    if (remotePlay_id = 0)
    {
        MsgBox, PS4 Remote Play not found
        return
    }
    if remotePlay_id
    {
        pc_wID := remotePlay_id[0]
        pc_hDC := DllCall("GetDC", "UInt", pc_wID)
        pc_fmtI := A_FormatInteger
        SetFormat, IntegerFast, Hex
        pc_c := DllCall("GetPixel", "UInt", pc_hDC, "Int", pc_x, "Int", pc_y, "UInt")
        pc_c := pc_c >> 16 & 0xff | pc_c & 0xff00 | (pc_c & 0xff) << 16
        pc_c .= ""
        SetFormat, IntegerFast, %pc_fmtI%
        DllCall("ReleaseDC", "UInt", pc_wID, "UInt", pc_hDC)
        return pc_c
        
    }
}


GetClientSize(hWnd, ByRef w := "", ByRef h := "")
{
    VarSetCapacity(rect, 16)
    DllCall("GetClientRect", "ptr", hWnd, "ptr", &rect)
    w := NumGet(rect, 8, "int")
    h := NumGet(rect, 12, "int")
}


Distance(c1, c2)
{ ; function by [VxE], return value range = [0, 441.67295593006372]
return Sqrt((((c1>>16)-(c2>>16))**2)+(((c1>>8&255)-(c2>>8&255))**2)+(((c1&255)-(c1&255))**2))
}

ConvertARGB(ARGB, Convert := 0)
{
    SetFormat, IntegerFast, Hex
    RGB += ARGB
    RGB := RGB & 0x00FFFFFF
    if (Convert)
        RGB := (RGB & 0xFF000000) | ((RGB & 0xFF0000) >> 16) | (RGB & 0x00FF00) | ((RGB & 0x0000FF) << 16)
    
    return RGB
}


MenuTest:
	;MsgBox, This will test the number of Left/Right presses at the championship selection menu. `nTry this if you have not finished all cafe menus. `nPress OK to test

	gosub, GrabRemotePlay
	Gui, Submit, NoHide
	loop, %menu_loops% {
			controller.Dpad.SetState(MenuDirect) 
			Sleep, 125
			controller.Dpad.SetState("None") 
			Sleep, 200
		}


	/*this section was used to test resoltuion specs

	/*
	gosub, GrabRemotePlay

	GetClientSize(id, ps_win_width, ps_win_height)
	gosub, PixelTuning
	MsgBox, Width %ps_win_width% Height %ps_win_height% pix1 %pix1x% 
	*/
	return


BitGrab(x, y, b)
{
    HWND := WinExist("PS Remote Play")
    pToken := Gdip_Startup()
    pBitmap := Gdip_BitmapFromHWND2(hwnd)
    
    pixs := []
    for i in range(-1*b, b+1){
        for j in range(-1*b, b+1){
            pixel := Gdip_GetPixel(pBitmap,x+i,y+j)
            rgb := ConvertARGB( pixel )
            pixs.Push(rgb)
        }
    }

    Gdip_DisposeImage(pBitmap)
    Gdip_Shutdown(pToken)
    return pixs
}


Gdip_BitmapFromHWND2(hwnd)
{
    WinGetPos,,, Width, Height, ahk_id %hwnd%
    hbm := CreateDIBSection(Width, Height), hdc := CreateCompatibleDC(), obm := SelectObject(hdc, hbm)
    RegExMatch(A_OsVersion, "\d+", Version)
    PrintWindow(hwnd, hdc, Version >= 8 ? 2 : 0)
    pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm)
    SelectObject(hdc, obm), DeleteObject(hbm), DeleteDC(hdc)
    return pBitmap
}


GetColo_p:
	gosub, GrabRemotePlay
	color_check1 := PixelColorSimple(pix1x, pix1y)
	return


GetColo_g:
	gosub, GrabRemotePlay
	color_check2 := PixelColorSimple(pix2x, pix2y)
	return


PSystem:
	Gui, Submit, NoHide
	if (SysCheck = 1){
		ps_load_time1 := 12000
		ps_load_time2 := 6000 
		ps_load_time3 := 9000
	}
	if (SysCheck = 2){
		ps_load_time1 := 37000
		ps_load_time2 := 12000
		ps_load_time3 := 33000
	}
	if (SysCheck = 3){
		ps_load_time1 := 37000
		ps_load_time2 := 12000
		ps_load_time3 := 33000
	}
	return


MenuSel:
	Gui, Submit, NoHide
	if (MenuCheck = 1){
		menu_s := 1
	}
	if (MenuCheck = 2){
		menu_s := 2
	}
	return


Reset:
	gosub, PauseLoop
	Reload
	Sleep 1000
	return


PauseLoop:
    controller.Buttons.Cross.SetState(false)
	controller.Dpad.SetState("None") 
    controller.Dpad.SetState("None") 
    controller.Dpad.SetState("None")
    return


range(start, stop:="", step:=1) {
	static range := { _NewEnum: Func("_RangeNewEnum") }
	if !step
		throw "range(): Parameter 'step' must not be 0 or blank"
	if (stop == "")
		stop := start, start := 0
	; Formula: r[i] := start + step*i ; r = range object, i = 0-based index
	; For a postive 'step', the constraints are i >= 0 and r[i] < stop
	; For a negative 'step', the constraints are i >= 0 and r[i] > stop
	; No result is returned if r[0] does not meet the value constraint
	if (step > 0 ? start < stop : start > stop) ;// start == start + step*0
		return { base: range, start: start, stop: stop, step: step }
}


_RangeNewEnum(r) {
	static enum := { "Next": Func("_RangeEnumNext") }
	return { base: enum, r: r, i: 0 }
}


_RangeEnumNext(enum, ByRef k, ByRef v:="") {
	stop := enum.r.stop, step := enum.r.step
	, k := enum.r.start + step*enum.i
	if (ret := step > 0 ? k < stop : k > stop)
		enum.i += 1
	return ret
}


Sleep(ms=1)
{
	global timeBeginPeriodHasAlreadyBeenCalled
	if (timeBeginPeriodHasAlreadyBeenCalled != 1)
	{
		DllCall("Winmm.dll\timeBeginPeriod", UInt, 1)
		timeBeginPeriodHasAlreadyBeenCalled := 1
	}	
	DllCall("Sleep", UInt, ms)
}


GuiClose:
    gosub, PauseLoop
    ExitApp

^Esc::ExitApp