antizona

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 2f71540fe06a9a246f3cf32709306a4169182c07
parent e0a00cc206a04e6b122e1f6c9d8d2ebfda689b9d
Author: averagecoder <averagecoder@noreply.codeberg.org>
Date:   Tue, 30 Jun 2026 01:40:59 +0300

feat: add /skin support

Diffstat:
Msrc/build.c | 325+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/render.c | 375+++++++++++++++++++++++++++++++++++++++----------------------------------------
Asrc/skins.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 432 insertions(+), 315 deletions(-)

diff --git a/src/build.c b/src/build.c @@ -14,6 +14,8 @@ #include <string.h> #include <unistd.h> +#include "skins.h" + #define MAX_FILES 32768 #define NAME_SZ 24 #define PATH_CACHE_SZ 16384 @@ -61,6 +63,12 @@ struct rw_header { uint32_t version; } __attribute__((packed)); +struct special_actor { + int id; + const char *model; + const char *txd; +}; + struct mat4 { float m[4][4]; }; struct rw_triangle { uint16_t v1, v2, m, v3; }; struct atomic_entry { uint32_t f, g; }; @@ -82,11 +90,6 @@ struct timecyc_entry { float fog_st; }; -struct path_cache_entry { - char rel[512]; - char resolved[1024]; -}; - struct arena { uint8_t *mem; size_t pos; @@ -139,8 +142,6 @@ struct tar_header { static struct ide_entry ide_db[65536]; static int ide_count = 0; -static struct path_entry path_db[PATH_DB_SZ]; -static int path_db_count = 0; static char existing_models[32768][NAME_SZ]; static int existing_models_count = 0; @@ -150,6 +151,39 @@ static int existing_textures_count = 0; static struct water_face water_db[8192]; static int water_face_count = 0; +static const struct special_actor special_actors[] = { + { 1, "truth", "truth" }, + { 2, "maccer", "maccer" }, + { 3, "andre", "andre" }, + { 4, "bbthin", "bbthin" }, + { 5, "bb", "bb" }, + { 6, "emmet", "emmet" }, + { 8, "janitor", "janitor" }, + { 290, "rose", "rose" }, + { 291, "paul", "paul" }, + { 292, "cesar", "cesar" }, + { 293, "ogloc", "ogloc" }, + { 294, "wuzimu", "wuzimu" }, + { 295, "toreno", "toreno" }, + { 296, "jizzy", "jizzy" }, + { 297, "maddogg", "maddogg" }, + { 298, "cat", "cat" }, + { 299, "claude", "claude" }, + { 300, "ryder2", "ryder" }, + { 301, "ryder3", "ryder" }, + { 302, "emmet", "emmet" }, + { 303, "andre", "andre" }, + { 304, "kendl", "kendl" }, + { 305, "jethro", "jethro" }, + { 306, "zero", "zero" }, + { 307, "tbone", "tbone" }, + { 308, "maccer", "maccer" }, + { 309, "forelli", "forelli" }, + { 310, "sweet", "sweet" }, + { 311, "smoke", "smoke" }, + { 312, "ryder", "ryder" } +}; + static void to_lower_str(char *str) { @@ -207,7 +241,7 @@ scan_existing_assets(void) closedir(dir); } } - + static int asset_cached(const char *name, int is_model) { @@ -231,7 +265,8 @@ asset_cached(const char *name, int is_model) } } } else { - for (i = 0; i < existing_textures_count; i++) { + + for (i = 0; i < existing_textures_count; i++) { if (strcmp(existing_textures[i], base) == 0) { return (1); } @@ -268,54 +303,6 @@ arena_alloc(struct arena *a, size_t sz) } #define A_ALLOC(a, type, count) (type *)arena_alloc((a), sizeof(type) * (count)) -static void -normalize_key(const char *src, char *dst, size_t sz) -{ - size_t i = 0; - while (*src && i < sz - 1) { - if (*src != '/' && *src != '\\') { - dst[i++] = tolower((unsigned char)*src); - } - src++; - } - dst[i] = '\0'; -} - -static void -index_directory_tree(const char *dir_path) -{ - DIR *dir; - struct dirent *de; - char sub_path[1024]; - struct stat st; - - if (!(dir = opendir(dir_path))) { - return; - } - - while ((de = readdir(dir))) { - if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) { - continue; - } - - snprintf(sub_path, sizeof(sub_path), "%s/%s", dir_path, de->d_name); - - if (stat(sub_path, &st) == 0) { - if (S_ISDIR(st.st_mode)) { - index_directory_tree(sub_path); - } else { - if (path_db_count < PATH_DB_SZ) { - normalize_key(sub_path, path_db[path_db_count].key, 512); - strncpy(path_db[path_db_count].resolved, sub_path, 1024); - path_db_count++; - } - } - } - } - closedir(dir); -} - - static size_t mread(void *dst, size_t sz, size_t n, struct mstream *s) { @@ -360,31 +347,58 @@ mat_mul(struct mat4 *dst, const struct mat4 *a, const struct mat4 *b) } static int -resolve_ci(const char *root, const char *rel, char *out, size_t out_sz) +resolve_simple(const char *root, const char *rel, char *out, size_t out_sz) { - char target_key[1024]; - char combined[1536]; - int i; + DIR *dir; + struct dirent *de; + char path[1024]; + char tmp[1024]; + char *seg, *saveptr; + int found; - snprintf(combined, sizeof(combined), "%s/%s", root, rel); - normalize_key(combined, target_key, sizeof(target_key)); + if (snprintf(path, sizeof(path), "%s", root) >= (int)sizeof(path)) { + return (0); + } + strncpy(tmp, rel, sizeof(tmp) - 1); + tmp[sizeof(tmp) - 1] = '\0'; - for (i = 0; i < path_db_count; i++) { - if (strcmp(path_db[i].key, target_key) == 0) { - strncpy(out, path_db[i].resolved, out_sz - 1); - out[out_sz - 1] = '\0'; - return (1); + seg = strtok_r(tmp, "/", &saveptr); + while (seg) { + dir = opendir(path); + if (!dir) { + return (0); + } + found = 0; + while ((de = readdir(dir))) { + if (strcasecmp(de->d_name, seg) == 0) { + if (snprintf(path + strlen(path), + sizeof(path) - strlen(path), "/%s", + de->d_name) >= (int)(sizeof(path) - strlen(path))) { + closedir(dir); + return (0); + } + found = 1; + break; + } + } + closedir(dir); + if (!found) { + return (0); } + seg = strtok_r(NULL, "/", &saveptr); } - return (0); + + strncpy(out, path, out_sz - 1); + out[out_sz - 1] = '\0'; + return (1); } static FILE * -fopen_ci(const char *root, const char *rel) +fopen_simple(const char *root, const char *rel) { char resolved[1024]; - if (resolve_ci(root, rel, resolved, sizeof(resolved))) { + if (resolve_simple(root, rel, resolved, sizeof(resolved))) { return (fopen(resolved, "rb")); } return (NULL); @@ -393,18 +407,24 @@ fopen_ci(const char *root, const char *rel) static void trim_spaces(char *str) { - char *start, *end; + char *start = str; + size_t len; + char *end; - start = str; while (*start == ' ' || *start == '\t') { start++; } if (start != str) { memmove(str, start, strlen(start) + 1); } - end = str + strlen(str) - 1; - while (end > str && (*end == ' ' || *end == '\t' || *end == '\r' || - *end == '\n')) { + + len = strlen(str); + if (len == 0) { + return; + } + + end = str + len - 1; + while (end >= str && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) { *end = '\0'; end--; } @@ -447,13 +467,19 @@ clean_ide_line(char *p) { char *comment; char *end; + size_t len; comment = strchr(p, '#'); if (comment) { *comment = '\0'; } - end = p + strlen(p) - 1; + len = strlen(p); + if (len == 0) { + return; + } + + end = p + len - 1; while (end >= p && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) { *end = '\0'; end--; @@ -461,20 +487,19 @@ clean_ide_line(char *p) } static void -preparse_ide(const char *root, const char *rel) +preparse_ide(const char *root, const char *rel, struct file_list *fl) { FILE *f; char line[512]; char *p; - int in_tobj; + int in_tobj = 0; + int in_peds = 0; - f = fopen_ci(root, rel); + f = fopen_simple(root, rel); if (!f) { return; } - in_tobj = 0; - while (fgets(line, sizeof(line), f)) { p = line; line[strcspn(line, "\r\n")] = '\0'; @@ -487,14 +512,27 @@ preparse_ide(const char *root, const char *rel) if (strncasecmp(p, "objs", 4) == 0) { in_tobj = 0; + in_peds = 0; + continue; + } + if (strncasecmp(p, "cars", 4) == 0) { + in_tobj = 0; + in_peds = 0; + continue; + } + if (strncasecmp(p, "peds", 4) == 0) { + in_tobj = 0; + in_peds = 1; continue; } if (strncasecmp(p, "tobj", 4) == 0) { in_tobj = 1; + in_peds = 0; continue; } if (strncasecmp(p, "end", 3) == 0) { in_tobj = 0; + in_peds = 0; continue; } @@ -506,17 +544,11 @@ preparse_ide(const char *root, const char *rel) int time_on = 0, time_off = 0; c1 = strchr(p, ','); - if (!c1) { - continue; - } + if (!c1) continue; c2 = strchr(c1 + 1, ','); - if (!c2) { - continue; - } + if (!c2) continue; c3 = strchr(c2 + 1, ','); - if (!c3) { - continue; - } + if (!c3) continue; len = c2 - (c1 + 1); if (len >= NAME_SZ) { @@ -540,8 +572,6 @@ preparse_ide(const char *root, const char *rel) char temp_line[512]; strncpy(temp_line, p, sizeof(temp_line) - 1); temp_line[sizeof(temp_line) - 1] = '\0'; - - /* sanitize trailing spaces and comments first */ clean_ide_line(temp_line); last_comma = strrchr(temp_line, ','); @@ -556,6 +586,14 @@ preparse_ide(const char *root, const char *rel) } } + if (in_peds && fl) { + char d_name[32], t_name[32]; + snprintf(d_name, sizeof(d_name), "%s.dff", model); + snprintf(t_name, sizeof(t_name), "%s.txd", txd); + list_add(fl, d_name); + list_add(fl, t_name); + } + ide_db[ide_count].id = atoi(p); strncpy(ide_db[ide_count].model, model, NAME_SZ); strncpy(ide_db[ide_count].txd, txd, NAME_SZ); @@ -567,6 +605,7 @@ preparse_ide(const char *root, const char *rel) fclose(f); } + static const struct ide_entry * ide_lookup(int id) { @@ -998,7 +1037,7 @@ process_particle_txd(const char *root, struct arena *a) size_t sz; uint8_t *buf; - f = fopen_ci(root, "models/particle.txd"); + f = fopen_simple(root, "models/particle.txd"); if (!f) { return; } @@ -1028,6 +1067,25 @@ is_container(uint32_t type) return (0); } +static int +is_skin(const char *name) +{ + int i; + + for (i = 0; i < 313; i++) { + if (skin_names[i] && skin_names[i][0] != '\0' && + strcasecmp(skin_names[i], name) == 0) { + return (1); + } + } + for (i = 0; i < (int)(sizeof(special_actors) / sizeof(special_actors[0])); i++) { + if (strcasecmp(special_actors[i].model, name) == 0) { + return (1); + } + } + return (0); +} + static void convert_dff(const uint8_t *data, size_t size, const char *name, struct arena *a) { @@ -1040,6 +1098,9 @@ convert_dff(const uint8_t *data, size_t size, const char *name, struct arena *a) uint32_t last_cont = 0; int32_t cur_g = -1, cur_m = -1, wait_tex = 0; size_t end; + int is_ped; + + is_ped = is_skin(name); atomics = A_ALLOC(a, struct atomic_entry, 256); @@ -1282,6 +1343,11 @@ convert_dff(const uint8_t *data, size_t size, const char *name, struct arena *a) float vx = f_v[i*3+0], vy = f_v[i*3+1], vz = f_v[i*3+2]; float tu = f_u[i*2+0], tv = f_u[i*2+1]; uint8_t cr = f_c[i*4+0], cg = f_c[i*4+1], cb = f_c[i*4+2], ca = f_c[i*4+3]; + + if (is_ped) { + vx = -vx; + } + fwrite(&vx, 4, 1, out); fwrite(&vy, 4, 1, out); fwrite(&vz, 4, 1, out); @@ -1313,7 +1379,7 @@ process_local_ipl(const char *root, const char *rel, FILE *out, struct file_list size_t sz; char *buf; - f = fopen_ci(root, rel); + f = fopen_simple(root, rel); if (!f) { return; } @@ -1329,13 +1395,13 @@ process_local_ipl(const char *root, const char *rel, FILE *out, struct file_list } static void -process_gta_dat_ide(const char *root, const char *dat_path) +process_gta_dat_ide(const char *root, const char *dat_path, struct file_list *fl) { FILE *f; char line[512]; char *p; - f = fopen_ci(root, dat_path); + f = fopen_simple(root, dat_path); if (!f) { return; } @@ -1365,7 +1431,7 @@ process_gta_dat_ide(const char *root, const char *dat_path) rel[i+1] = '\0'; } trim_spaces(rel); - preparse_ide(root, rel); + preparse_ide(root, rel, fl); } } fclose(f); @@ -1378,7 +1444,7 @@ process_gta_dat_ipl(const char *root, const char *dat_path, FILE *map_out, struc char line[512]; char *p; - f = fopen_ci(root, dat_path); + f = fopen_simple(root, dat_path); if (!f) { return; } @@ -1427,7 +1493,7 @@ process_water(const char *root) float bx_min, bx_max, by_min, by_max; struct water_face *a, *b, *face; - f = fopen_ci(root, "data/water.dat"); + f = fopen_simple(root, "data/water.dat"); if (f == NULL) { return; } @@ -1623,9 +1689,9 @@ process_timecyc(const char *root) int w_idx, s_idx, total_weathers; char *p; - f = fopen_ci(root, "data/timecycp.dat"); + f = fopen_simple(root, "data/timecycp.dat"); if (!f) { - f = fopen_ci(root, "data/timecyc.dat"); + f = fopen_simple(root, "data/timecyc.dat"); } if (!f) { return; @@ -1834,7 +1900,7 @@ process_img_file(const char *root, const char *rel_path, struct file_list *fl, i uint32_t chunk; int i; - if (!resolve_ci(root, rel_path, img_path, sizeof(img_path))) { + if (!resolve_simple(root, rel_path, img_path, sizeof(img_path))) { warnx("failed to resolve %s", rel_path); return; } @@ -1906,8 +1972,6 @@ main(int argc, char *argv[]) return (1); } - index_directory_tree(argv[1]); - mkdir("assets", 0777); mkdir("assets/models", 0777); mkdir("assets/textures", 0777); @@ -1920,19 +1984,38 @@ main(int argc, char *argv[]) err(1, "failed to create map.ipl"); } - process_gta_dat_ide(argv[1], "data/default.dat"); - process_gta_dat_ide(argv[1], "data/gta.dat"); + process_gta_dat_ide(argv[1], "data/default.dat", &fl); + process_gta_dat_ide(argv[1], "data/gta.dat", &fl); + + /* forcefully register special actors as required assets */ + for (int i = 0; i < (int)(sizeof(special_actors) / sizeof(special_actors[0])); i++) { + char d_name[64], t_name[64]; + snprintf(d_name, sizeof(d_name), "%s.dff", special_actors[i].model); + snprintf(t_name, sizeof(t_name), "%s.txd", special_actors[i].txd); + list_add(&fl, d_name); + list_add(&fl, t_name); + } + + for (int i = 0; i < 313; i++) { + if (skin_names[i][0] != '\0') { + char d_name[64], t_name[64]; + snprintf(d_name, sizeof(d_name), "%s.dff", skin_names[i]); + snprintf(t_name, sizeof(t_name), "%s.txd", skin_names[i]); + list_add(&fl, d_name); + list_add(&fl, t_name); + } + } process_gta_dat_ipl(argv[1], "data/default.dat", map_out, &fl); process_gta_dat_ipl(argv[1], "data/gta.dat", map_out, &fl); - if (!resolve_ci(argv[1], "MODELS/GTA3.IMG", img_path, sizeof(img_path))) { - errx(1, "failed to resolve GTA3.IMG"); + if (!resolve_simple(argv[1], "models/gta3.img", img_path, sizeof(img_path))) { + errx(1, "failed to resolve gta3.img"); } img_fd = open(img_path, O_RDONLY); if (img_fd < 0) { - err(1, "failed to open GTA3.IMG"); + err(1, "failed to open gta3.img"); } fstat(img_fd, &st); @@ -1943,7 +2026,7 @@ main(int argc, char *argv[]) hdr = (struct img_header *)img_data; if (memcmp(hdr->magic, "VER2", 4) != 0) { - errx(1, "invalid IMG"); + errx(1, "invalid img"); } entries = (struct img_entry *)(img_data + sizeof(struct img_header)); @@ -1970,23 +2053,15 @@ main(int argc, char *argv[]) memset(&missing_fl, 0, sizeof(missing_fl)); for (int i = 0; i < fl.count; i++) { if (!asset_exists(fl.names[i])) { - for (uint32_t k = 0; k < hdr->entries; k++) { - char n[NAME_SZ + 1]; - size_t x; - for (x = 0; x < 24 && entries[k].name[x]; x++) { - n[x] = tolower((unsigned char)entries[k].name[x]); - } - n[x] = '\0'; - if (strcmp(n, fl.names[i]) == 0) { - list_add(&missing_fl, fl.names[i]); - break; - } - } + list_add(&missing_fl, fl.names[i]); } } printf("Total map files: %d, missing/rebuilding: %d\n", fl.count, missing_fl.count); fl = missing_fl; + munmap(img_data, st.st_size); + close(img_fd); + shared_arena.size = 128 * 1024 * 1024; shared_arena.mem = malloc(shared_arena.size); process_particle_txd(argv[1], &shared_arena); @@ -2004,11 +2079,15 @@ main(int argc, char *argv[]) args = malloc(num_threads * sizeof(struct thread_arg)); process_img_file(argv[1], "models/gta3.img", &fl, num_threads, threads, args); + process_img_file(argv[1], "models/gta_int.img", &fl, num_threads, threads, args); + process_img_file(argv[1], "models/cutscene.img", &fl, num_threads, threads, args); + free(threads); free(args); + /* Compile outputs into assets.tar */ printf("Packing assets.tar...\n"); tar = fopen("assets.tar", "wb"); if (tar) { diff --git a/src/render.c b/src/render.c @@ -22,6 +22,8 @@ #include <time.h> #include <unistd.h> +#include "skins.h" + #define WIDTH 640 #define HEIGHT 480 #define MAX_CACHE 8192 @@ -31,7 +33,7 @@ #define MAP_MIN -3000.0f #define MAP_MAX 3000.0f #define MAX_RENDER_TRIS 262144 -#define TAR_INDEX_SIZE 32768 +#define TAR_INDEX_SIZE 32768 #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) @@ -150,12 +152,6 @@ struct tar_header { char pad[12]; } __attribute__((packed)); -struct tar_index_entry { - char name[128]; - const uint8_t *data; - size_t size; -}; - struct tga_header { uint8_t id_length; uint8_t color_map_type; @@ -171,6 +167,12 @@ struct tga_header { uint8_t image_descriptor; } __attribute__((packed)); +struct tar_index_entry { + char name[128]; + const uint8_t *data; + size_t size; +}; + static struct sys_gfx *g_sys; static struct transform_array g_transforms; static struct mesh **scene_meshes = NULL; @@ -182,7 +184,6 @@ static struct render_triangle render_list[MAX_RENDER_TRIS]; static int render_count = 0; static struct water_face *water_faces = NULL; static uint32_t water_count = 0; -static struct tar_index_entry tar_idx[TAR_INDEX_SIZE]; static struct texture *water_tex = NULL, *sand_tex = NULL; static pthread_t *workers = NULL; static struct spin_barrier barrier_start, barrier_end; @@ -192,6 +193,9 @@ static size_t v_scratch_cap = 0; static uint8_t *g_tar_mapped = NULL; static size_t g_tar_size = 0; +static struct tar_index_entry tar_idx[TAR_INDEX_SIZE]; +static struct asset_cache *g_cache = NULL; +static int active_skin_id = 7; static inline void spin_barrier_init(struct spin_barrier *b, int count) @@ -236,6 +240,69 @@ get_tar_size(const char *octal) return (size); } +static uint32_t +tar_hash(const char *str) +{ + uint32_t hash = 5381; + int c; + + while ((c = (unsigned char)*str++)) { + hash = ((hash << 5) + hash) + tolower(c); + } + return (hash); +} + +static void +tar_build_index(void) +{ + size_t offset = 0; + struct tar_header *h; + size_t file_sz; + uint32_t slot; + + memset(tar_idx, 0, sizeof(tar_idx)); + + while (offset < g_tar_size) { + h = (struct tar_header *)(g_tar_mapped + offset); + if (h->name[0] == '\0') { + break; + } + file_sz = get_tar_size(h->size); + const uint8_t *file_data = g_tar_mapped + offset + 512; + + slot = tar_hash(h->name) % TAR_INDEX_SIZE; + while (tar_idx[slot].data != NULL) { + if (strcasecmp(tar_idx[slot].name, h->name) == 0) { + goto next_file; + } + slot = (slot + 1) % TAR_INDEX_SIZE; + } + + strncpy(tar_idx[slot].name, h->name, sizeof(tar_idx[slot].name) - 1); + tar_idx[slot].data = file_data; + tar_idx[slot].size = file_sz; + + next_file: + offset += 512 + ((file_sz + 511) & ~511); + } +} + +static const uint8_t * +tar_lookup(const char *filename, size_t *out_size) +{ + uint32_t slot; + + slot = tar_hash(filename) % TAR_INDEX_SIZE; + while (tar_idx[slot].data != NULL) { + if (strcasecmp(tar_idx[slot].name, filename) == 0) { + *out_size = tar_idx[slot].size; + return (tar_idx[slot].data); + } + slot = (slot + 1) % TAR_INDEX_SIZE; + } + return (NULL); +} + static inline float edge_func(float ax, float ay, float bx, float by, float cx, float cy) { @@ -270,7 +337,6 @@ draw_triangle_band(const struct render_triangle *tri, int min_y_band, int max_y_ v1 = tri->v1; v2 = tri->v2; - /* prevent near-plane depth extrapolation artifacts */ min_inv_w = MIN(v0.w, MIN(v1.w, v2.w)); if (min_inv_w < 0.0001f) { min_inv_w = 0.0001f; @@ -636,14 +702,15 @@ gfx_init(struct sys_gfx *g) Pixmap bm_no; Cursor no_ptr; char no_data[] = { 0,0,0,0,0,0,0,0 }; - char **missing; - int nmissing; - char *def; int i; + char **missing_charsets; + int missing_count; + char *def_string; memset(g, 0, sizeof(*g)); setlocale(LC_ALL, ""); + setlocale(LC_NUMERIC, "C"); XSetLocaleModifiers(""); g->dpy = XOpenDisplay(NULL); @@ -664,11 +731,6 @@ gfx_init(struct sys_gfx *g) if (im) { g->ic = XCreateIC(im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, g->win, NULL); } - g->font_set = XCreateFontSet(g->dpy, "-misc-fixed-medium-r-normal--14-*,*", - &missing, &nmissing, &def); - if (nmissing > 0) { - XFreeStringList(missing); - } g->gc = XCreateGC(g->dpy, g->win, 0, NULL); g->img = XShmCreateImage(g->dpy, DefaultVisual(g->dpy, scr), @@ -692,6 +754,20 @@ gfx_init(struct sys_gfx *g) XFreeCursor(g->dpy, no_ptr); XFreePixmap(g->dpy, bm_no); + g->font_set = XCreateFontSet(g->dpy, "-misc-fixed-medium-r-normal--14-*,*", + &missing_charsets, &missing_count, &def_string); + if (!g->font_set) { + g->font_set = XCreateFontSet(g->dpy, "-*-fixed-medium-r-normal--14-*,*", + &missing_charsets, &missing_count, &def_string); + } + if (!g->font_set) { + g->font_set = XCreateFontSet(g->dpy, "fixed", + &missing_charsets, &missing_count, &def_string); + } + if (missing_count > 0) { + XFreeStringList(missing_charsets); + } + g->key_w = XKeysymToKeycode(g->dpy, XK_w); g->key_a = XKeysymToKeycode(g->dpy, XK_a); g->key_s = XKeysymToKeycode(g->dpy, XK_s); @@ -725,9 +801,6 @@ gfx_cleanup(struct sys_gfx *g) threads_running = 0; spin_barrier_wait(&barrier_start); - if (g->font_set) { - XFreeFontSet(g->dpy, g->font_set); - } for (i = 0; i < num_threads; i++) { pthread_join(workers[i], NULL); } @@ -738,6 +811,9 @@ gfx_cleanup(struct sys_gfx *g) XDestroyImage(g->img); shmdt(g->shm.shmaddr); free(g->zbuffer); + if (g->font_set) { + XFreeFontSet(g->dpy, g->font_set); + } XFreeGC(g->dpy, g->gc); XDestroyWindow(g->dpy, g->win); XCloseDisplay(g->dpy); @@ -754,11 +830,9 @@ gfx_present(struct sys_gfx *g) XSetForeground(g->dpy, g->gc, 0x00FF00); XSetBackground(g->dpy, g->gc, 0x000000); if (g->font_set) { - Xutf8DrawImageString(g->dpy, g->win, g->font_set, g->gc, - 10, HEIGHT - 20, txt, strlen(txt)); + Xutf8DrawImageString(g->dpy, g->win, g->font_set, g->gc, 10, HEIGHT - 20, txt, strlen(txt)); } else { - XDrawImageString(g->dpy, g->win, g->gc, - 10, HEIGHT - 20, txt, strlen(txt)); + XDrawImageString(g->dpy, g->win, g->gc, 10, HEIGHT - 20, txt, strlen(txt)); } } XSync(g->dpy, False); @@ -999,35 +1073,6 @@ tex_load_tga(struct texture *t, const uint8_t *data, size_t sz) } } -static uint32_t -tar_hash(const char *str) -{ - uint32_t hash = 5381; - int c; - - while ((c = (unsigned char)*str++)) { - hash = ((hash << 5) + hash) + tolower(c); - } - - return (hash); -} - -static const uint8_t * -tar_lookup(const char *filename, size_t *out_size) -{ - uint32_t slot; - - slot = tar_hash(filename) % TAR_INDEX_SIZE; - while (tar_idx[slot].data != NULL) { - if (strcasecmp(tar_idx[slot].name, filename) == 0) { - *out_size = tar_idx[slot].size; - return (tar_idx[slot].data); - } - slot = (slot + 1) % TAR_INDEX_SIZE; - } - return (NULL); -} - static struct texture * cache_get_tex(struct asset_cache *c, const char *name) { @@ -1224,7 +1269,6 @@ project_and_push(const struct clip_vertex *c0, const struct clip_vertex *c1, con v2.w = 1.0f / c2->w; v2.x = (c2->x * v2.w + 1.0f) * WIDTH * 0.5f; v2.y = (1.0f - c2->y * v2.w) * HEIGHT * 0.5f; v2.z = c2->w; v2.u = c2->u; v2.v = c2->v; v2.r = c2->r; v2.g = c2->g; v2.b = c2->b; v2.a = c2->a; v2.f = c2->f; - /* check backface */ area = edge_func(v0.x, v0.y, v1.x, v1.y, v2.x, v2.y); double_sided = (tex && tex->has_alpha) || is_water; if (area >= 0.0f) { @@ -1349,7 +1393,6 @@ draw_mesh(const struct mesh *m, const struct mat4 *mvp, int is_seabed) } cv[i].f = fog; - /* override model pre-light colors with uniform seamless underwater tone */ if (is_seabed) { cv[i].r = 0.25f * ar; cv[i].g = 0.28f * ag; @@ -1417,6 +1460,15 @@ cmp_draw_item(const void *a, const void *b) return (0); } +static const char * +ped_lookup_model(int id) +{ + if (id < 0 || id >= 313) { + return (NULL); + } + return (skin_names[id][0] != '\0' ? skin_names[id] : NULL); +} + static void draw_scene(struct sys_gfx *g, const struct mat4 *proj, const struct mat4 *view, float cam_x, float cam_y, float cam_z) { @@ -1472,7 +1524,6 @@ draw_scene(struct sys_gfx *g, const struct mat4 *proj, const struct mat4 *view, for (i = 0; i < scene_grid[gx][gz].count; i++) { id = scene_grid[gx][gz].ids[i]; - /* check time-of-day visibility for timed objects */ int16_t t_on = g_transforms.time_on[id]; int16_t t_off = g_transforms.time_off[id]; if (t_on != t_off) { @@ -1532,6 +1583,26 @@ draw_scene(struct sys_gfx *g, const struct mat4 *proj, const struct mat4 *view, mat_mul(&mvp, proj, &mvp); draw_mesh(m, &mvp, 0); } + + if (active_skin_id >= 0) { + const char *skin_model_name = ped_lookup_model(active_skin_id); + if (skin_model_name) { + struct mesh *skin_mesh = cache_get_mesh(g_cache, skin_model_name); + if (skin_mesh && skin_mesh->ply_verts) { + struct mat4 rot, trans, world; + mat_identity(&rot); + /* grove street */ + rot.m[0][0] = -1.0f; + rot.m[2][2] = -1.0f; + mat_translate(&trans, 2490.0f, 13.4f, -1670.0f); + mat_mul(&world, &trans, &rot); + mat_mul(&mvp, view, &world); + mat_mul(&mvp, proj, &mvp); + draw_mesh(skin_mesh, &mvp, 0); + } + } + } + flush_render_list(); } @@ -1587,9 +1658,7 @@ draw_water(const struct mat4 *proj, const struct mat4 *view) struct mat4 mvp; struct vec3 in, out; float t, far_clp, fog_st, ar, ag, ab, w, fog, u_tex, v_tex; - float wave_height, wave_phase, wave_speed; - float flow_x, flow_z, len, dir_x, dir_z; - float dx, dy, dz, scroll_u, scroll_v; + float wave_height, wave_factor, dy, flow_u, flow_v; uint32_t i; int j; @@ -1606,31 +1675,14 @@ draw_water(const struct mat4 *proj, const struct mat4 *view) for (i = 0; i < water_count; i++) { for (j = 0; j < water_faces[i].num; j++) { - flow_x = water_faces[i].u[j]; - flow_z = water_faces[i].v[j]; - wave_height = water_faces[i].h[j]; - - len = sqrtf(flow_x * flow_x + flow_z * flow_z); - dir_x = 0.7071f; - dir_z = 0.7071f; - if (len > 0.001f) { - dir_x = flow_x / len; - dir_z = flow_z / len; - } - - wave_speed = (len > 0.01f) ? (len * 3.0f) : 1.5f; + in.x = water_faces[i].x[j]; - wave_phase = t * wave_speed + - (water_faces[i].x[j] * dir_x + water_faces[i].y[j] * dir_z) * 0.04f; - - dy = sinf(wave_phase) * wave_height; - - dx = -cosf(wave_phase) * wave_height * 0.45f * dir_x; - dz = -cosf(wave_phase) * wave_height * 0.45f * dir_z; + wave_height = water_faces[i].h[j]; + wave_factor = sinf(t * 1.5f + water_faces[i].x[j] * 0.05f + water_faces[i].y[j] * 0.05f); + dy = wave_factor * wave_height; - in.x = water_faces[i].x[j] + dx; in.y = water_faces[i].z[j] + dy; - in.z = water_faces[i].y[j] + dz; + in.z = water_faces[i].y[j]; mat_transform(&out, &w, &mvp, &in); fog = (out.z - fog_st) / (far_clp - fog_st); @@ -1640,15 +1692,11 @@ draw_water(const struct mat4 *proj, const struct mat4 *view) fog = 1.0f; } - scroll_u = flow_x; - scroll_v = flow_z; - if (fabsf(scroll_u) < 0.001f && fabsf(scroll_v) < 0.001f) { - scroll_u = 0.006f; - scroll_v = 0.004f; - } + flow_u = water_faces[i].u[j]; + flow_v = water_faces[i].v[j]; - u_tex = (water_faces[i].x[j] - water_faces[i].x[0]) * 0.01f + t * scroll_u + dy * 0.03f; - v_tex = (water_faces[i].y[j] - water_faces[i].y[0]) * 0.01f + t * scroll_v + dy * 0.03f; + u_tex = (water_faces[i].x[j] - water_faces[i].x[0]) * 0.01f + t * (flow_u - 0.015f) + dy * 0.05f; + v_tex = (water_faces[i].y[j] - water_faces[i].y[0]) * 0.01f + t * (flow_v - 0.015f) + dy * 0.05f; cv[j].x = out.x; cv[j].y = out.y; @@ -1688,8 +1736,6 @@ draw_ocean_sector(const struct mat4 *mvp, float x0, float x1, float z0, float z1 float step = 500.0f; float tx, tz, next_tx, next_tz; float xs[4], zs[4]; - float wave_height, wave_phase, dx, dy, dz; - float dir_x = 0.7071f, dir_z = 0.7071f; int i; far_clp = g_sys->cur_weather.far_clp; @@ -1705,8 +1751,6 @@ draw_ocean_sector(const struct mat4 *mvp, float x0, float x1, float z0, float z1 prelight_g = is_water ? 1.0f : 0.38f; prelight_b = is_water ? 1.0f : 0.40f; - wave_height = is_water ? 0.8f : 0.0f; - for (tx = floorf(x0 / step) * step; tx < x1; tx += step) { next_tx = tx + step; if (next_tx > x1) { @@ -1722,21 +1766,9 @@ draw_ocean_sector(const struct mat4 *mvp, float x0, float x1, float z0, float z1 zs[0] = tz; zs[1] = tz; zs[2] = next_tz; zs[3] = next_tz; for (i = 0; i < 4; i++) { - dy = 0.0f; - dx = 0.0f; - dz = 0.0f; - - if (is_water) { - wave_phase = t * 1.5f + (xs[i] * dir_x + zs[i] * dir_z) * 0.04f; - dy = sinf(wave_phase) * wave_height; - dx = -cosf(wave_phase) * wave_height * 0.45f * dir_x; - dz = -cosf(wave_phase) * wave_height * 0.45f * dir_z; - } - - in.x = xs[i] + dx; - in.y = height + dy; - in.z = zs[i] + dz; - + in.x = xs[i]; + in.y = height; + in.z = zs[i]; mat_transform(&out, &w, mvp, &in); fog = (out.z - fog_st) / (far_clp - fog_st); if (fog < 0.0f) { @@ -1744,14 +1776,12 @@ draw_ocean_sector(const struct mat4 *mvp, float x0, float x1, float z0, float z1 } else if (fog > 1.0f) { fog = 1.0f; } - u_tex = xs[i] * 0.01f; v_tex = zs[i] * 0.01f; if (is_water) { - u_tex += t * 0.008f + dy * 0.03f; - v_tex += t * 0.006f + dy * 0.03f; + u_tex -= t * 0.02f; + v_tex -= t * 0.02f; } - cv[i] = (struct clip_vertex){ out.x, out.y, out.z, w, u_tex, v_tex, ar * prelight_r, ag * prelight_g, ab * prelight_b, @@ -1780,9 +1810,8 @@ draw_infinite_ocean(const struct mat4 *proj, const struct mat4 *view, float cam_ mat_mul(&mvp, proj, view); far_clp = g_sys->cur_weather.far_clp; - b = 2970.0f; /* 10m overlap to close seams */ + b = 2970.0f; - /* north sector */ n_x0 = cam_x - far_clp * 1.5f; n_x1 = cam_x + far_clp * 1.5f; n_z0 = fmaxf(b, cam_z - far_clp * 1.5f); @@ -1792,7 +1821,6 @@ draw_infinite_ocean(const struct mat4 *proj, const struct mat4 *view, float cam_ draw_ocean_sector(&mvp, n_x0, n_x1, n_z0, n_z1, 0.0f, t, water_tex, 1); } - /* south sector */ s_x0 = cam_x - far_clp * 1.5f; s_x1 = cam_x + far_clp * 1.5f; s_z0 = cam_z - far_clp * 1.5f; @@ -1802,7 +1830,6 @@ draw_infinite_ocean(const struct mat4 *proj, const struct mat4 *view, float cam_ draw_ocean_sector(&mvp, s_x0, s_x1, s_z0, s_z1, 0.0f, t, water_tex, 1); } - /* west sector */ w_x0 = cam_x - far_clp * 1.5f; w_x1 = fminf(-b, cam_x + far_clp * 1.5f); w_z0 = fmaxf(-b, cam_z - far_clp * 1.5f); @@ -1812,7 +1839,6 @@ draw_infinite_ocean(const struct mat4 *proj, const struct mat4 *view, float cam_ draw_ocean_sector(&mvp, w_x0, w_x1, w_z0, w_z1, 0.0f, t, water_tex, 1); } - /* east sector */ e_x0 = fmaxf(b, cam_x - far_clp * 1.5f); e_x1 = cam_x + far_clp * 1.5f; e_z0 = fmaxf(-b, cam_z - far_clp * 1.5f); @@ -1829,7 +1855,7 @@ update_camera(float *cx, float *cy, float *cz, float *cyaw, float *cpitch, const { float speed, rot_speed, cp, sp, cy_val, sy_val; - speed = 100.0f * dt; + speed = 10.0f * dt; rot_speed = 0.01f * dt; if (keys[258]) *cyaw -= rot_speed; if (keys[259]) *cyaw += rot_speed; @@ -1852,7 +1878,7 @@ update_camera(float *cx, float *cy, float *cz, float *cyaw, float *cpitch, const static void scene_load(struct asset_cache *cache, const uint8_t *data, size_t sz) { - char *buf, *line, *next_line, *p, *end; + char *buf, *line, *next_line, *p; int in_inst, id, interior, idx, gx, gz; char name[NAME_SZ]; float px, py, pz, qx, qy, qz, qw, w; @@ -1896,17 +1922,16 @@ scene_load(struct asset_cache *cache, const uint8_t *data, size_t sz) continue; } if (in_inst) { - /* initialize fields to default values before sscanf */ lod = -1; time_on = 0; time_off = 0; if (sscanf(p, "%d, %23[^,], %d, %f, %f, %f, %f, %f, %f, %f, %d, %d, %d", &id, name, &interior, &px, &py, &pz, &qx, &qy, &qz, &qw, &lod, &time_on, &time_off) >= 10) { - end = name + strlen(name) - 1; - while (end > name && (*end == ' ' || *end == '\t')) { - *end = '\0'; - end--; + char *end_ptr = name + strlen(name) - 1; + while (end_ptr > name && (*end_ptr == ' ' || *end_ptr == '\t')) { + *end_ptr = '\0'; + end_ptr--; } for (p = name; *p; p++) { *p = tolower((unsigned char)*p); @@ -1943,7 +1968,6 @@ scene_load(struct asset_cache *cache, const uint8_t *data, size_t sz) g_transforms.rot_qz[idx] = qz; g_transforms.rot_qw[idx] = qw; - /* store limits for time checking */ g_transforms.time_on[idx] = (time_on >= 0) ? time_on : 0; g_transforms.time_off[idx] = (time_off >= 0) ? time_off : 0; @@ -1980,49 +2004,13 @@ scene_load(struct asset_cache *cache, const uint8_t *data, size_t sz) free(buf); } -static void -tar_build_index(void) -{ - size_t offset = 0; - struct tar_header *h; - size_t file_sz; - uint32_t slot; - - memset(tar_idx, 0, sizeof(tar_idx)); - - while (offset < g_tar_size) { - h = (struct tar_header *)(g_tar_mapped + offset); - if (h->name[0] == '\0') { - break; - } - file_sz = get_tar_size(h->size); - const uint8_t *file_data = g_tar_mapped + offset + 512; - - slot = tar_hash(h->name) % TAR_INDEX_SIZE; - while (tar_idx[slot].data != NULL) { - if (strcasecmp(tar_idx[slot].name, h->name) == 0) { - goto next_file; - } - slot = (slot + 1) % TAR_INDEX_SIZE; - } - - strncpy(tar_idx[slot].name, h->name, sizeof(tar_idx[slot].name) - 1); - tar_idx[slot].data = file_data; - tar_idx[slot].size = file_sz; - - next_file: - offset += 512 + ((file_sz + 511) & ~511); - } -} - int main(void) { struct sys_gfx g; - struct asset_cache *cache; struct mat4 view; struct timespec ts, t_start, t_end; - XEvent ev; + XEvent sa_event; float cam_x = 2490.0f, cam_y = 15.0f, cam_z = -1660.0f, cam_yaw = -1.57f, cam_pitch = 0.0f, dt; int running; int tar_fd; @@ -2039,7 +2027,7 @@ main(void) g_sys = &g; gfx_init(&g); - cache = calloc(1, sizeof(struct asset_cache)); + g_cache = calloc(1, sizeof(struct asset_cache)); g.game_time_sec = 0.0f; g.game_time_hours = 12.0f; ts.tv_sec = 0; @@ -2055,11 +2043,12 @@ main(void) if (g_tar_mapped == MAP_FAILED) { err(1, "failed to mmap assets.tar"); } + tar_build_index(); ipl_data = tar_lookup("assets/map.ipl", &ipl_sz); if (ipl_data) { - scene_load(cache, ipl_data, ipl_sz); + scene_load(g_cache, ipl_data, ipl_sz); } water_data = tar_lookup("assets/water.bin", &water_sz); @@ -2078,8 +2067,8 @@ main(void) memcpy(g.weathers, tc_data + 4, g.num_weathers * 8 * sizeof(struct timecyc_entry)); } - water_tex = cache_get_tex(cache, "waterclear256"); - sand_tex = cache_get_tex(cache, "sand256"); + water_tex = cache_get_tex(g_cache, "waterclear256"); + sand_tex = cache_get_tex(g_cache, "sand256"); XMapWindow(g.dpy, g.win); clock_gettime(CLOCK_MONOTONIC, &t_start); @@ -2095,25 +2084,24 @@ main(void) m_dy = 0; while (XPending(g.dpy)) { - XNextEvent(g.dpy, &ev); + XNextEvent(g.dpy, &sa_event); - /* filter dead keys and system layout events */ - if (XFilterEvent(&ev, None)) { + if (XFilterEvent(&sa_event, None)) { continue; } - if (ev.type == ClientMessage && (Atom)ev.xclient.data.l[0] == g.wm_delete) { + if (sa_event.type == ClientMessage && (Atom)sa_event.xclient.data.l[0] == g.wm_delete) { running = 0; } - if (ev.type == FocusIn && !g.in_chat) { + if (sa_event.type == FocusIn && !g.in_chat) { XGrabPointer(g.dpy, g.win, True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); } - if (ev.type == MotionNotify) { - last_mx = ev.xmotion.x; - last_my = ev.xmotion.y; + if (sa_event.type == MotionNotify) { + last_mx = sa_event.xmotion.x; + last_my = sa_event.xmotion.y; } - if (ev.type == KeyPress) { - code = ev.xkey.keycode; + if (sa_event.type == KeyPress) { + code = sa_event.xkey.keycode; if (code == g.key_esc) { if (g.in_chat) { g.in_chat = 0; @@ -2129,9 +2117,9 @@ main(void) int n = 0; if (g_sys->ic) { - n = Xutf8LookupString(g_sys->ic, &ev.xkey, str, sizeof(str), &sym, &status); + n = Xutf8LookupString(g_sys->ic, &sa_event.xkey, str, sizeof(str), &sym, &status); } else { - n = XLookupString(&ev.xkey, str, sizeof(str), &sym, NULL); + n = XLookupString(&sa_event.xkey, str, sizeof(str), &sym, NULL); } if (sym == XK_Return) { @@ -2141,6 +2129,11 @@ main(void) g.game_weather = atoi(g.chat_buf + 4); } else if (strncmp(g.chat_buf, "/sd ", 4) == 0) { g.far_clip_override = atof(g.chat_buf + 4); + } else if (strncmp(g.chat_buf, "/skin ", 6) == 0) { + int new_id = atoi(g.chat_buf + 6); + if (ped_lookup_model(new_id) != NULL) { + active_skin_id = new_id; + } } g.in_chat = 0; XGrabPointer(g.dpy, g.win, True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); @@ -2176,8 +2169,8 @@ main(void) } } } - if (ev.type == KeyRelease) { - code = ev.xkey.keycode; + if (sa_event.type == KeyRelease) { + code = sa_event.xkey.keycode; if (code == g.key_w) g.keys['w'] = 0; else if (code == g.key_a) g.keys['a'] = 0; else if (code == g.key_s) g.keys['s'] = 0; @@ -2189,7 +2182,6 @@ main(void) } } - /* calculate single precise delta at the end of the frame */ m_dx = last_mx - WIDTH / 2; m_dy = last_my - HEIGHT / 2; @@ -2227,20 +2219,19 @@ main(void) close(tar_fd); gfx_cleanup(&g); - /* prevent memory leaks */ - for (i = 0; i < cache->mesh_count; i++) { - free(cache->meshes[i].ply_verts); - free(cache->meshes[i].indices); - free(cache->meshes[i].mat_ids); - if (cache->meshes[i].num_materials > 0) { - free(cache->meshes[i].raw_tex_names); - free(cache->meshes[i].textures); + for (i = 0; i < g_cache->mesh_count; i++) { + free(g_cache->meshes[i].ply_verts); + free(g_cache->meshes[i].indices); + free(g_cache->meshes[i].mat_ids); + if (g_cache->meshes[i].num_materials > 0) { + free(g_cache->meshes[i].raw_tex_names); + free(g_cache->meshes[i].textures); } } - for (i = 0; i < cache->tex_count; i++) { - free(cache->textures[i].pixels); + for (i = 0; i < g_cache->tex_count; i++) { + free(g_cache->textures[i].pixels); } - free(cache); + free(g_cache); for (gz = 0; gz < GRID_SZ; gz++) { for (gx = 0; gx < GRID_SZ; gx++) { diff --git a/src/skins.h b/src/skins.h @@ -0,0 +1,47 @@ +#ifndef SKINS_H +#define SKINS_H + +static const char *skin_names[313] = { + "", "truth", "maccer", "cdeput", "sfpdm1", "bb", "wfycrp", "male01", + "wmycd2", "bfori", "bfost", "vbfycrp", "bfyri", "bfyst", "bmori", "bmost", + "bmyap", "bmybu", "bmybe", "bmydj", "bmyri", "bmycr", "bmyst", "wmybmx", + "wbdyg1", "wbdyg2", "wmybp", "wmycon", "bmydrug", "wmydrug", "hmydrug", "dwfolc", + "dwmolc1", "dwmolc2", "dwmylc1", "hmogar", "wmygol1", "wmygol2", "hfori", "hfost", + "hfyri", "hfyst", "suzie", "hmori", "hmost", "hmybe", "hmyri", "hmycr", + "hmyst", "omokung", "wmymech", "bmymoun", "wmymoun", "ofori", "ofost", "ofyri", + "ofyst", "omori", "omost", "omyri", "omyst", "wmyplt", "wmopj", "bfypro", + "hfypro", "vwmyap", "bmypol1", "bmypol2", "wmoprea", "sbfyst", "wmosci", "wmysgrd", + "swmyhp1", "swmyhp2", "", "swfopro", "wfystew", "swmotr1", "wmotr1", "bmotr1", + "vbmybox", "vwmybox", "vhmyelv", "vbmyelv", "vimyelv", "vwfypro", "vhfyst", "vwfyst1", + "wfori", "wfost", "wfyjg", "wfyri", "wfyro", "wfyst", "wmori", "wmost", + "wmyjg", "wmylg", "wmyri", "wmyro", "wmycr", "wmyst", "ballas1", "ballas2", + "ballas3", "fam1", "fam2", "fam3", "lsv1", "lsv2", "lsv3", "maffa", + "maffb", "mafboss", "vla1", "vla2", "vla3", "triada", "triadb", "lvpdm1", + "triboss", "dnb1", "dnb2", "dnb3", "vmaff1", "vmaff2", "vmaff3", "vmaff4", + "dnmylc", "dnfolc1", "dnfolc2", "dnfylc", "dnmolc1", "dnmolc2", "sbmotr2", "swmotr2", + "sbmytr3", "swmotr3", "wfybe", "bfybe", "hfybe", "sofybu", "sbmyst", "sbmycr", + "bmycg", "wfycrk", "hmycm", "wmybu", "bfybu", "", "wfybu", "dwfylc1", + "wfypro", "wmyconb", "wmybe", "wmypizz", "bmobar", "cwfyhb", "cwmofr", "cwmohb1", + "cwmohb2", "cwmyfr", "cwmyhb1", "bmyboun", "wmyboun", "wmomib", "bmymib", "wmybell", + "bmochil", "sofyri", "somyst", "vwmybjd", "vwfycrp", "sfr1", "sfr2", "sfr3", + "bmybar", "wmybar", "wfysex", "wmyammo", "bmytatt", "vwmycr", "vbmocd", "vbmycr", + "vhmycr", "sbmyri", "somyri", "somybu", "swmyst", "wmyva", "copgrl3", "gungrl3", + "mecgrl3", "nurgrl3", "crogrl3", "gangrl3", "cwfofr", "cwfohb", "cwfyfr1", "cwfyfr2", + "cwmyhb2", "dwfylc2", "dwmylc2", "omykara", "wmykara", "wfyburg", "vwmycd", "vhfypro", + "", "omonood", "omoboat", "wfyclot", "vwmotr1", "vwmotr2", "vwfywai", "sbfori", + "swfyri", "wmyclot", "sbfost", "sbfyri", "sbmocd", "sbmori", "sbmost", "shmycr", + "sofori", "sofost", "sofyst", "somobu", "somori", "somost", "swmotr5", "swfori", + "swfost", "swfyst", "swmocd", "swmori", "swmost", "shfypro", "sbfypro", "swmotr4", + "swmyri", "smyst", "smyst2", "sfypro", "vbfyst2", "vbfypro", "vhfyst3", "bikera", + "bikerb", "bmypimp", "swmycr", "wfylg", "wmyva2", "bmosec", "bikdrug", "wmych", + "sbfystr", "swfystr", "heck1", "heck2", "bmycon", "wmycd1", "bmocd", "vwfywa2", + "wmoice", "tenpen", "pulaski", "hern", "dwayne", "smoke", "sweet", "ryder", + "forelli", "mediatr", "laemt1", "lvemt1", "sfemt1", "lafd1", "lvfd1", "sffd1", + "lapd1", "sfpd1", "lvpd1", "csher", "lapdm1", "swat", "fbi", "army", + "dsher", "somyap", "rose", "paul", "cesar", "ogloc", "wuzimu", "torino", + "jizzy", "maddogg", "cat", "claude", "ryder2", "ryder3", "emmet", "andre", + "kendl", "jethro", "zero", "tbone", "sindaco", "janitor", "bbthin", "smokev", + "psycho" +}; + +#endif