Files
assaultcube-/source/src/rendermodel.cpp
T
aurhatanddrian b8b0eb96bd Hide spectator reflection and fix moving in 3rd person perspective
Earlier in third person perspective:
1. reflection of the spectator (player1) was visible on the water (at least
after full cycle of spectate mode switching);
2. it was possible to move position of the invisible spectator's (player1)
ghost, although he should be attached to spectated player. Bug was visible
on the water (the reflection moved) and in some cases on minimap - when
player executed "spectate" and later "spectatemode 1", then camera stayed
in the same position, but player position changed.

Thanks BrutalSystem
2021-02-15 05:29:36 +01:00

1111 lines
36 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); scripterr(); 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", "requires", "" };
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, char *flags)
{
if(*scale && *name) // ignore "mapmodel" commands with insufficient parameters
{
if(!_ignoreillegalpaths && !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);
mmi.flags = 0;
if(*flags) mmi.flags = ATOI(flags);
if(mmi.scale < 0.25f || mmi.scale > 4.0f)
{
mmi.scale = 1.0f;
if(strcmp(scale, "0")) { flagmapconfigerror(LWW_CONFIGERR * 2); scripterr(); }
}
mmi.m = NULL;
filtertext(name, name, FTXT__MEDIAFILEPATH);
formatstring(mmi.name)("%s%s", mmpath, name[0] == '.' && name[1] == '/' ? name + 2 : name);
mapmodelchanged = 1;
flagmapconfigchange();
}
else { flagmapconfigerror(LWW_CONFIGERR * 2); scripterr(); }
}
COMMAND(mapmodel, "iiisss");
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 ? res : (mapmodels.inrange(*n) && (mapmodels[*n].flags & MMF_REQUIRED) ? " " : res));
}
COMMAND(mapmodelslotusage, "i");
void deletemapmodelslot(int *n, char *opt) // delete mapmodel slot - only if unused or "purge" is specified
{
EDITMP("deletemapmodelslot");
if(!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(mapmodels.inrange(*n))
{
mapmodelinfo &mmi = mapmodels[*n];
if((*rad || *h || *zoff || *scale || *name) && !noteditmode("editmapmodelslot") && !multiplayer("editmapmodelslot"))
{ // 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; }
}
EDITMP("sortmapmodelslots");
if(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");
void mapmodelregistryclear()
{
enumerate(mdlregistry, mapmodelattributes *, m,
loopi(MMA_NUM) DELSTRING(m->n[i]);
DELETEP(m);
);
mdlregistry.clear();
mapmodelchanged = 1;
}
COMMAND(mapmodelregistryclear, "");
hashtable<const char *, model *> mdllookup;
hashtable<const char *, char> mdlnotfound;
bool silentmodelloaderror = false;
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
{
if(!silentmodelloaderror) 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");
void updatemapmodeldependencies()
{
loopv(mapmodels) mapmodels[i].flags = 0;
loopv(ents) if(ents[i].type == MAPMODEL && mapmodels.inrange(ents[i].attr2)) mapmodels[ents[i].attr2].flags |= MMF_TEMP_USED;
bool foundone = true;
while(foundone)
{
foundone = false;
loopvk(mapmodels) if(mapmodels[k].flags & MMF_TEMP_USED)
{
const char *name = mmshortname(mapmodels[k].name);
mapmodelattributes **ap = mdlregistry.access(name), *a = ap ? *ap : NULL;
const char *dep = a ? a->n[MMA_REQUIRES] : NULL;
if(dep && *dep)
{
bool found = false;
loopv(mapmodels) if(!strcmp(mmshortname(mapmodels[i].name), dep))
{
if(!(mapmodels[i].flags & MMF_TEMP_USED)) foundone = true;
mapmodels[i].flags |= MMF_REQUIRED | MMF_TEMP_USED;
found = true;
}
if(!found)
{
defformatstring(cmd)("mapmodel 0 0 0 0 \"%s\" %d", dep, MMF_REQUIRED);
execute(cmd);
foundone = true;
clientlogf(" mapmodel %s added to config, because %s requires it", dep, name);
}
}
}
}
loopv(mapmodels) mapmodels[i].flags &= MMF_CONFIGMASK;
}
COMMAND(updatemapmodeldependencies, "");
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');
}
loopv(emmas) { delstring(emmas[i]->n[MMA_KEYWORDS]); delete emmas[i]; } // only delete what was exploded
resultcharvector(res, -1);
}
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();
}
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)
{
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, roll, 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, roll, 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
{
bool old_silent_texture_load = silent_texture_load;
silent_texture_load = true;
#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 {};
}
}
}
silent_texture_load = old_silent_texture_load;
}
void preload_playermodels()
{
model *playermdl = loadmodel("playermodels");
if(dynshadow && playermdl) playermdl->genshadows(8.0f, 4.0f);
loopi(NUMGUNS)
{
defformatstring(vwep)("weapons/%s/world", guns[i].modelname);
model *vwepmdl = loadmodel(vwep);
if(dynshadow && vwepmdl) vwepmdl->genshadows(8.0f, 4.0f);
}
}
void preload_entmodels()
{
extern const char *entmdlnames[];
for(int i = 0; entmdlnames[i][0]; i++)
{
model *mdl = loadmodel(entmdlnames[i]);
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 = loadmodel(bouncemdlnames[i]);
if(dynshadow && mdl) mdl->genshadows(8.0f, 2.0f);
}
}
bool preload_mapmodels(bool trydl)
{
int missing = 0;
loopv(mapmodels) mapmodels[i].flags &= ~MMF_TEMP_USED;
loopv(ents)
{
entity &e = ents[i];
if(e.type!=MAPMODEL || !mapmodels.inrange(e.attr2)) continue;
mapmodels[ents[i].attr2].flags |= MMF_TEMP_USED;
if(e.attr4 && lookuptexture(e.attr4, notexture, trydl) == notexture && (!_ignoreillegalpaths || gettextureslot(e.attr4))) missing++;
}
loopv(mapmodels) if((mapmodels[i].flags & (MMF_REQUIRED | MMF_TEMP_USED)) && !loadmodel(NULL, i, trydl)) missing++;
return !missing;
}
void renderclient(playerent *d, const char *mdlname, const char *vwepname, int tex)
{
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;
if(d->nocorpse) return;
d->pitch = 0.1f;
anim = ANIM_DEATH;
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; basetime = d->lastpain; }
else if(d->weaponsel==d->lastattackweapon && lastmillis-d->lastaction<300 && d->lastpain<d->lastaction && !d->weaponsel->reloading && (d->weaponsel->type!=GUN_GRENADE || ((grenades *)d->weaponsel)->cookingmillis>0))
{ anim = d->crouching ? ANIM_CROUCH_ATTACK : ANIM_ATTACK; speed = 300.0f/8; basetime = d->lastaction; }
else if(!d->onfloor && d->timeinair>50) { anim = (d->crouching ? ANIM_CROUCH_WALK : ANIM_JUMP)|ANIM_END; }
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 renderclientp(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/%02d.jpg", skinbase, teamname, skinid);
else switch(teamdisplaymode)
{
case 1: formatstring(skin)("%s/%s/%02d_%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;
if(d->weaponsel) formatstring(vwep)("weapons/%s/world", 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)) renderclientp(d);
if(player1->state==CS_DEAD || ((reflecting && !refracting) && (player1->state!=CS_SPECTATE || player1->spectatemode==SM_FLY))) renderclientp(player1);
}
void loadallmapmodels() // try to find all mapmodels in packages/models/mapmodels
{
vector<char *> files;
listdirsrecursive("packages/models/mapmodels", files);
files.sort(stringsort);
loopvrev(files) if(files.inrange(i + 1) && !strcmp(files[i], files[i + 1])) delstring(files.remove(i + 1)); // remove doubles
silentmodelloaderror = true;
loopv(files)
{
const char *mn = files[i] + strlen("packages/models/");
clientlogf("loadallmaps: %s %s", mn, loadmodel(mn) ? "loaded" : "failed to load");
delstring(files[i]);
}
silentmodelloaderror = false;
}
COMMAND(loadallmapmodels, "");