Простая статистика

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #1
Много кто из начинающих скриптеров просили пример статистики.
Однажды я написал его и потерял, вот нашел. Решил выложить сюда, может кому пригодится.

P.S. Код писал давно, так что на оптимизацию не особо смотрите.
PHP:
#pragma semicolon 1
#include <sourcemod>

/*
Для начала определяемся с какой базой мы будем работать.
В даном случае мы юзаем sqlite. Тоесть локальную базу.
Мы сам задаем имена базы и таблиц, поэтому нам не нужен переключатель mysql/sqlite
Так же не нужно переменных для имен таблиц.

Начнем.
*/

new Handle:g_hDatabase; // создаем хандл для базы данных
new g_iClientID[MAXPLAYERS+1]; // Массив для хранения айди игроков
new g_iClientKills[MAXPLAYERS+1]; // Массив для хранения количества убийств игроков
new g_iClientDeaths[MAXPLAYERS+1]; // Массив для хранения количества смертей игроков

public OnPluginStart()
{
	RegConsoleCmd("sm_top", Top_CMD);
	RegConsoleCmd("sm_mystats", MyStats_CMD);
	HookEvent("player_death", Event_OnPlayerDeath);
}

public OnMapStart()
{	
	if(g_hDatabase != INVALID_HANDLE) CloseHandle(g_hDatabase); // Если подключение активно - закрываем его
	
	decl String:szError[255];
	g_hDatabase = SQLite_UseDatabase("stats", szError, sizeof(szError)); // подключаемся к базе данных с названием stats
	
	if(g_hDatabase == INVALID_HANDLE) // Если не удалось подключиться вырубаем плагин
	{
		SetFailState("[Stats] Unable to connect to database (%s)", szError);
		return;
	}
	
	SQL_LockDatabase(g_hDatabase); // Блокируем базу от других запросов
	// создаем таблицу 
	SQL_FastQuery(g_hDatabase, "CREATE TABLE IF NOT EXISTS `stats_table` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `auth` VARCHAR(32) PRIMARY KEY, `name` TEXT, `kills` INTEGER NOT NULL default '0', `deaths` INTEGER NOT NULL default '0');");

	/*
	CREATE TABLE IF NOT EXISTS `stats_table` // Если таблица отсутствует - она будет создана
	(`id` INTEGER PRIMARY KEY AUTOINCREMENT, // Поле `id` айди игрока
	`auth` VARCHAR(32) PRIMARY KEY, // Здесь будет храниться стим игрока
	`name` TEXT, // Здесь будет храниться ник игрока
	`kills` INTEGER NOT NULL default '0',  // Здесь будут храниться убийства игрока
	`deaths` INTEGER NOT NULL default '0');");  // Здесь будут храниться смерти игрока
	
	PRIMARY KEY - значит что в этом поле не может быть совпадений
	AUTOINCREMENT - это добавляет автонумерацию
	NOT NULL - поле не может быть пустым
	default '0' - если значение не записано оно станет равным 0
	*/
	SQL_UnlockDatabase(g_hDatabase); // Разблокируем базу.
}

public OnClientPostAdminCheck(iClient) // ловим подключение игрока
{
	if(iClient)
	{
		g_iClientID[iClient] =
		g_iClientKills[iClient] =
		g_iClientDeaths[iClient] = 0; // Обнуляем всё на даного игрока

		if(!IsFakeClient(iClient))
		{
			decl String:sAuth[32], String:sQuery[256];
			GetClientAuthId(iClient, AuthId_Steam2, sAuth, sizeof(sAuth)); // получаем стим игрока
			FormatEx(sQuery, sizeof(sQuery), "SELECT `id`, `kills`, `deaths` FROM `stats_table` WHERE `auth` = '%s';", sAuth);
			// Получаем значения полей `id`, `kills`, `deaths` из таблицы `stats_table` по даному стиму
			SQL_TQuery(g_hDatabase, SQL_LoadClientCallback, sQuery, GetClientUserId(iClient));
			// Выполняем запрос.
			// В данных передаем не индекс игрока, а его user id (GetClientUserId(iClient))
		}
	}
}

public SQL_LoadClientCallback(Handle:owner, Handle:hndl, const String:error[], any:UserID) // Обрабатывем результат запроса
{
	if(hndl == INVALID_HANDLE)
	{
		LogError("[Stats] SQL_LoadClientCallback: %s", error);
	}
	else
	{
		new iClient = GetClientOfUserId(UserID);
		if(iClient) // Если игрок еще в игре
		{
			if(SQL_FetchRow(hndl)) // получаем строку из результата запроса.
			{
				g_iClientID[iClient] = SQL_FetchInt(hndl, 0); // получаем значение первого поля (id игрока) и записываем в переменную.
				g_iClientKills[iClient] = SQL_FetchInt(hndl, 1);
				g_iClientDeaths[iClient] = SQL_FetchInt(hndl, 2);
				/*
				SQL_FetchInt(hndl, 0) - получает целое число из значение поля.
				Второй аргумент - номер поля из запроса.
				В запросе у нас было SELECT `id`, `kills`, `deaths`. Нумеруем начиная с 0.
				id - 0, kills, deaths - 2
				*/
			}
			else
			{
				CreateClient(iClient); // Если получить не удалось значит игрока нет базе. Создаем его.
			}
		}
	}
}

