[SQLite] Алгоритм вычисление rank'a игрока

Сообщения
1,032
Реакции
828
Помог
10 раз(а)
Подскажите, как правильнее всего получить /rank игрока в статистики игроков.
Как я понимаю, для начало необходимо выполнить запрос с полями (AuthID, Skill), с сортировкой по Skill
Допустим, получим мы 5к строк. Неужели далее придется выполнить цикл по всем строкам, что бы выяснить позицию игрока в /rank'e ?

C++:
#include <amxmodx>
#include <amxmisc>
#include <reapi>
#include <sqlx>

#pragma loadlib sqlite

#pragma semicolon 1

const INACTIVITY_DAYS                = 30;                // Удалять неактивных игроков через 'n' дней
const SECONDS_IN_DAY                = 86400;                // Количество секунд в одном дне
const Float:DATA_UPDATE_TIMER        = 60.0;                // Обновление данных игроков каждые 'n' секунд

const QUERY_STRLEN                    = 512;
const PATH_STRLEN                    = 64;
const MESSAGE_STRLEN                = 512;
const TIME_STRLEN                    = 32;
const ELLIPSES_ARG                    = 4;
const AUTHID_STRLEN                    = 24;
const PLAYER_NAME_STRLEN            = 32;

enum _:PLAYER_DATA {
    em_sAuthID[AUTHID_STRLEN],
    em_sName[PLAYER_NAME_STRLEN],
    em_iKills,
    em_iDeaths,
    em_iHead,
    Float:em_fSkill,
    LastSeen
}

new Handle:g_SQLTuple, g_SQLQuery[QUERY_STRLEN];

new g_sLogsDir[PATH_STRLEN];
new g_sDataDir[PATH_STRLEN];
new g_sDataBaseFile[PATH_STRLEN + 32];

new g_ePlayersData[MAX_PLAYERS + 1][PLAYER_DATA];
new g_aLastDataUpdate[MAX_PLAYERS + 1];

public plugin_init() {
    register_plugin("Stats", "2.0", "Javekson");
    RegisterHookChain(RG_CBasePlayer_Killed, "CBasePlayer_Killed", .post = true);
    RegisterHookChain(RG_CBasePlayer_SetClientUserInfoName, "CBasePlayer_SetClientUserInfoName", .post = true);
    
    register_clcmd("say /rank", "ClCmdRank");
    register_clcmd("say /top", "ClCmdTop");
    
    set_task_ex(DATA_UPDATE_TIMER, "TaskDataUpdate", .flags = SetTask_Repeat);
}

public plugin_cfg() {
    get_localinfo("amxx_logs", g_sLogsDir, charsmax(g_sLogsDir));
    add(g_sLogsDir, charsmax(g_sLogsDir), "/stats");
    if(!dir_exists(g_sLogsDir)) mkdir(g_sLogsDir);
    
    get_localinfo("amxx_datadir", g_sDataDir, charsmax(g_sDataDir));
    formatex(g_sDataBaseFile, charsmax(g_sDataBaseFile), "%s/stats.db", g_sDataDir);
    
    if(!file_exists(g_sDataBaseFile)) {
        new iFileID = fopen(g_sDataBaseFile, "a");
        fclose(iFileID);
    }
    
    SQL_SetAffinity("sqlite");
    g_SQLTuple = SQL_MakeDbTuple("", "", "", g_sDataBaseFile);
    
    formatex(g_SQLQuery, charsmax(g_SQLQuery),
        "CREATE TABLE IF NOT EXISTS stats( \
        AuthID            TEXT        PRIMARY KEY, \
        Name            TEXT        NOT NULL, \
        Kills            INTEGER        DEFAULT 0, \
        Deaths            INTEGER        DEFAULT 0, \
        Head            INTEGER        DEFAULT 0, \
        Skill            FLOAT        DEFAULT '100.0', \
        LastSeen        INTEGER        DEFAULT 0)"
    );
    SQL_ThreadQuery(g_SQLTuple, "TableCreateQueryHandler", g_SQLQuery);
    
    formatex(g_SQLQuery, charsmax(g_SQLQuery), "DELETE FROM stats WHERE LastSeen < %d", get_systime() - INACTIVITY_DAYS * SECONDS_IN_DAY);
    SQL_ThreadQuery(g_SQLTuple, "DataDeleteQueryHandler", g_SQLQuery);
}

