Last active
August 18, 2021 14:49
-
-
Save sheeperino/3eebd4afe0cb35280fbe2e3ab155b72a to your computer and use it in GitHub Desktop.
Custom Specnr Macro with The Wall
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, rewritten by Sheep | |
;Follow Four's setup tutorial: https://youtu.be/PTgS3z1O5ws | |
;Specnr's tutorial: https://youtu.be/0gaG-P2XxrE | |
;Rename all practice worlds to begin with an _ to keep them from being relocated | |
;Have at least one kept world in each saves directory | |
;Check the GitHub post for more instructions | |
#NoEnv | |
#SingleInstance Force | |
#WinActivateForce | |
#MaxThreadsPerHotkey 10 | |
Process, Priority, , A | |
SetDefaultMouseSpeed, 1 | |
SetKeyDelay, -1 | |
SetTitleMatchMode, 2 | |
SetBatchLines, -1 | |
CoordMode, Mouse, Client | |
; Variables to configure | |
global autoReset := True ; Resets idle worlds after 5 minutes. Disable if using Shift | |
global beforeFreezeDelay := 550 ; delay before the world freezes after loading | |
global switchDelay := 20 ; increase if window switching lags | |
global fullScreenDelay := 100 ; time it takes to toggle fullscreen in ms | |
global obsDelay := 100 ; increase if not changing scenes in obs | |
global LiveSplitReset := "Numpad6" ; Reset macro for LiveSplit | |
global LiveSplitStart := "Numpad5" ; Start macro for LiveSplit | |
global WallHotkey := "Numpad4" ; Wall Scene macro for OBS | |
; Add/remove directories to array to account for more/less | |
global oldWorldsFolder := "C:\Users\Sophie\Desktop\MultiMC\instances\1.16.11\.minecraft\oldWorlds\" | |
global SavesDirectories = ["C:\Users\Sophie\Desktop\MultiMC\instances\1.16.11\.minecraft\saves\", "C:\Users\Sophie\Desktop\MultiMC\instances\1.16.12\.minecraft\saves\", "C:\Users\Sophie\Desktop\MultiMC\instances\1.16.13\.minecraft\saves\"] | |
; The Wall | |
global Width := 1280 | |
global Height := 720 | |
global SecWidth := 427 ; width and | |
global SecHeight := 720 ; height for a single section | |
global Map := [[1, 2, 3]] ; Create a new array for a new row | |
; Don't configure these | |
global index | |
global pauseAuto := False | |
global instances := SavesDirectories.MaxIndex() | |
global PIDs := GetAllPIDs() | |
global titles := [] | |
global resetStates := [] | |
global resetTimes := [] | |
tmptitle := "" | |
for i, tmppid in PIDs{ | |
WinGetTitle, tmptitle, ahk_pid %tmppid% | |
titles.Push(tmptitle) | |
resetStates.push(0) | |
resetTimes.push(0) | |
} | |
IfNotExist, %oldWorldsFolder% | |
FileCreateDir %oldWorldsFolder% | |
#Persistent | |
SetTimer, Repeat, 20 | |
return | |
Repeat: | |
Critical | |
for i, pid in PIDs { | |
if (needToResetTimer) { | |
if (WinExist("LiveSplit")) { | |
send, {%LiveSplitReset%}{%LiveSplitStart%} | |
needToResetTimer := False | |
} | |
} | |
HandleResetState(pid, i) | |
WinGetTitle, title, ahk_pid %pid% | |
if (title <> titles[i]) { | |
titles[i] := title | |
IfWinNotActive, title | |
if (IsInGame(title)) { | |
while (True) { | |
if (HasGameSaved(i, False)) | |
break | |
} | |
ControlSend, ahk_parent, {F3 Down}{Esc}{F3 Up}, ahk_pid %pid% | |
OutputDebug, paused the game | |
sleep, %beforeFreezeDelay% | |
SuspendInstance(pid) | |
OutputDebug, suspended instance | |
resetTimes[i] := A_NowUTC | |
} | |
} | |
if (autoReset && !pauseAuto) { | |
timeDelta := 500 ; 5 minutes | |
if (IsInGame(title) && resetStates[i] == 0 && index != i && resetTimes[i] > 0) { | |
if ((A_NowUTC - resetTimes[i]) >= timeDelta) | |
ResetInstance(i) | |
} | |
} | |
} | |
return | |
HandleResetState(pid, idx) { | |
if (resetStates[idx] == 0) ; Not resetting | |
return | |
if (resetStates[idx] == 1) ; Need to resume | |
ResumeInstance(pid) | |
else if (resetStates[idx] == 2) { ; Exit world | |
ControlSend, ahk_parent, {Shift down}{Tab}{Shift up}{Enter}, ahk_pid %pid% | |
} | |
else if (resetStates[idx] == 3) { ; Move worlds | |
MoveWorlds(idx) | |
resetStates[i] := False | |
} else { ; Done | |
resetStates[idx] := -1 | |
} | |
resetStates[idx] += 1 ; Progress State | |
} | |
SetTitles() { | |
for i, pid in PIDs { | |
WinSetTitle, ahk_pid %pid%, , Minecraft* - Instance %i% | |
} | |
} | |
WallCoords() | |
{ | |
MouseGetPos, mX, mY | |
if (mX < Width && mX > 0 && mY < Height && mY > 0) { | |
x := mX // SecWidth +1 | |
y := mY // SecHeight +1 | |
index := map[y][x] | |
return index | |
} | |
} | |
MoveWorlds(idx) | |
{ | |
dir := SavesDirectories[idx] | |
Loop, Files, %dir%*, D | |
{ | |
_Check :=SubStr(A_LoopFileName,1,1) | |
If (InStr(A_LoopFileName, "New World") || InStr(A_LoopFileName, "Speedrun #")) | |
FileMoveDir, %dir%%A_LoopFileName%, %oldWorldsFolder%%A_LoopFileName%%A_NowUTC%, R | |
} | |
} | |
IsInGame(currTitle) { ; If using another language, change Singleplayer and Multiplayer to match game title | |
return InStr(currTitle, "Singleplayer") || InStr(currTitle, "Multiplayer") || InStr(currTitle, "Instance") | |
} | |
ResetInstance(idx, Wall := True) { | |
if (idx){ | |
if (Wall) { | |
pid := PIDs[idx] | |
WinGetTitle, title, ahk_pid %pid% | |
if (!IsInGame(title)) | |
return | |
ControlSend, ahk_parent, {Esc 2}, ahk_pid %pid% | |
resetStates[idx] := 1 ; Set to Resume Instance | |
} else { | |
pid := PIDs[idx] | |
ControlSend, ahk_parent, {Esc}, ahk_pid %pid% | |
resetStates[idx] := 2 ; Set to Exit world | |
} | |
} | |
} | |
PlayInstance(idx) { | |
if (idx) { | |
OutputDebug, playing instance %idx% | |
pid := PIDs[idx] | |
ResumeInstance(PIDs[idx]) | |
OutputDebug, pid %pid% | |
sleep, %switchDelay% | |
WinActivate, ahk_pid %pid% | |
ControlSend, ahk_parent, {Esc}, ahk_pid %pid% | |
send {Numpad%idx% down} | |
sleep, %obsDelay% | |
send {Numpad%idx% up} | |
} | |
} | |
ExitWorld() { | |
if (fullscreen) { | |
send, {F11} | |
sleep, %fullScreenDelay% | |
} | |
SwitchToWall() | |
ResetInstance(index, False) | |
OutputDebug, reset instance %index% with %A_ThisHotkey% | |
} | |
SwitchToWall() { | |
CoordMode, Mouse, Client | |
WinMove, Windowed Projector, , , , Width +16, Height +39 | |
WinGetPos,,, X, Y, Windowed Projector | |
WinMove, Windowed Projector, , (A_ScreenWidth/2)-(X/2), (A_ScreenHeight/2)-(Y/2 +11) | |
Send, {%WallHotkey% down} | |
sleep, %obsDelay% | |
Send, {%WallHotkey% up} | |
WinActivate, Windowed Projector | |
} | |
SwitchToInstance() { ; Unused | |
pid := PIDs[idx] | |
; SwitchToBGScreen() | |
sleep, %switchDelay% | |
WinActivate, ahk_pid %pid% | |
send {Esc} | |
} | |
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 { | |
StringTrimRight, tmp, savesDir, 18 | |
subStr := StrReplace(tmp, "\", "/") | |
if (InStr(rawOut, subStr)) | |
return i | |
} | |
return -1 | |
} | |
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 | |
} | |
FreeMemory(pid) | |
{ | |
h:=DllCall("OpenProcess", "UInt", 0x001F0FFF, "Int", 0, "Int", pid) | |
DllCall("SetProcessWorkingSetSize", "UInt", h, "Int", -1, "Int", -1) | |
DllCall("CloseHandle", "Int", h) | |
} | |
UnsuspendAll() { | |
for i, pid in PIDs { | |
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) | |
} | |
} | |
GetAllPIDs() | |
{ | |
OutputDebug, getting all 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.16.1") && !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 | |
} | |
HasGameSaved(idx, checkTitle) { | |
rawLogFile := StrReplace(SavesDirectories[idx], "saves", "logs\latest.log") | |
StringTrimRight, logFile, rawLogFile, 1 | |
numLines := 0 | |
Loop, Read, %logFile% | |
{ | |
numLines += 1 | |
} | |
saved := False | |
startTime := A_TickCount | |
Loop, Read, %logFile% | |
{ | |
if ((numLines - A_Index) < 2) | |
{ | |
if (checkTitle && InStr(A_LoopReadLine, "Stopping worker threads")) { | |
saved := True | |
break | |
} | |
if (!checkTitle && InStr(A_LoopReadLine, "Saving chunks for level 'ServerLevel") && InStr(A_LoopReadLine, "minecraft:the_end")) { | |
saved := True | |
break | |
} | |
} | |
} | |
return saved | |
} | |
RControl::Suspend ; Pause all macros | |
LAlt:: ; Reload if macro locks up | |
UnsuspendAll() | |
Reload | |
return | |
]:: ; Switches to wall | |
SwitchToWall() | |
return | |
#If WinActive("Windowed Projector") | |
LButton:: | |
ResetInstance(WallCoords()) | |
Return | |
RButton:: ; do not use "~" with mouse buttons | |
OutputDebug, pressed RButton | |
PlayInstance(WallCoords()) | |
return | |
#IfWinActive, Minecraft | |
{ | |
*CapsLock:: ExitWorld() ; Reset | |
*F12:: | |
SetTitles() | |
return | |
*Tab:: ; BG Reset Instance 1 | |
ResetInstance(1) | |
return | |
*\:: ; BG Reset Instance 2 | |
ResetInstance(2) | |
return | |
*z:: ; BG Reset Instance 3 | |
ResetInstance(3) | |
return | |
:: ; BG Reset Instance 4 | |
ResetInstance(4) | |
return | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Stable Version
Specnr Version
Changelog
The Wall
v0.19.3:
v0.19.2:
v0.19.12:
v0.19.11a:
v0.19.11:
v0.19.1c (experimental):
v0.19.1b (experimental):
v0.19.1a: