Files
assaultcube-/source/src/rendermodel.cpp
T
ac-stef cb630bde49 add per-map overrides to disable graphics features
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
2016-09-04 17:44:22 +02:00

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, "");