#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 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 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 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 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 mdllookup; hashtable 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 : " "); } } 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 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 opts; loopi(MMA_NUM) opts.add(mdlattrnames[i]); opts.add("sortby:"); opts.add("explodekeywords"); opts.add(""); vector 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 mmas, emmas; enumerate(mdlregistry, mapmodelattributes *, m, { if(explodekeywords && m->n[MMA_KEYWORDS] && listlen(m->n[MMA_KEYWORDS]) > 1) { vector 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 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 batched; }; static vector batches; static vector modelattached; static int numbatches = -1; void startmodelbatches() { numbatches = 0; modelattached.setsize(0); } batchedmodel &addbatchedmodel(model *m) { modelbatch *b = NULL; if(m->batch>=0 && m->batchbatch]->m==m) b = batches[m->batch]; else { if(numbatchesbatched.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 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 { 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)) { 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 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)) renderclientp(player1); } void loadallmapmodels() // try to find all mapmodels in packages/models/mapmodels { vector 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, "");