forked from tsuriga/csgo-gsi-qsguide
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CSGOgsi with python and arduino project commit.
- Loading branch information
Showing
8 changed files
with
1,064 additions
and
225 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.