18 Commits
Author SHA1 Message Date
legonzaur 233479e348 remove name from kill 2023-09-08 09:21:02 +02:00
legonzaur 64030e5ef0 handle match close 2023-09-07 00:33:58 +02:00
legonzaur 03b775fec3 track player speed 2023-09-06 16:25:15 +02:00
legonzaur 23bfd52956 Sent stats on match end 2023-08-18 23:15:04 +02:00
legonzaur 925f87485b big refactor 2023-08-18 22:26:29 +02:00
legonzaur acdf6cf1ff add shared function file 2023-08-18 21:46:12 +02:00
legonzaur 80ddfbdc05 Revert "create shared funtion file"
This reverts commit 831315b15e.
2023-08-18 21:44:54 +02:00
legonzaur 3393d73b9a Revert "create shared function file"
This reverts commit e16cedf709.
2023-08-18 21:44:07 +02:00
legonzaur 831315b15e create shared funtion file 2023-08-18 21:41:47 +02:00
legonzaur e16cedf709 create shared function file 2023-08-18 21:41:39 +02:00
legonzaur d532984283 remove console logs 2023-08-18 20:01:53 +02:00
legonzaur 2134680f63 save shot stats per player & per weapon 2023-08-18 19:47:25 +02:00
legonzaur 9fbcf70608 formatting 2023-08-17 23:56:33 +02:00
legonzaur 00cf89f461 add matchstat 2023-08-17 23:35:20 +02:00
legonzaur 9ad4d4b258 v3 kill upload 2023-08-14 19:30:46 +02:00
legonzaur 6f37fea562 fix function type compile error 2023-08-12 20:11:47 +02:00
legonzaur 06202bb311 add passives to loadout 2023-08-10 12:33:09 +02:00
legonzaur 4deda3a113 Prepare for v3 2023-07-14 13:32:47 +02:00
5 changed files with 682 additions and 295 deletions
+19 -5
View File
@@ -1,17 +1,17 @@
{ {
"Name": "fvnkhead.killstat", "Name": "fvnkhead.killstat",
"Description": "Gather kill statistics and sends them to Tone API server.", "Description": "Gather kill statistics and sends them to Tone API server.",
"Version": "3.0.4", "Version": "3.0.2",
"LoadPriority": 1, "LoadPriority": 2,
"RequiredOnClient": false, "RequiredOnClient": false,
"ConVars": [ "ConVars": [
{ {
"Name": "killstat_version", "Name": "toneapi_version",
"DefaultValue": "ks_3.0.1" "DefaultValue": "3.0.2"
}, },
{ {
"Name": "Tone_URI", "Name": "Tone_URI",
"DefaultValue": "https://toneapi.ovh/v2" "DefaultValue": "https://tone.sleepycat.date/v3/server"
}, },
{ {
"Name": "Tone_token", "Name": "Tone_token",
@@ -23,6 +23,20 @@
} }
], ],
"Scripts": [ "Scripts": [
{
"Path": "toneapi.nut",
"RunOn": "SERVER",
"ServerCallback": {
"After": "toneapi_Init"
}
},
{
"Path": "matchstat.nut",
"RunOn": "SERVER",
"ServerCallback": {
"After": "matchstat_Init"
}
},
{ {
"Path": "killstat.nut", "Path": "killstat.nut",
"RunOn": "SERVER", "RunOn": "SERVER",
+79
View File
@@ -0,0 +1,79 @@
array<int> MAIN_DAMAGE_SOURCES = [
// primaries
eDamageSourceId.mp_weapon_car,
eDamageSourceId.mp_weapon_r97,
eDamageSourceId.mp_weapon_alternator_smg,
eDamageSourceId.mp_weapon_hemlok_smg,
eDamageSourceId.mp_weapon_hemlok,
eDamageSourceId.mp_weapon_vinson,
eDamageSourceId.mp_weapon_g2,
eDamageSourceId.mp_weapon_rspn101,
eDamageSourceId.mp_weapon_rspn101_og,
eDamageSourceId.mp_weapon_esaw,
eDamageSourceId.mp_weapon_lstar,
eDamageSourceId.mp_weapon_lmg,
eDamageSourceId.mp_weapon_shotgun,
eDamageSourceId.mp_weapon_mastiff,
eDamageSourceId.mp_weapon_dmr,
eDamageSourceId.mp_weapon_sniper,
eDamageSourceId.mp_weapon_doubletake,
eDamageSourceId.mp_weapon_pulse_lmg,
eDamageSourceId.mp_weapon_smr,
eDamageSourceId.mp_weapon_softball,
eDamageSourceId.mp_weapon_epg,
eDamageSourceId.mp_weapon_shotgun_pistol,
eDamageSourceId.mp_weapon_wingman_n,
// secondaries
eDamageSourceId.mp_weapon_smart_pistol,
eDamageSourceId.mp_weapon_wingman,
eDamageSourceId.mp_weapon_semipistol,
eDamageSourceId.mp_weapon_autopistol,
// anti-titan
eDamageSourceId.mp_weapon_mgl,
eDamageSourceId.mp_weapon_rocket_launcher,
eDamageSourceId.mp_weapon_arc_launcher,
eDamageSourceId.mp_weapon_defender
]
void function DumpWeaponModBitFields() {
Log("[DumpWeaponModBitFields]")
foreach (int damageSourceId in MAIN_DAMAGE_SOURCES) {
string weaponName = DamageSourceIDToString(damageSourceId)
array<string> mods = GetWeaponMods_Global(weaponName)
array<string> list = [weaponName]
foreach (string mod in mods) {
list.append(mod)
}
Log("[DumpWeaponModBitFields] " + ToPythonList(list))
}
}
string function ToPythonList(array<string> list) {
array<string> quoted = []
foreach (string s in list) {
quoted.append("'" + s + "'")
}
return "\"[" + join(quoted, ", ") + "]\""
}
string function join(array<string> list, string separator) {
string s = ""
for (int i = 0; i < list.len(); i++) {
s += list[i]
if (i < list.len() - 1) {
s += separator
}
}
return s
}
int function WeaponNameSort(entity a, entity b) {
return SortStringAlphabetize(a.GetWeaponClassName(), b.GetWeaponClassName())
}
+203 -255
View File
@@ -1,94 +1,26 @@
global function killstat_Init global function killstat_Init
struct Parameter {
string name
string value
}
struct {
string killstatVersion
string Tone_URI
string Tone_protocol
string servername
string Tone_token
bool connected
//remove this in the future
array<Parameter> customParameters
int matchId
string gameMode
string map
} file
void function killstat_Init() { void function killstat_Init() {
file.killstatVersion = GetConVarString("killstat_version") if(GetMapName() == "mp_lobby") {
file.Tone_URI = GetConVarString("Tone_URI") return
file.Tone_token = GetConVarString("Tone_token")
file.connected = false
file.servername = GetConVarString("ns_server_name")
//register to Tone API if default or invalid token
Tone_Test_Auth()
// custom parameters
string customParameterString = GetConVarString("killstat_custom_parameters")
array<string> customParameterEntries = split(customParameterString, ",")
file.customParameters = []
foreach (string customParameterEntry in customParameterEntries) {
array<string> customParameterPair = split(customParameterEntry, "=")
if (customParameterPair.len() != 2) {
Log("[WARN] ignoring invalid custom parameter: " + customParameterEntry)
continue
} }
Parameter customParameter
customParameter.name = strip(customParameterPair[0])
customParameter.value = strip(customParameterPair[1])
file.customParameters.append(customParameter)
}
// callbacks
AddCallback_GameStateEnter(eGameState.Playing, killstat_Begin)
AddCallback_OnPlayerKilled(killstat_Record) AddCallback_OnPlayerKilled(killstat_Record)
AddCallback_GameStateEnter(eGameState.Postmatch, killstat_End)
AddCallback_OnClientConnected(JoinMessage)
}
Parameter function NewParameter(string name, string value) {
Parameter p
p.name = name
p.value = value
return p
}
string prefix = "\x1b[38;5;81m[TONE API]\x1b[0m "
void function JoinMessage(entity player) {
//Chat_ServerPrivateMessage(player, prefix + "This server collects data using the Tone API. Check your data here: \x1b[34mtoneapi.com/" + player.GetPlayerName()+ "\x1b[0m", false, false)
Chat_ServerPrivateMessage(player, prefix + "This server collects data using the WIP Tone API. View statistics at https://toneapi.ovh", false, false)
}
void function killstat_Begin() {
//DumpWeaponModBitFields()
//TODO : request MatchID from API ------------------------------------------------------------------------------------------------
//TODO : request anonymization data from API
file.matchId = RandomInt(2000000000)
file.gameMode = GameRules_GetGameMode()
file.map = StringReplace(GetMapName(), "mp_", "")
Log("-----BEGIN KILLSTAT-----")
Log("Sending kill data to " + file.Tone_URI + "/server/kill")
} }
void function killstat_Record(entity victim, entity attacker, var damageInfo) { void function killstat_Record(entity victim, entity attacker, var damageInfo) {
if (!victim.IsPlayer() || !attacker.IsPlayer() || GetGameState() != eGameState.Playing) if (!victim.IsPlayer() || !attacker.IsPlayer() || GetGameState() != eGameState.Playing)
return return
if(!toneapi_data.matchId){
table values = {} ToneAPI_Log("[ERRR] Match is not registered!")
//remove this in the future return
foreach (Parameter p in file.customParameters) {
values[p] <- p.value
} }
table attackerValues = {}
table < string,
var > victimValues = {}
array < entity > attackerWeapons = attacker.GetMainWeapons() array < entity > attackerWeapons = attacker.GetMainWeapons()
array < entity > victimWeapons = victim.GetMainWeapons() array < entity > victimWeapons = victim.GetMainWeapons()
array < entity > attackerOffhandWeapons = attacker.GetOffhandWeapons() array < entity > attackerOffhandWeapons = attacker.GetOffhandWeapons()
@@ -110,79 +42,199 @@ void function killstat_Record(entity victim, entity attacker, var damageInfo) {
entity vow2 = GetNthWeapon(victimOffhandWeapons, 1) entity vow2 = GetNthWeapon(victimOffhandWeapons, 1)
entity vow3 = GetNthWeapon(victimOffhandWeapons, 2) entity vow3 = GetNthWeapon(victimOffhandWeapons, 2)
values["killstat_version"] <- file.killstatVersion
values["match_id"] <- format("%08x", file.matchId)
values["servername"] <- file.servername
values["game_mode"] <- file.gameMode
values["map"] <- file.map
values["game_time"] <- format("%.3f", Time())
values["player_count"] <- format("%d", GetPlayerArray().len())
values["attacker_name"] <- attacker.GetPlayerName()
values["attacker_id"] <- attacker.GetUID()
values["attacker_current_weapon"] <- GetWeaponName(attacker.GetLatestPrimaryWeapon())
values["attacker_current_weapon_mods"] <- GetWeaponMods(attacker.GetLatestPrimaryWeapon())
values["attacker_weapon_1"] <- GetWeaponName(aw1)
values["attacker_weapon_1_mods"] <- GetWeaponMods(aw1)
values["attacker_weapon_2"] <- GetWeaponName(aw2)
values["attacker_weapon_2_mods"] <- GetWeaponMods(aw2)
values["attacker_weapon_3"] <- GetWeaponName(aw3)
values["attacker_weapon_3_mods"] <- GetWeaponMods(aw3)
values["attacker_offhand_weapon_1"] <- GetWeaponName(aow1)
values["attacker_offhand_weapon_1_mods"] <- GetWeaponMods(aow1)
values["attacker_offhand_weapon_2"] <- GetWeaponName(aow2)
values["attacker_offhand_weapon_2_mods"] <- GetWeaponMods(aow1)
values["attacker_titan"] <- GetTitan(attacker)
values["victim_name"] <- victim.GetPlayerName()
values["victim_id"] <- victim.GetUID()
values["victim_current_weapon"] <- GetWeaponName(victim.GetLatestPrimaryWeapon())
values["victim_current_weapon_mods"] <- GetWeaponMods(victim.GetLatestPrimaryWeapon())
values["victim_weapon_1"] <- GetWeaponName(vw1)
values["victim_weapon_1_mods"] <- GetWeaponMods(vw1)
values["victim_weapon_2"] <- GetWeaponName(vw2)
values["victim_weapon_2_mods"] <- GetWeaponMods(vw2)
values["victim_weapon_3"] <- GetWeaponName(vw3)
values["victim_weapon_3_mods"] <- GetWeaponMods(vw3)
values["victim_offhand_weapon_1"] <- GetWeaponName(vow1)
values["victim_offhand_weapon_1_mods"] <- GetWeaponMods(vow1)
values["victim_offhand_weapon_2"] <- GetWeaponName(vow2)
values["victim_offhand_weapon_2_mods"] <- GetWeaponMods(vow2)
values["victim_titan"] <- GetTitan(victim)
int damageSourceId = DamageInfo_GetDamageSourceIdentifier(damageInfo) int damageSourceId = DamageInfo_GetDamageSourceIdentifier(damageInfo)
string damageName = DamageSourceIDToString(damageSourceId) string damageName = DamageSourceIDToString(damageSourceId)
values["cause_of_death"] <- damageName
float dist = Distance(attacker.GetOrigin(), victim.GetOrigin()) float dist = Distance(attacker.GetOrigin(), victim.GetOrigin())
values["distance"] <- format("%.3f", dist)
table values = {
version = toneapi_data.version
match_id = toneapi_data.matchId
game_time = Time()
player_count = GetPlayerArray().len()
cause_of_death = damageName
distance = dist
attacker = {
id = attacker.GetUID()
velocity = Distance( < 0, 0, 0 > , attacker.GetVelocity())
cloaked = attacker.IsCloaked(true)
state = GetMovementState(attacker)
current_weapon = {
id = GetWeaponName(attacker.GetLatestPrimaryWeapon())
mods = GetWeaponMods(attacker.GetLatestPrimaryWeapon())
}
loadout = {
primary = {
id = GetWeaponName(aw1)
mods = GetWeaponMods(aw1)
}
secondary = {
id = GetWeaponName(aw2)
mods = GetWeaponMods(aw2)
}
anti_titan = {
id = GetWeaponName(aw3)
mods = GetWeaponMods(aw3)
}
ordnance = {
id = GetWeaponName(aow1)
mods = GetWeaponMods(aow1)
}
tactical = {
id = GetWeaponName(aow2)
mods = GetWeaponMods(aow2)
}
passive1 = getPlayerPassive1(attacker)
passive2 = getPlayerPassive2(attacker)
titan = GetTitan(attacker)
}
}
victim = {
id = victim.GetUID()
velocity = Distance( < 0, 0, 0 > , victim.GetVelocity())
cloaked = victim.IsCloaked(true)
state = GetMovementState(victim)
current_weapon = {
id = GetWeaponName(victim.GetLatestPrimaryWeapon())
mods = GetWeaponMods(victim.GetLatestPrimaryWeapon())
}
loadout = {
primary = {
id = GetWeaponName(vw1)
mods = GetWeaponMods(vw1)
}
secondary = {
id = GetWeaponName(vw2)
mods = GetWeaponMods(vw2)
}
anti_titan = {
id = GetWeaponName(vw3)
mods = GetWeaponMods(vw3)
}
ordnance = {
id = GetWeaponName(vow1)
mods = GetWeaponMods(vow1)
}
tactical = {
id = GetWeaponName(vow2)
mods = GetWeaponMods(vow2)
}
passive1 = getPlayerPassive1(victim)
passive2 = getPlayerPassive2(victim)
titan = GetTitan(victim)
}
}
}
HttpRequest request HttpRequest request
request.method = HttpRequestMethod.POST request.method = HttpRequestMethod.POST
request.url = file.Tone_URI + "/server/kill" request.url = toneapi_data.Tone_URI + "/kill"
request.headers = {Authorization = ["Bearer " + file.Tone_token]}
request.body = EncodeJSON(values) request.body = EncodeJSON(values)
void functionref( HttpRequestResponse ) onSuccess = void function ( HttpRequestResponse response ) Tone_HTTP_Request(
{ request,
if(response.statusCode == 200 || response.statusCode == 201){ void
print("[Tone API] Kill data sent!") function(HttpRequestResponse response) {}
}else{ )
print("[Tone API][WARN] Couldn't send kill data")
print("[Tone API][WARN] " + response.body )
}
} }
void functionref( HttpRequestFailure ) onFailure = void function ( HttpRequestFailure failure ) string function GetMovementState(entity player) {
{ Assert(IsPilot(player))
print("[Tone API][WARN] Couldn't send kill data") //IsPhaseShifted
print("[Tone API][WARN] " + failure.errorMessage ) //IsStanding
} if (player.IsWallHanging())
NSHttpRequest(request, onSuccess, onFailure) return "WallHanging"
if (player.IsWallRunning())
return "WallRunning"
if (player.IsZiplining())
return "Ziplining"
if (!player.IsOnGround())
return "Airborne"
if (player.IsCrouched())
return "Crouching"
return "OnGround"
} }
void function killstat_End() { // Should sort main weapons in following order:
Log("-----END KILLSTAT-----") // 1. primary
// 2. secondary
// 3. anti-titan
int function MainWeaponSort(entity a, entity b) {
int aID = a.GetDamageSourceID()
int bID = b.GetDamageSourceID()
int aIdx = MAIN_DAMAGE_SOURCES.find(aID)
int bIdx = MAIN_DAMAGE_SOURCES.find(bID)
if (aIdx == bIdx) {
return 0
} else if (aIdx != -1 && bIdx == -1) {
return -1
} else if (aIdx == -1 && bIdx != -1) {
return 1
}
return aIdx < bIdx ? -1 : 1
}
entity function GetNthWeapon(array < entity > weapons, int index) {
return index < weapons.len() ? weapons[index] : null
}
var function GetWeaponData(entity weapon){
if(weapon != null) {
return {
id = weapon.GetWeaponClassName()
mods = weapon.GetModBitField()
}
}
return null
}
var function GetWeaponName(entity weapon) {
if (weapon != null) {
return weapon.GetWeaponClassName()
}
return null
}
int
function GetWeaponMods(entity weapon) {
if (weapon == null) {
return 0
}
int modBits = weapon.GetModBitField()
// return format("%d", modBits)
return modBits
}
var function GetTitan(entity player) {
if (player.IsTitan()){
return GetTitanCharacterName(player)
}
return null
}
var function getPlayerPassive1(entity player) {
foreach (string key, int val in ePassives){
if (PlayerHasPassive(player, val)) {
if(passive1Names.find(key) != -1){
return key
}
}
}
return null
}
var function getPlayerPassive2(entity player) {
foreach (string key, int val in ePassives){
if (PlayerHasPassive(player, val)) {
if(passive2Names.find(key) != -1){
return key
}
}
}
return null
} }
array < int > MAIN_DAMAGE_SOURCES = [ array < int > MAIN_DAMAGE_SOURCES = [
@@ -224,123 +276,19 @@ array<int> MAIN_DAMAGE_SOURCES = [
eDamageSourceId.mp_weapon_defender eDamageSourceId.mp_weapon_defender
] ]
void function DumpWeaponModBitFields() {
Log("[DumpWeaponModBitFields]")
foreach (int damageSourceId in MAIN_DAMAGE_SOURCES) {
string weaponName = DamageSourceIDToString(damageSourceId)
array<string> mods = GetWeaponMods_Global(weaponName)
array<string> list = [weaponName]
foreach (string mod in mods) {
list.append(mod)
}
Log("[DumpWeaponModBitFields] " + ToPythonList(list)) array < string > passive1Names = [
} "pas_ordnance_pack",
} "pas_power_cell",
"pas_fast_embark",
// Should sort main weapons in following order: "pas_fast_health_regen"
// 1. primary ]
// 2. secondary
// 3. anti-titan
int function MainWeaponSort(entity a, entity b) {
int aID = a.GetDamageSourceID()
int bID = b.GetDamageSourceID()
int aIdx = MAIN_DAMAGE_SOURCES.find(aID)
int bIdx = MAIN_DAMAGE_SOURCES.find(bID)
if (aIdx == bIdx) {
return 0
} else if (aIdx != -1 && bIdx == -1) {
return -1
} else if (aIdx == -1 && bIdx != -1) {
return 1
}
return aIdx < bIdx ? -1 : 1
}
int function WeaponNameSort(entity a, entity b) {
return SortStringAlphabetize(a.GetWeaponClassName(), b.GetWeaponClassName())
}
entity function GetNthWeapon(array<entity> weapons, int index) {
return index < weapons.len() ? weapons[index] : null
}
string function GetWeaponName(entity weapon) {
string s = "null"
if (weapon != null) {
s = weapon.GetWeaponClassName()
}
return s
}
string function GetWeaponMods(entity weapon) {
if (weapon == null) {
return "null"
}
int modBits = weapon.GetModBitField()
return format("%d", modBits)
}
string function GetTitan(entity player) {
if(!player.IsTitan()) return "null"
return GetTitanCharacterName(player)
}
string function Anonymize(entity player) {
return "null" // unused
}
string function ToPythonList(array<string> list) { array < string > passive2Names = [
array<string> quoted = [] "pas_stealth_movement",
foreach (string s in list) { "pas_wallhang",
quoted.append("'" + s + "'") "pas_ads_hover",
} "pas_enemy_death_icons",
"pas_at_hunter"
return "\"[" + join(quoted, ", ") + "]\"" ]
}
void function Log(string s) {
print("[fvnkhead.killstat] " + s)
}
string function join(array<string> list, string separator) {
string s = ""
for (int i = 0; i < list.len(); i++) {
s += list[i]
if (i < list.len() - 1) {
s += separator
}
}
return s
}
void function Tone_Test_Auth(){
HttpRequest request
request.method = HttpRequestMethod.POST
request.url = file.Tone_URI + "/server"
request.headers = {Authorization = ["Bearer "+ file.Tone_token]}
void functionref( HttpRequestResponse ) onSuccess = void function ( HttpRequestResponse response )
{
if(response.statusCode == 200){
print("[Tone API] Tone API Online !")
file.connected = true
}else{
print("[Tone API] Tone API login failed")
print("[Tone API] " + response.body )
}
}
void functionref( HttpRequestFailure ) onFailure = void function ( HttpRequestFailure failure )
{
print("[Tone API] Tone API login failed")
print("[Tone API] " + failure.errorMessage )
}
NSHttpRequest(request, onSuccess, onFailure)
}
+226
View File
@@ -0,0 +1,226 @@
global function matchstat_Init
typedef playerSpecificStats table < string, var >
typedef playerStats table < string, playerSpecificStats >
typedef players table < string, playerStats >
struct {
table < string, players > weaponsUsed
} file
void function matchstat_Init(){
WeaponFireCallbacks_AddCallbackOnOwnerClassFired("player", OnWeaponFired)
AddDamageCallback("player", OnDamage)
AddCallback_OnPlayerRespawned( TrackPlayer )
// AddCallback_GameStateEnter(eGameState.WinnerDetermined, StopTrackPlayer)
// FlagInit( "" )
AddCallback_GameStateEnter(eGameState.Postmatch, ToneAPI_CloseMatch)
}
var function incrementVar(var value){
return expect float(value) + 1
}
void function CreatePlayer(entity player){
string playerUID = player.GetUID()
if(!(playerUID in file.weaponsUsed)){
var playerName = player.GetPlayerName()
file.weaponsUsed[playerUID] <- {}
file.weaponsUsed[playerUID].weapons <- {}
file.weaponsUsed[playerUID].titans <- {}
file.weaponsUsed[playerUID].stats <- {}
file.weaponsUsed[playerUID].stats.distance <- {
ground = 0.0,
wall = 0.0,
air = 0.0
}
file.weaponsUsed[playerUID].stats.time <- {
ground = 0.0,
wall = 0.0,
air = 0.0
}
}
}
void function CreateWeaponStats(string weaponName, entity player){
CreatePlayer(player)
string playerUID = player.GetUID()
if(!(weaponName in file.weaponsUsed[playerUID].weapons)){
// Has to make ints floats because you can't mix types apparently
playerSpecificStats stats = {
shotsFired = 0.0,
shotsHit = 0.0,
shotsCrit = 0.0,
shotsHeadshot = 0.0,
shotsRichochet = 0.0,
playtime = 0.0
}
file.weaponsUsed[playerUID].weapons[weaponName] <- stats
}
}
void function CreateTitanStats(string titanName, entity player){
CreatePlayer(player)
string playerUID = player.GetUID()
if(!(titanName in file.weaponsUsed[playerUID].titans)){
// Has to make ints floats because you can't mix types apparently
playerSpecificStats stats = {
shotsFired = 0.0,
shotsHit = 0.0,
shotsCrit = 0.0,
shotsHeadshot = 0.0,
shotsRichochet = 0.0,
playtime = 0.0
}
file.weaponsUsed[playerUID].titans[titanName] <- stats
}
}
void function OnWeaponFired(entity weapon, WeaponPrimaryAttackParams attackParams, var ammoUsed){
entity player = weapon.GetOwner()
string weaponName = weapon.GetWeaponClassName()
if(!IsValid(player) || !player.IsPlayer()){
return
}
string playerUID = player.GetUID()
CreateWeaponStats(weaponName, player)
file.weaponsUsed[playerUID].weapons[weaponName].shotsFired = incrementVar(file.weaponsUsed[playerUID].weapons[weaponName].shotsFired)
if(player.IsTitan()){
string titanName = GetTitanCharacterName(player)
CreateTitanStats(titanName, player)
file.weaponsUsed[playerUID].titans[titanName].shotsFired = incrementVar(file.weaponsUsed[playerUID].titans[titanName].shotsFired)
}
}
void function OnDamage( entity victim, var damageInfo){
entity player = DamageInfo_GetAttacker( damageInfo )
if(!IsValid(player) || !player.IsPlayer()){
return
}
string playerUID = player.GetUID()
entity weapon = DamageInfo_GetWeapon( damageInfo )
entity inflictor = DamageInfo_GetInflictor( damageInfo )
string weaponName
if(weapon){
weaponName = weapon.GetWeaponClassName()
}
else{
if ( inflictor && inflictor instanceof CProjectile && inflictor.IsProjectile() ){
weaponName = inflictor.ProjectileGetWeaponClassName()
}else{
ToneAPI_Log("[ERRR] Couldn't get weapon information")
return
}
}
CreateWeaponStats(weaponName, player)
file.weaponsUsed[playerUID].weapons[weaponName].shotsHit = incrementVar(file.weaponsUsed[playerUID].weapons[weaponName].shotsHit)
if(player.IsTitan()){
string titanName = GetTitanCharacterName(player)
CreateTitanStats(titanName, player)
file.weaponsUsed[playerUID].titans[titanName].shotsHit = incrementVar(file.weaponsUsed[playerUID].titans[titanName].shotsHit)
}
int damageType = DamageInfo_GetCustomDamageType( damageInfo )
bool crit = bool( damageType & DF_CRITICAL )
bool headshot = bool( damageType & DF_HEADSHOT )
// if(crit){
// file.weaponsUsed[playerUID].weapons[weaponName].shotsCrit = incrementVar(file.weaponsUsed[playerUID].weapons[weaponName].shotsCrit)
// if(player.IsTitan()){
// file.weaponsUsed[playerUID].titans[titanName].shotsCrit = incrementVar(file.weaponsUsed[playerUID].titans[titanName].shotsCrit)
// }
// }
if(headshot || crit){
file.weaponsUsed[playerUID].weapons[weaponName].shotsHeadshot = incrementVar(file.weaponsUsed[playerUID].weapons[weaponName].shotsHeadshot)
if(player.IsTitan()){
string titanName = GetTitanCharacterName(player)
file.weaponsUsed[playerUID].titans[titanName].shotsHeadshot = incrementVar(file.weaponsUsed[playerUID].titans[titanName].shotsHeadshot)
}
}
if ( inflictor && inflictor.IsProjectile() ){
if(inflictor.proj.projectileBounceCount > 1){
file.weaponsUsed[playerUID].weapons[weaponName].shotsRichochet = incrementVar(file.weaponsUsed[playerUID].weapons[weaponName].shotsRichochet)
if(player.IsTitan()){
string titanName = GetTitanCharacterName(player)
file.weaponsUsed[playerUID].titans[titanName].shotsRichochet = incrementVar(file.weaponsUsed[playerUID].titans[titanName].shotsRichochet)
}
}
}
}
void function TrackPlayer(entity player) {
string playerUID = player.GetUID()
CreatePlayer(player)
thread TrackPlayer_Threaded(player)
}
// void function StopTrackPlayer() {
// }
void function TrackPlayer_Threaded(entity player){
player.EndSignal( "OnDestroy" )
player.EndSignal( "OnDeath" )
vector position = player.GetOrigin()
string playerUID = player.GetUID()
float interval = 0.2
while(true){
wait interval
// Hmm
Assert( IsValid( player ) )
if(!IsValid(player)) return
// Ugly, should probably use flags
if(GetGameState() == eGameState.WinnerDetermined) return
entity weapon = player.GetActiveWeapon()
if(weapon){
string weaponName = weapon.GetWeaponClassName()
CreateWeaponStats(weaponName, player)
file.weaponsUsed[playerUID].weapons[weaponName].playtime = expect float(file.weaponsUsed[playerUID].weapons[weaponName].playtime) + interval
}else{
CreatePlayer(player)
}
if(player.IsTitan()){
string titanName = GetTitanCharacterName(player)
CreateTitanStats(titanName, player)
file.weaponsUsed[playerUID].titans[titanName].playtime = expect float(file.weaponsUsed[playerUID].titans[titanName].playtime) + interval
}else if(player.IsPlayer()){
// Could be a little inaccurate but should work fine if interval is small enough
if(player.IsWallRunning()){
file.weaponsUsed[playerUID].stats.time.wall = expect float(file.weaponsUsed[playerUID].stats.time.wall) + interval
file.weaponsUsed[playerUID].stats.distance.wall = expect float(file.weaponsUsed[playerUID].stats.distance.wall) + Distance(position, player.GetOrigin())
}
else if(!player.IsOnGround()){
file.weaponsUsed[playerUID].stats.time.air = expect float(file.weaponsUsed[playerUID].stats.time.air) + interval
file.weaponsUsed[playerUID].stats.distance.air = expect float(file.weaponsUsed[playerUID].stats.distance.air) + Distance(position, player.GetOrigin())
}
else{
file.weaponsUsed[playerUID].stats.time.ground = expect float(file.weaponsUsed[playerUID].stats.time.ground) + interval
file.weaponsUsed[playerUID].stats.distance.ground = expect float(file.weaponsUsed[playerUID].stats.distance.ground) + Distance(position, player.GetOrigin())
}
}
position = player.GetOrigin()
}
}
void function ToneAPI_CloseMatch(){
if(!toneapi_data.matchId){
ToneAPI_Log("[ERRR] Match is not registered!")
return
}
HttpRequest request
request.method = HttpRequestMethod.POST
request.url = toneapi_data.Tone_URI + "/match/" + string(toneapi_data.matchId) + "/close"
request.body = EncodeJSON(file.weaponsUsed)
Tone_HTTP_Request(
request,
void
function(HttpRequestResponse response) {}
)
}
+120
View File
@@ -0,0 +1,120 @@
global function toneapi_Init
global function Tone_HTTP_Request
global function ToneAPI_Log
string prefix = "\x1b[38;5;81m[TONE API]\x1b[0m "
global struct toneapi_struct {
string version
string Tone_URI
string Tone_protocol
string Tone_token
bool connected
int ornull matchId
string gameMode
string map
}
global toneapi_struct toneapi_data
void function toneapi_Init(){
//TODO : request anonymization data from API
if(GetMapName() == "mp_lobby") {
return
}
toneapi_data.version = GetConVarString("toneapi_version")
toneapi_data.Tone_URI = GetConVarString("Tone_URI")
toneapi_data.Tone_token = GetConVarString("Tone_token")
toneapi_data.connected = false
//Test auth and print result to console when server start
// Tone_Test_Auth()
//We should probably blacklist mp_lobby this
Tone_Register_Match()
AddCallback_OnClientConnected(JoinMessage)
// AddCallback_OnClientConnected(RegisterPlayer)
}
void function JoinMessage(entity player) {
//Chat_ServerPrivateMessage(player, prefix + "This server collects data using the Tone API. Check your data here: \x1b[34mtoneapi.com/" + player.GetPlayerName()+ "\x1b[0m", false, false)
Chat_ServerPrivateMessage(player, prefix + "This server collects data using the WIP Tone API. View statistics at https://toneapi.github.io/ToneAPI_webclient/", false, false)
}
void function Tone_HTTP_Request(HttpRequest request, void functionref(HttpRequestResponse) cbSuccess) {
if (!request.method) request.method = HttpRequestMethod.POST
if (request.url == "") {
ToneAPI_Log("[ERRR] Couldn't find URI for request. This should be reported")
return
}
request.headers = {
Authorization = ["Bearer " + toneapi_data.Tone_token]
}
NSHttpRequest(
request,
void function(HttpRequestResponse response): (cbSuccess) {
if (response.statusCode == 200 || response.statusCode == 201) {
cbSuccess(response)
} else {
if(response.statusCode == 401){
ToneAPI_Log("[WARN] Something might be wrong with your token")
}else{
ToneAPI_Log("[WARN] Something went wrong ! You'd better report this")
}
ToneAPI_Log("[WARN] " + response.statusCode)
ToneAPI_Log("[WARN] " + response.body)
}
},
void function(HttpRequestFailure failure) {
ToneAPI_Log("[WARN] Couldn't request the server! ToneAPI may be down.")
ToneAPI_Log("[WARN] " + failure.errorCode)
ToneAPI_Log("[WARN] " + failure.errorMessage)
}
)
}
void function Tone_Test_Auth() {
HttpRequest request
request.method = HttpRequestMethod.POST
request.url = toneapi_data.Tone_URI + "/"
Tone_HTTP_Request(
request,
void function(HttpRequestResponse response) {
ToneAPI_Log("Tone API Initialized")
toneapi_data.connected = true
}
)
}
bool function hasCustomAirAccel(){
return Code_GetCurrentPlaylistVarOrUseValue("custom_air_accel_pilot", "null") != "null"
}
void function Tone_Register_Match() {
HttpRequest request
request.method = HttpRequestMethod.POST
request.url = toneapi_data.Tone_URI + "/match"
request.body = EncodeJSON({
gamemode = GameRules_GetGameMode()
game_map = StringReplace(GetMapName(), "mp_", "")
server_name = GetConVarString("ns_server_name")
air_accel = hasCustomAirAccel()
})
Tone_HTTP_Request(
request,
void function(HttpRequestResponse response) {
table data = DecodeJSON(response.body)
toneapi_data.matchId = expect int(data.match)
ToneAPI_Log("Tone API Online !")
ToneAPI_Log("Sending kills with match ID : " + data.match)
}
)
}
void function ToneAPI_Log(string s) {
print(prefix + s)
}