additional text attributes for mapmodels
This adds a means to store additional attributes for mapmodels, including
a caching mechanism, so the mapmodels don't have to be loaded to access
the information. The goal is, to provide fully automatic mapmodel menus
for map editing who also add information about downloaded mapmodels.
Model attributes are extracted everytime, when a model is loaded. At game
end, all available data is written to config/mapmodelattributes.cfg and
restored at next startup. On release, a file with all (but only) official
models should be included. Since autodownloaded models are usually "loaded"
after download, those are automatically added to the database. If someone
adds models manually to the directories (media pack, w/e), the command
"loadallmapmodels" can be used to try to find all available models. The
only way to delete something from the database, is to delete the file
config/mapmodelattributes.cfg when the game is closed.
The current list of attributes is:
keywords
List of one or more menu topics to list the model under. Check
config/menus_edit.cfg for available keywords. Unknown keywords will
be listed separately.
desc
Short name or description of the model. Will be used as menu entry.
usage
Important information for how to use that specific model.
author
Who created the mapmodel. If it's a derivative of another model, state
the author of the mod (refer to the original author under "license", if
you like). Always use the exact same spelling for the same author, to
allow menus to automatically group models by author.
license
Free-text attribute for license info or info about additional
contributors (or original authors for derivated works).
distribution
One of "official", "autodownload" or "manual". Other values default to
"manual". This is used to determine, if a map contains only original
media or media that can be download automatically.
version
Single numerical value to separate older from newer versions. Defaults
to zero. Higher numbers mean "newer". Maybe someone manages to implement
a versioning system for custom media in the future...
New commands:
mdlattribute <attr> <value>
To be used in model config files. Adds a value for a specified
attribute to the model.
mapmodelregister <modelpath> <attr1> <attr2> ...
Adds values for all attributes to a specific mapmodel. This is used to
restore the cached values from config/mapmodelattributes.cfg during
game start. Does not actually verify of load the model.
mapmodelattributes <modelpath> <attr>
Returns the value of a specific attribute of a mapmodel. If modelpath
is a number, it is interpreted as index for the list of models in the
current map config. If no attribute name is specified, all attributes
are printed to the console instead.
getmapmodelattributes <list of attribute names>
Returns a table of all known mapmodels. Each model is listed with the
modelpath and the values for the requested attributes. To be used
instead of a fixed table in menu_edit.cfg. In addition to attribute
names, the function also allows the keywords "explodekeywords" and
"sortby:" as arguments. If "explodekeywords" is given, all mapmodels
with more than one keyword are listed multiple times, once for each
keyword. If an attribute name is preceeded by "sortby:" the list will
be sorted by this table column. If "sortby:" is used several times,
this first sort will have highest priority. The list is always sorted
by path by default.
example:
getmapmodelattributes sortby: author sortby: desc explodekeywords
loadallmapmodels
Tries to load mapmodels from /all/ paths below packages/models/mapmodels.
Will throw a lot of error messages, because not all directories contain
models to load. This command can be used to build or rebuild the
complete list of all available mapmodels. It is only necessary, if models
are added manually to the directories, since every mapmodel gets added
to the list when it is loaded - which takes care of all automatically
downloaded models.
DOCREF mdlattribute mapmodelregister mapmodelattributes
DOCREF getmapmodelattributes loadallmapmodels
This commit is contained in:
@@ -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
|
||||
|
||||
+1
-1
@@ -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) ]
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)); }
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
+173
-2
@@ -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<mapmodelinfo> 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<const char *, mapmodelattributes *> 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<const char *, model *> mdllookup;
|
||||
hashtable<const char *, char> 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 : " <no attributes set>");
|
||||
}
|
||||
}
|
||||
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<mapmodelattributes *> 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<const char *> opts;
|
||||
loopi(MMA_NUM) opts.add(mdlattrnames[i]);
|
||||
opts.add("sortby:"); opts.add("explodekeywords"); opts.add("");
|
||||
vector<int> 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<mapmodelattributes *> mmas, emmas;
|
||||
enumerate(mdlregistry, mapmodelattributes *, m,
|
||||
{
|
||||
if(explodekeywords && m->n[MMA_KEYWORDS] && listlen(m->n[MMA_KEYWORDS]) > 1)
|
||||
{
|
||||
vector<char *> 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<char> 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<char *> 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, "");
|
||||
|
||||
@@ -302,6 +302,25 @@ int listfiles(const char *dir, const char *ext, vector<char *> &files)
|
||||
return dirs;
|
||||
}
|
||||
|
||||
#ifndef STANDALONE
|
||||
void listfilesrecursive(const char *dir, vector<char *> &files, int level)
|
||||
{
|
||||
if(level > 8) return; // 8 levels is insane enough...
|
||||
vector<char *> 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);
|
||||
|
||||
@@ -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<char *> &files);
|
||||
extern int listfiles(const char *dir, const char *ext, vector<char *> &files);
|
||||
extern void listfilesrecursive(const char *dir, vector<char *> &files, int level = 0);
|
||||
extern int listzipfiles(const char *dir, const char *ext, vector<char *> &files);
|
||||
extern bool delfile(const char *path);
|
||||
extern void backup(char *name, char *backupname);
|
||||
|
||||
Reference in New Issue
Block a user