Поддержка обратной связи геймпадом в WoT

от автора

Доброе время суток, уважаемое хабрасообщество!

Скоро будет год, как я играю на геймпаде в World of Tanks, если кто-то пропустил мимо, как я выбирал первый в своей жизни ПК для игры прошу сюда.

Играть геймпадом интересно, но разработчики не хотят официально поддерживать его, соответственно приходится всячески извращаться для получения «некоего функционала».

Начну с небольшого отступления и опишу подробности игры в WoT на геймпаде от «коробки 360».

Для более-менее вменяемой игры необходим Xpadder. Видео, как я настраивал

Как играть? Пример рядового боя

Отступление. Так как я не волшебник, а только учусь, очень много кода ниже было написано не мной, описание как и что делалось я писал по собственному пониманию, буду рад любым замечаниям и помощи.

Теперь к главному.
Всем известно, что во многих играх используется так называемая обратная связь с геймпадом или ForceFeedback, которая дает более глубокое погружение в игровой процесс за счет вибраций (едешь по кочкам — есть вибрация, получаешь удар от противника — есть вибрация и т.п.). Разумеется, что меня заинтересовала возможность получить виброотдачу на геймпаде в «танчиках».
Покопав данную тему нашел следующие моменты, позволяющие это сделать:
1. Всё что нужно пишется на python, именно этот язык используется в игре для выполнения всего и вся (думаю, что кроме ядра BigWorld).
2. На stackoverflow, нашел нужный мне код, позволяющий передавать вибрации в геймпад.
3. Wargaming уже встроил в свою игру вибрации для виброжопки вибронакидки GameTrix, сам проект вибронакидки является открытым и весь SDK и прочее доступно для изучения и скачивания.
4. Поиск по папкам игры выдал местонахождение скриптов «World_of_Tanks\res\scripts\client\vibroeffect».

Разобрав «по полочкам» SDK вибронакидки, скрипты игры — выходит, что с помощью XVM (eXtended Visualization Mod for World of Tanks), можно попробовать подменить BigWorld.WGVibration на GamePadVibration, непосредственно через который вызывать нужные методы вибрации непосредственно в геймпаде.
Но оказалось, что в игровом python нет полноценного ctypes, соответственно появилась необходимость сделать сервис, позволяющий выполнять «вибрирующий» скрипт на обычном python, получая команды из игры. Такой сервис был создан с помощью Flask. Ниже привожу код исполняемого скрипта, передающий вибрацию на геймпад при выстрелах, получении попаданий/рикошетов и повреждений танка. Виброэффекты писались мною из оригинальных для вибронакидки.

GPService.py

