diff --git a/README.md b/README.md index 91861fe7..edbdab93 100644 --- a/README.md +++ b/README.md @@ -157,8 +157,7 @@ Games check videos: Goals, fully finished goals could be checked in [here](CHANGELOG): * [ ] CTC entity format from Anachronox, -* [ ] MDA model skin selection by tag, -* [ ] SDEF/MDA dynamicaly allocate list of skins, +* [ ] SDEF dynamicaly allocate list of skins, * [ ] Support material load textures/textureinfo.dat from Anachronox, * [ ] Fix invisiable entities in basicsjam1_ziutek, * [ ] Make lightmap textures dynamic n64jam_palmlix, diff --git a/src/client/refresh/files/models.c b/src/client/refresh/files/models.c index 6ed6f7d2..adab32a3 100644 --- a/src/client/refresh/files/models.c +++ b/src/client/refresh/files/models.c @@ -2878,13 +2878,227 @@ Mod_LoadModel_SDEF(const char *mod_name, const void *buffer, int modfilelen, return extradata; } -static void * -Mod_LoadModel_MDA_Text(const char *mod_name, char *curr_buff, - readfile_t read_file, struct image_s ***skins, int *numskins, modtype_t *type) +typedef struct { + char *map; + char *alphafunc; + char *depthwrite; + char *uvgen; + char *blendmode; + char *depthfunc; + char *cull; + char *rgbgen; + char *uvmod; +} mda_pass_t; + +typedef struct { + mda_pass_t *passes; + size_t pass_count; +} mda_skin_t; + +typedef struct { + char *name; + char *evaluate; + mda_skin_t *skins; + size_t skin_count; +} mda_profile_t; + +typedef struct { + char *basemodel; + vec3_t headtri; + mda_profile_t *profiles; + size_t profile_count; +} mda_model_t; + +static void +Mod_LoadModel_MDA_Parse_SkipComment(char **curr_buff) +{ + size_t linesize; + + /* skip comment */ + linesize = strcspn(*curr_buff, "\n\r"); + *curr_buff += linesize; +} + +static void +Mod_LoadModel_MDA_Parse_Pass(const char *mod_name, char **curr_buff, char *curr_end, + mda_pass_t *pass) +{ + while (*curr_buff && *curr_buff < curr_end) + { + const char *token; + + token = COM_Parse(curr_buff); + if (!*token) + { + continue; + } + + else if (token[0] == '}') + { + /* skip end of section */ + break; + } + else if (token[0] == '#') + { + Mod_LoadModel_MDA_Parse_SkipComment(curr_buff); + } + else if (!strcmp(token, "map")) + { + token = COM_Parse(curr_buff); + pass->map = strdup(token); + Q_replacebackslash(pass->map); + } + else if (!strcmp(token, "uvmod")) + { + size_t linesize; + char *value; + + linesize = strcspn(*curr_buff, "\n\r"); + value = malloc(linesize + 1); + memcpy(value, *curr_buff, linesize); + value[linesize] = 0; + *curr_buff += linesize; + + pass->uvmod = value; + } + else if (!strcmp(token, "alphafunc") || + !strcmp(token, "depthwrite") || + !strcmp(token, "uvgen") || + !strcmp(token, "blendmode") || + !strcmp(token, "depthfunc") || + !strcmp(token, "cull") || + !strcmp(token, "rgbgen")) + { + char token_section[MAX_TOKEN_CHARS]; + + strncpy(token_section, token, sizeof(token_section) - 1); + token = COM_Parse(curr_buff); + + if (!strcmp(token_section, "alphafunc")) + { + pass->alphafunc = strdup(token); + } + else if (!strcmp(token_section, "depthwrite")) + { + pass->depthwrite = strdup(token); + } + else if (!strcmp(token_section, "uvgen")) + { + pass->uvgen = strdup(token); + } + else if (!strcmp(token_section, "blendmode")) + { + pass->blendmode = strdup(token); + } + else if (!strcmp(token_section, "depthfunc")) + { + pass->depthfunc = strdup(token); + } + else if (!strcmp(token_section, "cull")) + { + pass->cull = strdup(token); + } + else if (!strcmp(token_section, "rgbgen")) + { + pass->rgbgen = strdup(token); + } + } + } +} + +static void +Mod_LoadModel_MDA_Parse_Skin(const char *mod_name, char **curr_buff, char *curr_end, + mda_skin_t *skin) { - char base_model[MAX_QPATH * 2] = {0}; - char base_skin[MAX_QPATH * 2] = {0}; + while (*curr_buff && *curr_buff < curr_end) + { + const char *token; + token = COM_Parse(curr_buff); + if (!*token) + { + continue; + } + + else if (token[0] == '}') + { + /* skip end of section */ + break; + } + else if (token[0] == '#') + { + Mod_LoadModel_MDA_Parse_SkipComment(curr_buff); + } + else if (!strcmp(token, "pass")) + { + mda_pass_t *pass; + + skin->pass_count++; + skin->passes = realloc(skin->passes, skin->pass_count * sizeof(mda_pass_t)); + pass = &skin->passes[skin->pass_count - 1]; + memset(pass, 0, sizeof(*pass)); + + token = COM_Parse(curr_buff); + if (!token || token[0] != '{') + { + return; + } + + Mod_LoadModel_MDA_Parse_Pass(mod_name, curr_buff, curr_end, pass); + } + } +} + +static void +Mod_LoadModel_MDA_Parse_Profile(const char *mod_name, char **curr_buff, char *curr_end, + mda_profile_t *profile) +{ + while (*curr_buff && *curr_buff < curr_end) + { + const char *token; + + token = COM_Parse(curr_buff); + if (!*token) + { + continue; + } + + else if (token[0] == '}') + { + /* skip end of section */ + break; + } + else if (token[0] == '#') + { + Mod_LoadModel_MDA_Parse_SkipComment(curr_buff); + } + else if (!strcmp(token, "skin")) { + mda_skin_t *skin; + + profile->skin_count++; + profile->skins = realloc(profile->skins, profile->skin_count * sizeof(mda_skin_t)); + skin = &profile->skins[profile->skin_count - 1]; + memset(skin, 0, sizeof(*skin)); + + token = COM_Parse(curr_buff); + if (!token || token[0] != '{') + { + return; + } + + Mod_LoadModel_MDA_Parse_Skin(mod_name, curr_buff, curr_end, skin); + } + else if (!strcmp(token, "evaluate")) + { + profile->evaluate = strdup(COM_Parse(curr_buff)); + } + } +} + +static void +Mod_LoadModel_MDA_Parse(const char *mod_name, char *curr_buff, char *curr_end, + mda_model_t *mda) +{ while (curr_buff) { const char *token; @@ -2895,69 +3109,139 @@ Mod_LoadModel_MDA_Text(const char *mod_name, char *curr_buff, continue; } + else if (token[0] == '}') + { + /* skip end of section */ + continue; + } + else if (token[0] == '#') + { + Mod_LoadModel_MDA_Parse_SkipComment(&curr_buff); + + } + /* found basemodel */ else if (!strcmp(token, "basemodel")) { token = COM_Parse(&curr_buff); if (!token) { - return NULL; + return; } - strncpy(base_model, token, sizeof(base_model) - 1); - - Q_replacebackslash(base_model); + mda->basemodel = strdup(token); + Q_replacebackslash(mda->basemodel); + } + else if (!strcmp(token, "headtri")) + { + int i; - if (base_skin[0]) + for (i = 0; i < 3; i++) { - /* other fields is unused for now */ - break; + token = COM_Parse(&curr_buff); + mda->headtri[i] = (float)strtod(token, (char **)NULL); } } - /* TODO: should be profile {*} -> skin -> pass -> map */ - else if (!strcmp(token, "map")) + else if (!strcmp(token, "profile")) { - char* token_end = NULL; + mda_profile_t *profile; token = COM_Parse(&curr_buff); - if (!token) - { - return NULL; - } + mda->profile_count++; + mda->profiles = realloc(mda->profiles, mda->profile_count * sizeof(mda_profile_t)); + profile = &mda->profiles[mda->profile_count - 1]; + memset(profile, 0, sizeof(*profile)); - if (token[0] == '"') + if (!token || token[0] == '{') { - token ++; + profile->name = strdup(""); } - - token_end = strchr(token, '"'); - if (token_end) + else { - /* remove end " */ - *token_end = 0; + profile->name = strdup(token); + token = COM_Parse(&curr_buff); + if (!token || token[0] != '{') + { + return; + } } - strncpy(base_skin, token, sizeof(base_skin) - 1); + Mod_LoadModel_MDA_Parse_Profile(mod_name, &curr_buff, curr_end, profile); + } + } +} - Q_replacebackslash(base_skin); +static void +Mod_LoadModel_MDA_Free(mda_model_t *mda) +{ + size_t i; + + free(mda->basemodel); + for (i = 0; i < mda->profile_count; i++) { + mda_profile_t *profile; + size_t j; - if (base_model[0]) + profile = &mda->profiles[i]; + free(profile->name); + free(profile->evaluate); + for (j = 0; j < profile->skin_count; j++) + { + mda_skin_t *skin; + size_t k; + + skin = &profile->skins[j]; + for (k = 0; k < skin->pass_count; k++) { - /* other fields is unused for now */ - break; + mda_pass_t *pass; + + pass = &skin->passes[k]; + free(pass->map); + free(pass->alphafunc); + free(pass->depthwrite); + free(pass->uvgen); + free(pass->blendmode); + free(pass->depthfunc); + free(pass->cull); + free(pass->rgbgen); + free(pass->uvmod); } + free(skin->passes); } + free(profile->skins); } + free(mda->profiles); +} + +static void * +Mod_LoadModel_MDA_Text(const char *mod_name, char *curr_buff, size_t len, + readfile_t read_file, struct image_s ***skins, int *numskins, modtype_t *type) +{ + mda_model_t mda = {0}; + const char *profile_name; - if (base_model[0]) + profile_name = strrchr(mod_name, '!'); + if (profile_name) + { + /* skip ! */ + profile_name += 1; + } + else + { + profile_name = ""; + } + + Mod_LoadModel_MDA_Parse(mod_name, curr_buff, curr_buff + len, &mda); + + if (mda.basemodel) { void *extradata, *base; int base_size; - base_size = read_file(base_model, (void **)&base); + base_size = read_file(mda.basemodel, (void **)&base); if (base_size <= 0) { R_Printf(PRINT_DEVELOPER, "%s: %s No base model for %s\n", - __func__, mod_name, base_model); + __func__, mod_name, mda.basemodel); + Mod_LoadModel_MDA_Free(&mda); return NULL; } @@ -2965,36 +3249,69 @@ Mod_LoadModel_MDA_Text(const char *mod_name, char *curr_buff, extradata = Mod_LoadModelFile(mod_name, base, base_size, skins, numskins, read_file, type); + free(base); /* check skin path */ - if (extradata && *type == mod_alias) + if (extradata && *type == mod_alias && mda.profile_count) { - dmdx_t *pheader; - int i; + mda_profile_t *profile = NULL; - pheader = (dmdx_t *)extradata; - for (i=0; i < pheader->num_skins; i++) + if (profile_name) { - char *skin; + size_t i; - /* Update included model with skin path */ - skin = (char *)pheader + pheader->ofs_skins + i * MAX_SKINNAME; - if (!strchr(skin, '/') && !strchr(skin, '\\')) + for (i = 0; i < mda.profile_count; i++) { - char skin_path[MAX_QPATH * 2] = {0}; + if (!strcmp(mda.profiles[i].name, profile_name)) + { + profile = &mda.profiles[i]; + } + } + } - strncpy(skin_path, base_skin, sizeof(skin_path)); - strcpy(strrchr(skin_path, '/') + 1, skin); + if (!profile) + { + /* use first in list */ + profile = &mda.profiles[0]; + } + + if (profile) + { + size_t skins_count, i; + dmdx_t *pheader; - strncpy(skin, skin_path, MAX_SKINNAME); + pheader = (dmdx_t *)extradata; + skins_count = Q_min(profile->skin_count, pheader->num_skins); + + for (i = 0; i < skins_count; i++) + { + if (profile->skins[i].pass_count) + { + char *skin; + + /* Update included model with skin path */ + skin = (char *)pheader + pheader->ofs_skins + i * MAX_SKINNAME; + if (!strchr(skin, '/') && !strchr(skin, '\\')) + { + char skin_path[MAX_QPATH * 2] = {0}; + char *base_skin; + + base_skin = profile->skins[i].passes[0].map; + strncpy(skin_path, base_skin, sizeof(skin_path)); + strcpy(strrchr(skin_path, '/') + 1, skin); + strncpy(skin, skin_path, MAX_SKINNAME); + } + } } } } - free(base); + Mod_LoadModel_MDA_Free(&mda); + return extradata; } + Mod_LoadModel_MDA_Free(&mda); return NULL; } @@ -3009,8 +3326,8 @@ Mod_LoadModel_MDA(const char *mod_name, const void *buffer, int modfilelen, memcpy(text, (char *)buffer + 4, modfilelen - 4); text[modfilelen - 4] = 0; - extradata = Mod_LoadModel_MDA_Text(mod_name, text, read_file, skins, - numskins, type); + extradata = Mod_LoadModel_MDA_Text(mod_name, text, modfilelen - 4, + read_file, skins, numskins, type); free(text);