The mapper has now the choice to disable several graphics features on the client (for his map only, of course). The overrides are stored in the map header. mapoverride_nowaterreflect If "1", water reflection is disabled. Useful, when odd water colours are used to emulate other liquids that are not supposed to be that reflective. mapoverride_limitwaveheight If "1", waveheight is capped at 0.1 max. Useful for small water areas (like puddles), where higher waves would look weird. mapoverride_nostencilshadows If "1", stencil shadows are disabled. On clients, where stencil shadows are otherwise enabled, blob shadows are used instead. If shadows are disabled anyway, they stay disabled. Useful for dim maps, where hard shadows make no sense. The player is free to ignore any of those overrides: ignoreoverride_nowaterreflect If "1", mapoverride_nowaterreflect is ignored and regular client settings are used. ignoreoverride_limitwaveheight If "1", mapoverride_limitwaveheight is ignored, and "waveheight" is used unchanged. ignoreoverride_nostencilshadows If "1", mapoverride_nostencilshadows is ignored. DOCREF mapoverride_nowaterreflect ignoreoverride_nowaterreflect DOCREF mapoverride_limitwaveheight ignoreoverride_limitwaveheight DOCREF mapoverride_nostencilshadows ignoreoverride_nostencilshadows
1133 lines
35 KiB
C++
1133 lines
35 KiB
C++
#include "cube.h"
|
|
|
|
VARP(animationinterpolationtime, 0, 150, 1000);
|
|
|
|
model *loadingmodel = NULL;
|
|
mapmodelattributes loadingattributes;
|
|
|
|
#include "tristrip.h"
|
|
#include "modelcache.h"
|
|
#include "vertmodel.h"
|
|
#include "md2.h"
|
|
#include "md3.h"
|
|
|
|
#define checkmdl if(!loadingmodel) { conoutf("not loading a model"); flagmapconfigerror(LWW_MODELERR); return; }
|
|
|
|
void mdlcullface(int *cullface)
|
|
{
|
|
checkmdl;
|
|
loadingmodel->cullface = *cullface!=0;
|
|
}
|
|
|
|
COMMAND(mdlcullface, "i");
|
|
|
|
void mdlvertexlight(int *vertexlight)
|
|
{
|
|
checkmdl;
|
|
loadingmodel->vertexlight = *vertexlight!=0;
|
|
}
|
|
|
|
COMMAND(mdlvertexlight, "i");
|
|
|
|
void mdltranslucent(int *translucency)
|
|
{
|
|
checkmdl;
|
|
loadingmodel->translucency = *translucency/100.0f;
|
|
}
|
|
|
|
COMMAND(mdltranslucent, "i");
|
|
|
|
void mdlalphatest(int *alphatest)
|
|
{
|
|
checkmdl;
|
|
loadingmodel->alphatest = *alphatest/100.0f;
|
|
}
|
|
|
|
COMMAND(mdlalphatest, "i");
|
|
|
|
void mdlalphablend(int *alphablend) //ALX Alpha channel models
|
|
{
|
|
checkmdl;
|
|
loadingmodel->alphablend = *alphablend!=0;
|
|
}
|
|
COMMAND(mdlalphablend, "i");
|
|
|
|
void mdlscale(int *percent)
|
|
{
|
|
checkmdl;
|
|
float scale = 0.3f;
|
|
if(*percent>0) scale = *percent/100.0f;
|
|
else if(*percent<0) scale = 0.0f;
|
|
loadingmodel->scale = scale;
|
|
}
|
|
|
|
COMMAND(mdlscale, "i");
|
|
|
|
void mdltrans(float *x, float *y, float *z)
|
|
{
|
|
checkmdl;
|
|
loadingmodel->translate = vec(*x, *y, *z);
|
|
}
|
|
|
|
COMMAND(mdltrans, "fff");
|
|
|
|
void mdlshadowdist(int *dist)
|
|
{
|
|
checkmdl;
|
|
loadingmodel->shadowdist = *dist;
|
|
}
|
|
|
|
COMMAND(mdlshadowdist, "i");
|
|
|
|
void mdlcachelimit(int *limit)
|
|
{
|
|
checkmdl;
|
|
loadingmodel->cachelimit = *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/";
|
|
const char *mmshortname(const char *name) { return !strncmp(name, mmpath, strlen(mmpath)) ? name + strlen(mmpath) : name; }
|
|
|
|
void mapmodel(int *rad, int *h, int *zoff, char *scale, char *name)
|
|
{
|
|
if(*scale && *name) // ignore "mapmodel" commands with insufficient parameters
|
|
{
|
|
if(!strchr(name, '/') && !strchr(name, '\\')) flagmapconfigerror(LWW_CONFIGERR * 2); // throw errors for unconverted mapmodels (unspecific, because not all get detected)
|
|
intret(mapmodels.length());
|
|
mapmodelinfo &mmi = mapmodels.add();
|
|
mmi.rad = *rad;
|
|
mmi.h = *h;
|
|
mmi.zoff = *zoff;
|
|
mmi.scale = atof(scale);
|
|
if(mmi.scale < 0.25f || mmi.scale > 4.0f)
|
|
{
|
|
mmi.scale = 1.0f;
|
|
if(strcmp(scale, "0")) flagmapconfigerror(LWW_CONFIGERR * 2);
|
|
}
|
|
mmi.m = NULL;
|
|
|
|
filtertext(name, name, FTXT__MEDIAFILEPATH);
|
|
formatstring(mmi.name)("%s%s", mmpath, name);
|
|
mapmodelchanged = 1;
|
|
flagmapconfigchange();
|
|
}
|
|
else flagmapconfigerror(LWW_CONFIGERR * 2);
|
|
}
|
|
COMMAND(mapmodel, "iiiss");
|
|
|
|
void mapmodelreset()
|
|
{
|
|
if(execcontext==IEXC_MAPCFG)
|
|
{
|
|
mapmodels.shrink(0);
|
|
mapmodelchanged = 1;
|
|
}
|
|
}
|
|
COMMAND(mapmodelreset, "");
|
|
|
|
mapmodelinfo *getmminfo(int i) { return mapmodels.inrange(i) ? &mapmodels[i] : NULL; }
|
|
|
|
COMMANDF(mapmodelslotname, "i", (int *idx) { result(mapmodels.inrange(*idx) ? mmshortname(mapmodels[*idx].name) : ""); }); // returns the model name that is configured in a certain slot
|
|
COMMANDF(mapmodelslotbyname, "s", (char *name) // returns the slot(s) that a certain mapmodel is configured in
|
|
{
|
|
string res = "";
|
|
loopv(mapmodels) if(!strcmp(name, mapmodels[i].name)) concatformatstring(res, "%s%d", *res ? " " : "", i);
|
|
result(res);
|
|
});
|
|
|
|
void mapmodelslotusage(int *n) // returns all entity indices that contain a mapmodel of slot n
|
|
{
|
|
string res = "";
|
|
loopv(ents) if(ents[i].type == MAPMODEL && ents[i].attr2 == *n) concatformatstring(res, "%s%d", i ? " " : "", i);
|
|
result(res);
|
|
}
|
|
COMMAND(mapmodelslotusage, "i");
|
|
|
|
void deletemapmodelslot(int *n, char *opt) // delete mapmodel slot - only if unused or "purge" is specified
|
|
{
|
|
if(noteditmode("deletemapmodelslot") || multiplayer(true) || !mapmodels.inrange(*n)) return;
|
|
bool purgeall = !strcmp(opt, "purge");
|
|
if(!purgeall) loopv(ents) if(ents[i].type == MAPMODEL && ents[i].attr2 == *n) { conoutf("mapmodel slot #%d is in use: can't delete", *n); return; }
|
|
int deld = 0;
|
|
loopv(ents) if(ents[i].type == MAPMODEL)
|
|
{
|
|
entity &e = ents[i];
|
|
if(e.attr2 == *n)
|
|
{ // delete entity
|
|
deleted_ents.add(e);
|
|
memset(&e, 0, sizeof(persistent_entity));
|
|
e.type = NOTUSED;
|
|
deld++;
|
|
}
|
|
else if(e.attr2 > *n) e.attr2--; // adjust models in higher slots
|
|
}
|
|
mapmodels.remove(*n);
|
|
defformatstring(s)(" (%d mapmodels purged)", deld);
|
|
conoutf("mapmodel slot #%d deleted%s", *n, deld ? s : "");
|
|
unsavededits++;
|
|
mapmodelchanged = 1;
|
|
hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg
|
|
}
|
|
COMMAND(deletemapmodelslot, "is");
|
|
|
|
void editmapmodelslot(int *n, char *rad, char *h, char *zoff, char *scale, char *name) // edit slot parameters != ""
|
|
{
|
|
string res = "";
|
|
if(!noteditmode("editmapmodelslot") && !multiplayer(true) && mapmodels.inrange(*n))
|
|
{
|
|
mapmodelinfo &mmi = mapmodels[*n];
|
|
if(*rad || *h || *zoff || *scale || *name)
|
|
{ // change attributes
|
|
if(*rad) mmi.rad = strtol(rad, NULL, 0);
|
|
if(*h) mmi.h = strtol(h, NULL, 0);
|
|
if(*zoff) mmi.zoff = strtol(zoff, NULL, 0);
|
|
float s = atof(scale);
|
|
if(s < 0.25f || s > 4.0f) s = 1.0f;
|
|
if(*scale) mmi.scale = s;
|
|
mmi.m = NULL;
|
|
if(*name) formatstring(mmi.name)("%s%s", mmpath, name);
|
|
unsavededits++;
|
|
mapmodelchanged = 1;
|
|
hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg
|
|
}
|
|
formatstring(res)("%d %d %d %s \"%s\"", mmi.rad, mmi.h, mmi.zoff, floatstr(mmi.scale, true), mmshortname(mmi.name)); // give back all current attributes
|
|
}
|
|
result(res);
|
|
}
|
|
COMMAND(editmapmodelslot, "isssss");
|
|
|
|
struct tempmmslot { mapmodelinfo m; vector<int> oldslot; bool used; };
|
|
|
|
int tempmmcmp(tempmmslot *a, tempmmslot *b)
|
|
{
|
|
int n = strcmp(a->m.name, b->m.name);
|
|
if(n) return n;
|
|
if(a->m.rad != b->m.rad) return a->m.rad - b->m.rad;
|
|
if(a->m.h != b->m.h) return a->m.h - b->m.h;
|
|
if(a->m.zoff != b->m.zoff) return a->m.zoff - b->m.zoff;
|
|
if(a->m.scale != b->m.scale) return int((a->m.scale - b->m.scale) * 1e6);
|
|
return 0;
|
|
}
|
|
|
|
int tempmmsort(tempmmslot *a, tempmmslot *b)
|
|
{
|
|
int n = tempmmcmp(a, b);
|
|
return n ? n : a->oldslot[0] - b->oldslot[0];
|
|
}
|
|
|
|
int tempmmunsort(tempmmslot *a, tempmmslot *b)
|
|
{
|
|
return a->oldslot[0] - b->oldslot[0];
|
|
}
|
|
|
|
void sortmapmodelslots(char **args, int numargs)
|
|
{
|
|
bool nomerge = false, mergeused = false, nosort = false, unknownarg = false;
|
|
loopi(numargs) if(args[i][0])
|
|
{
|
|
if(!strcasecmp(args[i], "nomerge")) nomerge = true;
|
|
else if(!strcasecmp(args[i], "nosort")) nosort = true;
|
|
else if(!strcasecmp(args[i], "mergeused")) mergeused = true;
|
|
else { conoutf("sortmapmodelslots: unknown argument \"%s\"", args[i]); unknownarg = true; }
|
|
}
|
|
|
|
if(noteditmode("sortmapmodelslots") || multiplayer(true) || unknownarg || mapmodels.length() < 3) return;
|
|
|
|
vector<tempmmslot> tempslots;
|
|
loopv(mapmodels)
|
|
{
|
|
tempslots.add().m = mapmodels[i];
|
|
tempslots.last().oldslot.add(i);
|
|
tempslots.last().used = false;
|
|
}
|
|
loopv(ents) if(ents[i].type == MAPMODEL && tempslots.inrange(ents[i].attr2)) tempslots[ents[i].attr2].used = true;
|
|
tempslots.sort(tempmmsort);
|
|
|
|
// remove double entries
|
|
if(!nomerge) loopvrev(tempslots) if(i > 0)
|
|
{
|
|
tempmmslot &s1 = tempslots[i], &s0 = tempslots[i - 1];
|
|
if(!tempmmcmp(&s0, &s1) && (mergeused || !s0.used || !s1.used))
|
|
{
|
|
if(s1.used) s0.used = true;
|
|
loopvj(s1.oldslot) s0.oldslot.add(s1.oldslot[j]);
|
|
tempslots.remove(i);
|
|
}
|
|
}
|
|
if(nosort) tempslots.sort(tempmmunsort);
|
|
|
|
// create translation table
|
|
uchar newslot[256];
|
|
loopk(256) newslot[k] = k;
|
|
loopv(tempslots)
|
|
{
|
|
tempmmslot &t = tempslots[i];
|
|
loopvj(t.oldslot)
|
|
{
|
|
if(t.oldslot[j] < 256) newslot[t.oldslot[j]] = i;
|
|
}
|
|
}
|
|
|
|
// translate all mapmodel entities
|
|
loopv(ents) if(ents[i].type == MAPMODEL) ents[i].attr2 = newslot[ents[i].attr2];
|
|
|
|
conoutf("%d mapmodel slots%s, %d %sslots merged", tempslots.length(), nosort ? "" : " sorted", mapmodels.length() - tempslots.length(), mergeused ? "" : "unused ");
|
|
|
|
// rewrite mapmodel slot list
|
|
mapmodels.shrink(tempslots.length());
|
|
loopv(tempslots)
|
|
{
|
|
mapmodels[i] = tempslots[i].m;
|
|
mapmodels[i].m = NULL;
|
|
}
|
|
unsavededits++;
|
|
mapmodelchanged = 1;
|
|
hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg
|
|
}
|
|
COMMAND(sortmapmodelslots, "v");
|
|
|
|
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;
|
|
|
|
model *loadmodel(const char *name, int i, bool trydl) // load model by name (optional) or from mapmodels[i]
|
|
{
|
|
if(!name) // name == NULL -> get index i from mapmodels[]
|
|
{
|
|
if(!mapmodels.inrange(i)) return NULL;
|
|
mapmodelinfo &mmi = mapmodels[i];
|
|
if(mmi.m) return mmi.m; // mapmodels[i] was already loaded
|
|
name = mmi.name;
|
|
}
|
|
if(mdlnotfound.access(name)) return NULL; // already tried to find that earlier -> not available
|
|
model **mm = mdllookup.access(name);
|
|
model *m;
|
|
if(mm) m = *mm; // a model of that name is already loaded
|
|
else
|
|
{
|
|
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;
|
|
m = new md3(name); // try md3
|
|
loadingmodel = m;
|
|
if(!m->load()) // md3 didn't load -> we don't have that model
|
|
{
|
|
delete m;
|
|
m = loadingmodel = NULL;
|
|
if(trydl && !strncmp(name, mmpath, strlen(mmpath))) requirepackage(PCK_MAPMODEL, name);
|
|
else
|
|
{
|
|
conoutf("\f3failed to load model %s", name);
|
|
mdlnotfound.access(newstring(name), 0); // do not search for this name again
|
|
}
|
|
}
|
|
}
|
|
popscontext();
|
|
if(loadingmodel && m)
|
|
{
|
|
mdllookup.access(m->name(), m);
|
|
setmodelattributes(m->name(), loadingattributes);
|
|
if(m->shadowdist && !m->cullface)
|
|
{
|
|
conoutf("\f3mapmodel config error: disabling face culling in combination with shadows will cause visual errors (%s)", m->name());
|
|
flagmapconfigerror(LWW_MODELERR);
|
|
}
|
|
}
|
|
loadingmodel = NULL;
|
|
}
|
|
if(mapmodels.inrange(i) && !mapmodels[i].m) mapmodels[i].m = m;
|
|
return m;
|
|
}
|
|
|
|
void cleanupmodels()
|
|
{
|
|
enumerate(mdllookup, model *, m, m->cleanup());
|
|
}
|
|
|
|
void resetmdlnotfound()
|
|
{
|
|
enumeratek(mdlnotfound, const char *, m, delstring(m));
|
|
mdlnotfound.clear();
|
|
}
|
|
|
|
void getmapmodelattributes(char *name, char *attr)
|
|
{
|
|
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 : "");
|
|
}
|
|
COMMAND(getmapmodelattributes, "ss");
|
|
|
|
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");
|
|
if(f)
|
|
{
|
|
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 listallmapmodelattributes(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(listallmapmodelattributes, "v");
|
|
|
|
VARP(dynshadow, 0, 40, 100);
|
|
VARP(dynshadowdecay, 0, 1000, 3000);
|
|
|
|
struct batchedmodel
|
|
{
|
|
vec o;
|
|
int anim, varseed, tex;
|
|
float roll, yaw, pitch, speed;
|
|
int basetime;
|
|
playerent *d;
|
|
int attached;
|
|
float scale;
|
|
};
|
|
struct modelbatch
|
|
{
|
|
model *m;
|
|
vector<batchedmodel> batched;
|
|
};
|
|
static vector<modelbatch *> batches;
|
|
static vector<modelattach> modelattached;
|
|
static int numbatches = -1;
|
|
|
|
void startmodelbatches()
|
|
{
|
|
numbatches = 0;
|
|
modelattached.setsize(0);
|
|
}
|
|
|
|
batchedmodel &addbatchedmodel(model *m)
|
|
{
|
|
modelbatch *b = NULL;
|
|
if(m->batch>=0 && m->batch<numbatches && batches[m->batch]->m==m) b = batches[m->batch];
|
|
else
|
|
{
|
|
if(numbatches<batches.length())
|
|
{
|
|
b = batches[numbatches];
|
|
b->batched.setsize(0);
|
|
}
|
|
else b = batches.add(new modelbatch);
|
|
b->m = m;
|
|
m->batch = numbatches++;
|
|
}
|
|
return b->batched.add();
|
|
}
|
|
|
|
void renderbatchedmodel(model *m, batchedmodel &b)
|
|
{
|
|
modelattach *a = NULL;
|
|
if(b.attached>=0) a = &modelattached[b.attached];
|
|
|
|
if(stenciling)
|
|
{
|
|
m->render(b.anim|ANIM_NOSKIN, b.varseed, b.speed, b.basetime, b.o, b.roll, b.yaw, b.pitch, b.d, a, b.scale);
|
|
return;
|
|
}
|
|
|
|
int x = (int)b.o.x, y = (int)b.o.y;
|
|
if(!OUTBORD(x, y))
|
|
{
|
|
sqr *s = S(x, y);
|
|
glColor3ub(s->r, s->g, s->b);
|
|
}
|
|
else glColor3f(1, 1, 1);
|
|
|
|
m->setskin(b.tex);
|
|
|
|
if(b.anim&ANIM_TRANSLUCENT)
|
|
{
|
|
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
|
m->render(b.anim|ANIM_NOSKIN, b.varseed, b.speed, b.basetime, b.o, b.roll, b.yaw, b.pitch, b.d, a, b.scale);
|
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
|
|
glDepthFunc(GL_LEQUAL);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
GLfloat color[4];
|
|
glGetFloatv(GL_CURRENT_COLOR, color);
|
|
glColor4f(color[0], color[1], color[2], m->translucency);
|
|
}
|
|
|
|
m->render(b.anim, b.varseed, b.speed, b.basetime, b.o, b.roll, b.yaw, b.pitch, b.d, a, b.scale);
|
|
|
|
if(b.anim&ANIM_TRANSLUCENT)
|
|
{
|
|
glDepthFunc(GL_LESS);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
}
|
|
|
|
void renderbatchedmodelshadow(model *m, batchedmodel &b)
|
|
{
|
|
int x = (int)b.o.x, y = (int)b.o.y;
|
|
if(OUTBORD(x, y)) return;
|
|
sqr *s = S(x, y);
|
|
vec center(b.o.x, b.o.y, s->floor);
|
|
if(s->type==FHF) center.z -= s->vdelta/4.0f;
|
|
if(dynshadowquad && center.z-0.1f>b.o.z) return;
|
|
center.z += 0.1f;
|
|
modelattach *a = NULL;
|
|
if(b.attached>=0) a = &modelattached[b.attached];
|
|
float intensity = dynshadow/100.0f;
|
|
if(dynshadowdecay) switch(b.anim&ANIM_INDEX)
|
|
{
|
|
case ANIM_DECAY:
|
|
case ANIM_LYING_DEAD:
|
|
intensity *= max(1.0f - float(lastmillis - b.basetime)/dynshadowdecay, 0.0f);
|
|
break;
|
|
}
|
|
glColor4f(0, 0, 0, intensity);
|
|
m->rendershadow(b.anim, b.varseed, b.speed, b.basetime, dynshadowquad ? center : b.o, b.yaw, a);
|
|
}
|
|
|
|
static int sortbatchedmodels(const batchedmodel *x, const batchedmodel *y)
|
|
{
|
|
if(x->tex < y->tex) return -1;
|
|
if(x->tex > y->tex) return 1;
|
|
return 0;
|
|
}
|
|
|
|
struct translucentmodel
|
|
{
|
|
model *m;
|
|
batchedmodel *batched;
|
|
float dist;
|
|
};
|
|
|
|
static int sorttranslucentmodels(const translucentmodel *x, const translucentmodel *y)
|
|
{
|
|
if(x->dist > y->dist) return -1;
|
|
if(x->dist < y->dist) return 1;
|
|
return 0;
|
|
}
|
|
|
|
void clearmodelbatches()
|
|
{
|
|
numbatches = -1;
|
|
}
|
|
|
|
void endmodelbatches(bool flush)
|
|
{
|
|
vector<translucentmodel> translucent;
|
|
loopi(numbatches)
|
|
{
|
|
modelbatch &b = *batches[i];
|
|
if(b.batched.empty()) continue;
|
|
loopvj(b.batched) if(b.batched[j].tex) { b.batched.sort(sortbatchedmodels); break; }
|
|
b.m->startrender();
|
|
loopvj(b.batched)
|
|
{
|
|
batchedmodel &bm = b.batched[j];
|
|
if(bm.anim&ANIM_TRANSLUCENT)
|
|
{
|
|
translucentmodel &tm = translucent.add();
|
|
tm.m = b.m;
|
|
tm.batched = &bm;
|
|
tm.dist = camera1->o.dist(bm.o);
|
|
continue;
|
|
}
|
|
renderbatchedmodel(b.m, bm);
|
|
}
|
|
if(dynshadow && b.m->hasshadows() && (!reflecting || refracting) && (!effective_stencilshadow || !hasstencil || stencilbits < 8))
|
|
{
|
|
loopvj(b.batched)
|
|
{
|
|
batchedmodel &bm = b.batched[j];
|
|
if(bm.anim&ANIM_TRANSLUCENT) continue;
|
|
renderbatchedmodelshadow(b.m, bm);
|
|
}
|
|
}
|
|
b.m->endrender();
|
|
}
|
|
if(translucent.length())
|
|
{
|
|
translucent.sort(sorttranslucentmodels);
|
|
model *lastmodel = NULL;
|
|
loopv(translucent)
|
|
{
|
|
translucentmodel &tm = translucent[i];
|
|
if(lastmodel!=tm.m)
|
|
{
|
|
if(lastmodel) lastmodel->endrender();
|
|
(lastmodel = tm.m)->startrender();
|
|
}
|
|
renderbatchedmodel(tm.m, *tm.batched);
|
|
}
|
|
if(lastmodel) lastmodel->endrender();
|
|
}
|
|
if(flush) clearmodelbatches();
|
|
}
|
|
|
|
const int dbgmbatch = 0;
|
|
//VAR(dbgmbatch, 0, 0, 1);
|
|
|
|
VARP(popdeadplayers, 0, 0, 1);
|
|
void rendermodel(const char *mdl, int anim, int tex, float rad, const vec &o, float roll, float yaw, float pitch, float speed, int basetime, playerent *d, modelattach *a, float scale)
|
|
{
|
|
if(popdeadplayers && d && a)
|
|
{
|
|
int acv = anim&ANIM_INDEX;
|
|
if( acv == ANIM_DECAY || acv == ANIM_LYING_DEAD || acv == ANIM_CROUCH_DEATH || acv == ANIM_DEATH ) return;
|
|
}
|
|
model *m = loadmodel(mdl);
|
|
if(!m || (stenciling && (m->shadowdist <= 0 || anim&ANIM_TRANSLUCENT))) return;
|
|
|
|
if(rad >= 0)
|
|
{
|
|
if(!rad)
|
|
{
|
|
rad = m->radius;
|
|
if(roll != 0 || pitch != 0) rad = max(m->radius, m->zradius); // FIXME: this assumes worst-case even for small angles and should be eased up (especially for lamp posts)
|
|
rad *= scale;
|
|
}
|
|
if(isoccluded(camera1->o.x, camera1->o.y, o.x-rad, o.y-rad, rad*2)) return;
|
|
}
|
|
|
|
if(stenciling && d && !raycubelos(camera1->o, o, d->radius))
|
|
{
|
|
vec target(o);
|
|
target.z += d->eyeheight;
|
|
if(!raycubelos(camera1->o, target, d->radius)) return;
|
|
}
|
|
|
|
int varseed = 0;
|
|
if(d) switch(anim&ANIM_INDEX)
|
|
{
|
|
case ANIM_DEATH:
|
|
case ANIM_LYING_DEAD: varseed = (int)(size_t)d + d->lastpain; break;
|
|
default: varseed = (int)(size_t)d + d->lastaction; break;
|
|
}
|
|
|
|
if(a) for(int i = 0; a[i].tag; i++)
|
|
{
|
|
if(a[i].name) a[i].m = loadmodel(a[i].name);
|
|
//if(a[i].m && a[i].m->type()!=m->type()) a[i].m = NULL;
|
|
}
|
|
|
|
if(numbatches>=0 && !dbgmbatch)
|
|
{
|
|
batchedmodel &b = addbatchedmodel(m);
|
|
b.o = o;
|
|
b.anim = anim;
|
|
b.varseed = varseed;
|
|
b.tex = tex;
|
|
b.roll = roll;
|
|
b.yaw = yaw;
|
|
b.pitch = pitch;
|
|
b.speed = speed;
|
|
b.basetime = basetime;
|
|
b.d = d;
|
|
b.attached = a ? modelattached.length() : -1;
|
|
if(a) for(int i = 0;; i++) { modelattached.add(a[i]); if(!a[i].tag) break; }
|
|
b.scale = scale;
|
|
return;
|
|
}
|
|
|
|
if(stenciling)
|
|
{
|
|
m->startrender();
|
|
m->render(anim|ANIM_NOSKIN, varseed, speed, basetime, o, 0, yaw, pitch, d, a, scale);
|
|
m->endrender();
|
|
return;
|
|
}
|
|
|
|
m->startrender();
|
|
|
|
int x = (int)o.x, y = (int)o.y;
|
|
if(!OUTBORD(x, y))
|
|
{
|
|
sqr *s = S(x, y);
|
|
if(!(anim&ANIM_TRANSLUCENT) && dynshadow && m->hasshadows() && (!reflecting || refracting) && (!effective_stencilshadow || !hasstencil || stencilbits < 8))
|
|
{
|
|
vec center(o.x, o.y, s->floor);
|
|
if(s->type==FHF) center.z -= s->vdelta/4.0f;
|
|
if(!dynshadowquad || center.z-0.1f<=o.z)
|
|
{
|
|
center.z += 0.1f;
|
|
float intensity = dynshadow/100.0f;
|
|
if(dynshadowdecay) switch(anim&ANIM_INDEX)
|
|
{
|
|
case ANIM_DECAY:
|
|
case ANIM_LYING_DEAD:
|
|
intensity *= max(1.0f - float(lastmillis - basetime)/dynshadowdecay, 0.0f);
|
|
break;
|
|
}
|
|
glColor4f(0, 0, 0, intensity);
|
|
m->rendershadow(anim, varseed, speed, basetime, dynshadowquad ? center : o, yaw, a);
|
|
}
|
|
}
|
|
glColor3ub(s->r, s->g, s->b);
|
|
}
|
|
else glColor3f(1, 1, 1);
|
|
|
|
m->setskin(tex);
|
|
|
|
if(anim&ANIM_TRANSLUCENT)
|
|
{
|
|
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
|
m->render(anim|ANIM_NOSKIN, varseed, speed, basetime, o, 0, yaw, pitch, d, a, scale);
|
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
|
|
glDepthFunc(GL_LEQUAL);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
GLfloat color[4];
|
|
glGetFloatv(GL_CURRENT_COLOR, color);
|
|
glColor4f(color[0], color[1], color[2], m->translucency);
|
|
}
|
|
|
|
m->render(anim, varseed, speed, basetime, o, 0, yaw, pitch, d, a, scale);
|
|
|
|
if(anim&ANIM_TRANSLUCENT)
|
|
{
|
|
glDepthFunc(GL_LESS);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
m->endrender();
|
|
}
|
|
|
|
int findanim(const char *name)
|
|
{
|
|
const char *names[] = { "idle", "run", "attack", "pain", "jump", "land", "flipoff", "salute", "taunt", "wave", "point", "crouch idle", "crouch walk", "crouch attack", "crouch pain", "crouch death", "death", "lying dead", "flag", "gun idle", "gun shoot", "gun reload", "gun throw", "mapmodel", "trigger", "decay", "all" };
|
|
loopi(sizeof(names)/sizeof(names[0])) if(!strcmp(name, names[i])) return i;
|
|
return -1;
|
|
}
|
|
|
|
void loadskin(const char *dir, const char *altdir, Texture *&skin) // model skin sharing
|
|
{
|
|
#define ifnoload if((skin = textureload(path))==notexture)
|
|
defformatstring(path)("packages/models/%s/skin.jpg", dir);
|
|
ifnoload
|
|
{
|
|
strcpy(path+strlen(path)-3, "png");
|
|
ifnoload
|
|
{
|
|
formatstring(path)("packages/models/%s/skin.jpg", altdir);
|
|
ifnoload
|
|
{
|
|
strcpy(path+strlen(path)-3, "png");
|
|
ifnoload return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void preload_playermodels()
|
|
{
|
|
model *playermdl = loadmodel("playermodels");
|
|
if(dynshadow && playermdl) playermdl->genshadows(8.0f, 4.0f);
|
|
loopi(NUMGUNS)
|
|
{
|
|
if (i==GUN_CPISTOL) continue; //RR 18/12/12 - Remove when cpistol is added.
|
|
defformatstring(widn)("modmdlvwep%d", i);
|
|
defformatstring(vwep)("weapons/%s/world", identexists(widn)?getalias(widn):guns[i].modelname);
|
|
model *vwepmdl = loadmodel(vwep);
|
|
if(dynshadow && vwepmdl) vwepmdl->genshadows(8.0f, 4.0f);
|
|
}
|
|
}
|
|
|
|
void preload_entmodels()
|
|
{
|
|
string buf;
|
|
|
|
extern const char *entmdlnames[];
|
|
loopi(I_AKIMBO-I_CLIPS+1)
|
|
{
|
|
strcpy(buf, "pickups/");
|
|
|
|
defformatstring(widn)("modmdlpickup%d", i-3);
|
|
|
|
if (identexists(widn))
|
|
concatstring(buf, getalias(widn));
|
|
else
|
|
concatstring(buf, entmdlnames[i]);
|
|
|
|
model *mdl = loadmodel(buf);
|
|
|
|
if(dynshadow && mdl) mdl->genshadows(8.0f, 2.0f);
|
|
}
|
|
static const char *bouncemdlnames[] = { "misc/gib01", "misc/gib02", "misc/gib03", "weapons/grenade/static" };
|
|
loopi(sizeof(bouncemdlnames)/sizeof(bouncemdlnames[0]))
|
|
{
|
|
model *mdl = NULL;
|
|
defformatstring(widn)("modmdlbounce%d", i);
|
|
|
|
if (identexists(widn))
|
|
mdl = loadmodel(getalias(widn));
|
|
else
|
|
mdl = loadmodel(bouncemdlnames[i]);
|
|
|
|
if(dynshadow && mdl) mdl->genshadows(8.0f, 2.0f);
|
|
}
|
|
}
|
|
|
|
bool preload_mapmodels(bool trydl)
|
|
{
|
|
int missing = 0;
|
|
loopv(ents)
|
|
{
|
|
entity &e = ents[i];
|
|
if(e.type!=MAPMODEL || !mapmodels.inrange(e.attr2)) continue;
|
|
if(!loadmodel(NULL, e.attr2, trydl)) missing++;
|
|
if(e.attr4 && lookuptexture(e.attr4, notexture, trydl) == notexture) missing++;
|
|
}
|
|
return !missing;
|
|
}
|
|
|
|
inline void renderhboxpart(playerent *d, vec top, vec bottom, vec up)
|
|
{
|
|
if(d->state==CS_ALIVE && d->head.x >= 0)
|
|
{
|
|
glBegin(GL_LINE_LOOP);
|
|
loopi(8)
|
|
{
|
|
vec pos(camright);
|
|
pos.rotate(2*M_PI*i/8.0f, camdir).mul(HEADSIZE).add(d->head);
|
|
glVertex3fv(pos.v);
|
|
}
|
|
glEnd();
|
|
|
|
glBegin(GL_LINES);
|
|
glVertex3fv(bottom.v);
|
|
glVertex3fv(d->head.v);
|
|
glEnd();
|
|
}
|
|
|
|
vec spoke;
|
|
spoke.orthogonal(up);
|
|
spoke.normalize().mul(d->radius);
|
|
|
|
glBegin(GL_LINE_LOOP);
|
|
loopi(8)
|
|
{
|
|
vec pos(spoke);
|
|
pos.rotate(2*M_PI*i/8.0f, up).add(top);
|
|
glVertex3fv(pos.v);
|
|
}
|
|
glEnd();
|
|
glBegin(GL_LINE_LOOP);
|
|
loopi(8)
|
|
{
|
|
vec pos(spoke);
|
|
pos.rotate(2*M_PI*i/8.0f, up).add(bottom);
|
|
glVertex3fv(pos.v);
|
|
}
|
|
glEnd();
|
|
glBegin(GL_LINES);
|
|
loopi(8)
|
|
{
|
|
vec pos(spoke);
|
|
pos.rotate(2*M_PI*i/8.0f, up).add(bottom);
|
|
glVertex3fv(pos.v);
|
|
pos.sub(bottom).add(top);
|
|
glVertex3fv(pos.v);
|
|
}
|
|
glEnd();
|
|
}
|
|
|
|
void renderclient(playerent *d, const char *mdlname, const char *vwepname, int tex)
|
|
{
|
|
int varseed = (int)(size_t)d;
|
|
int anim = ANIM_IDLE|ANIM_LOOP;
|
|
float speed = 0.0;
|
|
vec o(d->o);
|
|
o.z -= d->eyeheight;
|
|
int basetime = -((int)(size_t)d&0xFFF);
|
|
if(d->state==CS_DEAD)
|
|
{
|
|
if(d==player1 && d->allowmove()) return;
|
|
loopv(bounceents) if(bounceents[i]->bouncetype==BT_GIB && bounceents[i]->owner==d) return;
|
|
d->pitch = 0.1f;
|
|
anim = ANIM_DEATH;
|
|
varseed += d->lastpain;
|
|
basetime = d->lastpain;
|
|
int t = lastmillis-d->lastpain;
|
|
if(t<0 || t>20000) return;
|
|
if(t>2000)
|
|
{
|
|
anim = ANIM_LYING_DEAD|ANIM_NOINTERP|ANIM_LOOP;
|
|
basetime += 2000;
|
|
t -= 2000;
|
|
o.z -= t*t/10000000000.0f*t;
|
|
}
|
|
}
|
|
else if(d->state==CS_EDITING) { anim = ANIM_JUMP|ANIM_END; }
|
|
else if(d->state==CS_LAGGED) { anim = ANIM_SALUTE|ANIM_LOOP|ANIM_TRANSLUCENT; }
|
|
else if(lastmillis-d->lastpain<300) { anim = d->crouching ? ANIM_CROUCH_PAIN : ANIM_PAIN; speed = 300.0f/4; varseed += d->lastpain; basetime = d->lastpain; }
|
|
// else if(!d->onfloor && d->timeinair>50) { anim = ANIM_JUMP|ANIM_END; }
|
|
else if(!d->onfloor && d->timeinair>50) { anim = (d->crouching ? ANIM_CROUCH_WALK : ANIM_JUMP)|ANIM_END; }
|
|
else if(d->weaponsel==d->lastattackweapon && lastmillis-d->lastaction<300 && d->lastpain < d->lastaction) { anim = d->crouching ? ANIM_CROUCH_ATTACK : ANIM_ATTACK; speed = 300.0f/8; basetime = d->lastaction; }
|
|
else if(!d->move && !d->strafe) { anim = (d->crouching ? ANIM_CROUCH_IDLE : ANIM_IDLE)|ANIM_LOOP; }
|
|
else { anim = (d->crouching ? ANIM_CROUCH_WALK : ANIM_RUN)|ANIM_LOOP; speed = 1860/d->maxspeed; }
|
|
if(d->move < 0) anim |= ANIM_REVERSE;
|
|
modelattach a[3];
|
|
int numattach = 0;
|
|
if(vwepname)
|
|
{
|
|
a[numattach].name = vwepname;
|
|
a[numattach].tag = "tag_weapon";
|
|
numattach++;
|
|
}
|
|
|
|
if(!stenciling && !reflecting && !refracting)
|
|
{
|
|
if(d->weaponsel==d->lastattackweapon && lastmillis-d->lastaction < d->weaponsel->flashtime())
|
|
anim |= ANIM_PARTICLE;
|
|
if(d != player1 && d->state==CS_ALIVE)
|
|
{
|
|
d->head = vec(-1, -1, -1);
|
|
a[numattach].tag = "tag_head";
|
|
a[numattach].pos = &d->head;
|
|
numattach++;
|
|
}
|
|
}
|
|
if(player1->isspectating() && d->clientnum == player1->followplayercn && player1->spectatemode == SM_FOLLOW3RD_TRANSPARENT)
|
|
{
|
|
anim |= ANIM_TRANSLUCENT; // see through followed player
|
|
if(stenciling) return;
|
|
}
|
|
rendermodel(mdlname, anim|ANIM_DYNALLOC, tex, 1.5f, o, 0, d->yaw+90, d->pitch/4, speed, basetime, d, a);
|
|
if(!stenciling && !reflecting && !refracting)
|
|
{
|
|
if(isteam(player1->team, d->team)) renderaboveheadicon(d);
|
|
}
|
|
}
|
|
|
|
VARP(teamdisplaymode, 0, 1, 2);
|
|
|
|
#define SKINBASE "packages/models/playermodels"
|
|
VARP(hidecustomskins, 0, 0, 2);
|
|
static vector<char *> playerskinlist;
|
|
|
|
const char *getclientskin(const char *name, const char *suf)
|
|
{
|
|
static string tmp;
|
|
int suflen = (int)strlen(suf), namelen = (int)strlen(name);
|
|
const char *s, *r = NULL;
|
|
loopv(playerskinlist)
|
|
{
|
|
s = playerskinlist[i];
|
|
int sl = (int)strlen(s) - suflen;
|
|
if(sl > 0 && !strcmp(s + sl, suf))
|
|
{
|
|
if(namelen == sl && !strncmp(name, s, namelen)) return s; // exact match
|
|
if(s[sl - 1] == '_')
|
|
{
|
|
copystring(tmp, s);
|
|
tmp[sl - 1] = '\0';
|
|
if(strstr(name, tmp)) r = s; // partial match
|
|
}
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void updateclientname(playerent *d)
|
|
{
|
|
static bool gotlist = false;
|
|
if(!gotlist) listfiles(SKINBASE "/custom", "jpg", playerskinlist);
|
|
gotlist = true;
|
|
if(!d || !playerskinlist.length()) return;
|
|
d->skin_noteam = getclientskin(d->name, "_ffa");
|
|
d->skin_cla = getclientskin(d->name, "_cla");
|
|
d->skin_rvsf = getclientskin(d->name, "_rvsf");
|
|
}
|
|
|
|
void renderclient(playerent *d)
|
|
{
|
|
if(!d) return;
|
|
int team = team_base(d->team);
|
|
const char *cs = NULL, *skinbase = SKINBASE, *teamname = team_basestring(team);
|
|
int skinid = 1 + d->skin();
|
|
string skin;
|
|
if(hidecustomskins == 0 || (hidecustomskins == 1 && !m_teammode))
|
|
{
|
|
cs = team ? d->skin_rvsf : d->skin_cla;
|
|
if(!m_teammode && d->skin_noteam) cs = d->skin_noteam;
|
|
}
|
|
if(cs)
|
|
formatstring(skin)("%s/custom/%s.jpg", skinbase, cs);
|
|
else
|
|
{
|
|
if(!m_teammode || !teamdisplaymode) formatstring(skin)("%s/%s/%02i.jpg", skinbase, teamname, skinid);
|
|
else switch(teamdisplaymode)
|
|
{
|
|
case 1: formatstring(skin)("%s/%s/%02i_%svest.jpg", skinbase, teamname, skinid, team ? "blue" : "red"); break;
|
|
case 2: default: formatstring(skin)("%s/%s/%s.jpg", skinbase, teamname, team ? "blue" : "red"); break;
|
|
}
|
|
}
|
|
string vwep;
|
|
defformatstring(widn)("modmdlvwep%d", d->weaponsel->type);
|
|
if(d->weaponsel) formatstring(vwep)("weapons/%s/world", identexists(widn)?getalias(widn):d->weaponsel->info.modelname);
|
|
else vwep[0] = 0;
|
|
renderclient(d, "playermodels", vwep[0] ? vwep : NULL, -(int)textureload(skin)->id);
|
|
}
|
|
|
|
void renderclients()
|
|
{
|
|
playerent *d;
|
|
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, "");
|