Pavlov TV API: Difference between revisions

From Pavlov VR Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
 
(7 intermediate revisions by the same user not shown)
Line 31: Line 31:
Ex.in JS to fetch this endpoint on port 1234 const response = await fetch('<nowiki>http://localhost:1234/MatchStatus'</nowiki>); const matchData = await response.json()
Ex.in JS to fetch this endpoint on port 1234 const response = await fetch('<nowiki>http://localhost:1234/MatchStatus'</nowiki>); const matchData = await response.json()


<blockquote>const response = await fetch('<nowiki>http://localhost:1234/MatchStatus'</nowiki>);  
<blockquote>const response = await fetch('<nowiki>http://localhost:1234/MatchStatus'</nowiki>);
 


const matchData = await response.json();</blockquote>'''Elements in MatchStatus'''
const matchData = await response.json();</blockquote>'''Elements in MatchStatus'''
Line 87: Line 88:


''Ex.in JS to fetch Team0 from MatchStatus''<blockquote>const teamZero = matchData.Team0  
''Ex.in JS to fetch Team0 from MatchStatus''<blockquote>const teamZero = matchData.Team0  


const teamOne = matchData.Team1</blockquote>These will give you an array of JSON style objects
const teamOne = matchData.Team1</blockquote>These will give you an array of JSON style objects
{| class="wikitable"
|    "Team0": [
       {
           "Name": "JaredOne",
           "Cash": 1500,
           "Score": 8,
           "Kills": 3,
           "Deaths": 2,
           "Dead": true,
           "Health": 0,
           "Armour": 0,
           "Helmet": false,
           "PrimaryWeapon": "<nowiki>https://test-pav-tv.vankrupt.net/icons/m16.png</nowiki>",
           "SecondaryWeapons": [
               "<nowiki>https://test-pav-tv.vankrupt.net/icons/knife.png</nowiki>"
           ],
           "Avatar": "<nowiki>http://prod.cdn.pavlov-vr.com/avatar/ThisTotallyExistsZero.png</nowiki>",
           "Bot": false
       },
       {
           "Name": "JaredTwo",
           "Cash": 150,
           "Score": 6,
           "Kills": 3,
           "Deaths": 2,
           "Dead": false,
           "Health": 100,
           "Armour": 0,
           "Helmet": false,
           "PrimaryWeapon": "<nowiki>https://test-pav-tv.vankrupt.net/icons/m16.png</nowiki>",
           "SecondaryWeapons": [
               "<nowiki>https://test-pav-tv.vankrupt.net/icons/knife.png</nowiki>"
           ],
           "Avatar": "<nowiki>http://prod.cdn.pavlov-vr.com/avatar/ThisTotallyExistsOne.png</nowiki>",
           "Bot": false
       },
|}
You can then grab subsequent information from the teams like this<blockquote>matchData.Team0[0].Name</blockquote>This would return the name in string form from the first player of Team0. You can do this with all of the information relating to the player, players are returned in order of score highest to lowest [as how it would show up in scoreboard]
==== For the values in each player: ====
'''Name''' returns a <u>String</u>
'''Cash, Score, Kills, Deaths, Health,''' and '''Armor''' are all returning <u>Integer</u> values
'''Dead, Helmet,''' and '''Bot''' return <u>Bool</u>
'''PrimaryWeapon, SecondaryWeapon,''' and '''Avatar''' all return <u>strings</u> containing links to image files that can be used in your scoreboard.
=== Killfeed GET Endpoint ===
Gives you a live killfeed of the match you are watching Ex. in Js of how you would get this endpoint as a Json object
<blockquote>const response = await fetch('<nowiki>http://localhost:1234/Killfeed'</nowiki>);
const killfeedData = await response.json();</blockquote>Returns the most recent
{| class="wikitable"
|"Killfeed": [
       {
           "Killer": "jaredOne",
           "Killed": "jaredTwo",
           "Headshot": true,
           "EntryLifespan": 3.2541036605834961,
           "KilledBy": "Tokarev",
           "KillerTeam": 0,
           "KilledTeam": 1
       }
   ]
|}
Ex of how you would get any of the following for the first object in the killfeed<blockquote>killfeedData.killfeed[0].killer</blockquote>'''Killer''' and '''Killed''' return <u>''String''</u> objects of the killer and killed respectively
'''Headshot''' returns whether the death was caused by a headshot, in the form of a <u>bool</u>
'''EntryLifespan''' returns the time left counting down from 4 seconds in a <u>float/number</u>
'''KilledBy''' returns the weaponId of the killing weapon in a ''string''
'''KillerTeam''' and '''KilledTeam''' returns either 0(blue) or 1(red) team of the Killer as an <u>Integer</u>
=== MatchEvents GET Endpoint ===
Gives you an event log for things like Win and Round, will list ALL events during the round if watching a replay, will list them as they happen if watching live<blockquote>const response = await fetch('<nowiki>http://localhost:1234/MatchEvents'</nowiki>);
const eventData = await response.json();</blockquote>
{| class="wikitable"
|{
   "MatchTime": 48.085689544677734,
   "TotalTime": 80.008003234863281,
   "Events": [
       {
           "Name": "Round",
           "Time": 15,
           "AdditionalData": 1
       },
       {
           "Name": "Win",
           "Time": 31,
           "AdditionalData": 1
       }
]
}
|}
'''Name''' gives the event name in a <u>string</u> being either “Round”, “Win”, “Planted”
'''Time''' gives the time in seconds in <u>integer</u> form of the event going into the game [will list events from start to end]
'''AdditionalData''' will be dependent for the event type
-For a “Win” event 0 will represent Team0 victory, 1 will represent Team1 victory
-For a “Round” event the number returned will represent what round it is
-For a “Planted” event the number does not matter it will always be 0
=== Camera Paths ===
Steps to Use:
1. Go into a map and record a camera path for keys [1], [2], [3] , or [4], using LCtrl + [Key]  The path will be saved to the api endpoint located here:<blockquote><nowiki>http://localhost:1234/Camera/Path/#</nowiki> </blockquote>'''NOTE''': Path values start at 0, so your [1] key will be Path 0, [2] is Path 1, [3] is Path 2, and [4] is Path 3.
'''NOTE 2''': Camera paths are deleted in between matches so make sure to save them before exiting
3. You can play the path with LShift + [Key]
=== Camera Path GET Endpoint - Retrieve saved camera path ===
Javascript example of how to use it
'''NOTE''': Replace the “#” with the number path corresponding above 0, 1, 2, 3
<blockquote>const response = await fetch('<nowiki>http://localhost:1234/Camera/Path/#'</nowiki>);
const cameraPath = await response.json();</blockquote>Example of returned path with a single point<blockquote>{"MapName":"Oilrig","PathNumber":0,"Points":[{"Time":0,"X":7453.70035785 53522,"Y":4584.0434961287474,"Z":1223.5857404802396,"Pitch":-4.375000059 6046359,"Yaw":-155.3252139880791,"Roll":0}]}</blockquote>
'''MapName''' gives you the name of the map of the current match you are in as a <u>string</u>
'''PathNumber''' is the same as the path you put in to retrieve it as an <u>integer</u>
'''Time''' gives you the time in seconds as a <u>float</u>
'''X,Y,Z''' give you the relative x-plane, y-plane, z-plane position each in <u>float</u>
'''Pitch, Roll, Yaw''' give you the orientation of the camera relative to the world normal, each in <u>float</u>
[[File:Pitchrollyaw.png|right|frameless]]
===== Camera Play GET Endpoint - It will call your game to play the path currently in that slot =====
Ex. of fetching it in Js, since there is no response, you can just run fetch<blockquote>fetch('<nowiki>http://localhost:1234/Camera/Play/0'</nowiki>)</blockquote>This is the same principle as getting the path, though there will be no response to check it will just trigger immediately in your client and you will go along the active path bound to that key.
==== Camera Path POST Endpoint - Insert your path into the live slot ====
Ex. of a javascript POST fetch request to the api
{| class="wikitable"
|const response = await fetch(`localhost:1234/Camera/Path/1`, {
     method: "POST",
     headers: {
       "Content-Type": "application/json",
     },
     body: JSON.stringify(data),
   });
|}
Notes: MapName and PathNumber do '''NOT''' matter in terms of POST-ing a path, so you do not have to modify those at all. The only thing that really matters is the points. It is recommended to store paths in json files for modification and accessibility ease.
'''GET <code>/Pause</code> returns'''
{| class="wikitable"
|{
 "Paused": true
}
|}
'''POST <code>/Pause</code>'''
{| class="wikitable"
|{
 "Paused": false
}
|}
returns
{| class="wikitable"
|{
 "Successful": true
}
|}
if successful returns false it will also include a string field <code>Reason</code> explaining why it failed
'''GET <code>/MatchTime</code>'''
returns
{| class="wikitable"
|{
 "MatchTime": 19.035182
}
|}
'''POST <code>/MatchTime</code>'''
{| class="wikitable"
|{
 "MatchTime": 120.5
}
|}
returns
{| class="wikitable"
|{
 "Successful": true
}
|}
if successful returns false it will also include a string field <code>Reason</code> explaining why it failed GET <code>/ReplayList</code> returns
{| class="wikitable"
|{
   "Replays": [
       {
           "Id": "Sand-DM-2024.11.15-01.44.05",
           "Name": "Sand",
           "GameMode": "DM",
           "Timestamp": "2024-11-15T01:44:05.687Z",
           "Shack": true,
           "LocalReplay": true,
           "Live": false
       },
       {
           "Id": "ac8d018f56f3f820033d319af942be3e",
           "Name": "Sand",
           "GameMode": "DM",
           "Timestamp": "2024-11-27T08:28:37.362Z",
           "Shack": false,
           "LocalReplay": false,
           "Live": false
       }
 ]
}
|}
'''POST <code>/LoadReplay</code>'''
{| class="wikitable"
|{
   "Id": "ac8d018f56f3f820033d319af942be3e"
}
|}
return
{| class="wikitable"
|{
   "Successful": true
}
|}
'''<code>/PlayerPos</code> endpoint to include these fields in SND if the bomb is available. Bomb States (0 = dropped, 1 = carried, 2 = planted)'''
{| class="wikitable"
|"BombX": -3479.5815804967287,
"BombY": -144.55990951728171,
"BombZ": -262.77667585565382,
"BombState": 1
|}
=== Possible issues you may encounter ===
'''TypeError: fetch failed - cause: Error: connect ECONNREFUSED:'''
This error happens because you are likely running your javascript function with nodejs or the like and it is trying to force ipv6, ensure it does ipv4 by changing “localhost” to “127.0.0.1”. When pulling from the web source it will not matter since it understands the difference. Newer versions of NodeJS should not have this problem and be able to determine between ipv4 and ipv6. (Version 19.3 or higher)
'''Refused to connect'''
If “http:localhost:1234” does not show anything or “refused to connect” the service is not running on this port and you will need to try to set up the API again. Ensure you have PavTVAPIPort=1234 inside of your gameusersettings.ini

Latest revision as of 13:35, 3 December 2024

Pavlov TV has exposed endpoints to allow users to create their own UI interface for use in steaming software such as OBS, data-sets, or any other purpose of your choosing from Pavlov replays.

Enable the API

1. Ensure you have PavlovVR installed on steam and locate the GameUserSettings.ini file.

This file on windows machines will usually be found at [ %localappdata%\Pavlov\Saved\Config\Windows\GameUserSettings.ini ] You can paste this into your file explorer(without the brackets [] ) to open the file.

2. Go the the bottom portion of the [/Script/Pavlov.PavlovGameUserSettings] part of the file, ABOVE[ScalabilityGroups].

3. Add PavTVAPIPort=1234 onto the file at that location. 1234 is the port that will be used, if you know this port is in use already, please find another on your device that is not in use and list it here.

4. Run PavTV

Using OBS with your generated/written HTML file

1. Copy your HTML file path

2. Go to “Sources” and click the “+” button

3. Add browser source and create new (name it whatever)

4. Adjust Resolution of the source(recording size)

5. Paste in the HTML file path and click OK

The GET Endpoints

*If you are new to this, you are able to put any of these endpoint “links” into your browser or run curl requests and check that they are functional


MatchStatus

Ex.in JS to fetch this endpoint on port 1234 const response = await fetch('http://localhost:1234/MatchStatus'); const matchData = await response.json()

const response = await fetch('http://localhost:1234/MatchStatus');


const matchData = await response.json();

Elements in MatchStatus

MatchActive- Will return whether a match is currently ongoing or not

Ex. “MatchActive”: true or “MatchActive”: false


RoundTime- Will return the active time into the round in seconds

Ex. "RoundTime": 105


Teams- Will return true/false if the there are teams in this match

Ex. "Teams": true


RoundsLeft- Will return the maximum number of rounds left in the match

Ex. "RoundsLeft": 15


Team0Score- Will return blue team’s score

Team1Score- Will return red team’s score

Ex. "Team0Score": 1,

"Team1Score": 2,


AttackingTeamId- Will return the ID of the currently attacking team 0-blue, 1- red

Ex. "AttackingTeamId": 1


Team0Cash- Will return blue team’s collective cash

Team1Cash- Will return red team’s collective cash

Ex. "Team0Cash": 2500, "Team1Cash": 2500 MatchStatus.Team0 or MatchStatus.Team1


MatchStatus.Team0 or MatchStatus.Team1

Ex.in JS to fetch Team0 from MatchStatus

const teamZero = matchData.Team0


const teamOne = matchData.Team1

These will give you an array of JSON style objects

    "Team0": [

       {

           "Name": "JaredOne",

           "Cash": 1500,

           "Score": 8,

           "Kills": 3,

           "Deaths": 2,

           "Dead": true,

           "Health": 0,

           "Armour": 0,

           "Helmet": false,

           "PrimaryWeapon": "https://test-pav-tv.vankrupt.net/icons/m16.png",

           "SecondaryWeapons": [

               "https://test-pav-tv.vankrupt.net/icons/knife.png"

           ],

           "Avatar": "http://prod.cdn.pavlov-vr.com/avatar/ThisTotallyExistsZero.png",

           "Bot": false

       },

       {

           "Name": "JaredTwo",

           "Cash": 150,

           "Score": 6,

           "Kills": 3,

           "Deaths": 2,

           "Dead": false,

           "Health": 100,

           "Armour": 0,

           "Helmet": false,

           "PrimaryWeapon": "https://test-pav-tv.vankrupt.net/icons/m16.png",

           "SecondaryWeapons": [

               "https://test-pav-tv.vankrupt.net/icons/knife.png"

           ],

           "Avatar": "http://prod.cdn.pavlov-vr.com/avatar/ThisTotallyExistsOne.png",

           "Bot": false

       },


You can then grab subsequent information from the teams like this

matchData.Team0[0].Name

This would return the name in string form from the first player of Team0. You can do this with all of the information relating to the player, players are returned in order of score highest to lowest [as how it would show up in scoreboard]


For the values in each player:

Name returns a String

Cash, Score, Kills, Deaths, Health, and Armor are all returning Integer values

Dead, Helmet, and Bot return Bool

PrimaryWeapon, SecondaryWeapon, and Avatar all return strings containing links to image files that can be used in your scoreboard.

Killfeed GET Endpoint

Gives you a live killfeed of the match you are watching Ex. in Js of how you would get this endpoint as a Json object

const response = await fetch('http://localhost:1234/Killfeed');


const killfeedData = await response.json();

Returns the most recent

"Killfeed": [

       {

           "Killer": "jaredOne",

           "Killed": "jaredTwo",

           "Headshot": true,

           "EntryLifespan": 3.2541036605834961,

           "KilledBy": "Tokarev",

           "KillerTeam": 0,

           "KilledTeam": 1

       }

   ]


Ex of how you would get any of the following for the first object in the killfeed

killfeedData.killfeed[0].killer

Killer and Killed return String objects of the killer and killed respectively

Headshot returns whether the death was caused by a headshot, in the form of a bool

EntryLifespan returns the time left counting down from 4 seconds in a float/number

KilledBy returns the weaponId of the killing weapon in a string

KillerTeam and KilledTeam returns either 0(blue) or 1(red) team of the Killer as an Integer

MatchEvents GET Endpoint

Gives you an event log for things like Win and Round, will list ALL events during the round if watching a replay, will list them as they happen if watching live

const response = await fetch('http://localhost:1234/MatchEvents'); const eventData = await response.json();

{

   "MatchTime": 48.085689544677734,

   "TotalTime": 80.008003234863281,

   "Events": [

       {

           "Name": "Round",

           "Time": 15,

           "AdditionalData": 1

       },

       {

           "Name": "Win",

           "Time": 31,

           "AdditionalData": 1

       }

]

}


Name gives the event name in a string being either “Round”, “Win”, “Planted”

Time gives the time in seconds in integer form of the event going into the game [will list events from start to end]

AdditionalData will be dependent for the event type

-For a “Win” event 0 will represent Team0 victory, 1 will represent Team1 victory

-For a “Round” event the number returned will represent what round it is

-For a “Planted” event the number does not matter it will always be 0

Camera Paths

Steps to Use:

1. Go into a map and record a camera path for keys [1], [2], [3] , or [4], using LCtrl + [Key] The path will be saved to the api endpoint located here:

http://localhost:1234/Camera/Path/#

NOTE: Path values start at 0, so your [1] key will be Path 0, [2] is Path 1, [3] is Path 2, and [4] is Path 3.

NOTE 2: Camera paths are deleted in between matches so make sure to save them before exiting

3. You can play the path with LShift + [Key]

Camera Path GET Endpoint - Retrieve saved camera path

Javascript example of how to use it

NOTE: Replace the “#” with the number path corresponding above 0, 1, 2, 3

const response = await fetch('http://localhost:1234/Camera/Path/#');


const cameraPath = await response.json();

Example of returned path with a single point

{"MapName":"Oilrig","PathNumber":0,"Points":[{"Time":0,"X":7453.70035785 53522,"Y":4584.0434961287474,"Z":1223.5857404802396,"Pitch":-4.375000059 6046359,"Yaw":-155.3252139880791,"Roll":0}]}


MapName gives you the name of the map of the current match you are in as a string

PathNumber is the same as the path you put in to retrieve it as an integer

Time gives you the time in seconds as a float

X,Y,Z give you the relative x-plane, y-plane, z-plane position each in float

Pitch, Roll, Yaw give you the orientation of the camera relative to the world normal, each in float

Pitchrollyaw.png
Camera Play GET Endpoint - It will call your game to play the path currently in that slot

Ex. of fetching it in Js, since there is no response, you can just run fetch

fetch('http://localhost:1234/Camera/Play/0')

This is the same principle as getting the path, though there will be no response to check it will just trigger immediately in your client and you will go along the active path bound to that key.

Camera Path POST Endpoint - Insert your path into the live slot

Ex. of a javascript POST fetch request to the api

const response = await fetch(`localhost:1234/Camera/Path/1`, {

     method: "POST",

     headers: {

       "Content-Type": "application/json",

     },

     body: JSON.stringify(data),

   });

Notes: MapName and PathNumber do NOT matter in terms of POST-ing a path, so you do not have to modify those at all. The only thing that really matters is the points. It is recommended to store paths in json files for modification and accessibility ease.

GET /Pause returns

{

 "Paused": true

}

POST /Pause

{

 "Paused": false

}


returns

{

 "Successful": true

}


if successful returns false it will also include a string field Reason explaining why it failed


GET /MatchTime

returns

{

 "MatchTime": 19.035182

}


POST /MatchTime

{

 "MatchTime": 120.5

}

returns

{

 "Successful": true

}


if successful returns false it will also include a string field Reason explaining why it failed GET /ReplayList returns

{

   "Replays": [

       {

           "Id": "Sand-DM-2024.11.15-01.44.05",

           "Name": "Sand",

           "GameMode": "DM",

           "Timestamp": "2024-11-15T01:44:05.687Z",

           "Shack": true,

           "LocalReplay": true,

           "Live": false

       },

       {

           "Id": "ac8d018f56f3f820033d319af942be3e",

           "Name": "Sand",

           "GameMode": "DM",

           "Timestamp": "2024-11-27T08:28:37.362Z",

           "Shack": false,

           "LocalReplay": false,

           "Live": false

       }

 ]

}


POST /LoadReplay

{

   "Id": "ac8d018f56f3f820033d319af942be3e"

}


return

{

   "Successful": true

}


/PlayerPos endpoint to include these fields in SND if the bomb is available. Bomb States (0 = dropped, 1 = carried, 2 = planted)

"BombX": -3479.5815804967287,

"BombY": -144.55990951728171,

"BombZ": -262.77667585565382,

"BombState": 1

Possible issues you may encounter

TypeError: fetch failed - cause: Error: connect ECONNREFUSED:

This error happens because you are likely running your javascript function with nodejs or the like and it is trying to force ipv6, ensure it does ipv4 by changing “localhost” to “127.0.0.1”. When pulling from the web source it will not matter since it understands the difference. Newer versions of NodeJS should not have this problem and be able to determine between ipv4 and ipv6. (Version 19.3 or higher)


Refused to connect

If “http:localhost:1234” does not show anything or “refused to connect” the service is not running on this port and you will need to try to set up the API again. Ensure you have PavTVAPIPort=1234 inside of your gameusersettings.ini