Page 1 of 1

Ultimate autohotkey macros for Grimlock 2

Posted: Wed Oct 29, 2014 10:37 pm
by Nerwan
This is based on a Grimrock 1 autohotkey script but I've re-factored and greatly extended it for Grimrock 2. It provides support for:

* Swapping front and back characters on the left right using 9 and 0 (and optionally attacking with the character you've swapped in using "o" and "p")
* Attacking using key strokes for all characters left/right hands (j k m , for right hand)
* Switching weapon sets for each char F1-F4
* Spell casting using the number pad or by macro binding the spell combos
* The full list of key mappings are at the bottom of the file

You'll need to save this as "grimrock2.ahk" and run it with http://www.autohotkey.com/

It should work on all monitor resolutions, I'd be interested to hear peoples experiences on that one. If it doesn't work try control-S and control-T to see if the macro is properly circling the character boxes and the spell book runes.

Code: Select all

; Legend of Grimrock 2
;  Ultimate keyboard macros v 0.1
; Autohotkey Script
;    by Ben Staniford
; Original Grimrock 1 script by d32, techzen, Adakos
; Download Autohotkey at: http://www.autohotkey.com/
#SingleInstance force
SetTitleMatchMode, 2
CoordMode, Mouse,

; This must be set >0 or grimrock won't process the commands properly
SetDefaultMouseSpeed, 2

; ====================================================================================
; | Global Constants - these should be valid for all screen resolutions              |
; ====================================================================================

; Workaround.. For some reason, grimrock 2 doesn't register mouseclicks unless you do them multiple times..  8 seems to work reliably
clickMultiplier := 8

; Weapon lock after swap (imposed by grimrock 2)
weaponLockTime := 1000

; relative positions of weapon slots and character bars
dx1l := 0.14 ; X position of 1st and 3rd char left hand
dx2l := 0.62 ; X position of 2nd and 4th char left hand
dx1r := 0.34 ; X position of 1st and 3rd char right hand
dx2r := 0.81 ; X position of 2nd and 4th char right hand
dy1  := 0.32 ; Y position of 1st and 2nd char hands
dy2  := 0.79 ; Y position of 3rd and 4th char hands

; spell book run buttons
xSpellButton7 := 0
ySpellButton7 := 0
xSpellButton4 := 0
ySpellButton4 := 0
xSpellButton1 := 0
ySpellButton1 := 0
xSpellButton8 := 0
ySpellButton8 := 0
xSpellButton5 := 0
ySpellButton5 := 0
xSpellButton2 := 0
ySpellButton2 := 0
xSpellButton9 := 0
ySpellButton9 := 0
xSpellButton6 := 0
ySpellButton6 := 0
xSpellButton3 := 0
ySpellButton3 := 0
xSpellCastingButton := 0
ySpellCastingButton := 0

; character portraits locations
dy1b  := 0.15 ; Y position of 1st and 2nd char portrait
dy2b  := 0.63 ; Y position of 3rd and 4th char portrait

; Hand and position enumerations used as short hand by functions
Left := 1
Right := 2
TopLeft := 1
TopRight := 2
BottomLeft := 3
BottomRight := 4

; ====================================================================================
; | Helper Functions                                                                 |
; ====================================================================================

; reads the resolution, determins the corner of characters' rectangle
Init()
{
   global
   sysget, resX, 0
   sysget, resY, 1
   characterHeight := resY * 0.39
   characterWidth := characterHeight / 0.90
   
   characterX := resX-characterWidth
   characterY := resY-characterHeight

   ; First rune row in spell book (assuming wizard in bottom left)
   xSpellButton7 := characterX+(characterWidth*dx1l)
   ySpellButton7 := characterY+(characterHeight*(dy2 - 0.1))
   xSpellButton4 := characterX+(characterWidth*dx1l)
   ySpellButton4 := characterY+(characterHeight*dy2)
   xSpellButton1 := characterX+(characterWidth*dx1l)
   ySpellButton1 := characterY+(characterHeight*(dy2 + 0.1))
   
   ; Second row in spell book 
   xSpellButton8 := characterX+(characterWidth*(dx1l + 0.09))
   ySpellButton8 := characterY+(characterHeight*(dy2 - 0.1))
   xSpellButton5 := characterX+(characterWidth*(dx1l + 0.09))
   ySpellButton5 := characterY+(characterHeight*dy2)
   xSpellButton2 := characterX+(characterWidth*(dx1l + 0.09))
   ySpellButton2 := characterY+(characterHeight*(dy2 + 0.1))
   
   ; Third row in spell book 
   xSpellButton9 := characterX+(characterWidth*(dx1l + 0.18))
   ySpellButton9 := characterY+(characterHeight*(dy2 - 0.1))
   xSpellButton6 := characterX+(characterWidth*(dx1l + 0.18))
   ySpellButton6 := characterY+(characterHeight*dy2)
   xSpellButton3 := characterX+(characterWidth*(dx1l + 0.18))
   ySpellButton3 := characterY+(characterHeight*(dy2 + 0.1))
   
   ; Casting button in spell book 
   xSpellCastingButton := characterX+(characterWidth*(dx1l + 0.24))
   ySpellCastingButton := characterY+(characterHeight*dy2)
}

Init()

StoreMousePos() 
{
   global
   MouseGetPos, mX, mY
}

RestoreMousePos() 
{
   global
   MouseMove, mX, mY
}

RightClickAndReturn(x, y)
{
   global
   StoreMousePos()
   MouseClick, right, x, y, clickMultiplier, 2, D
   MouseClick, right, x, y, clickMultiplier, 2, U
   RestoreMousePos()
}

SwapAtX(xPos) 
{
   global
   BlockInput On

   srcX := xPos
   srcY := characterY+(characterHeight*dy2b)
   dstX := xPos
   dstY := characterY+(characterHeight*dy1b)

   ; Click, drag and unclick
   MouseMove, srcX, srcY
   MouseClick, left, srcX, srcY, clickMultiplier, 2, D
   MouseClickDrag, left, srcX, srcY, dstX, dstY
   MouseClick, left, dstX, dstY, clickMultiplier, 2, U

   BlockInput Off
}

AttackWithHand(Hand, Position)
{
	global
	xPos := 0
	yPos := 0

    StoreMousePos()

	if (Hand = Right)
	{
		if (Position = TopLeft)
		{
			xPos := dx1l
			yPos := dy1
		}
		else if (Position = TopRight)
		{
			xPos := dx2l
			yPos := dy1
		}
		else if (Position = BottomLeft)
		{
			xPos := dx1l
			yPos := dy2
		}
		else if (Position = BottomRight)
		{
			xPos := dx2l
			yPos := dy2
		}
	}
	else
	{
		if (Position = TopLeft)
		{
			xPos := dx1r
			yPos := dy1
		}
		else if (Position = TopRight)
		{
			xPos := dx2r
			yPos := dy1
		}
		else if (Position = BottomLeft)
		{
			xPos := dx1r
			yPos := dy2
		}
		else if (Position = BottomRight)
		{
			xPos := dx2r
			yPos := dy2
		}
	}

	RightClickAndReturn(characterX+(characterWidth*xPos),  characterY+(characterHeight*yPos))
	RestoreMousePos()
}

CharacterSwapWeapons(Position)
{
	global

	xPos := 0
	yPos := 0

	if (Position = TopLeft)
	{
		xPos := dx1r
		yPos := dy1
	}
	else if (Position = TopRight)
	{
		xPos := dx2r
		yPos := dy1
	}
	else if (Position = BottomLeft)
	{
		xPos := dx1r
		yPos := dy2
	}
	else if (Position = BottomRight)
	{
		xPos := dx2r
		yPos := dy2
	}

	xPix := characterX+(characterWidth*xPos)
	yPix := characterY+(characterHeight*yPos)

    StoreMousePos()
    MouseMove, xPix, yPix
	Send {x}
	Sleep 100
	RestoreMousePos()
}

Swap(Side)
{
   global
   StoreMousePos()

   if (Side = Left)
   {
      SwapAtX(characterX+(characterWidth*dx1r))
   }
   else
   {
      SwapAtX(characterX+(characterWidth*dx2r))
   }

   RestoreMousePos()
}

SwapAndAttack(Side)
{
   global

   Swap(Side)
   Sleep weaponLockTime

   if (Side = Left)
   {
      AttackWithHand(Right, TopLeft)
   }
   else
   {
      AttackWithHand(Right, TopRight)
   }
}

ClickSpellRune(KeyPadNumber)
{
	global 

	xPos := 0
	yPos := 0

	if (KeyPadNumber = 1)
	{
		xPos := xSpellButton1
		yPos := ySpellButton1
	}
	else if (KeyPadNumber = 2)
	{
		xPos := xSpellButton2
		yPos := ySpellButton2
	}
	else if (KeyPadNumber = 3)
	{
		xPos := xSpellButton3
		yPos := ySpellButton3
	}
	else if (KeyPadNumber = 4)
	{
		xPos := xSpellButton4
		yPos := ySpellButton4
	}
	else if (KeyPadNumber = 5)
	{
		xPos := xSpellButton5
		yPos := ySpellButton5
	}
	else if (KeyPadNumber = 6)
	{
		xPos := xSpellButton6
		yPos := ySpellButton6
	}
	else if (KeyPadNumber = 7)
	{
		xPos := xSpellButton7
		yPos := ySpellButton7
	}
	else if (KeyPadNumber = 8)
	{
		xPos := xSpellButton8
		yPos := ySpellButton8
	}
	else if (KeyPadNumber = 9)
	{
		xPos := xSpellButton9
		yPos := ySpellButton9
	}
	else if (KeyPadNumber = 0)
	{
		xPos := xSpellCastingButton
		yPos := ySpellCastingButton
	}

	RightClickAndReturn(xPos, yPos)
}

; Should draw a box around all 4 characters
RunCharacterBoxTest()
{
   global
   StoreMousePos()
   MouseMove, characterX, resY-10, 6
   MouseMove, characterX, characterY, 6
   MouseMove, resX-10, characterY, 6
   MouseMove, resX-10, resY-10, 6
   MouseMove, characterX, resY-10, 6
   RestoreMousePos()
}

; Should visit all the runes on the spell book
RunSpellBookTest()
{
   global

   StoreMousePos()
   
   ; Up the first row
   MouseMove, xSpellButton1, ySpellButton1, 6
   MouseMove, xSpellButton4, ySpellButton4, 6
   MouseMove, xSpellButton7, ySpellButton7, 6

   ; Down the second row
   MouseMove, xSpellButton8, ySpellButton8, 6
   MouseMove, xSpellButton5, ySpellButton5, 6
   MouseMove, xSpellButton2, ySpellButton2, 6

   ; Up the third row
   MouseMove, xSpellButton3, ySpellButton3, 6
   MouseMove, xSpellButton6, ySpellButton6, 6
   MouseMove, xSpellButton9, ySpellButton9, 6

   MouseMove, xSpellCastingButton, ySpellCastingButton, 6

   RestoreMousePos()
}

; ====================================================================================
; | Key mappings                                                                     |
; ====================================================================================
#IfWinActive Grimrock ahk_class EWindowClass

; Bring up the inventory 
i::1

; Attack commands for all character/hand combinations
j::AttackWithHand(Right, TopLeft)
k::AttackWithHand(Right, TopRight)
m::AttackWithHand(Right, BottomLeft)
,::AttackWithHand(Right, BottomRight)
l::AttackWithHand(Left, TopLeft)
`;::AttackWithHand(Left, TopRight)
.::AttackWithHand(Left, BottomLeft)
/::AttackWithHand(Left, BottomRight)

; Swap characters
9::Swap(Left)
0::Swap(Right)

; Swap characters and attack on either side
o::SwapAndAttack(Left)
p::SwapAndAttack(Right)

; Swap both left and right chars
Capslock::
{
   Swap(Left)
   Swap(Right)
   return
}

; Weapon set swap keys
f1::CharacterSwapWeapons(TopLeft)
f2::CharacterSwapWeapons(TopRight)
f3::CharacterSwapWeapons(BottomLeft)
f4::CharacterSwapWeapons(BottomRight)

; Num pad open the spell book by pressing +
NumpadAdd::AttackWithHand(Right, BottomLeft)
; Num pad access to the spell book (assumes wizard is bottom left and is using left hand for spells)
Numpad1::ClickSpellRune(1)
Numpad2::ClickSpellRune(2)
Numpad3::ClickSpellRune(3)
Numpad4::ClickSpellRune(4)
Numpad5::ClickSpellRune(5)
Numpad6::ClickSpellRune(6)
Numpad7::ClickSpellRune(7)
Numpad8::ClickSpellRune(8)
Numpad9::ClickSpellRune(9)
; Pressing 0 casts the spell
Numpad0::ClickSpellRune(0)

; Hit control-t to check that the character box constants are correct for your monitor (This will move the mouse around your character box)
^t::RunCharacterBoxTest()

; Hit control-s to check that the spell book constants are correct for your monitor (This will move the mouse around the spell book assuming your wizard is bottom left)
^s::RunSpellBookTest()

#IfWinActive

Re: Ultimate autohotkey macros for Grimlock 2

Posted: Thu Oct 30, 2014 3:35 pm
by Madeiner
Thanks for the script :D
Im downloading it right now.

I have a small request if you have time to do it.
I have difficulties using my righthand, and i would like a key to let every character (minus the mage) attack at once, or in quick sequence.
Any chance to do that? I'd put that macro like on the spacebar so i only have to use mouse every now and then.

Thanks


EDIT: Disregard last request.
For anyone looking for this, i added this code to his script:

Space::
{
AttackWithHand(Right, TopLeft)
AttackWithHand(Right, TopRight)
AttackWithHand(Right, BottomRight)
return
}

Re: Ultimate autohotkey macros for Grimlock 2

Posted: Thu Oct 30, 2014 8:52 pm
by sapientCrow
nice script m8!!
thanks

Re: Ultimate autohotkey macros for Grimlock 2

Posted: Fri Oct 31, 2014 2:05 am
by Nerwan
Thanks Madeiner, glad it worked for you and you managed to figure our your "all attack" mapping. I guess you could do something very similar with spells. So lightning bolt, for example would be:

Code: Select all

Space::
{
   ClickSpellRune(4)
   ClickSpellRune(5)
   ClickSpellRune(8)
   ClickSpellRune(9)
   ClickSpellRune(0)
   return
}

Re: Ultimate autohotkey macros for Grimlock 2

Posted: Fri Oct 31, 2014 3:36 pm
by Coroner
Space::
{
ClickSpellRune(4)
ClickSpellRune(5)
ClickSpellRune(8)
ClickSpellRune(9)
ClickSpellRune(0)
return
}
Nope, not working... would need to click and drag over them, not click and return.

But anyway, very nice script. Thanks!

Re: Ultimate autohotkey macros for Grimlock 2

Posted: Tue Nov 25, 2014 6:18 pm
by Locus
I would ***LOVE*** to run this autohotkey script, particularly after banging my head against making my own..

ATM, it doesn't appear to be detecting resolution correctly. I'm running Grimrock 2 on Steam and I've tried reloading the script both before and after launching to no avail. The CTRL S and CTRL T doesn't draw a box correctly (2560 x1600 resolution monitor/game). I've tried 1920/1080 1920/1200 and no luck.

Is there any thoughts on how I can get it to work?

Re: Ultimate autohotkey macros for Grimlock 2

Posted: Mon Dec 22, 2014 8:51 am
by torham
This script is really nice, particularly for someone like me who is suffering from a repetative strain injury in their mouse hand. However, like Locus, I have found that it does not work at every resolution. I took measurements at various resolutions but was not able to come up with a single scaling factor, as it seems to change even within the same aspect ratio. It's probably possible for someone smarter to figure it out or as a last resort the script could have a table of known values.

For all the resolutions I tested this scaler was the only value that needed changed:

Code: Select all

characterHeight := resY * 0.39
The way I have found the correct value to replace 0.39 is by taking a screenshot and measuring the y coordinate of the top of the character portraits. For 1600x1200 it would be about 822. Then using this value I subtracted it from my y resolution and divided the result by the y resolution: (1200 - 822) / 1200 = 0.315

Then I took the result and placed it into the script like so (again this is for 1600x1200):

Code: Select all

characterHeight := resY * 0.315
One other thing to make sure is that your desktop resolution matches the resolution in game when you load the script, and that you are playing in fullscreen mode.

I wasn't able to test at 1920x1200 since my monitor isn't that large.