ENABLE_LOG = False d = dict() from flask import Flask from flask import request import ctypes  # Define necessary structures class XINPUT_VIBRATION(ctypes.Structure):     _fields_ = [("wLeftMotorSpeed", ctypes.c_ushort),                 ("wRightMotorSpeed", ctypes.c_ushort)]  xinput = ctypes.windll.xinput1_3  # Load Xinput.dll  # Set up function argument types and return type XInputSetState = xinput.XInputSetState XInputSetState.argtypes = [ctypes.c_uint, ctypes.POINTER(XINPUT_VIBRATION)] XInputSetState.restype = ctypes.c_uint  # You can also create a helper function like this: def set_vibration(controller, left_motor, right_motor):     vibration = XINPUT_VIBRATION(int(left_motor * 65535), int(right_motor * 65535))     XInputSetState(controller, ctypes.byref(vibration))      if not ENABLE_LOG:     import logging     log = logging.getLogger('werkzeug')     log.setLevel(logging.ERROR)  app = Flask(__name__)  @app.route('/connect') def connect():     print 'Connection checked - Ok.'     return 'True'  @app.route('/loadEffectFromFile') def loadEffectFromFile():     effectHandle = request.args.get('effectHandle')     fileName = request.args.get('fileName')     #print      #print 'loadEffectFromFile'     #print 'effectHandle =', request.args.get('effectHandle')     #print 'fileName =', request.args.get('fileName')     if not effectHandle in d:         d[effectHandle] = fileName     return ''      @app.route('/getEffectLength') def getEffectLength():     #print      #print 'getEffectLength'     #print 'effectHandle =', request.args.get('effectHandle')     #print 'returnedLength =', request.args.get('returnedLength')     return ''  import threading, time def shot_main():     set_vibration(0, 1.0, 1.0)     time.sleep(0.15)     set_vibration(0, 0, 0)     time.sleep(0.25)     set_vibration(0, 0.5, 0)     time.sleep(0.25)     set_vibration(0, 0.0, 0.0)  def shot_large():     set_vibration(0, 0.5, 0.6)     time.sleep(0.1)     set_vibration(0, 0.3, 0)     time.sleep(0.1)     set_vibration(0, 0.1, 0)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.5)     time.sleep(0.1)     set_vibration(0, 0.0, 0.0)      def shot_medium():     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0.6, 0.6)     time.sleep(0.1)     set_vibration(0, 0.4, 0)     time.sleep(0.1)     set_vibration(0, 0.2, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.6)     time.sleep(0.2)     set_vibration(0, 0.0, 0.0)  def shot_small():     set_vibration(0, 0.6, 0.8)     time.sleep(0.1)     set_vibration(0, 0, 0.4)     time.sleep(0.1)     set_vibration(0, 0.0, 0.0)  def hit_nonpenetration():     set_vibration(0, 0.5, 0.5)     time.sleep(0.1)     set_vibration(0, 0.0, 0.0)  def hit_ricochet():     set_vibration(0, 0.8, 0.8)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.2)     set_vibration(0, 0.0, 0.5)     time.sleep(0.05)     set_vibration(0, 0, 0)  def hit_splash():     set_vibration(0, 0.8, 0)     time.sleep(0.1)     set_vibration(0, 0.4, 0.8)     time.sleep(0.1)     set_vibration(0, 0, 0.4)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.05)     set_vibration(0, 0, 0.4)     time.sleep(0.15)     set_vibration(0, 0, 0)  def hit():     set_vibration(0, 1, 0)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.05)     set_vibration(0, 0, 1)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.05)     set_vibration(0, 0.6, 0.6)     time.sleep(0.1)     set_vibration(0, 0.5, 0.5)     time.sleep(0.05)     set_vibration(0, 0.25, 0.25)     time.sleep(0.05)     set_vibration(0, 0, 0)  def crit_contusion():     set_vibration(0, 1, 0)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.2)     set_vibration(0, 0, 0.4)     time.sleep(0.2)     set_vibration(0, 0, 0)     time.sleep(0.3)     set_vibration(0, 0, 0.3)     time.sleep(0.2)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0, 0.25)     time.sleep(0.2)     set_vibration(0, 0, 0)  def crit_death():     set_vibration(0, 1, 1)     time.sleep(0.1)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 1)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.8)     time.sleep(0.1)     set_vibration(0, 0.8, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.8)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0.5, 0.5)     time.sleep(0.1)     set_vibration(0, 0.4, 0.3)     time.sleep(0.1)     set_vibration(0, 0.3, 0.1)     time.sleep(0.1)     set_vibration(0, 0.2, 0)     time.sleep(0.1)     set_vibration(0, 0.1, 0)     time.sleep(0.1)     set_vibration(0, 0, 0)  def crit_engine():     set_vibration(0, 1, 0)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0.4, 0)     time.sleep(0.1)     set_vibration(0, 0.4, 0.6)     time.sleep(0.3)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.6)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0.4, 0.6)     time.sleep(0.1)     set_vibration(0, 0.4, 0)     time.sleep(0.1)     set_vibration(0, 0.4, 0.6)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.6)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0.4, 0.6)     time.sleep(0.1)     set_vibration(0, 0.4, 0)     time.sleep(0.2)     set_vibration(0, 0, 0)  def crit_fire():     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.3)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.5)     set_vibration(0, 0.5, 0.5)     time.sleep(2)     set_vibration(0, 0, 0)  def crit_run():     set_vibration(0, 1, 0)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.2)     set_vibration(0, 0, 0.8)     time.sleep(0.3)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.5)     time.sleep(0.3)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0.8, 0)     time.sleep(0.1)     set_vibration(0, 0.6, 0)     time.sleep(0.1)     set_vibration(0, 0.4, 0)     time.sleep(0.1)     set_vibration(0, 0.2, 0)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0.4, 0)     time.sleep(0.1)     set_vibration(0, 0.2, 0)     time.sleep(0.1)     set_vibration(0, 0.1, 0)     time.sleep(0.1)     set_vibration(0, 0, 0)  def crit_track_move():     set_vibration(0, 0.6, 0.2)     time.sleep(0.1)     set_vibration(0, 0, 0.1)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.4)     set_vibration(0, 0, 0.1)     time.sleep(0.1)     set_vibration(0, 0, 0.05)     time.sleep(0.1)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.1)     time.sleep(0.1)     set_vibration(0, 0, 0.05)     time.sleep(0.1)     set_vibration(0, 0, 0)  def crit_track():     set_vibration(0, 0.8, 0)     time.sleep(0.05)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0, 0.8)     time.sleep(0.05)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0.5, 0.5)     time.sleep(0.05)     set_vibration(0, 0, 0)     time.sleep(0.1)     set_vibration(0, 0.5, 0.5)     time.sleep(0.05)     set_vibration(0, 0, 0)          @app.route('/startEffect') def startEffect():     handle = request.args.get('handle')     count = request.args.get('count')          if handle in d:         print 'startEffect: '+ d[handle]         if 'shot_main' in d[handle]:             print 'shot_main'             # init thread             t1 = threading.Thread(target=shot_main)             # start threads             t1.start()                                 if 'shot_large' in d[handle]:             print 'shot_large'             # init thread             t1 = threading.Thread(target=shot_large)             # start threads             t1.start()                       if 'shot_medium' in d[handle]:             print 'shot_medium'             # init thread             t1 = threading.Thread(target=shot_medium)             # start threads             t1.start()                      if 'shot_small' in d[handle]:                 print 'shot_small'             # init thread             t1 = threading.Thread(target=shot_small)             # start threads             t1.start()                      if 'hit_nonpenetration' in d[handle]:                 print 'hit_nonpenetration'             # init thread             t1 = threading.Thread(target=hit_nonpenetration)             # start threads             t1.start()                      if 'hit_ricochet' in d[handle]:                 print 'hit_ricochet'             # init thread             t1 = threading.Thread(target=hit_ricochet)             # start threads             t1.start()                      if 'hit_splash' in d[handle]:                 print 'hit_splash'             # init thread             t1 = threading.Thread(target=hit_splash)             # start threads             t1.start()                      if 'hit' in d[handle]:                 print 'hit'             # init thread             t1 = threading.Thread(target=hit)             # start threads             t1.start()          if 'crit_contusion' in d[handle]:                 print 'crit_contusion'             # init thread             t1 = threading.Thread(target=crit_contusion)             # start threads             t1.start()          if 'crit_death' in d[handle]:                 print 'crit_death'             # init thread             t1 = threading.Thread(target=crit_death)             # start threads             t1.start()          if 'crit_engine' in d[handle]:                 print 'crit_engine'             # init thread             t1 = threading.Thread(target=crit_engine)             # start threads             t1.start()          if 'crit_fire' in d[handle]:                 print 'crit_fire'             # init thread             t1 = threading.Thread(target=crit_fire)             # start threads             t1.start()          if 'crit_run' in d[handle]:                 print 'crit_run'             # init thread             t1 = threading.Thread(target=crit_run)             # start threads             t1.start()          if 'crit_track_move' in d[handle]:                 print 'crit_track_move'             # init thread             t1 = threading.Thread(target=crit_track_move)             # start threads             t1.start()          if 'crit_track' in d[handle]:                 print 'crit_track'             # init thread             t1 = threading.Thread(target=crit_track)             # start threads             t1.start()                               # print      # print 'startEffect'     # print 'handle =', handle     # print 'count =', count          return ''  if __name__ == "__main__":     app.debug = True     app.run() 

