SkyTemple / py-desmume

Python Library and GUI for DeSmuME, the Nintendo DS emulator
GNU General Public License v3.0
24 stars 5 forks source link

keypad_add_key not working with "easy way of running the emulator" #5

Open ghost opened 3 years ago

ghost commented 3 years ago

When running the emulator in the window, I am not able to interact with it with the "keypad_add_key" and "keypad_rm_key", but the "touch_set_pos" seems to work as intended. When querying the keys (keypad_get()) it seems to be registering the "keypad_add_key" but the game does not seem to progress according to the keys I press. The script seems to work fine if it is headless (without the window it seems to register them) and it registers keys fine when pressing them on the keyboard during gameplay (in the window). Could you help?

from desmume.emulator import DeSmuME
from desmume.controls import keymask, Keys

emu = DeSmuME()
emu.open('D:\\Dropbox\\Speed Run\\DeSmuME 9.11\\Roms\\ROMs\\NA\\0561 - Harvest Moon DS (U)(Legacy).nds')

window = emu.create_sdl_window()

frame = 0
while not window.has_quit():

    frame += 1

    if frame == 405:
        emu.input.touch_set_pos(100, 100)

    if frame == 406:
        emu.input.touch_release()

    if frame == 405+150:
        emu.input.touch_set_pos(100, 100)

    if frame == 406+150:
        emu.input.touch_release()    

    if frame == 1500:
        a_keymask = keymask(Keys.KEY_A)
        emu.input.keypad_add_key(a_keymask)
        print("Press A")

    if frame == 1502:
        a_keymask = keymask(Keys.KEY_A)
        emu.input.keypad_rm_key(a_keymask)

    window.process_input()
    emu.cycle()
    window.draw()
theCapypara commented 3 years ago

Heya! So there are three things going on here:

  1. a bug in the documentation. The keymask command isn't actually required in this case, since the constant already contains the keymask value. The keymask function is only for getting a key by it's index number. It's a bit weird how this was set up and I just inherited it, which is why I got confused in the docs. You said this was working for you without the SDL windows? Maybe I need to look it up again and both is actually possible, it's been a while...
  2. For some reasons the emulator begins with all keys pressed, so you need to unpress A first.
  3. window.process_input will override the keymap with the user input for the SDL window, so you need to not call it if you want to do input manually.

Here's the code that's working for me:

from desmume.emulator import DeSmuME
from desmume.controls import Keys

emu = DeSmuME()
emu.open('/home/marco/dev/skytemple/skytemple/skyworkcopy.nds')

window = emu.create_sdl_window()

frame = 0
emu.input.keypad_rm_key(Keys.KEY_A)
while not window.has_quit():

    frame += 1

    if frame == 1660:
        emu.input.keypad_add_key(Keys.KEY_A)
        print("Press A")

    if frame == 1662:
        emu.input.keypad_rm_key(Keys.KEY_A)

    emu.cycle()
    window.draw()
ghost commented 3 years ago

Hello again!

Thank you very much, the last part with the window.process_input was my issue (and maybe the pre-pressed A also), but I had to modify your code a bit since it seems that window.process_input() is necessary. Without it, the touch_set_pos crashes the game (black screen). Also, with or without the touch_set_pos, the "quit" (i.e. the X) fails to work if you don't include window.process_input(). Therefore, I included it after my touch screen interactions, but before my keyboard interactions, every frame.

Also, I verified and it seems that it works with or without keymask. It makes sense that this would work when I was trying headless since I was removing the window.process_input().

Here is what I ended up using as a test:

from desmume.emulator import DeSmuME
from desmume.controls import keymask, Keys

emu = DeSmuME()
emu.open('D:\\Dropbox\\Speed Run\\DeSmuME 9.11\\Roms\\ROMs\\NA\\0561 - Harvest Moon DS (U)(Legacy).nds')

window = emu.create_sdl_window()

frame = 0
emu.input.keypad_rm_key(Keys.KEY_A)
while not window.has_quit():

    frame += 1

    if frame == 405:
        emu.input.touch_set_pos(100, 100)

    if frame == 406:
        emu.input.touch_release()

    if frame == 405+150:
        emu.input.touch_set_pos(100, 100)

    if frame == 406+150:
        emu.input.touch_release()    

    window.process_input()

    if frame == 1660:
        emu.input.keypad_add_key(Keys.KEY_A)
        print("Press A")

    if frame == 1662:
        emu.input.keypad_rm_key(Keys.KEY_A)

    emu.cycle()
    window.draw()
Minabsapi commented 2 years ago

