Skip to content

Commit

Permalink
CSGOgsi with python and arduino project commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
Darkness4 committed Aug 21, 2017
1 parent 84a0451 commit 838e25e
Show file tree
Hide file tree
Showing 8 changed files with 1,064 additions and 225 deletions.
110 changes: 80 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,101 @@
#Counter-Strike: Global Offensive Game State Integration Quick Start Guide
# CsgoGSI on Python and Arduino LCD shield

This is a quick start guide to the [CS:GO Game State Integration](https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_Game_State_Integration "CS:GO Game State Integration"), referenced on 2015-12-10.
> A Counter-Strike: Global Offensive Game State Integration project based on python with screen on arduino
By the end of this guide you'll have a program running in your console printing out information about rounds as you play.
This project is using the [game state integration from csgo](https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_Game_State_Integration).
The informations are received by the program made in python and are shown on an arduino LCD shield.

**NOTE!** At the time of writing (CS:GO version 13514) [the round payload is different for the first round compared to consecutive ones](http://i.imgur.com/UIijPE9.jpg "Round payloads for rounds 1-3"). I regard it as a bug and hope it'll be fixed in future patches. This is why the sample program prints scores wrong for the first round.
### Features

- **Bomb timer (40s)**
- Health + kevlar bar
- Kills and headshots counter
- Money infoformations on buy time

##The Steps

1. Clone this repository. If you're unfamiliar with the process, you can alternatively just copy the files above to some directory on your computer.
## Table of Contents

2. Copy the *gamestate_integration_quickstartguide.cfg* into your csgo/config directory (likely located at *Steam\SteamApps\common\Counter-Strike Global Offensive\csgo\cfg*).
- [Requirements](#requirements)
- [Install & Usage](#install-usage)
- [F.A.Q](#faq)

3. Install either NodeJS or Python 3, depending on which programming language you wish to use:
- [Node.js Stable programming environment](https://nodejs.org/en/download/stable/ "Node.js Stable"). The server may work on the Mature version (v4 at the time of writing) as well, I only tested the guide on Stable (v5.2 at the time of writing).
- [Python 3](https://www.python.org/downloads/ "Python downloads"). Tested on Python 3.4.

4. Run the endpoint server and leave it running in the background. You can do this by opening up a shell (e.g. Command Prompt) and running this in the directory you copied the files to: `node quickstartguide.js` for the NodeJS server, or `python quickstartguide.py` for the Python server.
## Requirements

5. Start up CS:GO and play offline against bots. For easy testing set `mp_maxrounds 2` in console once you load into a map. You can play online against other players just as well, it's just faster to see how the program works if you do it against bots. Note that the quick start sample server most likely does not work correctly on hostage or arms race maps.
- [Python 3.x](https://www.python.org/downloads/) (tested on 3.6)
- pySerial package
Open a terminal :

```sh
pip --install pyserial
```

## Payloads
- Arduino (tested on UNO rev3)
- Arduino LCD KeyPad Shield (tested on v1.1)
- USB Ports

Payloads are packages the CS:GO client sends to your server. Their contents depend on components you subscribe to in your endpoint server configuration file under *data*. For example they can contain information such as ammo count, round endings, bringing up menus, weapon skins, player kill and headshot counts, players' clan tags, when and which weapons are selected, etc.

Below is a list of all the subscribable components that could be found in the Game State integration article on Valve Developer wiki at the time of writing. The article doesn't have a definite list, however, so there could be more.
### Do not forget
Verify if the pin are the good ones on the arduino program for the LCD shield. (line 10)

```cs
LiquidCrystal(rs, enable, d4, d5, d6, d7)
```
"provider" "1"
"map" "1"
"round" "1"
"player_id" "1"
"player_state" "1"
"player_weapons" "1"
"player_match_stats" "1"
"allplayers_id" "1"
"allplayers_state" "1"
"allplayers_match_stats" "1"

For LCD shield v1.0. Comment line 17-22 and uncomment line 24-30 in the serialsend.ino. Like this :

```cs
// For V1.1 us this threshold
/*
if (adc_key_in < 50) return btnRIGHT;
if (adc_key_in < 250) return btnUP;
if (adc_key_in < 450) return btnDOWN;
if (adc_key_in < 650) return btnLEFT;
if (adc_key_in < 850) return btnSELECT;
*/
// For V1.0 comment the other threshold and use the one below:
if (adc_key_in < 50) return btnRIGHT;
if (adc_key_in < 195) return btnUP;
if (adc_key_in < 380) return btnDOWN;
if (adc_key_in < 555) return btnLEFT;
if (adc_key_in < 790) return btnSELECT;
```

To learn more about what kind of data the CS:GO client sends out run `node outputpayloads.js > out.txt` using all of the components from the list above. Then run the game and try to play out different scenarios: different maps, different side winning in different situations, spectate players that are using different guns with different skins, play different modes, etc. It'll write everything into *out.txt* file.
## Install & Usage

0. Put gamestate_integration_arduinotrack.cfg in Program Files (x86)\Steam\SteamApps\common\Counter-Strike Global Offensive\csgo\cfg.

1. Mount the shield on the arduino (obviously) and [push](https://www.arduino.cc/en/main/howto) the serialsend.ino in the arduino (remember the COM port).

2. Launch CSGO

3. Run the python program

```sh
python csgogsi.py
```

4. Enter the right COM like 'COM5'
```sh
Ports availables : ['COM9', 'COM10']
Please enter the corresponding COMX : COM9
```
5. Play some CSGO and enjoy!

## F.A.Q

1. The needed port (300 by default) is occupied. What should i do?

##What's next
Replace in csgogsi.py, line 195, col 33, the new corresponding port :

```python
SERVER = MyServer(('localhost', XXXREPLACEME), MyRequestHandler)
```
And in gamestate_integration_arduinotrack.cfg, line 3 :
```
"uri" "http://127.0.0.1:XXXREPLACEME"
```
Now you can start building your own service. From the script you'll quickly notice that parsing information out of the payloads is quite an arduous task at the moment – we can only hope they come up with an easier format in the future.
2. I don't understand what you've written!
Need ideas what to do with all this information? Create a live, detailed tournament feed for your website. Tweet every time you get a headshot. Hook up your Christmas lights to make your whole house pulse red when the bomb is planted. Have your ice machine drop a cube every time you shoot a bullet. Track what sort of skins your co-players' are rocking. Anything you can think of. Go bananas!
Sorry for my english. French is my primary language.
208 changes: 208 additions & 0 deletions csgogsi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# -*- coding: utf-8 -*-
"""
CSGO's informations displayed on an Arduino featuring a bomb timer.
@auteur: tsuriga, Darkness4
"""

from http.server import BaseHTTPRequestHandler, HTTPServer
import sys
import time
import json
import glob
import serial


def progress(i):
""" Progress bar, for arduino 5px large"""
switcher = {i <= 0: b"\x07",
i == 1: b"\x02",
i == 2: b"\x03",
i == 3: b"\x04",
i == 4: b"\x05",
i >= 5: b"\x06"}
return switcher[True]


def bombtimer():
"40s bomb timer on arduino"
offset = time.time()
actualtime = 40-time.time() + offset
while actualtime > 0:
oldtime = int(actualtime)
time.sleep(0.1)
actualtime = 40 - time.time() + offset
if oldtime != int(actualtime): # Actualization
S.write(b'BOMB PLANTED')
# Wait for second line
time.sleep(0.1)
S.write(progress(int(actualtime))) # 5s max
S.write(progress(int(actualtime-5))) # 10s max
S.write(progress(int(actualtime-10))) # 15s max
S.write(progress(int(actualtime-15))) # 20s max
S.write(progress(int(actualtime-20))) # 25s max
S.write(progress(int(actualtime-25)))
S.write(progress(int(actualtime-30)))
S.write(progress(int(actualtime-35)))
S.write(bytes(str(int(actualtime)).encode()))
return


def serial_ports():
""" Lists serial port names
:raises EnvironmentError:
On unsupported or unknown platforms
:returns:
A list of the serial ports available on the system
"""
if sys.platform.startswith('win'):
ports = ['COM%s' % (i + 1) for i in range(256)]
elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
# this excludes your current terminal "/dev/tty"
ports = glob.glob('/dev/tty[A-Za-z]*')
elif sys.platform.startswith('darwin'):
ports = glob.glob('/dev/tty.*')
else:
raise EnvironmentError('Unsupported platform')

result = []
for port in ports:
try:
connect = serial.Serial(port)
connect.close()
result.append(port)
except (OSError, serial.SerialException):
pass
return result


class MyServer(HTTPServer):
"""Server storing CSGO's information"""
def init_state(self):
"""
You can store states over multiple requests in the server
"""
self.round_phase = None
self.bomb = None
self.state = None
self.waiting = False


class MyRequestHandler(BaseHTTPRequestHandler):
"""CSGO's requests handler"""
def do_POST(self):
"""Receive CSGO's informations"""
length = int(self.headers['Content-Length'])
body = self.rfile.read(length).decode('utf-8')

self.parse_payload(json.loads(body))

self.send_header('Content-type', 'text/html')
self.send_response(200)
self.end_headers()

# Get Information function
def get_round_phase(self, payload):
"""Get round phase"""
if 'round' in payload and 'phase' in payload['round']:
return payload['round']['phase']

def get_state(self, payload):
"""Get player status"""
if 'player' in payload and 'state' in payload['player']:
return {'health': payload['player']['state']['health'],
'armor': payload['player']['state']['armor'],
'round_kills': payload['player']['state']['round_kills'],
'round_killhs': payload['player']['state']['round_killhs'],
'money': payload['player']['state']['money']}

def get_bomb(self, payload):
"""Get bomb status"""
if 'round' in payload and 'bomb' in payload['round']:
return payload['round']['bomb']

# Parsing and actions
def parse_payload(self, payload):
""" Search payload and execute arduino's codes"""
round_phase = self.get_round_phase(payload)

if round_phase is not None:
bomb = self.get_bomb(payload)
if bomb == 'planted':
if bomb != self.server.bomb:
self.server.bomb = bomb
bombtimer()
else:
self.server.bomb = bomb

state = self.get_state(payload)
if state != self.server.state: # if the state has changed
self.server.state = state # Récup des stats du joueurs
# Progress bar HP AM
health = int(state['health']) # Health
armor = int(state['armor']) # Armor
S.write(b'H: ') # Writing progress bar on Serial
S.write(progress(int(health/5)))
S.write(progress(int((health-25)/5)))
S.write(progress(int((health-50)/5)))
S.write(progress(int((health-75)/5)))
S.write(b' A: ')
S.write(progress(int(armor/5)))
S.write(progress(int((armor-25)/5)))
S.write(progress(int((armor-50)/5)))
S.write(progress(int((armor-75)/5)))
# Wait for second line
time.sleep(0.1)
# Kill or Money
if round_phase != 'freezetime':
# HS and Kill counter
headshots = int(state['round_killhs'])
kills = state['round_kills']-headshots
S.write(b'K: ')
for _ in range(0, kills): # counting
S.write(b'\x00') # Byte 0 char : kill no HS
for _ in range(0, headshots): # counting
S.write(b'\x01') # Byte 1 char : HS
else: # Not kill streak
S.write(bytes('M: {}'.format(state['money']).encode()))
elif not self.server.waiting:
self.server.waiting = True # isWaiting
S.write(b'Waiting for')
time.sleep(0.1)
S.write(b'matches')

def log_message(self, format, *args):
"""
Prevents requests from printing into the console
"""
return

# Arduino connection
print("Ports availables : {}".format(serial_ports()))
COM_STR = input("Please enter the corresponding COMX : ") # De la forme COMX
print("Waiting for Arduino")
while True:
try:
S = serial.Serial(COM_STR, 9600)
break
except IndexError:
pass
print(time.asctime(), '-', "Arduino detected")
time.sleep(2) # wait for the Serial to initialize

# Server initialization
SERVER = MyServer(('localhost', 3000), MyRequestHandler)
SERVER.init_state()

print(time.asctime(), '-', 'CS:GO GSI Quick Start server starting')

try:
SERVER.serve_forever()
except (KeyboardInterrupt, SystemExit):
pass

SERVER.server_close()
print(time.asctime(), '-', 'CS:GO GSI Quick Start server stopped')
print(time.asctime(), '-', 'Serial stopped')
S.close()
18 changes: 18 additions & 0 deletions gamestate_integration_arduinotrack.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"Arduino v.1"
{
"uri" "http://127.0.0.1:3000"
"timeout" "5.0"
"buffer" "0.1"
"throttle" "0.5"
"heartbeat" "60.0"
"data"
{
"provider" "1"
"map" "1"
"round" "1"
"player_id" "1"
"player_state" "1"
"player_weapons" "1"
"player_match_stats" "1"
}
}
14 changes: 0 additions & 14 deletions gamestate_integration_quickstartguide.cfg

This file was deleted.

Loading

0 comments on commit 838e25e

Please sign in to comment.