PLAYER MONITOR - TIME UTC~?? MSK~?? [1mp4l3r]

Month and Week table setting
Day table setting
Минимальное количество игр за месяц
Минимальное количество игр за неделю
Минимальное количество фрагов за месяц
Минимальное количество фрагов за неделю
Q3S - quake 3 stats - формат в котором хранится статистика игр в сжатом виде.

Для начала объявим такие типы данных:

//=============================================================================

typedef unsigned short u16; //2 байта
typedef          short i16;
typedef unsigned int   u32; //4 байта
typedef          int   i32;

В файле находится заголовок и данные. Все числа хранятся в little-endian.

//=============================================================================

struct stats_s
{
    i32 version; //версия файла[текущая версия - 0]
    u16 m_size;  //размер структуры с описанием матча
    u16 p_size;  //размер структуры с описанием игрока
};

//=============================================================================

struct match_s
{
    char map[MAX_QPATH];    //название карты
    char r_team[MAX_QPATH]; //имя красной команды
    char b_team[MAX_QPATH]; //имя синей команды
    i32 r_scores;           //количество очков красной команды
    i32 b_scores;           //количество очков синей команды
    qtime_t map_init;       //время начала игры[структура qtime_t для хранения
    //даты]{внутрь виртуальной машины движок передаёт локальное время компьютера}
    qtime_t map_free;       //время конца игры
    gametype_t type;        //тип игры
    i32 frag_limit;         //количество фрагов обозначающее конец игры
    i32 time_limit;         //количество минут обозначающее конец игры
    i32 flag_limit;         //количество флагов обозначающее конец игры
};

//=============================================================================

struct player_s
{
    char clean[MAX_QPATH];   //чистое имя[имя без цвета и других кодов]
    char color[MAX_QPATH];   //цветное имя[имя с кодами]
    u16 ga;                  //количество подобраных зелёных бронижелетов
    u16 ya;                  //количество подобраных жёлтых бронижелетов
    u16 ra;                  //количество подобраных красных бронижелетов
    u16 mh;                  //количество подобраных больших аптечек
    u16 at;                  //количество единиц брони подобранных
    u16 ht;                  //количество единиц аптечек подобранных
    u32 dg;                  //количество единиц урона нанесённое по противникам
    u32 dr;                  //количество единиц урона полученного игроком
    u16 k;                   //количество фрагов
    u16 t;                   //количество разморозок сделанное игроком
    u16 d;                   //количество смертей
    u16 u;                   //количество разморозок игрока
    u16 s;                   //количество самофрагов
    u16 weapon[10][4];       //массив данных о стрельбе игрока
    u16 ktable[MAX_CLIENTS]; //массив данных о фрагах
    u16 ttable[MAX_CLIENTS]; //массив данных о разморозках

    i16    race;             //тип игрока
    team_t team;             //команду игрока

    u32 time;                //время игры[миллисекунды]
};

// Константы ==================================================================

MAX_QPATH = 64
MAX_CLIENTS  = 64

// qtime_t структура ==========================================================

typedef struct qtime_s
{
    i32 tm_sec;     // seconds after the minute - [0,59]
    i32 tm_min;     // minutes after the hour - [0,59]
    i32 tm_hour;    // hours since midnight - [0,23]
    i32 tm_mday;    // day of the month - [1,31]
    i32 tm_mon;     // months since January - [0,11]
    i32 tm_year;    // years since 1900
    i32 tm_wday;    // days since Sunday - [0,6]
    i32 tm_yday;    // days since January 1 - [0,365]
    i32 tm_isdst;   // daylight savings time flag
} qtime_t;

// team_t от i32 - тип команды для версии 0 ===================================

TEAM_FREE = 0
TEAM_RED = 1
TEAM_BLUE = 2
TEAM_SPECTATOR = 3

// gametype_t от i32 - тип игры для версии 0 ==================================

GT_FFA = 0           // free for all
GT_TOURNAMENT = 1    // one on one tournament
GT_SINGLE_PLAYER = 2 // single player ffa
GT_TEAM = 3          // team deathmatch
GT_CTF = 4           // capture the flag
GT_CA = 5            // clan arena

//=============================================================================

Дополнительно:

Значения race, то есть тип игрока: 0 - человек, 1-5 - уровень сложности бота.

Массив данных о стрельбе: 10 оружий для каждого 4 параметра.
[
    [0 - попадания 1 - выстрелов 2 - фрагов 3 - смертей], //weapon, index = 0
    ...
    [], //weapon, index = 9
]

Массив данных о фрагах и разморозках:
Если игрок и индексом 3 фрагнул игрока с индексом 5, то в структуру описания
игрока 3 к числу с индексом 5 в массиве ktable прибавляется 1.
Аналогично для разморозок.

Камни и проблемы:

В текущей версии 0 размер структур такой что делится на 4 и на 8, поэтому
никаких проблем с компиляторами не возникает.

m_size 288
p_size 504

Но так как struct aligment в lcc равен 4 байтам, будет лучше если для gcc будет
добавлено __attribute__((packed, aligned(4))).

Проблемой стала структура qtime_t, с 1999 года прошло много времени и в gcc в
struct tm добавили 2 дополнительных поля в конце. Нельзя lcc версию структуры
скопировать в gcc версию структуры, так как у них разный размер.

struct match_s //LCC version size 288              struct match_s //GCC version size > 288
{                                                  {
    char map[MAX_QPATH];                               char map[MAX_QPATH];
    char r_team[MAX_QPATH];                            char r_team[MAX_QPATH];
    char b_team[MAX_QPATH];                            char b_team[MAX_QPATH];
    i32 r_scores;                                      i32 r_scores;
    i32 b_scores;                                      i32 b_scores;
    qtime_t map_init;             ----X---->           struct tm map_init;
    qtime_t map_free;             ----X---->           struct tm map_free;
    gametype_t type;                                   gametype_t type;
    i32 frag_limit;                                    i32 frag_limit;
    i32 time_limit;                                    i32 time_limit;
    i32 flag_limit;                                    i32 flag_limit;
};                                                 };

/* ISO C `broken-down time' structure.  */
struct tm
{
    int tm_sec;			/* Seconds.	[0-60] (1 leap second) */
    int tm_min;			/* Minutes.	[0-59] */
    int tm_hour;			/* Hours.	[0-23] */
    int tm_mday;			/* Day.		[1-31] */
    int tm_mon;			/* Month.	[0-11] */
    int tm_year;			/* Year	- 1900.  */
    int tm_wday;			/* Day of week.	[0-6] */
    int tm_yday;			/* Days in year.[0-365]	*/
    int tm_isdst;			/* DST.		[-1/0/1]*/

# ifdef	__USE_MISC
    long int tm_gmtoff;		/* Seconds east of UTC.  */
    const char *tm_zone;		/* Timezone abbreviation.  */
# else
    long int __tm_gmtoff;		/* Seconds east of UTC.  */
    const char *__tm_zone;	/* Timezone abbreviation.  */
# endif
};

// Сжатие =====================================================================

Давайте посчитаем размер не сжатого файла q3s:

struct stats_s   struct match_s   struct player_s
      8        +      288       +   MAX_CLIENTS * 504 = 32552 ~ 32Kb

Это очень много так как большая часть структур описывающих игрока пустая, это
зависит от сервера, но лично я больше 32 игроков не видел. То есть как минимум
половина из этого файла - нули.

Так же стоит заметить что игроков с именем длинной большей 14-16 символов очень
мало, то есть пустых символов от 48-50, но у каждого игрока две версии имени,
значит пустых символов от 96-100.

Такая же ситуация с массивом weapon, ktable и ttable.

Проанализировав файл можно сделать вывод что в нём очень много
последовательностей байтов с одинаковыми значениями. Значит здесь лучше всего
подойдёт алгоритм сжатия RLE. Но у RLE есть сильный недостаток: он увеличивает
в 2 раза последовательность из не повторяющихся символов. Для примера возьмём
строку "X11-freekill".
После сжатия с помощью RLE получится:

[1 X][2 1][1 -][1 f][1 r][2 e][1 k][1 i][2 l]

Было 12 символов, стало 18. Если бы не было "11", "ee", "ll" было бы 24, то
есть в 2 раза больше.

Этот недостаток можно исправить, для этого надо диапазон байта который обозначает
количество повторений(x) разделить на 2:
x < 0 значит дальше идёт x байт которые не удалось сжать
x > 0 значит дальше идёт x байт которые удалось сжать
Выглядит это так:

(-1 X)[2 1](-3 -fr)[2 e](-2 ki)[2 l]

Было 12 символов, стало 15. Теперь в лучшем случае не повторяющаяся последовательность
длинной l байт на выходе даст l+1 байт. А в худшем случае не повторяющаяся последовательность
длинной l байт на выходе даст l*1,3125 байт.

// Итог =======================================================================

|--------------------|
| struct stats_s     |
|--------------------|
| struct match_s     | ----(RLE с отрицательными числами)----> Q3S file format
|--------------------|
| 64*struct player_s |
|--------------------|

//=============================================================================

Примеры ktable & ttable:

ke = players[i].ktable[j] // сколько раз игрок с индексом i фрагнул игрока с индексом j
de = players[j].ktable[i] // сколько раз игрок с индексом i погиб от игрока с индексом j

Длина цветного имени:

int color_name_length(const char *str)
{
    int i = MAX_QPATH - 1;
    while(str[i] == 0 && i >= 0) i--;
    return i + 1;
}

Цветное имя могут разделять нули и другие не печатные символы, поэтому мы идём с конца пока
символ равен нулю. Выжно чтобы имя находилось в инициализированной памяти.

//=============================================================================

я в телеграм

Архивы за 2025 год:

2025.01
2025.02
2025.03
2025.04
2025.05
2025.06
2025.07
2025.08
2025.09
2025.10

Q3VIEW для просмотра файла q3s

q3s_view