diff --git a/.gitignore b/.gitignore index 698101bf..a8f82d93 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ config/init.cfg config/saved.cfg config/servers.cfg config/configtemplates.zip +config/mapmodelattributes.cfg packages/misc/checksums_md5.txt source/codeblocks/AssaultCube.layout source/codeblocks/AssaultCube_Linux.layout diff --git a/config/scontext.cfg b/config/scontext.cfg index 877084b7..5080e1da 100644 --- a/config/scontext.cfg +++ b/config/scontext.cfg @@ -12,7 +12,7 @@ const context_mdlcfg 4 // mdl configs, same // set allowed commands for the map config context mapcfgidents = [loadnotexture loadsky mapmodelreset mapmodel texturereset texture fog fogcolour mapsoundreset mapsound watercolour shadowyaw] -mdlcfgidents = [md2anim md2emit md2tag md3anim md3emit md3link md3load md3skin mdlalphatest mdlalphablend mdlcachelimit mdlcullface mdlscale mdlshadowdist mdltrans mdltranslucent mdlvertexlight] +mdlcfgidents = [md2anim md2emit md2tag md3anim md3emit md3link md3load md3skin mdlalphatest mdlalphablend mdlcachelimit mdlcullface mdlscale mdlshadowdist mdltrans mdltranslucent mdlvertexlight mdlattribute] loop i (listlen $mapcfgidents) [ scriptcontext $context_mapcfg (at $mapcfgidents $i) ] loop i (listlen $mdlcfgidents) [ scriptcontext $context_mdlcfg (at $mdlcfgidents $i) ] diff --git a/source/src/command.cpp b/source/src/command.cpp index b0d0dfb9..8544bd89 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -1209,7 +1209,7 @@ char *indexlist(const char *s, int pos) } COMMANDF(at, "si", (char *s, int *pos) { commandret = indexlist(s, *pos); }); -int listlen(char *s) +int listlen(const char *s) { int n = 0; whitespaceskip; diff --git a/source/src/main.cpp b/source/src/main.cpp index cc44025a..2566b1f2 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -42,6 +42,7 @@ void quit() // normal exit if(resetcfg) deletecfg(); else writecfg(); savehistory(); + writemapmodelattributes(); writeallxmaps(); cleanup(NULL); popscontext(); @@ -1263,6 +1264,7 @@ int main(int argc, char **argv) exec("config/scontext.cfg"); exec("config/locale.cfg"); exec("config/keymap.cfg"); + execfile("config/mapmodelattributes.cfg"); exec("config/menus.cfg"); exec("config/scripts.cfg"); exec("config/prefabs.cfg"); diff --git a/source/src/model.h b/source/src/model.h index ea840ae3..1db36b94 100644 --- a/source/src/model.h +++ b/source/src/model.h @@ -68,3 +68,13 @@ struct model }; struct mapmodelinfo { int rad, h, zoff; string name; model *m; }; + +enum { MMA_KEYWORDS = 0, MMA_DESC, MMA_DEFAULTPARAMS, MMA_USAGE, MMA_AUTHOR, MMA_LICENSE, MMA_DISTRIBUTION, MMA_VERSION, MMA_NUM }; + +struct mapmodelattributes +{ + string name; + const char *n[MMA_NUM]; + int tmp; + mapmodelattributes() { memset(this, 0, sizeof(*this)); } +}; diff --git a/source/src/protocol.h b/source/src/protocol.h index fa02b093..03128187 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -156,6 +156,7 @@ enum #define FTXT__SERVDESC (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK) #define FTXT__MAPMSG (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK) #define FTXT__MAPNAME (FTXT_NOCOLOR | FTXT_MAPNAME | FTXT_TOLOWER) +#define FTXT__MDLATTR (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK | FTXT_NOCOLOR | FTXT_SAFECS) #define FTXT__DEMONAME (FTXT_NOCOLOR | FTXT_MAPNAME) #define FTXT__PLAYERNAME (FTXT_NOWHITE | FTXT_NOCOLOR) #define FTXT__SERVERINFO (FTXT_ALLOWTAB) diff --git a/source/src/protos.h b/source/src/protos.h index 863f3025..b1919d12 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -744,6 +744,7 @@ extern void renderclients(); extern void renderclient(playerent *d); extern void renderclient(playerent *d, const char *mdlname, const char *vwepname, int tex = 0); extern void updateclientname(playerent *d); +extern void writemapmodelattributes(); // weapon extern void shoot(playerent *d, vec &to); @@ -840,6 +841,7 @@ extern void floatret(float v); extern void result(const char *s); extern void exec(const char *cfgfile); extern bool execfile(const char *cfgfile); +extern int listlen(const char *s); extern void resetcomplete(); extern void complete(char *s, bool reversedirection); extern void push(const char *name, const char *action); diff --git a/source/src/rendermodel.cpp b/source/src/rendermodel.cpp index e4899cf1..bfe79b7e 100644 --- a/source/src/rendermodel.cpp +++ b/source/src/rendermodel.cpp @@ -3,6 +3,7 @@ VARP(animationinterpolationtime, 0, 150, 1000); model *loadingmodel = NULL; +mapmodelattributes loadingattributes; #include "tristrip.h" #include "modelcache.h" @@ -86,9 +87,26 @@ void mdlcachelimit(int *limit) COMMAND(mdlcachelimit, "i"); +const char *mdlattrnames[] = { "keywords", "desc", "defaults", "usage", "author", "license", "distribution", "version", "" }; + +void mdlattribute(char *attrname, char *val) +{ + checkmdl; + int i = getlistindex(attrname, mdlattrnames, true, MMA_NUM); + if(i < MMA_NUM) + { + DELSTRING(loadingattributes.n[i]); + filtertext(val, val, FTXT__MDLATTR); + if(*val) loadingattributes.n[i] = newstring(val); + } +} + +COMMAND(mdlattribute, "ss"); + VAR(mapmodelchanged, 0, 0, 1); vector mapmodels; +const char *mmpath = "mapmodels/"; void mapmodel(int *rad, int *h, int *zoff, char *snap, char *name) { @@ -97,7 +115,7 @@ void mapmodel(int *rad, int *h, int *zoff, char *snap, char *name) mmi.h = *h; mmi.zoff = *zoff; mmi.m = NULL; - formatstring(mmi.name)("mapmodels/%s", name); + formatstring(mmi.name)("%s%s", mmpath, name); mapmodelchanged = 1; } @@ -120,6 +138,43 @@ COMMANDF(mapmodelbyname, "s", (char *name) result(res); }); +hashtable mdlregistry; + +void setmodelattributes(const char *name, mapmodelattributes &ma) +{ + if(!strchr(name, ' ') && !strncmp(name, mmpath, strlen(mmpath))) name += strlen(mmpath); // for now: ignore mapmodels with spaces in the path (next release: ban them) + else return; // we only want mapmodels + mapmodelattributes **mrp = mdlregistry.access(name), *mr = mrp ? *mrp : NULL, *r = mr; + if(!r) + { + r = new mapmodelattributes; + copystring(r->name, name); + } + loopi(MMA_NUM) + { + if(ma.n[i]) + { + if(!r->n[i]) mapmodelchanged = 1; + DELSTRING(r->n[i]); // overwrite existing attributes + r->n[i] = ma.n[i]; + ma.n[i] = NULL; + } + } + if(!mr) mdlregistry.access(r->name, r); +} + +void mapmodelregister_(char **args, int numargs) // read model attributes without loading the model +{ + mapmodelattributes ma; + if(numargs > 0) // need a name at least + { + defformatstring(p)("%s%s", mmpath, args[0]); + loopirev(--numargs < MMA_NUM ? numargs : MMA_NUM) ma.n[i] = *args[i + 1] ? newstring(args[i + 1]) : NULL; + setmodelattributes(p, ma); + } +} +COMMANDN(mapmodelregister, mapmodelregister_, "v"); + hashtable mdllookup; hashtable mdlnotfound; @@ -141,6 +196,7 @@ model *loadmodel(const char *name, int i, bool trydl) // load model by name pushscontext(IEXC_MDLCFG); m = new md2(name); // try md2 loadingmodel = m; + loopi(MMA_NUM) DELSTRING(loadingattributes.n[i]); if(!m->load()) // md2 didn't load { delete m; @@ -163,7 +219,11 @@ model *loadmodel(const char *name, int i, bool trydl) // load model by name } } popscontext(); - if(loadingmodel && m) mdllookup.access(m->name(), m); + if(loadingmodel && m) + { + mdllookup.access(m->name(), m); + setmodelattributes(m->name(), loadingattributes); + } loadingmodel = NULL; } if(mapmodels.inrange(i) && !mapmodels[i].m) mapmodels[i].m = m; @@ -175,6 +235,101 @@ void cleanupmodels() enumerate(mdllookup, model *, m, m->cleanup()); } +void mapmodelattributes_(char *name, char *attr) // mostly for debugging purposes... +{ + const char *res = NULL; + mapmodelattributes **ap = mdlregistry.access(name), *a = ap ? *ap : NULL; + if(a) + { + int i = getlistindex(attr, mdlattrnames, true, MMA_NUM); + if(i < MMA_NUM) res = a->n[i]; + else + { + string s = ""; + loopi(MMA_NUM) if(a->n[i]) concatformatstring(s, " %s:\"%s\"", mdlattrnames[i], a->n[i]); + conoutf("%s:%s", a->name, *s ? s : " "); + } + } + result(res ? res : ""); +} +COMMANDN(mapmodelattributes, mapmodelattributes_, "is"); + +static int mmasortorder = 0; +int mmasort(mapmodelattributes **a, mapmodelattributes **b) { return strcmp((*a)->name, (*b)->name); } +int mmasort2(mapmodelattributes **a, mapmodelattributes **b) +{ + const char *aa = (*a)->n[mmasortorder], *bb = (*b)->n[mmasortorder], nn[2] = { 127, 0 }; + int i = strcmp(aa ? aa : nn, bb ? bb : nn); + return i ? i : (*a)->tmp - (*b)->tmp; +} + +void writemapmodelattributes() +{ + stream *f = openfile("config" PATHDIVS "mapmodelattributes.cfg", "w"); + vector mmas; + enumerate(mdlregistry, mapmodelattributes *, m, mmas.add(m)); + mmas.sort(mmasort); + f->printf("// automatically written on exit. this is cached information extracted from model configs. no point in editing it.\n// [path %s]\n", conc(mdlattrnames, MMA_NUM, true)); // memory leak, but w/e + loopv(mmas) + { + f->printf("\nmapmodelregister %s", mmas[i]->name); + loopj(MMA_NUM) f->printf(" %s", escapestring(mmas[i]->n[j])); // escapestring can handle NULL + } + f->printf("\n\n"); + delete f; +} + +void getmapmodelattributes(char **args, int numargs) // create a list of mapmodel paths and selected attributes +{ + // parse argument list + vector opts; + loopi(MMA_NUM) opts.add(mdlattrnames[i]); + opts.add("sortby:"); opts.add("explodekeywords"); opts.add(""); + vector a; + loopi(numargs) a.add(getlistindex(args[i], (const char **)opts.getbuf(), false, opts.length())); + bool explodekeywords = false; + loopi(numargs) if(a[i] == MMA_NUM + 1) explodekeywords = true; + + // build list of mapmodels (explode if necessary) + vector mmas, emmas; + enumerate(mdlregistry, mapmodelattributes *, m, + { + if(explodekeywords && m->n[MMA_KEYWORDS] && listlen(m->n[MMA_KEYWORDS]) > 1) + { + vector keys; + explodelist(m->n[MMA_KEYWORDS], keys); + loopv(keys) + { + mmas.add(new mapmodelattributes(*m)); + emmas.add(mmas.last())->n[MMA_KEYWORDS] = keys[i]; + } + } + else mmas.add(m); + }); + + // sort the list + mmas.sort(mmasort); + loopirev(numargs - 1) if(a[i] == MMA_NUM) // sortby: + { + loopvj(mmas) mmas[j]->tmp = j; // store last sort order, because quicksort is not stable + if((mmasortorder = a[i + 1]) < MMA_NUM) mmas.sort(mmasort2); + } + + // assemble output + vector res; + loopv(mmas) + { + cvecprintf(res, "%s", mmas[i]->name); + loopvj(a) if(a[j] < MMA_NUM) cvecprintf(res, " %s", escapestring(mmas[i]->n[a[j]])); + res.add('\n'); + } + if(res.length()) res.last() = '\0'; + else res.add('\0'); + loopv(emmas) { delstring(emmas[i]->n[MMA_KEYWORDS]); delete emmas[i]; } // only delete what was exploded + result(res.getbuf()); +} +COMMAND(getmapmodelattributes, "v"); + VARP(dynshadow, 0, 40, 100); VARP(dynshadowdecay, 0, 1000, 3000); @@ -772,3 +927,19 @@ void renderclients() loopv(players) if((d = players[i]) && d->state!=CS_SPAWNING && d->state!=CS_SPECTATE && (!player1->isspectating() || player1->spectatemode != SM_FOLLOW1ST || player1->followplayercn != i)) renderclient(d); if(player1->state==CS_DEAD || (reflecting && !refracting)) renderclient(player1); } + +void loadallmapmodels() // try to find all mapmodels in packages/models/mapmodels +{ + vector files; + listfilesrecursive("packages/models/mapmodels", files); + loopvrev(files) if(strchr(files[i], '.')) delstring(files.remove(i)); // poor man's solution to remove ordinary files from the list: remove everything containing '.' + files.sort(stringsort); + loopvrev(files) if(files.inrange(i + 1) && !strcmp(files[i], files[i + 1])) delstring(files.remove(i + 1)); // remove doubles + loopv(files) + { + const char *mn = files[i] + strlen("packages/models/"); + clientlogf("loadallmaps: %s %s", mn, loadmodel(mn) ? "loaded" : "failed to load"); + delstring(files[i]); + } +} +COMMAND(loadallmapmodels, ""); diff --git a/source/src/stream.cpp b/source/src/stream.cpp index cd3c59ca..6996f354 100644 --- a/source/src/stream.cpp +++ b/source/src/stream.cpp @@ -302,6 +302,25 @@ int listfiles(const char *dir, const char *ext, vector &files) return dirs; } +#ifndef STANDALONE +void listfilesrecursive(const char *dir, vector &files, int level) +{ + if(level > 8) return; // 8 levels is insane enough... + vector thisdir; + listfiles(dir, NULL, thisdir); + loopv(thisdir) + { + if(thisdir[i][0] != '.') // ignore "." and ".." (and also other directories starting with '.', like it is unix-convention - and doesn't hurt on windows) + { + defformatstring(name)("%s/%s", dir, thisdir[i]); + files.add(newstring(name)); + listfilesrecursive(name, files, level + 1); + } + delstring(thisdir[i]); + } +} +#endif + bool delfile(const char *path) { return !remove(path); diff --git a/source/src/tools.h b/source/src/tools.h index be825d31..cd1f1455 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -964,6 +964,7 @@ extern char *loadfile(const char *fn, int *size, const char *mode = NULL); extern void filerotate(const char *basename, const char *ext, int keepold, const char *oldformat = NULL); extern bool listdir(const char *dir, const char *ext, vector &files); extern int listfiles(const char *dir, const char *ext, vector &files); +extern void listfilesrecursive(const char *dir, vector &files, int level = 0); extern int listzipfiles(const char *dir, const char *ext, vector &files); extern bool delfile(const char *path); extern void backup(char *name, char *backupname);