diff --git a/mod.json b/mod.json index d674b9a..d41170f 100644 --- a/mod.json +++ b/mod.json @@ -11,7 +11,7 @@ }, { "Name": "Tone_URI", - "DefaultValue": "https://tone.sleepycat.date/v2" + "DefaultValue": "https://tone.sleepycat.date/v3/server" }, { "Name": "Tone_token", diff --git a/mod/scripts/vscripts/dev_utils.nut b/mod/scripts/vscripts/dev_utils.nut new file mode 100644 index 0000000..466cf7a --- /dev/null +++ b/mod/scripts/vscripts/dev_utils.nut @@ -0,0 +1,79 @@ +array 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 mods = GetWeaponMods_Global(weaponName) + array list = [weaponName] + foreach (string mod in mods) { + list.append(mod) + } + + Log("[DumpWeaponModBitFields] " + ToPythonList(list)) + } +} + + +string function ToPythonList(array list) { + array quoted = [] + foreach (string s in list) { + quoted.append("'" + s + "'") + } + + return "\"[" + join(quoted, ", ") + "]\"" +} + + +string function join(array 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()) +} \ No newline at end of file diff --git a/mod/scripts/vscripts/killstat.nut b/mod/scripts/vscripts/killstat.nut index a2425b0..76da745 100644 --- a/mod/scripts/vscripts/killstat.nut +++ b/mod/scripts/vscripts/killstat.nut @@ -1,21 +1,13 @@ 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 customParameters - int matchId + string matchId string gameMode string map } file @@ -25,27 +17,12 @@ void function killstat_Init() { file.Tone_URI = GetConVarString("Tone_URI") file.Tone_token = GetConVarString("Tone_token") file.connected = false - file.servername = GetConVarString("ns_server_name") - //register to Tone API if default or invalid token + + //Test auth and print result to console when server start Tone_Test_Auth() - // custom parameters - string customParameterString = GetConVarString("killstat_custom_parameters") - array customParameterEntries = split(customParameterString, ",") - file.customParameters = [] - foreach (string customParameterEntry in customParameterEntries) { - array 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) - } - + //We should probably blacklist mp_lobby this + Tone_Register_Match() // callbacks AddCallback_GameStateEnter(eGameState.Playing, killstat_Begin) AddCallback_OnPlayerKilled(killstat_Record) @@ -53,13 +30,6 @@ void function killstat_Init() { 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) { @@ -68,26 +38,22 @@ void function JoinMessage(entity player) { } 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") + Log("Sending kill data to " + file.Tone_URI + "/kill") } void function killstat_Record(entity victim, entity attacker, var damageInfo) { if ( !victim.IsPlayer() || !attacker.IsPlayer() || GetGameState() != eGameState.Playing ) - return + return + // if(file.matchId) + // return - table values = {} - //remove this in the future - foreach (Parameter p in file.customParameters) { - values[p] <- p.value - } + + table attackerValues = {} + table victimValues = {} array attackerWeapons = attacker.GetMainWeapons() array victimWeapons = victim.GetMainWeapons() @@ -110,81 +76,236 @@ void function killstat_Record(entity victim, entity attacker, var damageInfo) { entity vow2 = GetNthWeapon(victimOffhandWeapons, 1) 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) string damageName = DamageSourceIDToString(damageSourceId) - values["cause_of_death"] <- damageName float dist = Distance(attacker.GetOrigin(), victim.GetOrigin()) - values["distance"] <- format("%.3f", dist) + + table values = { + killstat_version = file.killstatVersion + match_id = file.matchId + game_time = Time() + player_count = GetPlayerArray().len() + cause_of_death = damageName + distance = dist + attacker = { + name = attacker.GetPlayerName() + id = attacker.GetUID() + titan = GetTitan(attacker) + velocity = Distance(<0,0,0>, attacker.GetVelocity()) + cloaked = attacker.IsCloaked( true ) + state = GetMovementState(attacker) + current_weapon = { + name = GetWeaponName(attacker.GetLatestPrimaryWeapon()) + mods = GetWeaponMods(attacker.GetLatestPrimaryWeapon()) + } + loadout = { + primary = { + name = GetWeaponName(aw1) + mods = GetWeaponMods(aw1) + } + secondary = { + name = GetWeaponName(aw2) + mods = GetWeaponMods(aw2) + } + anti_titan = { + name = GetWeaponName(aw3) + mods = GetWeaponMods(aw3) + } + ordnance = { + name = GetWeaponName(aow1) + mods = GetWeaponMods(aow1) + } + tactical = { + name = GetWeaponName(aow2) + mods = GetWeaponMods(aow2) + } + } + } + victim = { + name = victim.GetPlayerName() + id = victim.GetUID() + titan = GetTitan(victim) + velocity = Distance(<0,0,0>, victim.GetVelocity()) + cloaked = victim.IsCloaked( true ) + state = GetMovementState(victim) + current_weapon = { + name = GetWeaponName(victim.GetLatestPrimaryWeapon()) + mods = GetWeaponMods(victim.GetLatestPrimaryWeapon()) + } + loadout = { + primary = { + name = GetWeaponName(vw1) + mods = GetWeaponMods(vw1) + } + secondary = { + name = GetWeaponName(vw2) + mods = GetWeaponMods(vw2) + } + anti_titan = { + name = GetWeaponName(vw3) + mods = GetWeaponMods(vw3) + } + ordnance = { + name = GetWeaponName(vow1) + mods = GetWeaponMods(vow1) + } + tactical = { + name = GetWeaponName(vow2) + mods = GetWeaponMods(vow2) + } + } + } + } HttpRequest request request.method = HttpRequestMethod.POST request.url = file.Tone_URI + "/server/kill" - request.headers = {Authorization = ["Bearer " + file.Tone_token]} request.body = EncodeJSON(values) - void functionref( HttpRequestResponse ) onSuccess = void function ( HttpRequestResponse response ) - { - if(response.statusCode == 200 || response.statusCode == 201){ - print("[Tone API] Kill data sent!") - }else{ - print("[Tone API][WARN] Couldn't send kill data") - print("[Tone API][WARN] " + response.body ) - } - } - - void functionref( HttpRequestFailure ) onFailure = void function ( HttpRequestFailure failure ) - { - print("[Tone API][WARN] Couldn't send kill data") - print("[Tone API][WARN] " + failure.errorMessage ) - } - NSHttpRequest(request, onSuccess, onFailure) + Tone_HTTP_Request( + request, + void function ( HttpRequestResponse response ) {} + ) } void function killstat_End() { Log("-----END KILLSTAT-----") } +string function GetMovementState(entity player){ + Assert(IsPilot(player)) + //IsPhaseShifted + //IsStanding + if(player.IsWallHanging()) + return "WallHanging" + if(player.IsWallRunning()) + return "WallRunning" + if(player.IsZiplining()) + return "Ziplining" + if(!player.IsOnGround()) + return "Airborne" + if(player.IsCrouched()) + return "Crouching" + return "OnGround" +} + +// Should sort main weapons in following order: +// 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 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 +} + +int function GetWeaponMods(entity weapon) { + if (weapon == null) { + return 0 + } + int modBits = weapon.GetModBitField() + // return format("%d", modBits) + return modBits +} + +string function GetTitan(entity player) { + if(!player.IsTitan()) return "null" + return GetTitanCharacterName(player) +} + +void function Log(string s) { + print(prefix + " " + s) +} + +void function Tone_Test_Auth(){ + HttpRequest request + request.method = HttpRequestMethod.POST + request.url = file.Tone_URI + "/" + Tone_HTTP_Request( + request, + void function ( HttpRequestResponse response ) { + Log("Tone API Online !") + file.connected = true + } + ) +} + + +void function Tone_Register_Match(){ + HttpRequest request + request.method = HttpRequestMethod.POST + request.url = file.Tone_URI + "/match" + request.body = EncodeJSON({ + gameMode = GameRules_GetGameMode() + map = StringReplace(GetMapName(), "mp_", "") + servername = GetConVarString("ns_server_name") + }) + Tone_HTTP_Request( + request, + void function ( HttpRequestResponse response ) { + Log("match ID : " + response.body) + file.matchId = response.body + } + ) +} + + +void function Tone_HTTP_Request(HttpRequest request, void functionref( HttpRequestResponse ) cbSuccess){ + if(!request.method) request.method = HttpRequestMethod.POST + if(request.url == ""){ + Log("[ERRR] Couldn't find URI for request. This should be reported") + return + } + request.headers = {Authorization = ["Bearer "+ file.Tone_token]} + + NSHttpRequest( + request, + void function ( HttpRequestResponse response ) : (cbSuccess) + { + if(response.statusCode == 200 || response.statusCode == 201){ + cbSuccess(response) + }else{ + Log("[WARN] Something might be wrong with your token") + Log("[WARN]" + response.statusCode) + Log("[WARN]" + response.body) + } + }, + void function ( HttpRequestFailure failure ) + { + Log("[WARN] Couldn't request the server! ToneAPI may be down.") + Log("[WARN]" + failure.errorCode) + Log("[WARN]" + failure.errorMessage) + } + ) +} + + array MAIN_DAMAGE_SOURCES = [ // primaries eDamageSourceId.mp_weapon_car, @@ -222,125 +343,4 @@ array MAIN_DAMAGE_SOURCES = [ 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 mods = GetWeaponMods_Global(weaponName) - array list = [weaponName] - foreach (string mod in mods) { - list.append(mod) - } - - Log("[DumpWeaponModBitFields] " + ToPythonList(list)) - } -} - -// Should sort main weapons in following order: -// 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 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 list) { - array quoted = [] - foreach (string s in list) { - quoted.append("'" + s + "'") - } - - return "\"[" + join(quoted, ", ") + "]\"" -} - -void function Log(string s) { - print("[fvnkhead.killstat] " + s) -} -string function join(array 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) -} \ No newline at end of file +] \ No newline at end of file