Skip to content

Instantly share code, notes, and snippets.

@fritz-c
Last active February 28, 2024 04:56
Show Gist options
  • Save fritz-c/7c0f5994a9bb21f4d93b58eca20882a8 to your computer and use it in GitHub Desktop.
Save fritz-c/7c0f5994a9bb21f4d93b58eca20882a8 to your computer and use it in GitHub Desktop.
(PC) Final Fantasy IX - Jump rope minigame automatic jumping script (computer vision based)

FF9 Jump Rope Jumper

The jumping script in action

Setup

Download the zip for this gist, extract it into a local directory, open up the command line and navigate to that directory.

python3 -m pip install -U --user Pillow mss

Usage

Open FF9 in windowed mode, 800x600 resolution, and move it to the very top-left corner of the screen, as close as you possibly can. Approach the rope jumping minigame and initiate it, but don't press anything when the first exclamation mark comes up

From the directory with the .py files, in the command line, run

python3 rope_jumper.py

and then click back to the game window so the program can input keyboard presses.

import time
import mss
from trigger_key import PressKey, ReleaseKey, VK_X
from PIL import Image, ImageStat
SCREEN_LOCATION = {"top": 290, "left": 295, "width": 25, "height": 30}
COLOR_MINIMUM_THRESHOLD = 225
KEYPRESS_DURATION_SEC = 0.01
sct = mss.mss()
def check_if_the_time_is_right():
sct_img = sct.grab(SCREEN_LOCATION)
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
extrema = ImageStat.Stat(img).extrema
return extrema[1][1] > COLOR_MINIMUM_THRESHOLD
def rope_jumper():
jump_count = 0
time_was_right_last_time = False
time_since = time.time()
while True:
the_time_is_right = check_if_the_time_is_right()
if (the_time_is_right and not time_was_right_last_time) or time.time() - time_since > 1:
time_since = time.time()
PressKey(VK_X)
time.sleep(KEYPRESS_DURATION_SEC)
ReleaseKey(VK_X)
jump_count += 1
print(f"{jump_count}x you're welcome")
time_was_right_last_time = the_time_is_right
rope_jumper()
# Code from https://stackoverflow.com/a/13615802
import ctypes
from ctypes import wintypes
import time
user32 = ctypes.WinDLL("user32", use_last_error=True)
INPUT_MOUSE = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2
KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP = 0x0002
KEYEVENTF_UNICODE = 0x0004
KEYEVENTF_SCANCODE = 0x0008
MAPVK_VK_TO_VSC = 0
# msdn.microsoft.com/en-us/library/dd375731
VK_TAB = 0x09
VK_MENU = 0x12
# C Key
VK_C = 0x43
# X Key
VK_X = 0x58
# C struct definitions
wintypes.ULONG_PTR = wintypes.WPARAM
class MOUSEINPUT(ctypes.Structure):
_fields_ = (
("dx", wintypes.LONG),
("dy", wintypes.LONG),
("mouseData", wintypes.DWORD),
("dwFlags", wintypes.DWORD),
("time", wintypes.DWORD),
("dwExtraInfo", wintypes.ULONG_PTR),
)
class KEYBDINPUT(ctypes.Structure):
_fields_ = (
("wVk", wintypes.WORD),
("wScan", wintypes.WORD),
("dwFlags", wintypes.DWORD),
("time", wintypes.DWORD),
("dwExtraInfo", wintypes.ULONG_PTR),
)
def __init__(self, *args, **kwds):
super(KEYBDINPUT, self).__init__(*args, **kwds)
# some programs use the scan code even if KEYEVENTF_SCANCODE
# isn't set in dwFflags, so attempt to map the correct code.
if not self.dwFlags & KEYEVENTF_UNICODE:
self.wScan = user32.MapVirtualKeyExW(self.wVk, MAPVK_VK_TO_VSC, 0)
class HARDWAREINPUT(ctypes.Structure):
_fields_ = (
("uMsg", wintypes.DWORD),
("wParamL", wintypes.WORD),
("wParamH", wintypes.WORD),
)
class INPUT(ctypes.Structure):
class _INPUT(ctypes.Union):
_fields_ = (("ki", KEYBDINPUT), ("mi", MOUSEINPUT), ("hi", HARDWAREINPUT))
_anonymous_ = ("_input",)
_fields_ = (("type", wintypes.DWORD), ("_input", _INPUT))
LPINPUT = ctypes.POINTER(INPUT)
def _check_count(result, func, args):
if result == 0:
raise ctypes.WinError(ctypes.get_last_error())
return args
user32.SendInput.errcheck = _check_count
user32.SendInput.argtypes = (
wintypes.UINT, # nInputs
LPINPUT, # pInputs
ctypes.c_int,
) # cbSize
# Functions
def PressKey(hexKeyCode):
x = INPUT(type=INPUT_KEYBOARD, ki=KEYBDINPUT(wVk=hexKeyCode))
user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))
def ReleaseKey(hexKeyCode):
x = INPUT(
type=INPUT_KEYBOARD, ki=KEYBDINPUT(wVk=hexKeyCode, dwFlags=KEYEVENTF_KEYUP)
)
user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))
@Clayboy692006
Copy link

I'm super late, but every time I run it Vivi only jumps twice and then fails on the third every time. What's going wrong? Is it because I'm playing the xbox pc version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment