TwitchPlays_TEMPLATE.py
for Python 3.x
[ published on 2021-12-16 ]

  1. # Written by DougDoug and DDarknut

  2.  

  3. # Hello! This file contains the main logic to process Twitch chat and convert it to game commands.

  4. # The code is written in Python 3.X

  5. # There are 2 other files needed to run this code:

  6.     # TwitchPlays_KeyCodes.py contains the key codes and functions to press keys in-game. You should not modify this file.

  7.     # TwitchPlays_Connection.py is the code that actually connects to Twitch. You should not modify this file.

  8.  

  9. # The source code primarily comes from:

  10.     # Wituz's "Twitch Plays" tutorial: http://www.wituz.com/make-your-own-twitch-plays-stream.html

  11.     # PythonProgramming's "Python Plays GTA V" tutorial: https://pythonprogramming.net/direct-input-game-python-plays-gta-v/

  12.     # DDarknut's message queue and updates to the Twitch networking code

  13.  

  14. # Disclaimer: 

  15.     # This code is NOT intended to be professionally optimized or organized.

  16.     # We created a simple version that works well for livestreaming, and I'm sharing it for educational purposes.

  17.  

  18. ##########################################################

  19.  

  20. TWITCH_CHANNEL = 'dougdougw' # Replace this with your Twitch username. Must be all lowercase.

  21.  

  22. ##########################################################

  23.  

  24. import keyboard

  25. import TwitchPlays_Connection

  26. import pydirectinput

  27. import random

  28. import pyautogui

  29. import concurrent.futures

  30. from TwitchPlays_KeyCodes import *

  31.  

  32. ##########################################################

  33.  

  34. # MESSAGE_RATE controls how fast we process incoming Twitch Chat messages. It's the number of seconds it will take to handle all messages in the queue.

  35. # This is used because Twitch delivers messages in "batches", rather than one at a time. So we process the messages over MESSAGE_RATE duration, rather than processing the entire batch at once.

  36. # A smaller number means we go through the message queue faster, but we will run out of messages faster and activity might "stagnate" while waiting for a new batch. 

  37. # A higher number means we go through the queue slower, and messages are more evenly spread out, but delay from the viewers' perspective is higher.

  38. # You can set this to 0 to disable the queue and handle all messages immediately. However, then the wait before another "batch" of messages is more noticeable.

  39. MESSAGE_RATE = 0.5

  40. # MAX_QUEUE_LENGTH limits the number of commands that will be processed in a given "batch" of messages. 

  41. # e.g. if you get a batch of 50 messages, you can choose to only process the first 10 of them and ignore the others.

  42. # This is helpful for games where too many inputs at once can actually hinder the gameplay.

  43. # Setting to ~50 is good for total chaos, ~5-10 is good for 2D platformers

  44. MAX_QUEUE_LENGTH = 20  

  45. MAX_WORKERS = 100 # Maximum number of threads you can process at a time 

  46.  

  47. last_time = time.time()

  48. message_queue = []

  49. thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS)

  50. active_tasks = []

  51. pyautogui.FAILSAFE = False

  52.  

  53. ##########################################################

  54.  

  55. # An optional count down before starting, so you have time to load up the game

  56. countdown = 10

  57. while countdown > 0:

  58.     print(countdown)

  59.     countdown -= 1

  60.     time.sleep(1)

  61. t = TwitchPlays_Connection.Twitch();

  62. t.twitch_connect(TWITCH_CHANNEL);

  63. def handle_message(message):

  64.     try:

  65.         msg = message['message'].lower()

  66.         username = message['username'].lower()

  67.  

  68.         print("Got the message: [" + msg + "] from user [" + username + "]")

  69.  

  70.         # Now that you have a chat message, this is where you add your game logic.

  71.         # Use the "HoldKey(KEYCODE)" function to press and hold down a keyboard key.

  72.         # Use the "ReleaseKey(KEYCODE)" function to release a specific keyboard key.

  73.         # Use the "HoldAndReleaseKey(KEYCODE, SECONDS)" function press down a key for X seconds, then release it.

  74.         # Use the pydirectinput library to press or move the mouse

  75.  

  76.         # I've added some example videogame logic code below:

  77.  

  78.         ###################################

  79.         # Example GTA V Code 

  80.         ###################################

  81.  

  82.         # If the chat message is "left", then hold down the A key for 2 seconds

  83.         if msg == "left"

  84.             HoldAndReleaseKey(A, 2)

  85.  

  86.         # If the chat message is "right", then hold down the D key for 2 seconds

  87.         if msg == "right"

  88.             HoldAndReleaseKey(D, 2)

  89.  

  90.         # If message is "drive", then permanently hold down the W key

  91.         if msg == "drive"

  92.             ReleaseKey(S) #release brake key first

  93.             HoldKey(W) #start permanently driving

  94.  

  95.         # If message is "reverse", then permanently hold down the S key

  96.         if msg == "reverse"

  97.             ReleaseKey(W) #release drive key first

  98.             HoldKey(S) #start permanently reversing

  99.  

  100.         # Release both the "drive" and "reverse" keys

  101.         if msg == "stop"

  102.             ReleaseKey(W)

  103.             ReleaseKey(S)

  104.  

  105.         # Press the spacebar for 0.7 seconds

  106.         if msg == "brake"

  107.             HoldAndReleaseKey(SPACE, 0.7)

  108.  

  109.         # Press the left mouse button down for 1 second, then release it

  110.         if msg == "shoot"

  111.             pydirectinput.mouseDown(button="left")

  112.             time.sleep(1)

  113.             pydirectinput.mouseUp(button="left")

  114.  

  115.         ####################################

  116.         ####################################

  117.  

  118.     except Exception as e:

  119.         print("Encountered exception: " + str(e))

  120.  

  121. while True:

  122.  

  123.     active_tasks = [t for t in active_tasks if not t.done()]

  124.  

  125.     #Check for new messages

  126.     new_messages = t.twitch_receive_messages();

  127.     if new_messages:

  128.         message_queue += new_messages; # New messages are added to the back of the queue

  129.         message_queue = message_queue[-MAX_QUEUE_LENGTH:] # Shorten the queue to only the most recent X messages

  130.  

  131.     messages_to_handle = []

  132.     if not message_queue:

  133.         # No messages in the queue

  134.         last_time = time.time()

  135.     else:

  136.         # Determine how many messages we should handle now

  137.         r = 1 if MESSAGE_RATE == 0 else (time.time() - last_time) / MESSAGE_RATE

  138.         n = int(r * len(message_queue))

  139.         if n > 0:

  140.             # Pop the messages we want off the front of the queue

  141.             messages_to_handle = message_queue[0:n]

  142.             del message_queue[0:n]

  143.             last_time = time.time();

  144.  

  145.     # If user presses Shift+Backspace, automatically end the program

  146.     if keyboard.is_pressed('shift+backspace'):

  147.         exit()

  148.  

  149.     if not messages_to_handle:

  150.         continue

  151.     else:

  152.         for message in messages_to_handle:

  153.             if len(active_tasks) <= MAX_WORKERS:

  154.                 active_tasks.append(thread_pool.submit(handle_message, message))

  155.             else:

  156.                 print(f'WARNING: active tasks ({len(active_tasks)}) exceeds number of workers ({MAX_WORKERS}). ({len(message_queue)} messages in the queue)')

End of Document