Для интересующихся добавляю ссылку на полный мод, который необходимо распаковать в папку с игрой.

Так же для работы вибраций на геймпаде:
1. Необходимо установить Python (последняя версия), папка пусть будет по-умолчанию «C:\Python27».

2. Необходимо установить Flask, я делал по этой инструкции. Качаем файл distribute_setup.py в папку «C:\temp» (например), запускаем консоль «Win+R» — cmd и выполняем команду «C:\Python27\python.exe C:\temp\distribute_setup.py» наблюдаем процесс загрузки нужных файлов в папку «C:\Python27\Scripts», далее в консоли запускаем команды по очереди и наблюдаем их выполнение:
C:\Python27\python.exe C:\Python27\Scripts\easy_install-2.7-script.py Flask
C:\Python27\python.exe C:\Python27\Scripts\easy_install-2.7-script.py Jinja2
C:\Python27\python.exe C:\Python27\Scripts\easy_install-2.7-script.py Werkzeug
C:\Python27\python.exe C:\Python27\Scripts\easy_install-2.7-script.py Virtualenv

В файле GPsettings.xml прописан скрытый запуск системного python из папки по умолчанию «C:\Python27».

В настоящее время я тестирую скрипт для забора эффектов напрямую из файлов для вибронакидки со смешиванием эффектов.

Удачи всем в боях!

ссылка на оригинал статьи http://habrahabr.ru/post/208478/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *