Skip to content
Permalink
Browse files

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
  • Loading branch information...
ac-stef committed Apr 20, 2015
1 parent c04da78 commit 03f974b2bc71be0e54bef2e69eac435c54a9a4f4
@@ -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
@@ -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);
@@ -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);

0 comments on commit 03f974b

Please sign in to comment.
You can’t perform that action at this time.