big refactor
This commit is contained in:
@@ -6,8 +6,8 @@
|
|||||||
"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",
|
||||||
@@ -24,14 +24,17 @@
|
|||||||
],
|
],
|
||||||
"Scripts": [
|
"Scripts": [
|
||||||
{
|
{
|
||||||
"Path": "toneapi_shared.nut",
|
"Path": "toneapi.nut",
|
||||||
"RunOn": "SERVER"
|
"RunOn": "SERVER",
|
||||||
|
"ServerCallback": {
|
||||||
|
"After": "toneapi_Init"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Path": "matchstat.nut",
|
"Path": "matchstat.nut",
|
||||||
"RunOn": "SERVER",
|
"RunOn": "SERVER",
|
||||||
"ServerCallback": {
|
"ServerCallback": {
|
||||||
"After": "matchStat_Init"
|
"After": "matchstat_Init"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,66 +1,20 @@
|
|||||||
global function killstat_Init
|
global function killstat_Init
|
||||||
|
|
||||||
struct {
|
void function killstat_Init() {
|
||||||
string killstatVersion
|
|
||||||
string Tone_URI
|
|
||||||
string Tone_protocol
|
|
||||||
string Tone_token
|
|
||||||
bool connected
|
|
||||||
|
|
||||||
string matchId
|
|
||||||
string gameMode
|
|
||||||
string map
|
|
||||||
}
|
|
||||||
file
|
|
||||||
|
|
||||||
void
|
|
||||||
|
|
||||||
function killstat_Init() {
|
|
||||||
file.killstatVersion = GetConVarString("killstat_version")
|
|
||||||
file.Tone_URI = GetConVarString("Tone_URI")
|
|
||||||
file.Tone_token = GetConVarString("Tone_token")
|
|
||||||
file.connected = false
|
|
||||||
if(GetMapName() == "mp_lobby") {
|
if(GetMapName() == "mp_lobby") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//Test auth and print result to console when server start
|
|
||||||
Tone_Test_Auth()
|
|
||||||
|
|
||||||
//We should probably blacklist mp_lobby this
|
|
||||||
Tone_Register_Match()
|
|
||||||
// callbacks
|
|
||||||
AddCallback_GameStateEnter(eGameState.Playing, killstat_Begin)
|
|
||||||
AddCallback_OnPlayerKilled(killstat_Record)
|
AddCallback_OnPlayerKilled(killstat_Record)
|
||||||
AddCallback_GameStateEnter(eGameState.Postmatch, killstat_End)
|
|
||||||
AddCallback_OnClientConnected(JoinMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool function hasCustomAirAccel(){
|
void function killstat_Record(entity victim, entity attacker, var damageInfo) {
|
||||||
return Code_GetCurrentPlaylistVarOrUseValue("custom_air_accel_pilot", "null") != "null"
|
|
||||||
}
|
|
||||||
|
|
||||||
void function JoinMessage(entity player) {
|
|
||||||
//Chat_ServerPrivateMessage(player, killstat_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, killstat_prefix + "This server collects data using the WIP Tone API. View statistics at https://toneapi.github.io/ToneAPI_webclient/", false, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
|
|
||||||
function killstat_Begin() {
|
|
||||||
//TODO : request MatchID from API ------------------------------------------------------------------------------------------------
|
|
||||||
//TODO : request anonymization data from API
|
|
||||||
|
|
||||||
Log("-----BEGIN KILLSTAT-----")
|
|
||||||
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)
|
if (!victim.IsPlayer() || !attacker.IsPlayer() || GetGameState() != eGameState.Playing)
|
||||||
return
|
return
|
||||||
// if(file.matchId)
|
if(!toneapi_data.matchId){
|
||||||
// return
|
ToneAPI_Log("[ERRR] Match is not registered!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
table attackerValues = {}
|
table attackerValues = {}
|
||||||
@@ -94,8 +48,8 @@ function killstat_Record(entity victim, entity attacker, var damageInfo) {
|
|||||||
float dist = Distance(attacker.GetOrigin(), victim.GetOrigin())
|
float dist = Distance(attacker.GetOrigin(), victim.GetOrigin())
|
||||||
|
|
||||||
table values = {
|
table values = {
|
||||||
killstat_version = file.killstatVersion
|
version = toneapi_data.version
|
||||||
match_id = int(file.matchId)
|
match_id = toneapi_data.matchId
|
||||||
game_time = Time()
|
game_time = Time()
|
||||||
player_count = GetPlayerArray().len()
|
player_count = GetPlayerArray().len()
|
||||||
cause_of_death = damageName
|
cause_of_death = damageName
|
||||||
@@ -177,7 +131,7 @@ function killstat_Record(entity victim, entity attacker, var damageInfo) {
|
|||||||
|
|
||||||
HttpRequest request
|
HttpRequest request
|
||||||
request.method = HttpRequestMethod.POST
|
request.method = HttpRequestMethod.POST
|
||||||
request.url = file.Tone_URI + "/kill"
|
request.url = toneapi_data.Tone_URI + "/kill"
|
||||||
request.body = EncodeJSON(values)
|
request.body = EncodeJSON(values)
|
||||||
|
|
||||||
Tone_HTTP_Request(
|
Tone_HTTP_Request(
|
||||||
@@ -187,15 +141,7 @@ function killstat_Record(entity victim, entity attacker, var damageInfo) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
string function GetMovementState(entity player) {
|
||||||
|
|
||||||
function killstat_End() {
|
|
||||||
Log("-----END KILLSTAT-----")
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
|
|
||||||
function GetMovementState(entity player) {
|
|
||||||
Assert(IsPilot(player))
|
Assert(IsPilot(player))
|
||||||
//IsPhaseShifted
|
//IsPhaseShifted
|
||||||
//IsStanding
|
//IsStanding
|
||||||
@@ -216,9 +162,7 @@ function GetMovementState(entity player) {
|
|||||||
// 1. primary
|
// 1. primary
|
||||||
// 2. secondary
|
// 2. secondary
|
||||||
// 3. anti-titan
|
// 3. anti-titan
|
||||||
int
|
int function MainWeaponSort(entity a, entity b) {
|
||||||
|
|
||||||
function MainWeaponSort(entity a, entity b) {
|
|
||||||
int aID = a.GetDamageSourceID()
|
int aID = a.GetDamageSourceID()
|
||||||
int bID = b.GetDamageSourceID()
|
int bID = b.GetDamageSourceID()
|
||||||
|
|
||||||
@@ -236,8 +180,7 @@ function MainWeaponSort(entity a, entity b) {
|
|||||||
return aIdx < bIdx ? -1 : 1
|
return aIdx < bIdx ? -1 : 1
|
||||||
}
|
}
|
||||||
|
|
||||||
entity
|
entity function GetNthWeapon(array < entity > weapons, int index) {
|
||||||
function GetNthWeapon(array < entity > weapons, int index) {
|
|
||||||
return index < weapons.len() ? weapons[index] : null
|
return index < weapons.len() ? weapons[index] : null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,80 +217,6 @@ var function GetTitan(entity player) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
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 Initialized")
|
|
||||||
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()
|
|
||||||
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)
|
|
||||||
Log("match ID : " + string(expect int(data.match)))
|
|
||||||
file.matchId = string(expect int(data.match))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if(response.statusCode == 401){
|
|
||||||
Log("[WARN] Something might be wrong with your token")
|
|
||||||
}else{
|
|
||||||
Log("[WARN] Something went wrong ! You'd better report this")
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var function getPlayerPassive1(entity player) {
|
var function getPlayerPassive1(entity player) {
|
||||||
foreach (string key, int val in ePassives){
|
foreach (string key, int val in ePassives){
|
||||||
if (PlayerHasPassive(player, val)) {
|
if (PlayerHasPassive(player, val)) {
|
||||||
|
|||||||
@@ -1,31 +1,23 @@
|
|||||||
global function matchStat_Init
|
global function matchstat_Init
|
||||||
|
|
||||||
struct weaponStats {
|
struct weaponStats {
|
||||||
int shotsFired
|
int shotsFired
|
||||||
int shotsHit
|
int shotsHit
|
||||||
int shotCrit
|
int shotCrit
|
||||||
int shotHeadshot
|
int shotHeadshot
|
||||||
|
int shotRichochet
|
||||||
}
|
}
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
table < entity, table < string, weaponStats > > weaponsUsed
|
table < entity, table < string, weaponStats > > weaponsUsed
|
||||||
} file
|
} file
|
||||||
|
|
||||||
void function matchStat_Init(){
|
void function matchstat_Init(){
|
||||||
// AddCallback_OnPlayerRespawned( TrackWeapons )
|
// AddCallback_OnPlayerRespawned( TrackWeapons )
|
||||||
AddCallback_OnPlayerGetsNewPilotLoadout( TrackWeaponsOnChange )
|
WeaponFireCallbacks_AddCallbackOnOwnerClassFired("player", OnWeaponFired)
|
||||||
AddCallback_OnPilotBecomesTitan( TrackTitanWeaponOnBecome )
|
|
||||||
AddDamageCallback("player", OnDamage)
|
AddDamageCallback("player", OnDamage)
|
||||||
}
|
}
|
||||||
|
|
||||||
void function TrackWeapons( entity player ) {
|
|
||||||
array<entity> weapons = player.GetMainWeapons()
|
|
||||||
array<entity> offhand = player.GetOffhandWeapons()
|
|
||||||
foreach(entity weapon in weapons){
|
|
||||||
WeaponFireCallbacks_AddCallbackOnWeaponFired(weapon, OnWeaponFired)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void function OnWeaponFired(entity weapon, WeaponPrimaryAttackParams attackParams, var ammoUsed){
|
void function OnWeaponFired(entity weapon, WeaponPrimaryAttackParams attackParams, var ammoUsed){
|
||||||
entity player = weapon.GetOwner()
|
entity player = weapon.GetOwner()
|
||||||
string weaponName = weapon.GetWeaponClassName()
|
string weaponName = weapon.GetWeaponClassName()
|
||||||
@@ -40,7 +32,8 @@ void function OnWeaponFired(entity weapon, WeaponPrimaryAttackParams attackParam
|
|||||||
shotsFired = 0,
|
shotsFired = 0,
|
||||||
shotsHit = 0,
|
shotsHit = 0,
|
||||||
shotCrit = 0,
|
shotCrit = 0,
|
||||||
shotHeadshot = 0
|
shotHeadshot = 0,
|
||||||
|
shotRichochet = 0
|
||||||
}
|
}
|
||||||
file.weaponsUsed[player][weaponName] <- stats
|
file.weaponsUsed[player][weaponName] <- stats
|
||||||
}
|
}
|
||||||
@@ -54,7 +47,19 @@ void function OnDamage( entity victim, var damageInfo){
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
entity weapon = DamageInfo_GetWeapon( damageInfo )
|
entity weapon = DamageInfo_GetWeapon( damageInfo )
|
||||||
string weaponName = weapon.GetWeaponClassName()
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!(player in file.weaponsUsed)){
|
if(!(player in file.weaponsUsed)){
|
||||||
file.weaponsUsed[player] <- {}
|
file.weaponsUsed[player] <- {}
|
||||||
@@ -64,7 +69,8 @@ void function OnDamage( entity victim, var damageInfo){
|
|||||||
shotsFired = 0,
|
shotsFired = 0,
|
||||||
shotsHit = 0,
|
shotsHit = 0,
|
||||||
shotCrit = 0,
|
shotCrit = 0,
|
||||||
shotHeadshot = 0
|
shotHeadshot = 0,
|
||||||
|
shotRichochet = 0
|
||||||
}
|
}
|
||||||
file.weaponsUsed[player][weaponName] <- stats
|
file.weaponsUsed[player][weaponName] <- stats
|
||||||
}
|
}
|
||||||
@@ -78,16 +84,10 @@ void function OnDamage( entity victim, var damageInfo){
|
|||||||
if(headshot){
|
if(headshot){
|
||||||
file.weaponsUsed[player][weaponName].shotHeadshot++
|
file.weaponsUsed[player][weaponName].shotHeadshot++
|
||||||
}
|
}
|
||||||
}
|
if ( inflictor && inflictor instanceof CProjectile && inflictor.IsProjectile() ){
|
||||||
|
if(inflictor.proj.projectileBounceCount > 1){
|
||||||
|
file.weaponsUsed[player][weaponName].shotRichochet++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void function TrackTitanWeapons( entity player ){
|
|
||||||
TrackWeapons( player )
|
|
||||||
}
|
|
||||||
|
|
||||||
void function TrackWeaponsOnChange( entity player, PilotLoadoutDef loadout ) {
|
|
||||||
TrackWeapons( player )
|
|
||||||
}
|
|
||||||
|
|
||||||
void function TrackTitanWeaponOnBecome( entity player, entity titan ){
|
|
||||||
TrackTitanWeapons( player )
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
globalize_all_functions
|
|
||||||
|
|
||||||
global string killstat_prefix = "\x1b[38;5;81m[TONE API]\x1b[0m "
|
|
||||||
|
|
||||||
void function Log(string s) {
|
|
||||||
print(killstat_prefix + s)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user