0

This is an issue I've ran into before, but I've always given up solving the problem and worked out a work around. Not today (hopefully).

I'm trying to make a bot for the classic Doom II. I want my bot to have access to the main menu which is accessed via the escape key. Naturally I tried:

sendkeys.send("{ESC}")

No luck. But then something weird happened. I accidently ran the code when I was already on the menu... and it closed the menu (which is normal if you press escape on the menu). So clearly Doom II listens to Sendkeys.

I've since tried sendinput, postmessage, and simulateinput. None have worked (they all have the same behaviour as described with sendkeys).

It would be great if someone could ride in on a white horse and give me code to get around this issue, but outside of that can any one simply explain this behaviour to me?

FraserOfSmeg
  • 1,128
  • 2
  • 23
  • 41
  • 1
    Ah, Doom II... One of the old classics... I really love that game :). -- Off-topics aside, are you running the DOS version of Doom II or the Windows 95 version? Because the problem _could_ be with the DOS emulator if you're using one. – Visual Vincent Oct 01 '16 at 16:54
  • 1
    @visualVincent it really is a classic, it's just a shame I'm so bad at it! I'm actually running it using Zandronum. I can't for the life of me figure out why escape will close the menu but not open it! – FraserOfSmeg Oct 01 '16 at 17:09
  • 1
    That's good, because I use Zandronum too! If you would've said DOS it'd been harder for me to help because I only have Doom95 and Zan. -- I'll try it out with both `SendKeys` and `SendInput`. Are you running in fullscreen mode or windowed mode? – Visual Vincent Oct 01 '16 at 17:13
  • 1
    @FraserOfSmeg In game, the game might test the keyboard state rather than process Windows messages. – Andrew Morton Oct 01 '16 at 17:20
  • 1
    @AndrewMorton : I believe it does, it is runs on OpenGL or a the classic Software renderer (you may choose renderer yourself). Either way `SendInput` _should_ work. – Visual Vincent Oct 01 '16 at 17:26
  • 1
    Hmm, I see what you mean. As you say the menu closes but you cannot open it... That's rather strange actually. I used WinAPI's `SendInput` by the way. – Visual Vincent Oct 01 '16 at 17:29
  • 1
    @VisualVincent If the game is going "hey keyboard! what keys are pressed *right now*?" and not going "hey my window message queue! Got anything for me? Ignore the keypress stuff" then any *Windows* message about the keyboard will be ignored. – Andrew Morton Oct 01 '16 at 17:33
  • 1
    @AndrewMorton : But `SendInput` does not use Window messages. According to the [**MSDN**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx): _"The SendInput function inserts the events in the INPUT structures serially into the keyboard or mouse input stream"_. -- Then again, it works for closing the menu but not opening it. – Visual Vincent Oct 01 '16 at 17:36
  • 1
    Okay I seem to have found a solution to this... Let me write my answer (it can take a little while to write it, there's some info to explain). – Visual Vincent Oct 01 '16 at 18:03
  • Sorry, just got back to my PC. I'm running in windowed mode. Thanks for all the support guys! I'm going to head to bed now, I'll check back tomorrow for the answer @VisualVincent! – FraserOfSmeg Oct 01 '16 at 18:18

1 Answers1

4

It seems that Zandronum does not accept virtual keys to be sent to it when the game is running (not paused). I'm not sure but it seems that virtual keys might actually be window messages, like Andrew Morton said (or they're at least something similar...). The workaround to this was to send a hardware scan code instead of a virtual key code.

A hardware scan code appears to be the code sent by the actual keyboard when pressing a key, while a virtual key code is the key which the system interprets from the scan code (reference).

So I managed to send keystrokes to Zandronum (both fullscreen and windowed) using a few WinAPI functions:

  • SendInput() which is used to send the actual keyboard input.
  • MapVirtualKeyEx() which is used to convert key codes to scan codes, or vice versa.
  • GetKeyboardLayout() which is used to get the user's current keyboard layout (I, for example, have a Swedish keyboard).

By using the below helper class (or more correctly: wrapper) that I built you may now send keystrokes (hardware or not) in a simple manner, with a larger variety of keys than what SendKeys.Send() includes. You may use any key in the System.Windows.Forms.Keys enumeration.

This was tested with Zandronum and works completely:

InputHelper.Keyboard.PressKey(Keys.Escape, True) 'True = Send key as hardware scan code.

EDIT (2019-09-20)

InputHelper has since long been moved to its own library. The answer has been updated to reflect this change.

Download InputHelper from GitHub:
https://github.com/Visual-Vincent/InputHelper/releases

Just for fun, I also managed to find a list of scan codes on the MSDN: https://msdn.microsoft.com/en-us/library/aa299374(v=vs.60).aspx


Since I'm a Doom fan myself and familiar with how it works, perhaps you should (per your old question) also make sure that you have selected New Game in the menu before you make it press enter?

Zandronum is aware of the names of the menu items, so you just have to give it the first letter and it will jump to the item starting with it:

InputHelper.Keyboard.PressKey(Keys.Escape, True) 'Open the menu.
System.Threading.Thread.Sleep(100)      'Small delay to let the menu open.
InputHelper.Keyboard.PressKey(Keys.N, True)      'Jump to the "New Game" menu item.
InputHelper.Keyboard.PressKey(Keys.Enter, True)  'Go into the "New Game" menu.
InputHelper.Keyboard.PressKey(Keys.Enter, True)  'Start a new game.

I've tested the above code in-game, running in fullscreen mode. Works like a charm.

Visual Vincent
  • 18,045
  • 5
  • 28
  • 75
  • This is not only exactly what I was looking for, but it's such a nice and neat implementation! Thanks! – FraserOfSmeg Oct 02 '16 at 03:34
  • this is what I was looking for! It's perfect! Thanks! – FraserOfSmeg Oct 02 '16 at 08:04
  • I said: "this is not *only* exactly..." I meant you gave me code that does exactly what I want and it was a great implementation! On a side note - do you know any way to get the player coordinates in doom2? – FraserOfSmeg Oct 02 '16 at 08:24
  • @FraserOfSmeg : Oooooh... I have completely missed that literally every time I've read the comment. Well good then :). _(I removed my two previous comments to keep this tidy)_ -- I don't know how to get the player coordinates from another process, no. You would have to read the Zandronum process's memory in order to get that (which can be rather tricky, since you must also find _where_ in memory the coordinates reside). Here's a little introduction: http://www.codeproject.com/Articles/716227/Csharp-How-to-Scan-a-Process-Memory – Visual Vincent Oct 02 '16 at 08:30
  • thanks. I've actually got my own code for memory editing from a previous game bot. I was just hoping that there might be a way to get the coords up in the console (so I can find the base memory address easier). Thanks anyhow! – FraserOfSmeg Oct 02 '16 at 09:35
  • @FraserOfSmeg : Oh, well that's indeed possible. Type `idmypos 1` in the console, or just `idmypos` on the keyboard (without the console). – Visual Vincent Oct 02 '16 at 10:05
  • @FraserOfSmeg : By the way, [see this other answer of mine](http://stackoverflow.com/a/39811354/3740093) for an example on how you can hold keys down. For Zandronum you'd have to use `SetHardwareKeyState()` instead of `SetKeyState()`. – Visual Vincent Oct 02 '16 at 11:51
  • I couldn't understand why It doesn't work in KinghtOnline – Murat Can OĞUZHAN Aug 16 '20 at 22:47
  • Does this implementation work on application running in background? – RoastDuck Mar 05 '22 at 05:38
  • Hello @RoastDuck, sorry for the late reply. Yes, InputHelper may work on background applications, but simulating input to non-foreground apps is a bit more difficult. You'd have to use the `InputHelper.WindowMessages.SendKeyPress()` method instead and provide a window handle to the **exact** window/text field/control you want to send the input to. Spy++, which can be installed with Visual Studio, can help you find the handle(s) you are looking for, but note that they will be different every time you open the app/window in question. You will need some programmatic way of locating them as well. – Visual Vincent Apr 24 '22 at 10:29