Last active
August 16, 2023 18:20
-
-
Save Specnr/c851a92a258dd1fdbe3eee588f3f14d8 to your computer and use it in GitHub Desktop.
A multi-instance macro with instance freezing for Minecraft RSG (Depreciated, use the wall macro it has a multi mode :])
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; Multi instance AHK resetting script | |
; Author Specnr | |
; Follow setup video (mainly for OBS setup) https://youtu.be/0gaG-P2XxrE | |
; Run the setup script https://gist.github.com/Specnr/b13dae781f1b70bb6592027205870c7e | |
; Follow extra instructions https://gist.github.com/Specnr/c851a92a258dd1fdbe3eee588f3f14d8#gistcomment-3810003 | |
#NoEnv | |
#SingleInstance Force | |
SetKeyDelay, 0 | |
SetWinDelay, 1 | |
SetTitleMatchMode, 2 | |
; Variables to configure | |
global instanceFreezing := True | |
global unpauseOnSwitch := True | |
global fullscreen := False | |
global disableTTS := False | |
global countAttempts := True | |
global autoReset := False ; Resets idle worlds after 5 minutes | |
global beforeFreezeDelay := 170 ; increase if doesnt join world | |
global fullScreenDelay := 270 ; increse if fullscreening issues | |
global restartDelay := 200 ; increase if saying missing instanceNumber in .minecraft (and you ran setup) | |
global maxLoops := 20 ; increase if macro regularly locks | |
global obsDelay := 50 ; increase if not switching scenes | |
; Don't configure these | |
global currInst := -1 | |
global pauseAuto := False | |
global SavesDirectories := [] | |
global instances := 0 | |
global rawPIDs := [] | |
global PIDs := [] | |
global titles := [] | |
global resetStates := [] | |
global resetTimes := [] | |
if (instanceFreezing) { | |
UnsuspendAll() | |
sleep, %restartDelay% | |
} | |
GetAllPIDs() | |
SetTitles() | |
tmptitle := "" | |
for i, tmppid in PIDs{ | |
WinGetTitle, tmptitle, ahk_pid %tmppid% | |
titles.Push(tmptitle) | |
resetStates.push(0) | |
resetTimes.push(0) | |
WinSet, AlwaysOnTop, Off, ahk_pid %tmppid% | |
} | |
if (!disableTTS) | |
ComObjCreate("SAPI.SpVoice").Speak("Ready") | |
#Persistent | |
SetTimer, Repeat, 20 | |
return | |
Repeat: | |
Critical | |
for i, pid in PIDs { | |
HandleResetState(pid, i) | |
WinGetTitle, title, ahk_pid %pid% | |
if (title <> titles[i]) { | |
titles[i] := title | |
if (currInst > 0 && IsInGame(title)) { | |
if (i != currInst) { | |
IfWinNotActive, title | |
{ | |
while (True) { | |
if (HasGameSaved(i) || A_Index > maxLoops) | |
break | |
} | |
ControlSend, ahk_parent, {Blind}{F3 Down}{Esc}{F3 Up}, ahk_pid %pid% | |
if (instanceFreezing) { | |
sleep, %beforeFreezeDelay% | |
SuspendInstance(pid) | |
} | |
resetTimes[i] := A_TickCount | |
} | |
} | |
} | |
} | |
if (autoReset && !pauseAuto) { | |
timeDelta := 300000 ; 5 minutes | |
if (IsInGame(title) && resetStates[i] == 0 && currInst != i && resetTimes[i] > 0) { | |
if ((A_TickCount - resetTimes[i]) >= timeDelta) | |
ResetInstance(i) | |
} | |
} | |
} | |
return | |
HandleResetState(pid, idx) { | |
if (resetStates[idx] == 0) ; Not resetting | |
return | |
if (resetStates[idx] == 1 && instanceFreezing) ; Need to resume | |
ResumeInstance(pid) | |
else if (resetStates[idx] == 2) ; Exit world | |
ControlSend, ahk_parent, {Blind}{Shift down}{Tab}{Shift up}{Enter}, ahk_pid %pid% | |
else if (resetStates[idx] == 3) { ; Count attempts | |
if (countAttempts) | |
{ | |
FileRead, WorldNumber, RSG_1_16.txt | |
if (ErrorLevel) | |
WorldNumber = 0 | |
else | |
FileDelete, RSG_1_16.txt | |
WorldNumber += 1 | |
FileAppend, %WorldNumber%, RSG_1_16.txt | |
} | |
resetStates[i] := False | |
} else { ; Done | |
resetStates[idx] := -1 | |
} | |
resetStates[idx] += 1 ; Progress State | |
} | |
HasGameSaved(idx) { | |
logFile := SavesDirectories[idx] . "logs\latest.log" | |
numLines := 0 | |
Loop, Read, %logFile% | |
{ | |
numLines += 1 | |
} | |
saved := False | |
startTime := A_TickCount | |
Loop, Read, %logFile% | |
{ | |
if ((numLines - A_Index) < 5) | |
{ | |
if (InStr(A_LoopReadLine, "Loaded 0") || (InStr(A_LoopReadLine, "Saving chunks for level 'ServerLevel") && InStr(A_LoopReadLine, "minecraft:the_end"))) { | |
saved := True | |
break | |
} | |
} | |
} | |
return saved | |
} | |
RunHide(Command) | |
{ | |
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 | |
} | |
GetSavesDir(pid) | |
{ | |
command := Format("powershell.exe $x = Get-WmiObject Win32_Process -Filter \""ProcessId = {1}\""; $x.CommandLine", pid) | |
rawOut := RunHide(command) | |
if (InStr(rawOut, "--gameDir")) { | |
strStart := RegExMatch(rawOut, "P)--gameDir (?:""(.+?)""|([^\s]+))", strLen, 1) | |
return SubStr(rawOut, strStart+10, strLen-10) . "\" | |
} else { | |
strStart := RegExMatch(rawOut, "P)(?:-Djava\.library\.path=(.+?) )|(?:\""-Djava\.library.path=(.+?)\"")", strLen, 1) | |
if (SubStr(rawOut, strStart+20, 1) == "=") { | |
strLen -= 1 | |
strStart += 1 | |
} | |
return StrReplace(SubStr(rawOut, strStart+20, strLen-28) . ".minecraft\", "/", "\") | |
} | |
} | |
GetInstanceTotal() { | |
idx := 1 | |
global rawPIDs | |
WinGet, all, list | |
Loop, %all% | |
{ | |
WinGet, pid, PID, % "ahk_id " all%A_Index% | |
WinGetTitle, title, ahk_pid %pid% | |
if (InStr(title, "Minecraft*")) { | |
rawPIDs[idx] := pid | |
idx += 1 | |
} | |
} | |
return rawPIDs.MaxIndex() | |
} | |
GetInstanceNumberFromSaves(saves) { | |
numFile := saves . "instanceNumber.txt" | |
num := -1 | |
if (saves == "" || saves == ".minecraft" || saves == ".minecraft\" || saves == ".minecraft/") ; Misread something | |
Reload | |
if (!FileExist(numFile)) | |
MsgBox, Missing instanceNumber.txt in %saves%. Run the setup script (see instructions) | |
else | |
FileRead, num, %numFile% | |
return num | |
} | |
GetAllPIDs() | |
{ | |
global SavesDirectories | |
global PIDs | |
global instances := GetInstanceTotal() | |
; Generate saves and order PIDs | |
Loop, %instances% { | |
saves := GetSavesDir(rawPIDs[A_Index]) | |
if (num := GetInstanceNumberFromSaves(saves)) == -1 | |
ExitApp | |
PIDS[num] := rawPIDs[A_Index] | |
SavesDirectories[num] := saves | |
} | |
} | |
FreeMemory(pid) | |
{ | |
h:=DllCall("OpenProcess", "UInt", 0x001F0FFF, "Int", 0, "Int", pid) | |
DllCall("SetProcessWorkingSetSize", "UInt", h, "Int", -1, "Int", -1) | |
DllCall("CloseHandle", "Int", h) | |
} | |
UnsuspendAll() { | |
currInst := -1 | |
WinGet, all, list | |
Loop, %all% | |
{ | |
WinGet, pid, PID, % "ahk_id " all%A_Index% | |
WinGetTitle, title, ahk_pid %pid% | |
if (InStr(title, "Minecraft*")) | |
ResumeInstance(pid) | |
} | |
} | |
SuspendInstance(pid) { | |
hProcess := DllCall("OpenProcess", "UInt", 0x1F0FFF, "Int", 0, "Int", pid) | |
If (hProcess) { | |
DllCall("ntdll.dll\NtSuspendProcess", "Int", hProcess) | |
DllCall("CloseHandle", "Int", hProcess) | |
} | |
FreeMemory(pid) | |
} | |
ResumeInstance(pid) { | |
hProcess := DllCall("OpenProcess", "UInt", 0x1F0FFF, "Int", 0, "Int", pid) | |
If (hProcess) { | |
DllCall("ntdll.dll\NtResumeProcess", "Int", hProcess) | |
DllCall("CloseHandle", "Int", hProcess) | |
} | |
} | |
IsProcessSuspended(pid) { | |
WinGetTitle, title, ahk_pid %pid% | |
return InStr(title, "Not Responding") | |
} | |
SwitchInstance(idx) | |
{ | |
currInst := idx | |
pid := PIDs[idx] | |
if (instanceFreezing) | |
ResumeInstance(pid) | |
WinSet, AlwaysOnTop, On, ahk_pid %pid% | |
WinSet, AlwaysOnTop, Off, ahk_pid %pid% | |
send {Numpad%idx% down} | |
ControlSend,, {Numpad%idx%}, ahk_exe obs64.exe | |
sleep, %obsDelay% | |
send {Numpad%idx% up} | |
if (fullscreen) { | |
ControlSend, ahk_parent, {Blind}{F11}, ahk_pid %pid% | |
sleep, %fullScreenDelay% | |
} | |
Send, {LButton} ; Make sure the window is activated | |
} | |
GetActiveInstanceNum() { | |
WinGet, pid, PID, A | |
WinGetTitle, title, ahk_pid %pid% | |
if (IsInGame(title)) { | |
for i, tmppid in PIDs { | |
if (tmppid == pid) | |
return i | |
} | |
} | |
return -1 | |
} | |
IsInGame(currTitle) { ; If using another language, change Singleplayer and Multiplayer to match game title | |
return InStr(currTitle, "Singleplayer") || InStr(currTitle, "Multiplayer") || InStr(currTitle, "Instance") | |
} | |
ExitWorld() | |
{ | |
if (fullscreen) { | |
send, {F11} | |
sleep, %fullScreenDelay% | |
} | |
if (idx := GetActiveInstanceNum()) > 0 | |
{ | |
WinSet, AlwaysOnTop, Off, ahk_pid %pid% | |
nextIdx := Mod(idx, instances) + 1 | |
nextPID := PIDs[nextIdx] | |
pauseAuto := True | |
SwitchInstance(nextIdx) | |
if (unpauseOnSwitch) | |
ControlSend, ahk_parent, {Blind}{Esc}, ahk_pid %nextPID% | |
ResetInstance(idx, False) | |
pauseAuto := False | |
} | |
} | |
ResetInstance(idx, bg := True) { | |
if (bg) { | |
pid := PIDs[idx] | |
WinGetTitle, title, ahk_pid %pid% | |
if (GetActiveInstanceNum() == idx || !IsInGame(title)) | |
return | |
ControlSend, ahk_parent, {Blind}{Esc 2}, ahk_pid %pid% | |
if (instanceFreezing) | |
resetStates[idx] := 1 ; Set to Resume Instance | |
else | |
resetStates[idx] := 2 ; Set to Exit world | |
} else { | |
pid := PIDs[idx] | |
ControlSend, ahk_parent, {Blind}{Esc}, ahk_pid %pid% | |
resetStates[idx] := 2 ; Set to Exit world | |
} | |
} | |
SetTitles() { | |
for i, pid in PIDs { | |
WinSetTitle, ahk_pid %pid%, , Minecraft* - Instance %i% | |
} | |
} | |
RAlt::Suspend ; Pause all macros | |
^LAlt:: ; Reload if macro locks up | |
Reload | |
return | |
#IfWinActive, Minecraft | |
{ | |
*U:: ExitWorld() ; Reset | |
*O:: ; BG Reset Instance 1 | |
ResetInstance(1) | |
return | |
*P:: ; BG Reset Instance 2 | |
ResetInstance(2) | |
return | |
*[:: ; BG Reset Instance 3 | |
ResetInstance(3) | |
return | |
; Follow the pattern if you have more instances | |
; Remove keys you dont use to avoid complications | |
} |
Changelog
v0.24.4:
- Fixed OBS not scene switching issues, past fix only worked for me LULW
v0.24.3:
- Fixed OBS hotkey issues and removed any delay associated
- Removed world moving, use separate script instead
v0.24.2:
- probably fixed issue where it would say missing instance number when it wasnt missing.
- Set default autoreset feature to False to match srdc ruleset
v0.24.1:
- Fix for bg resetting while instance freezing off
v0.24:
- Added instance freezing as an option
- I recommend you keep it on, but if you have a lot of RAM, try it off with the dynamic FPS mod
v0.23:
- Removed need for switch delay :)
v0.22.6:
- Added fix for macro locking
v0.22.5:
- Added fix for fullscreen
v0.22.4:
- Changed the way i unpause on switch to allow for better reliability
v0.22.3:
- Added pause on world load to be toggleable to allow for faster resets
v0.22.2:
- Made universally work between 1.16, 1.15 and Icarus 1.16
- (Maybe more, only tested those 3)
v0.22.1:
- Made work with spaces in folder structure
v0.22:
- Added new setup script to simplify things (see above)
- Removed need for taskbar order at all (after using setup script)
- Allowed for world moving across drives
v0.21.1:
- Changed back to winactivate for switching to make more consistent.
- Taskbar order no longer matters after initial load/reload
v0.21:
- Removed need for saves directories
- Removed need for switch buffer allowing for much lower switch delay
- Watch New setup video for how to use this new style of macro
v0.20.1:
- Added line back that i missed lol
v0.20:
- Added dependency of new livesplit dll linked above
- To install just download the .dll file and move it into into your Livesplit folder -> Components replacing the current. Then check both boxes in LiveSplit's IGT settings
- Speeds up resets and removes any livesplit related issues
v0.19.8:
- Added option to use multiple livesplits to avoid some delay at the cost of convenience
v0.19.7:
- Auto Reset after 5 minutes now works as expected
v0.19.6:
- Made resets faster by switching before killing livesplit
v0.19.5:
- Fixed not moving worlds
v0.19.4:
- Added fix for non-multimc users
v0.19.3:
- Fixed all issues with uncrouching and other issues while background resetting
- Added instance number indicator to old worlds to make verification easier
v0.19.2:
- Removed need for any structure to instance folder names
v0.19.1:
- Added fix for newer versions of autoreset renaming worlds
v0.19:
- Made work with auto reset, also now DEPENDS on it
- Download link above
v0.18.1:
- Added back shift calls to exit world to fix issues when CHECKING
- WARNING: if you use shift for anything, disable autoReset for now
v0.18:
- Single LiveSplit support with updating saves paths for IGT
- Added F12 hotkey to set titles before opening OBS at the start of a session to allow for easy setup
- Added steps to fix macro freezing in the comment above
v0.17.4:
- Removed all shift calls
- Resetting in spectator kinda weird, either don't or after pressing button go back and manually exit the world
v0.17.3:
- Moved shift calls to one state to reduce shifting error
v0.17.2:
- Press F3+Esc on world load for better visibility bg resets
- You need to press F3+P on all instances
v0.17.1:
- Made memory freeing more efficient
v0.17:
- Added RAM freeing for suspended instances
v0.16:
- Added feature to automatically background reset after 5 minutes
v0.15:
- Tested and fixed some issues with fullscreen
v0.14:
- Added reset state management to allow for more efficient resetting
v0.13.1:
- Small fix for fullscreen users
v0.13:
- Fixed trying to bg reset while bg resetting
v0.12:
- Fixed issue with macro infinite loop case
v0.11:
- Fixed issues with window switching
v0.10:
- Added reset queue to allow for quicker resetting
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The Wall now supports this style of resetting, switch now!
NEW SETUP VIDEO