I personally can't get the custom key processing working correctly. Here's a simplified extract of my program using it (which is intended to fully handle keypress processing, not just arbitrarily enter keys):

from desmume.emulator import DeSmuME
from desmume.controls import keymask, Keys
from blessed import Terminal # Using blessed for rendering as well as keypress handling

term = Terminal()
emu = DeSmuME()
emu.open("/home/minabsapi/Lab/Projects/0008/files/basis/ROMs + saves/POKEDUN_SORA_C2SP01_00.nds")   # Replace with your ROM path
emu_win = emu.create_sdl_window()

frame = 0
running = True
halt = False
emu_keys_map = {
    "KEY_UP" : Keys.KEY_UP, 
    "KEY_DOWN" : Keys.KEY_DOWN, 
    "KEY_LEFT" : Keys.KEY_LEFT, 
    "KEY_RIGHT" : Keys.KEY_RIGHT, 
    "8" : Keys.KEY_X, 
    "4" : Keys.KEY_Y, 
    "2" : Keys.KEY_B, 
    "6" : Keys.KEY_A, 
    "7" : Keys.KEY_L, 
    "9" : Keys.KEY_R, 
    "1" : Keys.KEY_START, 
    "3" : Keys.KEY_SELECT,
}
emu_pressed_key = Keys.KEY_NONE
emu_pressed_key_frame = 0

# ...

got_key = lambda key: key.name if key.is_sequence else key

def clear():
    global term
    print(term.clear + term.home)

# ...

# ...

# for x in emu_keys_map:
#   emu.input.keypad_rm_key(emu_keys_map[x])

# ...

with term.cbreak():
    while not halt:
        # ...
        key = term.inkey(timeout=0.0075)
        if key:
            if got_key(key) == 'KEY_ESCAPE':
                clear()
                print(
"""
Are you sure you want to exit?

y: yes
n: no
"""
                )
                key = term.inkey()
                while got_key(key) not in ['y', 'n']:
                    key = term.inkey()
                if got_key(key) == 'y':
                    halt = True
                    break
                clear()
            elif got_key(key) == '0':   # For debugging purpose
                emu.input.keypad_rm_key(Keys.NO_KEY_SET)
            if running:
                try:
                    emu_pressed_key = emu_keys_map[got_key(key)]
                    emu.input.keypad_add_key(emu_pressed_key)
                    emu_pressed_key_frame = frame
                except KeyError:
                    pass
        # ...
        emu.cycle()
        emu_win.draw()
        frame += 1
        if frame - emu_pressed_key_frame == 4:
            emu.input.keypad_rm_key(emu_pressed_key)

Trying this on the Pokémon Mystery Dungeon: Explorers of Sky ROM, this configuration creates a buggy mess where

theCapypara commented 2 years ago

Oof, this is all very odd. I'll have to see when I find some time to investigate your issue.

Minabsapi commented 2 years ago

Meanwhile I investigated myself and found what's wrong.

First,

The keymask command isn't actually required in this case, since the constant already contains the keymask value

Referring to the code (check full implementation):

https://github.com/SkyTemple/py-desmume/blob/1e347cbd0e4ea95257c430e1471982c437d16ead/desmume/controls.py#L46 https://github.com/SkyTemple/py-desmume/blob/1e347cbd0e4ea95257c430e1471982c437d16ead/desmume/controls.py#L106 https://github.com/SkyTemple/py-desmume/blob/1e347cbd0e4ea95257c430e1471982c437d16ead/desmume/emulator.py#L173

It isn't the case, the keymask method is actually required, since the Keys class acting as an enumeration doesn't hold the actual keypad mask value, but rather the index of the keypad variable bit to change counting from 1

This brings us to the actual problem: upon multiple testing and playing around keypad_update I found out the odd behavior was because of the keymask method giving an incorrect value because of a little mistake. The shifting was smart, but as you started your enumeration from 1 instead of 0 every keypad mask output got their value shifted, acting as the next key rather than the one intended. That's the reason why A wasn't working when I used it and worked without. Because coincidentally the A key value in your enum and the keypad mask are both 1.

Although I think the existence of keymask should be reconsidered (hardcoding power of 2 in Keys shouldn't hurt :p) I'll just commit a small fix on it so everything works again

Minabsapi commented 2 years ago

Not all problems are fixed, though. For some reason I still need to call process_input() (which basically effects in resetting the keypad) every iteration to avoid getting stuck. After some adaptations this is the only way I got everything working smoothly. An evil bringing some good, as that forces touchscreen support for me