commit 2f71540fe06a9a246f3cf32709306a4169182c07
parent e0a00cc206a04e6b122e1f6c9d8d2ebfda689b9d
Author: averagecoder <averagecoder@noreply.codeberg.org>
Date: Tue, 30 Jun 2026 01:40:59 +0300
feat: add /skin support
Diffstat:
| M | src/build.c | | | 325 | +++++++++++++++++++++++++++++++++++++++++++++++++------------------------------ |
| M | src/render.c | | | 375 | +++++++++++++++++++++++++++++++++++++++---------------------------------------- |
| A | src/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