public TableCreateQueryHandler(iFailState, Handle:hQuery, sError[], iErrNum, aData[], iSize, Float:fQueueTime) {
    if(iFailState != TQUERY_SUCCESS) {
        SQL_GetQueryString(hQuery, g_SQLQuery, charsmax(g_SQLQuery));
        QueryHandlerError(g_SQLQuery, sError, iErrNum, "TableCreateQueryHandler");
        pause("a");
    }
}

public DataDeleteQueryHandler(iFailState, Handle:hQuery, sError[], iErrNum, aData[], iSize, Float:fQueueTime) {
    if(iFailState != TQUERY_SUCCESS) {
        SQL_GetQueryString(hQuery, g_SQLQuery, charsmax(g_SQLQuery));
        QueryHandlerError(g_SQLQuery, sError, iErrNum, "DataDeleteQueryHandler");
    }
}

public client_putinserver(id) {
    if(is_user_bot(id) || is_user_hltv(id)) {
        return PLUGIN_CONTINUE;
    }
    
    new sAuthID[AUTHID_STRLEN], sPlayerName[PLAYER_NAME_STRLEN];
    get_user_authid(id, sAuthID, charsmax(sAuthID));
    get_user_name(id, sPlayerName, charsmax(sPlayerName));
    
    g_ePlayersData[id][em_sAuthID] = sAuthID;
    g_ePlayersData[id][em_sName] = sPlayerName;
    g_ePlayersData[id][em_iKills] = 0;
    g_ePlayersData[id][em_iDeaths] = 0;
    g_ePlayersData[id][em_iHead] = 0;
    g_ePlayersData[id][em_fSkill] = 100.0;
    g_ePlayersData[id][LastSeen] = 0;
    
    g_aLastDataUpdate[id] = get_systime();
    
    new aData[1]; aData[0] = id;
    formatex(g_SQLQuery, charsmax(g_SQLQuery), "SELECT * FROM stats WHERE AuthID = '%s'", g_ePlayersData[id][em_sAuthID]);
    SQL_ThreadQuery(g_SQLTuple, "PlayerCheckQueryHandler", g_SQLQuery, aData, sizeof(aData));
    
    return PLUGIN_CONTINUE;
}

public PlayerCheckQueryHandler(iFailState, Handle:hQuery, sError[], iErrNum, aData[], iSize, Float:fQueueTime) {
    if(iFailState != TQUERY_SUCCESS) {
        SQL_GetQueryString(hQuery, g_SQLQuery, charsmax(g_SQLQuery));
        QueryHandlerError(g_SQLQuery, sError, iErrNum, "PlayerCheckQueryHandler");
        return PLUGIN_CONTINUE;
    }
    
    new id = aData[0];
    
    if(SQL_MoreResults(hQuery)) {
        g_ePlayersData[id][em_iKills] = SQL_ReadResult(hQuery, 2);
        g_ePlayersData[id][em_iDeaths] = SQL_ReadResult(hQuery, 3);
        g_ePlayersData[id][em_iHead] = SQL_ReadResult(hQuery, 4);
        SQL_ReadResult(hQuery, 5, g_ePlayersData[id][em_fSkill]);
        
        return PLUGIN_CONTINUE;
    }
    
    new sQuoteStringPlayerName[PLAYER_NAME_STRLEN * 2];
    SQL_QuoteString(Empty_Handle, sQuoteStringPlayerName, charsmax(sQuoteStringPlayerName), g_ePlayersData[id][em_sName]);
    
    formatex(g_SQLQuery, charsmax(g_SQLQuery), "INSERT INTO stats(AuthID, Name) VALUES ('%s', '%s')",
        g_ePlayersData[id][em_sAuthID], sQuoteStringPlayerName
    );
    
    SQL_ThreadQuery(g_SQLTuple, "AddDataQueryHandler", g_SQLQuery);
    
    return PLUGIN_CONTINUE;
}

public AddDataQueryHandler(iFailState, Handle:hQuery, sError[], iErrNum, aData[], iSize, Float:fQueueTime) {
    if(iFailState != TQUERY_SUCCESS) {
        SQL_GetQueryString(hQuery, g_SQLQuery, charsmax(g_SQLQuery));
        QueryHandlerError(g_SQLQuery, sError, iErrNum, "AddDataQueryHandler");
        return PLUGIN_CONTINUE;
    }
    return PLUGIN_CONTINUE;
}

public client_disconnected(id) {
    if(is_user_connected(id)) {
        g_ePlayersData[id][LastSeen] = get_systime();
        DataUpdate(id);
    }
}

public CBasePlayer_Killed(const iVictim, iKiller, iGib) {
    if(iVictim == iKiller) return PLUGIN_CONTINUE;
    
    if(!is_user_connected(iKiller)) return PLUGIN_CONTINUE;
    
    g_ePlayersData[iVictim][em_iDeaths]++;
    g_ePlayersData[iKiller][em_iKills]++;
        
    if(get_member(iVictim, m_bHeadshotKilled)) {
            g_ePlayersData[iKiller][em_iHead]++;
    }
    
    new Float:fDelta = 1.0 / (1.0 + floatpower(10.0,(g_ePlayersData[iKiller][em_fSkill] - g_ePlayersData[iVictim][em_fSkill]) / 100.0));
    new Float:fKillerKoeff = (g_ePlayersData[iKiller][em_iKills] < 100) ? 2.0 : 1.5;
    new Float:fVictimkKoeff = (g_ePlayersData[iVictim][em_iKills] < 100) ? 2.0 : 1.5;
    
    g_ePlayersData[iKiller][em_fSkill] += (fKillerKoeff * fDelta);
    g_ePlayersData[iVictim][em_fSkill] -= (fVictimkKoeff * fDelta);
    
    return PLUGIN_CONTINUE;
}

public CBasePlayer_SetClientUserInfoName(const id, sInfoBuffer[], sNewName[]) {
    formatex(g_ePlayersData[id][em_sName], sizeof(g_ePlayersData[][em_sName]), "%s", sNewName);
}

public TaskDataUpdate() {
    new aPlayersID[MAX_PLAYERS], iPlayersNum, iSysTime = get_systime();
    get_players_ex(aPlayersID, iPlayersNum, GetPlayers_ExcludeBots | GetPlayers_ExcludeHLTV);
    
    for(new i; i < iPlayersNum; i++) {
        if(iSysTime - g_aLastDataUpdate[aPlayersID[i]] >= DATA_UPDATE_TIMER) {
            DataUpdate(aPlayersID[i]);
            g_aLastDataUpdate[aPlayersID[i]] = iSysTime;
        }
    }
}

public ClCmdRank(const id) {
    return PLUGIN_CONTINUE;
}

public ClCmdTop(const id) {
    return PLUGIN_CONTINUE;
}

DataUpdate(id) {
    new sQuoteStringPlayerName[PLAYER_NAME_STRLEN * 2];
    SQL_QuoteString(Empty_Handle, sQuoteStringPlayerName, charsmax(sQuoteStringPlayerName), g_ePlayersData[id][em_sName]);
    
    formatex(g_SQLQuery, charsmax(g_SQLQuery),
        "UPDATE stats SET \
        Name = '%s', \
        Kills = %d, \
        Deaths = %d, \
        Head = %d, \
        Skill = %f, \
        LastSeen = %d \
        WHERE AuthID = '%s'",
        sQuoteStringPlayerName,
        g_ePlayersData[id][em_iKills],
        g_ePlayersData[id][em_iDeaths],
        g_ePlayersData[id][em_iHead],
        g_ePlayersData[id][em_fSkill],
        g_ePlayersData[id][LastSeen],
        g_ePlayersData[id][em_sAuthID]
    );
    
    SQL_ThreadQuery(g_SQLTuple, "DataUpdateQueryHandler", g_SQLQuery);
}

public DataUpdateQueryHandler(iFailState, Handle:hQuery, sError[], iErrNum, aData[], iSize, Float:fQueueTime) {
    if(iFailState != TQUERY_SUCCESS) {
        SQL_GetQueryString(hQuery, g_SQLQuery, charsmax(g_SQLQuery));
        QueryHandlerError(g_SQLQuery, sError, iErrNum, "DataUpdateQueryHandler");
        return PLUGIN_CONTINUE;
    }
    return PLUGIN_CONTINUE;
}

public plugin_end() {
    SQL_FreeHandle(g_SQLTuple);
}

stock QueryHandlerError(sQuery[], sError[], iErrNum, sHandlerName[]) {
    Logging(g_sLogsDir, "stats_error_", "^"--------------------[Error %s]--------------------^"", sHandlerName);
    Logging(g_sLogsDir, "stats_error_", "^"[Query]: %s^"", sQuery);
    Logging(g_sLogsDir, "stats_error_", "^"[ErrorCode]: %d^"", iErrNum);
    Logging(g_sLogsDir, "stats_error_", "^"[QueryError]: %s^"", sError);
}

stock Logging(const sLogsDir[], const sFileName[], const sMessage[], any:...) {
    new sFmtMsg[MESSAGE_STRLEN], sTime[TIME_STRLEN], sLogFile[PATH_STRLEN + 32], iFileID;
    vformat(sFmtMsg, charsmax(sFmtMsg), sMessage, ELLIPSES_ARG);
    get_time("%d.%m.%Y.log", sTime, charsmax(sTime));
    formatex(sLogFile, charsmax(sLogFile), "%s/%s%s", sLogsDir, sFileName, sTime);
    iFileID = fopen(sLogFile, "at");
    get_time("%d.%m.%Y - %H:%M:%S", sTime, charsmax(sTime));
    fprintf(iFileID, "^"%s^" %s^n", sTime, sFmtMsg);
    fclose(iFileID);
}
 
Сообщения
207
Реакции
420
Помог
10 раз(а)
Всё намного проще, чем кажется.
На примере HLX:CE.
Пометки по запросу: HLStatsX:CE хранит перечень уникальных идентификаторов в отдельной таблице (hlstats_PlayerUniqueIds), т.к. таблица с игроками хранит по сути пару уникальных ключей "игра-уникальный идентификатор".
SQL:
SELECT
  COUNT(*) rank
FROM
  `hlstats_Players`
WHERE
  `skill` >= (
    SELECT
      `skill`
    FROM
      `hlstats_Players`
    WHERE
      `playerId` = (
        SELECT
          `playerId`
        FROM
         `hlstats_PlayerUniqueIds`
        WHERE
          `uniqueId` = '0:55071931'
      )
  ) AND `hideranking` = 0;
1552208595885.png

Возможно, запрос местами составлен ужасно, но он работает.
 
Сообщения
1,032
Реакции
828
Помог
10 раз(а)
CrazyHackGUT, решил задачу иначе, правда убил более 3 часов, пока додумался блин )))

Что бы решить задачу(совет начинающим и мне в том числе) необходимо видеть все визуально, в голове все предусмотреть невозможно, визуализация помогает решить задачи быстрее, собственно так и решил, когда посмотрел на результат визуально.

Имеется статистика игроков, необходимо получить ранг, узнать позицию игрока среди тысячи других игроков, по скиллу.

Первым дело, что я сделал, это вывел всех игроков и отсортировал Skill по убыванию.

formatex(g_SQLQuery, charsmax(g_SQLQuery), "SELECT * FROM stats ORDER BY Skill DESC");

Получил список и визуально нашел себя на 108 месте, это и был моим рангом.
2019-03-10_155142.png

Дальше все очень просто, осталось решить с помощью кода, как выдернуть число(ранг) 108.
Считаем количество игроков выше тебя по скилу и получаем число 107, к нему прибавляет + 1 и получаем цифру 108 это и будет твоим рангом.

Учитывая, что мы знаем свой скилл, то запрос выглядит так:
Ранг = Количество игроков чей скилл больше моего + 1

formatex(g_SQLQuery, charsmax(g_SQLQuery), "SELECT COUNT(Skill) FROM stats WHERE Skill > '%f' ORDER BY Skill DESC", g_ePlayersData[id][em_fSkill]);
 
Сообщения
957
Реакции
1,185
Помог
52 раз(а)
Сообщения
1,032
Реакции
828
Помог
10 раз(а)
Sonyx, я тоже так думал, но скилл может быть одинаковым и поделиться на двоих-троих человек к примеру, потому более точный подсчет будет именно + 1
10 Мар 2019
Sonyx, хотя я думаю такого и не случиться, шансов получить такое мало
 
Сообщения
2,491
Реакции
2,794
Помог
61 раз(а)
Javekson, я делал order by skill, exp DESC. Таким образом дополнительно сортировка шла не только по ранку, но и експе (можно K/D)
10 Мар 2019
хотя я думаю такого и не случиться, шансов получить такое мало
много как показала практика. и шанс большой в начале установки плагина, потом убивает. А при увеличении количества игроков снова возрастает.
 

Пользователи, просматривающие эту тему

Сейчас на форуме нет ни одного пользователя.
Сверху Снизу