CreateClient(iClient)
{
	decl String:sName[MAX_NAME_LENGTH*2+1], String:sAuth[32], String:sQuery[256];
	GetClientName(iClient, sAuth, sizeof(sAuth));
	SQL_EscapeString(g_hDatabase, sAuth, sName, sizeof(sName)); // Экранируем строку имени игрока. Это нужно на случай если в нике игрока есть символ '   им выделяются значения.
	/*
	Например мы в зпросе передаем:
	Записать в поле `name` = '%s', где %s - это строка "Я Вася"
	В запрос уйдет `name` = 'Я Вася'
	А если строка "Я ра'ковский"
	В запрос уйдет `name` = 'Я ра' ковский
	Тоесть не всё влезет в '. Потому кастомные строки лучше экарнировать.
	*/
	GetClientAuthId(iClient, AuthId_Steam2, sAuth, sizeof(sAuth));
	FormatEx(sQuery, sizeof(sQuery), "INSERT INTO `stats_table` (`name`, `auth`) VALUES ('%s', '%s');", sName, sAuth);
	SQL_TQuery(g_hDatabase, SQL_CreateClientCallback, sQuery, GetClientUserId(iClient));
}

public SQL_CreateClientCallback(Handle:owner, Handle:hndl, const String:error[], any:UserID) // Обрабатывем результат запроса
{
	if(hndl == INVALID_HANDLE)
	{
		LogError("[Stats] SQL_CreateClientCallback: %s", error);
	}
	else
	{
		new iClient = GetClientOfUserId(UserID);
		if(iClient)
		{
			// Если игрок добавился в базу и он еще в игре - получаем его ид.
			g_iClientID[iClient] = SQL_GetInsertId(g_hDatabase);
		}
	}
}

public OnClientDisconnect(iClient) // Ловим выход игрока
{
	if(iClient && !IsFakeClient(iClient)) SaveClient(iClient); // Сохраняем его статистику
}

SaveClient(iClient)
{
	decl String:sName[32], String:sNameDB[150], String:sQuery[256];
	GetClientName(iClient, sName, sizeof(sName));
	SQL_EscapeString(g_hDatabase, sName, sNameDB, sizeof(sNameDB)); // Экранируем опасные символы в нике
	FormatEx(sQuery, sizeof(sQuery), "UPDATE `stats_table` SET `name` = '%s', `kills` = '%i', `deaths` = '%i' WHERE `id` = '%i';", sNameDB, g_iClientKills[iClient], g_iClientDeaths[iClient], g_iClientID[iClient]);
	// Обновляем в базе ник игрока, убиства и смерти
	SQL_TQuery(g_hDatabase, SQL_SaveClientCallback, sQuery);
}

public SQL_SaveClientCallback(Handle:owner, Handle:hndl, const String:error[], any:UserID)
{
	if(hndl == INVALID_HANDLE)
	{
		LogError("[Stats] SQL_MyStatsCallback: %s", error);
	}
}

public Event_OnPlayerDeath(Handle:hEvent, const String:name[], bool:dontBroadcast)
{
	new iClient = GetClientOfUserId(GetEventInt(hEvent, "userid")), // Получаем умершего
		iAttacker = GetClientOfUserId(GetEventInt(hEvent, "attacker")); // Получаем убийцу

	++g_iClientKills[iAttacker]; // Добавляем убийство атакующему
	++g_iClientDeaths[iClient]; // Добавляем смерть жертве
}

public Action:MyStats_CMD(iClient, args) // Игрок смотрит свою статистику
{
	// Тут у нас есть 2 способа.
	// 1 - Вывести игроку то что у нас уже сохранено
	// 2 - Вывести игроку подробную информацию
	//
	// Я покажу оба
	
	// 1
	PrintToChat(iClient, "У тебя %i Убийств и %i Смертей. Соотношение %2.0f", g_iClientKills[iClient], g_iClientDeaths[iClient], float(g_iClientKills[iClient]/g_iClientDeaths[iClient]));
	
	// 2
	SaveClient(iClient); // Обновляем игрока в базе данных
	decl String:sQuery[256];
	Format(sQuery, sizeof(sQuery), "SELECT * FROM `stats_table` WHERE `id` = '%i';", g_iClientID[iClient]);
	SQL_TQuery(g_hDatabase, SQL_MyStatsCallback, sQuery, GetClientUserId(iClient));

	return Plugin_Handled;
}

public SQL_MyStatsCallback(Handle:owner, Handle:hndl, const String:error[], any:UserID) // Обрабатывем результат запроса
{
	if(hndl == INVALID_HANDLE)
	{
		LogError("[Stats] SQL_MyStatsCallback: %s", error);
	}
	else
	{
		new iClient = GetClientOfUserId(UserID);
		if(iClient) PrintToChat(iClient, "...."); // Выводим полученные поля.
	}
}

public Action:Top_CMD(iClient, args) // Игрок хочет посмоотреть топ
{
	SaveClient(iClient); // Обновляем игрока в базе данных
	decl String:sQuery[256];
	FormatEx(sQuery, sizeof(sQuery), "SELECT `name`, `kills`, `deaths` FROM `stats_table` ORDER BY (`kills`/`deaths`) DESC LIMIT %i OFFSET %i;", 10, 0);
	SQL_TQuery(g_hDatabase, SQL_TopCallback, sQuery, GetClientUserId(iClient));

	return Plugin_Handled;
}

public SQL_TopCallback(Handle:owner, Handle:hndl, const String:error[], any:UserID)
{
	if(hndl == INVALID_HANDLE) LogError("[Stats] Query Fail load top: %s", error);
	else
	{
		new iClient = GetClientOfUserId(UserID);
		if(iClient)
		{
			decl String:sDisplay[128], String:sName[MAX_NAME_LENGTH], iCount, Handle:hPanel;

			iCount = SQL_GetRowCount(hndl);
			hPanel = CreatePanel();

			SetPanelTitle(hPanel, "ТОП 10");
			
			DrawPanelItem(hPanel, " ", ITEMDRAW_SPACER|ITEMDRAW_RAWLINE);
			DrawPanelText(hPanel, "-----------------------------");
			DrawPanelItem(hPanel, " ", ITEMDRAW_SPACER|ITEMDRAW_RAWLINE);

			for (new i = 1; i <= iCount; i++)
			{
				if(SQL_FetchRow(hndl))
				{
					SQL_FetchString(hndl, 0, sName, sizeof(sName)-1);
					FormatEx(sDisplay, sizeof(sDisplay), "%i. %s [%d]", i, sName, SQL_FetchInt(hndl, 1));
					DrawPanelText(hPanel, sDisplay);
				}
			}

			DrawPanelItem(hPanel, " ", ITEMDRAW_SPACER|ITEMDRAW_RAWLINE);
			DrawPanelText(hPanel, "-----------------------------");
			DrawPanelItem(hPanel, " ", ITEMDRAW_SPACER|ITEMDRAW_RAWLINE);

			SetPanelCurrentKey(hPanel, 10);
			DrawPanelItem(hPanel, "Выход");

			SendPanelToClient(hPanel, iClient, PanelHandler, 30);
			CloseHandle(hPanel);
		}
	}
}

public PanelHandler(Handle:menu, MenuAction:action, param1, param2) {}
 

Вложения

  • stats.sp
    11.3 КБ · Просмотры: 65

SourceGod

Участник
Сообщения
47
Реакции
0
R1KO, а что хранит в себе данные в плагинe warmod.? Допустим игрок заходит на сервер и пишет что не хватает столько из стольких игроков?
 

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #3
SourceGod, не совсем понял вопроса
 

SourceGod

Участник
Сообщения
47
Реакции
0
R1KO, есть плагин warmod он для проведения матчей.. Плагин везде сырой. Хочу новый написать на новом синтаксисе. Только не могу понять что именно он должен хронить в себе и как!?
 

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #5
SourceGod, ну по идее там всё хранится в переменных. Конечно если плагин не имеет статистики. Если имеет то рациональнее всего sql/mysql. Ну и всё зависит от того что нужно хранить. Если хочешь писать свой - начинай с основы, так сказать со "скелета", это система готовности, загрузка конфиги и команды управления матчем. А потом уже наращивай функционал.
 

SourceGod

Участник
Сообщения
47
Реакции
0
R1KO, допустим собралось 10 людей. И матч начинается счет 0:0 как сделать проверку на то что какая то из команд выиграла
 

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #7
SourceGod, в эвенте конца раунда есть параметр winner, можно по нему считать. Можно получать счет команда через GetTeamScore и сравнивать
 

SourceGod

Участник
Сообщения
47
Реакции
0
R1KO, спасибо.. Учту

Добавлено через 2 минуты
R1KO, кстати куда пропала кнопка спасибо?
 
Последнее редактирование:
Сверху Снизу