Разработчик
Скриптер
Проверенный
- Сообщения
- 2,751
- Реакции
- 3,016
- Помог
- 61 раз(а)
Так как не имеется никаких функций для получения статистики по оружию игрока в SourceMod (CS:GO) (допустим get_user_wstats(), get_user_wstats() и подобного) решил сделать API. Попытка пока-что не увенчалась успехом, событие player_hurt не умеет адекватно показывать с какого оружия нанесён урон (вместо USP пишет P2000 и так с многими оружиями, что доступны для замены в инвентаре игрока). Таковая проблема имеется на vanilla сервере (игрой в логи пишется не корректное оружие).
Призываю в тред как минимум:
ибо проблема достойна публичного обсуждения.
Костылекод прикладываю.
Нечто подобное API реализовано в плагине gameME Plugin
https://github.com/gamemedev/plugin-sourcemod/blob/master/scripting/gameme.sp#L161
Призываю в тред как минимум:
ибо проблема достойна публичного обсуждения.
Костылекод прикладываю.
Код:
#include <sourcemod>
#include <sdktools>
#include <cstrike>
#define MAX_PLAYERS 32
static const char VERSION[] = "1.0.0";
public Plugin myinfo =
{
name = "CSSTATS API",
author = "wopox1337",
version = VERSION
};
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
CreateNative("get_client_wstats", __get_client_wstats);
return APLRes_Success;
}
ConVar csstats_pause;
ConVar csstats_rankbots;
public void OnPluginStart()
{
csstats_pause = CreateConVar("csstats_pause", "0",
.hasMin = true, .min = 0.0, .hasMax = true, .max = 1.0
);
csstats_rankbots = CreateConVar("csstats_rankbots", "1",
.hasMin = true, .min = 0.0, .hasMax = true, .max = 1.0
);
HookEvent("weapon_fire", Event_CSGOWeaponFire);
HookEvent("player_hurt", Event_CSGOPlayerHurt);
// HookEvent("weapon_fire_on_empty", Event_CSGOPlayerFire);
Init_Tests();
}
// *****************************************************
// class Stats
// *****************************************************
enum BodyHits_s {
hit_Chest,
hit_Head,
hit_Stomach,
hit_LeftArm,
hit_RightArm,
hit_LeftLeg,
hit_RightLeg
}
enum struct Stats_s {
int hits;
int shots;
int damage;
int hs;
int tks;
int kills;
int deaths;
int bodyHits[BodyHits_s];
int bPlants;
int bExplosions;
int bDefusions;
int bDefused;
void Clear() {
this.hits = this.shots = this.damage = this.hs = this.tks = this.kills = this.deaths = this.bDefusions = this.bDefused = this.bPlants = this.bExplosions = 0;
}
}
enum struct PlayerWeapon {
char name[32];
int ammo;
int clip;
Stats_s stats;
}
const MAX_CWEAPONS = 6;
const PlayerWeapon_SIZE = view_as<int>(CSWeapon_MAX_WEAPONS) + MAX_CWEAPONS;
PlayerWeapon g_aWeapons[MAX_PLAYERS + 1][PlayerWeapon_SIZE];
PlayerWeapon g_aAttackers[MAX_PLAYERS + 1][MAX_PLAYERS + 1];
PlayerWeapon g_aVictims[MAX_PLAYERS + 1][MAX_PLAYERS + 1];
Stats_s g_aWeaponsR[MAX_PLAYERS + 1][PlayerWeapon_SIZE];
Stats_s g_aLifeStats[MAX_PLAYERS + 1];
#define _ClientWeaponStats(%1,%2) g_aWeapons[%1][%2].stats
#define _ClientAttackersStats(%1,%2) g_aAttackers[%1][%2].stats
#define _ClientVictimsStats(%1,%2) g_aVictims[%1][%2].stats
#define _ClientWeaponStatsR(%1,%2) g_aWeaponsR[%1][%2]
#define _ClientLifeStats(%1) g_aLifeStats[%1]
// Player_s g_aPlayers[MAX_PLAYERS + 1];
public Action Event_CSGOWeaponFire(Event event, char[] name, bool dontBroadcast)
{
int client = GetClientOfUserId(event.GetInt("userid"));
static char szWeapon[64];
event.GetString("weapon", szWeapon, sizeof(szWeapon));
static CSWeaponID iWeaponID = _NormalizeWeaponID(szWeapon);
___LogWeapon("Event_CSGOWeaponFire", client, szWeapon, iWeaponID);
saveShot(client, iWeaponID);
}
stock void ___LogWeapon(char[] func, int client, char[] szWeapon, CSWeaponID iWeaponID)
{
PrintToServer(" ::: szWeapon:'%s'", szWeapon);
CSWeaponID iCurWeapon;
char szCurWeapon[64];
switch(iWeaponID)
{
case CSWeapon_DEAGLE,
// Grenades
CSWeapon_FLASHBANG, CSWeapon_SMOKEGRENADE, CSWeapon_HEGRENADE,
CSWeapon_INCGRENADE, CSWeapon_TAGGRENADE, CSWeapon_MOLOTOV,
CSWeapon_DECOY,
//
CSWeapon_KNIFE:
{
iCurWeapon = iWeaponID;
}
default:
{
iCurWeapon = __GetCurrentWeaponID(client);
}
}
if(!CS_IsValidWeaponID(iCurWeapon))
{
PrintToServer(" Invalid weaponID:%i", iCurWeapon);
return;
}
CS_WeaponIDToAlias(iCurWeapon, szCurWeapon, sizeof(szCurWeapon));
PrintToServer(" \t > %s() -> client:%i, szWeapon:'%s'(id:%i), __GetCurrentWeaponID:%i ('s')",
func, client, szWeapon, iWeaponID, iCurWeapon
);
}
// sv_cheats 1; bot_stop 1
CSWeaponID _NormalizeWeaponID(char[] szWeapon)
{
CS_GetTranslatedWeaponAlias(szWeapon, szWeapon, 64);
// PrintToServer("szWeapon='%s' (%i)", szWeapon, CS_AliasToWeaponID(szWeapon));
return CS_AliasToWeaponID(szWeapon);
}
void saveShot(int client, CSWeaponID iWeaponID)
{
if ( !isModuleActive() )
return;
if ( ignoreBots(client) )
return;
_ClientWeaponStats(client, iWeaponID).shots++;
// _ClientWeaponStatsR(client, iWeaponID).shots++;
// PrintToServer("client:%i, iWeaponID:%i, (WShots:%i)", client, iWeaponID, g_aPlayers[client].weapons[iWeaponID].shots);
}
public Action Event_CSGOPlayerHurt(Event event, char[] name, bool dontBroadcast)
{
// "userid" "short" // player index who was hurt
// "attacker" "short" // player index who attacked
// "health" "byte" // remaining health points
// "armor" "byte" // remaining armor points
// "weapon" "string" // weapon name attacker used, if not the world
// "dmg_health" "byte" // damage done to health
// "dmg_armor" "byte" // damage done to armor
// "hitgroup" "byte" // hitgroup that was damaged
int client = GetClientOfUserId(event.GetInt("userid"));
int attacker = GetClientOfUserId(event.GetInt("attacker"));
static char szWeapon[64];
event.GetString("weapon", szWeapon, sizeof(szWeapon));
static CSWeaponID iWeaponID;
if(strcmp(szWeapon, "inferno") == 0)
{
iWeaponID = CSWeapon_MOLOTOV;
PrintToServer("AAAAAAAAAAAAAA!");
}
else iWeaponID = _NormalizeWeaponID(szWeapon);
___LogWeapon("Event_CSGOPlayerHurt", attacker, szWeapon, iWeaponID);
int iDamage = GetClientOfUserId(event.GetInt("dmg_health"));
int iHitGroup = GetClientOfUserId(event.GetInt("hitgroup"));
saveHit(attacker, client, iWeaponID, iDamage, iHitGroup);
}
void saveHit(int client, int victim, CSWeaponID iWeaponID, int iDamage, int body)
{
// if ( !isModuleActive() )
// return;
// if ( ignoreBots(client, victim) )
// return;
// if ( client == victim )
// return;
_ClientWeaponStats(client, iWeaponID).hits++;
PrintToServer("SaveHIT(client:%i, iWeap:%i), hits:%i", client, iWeaponID, _ClientWeaponStats(client, iWeaponID).hits);
_ClientWeaponStats(client, iWeaponID).damage += iDamage;
_ClientWeaponStats(client, iWeaponID).bodyHits[body]++;
_ClientAttackersStats(client, victim).hits++;
_ClientAttackersStats(client, victim).damage += iDamage;
_ClientAttackersStats(client, victim).bodyHits[body]++;
_ClientVictimsStats(victim, client).hits++;
_ClientVictimsStats(victim, client).damage += iDamage;
_ClientVictimsStats(victim, client).bodyHits[body]++;
_ClientWeaponStatsR(client, iWeaponID).hits++;
_ClientWeaponStatsR(client, iWeaponID).damage += iDamage;
_ClientWeaponStatsR(client, iWeaponID).bodyHits[body]++;
_ClientLifeStats(client).hits++;
_ClientLifeStats(client).damage += iDamage;
_ClientLifeStats(client).bodyHits[body]++;
}
// API
native bool get_client_wstats(int client, int iWeaponID, any[] aStats);
public int __get_client_wstats(Handle plugin, int numParams)
{
enum { arg_client = 1, arg_weaponID, arg_stats };
// Check player range
// Check weapon range
static int client; client = GetNativeCell(arg_client);
static int iWeaponID; iWeaponID = GetNativeCell(arg_weaponID);
static iShots; iShots = _ClientWeaponStats(client, iWeaponID).shots;
if (iShots)
{
static Stats_s aStats;
aStats.kills = _ClientWeaponStats(client, iWeaponID).kills;
aStats.deaths = _ClientWeaponStats(client, iWeaponID).deaths;
aStats.hs = _ClientWeaponStats(client, iWeaponID).hs;
aStats.tks = _ClientWeaponStats(client, iWeaponID).tks;
aStats.shots = _ClientWeaponStats(client, iWeaponID).shots;
aStats.hits = _ClientWeaponStats(client, iWeaponID).hits;
aStats.damage = _ClientWeaponStats(client, iWeaponID).damage;
PrintToServer("[API CB]->{hits:%i, shots:%i, damage:%i, hs:%i, tks:%i, kills:%i, deaths:%i}",
aStats.hits, aStats.shots, aStats.damage, aStats.hs, aStats.tks, aStats.kills, aStats.deaths
);
SetNativeArray(arg_stats, aStats, sizeof(aStats));
return true;
}
return false;
}
// MISC
bool isModuleActive()
{
return !csstats_pause.BoolValue;
}
bool ignoreBots(int client, int other = 0)
{
return ( !csstats_rankbots.BoolValue && ( IsFakeClient(client) || (other && IsFakeClient(other)) ) );
}
// TEST
void Init_Tests()
{
RegConsoleCmd("TEST", Command_Test);
}
public Action Command_Test(int client, int args)
{
Stats_s aStats;
get_client_wstats(client, 61, aStats);
PrintToServer("[API Response]->{hits:%i, shots:%i, damage:%i, hs:%i, tks:%i, kills:%i, deaths:%i}",
aStats.hits, aStats.shots, aStats.damage, aStats.hs, aStats.tks, aStats.kills, aStats.deaths
);
return Plugin_Handled;
}
stock CSWeaponID __GetCurrentWeaponID(int client)
{
int iWeaponEnt = GetEntPropEnt(client, Prop_Data, "m_hActiveWeapon");
// PrintToServer("m_hActiveWeapon:%i, m_iItemDefinitionIndex:%i", iWeaponEnt, GetEntProp(iWeaponEnt, Prop_Send, "m_iItemDefinitionIndex"));
return view_as<CSWeaponID>( GetEntProp(iWeaponEnt, Prop_Send, "m_iItemDefinitionIndex") );
}
// sv_cheats 1; bot_stop 1
30 Авг 2019
https://github.com/gamemedev/plugin-sourcemod/blob/master/scripting/gameme.sp#L161