|
Минимальное количество игр за месяц
Минимальное количество игр за неделю
Минимальное количество фрагов за месяц
Минимальное количество фрагов за неделю
|
|
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