PLAYER MONITOR - TIME UTC~?? MSK~?? [X410-freekill]

Month and Week table setting
Day table setting
Минимальное количество игр за месяц
Минимальное количество игр за неделю
Минимальное количество фрагов за месяц
Минимальное количество фрагов за неделю
	ELO таблица 2025.1 - 2026.3 -> elo_2025.0-2026.2.tsv.
	
	Архивы за 2026:
	        2026.1 -> 2026_0.tar
	        2026.2 -> 2026_1.tar
	        2026.3 -> 2026_2.tar
	
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 |
|--------------------|

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

я в телеграм

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

q3s_view