Skip to content

Instantly share code, notes, and snippets.

@sheeperino
Last active August 29, 2021 02:01
Show Gist options
  • Save sheeperino/a60c03af87144bd0f67dfb13e5e92c5f to your computer and use it in GitHub Desktop.
Save sheeperino/a60c03af87144bd0f67dfb13e5e92c5f to your computer and use it in GitHub Desktop.
Custom Pigeon Macro pre1.9
; Pre 1.9 RSG Multi Instance Macro, Created by FinestPigeon. Coding help from: Peej, ScarRuns, Specnr, and Four.
; ***READ*** (common issues that need to be addressed)
; Sometimes a clogged saves folder can make it so the GUI seems "Too slow for the macro" If this happens, clear your saves folders
; You will most likley need to have 2 OBS' running to ensure verification. 1 OBS with Display Capture (For streams and main recordings), And a second with 4 Game Captures running on the same screen.
; An example of my OBS setups, Main OBS: https://imgur.com/a/55yuwxS | Verification OBS: https://imgur.com/a/rw5agz1
; An example of how i set up my verification OBS: https://youtu.be/Nmsanpob7C0
; If you use a custom texture pack, make sure the title screen's splash text color is default (Yellow). This is used in the macro to make sure you are in the title screen before creating the world
; If you have any other questions/concerns, message me on discord. FinestPigeon#8738
#NoEnv
#SingleInstance, force
Process, Priority, , A
; Options:
global savesDirectories := ["C:\Users\Sophie\Desktop\MultiMC\instances\1.7.21\.minecraft\saves\","C:\Users\Sophie\Desktop\MultiMC\instances\1.7.22\.minecraft\saves\"]
global oldWorldsFolder := "C:\Users\Sophie\Desktop\MultiMC\instances\1.7.21\.minecraft\oldWorlds\"
global KeyDelay := 10 ; Delay between each keypress
global HardcoreDelay := 50 ; Delay to ensure you activate hardcore
global SaveAndQuitDelay := 100 ; The amount of time between the macro pressing "Escape" and it pressing "SaveAndQuit". If you are experiencing crashes, Increase this number
global obsDelay := 50 ; Delay for obs scene switching
global liveSplitStart := "Numpad5" ; The key that starts your LiveSplit timer, Preferably use a function key
global liveSplitReset := "Numpad6" ; The key that resets yourLiveSplit timer, Preferably use a function key
global hardcore := "Yes" ; Change this to "Yes" if you play Hardcore mode
; edit at your own risk
global GUIscale := getGUIscale()
OutputDebug, gui scale: %GUIscale%
global instances := savesDirectories.MaxIndex()
OutputDebug, number of instances: %instances%
global PIDs := SetupPIDs()
global currInst := -1
global titles := []
tmptitle := ""
for i, tmppid in PIDs{
WinGetTitle, tmptitle, ahk_pid %tmppid%
titles.Push(tmptitle)
}
SetDefaultMouseSpeed, 0
SetMouseDelay, -1
SetKeyDelay, -1
SetTitleMatchMode, 2
IfNotExist, %oldWorldsFolder%
FileCreateDir %oldWorldsFolder%
SleepGUI(time)
{
DllCall("Sleep",UInt,time)
}
Main() {
SendInput, {%liveSplitReset%}
ExitWorld()
Loop,
{
PixelSearch, Px, Py, 0, 0, 1920, 1080, 0xFFFF00, 0, Fast RGB
if (!ErrorLevel)
Break
}
EnterSinglePlayer()
Loop,
{
PixelSearch, Px, Py, 0, 0, 1920, 1080, 0xA0A0A0, 0, Fast RGB
if (!ErrorLevel)
Break
}
OutputDebug, in world list
CreateWorld()
if (idx := GetActiveInstanceNum()) > 0
{
nextIdx := Mod(idx, instances) + 1
SwitchInstance(nextIdx)
MoveWorlds(idx)
}
}
RunHide(Command)
{
OutputDebug, runhide
dhw := A_DetectHiddenWindows
DetectHiddenWindows, On
Run, %ComSpec%,, Hide, cPid
WinWait, ahk_pid %cPid%
DetectHiddenWindows, %dhw%
DllCall("AttachConsole", "uint", cPid)
Shell := ComObjCreate("WScript.Shell")
Exec := Shell.Exec(Command)
Result := Exec.StdOut.ReadAll()
DllCall("FreeConsole")
Process, Close, %cPid%
Return Result
}
EnterSinglePlayer()
{
; Sleep MenuDelay
OutputDebug, in title screen
if (GUIscale = "Auto") {
SleepGUI(KeyDelay)
MouseClick, Left, 950, 500
SleepGUI(KeyDelay)
}
else if (GUIscale = "Small") {
SleepGUI(KeyDelay)
MouseClick, Left, 1150, 560
SleepGUI(KeyDelay)
}
else if (GUIscale = "Normal") {
SleepGUI(KeyDelay)
MouseClick, Left, 960, 390
SleepGUI(KeyDelay)
}
else if (GUIscale = "Large") {
SleepGUI(KeyDelay)
MouseClick, Left, 650, 380
SleepGUI(KeyDelay)
}
}
ExitWorld()
{
if (guiScale = "Auto")
{
Send, {Escape}
SleepGUI(SaveAndQuitDelay)
MouseClick, Left, 960, 720
}
else if (guiScale = "Large")
{
Send, {Escape}
SleepGUI(SaveAndQuitDelay)
MouseClick, Left, 650, 550
}
else if (guiScale = "Normal")
{
Send, {Escape}
SleepGUI(SaveAndQuitDelay)
MouseClick, Left, 960, 500
}
else if (guiScale = "Small")
{
Send, {Escape}
SleepGUI(SaveAndQuitDelay)
MouseClick, Left, 960, 385
}
OutputDebug, exited world
}
CreateWorld()
{
if (guiScale = "Auto")
{
MouseClick, Left, 1270, 910
if (hardcore = "Yes"){
SleepGUI(HardcoreDelay)
MouseClick, Left, 950, 500
SleepGUI(HardcoreDelay)
}
}
else if (guiScale = "Large")
{
MouseClick, Left, 1000, 620
if (hardcore = "Yes"){
SleepGUI(HardcoreDelay)
MouseClick, Left, 650, 380
SleepGUI(HardcoreDelay)
}
}
else if (guiScale = "Normal")
{
MouseClick, Left, 1118, 1000
if (hardcore = "Yes"){
SleepGUI(HardcoreDelay)
MouseClick, Left, 960, 250
SleepGUI(HardcoreDelay)
}
}
else if (guiScale = "Small")
{
MouseClick, Left, 1150, 800
if (hardcore = "Yes"){
SleepGUI(HardcoreDelay)
MouseClick, Left, 1150, 580
SleepGUI(HardcoreDelay)
}
}
SleepGUI(KeyDelay)
Send, {Enter}
OutputDebug, might have created a world
}
MoveWorlds(idx)
{
dir := SavesDirectories[idx]
Loop, Files, %dir%*, D
{
If (InStr(A_LoopFileName, "New World"))
FileMoveDir, %dir%%A_LoopFileName%, %oldWorldsFolder%%A_LoopFileName%%A_NowUTC%Instance %idx%, R
}
}
CheckJoined(idx)
{
rawLogFile := StrReplace(savesDirectories[idx], "saves", "logs\latest.log")
StringTrimRight, logFile, rawLogFile, 1
numLines := 0
Loop, Read, %logFile%
{
numLines += 1
}
joined := False
startTime := A_TickCount
Loop, Read, %logFile%
{
if ((numLines - A_Index) < 1)
{
if (InStr(A_LoopReadLine, "joined the game")) {
joined := True
break
}
}
}
return joined
}
getGUIscale()
{
optionsFile := StrReplace(savesDirectories[1], "saves\", "options.txt")
FileReadLine, guiScaleLine, %optionsFile%, 7
if InStr(guiScaleLine, 1)
return "Small"
else if InStr(guiScaleLine, 2)
return "Normal"
else if InStr(guiScaleLine, 3)
return "Large"
else
return "Auto"
}
GetInstanceNum(pid)
{
command := Format("powershell.exe $x = Get-WmiObject Win32_Process -Filter \""ProcessId = {1}\""; $x.CommandLine", pid)
rawOut := RunHide(command)
for i, savesDir in SavesDirectories {
if (InStr(savesDir, ".minecraft")) {
StringTrimRight, tmp, savesDir, 18
tmp := StrReplace(tmp, "\", "/")
OutputDebug, %tmp%
}
Else
StringTrimRight, tmp, savesDir, 7
if (InStr(rawOut, tmp))
return i
}
return -1
}
GetActiveInstanceNum() {
WinGet, pid, PID, A
WinGetTitle, title, ahk_pid %pid%
for i, tmppid in PIDs {
if (tmppid == pid)
return i
}
}
SwitchInstance(idx)
{
#WinActivateForce
currInst := idx
pid := PIDs[idx]
WinActivate, ahk_pid %pid%
send {esc}
sleep, 50
send {Numpad%idx% down}
sleep, 50
send {Numpad%idx% up}
}
SetupPIDs()
{
OutputDebug, setting up pids
orderedPIDs := []
loop, %instances%
orderedPIDs.Push(-1)
WinGet, all, list
Loop, %all%
{
WinGet, pid, PID, % "ahk_id " all%A_Index%
WinGetTitle, title, ahk_pid %pid%
if (InStr(title, "Minecraft 1.") || InStr(title, "Instance") && !InStr(title, "Not Responding"))
Output .= pid "`n"
}
tmpPids := StrSplit(Output, "`n")
for i, pid in tmpPids {
if (pid) {
inst := GetInstanceNum(pid)
OutputDebug, instance num: %inst%
orderedPIDs[inst] := pid
}
}
return orderedPIDs
}
SetTitles() {
for i, pid in PIDs {
WinSetTitle, ahk_pid %pid%, , Minecraft - Instance %i% Singleplayer
OutputDebug, %i%
OutputDebug, %pid%
}
}
#::
SetTitles()
return
*CapsLock:: ; The keybind for exiting a world, creating world, switching instances
Main()
return
@sheeperino
Copy link
Author

sheeperino commented Aug 12, 2021

Changelog

v0.4:

  • Uses a more precise sleep function, possibly avoiding bugs

v0.3:

  • I forgor 💀

v0.2:

  • re-added title screen PixelSearch and implemented world list PixelSearch
  • added Hardcore delay to ensure you set world difficulty to hardcore

v0.1:

  • cleaned code

v0.0.2:

  • menu delay now works for every menu
  • removed PixelSearch functions as they don't work
  • faster and more consistent resets

v0.0.1:

  • initial version
  • single instance only
  • can move worlds
  • checks if player is in game to start timer
  • automatically gets GUI scale

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment